VST3: fetch midi mappings all at once, use it for note/sound-off
[carla.git] / source / modules / juce_gui_extra / native / juce_mac_WebBrowserComponent.mm
blob6f4c5b20b2b72b9db30710e8c0f1c4dfebb6f407
1 /*\r
2   ==============================================================================\r
3 \r
4    This file is part of the JUCE library.\r
5    Copyright (c) 2022 - Raw Material Software Limited\r
6 \r
7    JUCE is an open source library subject to commercial or open-source\r
8    licensing.\r
9 \r
10    By using JUCE, you agree to the terms of both the JUCE 7 End-User License\r
11    Agreement and JUCE Privacy Policy.\r
13    End User License Agreement: www.juce.com/juce-7-licence\r
14    Privacy Policy: www.juce.com/juce-privacy-policy\r
16    Or: You may also use this code under the terms of the GPL v3 (see\r
17    www.gnu.org/licenses).\r
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER\r
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE\r
21    DISCLAIMED.\r
23   ==============================================================================\r
24 */\r
26 namespace juce\r
27 {\r
29 #if JUCE_MAC && defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12\r
30  #define WKWEBVIEW_OPENPANEL_SUPPORTED 1\r
31 #endif\r
33 static NSURL* appendParametersToFileURL (const URL& url, NSURL* fileUrl)\r
34 {\r
35     if (@available (macOS 10.10, *))\r
36     {\r
37         const auto parameterNames = url.getParameterNames();\r
38         const auto parameterValues = url.getParameterValues();\r
40         jassert (parameterNames.size() == parameterValues.size());\r
42         if (parameterNames.isEmpty())\r
43             return fileUrl;\r
45         NSUniquePtr<NSURLComponents> components ([[NSURLComponents alloc] initWithURL: fileUrl resolvingAgainstBaseURL: NO]);\r
46         NSUniquePtr<NSMutableArray> queryItems ([[NSMutableArray alloc] init]);\r
48         for (int i = 0; i < parameterNames.size(); ++i)\r
49             [queryItems.get() addObject: [NSURLQueryItem queryItemWithName: juceStringToNS (parameterNames[i])\r
50                                                                      value: juceStringToNS (parameterValues[i])]];\r
52         [components.get() setQueryItems: queryItems.get()];\r
54         return [components.get() URL];\r
55     }\r
57     const auto queryString = url.getQueryString();\r
59     if (queryString.isNotEmpty())\r
60         if (NSString* fileUrlString = [fileUrl absoluteString])\r
61             return [NSURL URLWithString: [fileUrlString stringByAppendingString: juceStringToNS (queryString)]];\r
63     return fileUrl;\r
64 }\r
66 static NSMutableURLRequest* getRequestForURL (const String& url, const StringArray* headers, const MemoryBlock* postData)\r
67 {\r
68     NSString* urlString = juceStringToNS (url);\r
70      if (@available (macOS 10.9, *))\r
71      {\r
72          urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters: [NSCharacterSet URLQueryAllowedCharacterSet]];\r
73      }\r
74      else\r
75      {\r
76          JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")\r
77          urlString = [urlString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];\r
78          JUCE_END_IGNORE_WARNINGS_GCC_LIKE\r
79      }\r
81      if (NSURL* nsURL = [NSURL URLWithString: urlString])\r
82      {\r
83          NSMutableURLRequest* r\r
84              = [NSMutableURLRequest requestWithURL: nsURL\r
85                                        cachePolicy: NSURLRequestUseProtocolCachePolicy\r
86                                    timeoutInterval: 30.0];\r
88          if (postData != nullptr && postData->getSize() > 0)\r
89          {\r
90              [r setHTTPMethod: nsStringLiteral ("POST")];\r
91              [r setHTTPBody: [NSData dataWithBytes: postData->getData()\r
92                                             length: postData->getSize()]];\r
93          }\r
95          if (headers != nullptr)\r
96          {\r
97              for (int i = 0; i < headers->size(); ++i)\r
98              {\r
99                  auto headerName  = (*headers)[i].upToFirstOccurrenceOf (":", false, false).trim();\r
100                  auto headerValue = (*headers)[i].fromFirstOccurrenceOf (":", false, false).trim();\r
102                  [r setValue: juceStringToNS (headerValue)\r
103                     forHTTPHeaderField: juceStringToNS (headerName)];\r
104              }\r
105          }\r
107          return r;\r
108      }\r
110     return nullptr;\r
113 #if JUCE_MAC\r
114 template <class WebViewClass>\r
115 struct WebViewKeyEquivalentResponder : public ObjCClass<WebViewClass>\r
117     WebViewKeyEquivalentResponder()\r
118         : ObjCClass<WebViewClass> ("WebViewKeyEquivalentResponder_")\r
119     {\r
120         ObjCClass<WebViewClass>::addMethod (@selector (performKeyEquivalent:), performKeyEquivalent);\r
121         ObjCClass<WebViewClass>::registerClass();\r
122     }\r
124 private:\r
125     static BOOL performKeyEquivalent (id self, SEL selector, NSEvent* event)\r
126     {\r
127         const auto isCommandDown = [event]\r
128         {\r
129             const auto modifierFlags = [event modifierFlags];\r
131            #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12\r
132             if (@available (macOS 10.12, *))\r
133                 return (modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagCommand;\r
134            #endif\r
136             JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")\r
137             return (modifierFlags & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask;\r
138             JUCE_END_IGNORE_WARNINGS_GCC_LIKE\r
139         }();\r
141         if (isCommandDown)\r
142         {\r
143             auto sendAction = [&] (SEL actionSelector) -> BOOL\r
144             {\r
145                 return [NSApp sendAction: actionSelector\r
146                                       to: [[self window] firstResponder]\r
147                                     from: self];\r
148             };\r
150             if ([[event charactersIgnoringModifiers] isEqualToString: @"x"])  return sendAction (@selector (cut:));\r
151             if ([[event charactersIgnoringModifiers] isEqualToString: @"c"])  return sendAction (@selector (copy:));\r
152             if ([[event charactersIgnoringModifiers] isEqualToString: @"v"])  return sendAction (@selector (paste:));\r
153             if ([[event charactersIgnoringModifiers] isEqualToString: @"a"])  return sendAction (@selector (selectAll:));\r
154         }\r
156         return ObjCClass<WebViewClass>::template sendSuperclassMessage<BOOL> (self, selector, event);\r
157     }\r
158 };\r
160 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")\r
161 struct DownloadClickDetectorClass  : public ObjCClass<NSObject>\r
163     DownloadClickDetectorClass()  : ObjCClass<NSObject> ("JUCEWebClickDetector_")\r
164     {\r
165         addIvar<WebBrowserComponent*> ("owner");\r
167         addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:), decidePolicyForNavigationAction);\r
168         addMethod (@selector (webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:), decidePolicyForNewWindowAction);\r
169         addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame);\r
170         addMethod (@selector (webView:didFailLoadWithError:forFrame:),  didFailLoadWithError);\r
171         addMethod (@selector (webView:didFailProvisionalLoadWithError:forFrame:),  didFailLoadWithError);\r
172         addMethod (@selector (webView:willCloseFrame:), willCloseFrame);\r
173         addMethod (@selector (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:), runOpenPanel);\r
175         registerClass();\r
176     }\r
178     static void setOwner (id self, WebBrowserComponent* owner)   { object_setInstanceVariable (self, "owner", owner); }\r
179     static WebBrowserComponent* getOwner (id self)               { return getIvar<WebBrowserComponent*> (self, "owner"); }\r
181 private:\r
182     static String getOriginalURL (NSDictionary* actionInformation)\r
183     {\r
184         if (NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")])\r
185             return nsStringToJuce ([url absoluteString]);\r
187         return {};\r
188     }\r
190     static void decidePolicyForNavigationAction (id self, SEL, WebView*, NSDictionary* actionInformation,\r
191                                                  NSURLRequest*, WebFrame*, id<WebPolicyDecisionListener> listener)\r
192     {\r
193         if (getOwner (self)->pageAboutToLoad (getOriginalURL (actionInformation)))\r
194             [listener use];\r
195         else\r
196             [listener ignore];\r
197     }\r
199     static void decidePolicyForNewWindowAction (id self, SEL, WebView*, NSDictionary* actionInformation,\r
200                                                 NSURLRequest*, NSString*, id<WebPolicyDecisionListener> listener)\r
201     {\r
202         getOwner (self)->newWindowAttemptingToLoad (getOriginalURL (actionInformation));\r
203         [listener ignore];\r
204     }\r
206     static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame)\r
207     {\r
208         if ([frame isEqual: [sender mainFrame]])\r
209         {\r
210             NSURL* url = [[[frame dataSource] request] URL];\r
211             getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString]));\r
212         }\r
213     }\r
215     static void didFailLoadWithError (id self, SEL, WebView* sender, NSError* error, WebFrame* frame)\r
216     {\r
217         if ([frame isEqual: [sender mainFrame]] && error != nullptr && [error code] != NSURLErrorCancelled)\r
218         {\r
219             auto errorString = nsStringToJuce ([error localizedDescription]);\r
220             bool proceedToErrorPage = getOwner (self)->pageLoadHadNetworkError (errorString);\r
222             // WebKit doesn't have an internal error page, so make a really simple one ourselves\r
223             if (proceedToErrorPage)\r
224                 getOwner (self)->goToURL ("data:text/plain;charset=UTF-8," + errorString);\r
225         }\r
226     }\r
228     static void willCloseFrame (id self, SEL, WebView*, WebFrame*)\r
229     {\r
230         getOwner (self)->windowCloseRequest();\r
231     }\r
233     static void runOpenPanel (id, SEL, WebView*, id<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles)\r
234     {\r
235         struct DeletedFileChooserWrapper   : private DeletedAtShutdown\r
236         {\r
237             DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, id<WebOpenPanelResultListener> rl)\r
238                 : chooser (std::move (fc)), listener (rl)\r
239             {\r
240                 [listener.get() retain];\r
241             }\r
243             std::unique_ptr<FileChooser> chooser;\r
244             ObjCObjectHandle<id<WebOpenPanelResultListener>> listener;\r
245         };\r
247         auto chooser = std::make_unique<FileChooser> (TRANS("Select the file you want to upload..."),\r
248                                                       File::getSpecialLocation (File::userHomeDirectory), "*");\r
249         auto* wrapper = new DeletedFileChooserWrapper (std::move (chooser), resultListener);\r
251         auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles\r
252                     | (allowMultipleFiles ? FileBrowserComponent::canSelectMultipleItems : 0);\r
254         wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&)\r
255         {\r
256             for (auto& f : wrapper->chooser->getResults())\r
257                 [wrapper->listener.get() chooseFilename: juceStringToNS (f.getFullPathName())];\r
259             delete wrapper;\r
260         });\r
261     }\r
262 };\r
263 JUCE_END_IGNORE_WARNINGS_GCC_LIKE\r
264 #endif\r
266 struct API_AVAILABLE (macos (10.10)) WebViewDelegateClass  : public ObjCClass<NSObject>\r
268     WebViewDelegateClass()  : ObjCClass<NSObject> ("JUCEWebViewDelegate_")\r
269     {\r
270         addIvar<WebBrowserComponent*> ("owner");\r
272         addMethod (@selector (webView:decidePolicyForNavigationAction:decisionHandler:),  decidePolicyForNavigationAction);\r
273         addMethod (@selector (webView:didFinishNavigation:),                              didFinishNavigation);\r
274         addMethod (@selector (webView:didFailNavigation:withError:),                      didFailNavigation);\r
275         addMethod (@selector (webView:didFailProvisionalNavigation:withError:),           didFailProvisionalNavigation);\r
276         addMethod (@selector (webViewDidClose:),                                          webViewDidClose);\r
277         addMethod (@selector (webView:createWebViewWithConfiguration:forNavigationAction:\r
278                               windowFeatures:),                                           createWebView);\r
280        #if WKWEBVIEW_OPENPANEL_SUPPORTED\r
281         if (@available (macOS 10.12, *))\r
282             addMethod (@selector (webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:), runOpenPanel);\r
283        #endif\r
285         registerClass();\r
286     }\r
288     static void setOwner (id self, WebBrowserComponent* owner)   { object_setInstanceVariable (self, "owner", owner); }\r
289     static WebBrowserComponent* getOwner (id self)               { return getIvar<WebBrowserComponent*> (self, "owner"); }\r
291 private:\r
292     static void decidePolicyForNavigationAction (id self, SEL, WKWebView*, WKNavigationAction* navigationAction,\r
293                                                  void (^decisionHandler)(WKNavigationActionPolicy))\r
294     {\r
295         if (getOwner (self)->pageAboutToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString])))\r
296             decisionHandler (WKNavigationActionPolicyAllow);\r
297         else\r
298             decisionHandler (WKNavigationActionPolicyCancel);\r
299     }\r
301     static void didFinishNavigation (id self, SEL, WKWebView* webview, WKNavigation*)\r
302     {\r
303         getOwner (self)->pageFinishedLoading (nsStringToJuce ([[webview URL] absoluteString]));\r
304     }\r
306     static void displayError (WebBrowserComponent* owner, NSError* error)\r
307     {\r
308         if ([error code] != NSURLErrorCancelled)\r
309         {\r
310             auto errorString = nsStringToJuce ([error localizedDescription]);\r
311             bool proceedToErrorPage = owner->pageLoadHadNetworkError (errorString);\r
313             // WKWebView doesn't have an internal error page, so make a really simple one ourselves\r
314             if (proceedToErrorPage)\r
315                 owner->goToURL ("data:text/plain;charset=UTF-8," + errorString);\r
316         }\r
317     }\r
319     static void didFailNavigation (id self, SEL, WKWebView*, WKNavigation*, NSError* error)\r
320     {\r
321         displayError (getOwner (self), error);\r
322     }\r
324     static void didFailProvisionalNavigation (id self, SEL, WKWebView*, WKNavigation*, NSError* error)\r
325     {\r
326         displayError (getOwner (self), error);\r
327     }\r
329     static void webViewDidClose (id self, SEL, WKWebView*)\r
330     {\r
331         getOwner (self)->windowCloseRequest();\r
332     }\r
334     static WKWebView* createWebView (id self, SEL, WKWebView*, WKWebViewConfiguration*,\r
335                                      WKNavigationAction* navigationAction, WKWindowFeatures*)\r
336     {\r
337         getOwner (self)->newWindowAttemptingToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString]));\r
338         return nil;\r
339     }\r
341    #if WKWEBVIEW_OPENPANEL_SUPPORTED\r
342     API_AVAILABLE (macos (10.12))\r
343     static void runOpenPanel (id, SEL, WKWebView*, WKOpenPanelParameters* parameters, WKFrameInfo*,\r
344                               void (^completionHandler)(NSArray<NSURL*>*))\r
345     {\r
346         using CompletionHandlerType = decltype (completionHandler);\r
348         class DeletedFileChooserWrapper   : private DeletedAtShutdown\r
349         {\r
350         public:\r
351             DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, CompletionHandlerType h)\r
352                 : chooser (std::move (fc)), handler (h)\r
353             {\r
354                 [handler.get() retain];\r
355             }\r
357             ~DeletedFileChooserWrapper()\r
358             {\r
359                 callHandler (nullptr);\r
360             }\r
362             void callHandler (NSArray<NSURL*>* urls)\r
363             {\r
364                 if (handlerCalled)\r
365                     return;\r
367                 handler.get() (urls);\r
368                 handlerCalled = true;\r
369             }\r
371             std::unique_ptr<FileChooser> chooser;\r
373         private:\r
374             ObjCObjectHandle<CompletionHandlerType> handler;\r
375             bool handlerCalled = false;\r
376         };\r
378         auto chooser = std::make_unique<FileChooser> (TRANS("Select the file you want to upload..."),\r
379                                                       File::getSpecialLocation (File::userHomeDirectory), "*");\r
380         auto* wrapper = new DeletedFileChooserWrapper (std::move (chooser), completionHandler);\r
382         auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles\r
383                     | ([parameters allowsMultipleSelection] ? FileBrowserComponent::canSelectMultipleItems : 0);\r
385        #if (defined (MAC_OS_X_VERSION_10_14) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14)\r
386         if (@available (macOS 10.14, *))\r
387         {\r
388             if ([parameters allowsDirectories])\r
389                 flags |= FileBrowserComponent::canSelectDirectories;\r
390         }\r
391        #endif\r
393         wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&)\r
394         {\r
395             auto results = wrapper->chooser->getResults();\r
396             auto urls = [NSMutableArray arrayWithCapacity: (NSUInteger) results.size()];\r
398             for (auto& f : results)\r
399                 [urls addObject: [NSURL fileURLWithPath: juceStringToNS (f.getFullPathName())]];\r
401             wrapper->callHandler (urls);\r
402             delete wrapper;\r
403         });\r
404     }\r
405    #endif\r
406 };\r
408 //==============================================================================\r
409 struct WebViewBase\r
411     virtual ~WebViewBase() = default;\r
413     virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0;\r
414     virtual void goBack() = 0;\r
415     virtual void goForward() = 0;\r
416     virtual void stop() = 0;\r
417     virtual void refresh() = 0;\r
419     virtual id getWebView() = 0;\r
420 };\r
422 #if JUCE_MAC\r
423 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")\r
424 class WebViewImpl  : public WebViewBase\r
426 public:\r
427     WebViewImpl (WebBrowserComponent* owner)\r
428     {\r
429         static WebViewKeyEquivalentResponder<WebView> webviewClass;\r
431         webView.reset ([webviewClass.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)\r
432                                                           frameName: nsEmptyString()\r
433                                                           groupName: nsEmptyString()]);\r
435         static DownloadClickDetectorClass cls;\r
436         clickListener.reset ([cls.createInstance() init]);\r
437         DownloadClickDetectorClass::setOwner (clickListener.get(), owner);\r
439         [webView.get() setPolicyDelegate:    clickListener.get()];\r
440         [webView.get() setFrameLoadDelegate: clickListener.get()];\r
441         [webView.get() setUIDelegate:        clickListener.get()];\r
442     }\r
444     ~WebViewImpl() override\r
445     {\r
446         [webView.get() setPolicyDelegate:    nil];\r
447         [webView.get() setFrameLoadDelegate: nil];\r
448         [webView.get() setUIDelegate:        nil];\r
449     }\r
451     void goToURL (const String& url,\r
452                   const StringArray* headers,\r
453                   const MemoryBlock* postData) override\r
454     {\r
455         if (url.trimStart().startsWithIgnoreCase ("javascript:"))\r
456         {\r
457             [webView.get() stringByEvaluatingJavaScriptFromString: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))];\r
458             return;\r
459         }\r
461         stop();\r
463         auto getRequest = [&]() -> NSMutableURLRequest*\r
464         {\r
465             if (url.trimStart().startsWithIgnoreCase ("file:"))\r
466             {\r
467                 auto file = URL (url).getLocalFile();\r
469                 if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())])\r
470                     return [NSMutableURLRequest requestWithURL: appendParametersToFileURL (url, nsUrl)\r
471                                                    cachePolicy: NSURLRequestUseProtocolCachePolicy\r
472                                                timeoutInterval: 30.0];\r
474                 return nullptr;\r
475             }\r
477             return getRequestForURL (url, headers, postData);\r
478         };\r
480         if (NSMutableURLRequest* request = getRequest())\r
481             [[webView.get() mainFrame] loadRequest: request];\r
482     }\r
484     void goBack() override      { [webView.get() goBack]; }\r
485     void goForward() override   { [webView.get() goForward]; }\r
487     void stop() override        { [webView.get() stopLoading: nil]; }\r
488     void refresh() override     { [webView.get() reload: nil]; }\r
490     id getWebView() override    { return webView.get(); }\r
492     void mouseMove (const MouseEvent&)\r
493     {\r
494         JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")\r
495         // WebKit doesn't capture mouse-moves itself, so it seems the only way to make\r
496         // them work is to push them via this non-public method..\r
497         if ([webView.get() respondsToSelector: @selector (_updateMouseoverWithFakeEvent)])\r
498             [webView.get() performSelector:    @selector (_updateMouseoverWithFakeEvent)];\r
499         JUCE_END_IGNORE_WARNINGS_GCC_LIKE\r
500     }\r
502 private:\r
503     ObjCObjectHandle<WebView*> webView;\r
504     ObjCObjectHandle<id> clickListener;\r
505 };\r
506 JUCE_END_IGNORE_WARNINGS_GCC_LIKE\r
507 #endif\r
509 class API_AVAILABLE (macos (10.11)) WKWebViewImpl : public WebViewBase\r
511 public:\r
512     WKWebViewImpl (WebBrowserComponent* owner)\r
513     {\r
514         #if JUCE_MAC\r
515          static WebViewKeyEquivalentResponder<WKWebView> webviewClass;\r
517          webView.reset ([webviewClass.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]);\r
518         #else\r
519          webView.reset ([[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f)]);\r
520         #endif\r
522          static WebViewDelegateClass cls;\r
523          webViewDelegate.reset ([cls.createInstance() init]);\r
524          WebViewDelegateClass::setOwner (webViewDelegate.get(), owner);\r
526          [webView.get() setNavigationDelegate: webViewDelegate.get()];\r
527          [webView.get() setUIDelegate:         webViewDelegate.get()];\r
528     }\r
530     ~WKWebViewImpl() override\r
531     {\r
532         [webView.get() setNavigationDelegate: nil];\r
533         [webView.get() setUIDelegate:         nil];\r
534     }\r
536     void goToURL (const String& url,\r
537                   const StringArray* headers,\r
538                   const MemoryBlock* postData) override\r
539     {\r
540         auto trimmed = url.trimStart();\r
542         if (trimmed.startsWithIgnoreCase ("javascript:"))\r
543         {\r
544             [webView.get() evaluateJavaScript: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))\r
545                             completionHandler: nil];\r
547             return;\r
548         }\r
550         stop();\r
552         if (trimmed.startsWithIgnoreCase ("file:"))\r
553         {\r
554             auto file = URL (url).getLocalFile();\r
556             if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())])\r
557                 [webView.get() loadFileURL: appendParametersToFileURL (url, nsUrl) allowingReadAccessToURL: nsUrl];\r
558         }\r
559         else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData))\r
560         {\r
561             [webView.get() loadRequest: request];\r
562         }\r
563     }\r
565     void goBack() override      { [webView.get() goBack]; }\r
566     void goForward() override   { [webView.get() goForward]; }\r
568     void stop() override        { [webView.get() stopLoading]; }\r
569     void refresh() override     { [webView.get() reload]; }\r
571     id getWebView() override    { return webView.get(); }\r
573 private:\r
574     ObjCObjectHandle<WKWebView*> webView;\r
575     ObjCObjectHandle<id> webViewDelegate;\r
576 };\r
578 //==============================================================================\r
579 class WebBrowserComponent::Pimpl\r
580                                    #if JUCE_MAC\r
581                                     : public NSViewComponent\r
582                                    #else\r
583                                     : public UIViewComponent\r
584                                    #endif\r
586 public:\r
587     Pimpl (WebBrowserComponent* owner)\r
588     {\r
589         if (@available (macOS 10.11, *))\r
590             webView = std::make_unique<WKWebViewImpl> (owner);\r
591        #if JUCE_MAC\r
592         else\r
593             webView = std::make_unique<WebViewImpl> (owner);\r
594        #endif\r
596         setView (webView->getWebView());\r
597     }\r
599     ~Pimpl()\r
600     {\r
601         webView = nullptr;\r
602         setView (nil);\r
603     }\r
605     void goToURL (const String& url,\r
606                   const StringArray* headers,\r
607                   const MemoryBlock* postData)\r
608     {\r
609         webView->goToURL (url, headers, postData);\r
610     }\r
612     void goBack()      { webView->goBack(); }\r
613     void goForward()   { webView->goForward(); }\r
615     void stop()        { webView->stop(); }\r
616     void refresh()     { webView->refresh(); }\r
618 private:\r
619     std::unique_ptr<WebViewBase> webView;\r
620 };\r
622 //==============================================================================\r
623 WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden)\r
624     : unloadPageWhenHidden (unloadWhenHidden)\r
626     setOpaque (true);\r
627     browser.reset (new Pimpl (this));\r
628     addAndMakeVisible (browser.get());\r
631 WebBrowserComponent::~WebBrowserComponent() = default;\r
633 //==============================================================================\r
634 void WebBrowserComponent::goToURL (const String& url,\r
635                                    const StringArray* headers,\r
636                                    const MemoryBlock* postData)\r
638     lastURL = url;\r
640     if (headers != nullptr)\r
641         lastHeaders = *headers;\r
642     else\r
643         lastHeaders.clear();\r
645     if (postData != nullptr)\r
646         lastPostData = *postData;\r
647     else\r
648         lastPostData.reset();\r
650     blankPageShown = false;\r
652     browser->goToURL (url, headers, postData);\r
655 void WebBrowserComponent::stop()\r
657     browser->stop();\r
660 void WebBrowserComponent::goBack()\r
662     lastURL.clear();\r
663     blankPageShown = false;\r
664     browser->goBack();\r
667 void WebBrowserComponent::goForward()\r
669     lastURL.clear();\r
670     browser->goForward();\r
673 void WebBrowserComponent::refresh()\r
675     browser->refresh();\r
678 //==============================================================================\r
679 void WebBrowserComponent::paint (Graphics&)\r
683 void WebBrowserComponent::checkWindowAssociation()\r
685     if (isShowing())\r
686     {\r
687         reloadLastURL();\r
689         if (blankPageShown)\r
690             goBack();\r
691     }\r
692     else\r
693     {\r
694         if (unloadPageWhenHidden && ! blankPageShown)\r
695         {\r
696             // when the component becomes invisible, some stuff like flash\r
697             // carries on playing audio, so we need to force it onto a blank\r
698             // page to avoid this, (and send it back when it's made visible again).\r
700             blankPageShown = true;\r
701             browser->goToURL ("about:blank", nullptr, nullptr);\r
702         }\r
703     }\r
706 void WebBrowserComponent::reloadLastURL()\r
708     if (lastURL.isNotEmpty())\r
709     {\r
710         goToURL (lastURL, &lastHeaders, &lastPostData);\r
711         lastURL.clear();\r
712     }\r
715 void WebBrowserComponent::parentHierarchyChanged()\r
717     checkWindowAssociation();\r
720 void WebBrowserComponent::resized()\r
722     browser->setSize (getWidth(), getHeight());\r
725 void WebBrowserComponent::visibilityChanged()\r
727     checkWindowAssociation();\r
730 void WebBrowserComponent::focusGained (FocusChangeType)\r
734 void WebBrowserComponent::clearCookies()\r
736     NSHTTPCookieStorage* storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];\r
738     if (NSArray* cookies = [storage cookies])\r
739     {\r
740         const NSUInteger n = [cookies count];\r
742         for (NSUInteger i = 0; i < n; ++i)\r
743             [storage deleteCookie: [cookies objectAtIndex: i]];\r
744     }\r
746     [[NSUserDefaults standardUserDefaults] synchronize];\r
749 } // namespace juce\r