1///////////////////////////////////////////////////////////////////////////// 2// Name: src/osx/webview_webkit.mm 3// Purpose: wxWebViewWebKit - embeddable web kit control, 4// OS X implementation of web view component 5// Author: Jethro Grassie / Kevin Ollivier / Marianne Gagnon 6// Modified by: 7// Created: 2004-4-16 8// Copyright: (c) Jethro Grassie / Kevin Ollivier / Marianne Gagnon 9// Licence: wxWindows licence 10///////////////////////////////////////////////////////////////////////////// 11 12// https://developer.apple.com/documentation/webkit/wkwebview 13 14#include "wx/osx/webview_webkit.h" 15 16#if wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT && defined(__WXOSX__) 17 18// For compilers that support precompilation, includes "wx.h". 19#include "wx/wxprec.h" 20 21#ifndef WX_PRECOMP 22 #include "wx/wx.h" 23#endif 24 25#include "wx/osx/private.h" 26#include "wx/osx/core/cfref.h" 27#include "wx/osx/private/available.h" 28#include "wx/private/jsscriptwrapper.h" 29 30#include "wx/hashmap.h" 31#include "wx/filesys.h" 32#include "wx/msgdlg.h" 33#include "wx/textdlg.h" 34#include "wx/filedlg.h" 35 36#include <WebKit/WebKit.h> 37#include <Foundation/NSURLError.h> 38 39// using native types to get compile errors and warnings 40 41#define DEBUG_WEBKIT_SIZING 0 42 43// ---------------------------------------------------------------------------- 44// macros 45// ---------------------------------------------------------------------------- 46 47wxIMPLEMENT_DYNAMIC_CLASS(wxWebViewWebKit, wxWebView); 48 49wxBEGIN_EVENT_TABLE(wxWebViewWebKit, wxControl) 50wxEND_EVENT_TABLE() 51 52@interface WXWKWebView: WKWebView 53{ 54 wxWebViewWebKit* m_webView; 55} 56 57- (void)setWebView:(wxWebViewWebKit*)webView; 58 59@end 60 61@interface WebViewNavigationDelegate : NSObject<WKNavigationDelegate> 62{ 63 wxWebViewWebKit* webKitWindow; 64} 65 66- (id)initWithWxWindow: (wxWebViewWebKit*)inWindow; 67 68@end 69 70@interface WebViewUIDelegate : NSObject<WKUIDelegate> 71{ 72 wxWebViewWebKit* webKitWindow; 73} 74 75- (id)initWithWxWindow: (wxWebViewWebKit*)inWindow; 76 77@end 78 79#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 80@interface WebViewCustomProtocol : NSObject<WKURLSchemeHandler> 81{ 82 wxWebViewHandler* m_handler; 83} 84 85- (id)initWithHandler:(wxWebViewHandler*) handler; 86 87@end 88#endif // macOS 10.13+ 89 90@interface WebViewScriptMessageHandler: NSObject<WKScriptMessageHandler> 91{ 92 wxWebViewWebKit* webKitWindow; 93} 94 95- (id)initWithWxWindow: (wxWebViewWebKit*)inWindow; 96 97@end 98 99//----------------------------------------------------------------------------- 100// wxWebViewFactoryWebKit 101//----------------------------------------------------------------------------- 102 103wxVersionInfo wxWebViewFactoryWebKit::GetVersionInfo() 104{ 105 int verMaj, verMin, verMicro; 106 wxGetOsVersion(&verMaj, &verMin, &verMicro); 107 return wxVersionInfo("WKWebView", verMaj, verMin, verMicro); 108} 109 110// ---------------------------------------------------------------------------- 111// creation/destruction 112// ---------------------------------------------------------------------------- 113 114bool wxWebViewWebKit::Create(wxWindow *parent, 115 wxWindowID winID, 116 const wxString& strURL, 117 const wxPoint& pos, 118 const wxSize& size, long style, 119 const wxString& name) 120{ 121 DontCreatePeer(); 122 wxControl::Create(parent, winID, pos, size, style, wxDefaultValidator, name); 123 124 NSRect r = wxOSXGetFrameForControl( this, pos , size ) ; 125 WKWebViewConfiguration* webViewConfig = [[WKWebViewConfiguration alloc] init]; 126 127 // WebKit API available since macOS 10.11 and iOS 9.0 128 SEL fullScreenSelector = @selector(_setFullScreenEnabled:); 129 if ([webViewConfig.preferences respondsToSelector:fullScreenSelector]) 130 [webViewConfig.preferences performSelector:fullScreenSelector withObject:[NSNumber numberWithBool:YES]]; 131 132 if (!m_handlers.empty()) 133 { 134#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 135 if ( WX_IS_MACOS_AVAILABLE(10, 13) ) 136 { 137 for (wxStringToWebHandlerMap::iterator it = m_handlers.begin(); it != m_handlers.end(); it++) 138 { 139 [webViewConfig setURLSchemeHandler:[[WebViewCustomProtocol alloc] initWithHandler:it->second.get()] 140 forURLScheme:wxCFStringRef(it->first).AsNSString()]; 141 } 142 } 143 else 144#endif // macOS 10.13+ 145 wxLogDebug("Registering custom wxWebView handlers is not supported under macOS < 10.13"); 146 } 147 148 m_webView = [[WXWKWebView alloc] initWithFrame:r configuration:webViewConfig]; 149 [(WXWKWebView*)m_webView setWebView:this]; 150 SetPeer(new wxWidgetCocoaImpl( this, m_webView )); 151 152 MacPostControlCreate(pos, size); 153 154 if (!m_customUserAgent.empty()) 155 SetUserAgent(m_customUserAgent); 156 157 [m_webView setHidden:false]; 158 159 160 // Register event listener interfaces 161 WebViewNavigationDelegate* navDelegate = 162 [[WebViewNavigationDelegate alloc] initWithWxWindow: this]; 163 [m_webView addObserver:navDelegate forKeyPath:@"title" options:0 context:this]; 164 165 [m_webView setNavigationDelegate:navDelegate]; 166 167 m_navigationDelegate = navDelegate; 168 169 WebViewUIDelegate* uiDelegate = 170 [[WebViewUIDelegate alloc] initWithWxWindow: this]; 171 172 [m_webView setUIDelegate:uiDelegate]; 173 174 // WebKit API available since macOS 10.13 and iOS 11.0 175 SEL fullScreenDelegateSelector = @selector(_setFullscreenDelegate:); 176 if ([m_webView respondsToSelector:fullScreenDelegateSelector]) 177 [m_webView performSelector:fullScreenDelegateSelector withObject:uiDelegate]; 178 179 m_UIDelegate = uiDelegate; 180 181 LoadURL(strURL); 182 return true; 183} 184 185wxWebViewWebKit::~wxWebViewWebKit() 186{ 187 [m_webView removeObserver:m_navigationDelegate forKeyPath:@"title" context:this]; 188 [m_webView setNavigationDelegate: nil]; 189 [m_webView setUIDelegate: nil]; 190 191 [m_navigationDelegate release]; 192 [m_UIDelegate release]; 193} 194 195// ---------------------------------------------------------------------------- 196// public methods 197// ---------------------------------------------------------------------------- 198 199bool wxWebViewWebKit::CanGoBack() const 200{ 201 if ( !m_webView ) 202 return false; 203 204 return [m_webView canGoBack]; 205} 206 207bool wxWebViewWebKit::CanGoForward() const 208{ 209 if ( !m_webView ) 210 return false; 211 212 return [m_webView canGoForward]; 213} 214 215void wxWebViewWebKit::GoBack() 216{ 217 if ( !m_webView ) 218 return; 219 220 [m_webView goBack]; 221} 222 223void wxWebViewWebKit::GoForward() 224{ 225 if ( !m_webView ) 226 return; 227 228 [m_webView goForward]; 229} 230 231bool wxWebViewWebKit::IsBusy() const 232{ 233 return m_webView.loading ? true : false; 234} 235 236void wxWebViewWebKit::Reload(wxWebViewReloadFlags flags) 237{ 238 if ( !m_webView ) 239 return; 240 241 if (flags & wxWEBVIEW_RELOAD_NO_CACHE) 242 { 243 [m_webView reloadFromOrigin]; 244 } 245 else 246 { 247 [m_webView reload]; 248 } 249} 250 251void wxWebViewWebKit::Stop() 252{ 253 if ( !m_webView ) 254 return; 255 256 [m_webView stopLoading]; 257} 258 259void wxWebViewWebKit::Print() 260{ 261 262 // TODO: allow specifying the "show prompt" parameter in Print() ? 263 bool showPrompt = true; 264 265 if ( !m_webView ) 266 return; 267 268 // As of macOS SDK 10.15 no offical printing API is available for WKWebView 269 // Try if the undocumented printOperationWithPrintInfo: is available and use it 270 // to create a printing operation 271 // https://bugs.webkit.org/show_bug.cgi?id=151276 272 SEL printSelector = @selector(printOperationWithPrintInfo:); 273 if (![m_webView respondsToSelector:printSelector]) 274 printSelector = nil; 275 276 if (!printSelector) 277 { 278 wxLogError(_("Printing is not supported by the system web control")); 279 return; 280 } 281 282 NSPrintOperation* op = (NSPrintOperation*)[m_webView 283 performSelector:printSelector 284 withObject:[NSPrintInfo sharedPrintInfo]]; 285 if (!op) 286 { 287 wxLogError(_("Print operation could not be initialized")); 288 return; 289 } 290 291 op.view.frame = m_webView.frame; 292 if (showPrompt) 293 { 294 [op setShowsPrintPanel: showPrompt]; 295 // in my tests, the progress bar always freezes and it stops the whole 296 // print operation. do not turn this to true unless there is a 297 // workaround for the bug. 298 [op setShowsProgressPanel: false]; 299 } 300 // Print it. 301 [op runOperationModalForWindow:m_webView.window 302 delegate:nil didRunSelector:nil contextInfo:nil]; 303} 304 305void wxWebViewWebKit::SetEditable(bool WXUNUSED(enable)) 306{ 307} 308 309bool wxWebViewWebKit::IsEditable() const 310{ 311 return false; 312} 313 314bool wxWebViewWebKit::IsAccessToDevToolsEnabled() const 315{ 316 // WebKit API available since macOS 10.11 and iOS 9.0 317 WKPreferences* prefs = m_webView.configuration.preferences; 318 SEL devToolsSelector = @selector(_developerExtrasEnabled); 319 id val = nil; 320 if ([prefs respondsToSelector:devToolsSelector]) 321 val = [prefs performSelector:devToolsSelector]; 322 return (val != nil); 323} 324 325void wxWebViewWebKit::EnableAccessToDevTools(bool enable) 326{ 327 // WebKit API available since macOS 10.11 and iOS 9.0 328 WKPreferences* prefs = m_webView.configuration.preferences; 329 SEL devToolsSelector = @selector(_setDeveloperExtrasEnabled:); 330 if ([prefs respondsToSelector:devToolsSelector]) 331 [prefs performSelector:devToolsSelector withObject:(id)enable]; 332} 333 334bool wxWebViewWebKit::SetUserAgent(const wxString& userAgent) 335{ 336 if (WX_IS_MACOS_AVAILABLE(10, 11)) 337 { 338 if (m_webView) 339 m_webView.customUserAgent = wxCFStringRef(userAgent).AsNSString(); 340 else 341 m_customUserAgent = userAgent; 342 343 return true; 344 } 345 else 346 return false; 347} 348 349void wxWebViewWebKit::SetZoomType(wxWebViewZoomType zoomType) 350{ 351 // there is only one supported zoom type at the moment so this setter 352 // does nothing beyond checking sanity 353 wxASSERT(zoomType == wxWEBVIEW_ZOOM_TYPE_LAYOUT); 354} 355 356wxWebViewZoomType wxWebViewWebKit::GetZoomType() const 357{ 358 return wxWEBVIEW_ZOOM_TYPE_LAYOUT; 359} 360 361bool wxWebViewWebKit::CanSetZoomType(wxWebViewZoomType type) const 362{ 363 switch (type) 364 { 365 // for now that's the only one that is supported 366 case wxWEBVIEW_ZOOM_TYPE_LAYOUT: 367 return true; 368 369 default: 370 return false; 371 } 372} 373 374bool wxWebViewWebKit::RunScriptSync(const wxString& javascript, wxString* output) const 375{ 376 __block bool scriptExecuted = false; 377 __block wxString outputStr; 378 __block bool scriptSuccess = false; 379 380 // Start script execution 381 [m_webView evaluateJavaScript:wxCFStringRef(javascript).AsNSString() 382 completionHandler:^(id _Nullable obj, NSError * _Nullable error) { 383 if (error) 384 { 385 outputStr.assign(wxCFStringRef(error.localizedFailureReason).AsString()); 386 } 387 else 388 { 389 if ([obj isKindOfClass:[NSNumber class]]) 390 { 391 NSNumber* num = (NSNumber*) obj; 392 CFTypeID numID = CFGetTypeID((__bridge CFTypeRef)(num)); 393 if (numID == CFBooleanGetTypeID()) 394 outputStr = num.boolValue ? "true" : "false"; 395 else 396 outputStr = wxCFStringRef::AsString(num.stringValue); 397 } 398 else if (obj) 399 outputStr.assign(wxCFStringRef::AsString([NSString stringWithFormat:@"%@", obj])); 400 401 scriptSuccess = true; 402 } 403 404 scriptExecuted = true; 405 }]; 406 407 // Wait for script exection 408 while (!scriptExecuted) 409 wxYield(); 410 411 if (output) 412 output->assign(outputStr); 413 414 return scriptSuccess; 415} 416 417bool wxWebViewWebKit::RunScript(const wxString& javascript, wxString* output) const 418{ 419 wxJSScriptWrapper wrapJS(javascript, &m_runScriptCount); 420 421 // This string is also used as an error indicator: it's cleared if there is 422 // no error or used in the warning message below if there is one. 423 wxString result; 424 if (RunScriptSync(wrapJS.GetWrappedCode(), &result) 425 && result == wxS("true")) 426 { 427 if (RunScriptSync(wrapJS.GetOutputCode() + ";", &result)) 428 { 429 if (output) 430 *output = result; 431 result.clear(); 432 } 433 434 RunScriptSync(wrapJS.GetCleanUpCode()); 435 } 436 437 if (!result.empty()) 438 { 439 wxLogWarning(_("Error running JavaScript: %s"), result); 440 return false; 441 } 442 443 return true; 444} 445 446bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name) 447{ 448 [m_webView.configuration.userContentController addScriptMessageHandler: 449 [[WebViewScriptMessageHandler alloc] initWithWxWindow:this] name:wxCFStringRef(name).AsNSString()]; 450 // Make webkit message handler available under common name 451 wxString js = wxString::Format("window.%s = window.webkit.messageHandlers.%s;", 452 name, name); 453 AddUserScript(js); 454 RunScript(js); 455 return true; 456} 457 458bool wxWebViewWebKit::RemoveScriptMessageHandler(const wxString& name) 459{ 460 [m_webView.configuration.userContentController removeScriptMessageHandlerForName:wxCFStringRef(name).AsNSString()]; 461 return true; 462} 463 464bool wxWebViewWebKit::AddUserScript(const wxString& javascript, 465 wxWebViewUserScriptInjectionTime injectionTime) 466{ 467 WKUserScript* userScript = 468 [[WKUserScript alloc] initWithSource:wxCFStringRef(javascript).AsNSString() 469 injectionTime:(injectionTime == wxWEBVIEW_INJECT_AT_DOCUMENT_START) ? 470 WKUserScriptInjectionTimeAtDocumentStart : WKUserScriptInjectionTimeAtDocumentEnd 471 forMainFrameOnly:NO]; 472 [m_webView.configuration.userContentController addUserScript:userScript]; 473 return true; 474} 475 476void wxWebViewWebKit::RemoveAllUserScripts() 477{ 478 [m_webView.configuration.userContentController removeAllUserScripts]; 479} 480 481void wxWebViewWebKit::LoadURL(const wxString& url) 482{ 483 [m_webView loadRequest:[NSURLRequest requestWithURL: 484 [NSURL URLWithString:wxCFStringRef(url).AsNSString()]]]; 485} 486 487wxString wxWebViewWebKit::GetCurrentURL() const 488{ 489 return wxCFStringRef::AsString(m_webView.URL.absoluteString); 490} 491 492wxString wxWebViewWebKit::GetCurrentTitle() const 493{ 494 return wxCFStringRef::AsString(m_webView.title); 495} 496 497float wxWebViewWebKit::GetZoomFactor() const 498{ 499 return m_webView.magnification; 500} 501 502void wxWebViewWebKit::SetZoomFactor(float zoom) 503{ 504 m_webView.magnification = zoom; 505} 506 507void wxWebViewWebKit::DoSetPage(const wxString& src, const wxString& baseUrl) 508{ 509 if ( !m_webView ) 510 return; 511 512 [m_webView loadHTMLString:wxCFStringRef( src ).AsNSString() 513 baseURL:[NSURL URLWithString: 514 wxCFStringRef( baseUrl ).AsNSString()]]; 515} 516 517void wxWebViewWebKit::EnableHistory(bool WXUNUSED(enable)) 518{ 519 if ( !m_webView ) 520 return; 521 522 // TODO: implement 523} 524 525void wxWebViewWebKit::ClearHistory() 526{ 527 // TODO: implement 528} 529 530wxVector<wxSharedPtr<wxWebViewHistoryItem> > wxWebViewWebKit::GetBackwardHistory() 531{ 532 wxVector<wxSharedPtr<wxWebViewHistoryItem> > backhist; 533 WKBackForwardList* history = [m_webView backForwardList]; 534 int count = history.backList.count; 535 for(int i = -count; i < 0; i++) 536 { 537 WKBackForwardListItem* item = [history itemAtIndex:i]; 538 wxString url = wxCFStringRef::AsString(item.URL.absoluteString); 539 wxString title = wxCFStringRef::AsString([item title]); 540 wxWebViewHistoryItem* wxitem = new wxWebViewHistoryItem(url, title); 541 wxitem->m_histItem = item; 542 wxSharedPtr<wxWebViewHistoryItem> itemptr(wxitem); 543 backhist.push_back(itemptr); 544 } 545 return backhist; 546} 547 548wxVector<wxSharedPtr<wxWebViewHistoryItem> > wxWebViewWebKit::GetForwardHistory() 549{ 550 wxVector<wxSharedPtr<wxWebViewHistoryItem> > forwardhist; 551 WKBackForwardList* history = [m_webView backForwardList]; 552 int count = history.forwardList.count; 553 for(int i = 1; i <= count; i++) 554 { 555 WKBackForwardListItem* item = [history itemAtIndex:i]; 556 wxString url = wxCFStringRef::AsString(item.URL.absoluteString); 557 wxString title = wxCFStringRef::AsString([item title]); 558 wxWebViewHistoryItem* wxitem = new wxWebViewHistoryItem(url, title); 559 wxitem->m_histItem = item; 560 wxSharedPtr<wxWebViewHistoryItem> itemptr(wxitem); 561 forwardhist.push_back(itemptr); 562 } 563 return forwardhist; 564} 565 566void wxWebViewWebKit::LoadHistoryItem(wxSharedPtr<wxWebViewHistoryItem> item) 567{ 568 [m_webView goToBackForwardListItem:item->m_histItem]; 569} 570 571void wxWebViewWebKit::Paste() 572{ 573#if defined(__WXOSX_IPHONE__) 574 wxWebView::Paste(); 575#else 576 // The default (javascript) implementation presents the user with a popup 577 // menu containing a single 'Paste' menu item. 578 // Send this action to directly paste as expected. 579 [[NSApplication sharedApplication] sendAction:@selector(paste:) to:nil from:m_webView]; 580#endif 581} 582 583bool wxWebViewWebKit::CanUndo() const 584{ 585 return [[m_webView undoManager] canUndo]; 586} 587 588bool wxWebViewWebKit::CanRedo() const 589{ 590 return [[m_webView undoManager] canRedo]; 591} 592 593void wxWebViewWebKit::Undo() 594{ 595 [[m_webView undoManager] undo]; 596} 597 598void wxWebViewWebKit::Redo() 599{ 600 [[m_webView undoManager] redo]; 601} 602 603void wxWebViewWebKit::RegisterHandler(wxSharedPtr<wxWebViewHandler> handler) 604{ 605 m_handlers[handler->GetName()] = handler; 606} 607 608//------------------------------------------------------------ 609// Listener interfaces 610//------------------------------------------------------------ 611 612// NB: I'm still tracking this down, but it appears the Cocoa window 613// still has these events fired on it while the Carbon control is being 614// destroyed. Therefore, we must be careful to check both the existence 615// of the Carbon control and the event handler before firing events. 616 617@implementation WXWKWebView 618 619- (void)setWebView:(wxWebViewWebKit *)webView 620{ 621 m_webView = webView; 622} 623 624#if !defined(__WXOSX_IPHONE__) 625- (void)willOpenMenu:(NSMenu *)menu withEvent:(NSEvent *)event 626{ 627 if (m_webView && !m_webView->IsContextMenuEnabled()) 628 [menu removeAllItems]; 629} 630 631-(id)validRequestorForSendType:(NSString*)sendType returnType:(NSString*)returnType 632{ 633 if (m_webView && !m_webView->IsContextMenuEnabled()) 634 return nil; 635 else 636 return [super validRequestorForSendType:sendType returnType:returnType]; 637} 638 639- (BOOL)performKeyEquivalent:(NSEvent *)event 640{ 641 if ([event modifierFlags] & NSCommandKeyMask) 642 { 643 switch ([event.characters characterAtIndex:0]) 644 { 645 case 'a': 646 [self selectAll:nil]; 647 return YES; 648 case 'c': 649 m_webView->Copy(); 650 return YES; 651 case 'v': 652 m_webView->Paste(); 653 return YES; 654 case 'x': 655 m_webView->Cut(); 656 return YES; 657 } 658 } 659 660 return [super performKeyEquivalent:event]; 661} 662#endif // !defined(__WXOSX_IPHONE__) 663 664@end 665 666@implementation WebViewNavigationDelegate 667 668- (id)initWithWxWindow: (wxWebViewWebKit*)inWindow 669{ 670 if (self = [super init]) 671 { 672 webKitWindow = inWindow; // non retained 673 } 674 return self; 675} 676 677- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context 678{ 679 if (context == webKitWindow) 680 { 681 wxWebViewEvent event(wxEVT_WEBVIEW_TITLE_CHANGED, 682 webKitWindow->GetId(), 683 webKitWindow->GetCurrentURL(), 684 ""); 685 686 event.SetString(webKitWindow->GetCurrentTitle()); 687 688 webKitWindow->ProcessWindowEvent(event); 689 } 690 else 691 [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 692} 693 694- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation 695{ 696 if (webKitWindow){ 697 NSString *url = webView.URL.absoluteString; 698 wxWebViewEvent event(wxEVT_WEBVIEW_NAVIGATED, 699 webKitWindow->GetId(), 700 wxCFStringRef::AsString( url ), 701 ""); 702 703 if (webKitWindow && webKitWindow->GetEventHandler()) 704 webKitWindow->GetEventHandler()->ProcessEvent(event); 705 } 706} 707 708- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation 709{ 710 if (webKitWindow){ 711 NSString *url = webView.URL.absoluteString; 712 713 wxWebViewEvent event(wxEVT_WEBVIEW_LOADED, 714 webKitWindow->GetId(), 715 wxCFStringRef::AsString( url ), 716 ""); 717 718 if (webKitWindow && webKitWindow->GetEventHandler()) 719 webKitWindow->GetEventHandler()->ProcessEvent(event); 720 } 721} 722 723wxString nsErrorToWxHtmlError(NSError* error, wxWebViewNavigationError* out) 724{ 725 *out = wxWEBVIEW_NAV_ERR_OTHER; 726 727 if ([[error domain] isEqualToString:NSURLErrorDomain]) 728 { 729 switch ([error code]) 730 { 731 case NSURLErrorCannotFindHost: 732 case NSURLErrorFileDoesNotExist: 733 case NSURLErrorRedirectToNonExistentLocation: 734 *out = wxWEBVIEW_NAV_ERR_NOT_FOUND; 735 break; 736 737 case NSURLErrorResourceUnavailable: 738 case NSURLErrorHTTPTooManyRedirects: 739 case NSURLErrorDataLengthExceedsMaximum: 740 case NSURLErrorBadURL: 741 case NSURLErrorFileIsDirectory: 742 *out = wxWEBVIEW_NAV_ERR_REQUEST; 743 break; 744 745 case NSURLErrorTimedOut: 746 case NSURLErrorDNSLookupFailed: 747 case NSURLErrorNetworkConnectionLost: 748 case NSURLErrorCannotConnectToHost: 749 case NSURLErrorNotConnectedToInternet: 750 //case NSURLErrorInternationalRoamingOff: 751 //case NSURLErrorCallIsActive: 752 //case NSURLErrorDataNotAllowed: 753 *out = wxWEBVIEW_NAV_ERR_CONNECTION; 754 break; 755 756 case NSURLErrorCancelled: 757 case NSURLErrorUserCancelledAuthentication: 758 *out = wxWEBVIEW_NAV_ERR_USER_CANCELLED; 759 break; 760 761 case NSURLErrorCannotDecodeRawData: 762 case NSURLErrorCannotDecodeContentData: 763 case NSURLErrorCannotParseResponse: 764 case NSURLErrorBadServerResponse: 765 *out = wxWEBVIEW_NAV_ERR_REQUEST; 766 break; 767 768 case NSURLErrorUserAuthenticationRequired: 769 case NSURLErrorSecureConnectionFailed: 770 case NSURLErrorClientCertificateRequired: 771 *out = wxWEBVIEW_NAV_ERR_AUTH; 772 break; 773 774 case NSURLErrorNoPermissionsToReadFile: 775 *out = wxWEBVIEW_NAV_ERR_SECURITY; 776 break; 777 778 case NSURLErrorServerCertificateHasBadDate: 779 case NSURLErrorServerCertificateUntrusted: 780 case NSURLErrorServerCertificateHasUnknownRoot: 781 case NSURLErrorServerCertificateNotYetValid: 782 case NSURLErrorClientCertificateRejected: 783 *out = wxWEBVIEW_NAV_ERR_CERTIFICATE; 784 break; 785 } 786 } 787 788 wxString message = wxCFStringRef::AsString([error localizedDescription]); 789 NSString* detail = [error localizedFailureReason]; 790 if (detail != NULL) 791 { 792 message = message + " (" + wxCFStringRef::AsString(detail) + ")"; 793 } 794 return message; 795} 796 797- (void)webView:(WKWebView *)webView 798 didFailNavigation:(WKNavigation *)navigation 799 withError:(NSError *)error; 800{ 801 if (webKitWindow){ 802 NSString *url = webView.URL.absoluteString; 803 804 wxWebViewNavigationError type; 805 wxString description = nsErrorToWxHtmlError(error, &type); 806 wxWebViewEvent event(wxEVT_WEBVIEW_ERROR, 807 webKitWindow->GetId(), 808 wxCFStringRef::AsString( url ), 809 wxEmptyString); 810 event.SetString(description); 811 event.SetInt(type); 812 813 if (webKitWindow && webKitWindow->GetEventHandler()) 814 { 815 webKitWindow->GetEventHandler()->ProcessEvent(event); 816 } 817 } 818} 819 820- (void)webView:(WKWebView *)webView 821 didFailProvisionalNavigation:(WKNavigation *)navigation 822 withError:(NSError *)error; 823{ 824 if (webKitWindow){ 825 NSString *url = webView.URL.absoluteString; 826 827 wxWebViewNavigationError type; 828 wxString description = nsErrorToWxHtmlError(error, &type); 829 wxWebViewEvent event(wxEVT_WEBVIEW_ERROR, 830 webKitWindow->GetId(), 831 wxCFStringRef::AsString( url ), 832 wxEmptyString); 833 event.SetString(description); 834 event.SetInt(type); 835 836 if (webKitWindow && webKitWindow->GetEventHandler()) 837 webKitWindow->GetEventHandler()->ProcessEvent(event); 838 } 839} 840 841- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction 842 decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 843{ 844 NSString *url = [[navigationAction.request URL] absoluteString]; 845 wxWebViewNavigationActionFlags navFlags = 846 navigationAction.navigationType == WKNavigationTypeLinkActivated ? 847 wxWEBVIEW_NAV_ACTION_USER : 848 wxWEBVIEW_NAV_ACTION_OTHER; 849 850 wxWebViewEvent event(wxEVT_WEBVIEW_NAVIGATING, 851 webKitWindow->GetId(), 852 wxCFStringRef::AsString( url ), "", navFlags); 853 854 if (webKitWindow && webKitWindow->GetEventHandler()) 855 webKitWindow->GetEventHandler()->ProcessEvent(event); 856 857 decisionHandler(event.IsAllowed() ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel); 858} 859 860@end 861 862#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 863@implementation WebViewCustomProtocol 864 865- (id)initWithHandler:(wxWebViewHandler *)handler 866{ 867 m_handler = handler; 868 return self; 869} 870 871- (void)webView:(WKWebView *)webView startURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask 872WX_API_AVAILABLE_MACOS(10, 13) 873{ 874 NSURLRequest *request = urlSchemeTask.request; 875 NSString* path = [[request URL] absoluteString]; 876 877 wxString wxpath = wxCFStringRef::AsString(path); 878 879 wxFSFile* file = m_handler->GetFile(wxpath); 880 881 if (!file) 882 { 883 NSError *error = [[NSError alloc] initWithDomain:NSURLErrorDomain 884 code:NSURLErrorFileDoesNotExist 885 userInfo:nil]; 886 887 [urlSchemeTask didFailWithError:error]; 888 889 [error release]; 890 891 return; 892 } 893 894 size_t length = file->GetStream()->GetLength(); 895 896 897 NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[request URL] 898 MIMEType:wxCFStringRef(file->GetMimeType()).AsNSString() 899 expectedContentLength:length 900 textEncodingName:nil]; 901 902 //Load the data, we malloc it so it is tidied up properly 903 void* buffer = malloc(length); 904 file->GetStream()->Read(buffer, length); 905 NSData *data = [[NSData alloc] initWithBytesNoCopy:buffer length:length]; 906 907 //Set the data 908 [urlSchemeTask didReceiveResponse:response]; 909 [urlSchemeTask didReceiveData:data]; 910 911 //Notify that we have finished 912 [urlSchemeTask didFinish]; 913 914 [data release]; 915 916 [response release]; 917} 918 919- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask 920WX_API_AVAILABLE_MACOS(10, 13) 921{ 922 923} 924 925@end 926#endif // macOS 10.13+ 927 928 929@implementation WebViewUIDelegate 930 931- (id)initWithWxWindow: (wxWebViewWebKit*)inWindow 932{ 933 if (self = [super init]) 934 { 935 webKitWindow = inWindow; // non retained 936 } 937 return self; 938} 939 940- (WKWebView *)webView:(WKWebView *)webView 941 createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration 942 forNavigationAction:(WKNavigationAction *)navigationAction 943 windowFeatures:(WKWindowFeatures *)windowFeatures 944{ 945 wxWebViewNavigationActionFlags navFlags = 946 navigationAction.navigationType == WKNavigationTypeLinkActivated ? 947 wxWEBVIEW_NAV_ACTION_USER : 948 wxWEBVIEW_NAV_ACTION_OTHER; 949 950 wxWebViewEvent event(wxEVT_WEBVIEW_NEWWINDOW, 951 webKitWindow->GetId(), 952 wxCFStringRef::AsString( navigationAction.request.URL.absoluteString ), 953 "", navFlags); 954 955 if (webKitWindow && webKitWindow->GetEventHandler()) 956 webKitWindow->GetEventHandler()->ProcessEvent(event); 957 958 return nil; 959} 960 961- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message 962 initiatedByFrame:(WKFrameInfo *)frame 963 completionHandler:(void (^)())completionHandler 964{ 965 wxMessageDialog dlg(webKitWindow->GetParent(), wxCFStringRef::AsString(message)); 966 dlg.ShowModal(); 967 completionHandler(); 968} 969 970- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message 971 initiatedByFrame:(WKFrameInfo *)frame 972 completionHandler:(void (^)(BOOL))completionHandler 973{ 974 wxMessageDialog dlg(webKitWindow->GetParent(), wxCFStringRef::AsString(message), 975 wxMessageBoxCaptionStr, wxOK|wxCANCEL|wxCENTRE); 976 completionHandler(dlg.ShowModal() == wxID_OK); 977} 978 979- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt 980 defaultText:(NSString *)defaultText 981 initiatedByFrame:(WKFrameInfo *)frame 982 completionHandler:(void (^)(NSString * _Nullable))completionHandler 983{ 984 wxString resultText; 985 wxTextEntryDialog dlg(webKitWindow->GetParent(), wxCFStringRef::AsString(prompt), 986 wxGetTextFromUserPromptStr, wxCFStringRef::AsString(defaultText)); 987 if (dlg.ShowModal() == wxID_OK) 988 resultText = dlg.GetValue(); 989 990 completionHandler(wxCFStringRef(resultText).AsNSString()); 991} 992 993#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 994- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters 995 initiatedByFrame:(WKFrameInfo *)frame 996 completionHandler:(void (^)(NSArray<NSURL *> * _Nullable))completionHandler 997WX_API_AVAILABLE_MACOS(10, 12) 998{ 999 long style = wxFD_OPEN | wxFD_FILE_MUST_EXIST; 1000 if (parameters.allowsMultipleSelection) 1001 style |= wxFD_MULTIPLE; 1002 1003 wxFileDialog dlg(webKitWindow->GetParent(), wxFileSelectorPromptStr, "", "", 1004 wxFileSelectorDefaultWildcardStr, style); 1005 if (dlg.ShowModal() == wxID_OK) 1006 { 1007 wxArrayString filePaths; 1008 dlg.GetPaths(filePaths); 1009 NSMutableArray* urls = [[NSMutableArray alloc] init]; 1010 for (wxArrayString::iterator it = filePaths.begin(); it != filePaths.end(); it++) 1011 [urls addObject:[NSURL fileURLWithPath:wxCFStringRef(*it).AsNSString()]]; 1012 completionHandler(urls); 1013 [urls release]; 1014 } 1015 else 1016 completionHandler(nil); 1017} 1018#endif // __MAC_OS_X_VERSION_MAX_ALLOWED 1019 1020// The following WKUIDelegateMethods are undocumented as of macOS SDK 11.0, 1021// but are documented in the WebKit cocoa interface headers: 1022// https://github.com/WebKit/WebKit/blob/main/Source/WebKit/UIProcess/API/Cocoa/WKUIDelegatePrivate.h 1023 1024- (void)_webView:(WKWebView *)webView printFrame:(WKFrameInfo*)frame 1025{ 1026 webKitWindow->Print(); 1027} 1028 1029- (void)SendFullscreenChangedEvent:(int)status 1030{ 1031 wxWebViewEvent event(wxEVT_WEBVIEW_FULLSCREEN_CHANGED, webKitWindow->GetId(), 1032 webKitWindow->GetCurrentURL(), wxString()); 1033 event.SetEventObject(webKitWindow); 1034 event.SetInt(status); 1035 webKitWindow->HandleWindowEvent(event); 1036} 1037 1038- (void)_webViewDidEnterFullscreen:(WKWebView *)webView 1039{ 1040 [self SendFullscreenChangedEvent:1]; 1041} 1042 1043- (void)_webViewDidExitFullscreen:(WKWebView *)webView 1044{ 1045 [self SendFullscreenChangedEvent:0]; 1046} 1047 1048@end 1049 1050@implementation WebViewScriptMessageHandler 1051 1052- (id)initWithWxWindow: (wxWebViewWebKit*)inWindow 1053{ 1054 if (self = [super init]) 1055 { 1056 webKitWindow = inWindow; // non retained 1057 } 1058 return self; 1059} 1060 1061- (void)userContentController:(nonnull WKUserContentController *)userContentController 1062 didReceiveScriptMessage:(nonnull WKScriptMessage *)message 1063{ 1064 wxWebViewEvent event(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, 1065 webKitWindow->GetId(), 1066 webKitWindow->GetCurrentURL(), 1067 "", 1068 wxWEBVIEW_NAV_ACTION_NONE, 1069 wxCFStringRef::AsString(message.name)); 1070 if ([message.body isKindOfClass:NSString.class]) 1071 event.SetString(wxCFStringRef::AsString(message.body)); 1072 else if ([message.body isKindOfClass:NSNumber.class]) 1073 event.SetString(wxCFStringRef::AsString(((NSNumber*)message.body).stringValue)); 1074 else if ([message.body isKindOfClass:NSDate.class]) 1075 event.SetString(wxCFStringRef::AsString(((NSDate*)message.body).description)); 1076 else if ([message.body isKindOfClass:NSNull.class]) 1077 event.SetString("null"); 1078 else if ([message.body isKindOfClass:NSDictionary.class] || [message.body isKindOfClass:NSArray.class]) 1079 { 1080 NSError* error = nil; 1081 NSData* jsonData = [NSJSONSerialization dataWithJSONObject:message.body options:0 error:&error]; 1082 NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 1083 event.SetString(wxCFStringRef::AsString(jsonString)); 1084 } 1085 1086 webKitWindow->ProcessWindowEvent(event); 1087} 1088 1089@end 1090 1091#endif //wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT 1092