The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

751 lines
27KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - 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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-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.9, *))
  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 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. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new")
  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. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  328. #endif
  329. };
  330. //==============================================================================
  331. struct WebViewBase
  332. {
  333. virtual ~WebViewBase() = default;
  334. virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0;
  335. virtual void goBack() = 0;
  336. virtual void goForward() = 0;
  337. virtual void stop() = 0;
  338. virtual void refresh() = 0;
  339. virtual id getWebView() = 0;
  340. };
  341. #if JUCE_MAC
  342. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  343. class WebViewImpl : public WebViewBase
  344. {
  345. public:
  346. WebViewImpl (WebBrowserComponent* owner)
  347. {
  348. static WebViewKeyEquivalentResponder<WebView> webviewClass;
  349. webView.reset ([webviewClass.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)
  350. frameName: nsEmptyString()
  351. groupName: nsEmptyString()]);
  352. static DownloadClickDetectorClass cls;
  353. clickListener.reset ([cls.createInstance() init]);
  354. DownloadClickDetectorClass::setOwner (clickListener.get(), owner);
  355. [webView.get() setPolicyDelegate: clickListener.get()];
  356. [webView.get() setFrameLoadDelegate: clickListener.get()];
  357. [webView.get() setUIDelegate: clickListener.get()];
  358. }
  359. ~WebViewImpl() override
  360. {
  361. [webView.get() setPolicyDelegate: nil];
  362. [webView.get() setFrameLoadDelegate: nil];
  363. [webView.get() setUIDelegate: nil];
  364. }
  365. void goToURL (const String& url,
  366. const StringArray* headers,
  367. const MemoryBlock* postData) override
  368. {
  369. if (url.trimStart().startsWithIgnoreCase ("javascript:"))
  370. {
  371. [webView.get() stringByEvaluatingJavaScriptFromString: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))];
  372. return;
  373. }
  374. stop();
  375. auto getRequest = [&]() -> NSMutableURLRequest*
  376. {
  377. if (url.trimStart().startsWithIgnoreCase ("file:"))
  378. {
  379. auto file = URL (url).getLocalFile();
  380. if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())])
  381. return [NSMutableURLRequest requestWithURL: appendParametersToFileURL (url, nsUrl)
  382. cachePolicy: NSURLRequestUseProtocolCachePolicy
  383. timeoutInterval: 30.0];
  384. return nullptr;
  385. }
  386. return getRequestForURL (url, headers, postData);
  387. };
  388. if (NSMutableURLRequest* request = getRequest())
  389. [[webView.get() mainFrame] loadRequest: request];
  390. }
  391. void goBack() override { [webView.get() goBack]; }
  392. void goForward() override { [webView.get() goForward]; }
  393. void stop() override { [webView.get() stopLoading: nil]; }
  394. void refresh() override { [webView.get() reload: nil]; }
  395. id getWebView() override { return webView.get(); }
  396. void mouseMove (const MouseEvent&)
  397. {
  398. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  399. // WebKit doesn't capture mouse-moves itself, so it seems the only way to make
  400. // them work is to push them via this non-public method..
  401. if ([webView.get() respondsToSelector: @selector (_updateMouseoverWithFakeEvent)])
  402. [webView.get() performSelector: @selector (_updateMouseoverWithFakeEvent)];
  403. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  404. }
  405. private:
  406. ObjCObjectHandle<WebView*> webView;
  407. ObjCObjectHandle<id> clickListener;
  408. };
  409. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  410. #endif
  411. class WKWebViewImpl : public WebViewBase
  412. {
  413. public:
  414. WKWebViewImpl (WebBrowserComponent* owner)
  415. {
  416. #if JUCE_MAC
  417. static WebViewKeyEquivalentResponder<WKWebView> webviewClass;
  418. webView.reset ([webviewClass.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]);
  419. #else
  420. webView.reset ([[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f)]);
  421. #endif
  422. static WebViewDelegateClass cls;
  423. webViewDelegate.reset ([cls.createInstance() init]);
  424. WebViewDelegateClass::setOwner (webViewDelegate.get(), owner);
  425. [webView.get() setNavigationDelegate: webViewDelegate.get()];
  426. [webView.get() setUIDelegate: webViewDelegate.get()];
  427. }
  428. ~WKWebViewImpl() override
  429. {
  430. [webView.get() setNavigationDelegate: nil];
  431. [webView.get() setUIDelegate: nil];
  432. }
  433. void goToURL (const String& url,
  434. const StringArray* headers,
  435. const MemoryBlock* postData) override
  436. {
  437. auto trimmed = url.trimStart();
  438. if (trimmed.startsWithIgnoreCase ("javascript:"))
  439. {
  440. [webView.get() evaluateJavaScript: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))
  441. completionHandler: nil];
  442. return;
  443. }
  444. stop();
  445. if (trimmed.startsWithIgnoreCase ("file:"))
  446. {
  447. auto file = URL (url).getLocalFile();
  448. if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())])
  449. [webView.get() loadFileURL: appendParametersToFileURL (url, nsUrl) allowingReadAccessToURL: nsUrl];
  450. }
  451. else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData))
  452. {
  453. [webView.get() loadRequest: request];
  454. }
  455. }
  456. void goBack() override { [webView.get() goBack]; }
  457. void goForward() override { [webView.get() goForward]; }
  458. void stop() override { [webView.get() stopLoading]; }
  459. void refresh() override { [webView.get() reload]; }
  460. id getWebView() override { return webView.get(); }
  461. private:
  462. ObjCObjectHandle<WKWebView*> webView;
  463. ObjCObjectHandle<id> webViewDelegate;
  464. };
  465. //==============================================================================
  466. class WebBrowserComponent::Pimpl
  467. #if JUCE_MAC
  468. : public NSViewComponent
  469. #else
  470. : public UIViewComponent
  471. #endif
  472. {
  473. public:
  474. Pimpl (WebBrowserComponent* owner)
  475. {
  476. if (@available (macOS 10.10, *))
  477. webView = std::make_unique<WKWebViewImpl> (owner);
  478. #if JUCE_MAC
  479. else
  480. webView = std::make_unique<WebViewImpl> (owner);
  481. #endif
  482. setView (webView->getWebView());
  483. }
  484. ~Pimpl()
  485. {
  486. webView = nullptr;
  487. setView (nil);
  488. }
  489. void goToURL (const String& url,
  490. const StringArray* headers,
  491. const MemoryBlock* postData)
  492. {
  493. webView->goToURL (url, headers, postData);
  494. }
  495. void goBack() { webView->goBack(); }
  496. void goForward() { webView->goForward(); }
  497. void stop() { webView->stop(); }
  498. void refresh() { webView->refresh(); }
  499. private:
  500. std::unique_ptr<WebViewBase> webView;
  501. };
  502. //==============================================================================
  503. WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden)
  504. : unloadPageWhenHidden (unloadWhenHidden)
  505. {
  506. setOpaque (true);
  507. browser.reset (new Pimpl (this));
  508. addAndMakeVisible (browser.get());
  509. }
  510. WebBrowserComponent::~WebBrowserComponent() = default;
  511. //==============================================================================
  512. void WebBrowserComponent::goToURL (const String& url,
  513. const StringArray* headers,
  514. const MemoryBlock* postData)
  515. {
  516. lastURL = url;
  517. if (headers != nullptr)
  518. lastHeaders = *headers;
  519. else
  520. lastHeaders.clear();
  521. if (postData != nullptr)
  522. lastPostData = *postData;
  523. else
  524. lastPostData.reset();
  525. blankPageShown = false;
  526. browser->goToURL (url, headers, postData);
  527. }
  528. void WebBrowserComponent::stop()
  529. {
  530. browser->stop();
  531. }
  532. void WebBrowserComponent::goBack()
  533. {
  534. lastURL.clear();
  535. blankPageShown = false;
  536. browser->goBack();
  537. }
  538. void WebBrowserComponent::goForward()
  539. {
  540. lastURL.clear();
  541. browser->goForward();
  542. }
  543. void WebBrowserComponent::refresh()
  544. {
  545. browser->refresh();
  546. }
  547. //==============================================================================
  548. void WebBrowserComponent::paint (Graphics&)
  549. {
  550. }
  551. void WebBrowserComponent::checkWindowAssociation()
  552. {
  553. if (isShowing())
  554. {
  555. reloadLastURL();
  556. if (blankPageShown)
  557. goBack();
  558. }
  559. else
  560. {
  561. if (unloadPageWhenHidden && ! blankPageShown)
  562. {
  563. // when the component becomes invisible, some stuff like flash
  564. // carries on playing audio, so we need to force it onto a blank
  565. // page to avoid this, (and send it back when it's made visible again).
  566. blankPageShown = true;
  567. browser->goToURL ("about:blank", nullptr, nullptr);
  568. }
  569. }
  570. }
  571. void WebBrowserComponent::reloadLastURL()
  572. {
  573. if (lastURL.isNotEmpty())
  574. {
  575. goToURL (lastURL, &lastHeaders, &lastPostData);
  576. lastURL.clear();
  577. }
  578. }
  579. void WebBrowserComponent::parentHierarchyChanged()
  580. {
  581. checkWindowAssociation();
  582. }
  583. void WebBrowserComponent::resized()
  584. {
  585. browser->setSize (getWidth(), getHeight());
  586. }
  587. void WebBrowserComponent::visibilityChanged()
  588. {
  589. checkWindowAssociation();
  590. }
  591. void WebBrowserComponent::focusGained (FocusChangeType)
  592. {
  593. }
  594. void WebBrowserComponent::clearCookies()
  595. {
  596. NSHTTPCookieStorage* storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
  597. if (NSArray* cookies = [storage cookies])
  598. {
  599. const NSUInteger n = [cookies count];
  600. for (NSUInteger i = 0; i < n; ++i)
  601. [storage deleteCookie: [cookies objectAtIndex: i]];
  602. }
  603. [[NSUserDefaults standardUserDefaults] synchronize];
  604. }
  605. } // namespace juce