Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

juce_mac_WebBrowserComponent.mm 27KB

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