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.

802 lines
28KB

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