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