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.

428 lines
14KB

  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. namespace juce
  20. {
  21. JUCE_DECLARE_UUID_GETTER (DWebBrowserEvents2, "34A715A0-6587-11D0-924A-0020AFC7AC4D")
  22. JUCE_DECLARE_UUID_GETTER (IConnectionPointContainer, "B196B284-BAB4-101A-B69C-00AA00341D07")
  23. JUCE_DECLARE_UUID_GETTER (IWebBrowser2, "D30C1661-CDAF-11D0-8A3E-00C04FC9E26E")
  24. JUCE_DECLARE_UUID_GETTER (WebBrowser, "8856F961-340A-11D0-A96B-00C04FD705A2")
  25. class WebBrowserComponent::Pimpl : public ActiveXControlComponent
  26. {
  27. public:
  28. Pimpl() {}
  29. ~Pimpl()
  30. {
  31. if (connectionPoint != nullptr)
  32. connectionPoint->Unadvise (adviseCookie);
  33. if (browser != nullptr)
  34. browser->Release();
  35. }
  36. void createBrowser()
  37. {
  38. auto webCLSID = __uuidof (WebBrowser);
  39. createControl (&webCLSID);
  40. auto iidWebBrowser2 = __uuidof (IWebBrowser2);
  41. auto iidConnectionPointContainer = __uuidof (IConnectionPointContainer);
  42. browser = (IWebBrowser2*) queryInterface (&iidWebBrowser2);
  43. if (auto connectionPointContainer = (IConnectionPointContainer*) queryInterface (&iidConnectionPointContainer))
  44. {
  45. connectionPointContainer->FindConnectionPoint (__uuidof (DWebBrowserEvents2), &connectionPoint);
  46. if (connectionPoint != nullptr)
  47. {
  48. auto* owner = dynamic_cast<WebBrowserComponent*> (getParentComponent());
  49. jassert (owner != nullptr);
  50. auto handler = new EventHandler (*owner);
  51. connectionPoint->Advise (handler, &adviseCookie);
  52. handler->Release();
  53. }
  54. }
  55. }
  56. void goToURL (const String& url,
  57. const StringArray* headers,
  58. const MemoryBlock* postData)
  59. {
  60. if (browser != nullptr)
  61. {
  62. LPSAFEARRAY sa = nullptr;
  63. VARIANT headerFlags, frame, postDataVar, headersVar; // (_variant_t isn't available in all compilers)
  64. VariantInit (&headerFlags);
  65. VariantInit (&frame);
  66. VariantInit (&postDataVar);
  67. VariantInit (&headersVar);
  68. if (headers != nullptr)
  69. {
  70. V_VT (&headersVar) = VT_BSTR;
  71. V_BSTR (&headersVar) = SysAllocString ((const OLECHAR*) headers->joinIntoString ("\r\n").toWideCharPointer());
  72. }
  73. if (postData != nullptr && postData->getSize() > 0)
  74. {
  75. sa = SafeArrayCreateVector (VT_UI1, 0, (ULONG) postData->getSize());
  76. if (sa != nullptr)
  77. {
  78. void* data = nullptr;
  79. SafeArrayAccessData (sa, &data);
  80. jassert (data != nullptr);
  81. if (data != nullptr)
  82. {
  83. postData->copyTo (data, 0, postData->getSize());
  84. SafeArrayUnaccessData (sa);
  85. VARIANT postDataVar2;
  86. VariantInit (&postDataVar2);
  87. V_VT (&postDataVar2) = VT_ARRAY | VT_UI1;
  88. V_ARRAY (&postDataVar2) = sa;
  89. postDataVar = postDataVar2;
  90. }
  91. }
  92. }
  93. auto urlBSTR = SysAllocString ((const OLECHAR*) url.toWideCharPointer());
  94. browser->Navigate (urlBSTR, &headerFlags, &frame, &postDataVar, &headersVar);
  95. SysFreeString (urlBSTR);
  96. if (sa != nullptr)
  97. SafeArrayDestroy (sa);
  98. VariantClear (&headerFlags);
  99. VariantClear (&frame);
  100. VariantClear (&postDataVar);
  101. VariantClear (&headersVar);
  102. }
  103. }
  104. //==============================================================================
  105. IWebBrowser2* browser = nullptr;
  106. private:
  107. IConnectionPoint* connectionPoint = nullptr;
  108. DWORD adviseCookie = 0;
  109. //==============================================================================
  110. struct EventHandler : public ComBaseClassHelper<IDispatch>,
  111. public ComponentMovementWatcher
  112. {
  113. EventHandler (WebBrowserComponent& w) : ComponentMovementWatcher (&w), owner (w) {}
  114. JUCE_COMRESULT GetTypeInfoCount (UINT*) { return E_NOTIMPL; }
  115. JUCE_COMRESULT GetTypeInfo (UINT, LCID, ITypeInfo**) { return E_NOTIMPL; }
  116. JUCE_COMRESULT GetIDsOfNames (REFIID, LPOLESTR*, UINT, LCID, DISPID*) { return E_NOTIMPL; }
  117. JUCE_COMRESULT Invoke (DISPID dispIdMember, REFIID /*riid*/, LCID /*lcid*/, WORD /*wFlags*/, DISPPARAMS* pDispParams,
  118. VARIANT* /*pVarResult*/, EXCEPINFO* /*pExcepInfo*/, UINT* /*puArgErr*/)
  119. {
  120. if (dispIdMember == DISPID_BEFORENAVIGATE2)
  121. {
  122. *pDispParams->rgvarg->pboolVal
  123. = owner.pageAboutToLoad (getStringFromVariant (pDispParams->rgvarg[5].pvarVal)) ? VARIANT_FALSE
  124. : VARIANT_TRUE;
  125. return S_OK;
  126. }
  127. if (dispIdMember == 273 /*DISPID_NEWWINDOW3*/)
  128. {
  129. owner.newWindowAttemptingToLoad (pDispParams->rgvarg[0].bstrVal);
  130. *pDispParams->rgvarg[3].pboolVal = VARIANT_TRUE;
  131. return S_OK;
  132. }
  133. if (dispIdMember == DISPID_DOCUMENTCOMPLETE)
  134. {
  135. owner.pageFinishedLoading (getStringFromVariant (pDispParams->rgvarg[0].pvarVal));
  136. return S_OK;
  137. }
  138. if (dispIdMember == DISPID_NAVIGATEERROR)
  139. {
  140. int statusCode = pDispParams->rgvarg[1].pvarVal->intVal;
  141. *pDispParams->rgvarg[0].pboolVal = VARIANT_FALSE;
  142. // IWebBrowser2 also reports http status codes here, we need
  143. // report only network erros
  144. if (statusCode < 0)
  145. {
  146. LPTSTR messageBuffer = nullptr;
  147. auto size = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
  148. nullptr, statusCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
  149. (LPTSTR) &messageBuffer, 0, nullptr);
  150. String message (messageBuffer, size);
  151. LocalFree (messageBuffer);
  152. if (! owner.pageLoadHadNetworkError (message))
  153. *pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE;
  154. }
  155. return S_OK;
  156. }
  157. if (dispIdMember == 263 /*DISPID_WINDOWCLOSING*/)
  158. {
  159. owner.windowCloseRequest();
  160. // setting this bool tells the browser to ignore the event - we'll handle it.
  161. if (pDispParams->cArgs > 0 && pDispParams->rgvarg[0].vt == (VT_BYREF | VT_BOOL))
  162. *pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE;
  163. return S_OK;
  164. }
  165. return E_NOTIMPL;
  166. }
  167. void componentMovedOrResized (bool, bool) override {}
  168. void componentPeerChanged() override {}
  169. void componentVisibilityChanged() override { owner.visibilityChanged(); }
  170. private:
  171. WebBrowserComponent& owner;
  172. static String getStringFromVariant (VARIANT* v)
  173. {
  174. return (v->vt & VT_BYREF) != 0 ? *v->pbstrVal
  175. : v->bstrVal;
  176. }
  177. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EventHandler)
  178. };
  179. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  180. };
  181. //==============================================================================
  182. WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_)
  183. : browser (nullptr),
  184. blankPageShown (false),
  185. unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_)
  186. {
  187. setOpaque (true);
  188. addAndMakeVisible (browser = new Pimpl());
  189. }
  190. WebBrowserComponent::~WebBrowserComponent()
  191. {
  192. delete browser;
  193. }
  194. //==============================================================================
  195. void WebBrowserComponent::goToURL (const String& url,
  196. const StringArray* headers,
  197. const MemoryBlock* postData)
  198. {
  199. lastURL = url;
  200. if (headers != nullptr)
  201. lastHeaders = *headers;
  202. else
  203. lastHeaders.clear();
  204. if (postData != nullptr)
  205. lastPostData = *postData;
  206. else
  207. lastPostData.reset();
  208. blankPageShown = false;
  209. if (browser->browser == nullptr)
  210. checkWindowAssociation();
  211. browser->goToURL (url, headers, postData);
  212. }
  213. void WebBrowserComponent::stop()
  214. {
  215. if (browser->browser != nullptr)
  216. browser->browser->Stop();
  217. }
  218. void WebBrowserComponent::goBack()
  219. {
  220. lastURL.clear();
  221. blankPageShown = false;
  222. if (browser->browser != nullptr)
  223. browser->browser->GoBack();
  224. }
  225. void WebBrowserComponent::goForward()
  226. {
  227. lastURL.clear();
  228. if (browser->browser != nullptr)
  229. browser->browser->GoForward();
  230. }
  231. void WebBrowserComponent::refresh()
  232. {
  233. if (browser->browser != nullptr)
  234. browser->browser->Refresh();
  235. }
  236. //==============================================================================
  237. void WebBrowserComponent::paint (Graphics& g)
  238. {
  239. if (browser->browser == nullptr)
  240. {
  241. g.fillAll (Colours::white);
  242. checkWindowAssociation();
  243. }
  244. }
  245. void WebBrowserComponent::checkWindowAssociation()
  246. {
  247. if (isShowing())
  248. {
  249. if (browser->browser == nullptr && getPeer() != nullptr)
  250. {
  251. browser->createBrowser();
  252. reloadLastURL();
  253. }
  254. else
  255. {
  256. if (blankPageShown)
  257. goBack();
  258. }
  259. }
  260. else
  261. {
  262. if (browser != nullptr && unloadPageWhenBrowserIsHidden && ! blankPageShown)
  263. {
  264. // when the component becomes invisible, some stuff like flash
  265. // carries on playing audio, so we need to force it onto a blank
  266. // page to avoid this..
  267. blankPageShown = true;
  268. browser->goToURL ("about:blank", 0, 0);
  269. }
  270. }
  271. }
  272. void WebBrowserComponent::reloadLastURL()
  273. {
  274. if (lastURL.isNotEmpty())
  275. {
  276. goToURL (lastURL, &lastHeaders, &lastPostData);
  277. lastURL.clear();
  278. }
  279. }
  280. void WebBrowserComponent::parentHierarchyChanged()
  281. {
  282. checkWindowAssociation();
  283. }
  284. void WebBrowserComponent::resized()
  285. {
  286. browser->setSize (getWidth(), getHeight());
  287. }
  288. void WebBrowserComponent::visibilityChanged()
  289. {
  290. checkWindowAssociation();
  291. }
  292. void WebBrowserComponent::focusGained (FocusChangeType)
  293. {
  294. auto iidOleObject = __uuidof (IOleObject);
  295. auto iidOleWindow = __uuidof (IOleWindow);
  296. if (auto oleObject = (IOleObject*) browser->queryInterface (&iidOleObject))
  297. {
  298. if (auto oleWindow = (IOleWindow*) browser->queryInterface (&iidOleWindow))
  299. {
  300. IOleClientSite* oleClientSite = nullptr;
  301. if (SUCCEEDED (oleObject->GetClientSite (&oleClientSite)))
  302. {
  303. HWND hwnd;
  304. oleWindow->GetWindow (&hwnd);
  305. oleObject->DoVerb (OLEIVERB_UIACTIVATE, nullptr, oleClientSite, 0, hwnd, nullptr);
  306. oleClientSite->Release();
  307. }
  308. oleWindow->Release();
  309. }
  310. oleObject->Release();
  311. }
  312. }
  313. void WebBrowserComponent::clearCookies()
  314. {
  315. HeapBlock<::INTERNET_CACHE_ENTRY_INFO> entry;
  316. ::DWORD entrySize = sizeof (::INTERNET_CACHE_ENTRY_INFO);
  317. ::HANDLE urlCacheHandle = ::FindFirstUrlCacheEntry (TEXT ("cookie:"), entry.getData(), &entrySize);
  318. if (urlCacheHandle == nullptr && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
  319. {
  320. entry.realloc (1, entrySize);
  321. urlCacheHandle = ::FindFirstUrlCacheEntry (TEXT ("cookie:"), entry.getData(), &entrySize);
  322. }
  323. if (urlCacheHandle != nullptr)
  324. {
  325. for (;;)
  326. {
  327. ::DeleteUrlCacheEntry (entry.getData()->lpszSourceUrlName);
  328. if (::FindNextUrlCacheEntry (urlCacheHandle, entry.getData(), &entrySize) == 0)
  329. {
  330. if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
  331. {
  332. entry.realloc (1, entrySize);
  333. if (::FindNextUrlCacheEntry (urlCacheHandle, entry.getData(), &entrySize) != 0)
  334. continue;
  335. }
  336. break;
  337. }
  338. }
  339. FindCloseUrlCache (urlCacheHandle);
  340. }
  341. }
  342. } // namespace juce