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 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #if JUCE_MAC
  20. namespace juce
  21. {
  22. struct WebViewKeyEquivalentResponder : public ObjCClass<WebView>
  23. {
  24. WebViewKeyEquivalentResponder() : ObjCClass<WebView> ("WebViewKeyEquivalentResponder_")
  25. {
  26. addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@");
  27. registerClass();
  28. }
  29. private:
  30. static BOOL performKeyEquivalent (id self, SEL selector, NSEvent* event)
  31. {
  32. NSResponder* first = [[self window] firstResponder];
  33. #if (defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12)
  34. if (([event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagCommand)
  35. #else
  36. if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask)
  37. #endif
  38. {
  39. if ([[event charactersIgnoringModifiers] isEqualToString:@"x"]) return [NSApp sendAction:@selector(cut:) to:first from:self];
  40. if ([[event charactersIgnoringModifiers] isEqualToString:@"c"]) return [NSApp sendAction:@selector(copy:) to:first from:self];
  41. if ([[event charactersIgnoringModifiers] isEqualToString:@"v"]) return [NSApp sendAction:@selector(paste:) to:first from:self];
  42. if ([[event charactersIgnoringModifiers] isEqualToString:@"a"]) return [NSApp sendAction:@selector(selectAll:) to:first from:self];
  43. }
  44. objc_super s = { self, [WebView class] };
  45. return ObjCMsgSendSuper<BOOL, NSEvent*> (&s, selector, event);
  46. }
  47. };
  48. struct DownloadClickDetectorClass : public ObjCClass<NSObject>
  49. {
  50. DownloadClickDetectorClass() : ObjCClass<NSObject> ("JUCEWebClickDetector_")
  51. {
  52. addIvar<WebBrowserComponent*> ("owner");
  53. addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:),
  54. decidePolicyForNavigationAction, "v@:@@@@@");
  55. addMethod (@selector (webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:),
  56. decidePolicyForNewWindowAction, "v@:@@@@@");
  57. addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@");
  58. addMethod (@selector (webView:didFailLoadWithError:forFrame:), didFailLoadWithError, "v@:@@@");
  59. addMethod (@selector (webView:didFailProvisionalLoadWithError:forFrame:), didFailLoadWithError, "v@:@@@");
  60. addMethod (@selector (webView:willCloseFrame:), willCloseFrame, "v@:@@");
  61. addMethod (@selector (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:), runOpenPanel, "v@:@@", @encode (BOOL));
  62. registerClass();
  63. }
  64. static void setOwner (id self, WebBrowserComponent* owner) { object_setInstanceVariable (self, "owner", owner); }
  65. static WebBrowserComponent* getOwner (id self) { return getIvar<WebBrowserComponent*> (self, "owner"); }
  66. private:
  67. static String getOriginalURL (NSDictionary* actionInformation)
  68. {
  69. if (NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")])
  70. return nsStringToJuce ([url absoluteString]);
  71. return {};
  72. }
  73. static void decidePolicyForNavigationAction (id self, SEL, WebView*, NSDictionary* actionInformation,
  74. NSURLRequest*, WebFrame*, id<WebPolicyDecisionListener> listener)
  75. {
  76. if (getOwner (self)->pageAboutToLoad (getOriginalURL (actionInformation)))
  77. [listener use];
  78. else
  79. [listener ignore];
  80. }
  81. static void decidePolicyForNewWindowAction (id self, SEL, WebView*, NSDictionary* actionInformation,
  82. NSURLRequest*, NSString*, id<WebPolicyDecisionListener> listener)
  83. {
  84. getOwner (self)->newWindowAttemptingToLoad (getOriginalURL (actionInformation));
  85. [listener ignore];
  86. }
  87. static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame)
  88. {
  89. if ([frame isEqual: [sender mainFrame]])
  90. {
  91. NSURL* url = [[[frame dataSource] request] URL];
  92. getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString]));
  93. }
  94. }
  95. static void didFailLoadWithError (id self, SEL, WebView* sender, NSError* error, WebFrame* frame)
  96. {
  97. if ([frame isEqual: [sender mainFrame]] && error != nullptr && [error code] != NSURLErrorCancelled)
  98. {
  99. String errorString (nsStringToJuce ([error localizedDescription]));
  100. bool proceedToErrorPage = getOwner (self)->pageLoadHadNetworkError (errorString);
  101. // WebKit doesn't have an internal error page, so make a really simple one ourselves
  102. if (proceedToErrorPage)
  103. getOwner(self)->goToURL (String ("data:text/plain;charset=UTF-8,") + errorString);
  104. }
  105. }
  106. static void willCloseFrame (id self, SEL, WebView*, WebFrame*)
  107. {
  108. getOwner (self)->windowCloseRequest();
  109. }
  110. static void runOpenPanel (id, SEL, WebView*, id<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles)
  111. {
  112. #if JUCE_MODAL_LOOPS_PERMITTED
  113. FileChooser chooser (TRANS("Select the file you want to upload..."),
  114. File::getSpecialLocation (File::userHomeDirectory), "*");
  115. if (allowMultipleFiles ? chooser.browseForMultipleFilesToOpen()
  116. : chooser.browseForFileToOpen())
  117. {
  118. for (auto& f : chooser.getResults())
  119. [resultListener chooseFilename: juceStringToNS (f.getFullPathName())];
  120. }
  121. #else
  122. ignoreUnused (resultListener, allowMultipleFiles);
  123. jassertfalse; // Can't use this without modal loops being enabled!
  124. #endif
  125. }
  126. };
  127. #else
  128. //==============================================================================
  129. @interface WebViewTapDetector : NSObject<UIGestureRecognizerDelegate>
  130. {
  131. }
  132. - (BOOL) gestureRecognizer: (UIGestureRecognizer*) gestureRecognizer
  133. shouldRecognizeSimultaneouslyWithGestureRecognizer: (UIGestureRecognizer*) otherGestureRecognizer;
  134. @end
  135. @implementation WebViewTapDetector
  136. - (BOOL) gestureRecognizer: (UIGestureRecognizer*) gestureRecognizer
  137. shouldRecognizeSimultaneouslyWithGestureRecognizer: (UIGestureRecognizer*) otherGestureRecognizer
  138. {
  139. juce::ignoreUnused (gestureRecognizer, otherGestureRecognizer);
  140. return YES;
  141. }
  142. @end
  143. //==============================================================================
  144. @interface WebViewURLChangeDetector : NSObject<UIWebViewDelegate>
  145. {
  146. juce::WebBrowserComponent* ownerComponent;
  147. }
  148. - (WebViewURLChangeDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent;
  149. - (BOOL) webView: (UIWebView*) webView shouldStartLoadWithRequest: (NSURLRequest*) request
  150. navigationType: (UIWebViewNavigationType) navigationType;
  151. - (void) webViewDidFinishLoad: (UIWebView*) webView;
  152. @end
  153. @implementation WebViewURLChangeDetector
  154. - (WebViewURLChangeDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComp
  155. {
  156. [super init];
  157. ownerComponent = ownerComp;
  158. return self;
  159. }
  160. - (BOOL) webView: (UIWebView*) webView shouldStartLoadWithRequest: (NSURLRequest*) request
  161. navigationType: (UIWebViewNavigationType) navigationType
  162. {
  163. juce::ignoreUnused (webView, navigationType);
  164. return ownerComponent->pageAboutToLoad (juce::nsStringToJuce (request.URL.absoluteString));
  165. }
  166. - (void) webViewDidFinishLoad: (UIWebView*) webView
  167. {
  168. ownerComponent->pageFinishedLoading (juce::nsStringToJuce (webView.request.URL.absoluteString));
  169. }
  170. @end
  171. namespace juce
  172. {
  173. #endif
  174. //==============================================================================
  175. class WebBrowserComponent::Pimpl
  176. #if JUCE_MAC
  177. : public NSViewComponent
  178. #else
  179. : public UIViewComponent
  180. #endif
  181. {
  182. public:
  183. Pimpl (WebBrowserComponent* owner)
  184. {
  185. #if JUCE_MAC
  186. static WebViewKeyEquivalentResponder webviewClass;
  187. webView = (WebView*) webviewClass.createInstance();
  188. webView = [webView initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)
  189. frameName: nsEmptyString()
  190. groupName: nsEmptyString()];
  191. setView (webView);
  192. static DownloadClickDetectorClass cls;
  193. clickListener = [cls.createInstance() init];
  194. DownloadClickDetectorClass::setOwner (clickListener, owner);
  195. [webView setPolicyDelegate: clickListener];
  196. [webView setFrameLoadDelegate: clickListener];
  197. [webView setUIDelegate: clickListener];
  198. #else
  199. webView = [[UIWebView alloc] initWithFrame: CGRectMake (0, 0, 1.0f, 1.0f)];
  200. setView (webView);
  201. tapDetector = [[WebViewTapDetector alloc] init];
  202. urlDetector = [[WebViewURLChangeDetector alloc] initWithWebBrowserOwner: owner];
  203. gestureRecogniser = nil;
  204. webView.delegate = urlDetector;
  205. #endif
  206. }
  207. ~Pimpl()
  208. {
  209. #if JUCE_MAC
  210. [webView setPolicyDelegate: nil];
  211. [webView setFrameLoadDelegate: nil];
  212. [webView setUIDelegate: nil];
  213. [clickListener release];
  214. #else
  215. webView.delegate = nil;
  216. [webView removeGestureRecognizer: gestureRecogniser];
  217. [gestureRecogniser release];
  218. [tapDetector release];
  219. [urlDetector release];
  220. #endif
  221. setView (nil);
  222. }
  223. void goToURL (const String& url,
  224. const StringArray* headers,
  225. const MemoryBlock* postData)
  226. {
  227. stop();
  228. if (url.trimStart().startsWithIgnoreCase ("javascript:"))
  229. {
  230. [webView stringByEvaluatingJavaScriptFromString:
  231. juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))];
  232. }
  233. else
  234. {
  235. NSString* urlString = juceStringToNS (url);
  236. #if (JUCE_MAC && (defined (__MAC_OS_X_VERSION_MIN_REQUIRED) && defined (__MAC_10_9) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9)) || (JUCE_IOS && (defined (__IPHONE_OS_VERSION_MIN_REQUIRED) && defined (__IPHONE_7_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0))
  237. urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
  238. #else
  239. urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  240. #endif
  241. NSMutableURLRequest* r
  242. = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: urlString]
  243. cachePolicy: NSURLRequestUseProtocolCachePolicy
  244. timeoutInterval: 30.0];
  245. if (postData != nullptr && postData->getSize() > 0)
  246. {
  247. [r setHTTPMethod: nsStringLiteral ("POST")];
  248. [r setHTTPBody: [NSData dataWithBytes: postData->getData()
  249. length: postData->getSize()]];
  250. }
  251. if (headers != nullptr)
  252. {
  253. for (int i = 0; i < headers->size(); ++i)
  254. {
  255. const String headerName ((*headers)[i].upToFirstOccurrenceOf (":", false, false).trim());
  256. const String headerValue ((*headers)[i].fromFirstOccurrenceOf (":", false, false).trim());
  257. [r setValue: juceStringToNS (headerValue)
  258. forHTTPHeaderField: juceStringToNS (headerName)];
  259. }
  260. }
  261. #if JUCE_MAC
  262. [[webView mainFrame] loadRequest: r];
  263. #else
  264. [webView loadRequest: r];
  265. #endif
  266. #if JUCE_IOS
  267. [webView setScalesPageToFit:YES];
  268. #endif
  269. }
  270. }
  271. void goBack() { [webView goBack]; }
  272. void goForward() { [webView goForward]; }
  273. #if JUCE_MAC
  274. void stop() { [webView stopLoading: nil]; }
  275. void refresh() { [webView reload: nil]; }
  276. #else
  277. void stop() { [webView stopLoading]; }
  278. void refresh() { [webView reload]; }
  279. #endif
  280. void mouseMove (const MouseEvent&)
  281. {
  282. // WebKit doesn't capture mouse-moves itself, so it seems the only way to make
  283. // them work is to push them via this non-public method..
  284. if ([webView respondsToSelector: @selector (_updateMouseoverWithFakeEvent)])
  285. [webView performSelector: @selector (_updateMouseoverWithFakeEvent)];
  286. }
  287. private:
  288. #if JUCE_MAC
  289. WebView* webView;
  290. id clickListener;
  291. #else
  292. UIWebView* webView;
  293. WebViewTapDetector* tapDetector;
  294. WebViewURLChangeDetector* urlDetector;
  295. UITapGestureRecognizer* gestureRecogniser;
  296. #endif
  297. };
  298. //==============================================================================
  299. WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden)
  300. : browser (nullptr),
  301. blankPageShown (false),
  302. unloadPageWhenBrowserIsHidden (unloadWhenHidden)
  303. {
  304. setOpaque (true);
  305. addAndMakeVisible (browser = new Pimpl (this));
  306. }
  307. WebBrowserComponent::~WebBrowserComponent()
  308. {
  309. deleteAndZero (browser);
  310. }
  311. //==============================================================================
  312. void WebBrowserComponent::goToURL (const String& url,
  313. const StringArray* headers,
  314. const MemoryBlock* postData)
  315. {
  316. lastURL = url;
  317. if (headers != nullptr)
  318. lastHeaders = *headers;
  319. else
  320. lastHeaders.clear();
  321. if (postData != nullptr)
  322. lastPostData = *postData;
  323. else
  324. lastPostData.reset();
  325. blankPageShown = false;
  326. browser->goToURL (url, headers, postData);
  327. }
  328. void WebBrowserComponent::stop()
  329. {
  330. browser->stop();
  331. }
  332. void WebBrowserComponent::goBack()
  333. {
  334. lastURL.clear();
  335. blankPageShown = false;
  336. browser->goBack();
  337. }
  338. void WebBrowserComponent::goForward()
  339. {
  340. lastURL.clear();
  341. browser->goForward();
  342. }
  343. void WebBrowserComponent::refresh()
  344. {
  345. browser->refresh();
  346. }
  347. //==============================================================================
  348. void WebBrowserComponent::paint (Graphics&)
  349. {
  350. }
  351. void WebBrowserComponent::checkWindowAssociation()
  352. {
  353. if (isShowing())
  354. {
  355. reloadLastURL();
  356. if (blankPageShown)
  357. goBack();
  358. }
  359. else
  360. {
  361. if (unloadPageWhenBrowserIsHidden && ! blankPageShown)
  362. {
  363. // when the component becomes invisible, some stuff like flash
  364. // carries on playing audio, so we need to force it onto a blank
  365. // page to avoid this, (and send it back when it's made visible again).
  366. blankPageShown = true;
  367. browser->goToURL ("about:blank", 0, 0);
  368. }
  369. }
  370. }
  371. void WebBrowserComponent::reloadLastURL()
  372. {
  373. if (lastURL.isNotEmpty())
  374. {
  375. goToURL (lastURL, &lastHeaders, &lastPostData);
  376. lastURL.clear();
  377. }
  378. }
  379. void WebBrowserComponent::parentHierarchyChanged()
  380. {
  381. checkWindowAssociation();
  382. }
  383. void WebBrowserComponent::resized()
  384. {
  385. browser->setSize (getWidth(), getHeight());
  386. }
  387. void WebBrowserComponent::visibilityChanged()
  388. {
  389. checkWindowAssociation();
  390. }
  391. void WebBrowserComponent::focusGained (FocusChangeType)
  392. {
  393. }
  394. void WebBrowserComponent::clearCookies()
  395. {
  396. NSHTTPCookieStorage* storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
  397. if (NSArray* cookies = [storage cookies])
  398. {
  399. const NSUInteger n = [cookies count];
  400. for (NSUInteger i = 0; i < n; ++i)
  401. [storage deleteCookie: [cookies objectAtIndex: i]];
  402. }
  403. [[NSUserDefaults standardUserDefaults] synchronize];
  404. }
  405. } // namespace juce