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.

415 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #if JUCE_MAC
  18. struct DownloadClickDetectorClass : public ObjCClass<NSObject>
  19. {
  20. DownloadClickDetectorClass() : ObjCClass<NSObject> ("JUCEWebClickDetector_")
  21. {
  22. addIvar<WebBrowserComponent*> ("owner");
  23. addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:),
  24. decidePolicyForNavigationAction, "v@:@@@@@");
  25. addMethod (@selector (webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:),
  26. decidePolicyForNewWindowAction, "v@:@@@@@");
  27. addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@");
  28. addMethod (@selector (webView:willCloseFrame:), willCloseFrame, "v@:@@");
  29. addMethod (@selector (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:), runOpenPanel, "v@:@@", @encode (BOOL));
  30. registerClass();
  31. }
  32. static void setOwner (id self, WebBrowserComponent* owner) { object_setInstanceVariable (self, "owner", owner); }
  33. static WebBrowserComponent* getOwner (id self) { return getIvar<WebBrowserComponent*> (self, "owner"); }
  34. private:
  35. static String getOriginalURL (NSDictionary* actionInformation)
  36. {
  37. if (NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")])
  38. return nsStringToJuce ([url absoluteString]);
  39. return String();
  40. }
  41. static void decidePolicyForNavigationAction (id self, SEL, WebView*, NSDictionary* actionInformation,
  42. NSURLRequest*, WebFrame*, id<WebPolicyDecisionListener> listener)
  43. {
  44. if (getOwner (self)->pageAboutToLoad (getOriginalURL (actionInformation)))
  45. [listener use];
  46. else
  47. [listener ignore];
  48. }
  49. static void decidePolicyForNewWindowAction (id self, SEL, WebView*, NSDictionary* actionInformation,
  50. NSURLRequest*, NSString*, id<WebPolicyDecisionListener> listener)
  51. {
  52. getOwner (self)->newWindowAttemptingToLoad (getOriginalURL (actionInformation));
  53. [listener ignore];
  54. }
  55. static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame)
  56. {
  57. if ([frame isEqual: [sender mainFrame]])
  58. {
  59. NSURL* url = [[[frame dataSource] request] URL];
  60. getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString]));
  61. }
  62. }
  63. static void willCloseFrame (id self, SEL, WebView*, WebFrame*)
  64. {
  65. getOwner (self)->windowCloseRequest();
  66. }
  67. static void runOpenPanel (id, SEL, WebView*, id<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles)
  68. {
  69. #if JUCE_MODAL_LOOPS_PERMITTED
  70. FileChooser chooser (TRANS("Select the file you want to upload..."),
  71. File::getSpecialLocation (File::userHomeDirectory), "*");
  72. if (allowMultipleFiles ? chooser.browseForMultipleFilesToOpen()
  73. : chooser.browseForFileToOpen())
  74. {
  75. const Array<File>& files = chooser.getResults();
  76. for (int i = 0; i < files.size(); ++i)
  77. [resultListener chooseFilename: juceStringToNS (files.getReference(i).getFullPathName())];
  78. }
  79. #else
  80. (void) resultListener; (void) allowMultipleFiles;
  81. jassertfalse; // Can't use this without modal loops being enabled!
  82. #endif
  83. }
  84. };
  85. #else
  86. } // (juce namespace)
  87. //==============================================================================
  88. @interface WebViewTapDetector : NSObject<UIGestureRecognizerDelegate>
  89. {
  90. }
  91. - (BOOL) gestureRecognizer: (UIGestureRecognizer*) gestureRecognizer
  92. shouldRecognizeSimultaneouslyWithGestureRecognizer: (UIGestureRecognizer*) otherGestureRecognizer;
  93. @end
  94. @implementation WebViewTapDetector
  95. - (BOOL) gestureRecognizer: (UIGestureRecognizer*) gestureRecognizer
  96. shouldRecognizeSimultaneouslyWithGestureRecognizer: (UIGestureRecognizer*) otherGestureRecognizer
  97. {
  98. (void) gestureRecognizer;
  99. (void) otherGestureRecognizer;
  100. return YES;
  101. }
  102. @end
  103. //==============================================================================
  104. @interface WebViewURLChangeDetector : NSObject<UIWebViewDelegate>
  105. {
  106. juce::WebBrowserComponent* ownerComponent;
  107. }
  108. - (WebViewURLChangeDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent;
  109. - (BOOL) webView: (UIWebView*) webView shouldStartLoadWithRequest: (NSURLRequest*) request
  110. navigationType: (UIWebViewNavigationType) navigationType;
  111. - (void) webViewDidFinishLoad: (UIWebView*) webView;
  112. @end
  113. @implementation WebViewURLChangeDetector
  114. - (WebViewURLChangeDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComp
  115. {
  116. [super init];
  117. ownerComponent = ownerComp;
  118. return self;
  119. }
  120. - (BOOL) webView: (UIWebView*) webView shouldStartLoadWithRequest: (NSURLRequest*) request
  121. navigationType: (UIWebViewNavigationType) navigationType
  122. {
  123. (void) webView;
  124. (void) navigationType;
  125. return ownerComponent->pageAboutToLoad (nsStringToJuce (request.URL.absoluteString));
  126. }
  127. - (void) webViewDidFinishLoad: (UIWebView*) webView
  128. {
  129. ownerComponent->pageFinishedLoading (nsStringToJuce (webView.request.URL.absoluteString));
  130. }
  131. @end
  132. namespace juce {
  133. #endif
  134. //==============================================================================
  135. class WebBrowserComponent::Pimpl
  136. #if JUCE_MAC
  137. : public NSViewComponent
  138. #else
  139. : public UIViewComponent
  140. #endif
  141. {
  142. public:
  143. Pimpl (WebBrowserComponent* owner)
  144. {
  145. #if JUCE_MAC
  146. webView = [[WebView alloc] initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)
  147. frameName: nsEmptyString()
  148. groupName: nsEmptyString()];
  149. setView (webView);
  150. static DownloadClickDetectorClass cls;
  151. clickListener = [cls.createInstance() init];
  152. DownloadClickDetectorClass::setOwner (clickListener, owner);
  153. [webView setPolicyDelegate: clickListener];
  154. [webView setFrameLoadDelegate: clickListener];
  155. [webView setUIDelegate: clickListener];
  156. #else
  157. webView = [[UIWebView alloc] initWithFrame: CGRectMake (0, 0, 1.0f, 1.0f)];
  158. setView (webView);
  159. tapDetector = [[WebViewTapDetector alloc] init];
  160. urlDetector = [[WebViewURLChangeDetector alloc] initWithWebBrowserOwner: owner];
  161. gestureRecogniser = nil;
  162. webView.delegate = urlDetector;
  163. #endif
  164. }
  165. ~Pimpl()
  166. {
  167. #if JUCE_MAC
  168. [webView setPolicyDelegate: nil];
  169. [webView setFrameLoadDelegate: nil];
  170. [webView setUIDelegate: nil];
  171. [clickListener release];
  172. #else
  173. webView.delegate = nil;
  174. [webView removeGestureRecognizer: gestureRecogniser];
  175. [gestureRecogniser release];
  176. [tapDetector release];
  177. [urlDetector release];
  178. #endif
  179. setView (nil);
  180. }
  181. void goToURL (const String& url,
  182. const StringArray* headers,
  183. const MemoryBlock* postData)
  184. {
  185. stop();
  186. if (url.trimStart().startsWithIgnoreCase ("javascript:"))
  187. {
  188. [webView stringByEvaluatingJavaScriptFromString:
  189. juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))];
  190. }
  191. else
  192. {
  193. NSMutableURLRequest* r
  194. = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (url)]
  195. cachePolicy: NSURLRequestUseProtocolCachePolicy
  196. timeoutInterval: 30.0];
  197. if (postData != nullptr && postData->getSize() > 0)
  198. {
  199. [r setHTTPMethod: nsStringLiteral ("POST")];
  200. [r setHTTPBody: [NSData dataWithBytes: postData->getData()
  201. length: postData->getSize()]];
  202. }
  203. if (headers != nullptr)
  204. {
  205. for (int i = 0; i < headers->size(); ++i)
  206. {
  207. const String headerName ((*headers)[i].upToFirstOccurrenceOf (":", false, false).trim());
  208. const String headerValue ((*headers)[i].fromFirstOccurrenceOf (":", false, false).trim());
  209. [r setValue: juceStringToNS (headerValue)
  210. forHTTPHeaderField: juceStringToNS (headerName)];
  211. }
  212. }
  213. #if JUCE_MAC
  214. [[webView mainFrame] loadRequest: r];
  215. #else
  216. [webView loadRequest: r];
  217. #endif
  218. }
  219. }
  220. void goBack() { [webView goBack]; }
  221. void goForward() { [webView goForward]; }
  222. #if JUCE_MAC
  223. void stop() { [webView stopLoading: nil]; }
  224. void refresh() { [webView reload: nil]; }
  225. #else
  226. void stop() { [webView stopLoading]; }
  227. void refresh() { [webView reload]; }
  228. #endif
  229. void mouseMove (const MouseEvent&)
  230. {
  231. // WebKit doesn't capture mouse-moves itself, so it seems the only way to make
  232. // them work is to push them via this non-public method..
  233. if ([webView respondsToSelector: @selector (_updateMouseoverWithFakeEvent)])
  234. [webView performSelector: @selector (_updateMouseoverWithFakeEvent)];
  235. }
  236. private:
  237. #if JUCE_MAC
  238. WebView* webView;
  239. id clickListener;
  240. #else
  241. UIWebView* webView;
  242. WebViewTapDetector* tapDetector;
  243. WebViewURLChangeDetector* urlDetector;
  244. UITapGestureRecognizer* gestureRecogniser;
  245. #endif
  246. };
  247. //==============================================================================
  248. WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden)
  249. : browser (nullptr),
  250. blankPageShown (false),
  251. unloadPageWhenBrowserIsHidden (unloadWhenHidden)
  252. {
  253. setOpaque (true);
  254. addAndMakeVisible (browser = new Pimpl (this));
  255. }
  256. WebBrowserComponent::~WebBrowserComponent()
  257. {
  258. deleteAndZero (browser);
  259. }
  260. //==============================================================================
  261. void WebBrowserComponent::goToURL (const String& url,
  262. const StringArray* headers,
  263. const MemoryBlock* postData)
  264. {
  265. lastURL = url;
  266. if (headers != nullptr)
  267. lastHeaders = *headers;
  268. else
  269. lastHeaders.clear();
  270. if (postData != nullptr)
  271. lastPostData = *postData;
  272. else
  273. lastPostData.reset();
  274. blankPageShown = false;
  275. browser->goToURL (url, headers, postData);
  276. }
  277. void WebBrowserComponent::stop()
  278. {
  279. browser->stop();
  280. }
  281. void WebBrowserComponent::goBack()
  282. {
  283. lastURL.clear();
  284. blankPageShown = false;
  285. browser->goBack();
  286. }
  287. void WebBrowserComponent::goForward()
  288. {
  289. lastURL.clear();
  290. browser->goForward();
  291. }
  292. void WebBrowserComponent::refresh()
  293. {
  294. browser->refresh();
  295. }
  296. //==============================================================================
  297. void WebBrowserComponent::paint (Graphics&)
  298. {
  299. }
  300. void WebBrowserComponent::checkWindowAssociation()
  301. {
  302. if (isShowing())
  303. {
  304. reloadLastURL();
  305. if (blankPageShown)
  306. goBack();
  307. }
  308. else
  309. {
  310. if (unloadPageWhenBrowserIsHidden && ! blankPageShown)
  311. {
  312. // when the component becomes invisible, some stuff like flash
  313. // carries on playing audio, so we need to force it onto a blank
  314. // page to avoid this, (and send it back when it's made visible again).
  315. blankPageShown = true;
  316. browser->goToURL ("about:blank", 0, 0);
  317. }
  318. }
  319. }
  320. void WebBrowserComponent::reloadLastURL()
  321. {
  322. if (lastURL.isNotEmpty())
  323. {
  324. goToURL (lastURL, &lastHeaders, &lastPostData);
  325. lastURL.clear();
  326. }
  327. }
  328. void WebBrowserComponent::parentHierarchyChanged()
  329. {
  330. checkWindowAssociation();
  331. }
  332. void WebBrowserComponent::resized()
  333. {
  334. browser->setSize (getWidth(), getHeight());
  335. }
  336. void WebBrowserComponent::visibilityChanged()
  337. {
  338. checkWindowAssociation();
  339. }
  340. void WebBrowserComponent::focusGained (FocusChangeType)
  341. {
  342. }