From 4df334cec4699118c4822fa3e9a856d3c4bac5b7 Mon Sep 17 00:00:00 2001 From: Tobias Taschner Date: Mon, 9 Nov 2020 12:44:47 +0000 Subject: [PATCH] macOS/iOS: Use WKWebView for wxWebView Implementation WKWebView replaces WebView on macOS (available since 10.10, deprecated since 10.14) and UIWebView on iOS (available since 8.0 deprecated since 10.0). Using WKWebView ensures future compatiblity and unifies the interface used for iOS and macOS. --- include/wx/defs.h | 8 +- include/wx/osx/webview_webkit.h | 47 +- interface/wx/webview.h | 10 +- src/osx/webview_webkit.mm | 784 ++++++++++++-------------------- 4 files changed, 320 insertions(+), 529 deletions(-) diff --git a/include/wx/defs.h b/include/wx/defs.h index 101a016727..f716e0f558 100644 --- a/include/wx/defs.h +++ b/include/wx/defs.h @@ -2789,6 +2789,7 @@ DECLARE_WXCOCOA_OBJC_CLASS(NSRotationGestureRecognizer); DECLARE_WXCOCOA_OBJC_CLASS(NSPressGestureRecognizer); DECLARE_WXCOCOA_OBJC_CLASS(NSTouch); DECLARE_WXCOCOA_OBJC_CLASS(NSPasteboard); +DECLARE_WXCOCOA_OBJC_CLASS(WKWebView); typedef WX_NSWindow WXWindow; typedef WX_NSView WXWidget; @@ -2797,6 +2798,7 @@ typedef WX_NSMenu WXHMENU; typedef WX_NSOpenGLPixelFormat WXGLPixelFormat; typedef WX_NSOpenGLContext WXGLContext; typedef WX_NSPasteboard OSXPasteboard; +typedef WX_WKWebView OSXWebViewPtr; #elif wxOSX_USE_IPHONE @@ -2810,7 +2812,6 @@ DECLARE_WXCOCOA_OBJC_CLASS(UIImage); DECLARE_WXCOCOA_OBJC_CLASS(UIEvent); DECLARE_WXCOCOA_OBJC_CLASS(NSSet); DECLARE_WXCOCOA_OBJC_CLASS(EAGLContext); -DECLARE_WXCOCOA_OBJC_CLASS(UIWebView); DECLARE_WXCOCOA_OBJC_CLASS(UIPasteboard); typedef WX_UIWindow WXWindow; @@ -2819,15 +2820,10 @@ typedef WX_UIImage WXImage; typedef WX_UIMenu WXHMENU; typedef WX_EAGLContext WXGLContext; typedef WX_NSString WXGLPixelFormat; -typedef WX_UIWebView OSXWebViewPtr; typedef WX_UIPasteboard WXOSXPasteboard; #endif -#if wxOSX_USE_COCOA_OR_CARBON -DECLARE_WXCOCOA_OBJC_CLASS(WebView); -typedef WX_WebView OSXWebViewPtr; -#endif #endif /* __WXMAC__ */ diff --git a/include/wx/osx/webview_webkit.h b/include/wx/osx/webview_webkit.h index d7c8a91e5f..2cbf36f489 100644 --- a/include/wx/osx/webview_webkit.h +++ b/include/wx/osx/webview_webkit.h @@ -25,6 +25,8 @@ // Web Kit Control // ---------------------------------------------------------------------------- +WX_DECLARE_STRING_HASH_MAP(wxSharedPtr, wxStringToWebHandlerMap); + class WXDLLIMPEXP_WEBVIEW wxWebViewWebKit : public wxWebView { public: @@ -71,7 +73,7 @@ public: virtual wxWebViewZoomType GetZoomType() const wxOVERRIDE; virtual bool CanSetZoomType(wxWebViewZoomType type) const wxOVERRIDE; - virtual bool IsBusy() const wxOVERRIDE { return m_busy; } + virtual bool IsBusy() const wxOVERRIDE; //History functions virtual void ClearHistory() wxOVERRIDE; @@ -121,54 +123,21 @@ public: virtual void* GetNativeBackend() const wxOVERRIDE { return m_webView; } - // ---- methods not from the parent (common) interface - bool CanGetPageSource() const; - - void SetScrollPos(int pos); - int GetScrollPos(); - - bool CanIncreaseTextSize() const; - void IncreaseTextSize(); - bool CanDecreaseTextSize() const; - void DecreaseTextSize(); - - float GetWebkitZoom() const; - void SetWebkitZoom(float zoom); - - // don't hide base class virtuals - virtual void SetScrollPos( int orient, int pos, bool refresh = true ) wxOVERRIDE - { return wxControl::SetScrollPos(orient, pos, refresh); } - virtual int GetScrollPos( int orient ) const wxOVERRIDE - { return wxControl::GetScrollPos(orient); } - - //we need to resize the webview when the control size changes - void OnSize(wxSizeEvent &event); - void OnMove(wxMoveEvent &event); - void OnMouseEvents(wxMouseEvent &event); - - bool m_busy; - bool m_nextNavigationIsNewWindow; - protected: virtual void DoSetPage(const wxString& html, const wxString& baseUrl) wxOVERRIDE; wxDECLARE_EVENT_TABLE(); - void MacVisibilityChanged() wxOVERRIDE; private: - wxWindow *m_parent; - wxWindowID m_windowID; - wxString m_pageTitle; - OSXWebViewPtr m_webView; + wxStringToWebHandlerMap m_handlers; - WX_NSObject m_loadDelegate; - WX_NSObject m_policyDelegate; + WX_NSObject m_navigationDelegate; WX_NSObject m_UIDelegate; - // we may use this later to setup our own mouse events, - // so leave it in for now. - void* m_webKitCtrlEventHandler; + bool RunScriptSync(const wxString& javascript, wxString* output = NULL); + bool QueryCommandEnabled(const wxString& command) const; + void ExecCommand(const wxString& command); }; class WXDLLIMPEXP_WEBVIEW wxWebViewFactoryWebKit : public wxWebViewFactory diff --git a/interface/wx/webview.h b/interface/wx/webview.h index a1d1ce04e3..23ab193a81 100644 --- a/interface/wx/webview.h +++ b/interface/wx/webview.h @@ -347,12 +347,13 @@ public: and under Fedora it is webkitgtk4-devel. All wxWEBVIEW_WEBKIT features are supported except for clearing and enabling / disabling the history. - @par wxWEBVIEW_WEBKIT (OSX) + @par wxWEBVIEW_WEBKIT (macOS) The macOS WebKit backend uses Apple's - WebView + WKWebView class. This backend has full support for custom schemes and virtual file - systems. + systems on macOS 10.13+. In order to use handlers two-step creation has to be used + and RegisterHandler() has to be called before Create(). @section async Asynchronous Notifications @@ -565,6 +566,9 @@ public: /** Registers a custom scheme handler. @param handler A shared pointer to a wxWebHandler. + @note On macOS in order to use handlers two-step creation has to be + used and RegisterHandler() has to be called before Create(). + With the other backends it has to be called after Create(). */ virtual void RegisterHandler(wxSharedPtr handler) = 0; diff --git a/src/osx/webview_webkit.mm b/src/osx/webview_webkit.mm index 77bfc3dc9e..9a48ed0bf9 100644 --- a/src/osx/webview_webkit.mm +++ b/src/osx/webview_webkit.mm @@ -9,11 +9,11 @@ // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// -// http://developer.apple.com/mac/library/documentation/Cocoa/Reference/WebKit/Classes/WebView_Class/Reference/Reference.html +// https://developer.apple.com/documentation/webkit/wkwebview #include "wx/osx/webview_webkit.h" -#if wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT && defined(__WXOSX__) +#if wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT && defined(__WXOSX__) // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" @@ -24,20 +24,13 @@ #include "wx/osx/private.h" #include "wx/osx/core/cfref.h" +#include "wx/osx/private/available.h" #include "wx/private/jsscriptwrapper.h" #include "wx/hashmap.h" #include "wx/filesys.h" -#if wxOSX_USE_IPHONE -#include -#else #include -#if __MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_15 - #include - #include -#endif -#endif #include // using native types to get compile errors and warnings @@ -53,7 +46,16 @@ wxIMPLEMENT_DYNAMIC_CLASS(wxWebViewWebKit, wxWebView); wxBEGIN_EVENT_TABLE(wxWebViewWebKit, wxControl) wxEND_EVENT_TABLE() -@interface WebViewLoadDelegate : NSObject +@interface WXWKWebView: WKWebView +{ + wxWebViewWebKit* m_webView; +} + +- (void)setWebView:(wxWebViewWebKit*)webView; + +@end + +@interface WebViewNavigationDelegate : NSObject { wxWebViewWebKit* webKitWindow; } @@ -62,7 +64,7 @@ wxEND_EVENT_TABLE() @end -@interface WebViewPolicyDelegate : NSObject +@interface WebViewUIDelegate : NSObject { wxWebViewWebKit* webKitWindow; } @@ -71,24 +73,16 @@ wxEND_EVENT_TABLE() @end -@interface WebViewUIDelegate : NSObject +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 +@interface WebViewCustomProtocol : NSObject { - wxWebViewWebKit* webKitWindow; + wxWebViewHandler* m_handler; } -- (id)initWithWxWindow: (wxWebViewWebKit*)inWindow; +- (id)initWithHandler:(wxWebViewHandler*) handler; @end - -//We use a hash to map scheme names to wxWebViewHandler -WX_DECLARE_STRING_HASH_MAP(wxSharedPtr, wxStringToWebHandlerMap); - -static wxStringToWebHandlerMap g_stringHandlerMap; - -@interface WebViewCustomProtocol : NSURLProtocol -{ -} -@end +#endif // macOS 10.13+ // ---------------------------------------------------------------------------- // creation/destruction @@ -101,25 +95,30 @@ bool wxWebViewWebKit::Create(wxWindow *parent, const wxSize& size, long style, const wxString& name) { - m_busy = false; - m_nextNavigationIsNewWindow = false; - DontCreatePeer(); wxControl::Create(parent, winID, pos, size, style, wxDefaultValidator, name); -#if wxOSX_USE_IPHONE - CGRect r = wxOSXGetFrameForControl( this, pos , size ) ; - m_webView = [[UIWebView alloc] initWithFrame:r]; - - SetPeer( new wxWidgetIPhoneImpl( this, m_webView ) ); - -#else NSRect r = wxOSXGetFrameForControl( this, pos , size ) ; - m_webView = [[WebView alloc] initWithFrame:r - frameName:@"webkitFrame" - groupName:@"webkitGroup"]; + WKWebViewConfiguration* webViewConfig = [[WKWebViewConfiguration alloc] init]; + if (!m_handlers.empty()) + { +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 + if ( WX_IS_MACOS_AVAILABLE(10, 13) ) + { + for (wxStringToWebHandlerMap::iterator it = m_handlers.begin(); it != m_handlers.end(); it++) + { + [webViewConfig setURLSchemeHandler:[[WebViewCustomProtocol alloc] initWithHandler:it->second.get()] + forURLScheme:wxCFStringRef(it->first).AsNSString()]; + } + } + else +#endif // macOS 10.13+ + wxLogDebug("Registering custom wxWebView handlers is not supported under macOS < 10.13"); + } + + m_webView = [[WXWKWebView alloc] initWithFrame:r configuration:webViewConfig]; + [(WXWKWebView*)m_webView setWebView:this]; SetPeer(new wxWidgetCocoaImpl( this, m_webView )); -#endif MacPostControlCreate(pos, size); @@ -127,32 +126,20 @@ bool wxWebViewWebKit::Create(wxWindow *parent, // Register event listener interfaces -#if wxOSX_USE_IPHONE -#else - WebViewLoadDelegate* loadDelegate = - [[WebViewLoadDelegate alloc] initWithWxWindow: this]; + WebViewNavigationDelegate* navDelegate = + [[WebViewNavigationDelegate alloc] initWithWxWindow: this]; + [m_webView addObserver:navDelegate forKeyPath:@"title" options:0 context:this]; - [m_webView setFrameLoadDelegate:loadDelegate]; - - m_loadDelegate = loadDelegate; + [m_webView setNavigationDelegate:navDelegate]; - // this is used to veto page loads, etc. - WebViewPolicyDelegate* policyDelegate = - [[WebViewPolicyDelegate alloc] initWithWxWindow: this]; - - [m_webView setPolicyDelegate:policyDelegate]; - - m_policyDelegate = policyDelegate; + m_navigationDelegate = navDelegate; WebViewUIDelegate* uiDelegate = [[WebViewUIDelegate alloc] initWithWxWindow: this]; [m_webView setUIDelegate:uiDelegate]; - + m_UIDelegate = uiDelegate; -#endif - //Register our own class for custom scheme handling - [NSURLProtocol registerClass:[WebViewCustomProtocol class]]; LoadURL(strURL); return true; @@ -160,16 +147,12 @@ bool wxWebViewWebKit::Create(wxWindow *parent, wxWebViewWebKit::~wxWebViewWebKit() { -#if wxOSX_USE_IPHONE -#else - [m_webView setFrameLoadDelegate: nil]; - [m_webView setPolicyDelegate: nil]; + [m_webView removeObserver:m_navigationDelegate forKeyPath:@"title" context:this]; + [m_webView setNavigationDelegate: nil]; [m_webView setUIDelegate: nil]; - [m_loadDelegate release]; - [m_policyDelegate release]; + [m_navigationDelegate release]; [m_UIDelegate release]; -#endif } // ---------------------------------------------------------------------------- @@ -208,6 +191,11 @@ void wxWebViewWebKit::GoForward() [m_webView goForward]; } +bool wxWebViewWebKit::IsBusy() const +{ + return m_webView.loading ? true : false; +} + void wxWebViewWebKit::Reload(wxWebViewReloadFlags flags) { if ( !m_webView ) @@ -215,14 +203,11 @@ void wxWebViewWebKit::Reload(wxWebViewReloadFlags flags) if (flags & wxWEBVIEW_RELOAD_NO_CACHE) { - // TODO: test this indeed bypasses the cache - [[m_webView preferences] setUsesPageCache:NO]; - [[m_webView mainFrame] reload]; - [[m_webView preferences] setUsesPageCache:YES]; + [m_webView reloadFromOrigin]; } else { - [[m_webView mainFrame] reload]; + [m_webView reload]; } } @@ -231,79 +216,14 @@ void wxWebViewWebKit::Stop() if ( !m_webView ) return; - [[m_webView mainFrame] stopLoading]; -} - -bool wxWebViewWebKit::CanGetPageSource() const -{ - if ( !m_webView ) - return false; - - WebDataSource* dataSource = [[m_webView mainFrame] dataSource]; - return ( [[dataSource representation] canProvideDocumentSource] ); + [m_webView stopLoading]; } wxString wxWebViewWebKit::GetPageSource() const { - - if (CanGetPageSource()) - { - WebDataSource* dataSource = [[m_webView mainFrame] dataSource]; - wxASSERT (dataSource != nil); - - id representation = [dataSource representation]; - wxASSERT (representation != nil); - - NSString* source = [representation documentSource]; - if (source == nil) - { - return wxEmptyString; - } - - return wxCFStringRef::AsString( source ); - } - - return wxEmptyString; -} - -bool wxWebViewWebKit::CanIncreaseTextSize() const -{ - if ( !m_webView ) - return false; - - if ([m_webView canMakeTextLarger]) - return true; - else - return false; -} - -void wxWebViewWebKit::IncreaseTextSize() -{ - if ( !m_webView ) - return; - - if (CanIncreaseTextSize()) - [m_webView makeTextLarger:(WebView*)m_webView]; -} - -bool wxWebViewWebKit::CanDecreaseTextSize() const -{ - if ( !m_webView ) - return false; - - if ([m_webView canMakeTextSmaller]) - return true; - else - return false; -} - -void wxWebViewWebKit::DecreaseTextSize() -{ - if ( !m_webView ) - return; - - if (CanDecreaseTextSize()) - [m_webView makeTextSmaller:(WebView*)m_webView]; + wxString text; + const_cast(this)->RunScript("document.documentElement.outerHTML;", &text); + return text; } void wxWebViewWebKit::Print() @@ -315,9 +235,30 @@ void wxWebViewWebKit::Print() if ( !m_webView ) return; - id view = [[[m_webView mainFrame] frameView] documentView]; - NSPrintOperation *op = [NSPrintOperation printOperationWithView:view - printInfo: [NSPrintInfo sharedPrintInfo]]; + // As of macOS SDK 10.15 no offical printing API is available for WKWebView + // Try if the undocumented printOperationWithPrintInfo: is available and use it + // to create a printing operation + // https://bugs.webkit.org/show_bug.cgi?id=151276 + SEL printSelector = @selector(printOperationWithPrintInfo:); + if (![m_webView respondsToSelector:printSelector]) + printSelector = nil; + + if (!printSelector) + { + wxLogError(_("Printing is not supported by the system web control")); + return; + } + + NSPrintOperation* op = (NSPrintOperation*)[m_webView + performSelector:printSelector + withObject:[NSPrintInfo sharedPrintInfo]]; + if (!op) + { + wxLogError(_("Print operation could not be initialized")); + return; + } + + op.view.frame = m_webView.frame; if (showPrompt) { [op setShowsPrintPanel: showPrompt]; @@ -330,35 +271,25 @@ void wxWebViewWebKit::Print() [op runOperation]; } -void wxWebViewWebKit::SetEditable(bool enable) +void wxWebViewWebKit::SetEditable(bool WXUNUSED(enable)) { - if ( !m_webView ) - return; - - [m_webView setEditable:enable ]; } bool wxWebViewWebKit::IsEditable() const { - if ( !m_webView ) - return false; - - return [m_webView isEditable]; + return false; } void wxWebViewWebKit::SetZoomType(wxWebViewZoomType zoomType) { // there is only one supported zoom type at the moment so this setter // does nothing beyond checking sanity - wxASSERT(zoomType == wxWEBVIEW_ZOOM_TYPE_TEXT); + wxASSERT(zoomType == wxWEBVIEW_ZOOM_TYPE_LAYOUT); } wxWebViewZoomType wxWebViewWebKit::GetZoomType() const { - // for now that's the only one that is supported - // FIXME: does the default zoom type change depending on webkit versions? :S - // Then this will be wrong - return wxWEBVIEW_ZOOM_TYPE_TEXT; + return wxWEBVIEW_ZOOM_TYPE_LAYOUT; } bool wxWebViewWebKit::CanSetZoomType(wxWebViewZoomType type) const @@ -366,9 +297,7 @@ bool wxWebViewWebKit::CanSetZoomType(wxWebViewZoomType type) const switch (type) { // for now that's the only one that is supported - // TODO: I know recent versions of webkit support layout zoom too, - // check if we can support it - case wxWEBVIEW_ZOOM_TYPE_TEXT: + case wxWEBVIEW_ZOOM_TYPE_LAYOUT: return true; default: @@ -376,116 +305,104 @@ bool wxWebViewWebKit::CanSetZoomType(wxWebViewZoomType type) const } } -int wxWebViewWebKit::GetScrollPos() -{ - id result = [[m_webView windowScriptObject] - evaluateWebScript:@"document.body.scrollTop"]; - return [result intValue]; -} - -void wxWebViewWebKit::SetScrollPos(int pos) -{ - if ( !m_webView ) - return; - - wxString javascript; - javascript.Printf(wxT("document.body.scrollTop = %d;"), pos); - [[m_webView windowScriptObject] evaluateWebScript: - wxCFStringRef( javascript ).AsNSString()]; -} - wxString wxWebViewWebKit::GetSelectedText() const { - DOMRange* dr = [m_webView selectedDOMRange]; - if ( !dr ) - return wxString(); + wxString text; + const_cast(this)->RunScript("window.getSelection().toString();", &text); + return text; +} - return wxCFStringRef::AsString([dr toString]); +bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) +{ + __block bool scriptExecuted = false; + __block wxString outputStr; + __block bool scriptSuccess = false; + + // Start script execution + [m_webView evaluateJavaScript:wxCFStringRef(javascript).AsNSString() + completionHandler:^(id _Nullable obj, NSError * _Nullable error) { + if (error) + { + outputStr.assign(wxCFStringRef(error.localizedFailureReason).AsString()); + } + else + { + if ([obj isKindOfClass:[NSNumber class]]) + { + NSNumber* num = (NSNumber*) obj; + CFTypeID numID = CFGetTypeID((__bridge CFTypeRef)(num)); + if (numID == CFBooleanGetTypeID()) + outputStr = num.boolValue ? "true" : "false"; + else + outputStr = wxCFStringRef::AsString(num.stringValue); + } + else if (obj) + outputStr.assign(wxCFStringRef::AsString([NSString stringWithFormat:@"%@", obj])); + + scriptSuccess = true; + } + + scriptExecuted = true; + }]; + + // Wait for script exection + while (!scriptExecuted) + wxYield(); + + if (output) + output->assign(outputStr); + + return scriptSuccess; } bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) { - wxCHECK_MSG( m_webView, false, - wxS("wxWebView must be created before calling RunScript()") ); - wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); - NSString* result = [m_webView stringByEvaluatingJavaScriptFromString: - wxCFStringRef( wrapJS.GetWrappedCode() ).AsNSString()]; - - wxString err; - if ( result == nil ) + // This string is also used as an error indicator: it's cleared if there is + // no error or used in the warning message below if there is one. + wxString result; + if (RunScriptSync(wrapJS.GetWrappedCode(), &result) + && result == wxS("true")) { - // This is not very informative, but we just don't have any other - // information in this case. - err = _("failed to evaluate"); - } - else if ( [result isEqualToString:@"true"] ) - { - result = [m_webView stringByEvaluatingJavaScriptFromString: - wxCFStringRef( wrapJS.GetOutputCode() ).AsNSString()]; - - [m_webView stringByEvaluatingJavaScriptFromString: - wxCFStringRef( wrapJS.GetCleanUpCode() ).AsNSString()]; - - if ( output != NULL ) + if (RunScriptSync(wrapJS.GetOutputCode() + ";", &result)) { - if ( result ) - *output = wxCFStringRef::AsString(result); - else - err = _("failed to retrieve execution result"); + if (output) + *output = result; + result.clear(); } - } - else // result available but not the expected "true" - { - err = wxCFStringRef::AsString(result); + + RunScriptSync(wrapJS.GetCleanUpCode()); } - if ( !err.empty() ) + if (!result.empty()) { - wxLogWarning(_("Error running JavaScript: %s"), err); + wxLogWarning(_("Error running JavaScript: %s"), result); return false; } return true; } -void wxWebViewWebKit::OnSize(wxSizeEvent &event) -{ -} - -void wxWebViewWebKit::MacVisibilityChanged(){ -} - void wxWebViewWebKit::LoadURL(const wxString& url) { - [[m_webView mainFrame] loadRequest:[NSURLRequest requestWithURL: + [m_webView loadRequest:[NSURLRequest requestWithURL: [NSURL URLWithString:wxCFStringRef(url).AsNSString()]]]; } wxString wxWebViewWebKit::GetCurrentURL() const { - return wxCFStringRef::AsString([m_webView mainFrameURL]); + return wxCFStringRef::AsString(m_webView.URL.absoluteString); } wxString wxWebViewWebKit::GetCurrentTitle() const { - return wxCFStringRef::AsString([m_webView mainFrameTitle]); -} - -float wxWebViewWebKit::GetWebkitZoom() const -{ - return [m_webView textSizeMultiplier]; -} - -void wxWebViewWebKit::SetWebkitZoom(float zoom) -{ - [m_webView setTextSizeMultiplier:zoom]; + return wxCFStringRef::AsString(m_webView.title); } wxWebViewZoom wxWebViewWebKit::GetZoom() const { - float zoom = GetWebkitZoom(); + float zoom = GetZoomFactor(); // arbitrary way to map float zoom to our common zoom enum if (zoom <= 0.55) @@ -516,7 +433,7 @@ wxWebViewZoom wxWebViewWebKit::GetZoom() const float wxWebViewWebKit::GetZoomFactor() const { - return GetWebkitZoom(); + return m_webView.magnification; } void wxWebViewWebKit::SetZoom(wxWebViewZoom zoom) @@ -525,34 +442,33 @@ void wxWebViewWebKit::SetZoom(wxWebViewZoom zoom) switch (zoom) { case wxWEBVIEW_ZOOM_TINY: - SetWebkitZoom(0.4f); + SetZoomFactor(0.4f); break; case wxWEBVIEW_ZOOM_SMALL: - SetWebkitZoom(0.7f); + SetZoomFactor(0.7f); break; case wxWEBVIEW_ZOOM_MEDIUM: - SetWebkitZoom(1.0f); + SetZoomFactor(1.0f); break; case wxWEBVIEW_ZOOM_LARGE: - SetWebkitZoom(1.3); + SetZoomFactor(1.3); break; case wxWEBVIEW_ZOOM_LARGEST: - SetWebkitZoom(1.6); + SetZoomFactor(1.6); break; default: wxASSERT(false); } - } void wxWebViewWebKit::SetZoomFactor(float zoom) { - SetWebkitZoom(zoom); + m_webView.magnification = zoom; } void wxWebViewWebKit::DoSetPage(const wxString& src, const wxString& baseUrl) @@ -560,54 +476,36 @@ void wxWebViewWebKit::DoSetPage(const wxString& src, const wxString& baseUrl) if ( !m_webView ) return; - [[m_webView mainFrame] loadHTMLString:wxCFStringRef( src ).AsNSString() + [m_webView loadHTMLString:wxCFStringRef( src ).AsNSString() baseURL:[NSURL URLWithString: wxCFStringRef( baseUrl ).AsNSString()]]; } void wxWebViewWebKit::Cut() { - if ( !m_webView ) - return; - - [m_webView cut:m_webView]; + ExecCommand("cut"); } void wxWebViewWebKit::Copy() { - if ( !m_webView ) - return; - - [m_webView copy:m_webView]; + ExecCommand("copy"); } void wxWebViewWebKit::Paste() { - if ( !m_webView ) - return; - - [m_webView paste:m_webView]; + ExecCommand("paste"); } void wxWebViewWebKit::DeleteSelection() { - if ( !m_webView ) - return; - - [m_webView deleteSelection]; + ExecCommand("delete"); } bool wxWebViewWebKit::HasSelection() const { - DOMRange* range = [m_webView selectedDOMRange]; - if(!range) - { - return false; - } - else - { - return true; - } + wxString rangeCountStr; + const_cast(this)->RunScript("window.getSelection().rangeCount;", &rangeCountStr); + return rangeCountStr != "0"; } void wxWebViewWebKit::ClearSelection() @@ -623,18 +521,15 @@ void wxWebViewWebKit::SelectAll() wxString wxWebViewWebKit::GetSelectedSource() const { - DOMRange* dr = [m_webView selectedDOMRange]; - if ( !dr ) - return wxString(); - - return wxCFStringRef::AsString([dr markupString]); + // TODO: not implemented in SDK (could probably be implemented by script) + return wxString(); } wxString wxWebViewWebKit::GetPageText() const { - NSString *result = [m_webView stringByEvaluatingJavaScriptFromString: - @"document.body.textContent"]; - return wxCFStringRef::AsString(result); + wxString text; + const_cast(this)->RunScript("document.body.innerText;", &text); + return text; } void wxWebViewWebKit::EnableHistory(bool enable) @@ -642,24 +537,23 @@ void wxWebViewWebKit::EnableHistory(bool enable) if ( !m_webView ) return; - [m_webView setMaintainsBackForwardList:enable]; + // TODO: implement } void wxWebViewWebKit::ClearHistory() { - [m_webView setMaintainsBackForwardList:NO]; - [m_webView setMaintainsBackForwardList:YES]; + // TODO: implement } wxVector > wxWebViewWebKit::GetBackwardHistory() { wxVector > backhist; - WebBackForwardList* history = [m_webView backForwardList]; - int count = [history backListCount]; + WKBackForwardList* history = [m_webView backForwardList]; + int count = history.backList.count; for(int i = -count; i < 0; i++) { - WebHistoryItem* item = [history itemAtIndex:i]; - wxString url = wxCFStringRef::AsString([item URLString]); + WKBackForwardListItem* item = [history itemAtIndex:i]; + wxString url = wxCFStringRef::AsString(item.URL.absoluteString); wxString title = wxCFStringRef::AsString([item title]); wxWebViewHistoryItem* wxitem = new wxWebViewHistoryItem(url, title); wxitem->m_histItem = item; @@ -672,12 +566,12 @@ wxVector > wxWebViewWebKit::GetBackwardHistory wxVector > wxWebViewWebKit::GetForwardHistory() { wxVector > forwardhist; - WebBackForwardList* history = [m_webView backForwardList]; - int count = [history forwardListCount]; + WKBackForwardList* history = [m_webView backForwardList]; + int count = history.forwardList.count; for(int i = 1; i <= count; i++) { - WebHistoryItem* item = [history itemAtIndex:i]; - wxString url = wxCFStringRef::AsString([item URLString]); + WKBackForwardListItem* item = [history itemAtIndex:i]; + wxString url = wxCFStringRef::AsString(item.URL.absoluteString); wxString title = wxCFStringRef::AsString([item title]); wxWebViewHistoryItem* wxitem = new wxWebViewHistoryItem(url, title); wxitem->m_histItem = item; @@ -689,7 +583,7 @@ wxVector > wxWebViewWebKit::GetForwardHistory( void wxWebViewWebKit::LoadHistoryItem(wxSharedPtr item) { - [m_webView goToBackForwardItem:item->m_histItem]; + [m_webView goToBackForwardListItem:item->m_histItem]; } bool wxWebViewWebKit::CanUndo() const @@ -714,22 +608,58 @@ void wxWebViewWebKit::Redo() void wxWebViewWebKit::RegisterHandler(wxSharedPtr handler) { - g_stringHandlerMap[handler->GetName()] = handler; + m_handlers[handler->GetName()] = handler; +} + +bool wxWebViewWebKit::QueryCommandEnabled(const wxString& command) const +{ + wxString resultStr; + const_cast(this)->RunScript( + wxString::Format("function f(){ return document.queryCommandEnabled('%s'); } f();", command), &resultStr); + return resultStr.IsSameAs("true", false); +} + +void wxWebViewWebKit::ExecCommand(const wxString& command) +{ + RunScript(wxString::Format("document.execCommand('%s');", command)); } //------------------------------------------------------------ // Listener interfaces //------------------------------------------------------------ -#if wxOSX_USE_IPHONE -#else - // NB: I'm still tracking this down, but it appears the Cocoa window // still has these events fired on it while the Carbon control is being // destroyed. Therefore, we must be careful to check both the existence // of the Carbon control and the event handler before firing events. -@implementation WebViewLoadDelegate +@implementation WXWKWebView + +- (void)setWebView:(wxWebViewWebKit *)webView +{ + m_webView = webView; +} + +#if !defined(__WXOSX_IPHONE__) +- (void)willOpenMenu:(NSMenu *)menu withEvent:(NSEvent *)event +{ + if (m_webView && !m_webView->IsContextMenuEnabled()) + [menu removeAllItems]; +} + +-(id)validRequestorForSendType:(NSString*)sendType returnType:(NSString*)returnType +{ + if (m_webView && !m_webView->IsContextMenuEnabled()) + return nil; + else + return [super validRequestorForSendType:sendType returnType:returnType]; +} + +#endif + +@end + +@implementation WebViewNavigationDelegate - (id)initWithWxWindow: (wxWebViewWebKit*)inWindow { @@ -740,41 +670,46 @@ void wxWebViewWebKit::RegisterHandler(wxSharedPtr handler) return self; } -- (void)webView:(WebView *)sender - didStartProvisionalLoadForFrame:(WebFrame *)frame +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context { - webKitWindow->m_busy = true; + if (context == webKitWindow) + { + wxWebViewEvent event(wxEVT_WEBVIEW_TITLE_CHANGED, + webKitWindow->GetId(), + webKitWindow->GetCurrentURL(), + ""); + + event.SetString(webKitWindow->GetCurrentTitle()); + + webKitWindow->ProcessWindowEvent(event); + } + else + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } -- (void)webView:(WebView *)sender didCommitLoadForFrame:(WebFrame *)frame +- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation { - webKitWindow->m_busy = true; - - if (webKitWindow && frame == [sender mainFrame]){ - NSString *url = [[[[frame dataSource] request] URL] absoluteString]; - wxString target = wxCFStringRef::AsString([frame name]); + if (webKitWindow){ + NSString *url = webView.URL.absoluteString; wxWebViewEvent event(wxEVT_WEBVIEW_NAVIGATED, webKitWindow->GetId(), wxCFStringRef::AsString( url ), - target); + ""); if (webKitWindow && webKitWindow->GetEventHandler()) webKitWindow->GetEventHandler()->ProcessEvent(event); } } -- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { - webKitWindow->m_busy = false; + if (webKitWindow){ + NSString *url = webView.URL.absoluteString; - if (webKitWindow && frame == [sender mainFrame]){ - NSString *url = [[[[frame dataSource] request] URL] absoluteString]; - - wxString target = wxCFStringRef::AsString([frame name]); wxWebViewEvent event(wxEVT_WEBVIEW_LOADED, webKitWindow->GetId(), wxCFStringRef::AsString( url ), - target); + ""); if (webKitWindow && webKitWindow->GetEventHandler()) webKitWindow->GetEventHandler()->ProcessEvent(event); @@ -855,13 +790,12 @@ wxString nsErrorToWxHtmlError(NSError* error, wxWebViewNavigationError* out) return message; } -- (void)webView:(WebView *)sender didFailLoadWithError:(NSError*) error - forFrame:(WebFrame *)frame +- (void)webView:(WKWebView *)webView + didFailNavigation:(WKNavigation *)navigation + withError:(NSError *)error; { - webKitWindow->m_busy = false; - - if (webKitWindow && frame == [sender mainFrame]){ - NSString *url = [[[[frame dataSource] request] URL] absoluteString]; + if (webKitWindow){ + NSString *url = webView.URL.absoluteString; wxWebViewNavigationError type; wxString description = nsErrorToWxHtmlError(error, &type); @@ -879,15 +813,12 @@ wxString nsErrorToWxHtmlError(NSError* error, wxWebViewNavigationError* out) } } -- (void)webView:(WebView *)sender - didFailProvisionalLoadWithError:(NSError*)error - forFrame:(WebFrame *)frame +- (void)webView:(WKWebView *)webView + didFailProvisionalNavigation:(WKNavigation *)navigation + withError:(NSError *)error; { - webKitWindow->m_busy = false; - - if (webKitWindow && frame == [sender mainFrame]){ - NSString *url = [[[[frame provisionalDataSource] request] URL] - absoluteString]; + if (webKitWindow){ + NSString *url = webView.URL.absoluteString; wxWebViewNavigationError type; wxString description = nsErrorToWxHtmlError(error, &type); @@ -903,158 +834,54 @@ wxString nsErrorToWxHtmlError(NSError* error, wxWebViewNavigationError* out) } } -- (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title - forFrame:(WebFrame *)frame +- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction + decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { - wxString target = wxCFStringRef::AsString([frame name]); - wxWebViewEvent event(wxEVT_WEBVIEW_TITLE_CHANGED, - webKitWindow->GetId(), - webKitWindow->GetCurrentURL(), - target); + NSString *url = [[navigationAction.request URL] absoluteString]; + wxWebViewNavigationActionFlags navFlags = + navigationAction.navigationType == WKNavigationTypeLinkActivated ? + wxWEBVIEW_NAV_ACTION_USER : + wxWEBVIEW_NAV_ACTION_OTHER; - event.SetString(wxCFStringRef::AsString(title)); + wxWebViewEvent event(wxEVT_WEBVIEW_NAVIGATING, + webKitWindow->GetId(), + wxCFStringRef::AsString( url ), "", navFlags); if (webKitWindow && webKitWindow->GetEventHandler()) webKitWindow->GetEventHandler()->ProcessEvent(event); + + decisionHandler(event.IsAllowed() ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel); } + @end -@implementation WebViewPolicyDelegate +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 +@implementation WebViewCustomProtocol -- (id)initWithWxWindow: (wxWebViewWebKit*)inWindow +- (id)initWithHandler:(wxWebViewHandler *)handler { - if (self = [super init]) - { - webKitWindow = inWindow; // non retained - } + m_handler = handler; return self; } -- (void)webView:(WebView *)sender - decidePolicyForNavigationAction:(NSDictionary *)actionInformation - request:(NSURLRequest *)request - frame:(WebFrame *)frame - decisionListener:(id)listener +- (void)webView:(WKWebView *)webView startURLSchemeTask:(id)urlSchemeTask +WX_API_AVAILABLE_MACOS(10, 13) { - wxUnusedVar(frame); - - NSString *url = [[request URL] absoluteString]; - if (webKitWindow->m_nextNavigationIsNewWindow) - { - // This navigation has been marked as a new window - // cancel the request here and send an apropriate event - // to the application code - webKitWindow->m_nextNavigationIsNewWindow = false; - - wxWebViewEvent event(wxEVT_WEBVIEW_NEWWINDOW, - webKitWindow->GetId(), - wxCFStringRef::AsString( url ), ""); - - if (webKitWindow && webKitWindow->GetEventHandler()) - webKitWindow->GetEventHandler()->ProcessEvent(event); - - [listener ignore]; - return; - } - - webKitWindow->m_busy = true; - wxString target = wxCFStringRef::AsString([frame name]); - wxWebViewEvent event(wxEVT_WEBVIEW_NAVIGATING, - webKitWindow->GetId(), - wxCFStringRef::AsString( url ), target); - - if (webKitWindow && webKitWindow->GetEventHandler()) - webKitWindow->GetEventHandler()->ProcessEvent(event); - - if (!event.IsAllowed()) - { - webKitWindow->m_busy = false; - [listener ignore]; - } - else - { - [listener use]; - } -} - -- (void)webView:(WebView *)sender - decidePolicyForNewWindowAction:(NSDictionary *)actionInformation - request:(NSURLRequest *)request - newFrameName:(NSString *)frameName - decisionListener:(id < WebPolicyDecisionListener >)listener -{ - NSString *url = [[request URL] absoluteString]; - - wxWebViewNavigationActionFlags flags = wxWEBVIEW_NAV_ACTION_USER; - - int action = [[actionInformation objectForKey:WebActionNavigationTypeKey] intValue]; - if (action == WebNavigationTypeOther) - flags = wxWEBVIEW_NAV_ACTION_OTHER; - - wxWebViewEvent event(wxEVT_WEBVIEW_NEWWINDOW, - webKitWindow->GetId(), - wxCFStringRef::AsString( url ), "", flags); - - if (webKitWindow && webKitWindow->GetEventHandler()) - webKitWindow->GetEventHandler()->ProcessEvent(event); - - [listener ignore]; -} -@end - -#endif - -@implementation WebViewCustomProtocol - -+ (BOOL)canInitWithRequest:(NSURLRequest *)request -{ - NSString *scheme = [[request URL] scheme]; - - wxStringToWebHandlerMap::const_iterator it; - for( it = g_stringHandlerMap.begin(); it != g_stringHandlerMap.end(); ++it ) - { - if(it->first.IsSameAs(wxCFStringRef::AsString(scheme))) - { - return YES; - } - } - - return NO; -} - -+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request -{ - //We don't do any processing here as the wxWebViewHandler classes do it - return request; -} - -- (void)startLoading -{ - NSURLRequest *request = [self request]; + NSURLRequest *request = urlSchemeTask.request; NSString* path = [[request URL] absoluteString]; - id client = [self client]; - wxString wxpath = wxCFStringRef::AsString(path); - wxString scheme = wxCFStringRef::AsString([[request URL] scheme]); - - // since canInitRequest has already checked whether this scheme is supported - // the hash map contains this entry, but to satisfy static code analysis - // suspecting nullptr dereference ... -#ifndef __clang_analyzer__ - wxFSFile* file = g_stringHandlerMap[scheme]->GetFile(wxpath); -#else - wxFSFile* file = NULL; -#endif - + + wxFSFile* file = m_handler->GetFile(wxpath); + if (!file) { NSError *error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]; - [client URLProtocol:self didFailWithError:error]; - + [urlSchemeTask didFailWithError:error]; + [error release]; return; @@ -1073,27 +900,26 @@ wxString nsErrorToWxHtmlError(NSError* error, wxWebViewNavigationError* out) file->GetStream()->Read(buffer, length); NSData *data = [[NSData alloc] initWithBytesNoCopy:buffer length:length]; - //We do not support caching anything yet - [client URLProtocol:self didReceiveResponse:response - cacheStoragePolicy:NSURLCacheStorageNotAllowed]; - //Set the data - [client URLProtocol:self didLoadData:data]; + [urlSchemeTask didReceiveResponse:response]; + [urlSchemeTask didReceiveData:data]; //Notify that we have finished - [client URLProtocolDidFinishLoading:self]; + [urlSchemeTask didFinish]; [data release]; - + [response release]; } -- (void)stopLoading +- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id)urlSchemeTask +WX_API_AVAILABLE_MACOS(10, 13) { } @end +#endif // macOS 10.13+ @implementation WebViewUIDelegate @@ -1107,31 +933,27 @@ wxString nsErrorToWxHtmlError(NSError* error, wxWebViewNavigationError* out) return self; } -- (WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request +- (WKWebView *)webView:(WKWebView *)webView + createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration + forNavigationAction:(WKNavigationAction *)navigationAction + windowFeatures:(WKWindowFeatures *)windowFeatures { - // This method is called when window.open() is used in javascript with a target != _self - // request is always nil, so it can't be used for event generation - // Mark the next navigation as "new window" - webKitWindow->m_nextNavigationIsNewWindow = true; - return sender; + wxWebViewNavigationActionFlags navFlags = + navigationAction.navigationType == WKNavigationTypeLinkActivated ? + wxWEBVIEW_NAV_ACTION_USER : + wxWEBVIEW_NAV_ACTION_OTHER; + + wxWebViewEvent event(wxEVT_WEBVIEW_NEWWINDOW, + webKitWindow->GetId(), + wxCFStringRef::AsString( navigationAction.request.URL.absoluteString ), + "", navFlags); + + if (webKitWindow && webKitWindow->GetEventHandler()) + webKitWindow->GetEventHandler()->ProcessEvent(event); + + return nil; } -- (void)webView:(WebView *)sender printFrameView:(WebFrameView *)frameView -{ - wxUnusedVar(sender); - wxUnusedVar(frameView); - - webKitWindow->Print(); -} - -- (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element - defaultMenuItems:(NSArray *) defaultMenuItems -{ - if(webKitWindow->IsContextMenuEnabled()) - return defaultMenuItems; - else - return nil; -} @end #endif //wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT