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