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.

986 lines
33KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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. struct InternalWebViewType
  21. {
  22. InternalWebViewType() = default;
  23. virtual ~InternalWebViewType() = default;
  24. virtual void createBrowser() = 0;
  25. virtual bool hasBrowserBeenCreated() = 0;
  26. virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0;
  27. virtual void stop() = 0;
  28. virtual void goBack() = 0;
  29. virtual void goForward() = 0;
  30. virtual void refresh() = 0;
  31. virtual void focusGained() {}
  32. virtual void setWebViewSize (int, int) = 0;
  33. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InternalWebViewType)
  34. };
  35. //==============================================================================
  36. class Win32WebView : public InternalWebViewType,
  37. public ActiveXControlComponent
  38. {
  39. public:
  40. Win32WebView (WebBrowserComponent& owner)
  41. {
  42. owner.addAndMakeVisible (this);
  43. }
  44. ~Win32WebView() override
  45. {
  46. if (connectionPoint != nullptr)
  47. {
  48. connectionPoint->Unadvise (adviseCookie);
  49. connectionPoint->Release();
  50. }
  51. if (browser != nullptr)
  52. browser->Release();
  53. }
  54. void createBrowser() override
  55. {
  56. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
  57. auto webCLSID = __uuidof (WebBrowser);
  58. createControl (&webCLSID);
  59. auto iidWebBrowser2 = __uuidof (IWebBrowser2);
  60. auto iidConnectionPointContainer = __uuidof (IConnectionPointContainer);
  61. browser = (IWebBrowser2*) queryInterface (&iidWebBrowser2);
  62. if (auto connectionPointContainer = (IConnectionPointContainer*) queryInterface (&iidConnectionPointContainer))
  63. {
  64. connectionPointContainer->FindConnectionPoint (__uuidof (DWebBrowserEvents2), &connectionPoint);
  65. if (connectionPoint != nullptr)
  66. {
  67. if (auto* owner = dynamic_cast<WebBrowserComponent*> (Component::getParentComponent()))
  68. {
  69. auto handler = new EventHandler (*owner);
  70. connectionPoint->Advise (handler, &adviseCookie);
  71. handler->Release();
  72. }
  73. }
  74. connectionPointContainer->Release();
  75. }
  76. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  77. }
  78. bool hasBrowserBeenCreated() override
  79. {
  80. return browser != nullptr;
  81. }
  82. void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override
  83. {
  84. if (browser != nullptr)
  85. {
  86. VARIANT headerFlags, frame, postDataVar, headersVar; // (_variant_t isn't available in all compilers)
  87. VariantInit (&headerFlags);
  88. VariantInit (&frame);
  89. VariantInit (&postDataVar);
  90. VariantInit (&headersVar);
  91. if (headers != nullptr)
  92. {
  93. V_VT (&headersVar) = VT_BSTR;
  94. V_BSTR (&headersVar) = SysAllocString ((const OLECHAR*) headers->joinIntoString ("\r\n").toWideCharPointer());
  95. }
  96. if (postData != nullptr && postData->getSize() > 0)
  97. {
  98. auto sa = SafeArrayCreateVector (VT_UI1, 0, (ULONG) postData->getSize());
  99. if (sa != nullptr)
  100. {
  101. void* data = nullptr;
  102. SafeArrayAccessData (sa, &data);
  103. jassert (data != nullptr);
  104. if (data != nullptr)
  105. {
  106. postData->copyTo (data, 0, postData->getSize());
  107. SafeArrayUnaccessData (sa);
  108. VARIANT postDataVar2;
  109. VariantInit (&postDataVar2);
  110. V_VT (&postDataVar2) = VT_ARRAY | VT_UI1;
  111. V_ARRAY (&postDataVar2) = sa;
  112. sa = nullptr;
  113. postDataVar = postDataVar2;
  114. }
  115. else
  116. {
  117. SafeArrayDestroy (sa);
  118. }
  119. }
  120. }
  121. auto urlBSTR = SysAllocString ((const OLECHAR*) url.toWideCharPointer());
  122. browser->Navigate (urlBSTR, &headerFlags, &frame, &postDataVar, &headersVar);
  123. SysFreeString (urlBSTR);
  124. VariantClear (&headerFlags);
  125. VariantClear (&frame);
  126. VariantClear (&postDataVar);
  127. VariantClear (&headersVar);
  128. }
  129. }
  130. void stop() override
  131. {
  132. if (browser != nullptr)
  133. browser->Stop();
  134. }
  135. void goBack() override
  136. {
  137. if (browser != nullptr)
  138. browser->GoBack();
  139. }
  140. void goForward() override
  141. {
  142. if (browser != nullptr)
  143. browser->GoForward();
  144. }
  145. void refresh() override
  146. {
  147. if (browser != nullptr)
  148. browser->Refresh();
  149. }
  150. void focusGained() override
  151. {
  152. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
  153. auto iidOleObject = __uuidof (IOleObject);
  154. auto iidOleWindow = __uuidof (IOleWindow);
  155. if (auto oleObject = (IOleObject*) queryInterface (&iidOleObject))
  156. {
  157. if (auto oleWindow = (IOleWindow*) queryInterface (&iidOleWindow))
  158. {
  159. IOleClientSite* oleClientSite = nullptr;
  160. if (SUCCEEDED (oleObject->GetClientSite (&oleClientSite)))
  161. {
  162. HWND hwnd;
  163. oleWindow->GetWindow (&hwnd);
  164. oleObject->DoVerb (OLEIVERB_UIACTIVATE, nullptr, oleClientSite, 0, hwnd, nullptr);
  165. oleClientSite->Release();
  166. }
  167. oleWindow->Release();
  168. }
  169. oleObject->Release();
  170. }
  171. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  172. }
  173. using ActiveXControlComponent::focusGained;
  174. void setWebViewSize (int width, int height) override
  175. {
  176. setSize (width, height);
  177. }
  178. private:
  179. IWebBrowser2* browser = nullptr;
  180. IConnectionPoint* connectionPoint = nullptr;
  181. DWORD adviseCookie = 0;
  182. //==============================================================================
  183. struct EventHandler : public ComBaseClassHelper<IDispatch>,
  184. public ComponentMovementWatcher
  185. {
  186. EventHandler (WebBrowserComponent& w) : ComponentMovementWatcher (&w), owner (w) {}
  187. JUCE_COMRESULT GetTypeInfoCount (UINT*) override { return E_NOTIMPL; }
  188. JUCE_COMRESULT GetTypeInfo (UINT, LCID, ITypeInfo**) override { return E_NOTIMPL; }
  189. JUCE_COMRESULT GetIDsOfNames (REFIID, LPOLESTR*, UINT, LCID, DISPID*) override { return E_NOTIMPL; }
  190. JUCE_COMRESULT Invoke (DISPID dispIdMember, REFIID /*riid*/, LCID /*lcid*/, WORD /*wFlags*/, DISPPARAMS* pDispParams,
  191. VARIANT* /*pVarResult*/, EXCEPINFO* /*pExcepInfo*/, UINT* /*puArgErr*/) override
  192. {
  193. if (dispIdMember == DISPID_BEFORENAVIGATE2)
  194. {
  195. *pDispParams->rgvarg->pboolVal
  196. = owner.pageAboutToLoad (getStringFromVariant (pDispParams->rgvarg[5].pvarVal)) ? VARIANT_FALSE
  197. : VARIANT_TRUE;
  198. return S_OK;
  199. }
  200. if (dispIdMember == 273 /*DISPID_NEWWINDOW3*/)
  201. {
  202. owner.newWindowAttemptingToLoad (pDispParams->rgvarg[0].bstrVal);
  203. *pDispParams->rgvarg[3].pboolVal = VARIANT_TRUE;
  204. return S_OK;
  205. }
  206. if (dispIdMember == DISPID_DOCUMENTCOMPLETE)
  207. {
  208. owner.pageFinishedLoading (getStringFromVariant (pDispParams->rgvarg[0].pvarVal));
  209. return S_OK;
  210. }
  211. if (dispIdMember == 271 /*DISPID_NAVIGATEERROR*/)
  212. {
  213. int statusCode = pDispParams->rgvarg[1].pvarVal->intVal;
  214. *pDispParams->rgvarg[0].pboolVal = VARIANT_FALSE;
  215. // IWebBrowser2 also reports http status codes here, we need
  216. // report only network errors
  217. if (statusCode < 0)
  218. {
  219. LPTSTR messageBuffer = nullptr;
  220. auto size = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
  221. nullptr, (DWORD) statusCode, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
  222. (LPTSTR) &messageBuffer, 0, nullptr);
  223. String message (messageBuffer, size);
  224. LocalFree (messageBuffer);
  225. if (! owner.pageLoadHadNetworkError (message))
  226. *pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE;
  227. }
  228. return S_OK;
  229. }
  230. if (dispIdMember == 263 /*DISPID_WINDOWCLOSING*/)
  231. {
  232. owner.windowCloseRequest();
  233. // setting this bool tells the browser to ignore the event - we'll handle it.
  234. if (pDispParams->cArgs > 0 && pDispParams->rgvarg[0].vt == (VT_BYREF | VT_BOOL))
  235. *pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE;
  236. return S_OK;
  237. }
  238. return E_NOTIMPL;
  239. }
  240. void componentMovedOrResized (bool, bool) override {}
  241. void componentPeerChanged() override {}
  242. void componentVisibilityChanged() override { owner.visibilityChanged(); }
  243. using ComponentMovementWatcher::componentVisibilityChanged;
  244. using ComponentMovementWatcher::componentMovedOrResized;
  245. private:
  246. WebBrowserComponent& owner;
  247. static String getStringFromVariant (VARIANT* v)
  248. {
  249. return (v->vt & VT_BYREF) != 0 ? *v->pbstrVal
  250. : v->bstrVal;
  251. }
  252. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EventHandler)
  253. };
  254. //==============================================================================
  255. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32WebView)
  256. };
  257. #if JUCE_USE_WIN_WEBVIEW2
  258. using namespace Microsoft::WRL;
  259. class WebView2 : public InternalWebViewType,
  260. public Component,
  261. public ComponentMovementWatcher
  262. {
  263. public:
  264. WebView2 (WebBrowserComponent& o, const WebView2Preferences& prefs)
  265. : ComponentMovementWatcher (&o),
  266. owner (o),
  267. preferences (prefs)
  268. {
  269. if (! createWebViewEnvironment())
  270. throw std::runtime_error ("Failed to create the CoreWebView2Environemnt");
  271. owner.addAndMakeVisible (this);
  272. }
  273. ~WebView2() override
  274. {
  275. removeEventHandlers();
  276. closeWebView();
  277. if (webView2LoaderHandle != nullptr)
  278. ::FreeLibrary (webView2LoaderHandle);
  279. }
  280. void createBrowser() override
  281. {
  282. if (webView == nullptr)
  283. {
  284. jassert (webViewEnvironment != nullptr);
  285. createWebView();
  286. }
  287. }
  288. bool hasBrowserBeenCreated() override
  289. {
  290. return webView != nullptr || isCreating;
  291. }
  292. void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override
  293. {
  294. urlRequest = { url,
  295. headers != nullptr ? *headers : StringArray(),
  296. postData != nullptr && postData->getSize() > 0 ? *postData : MemoryBlock() };
  297. if (webView != nullptr)
  298. webView->Navigate (urlRequest.url.toWideCharPointer());
  299. }
  300. void stop() override
  301. {
  302. if (webView != nullptr)
  303. webView->Stop();
  304. }
  305. void goBack() override
  306. {
  307. if (webView != nullptr)
  308. {
  309. BOOL canGoBack = false;
  310. webView->get_CanGoBack (&canGoBack);
  311. if (canGoBack)
  312. webView->GoBack();
  313. }
  314. }
  315. void goForward() override
  316. {
  317. if (webView != nullptr)
  318. {
  319. BOOL canGoForward = false;
  320. webView->get_CanGoForward (&canGoForward);
  321. if (canGoForward)
  322. webView->GoForward();
  323. }
  324. }
  325. void refresh() override
  326. {
  327. if (webView != nullptr)
  328. webView->Reload();
  329. }
  330. void setWebViewSize (int width, int height) override
  331. {
  332. setSize (width, height);
  333. }
  334. void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
  335. {
  336. if (auto* peer = owner.getTopLevelComponent()->getPeer())
  337. setControlBounds (peer->getAreaCoveredBy (owner));
  338. }
  339. void componentPeerChanged() override
  340. {
  341. componentMovedOrResized (true, true);
  342. }
  343. void componentVisibilityChanged() override
  344. {
  345. setControlVisible (owner.isShowing());
  346. componentPeerChanged();
  347. owner.visibilityChanged();
  348. }
  349. private:
  350. //==============================================================================
  351. template <class ArgType>
  352. static String getUriStringFromArgs (ArgType* args)
  353. {
  354. if (args != nullptr)
  355. {
  356. LPWSTR uri;
  357. args->get_Uri (&uri);
  358. return uri;
  359. }
  360. return {};
  361. }
  362. //==============================================================================
  363. void addEventHandlers()
  364. {
  365. if (webView != nullptr)
  366. {
  367. webView->add_NavigationStarting (Callback<ICoreWebView2NavigationStartingEventHandler> (
  368. [this] (ICoreWebView2*, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
  369. {
  370. auto uriString = getUriStringFromArgs (args);
  371. if (uriString.isNotEmpty() && ! owner.pageAboutToLoad (uriString))
  372. args->put_Cancel (true);
  373. return S_OK;
  374. }).Get(), &navigationStartingToken);
  375. webView->add_NewWindowRequested (Callback<ICoreWebView2NewWindowRequestedEventHandler> (
  376. [this] (ICoreWebView2*, ICoreWebView2NewWindowRequestedEventArgs* args) -> HRESULT
  377. {
  378. auto uriString = getUriStringFromArgs (args);
  379. if (uriString.isNotEmpty())
  380. {
  381. owner.newWindowAttemptingToLoad (uriString);
  382. args->put_Handled (true);
  383. }
  384. return S_OK;
  385. }).Get(), &newWindowRequestedToken);
  386. webView->add_WindowCloseRequested (Callback<ICoreWebView2WindowCloseRequestedEventHandler> (
  387. [this] (ICoreWebView2*, IUnknown*) -> HRESULT
  388. {
  389. owner.windowCloseRequest();
  390. return S_OK;
  391. }).Get(), &windowCloseRequestedToken);
  392. webView->add_NavigationCompleted (Callback<ICoreWebView2NavigationCompletedEventHandler> (
  393. [this] (ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT
  394. {
  395. LPWSTR uri;
  396. sender->get_Source (&uri);
  397. String uriString (uri);
  398. if (uriString.isNotEmpty())
  399. {
  400. BOOL success = false;
  401. args->get_IsSuccess (&success);
  402. COREWEBVIEW2_WEB_ERROR_STATUS errorStatus;
  403. args->get_WebErrorStatus (&errorStatus);
  404. if (success
  405. || errorStatus == COREWEBVIEW2_WEB_ERROR_STATUS_OPERATION_CANCELED) // this error seems to happen erroneously so ignore
  406. {
  407. owner.pageFinishedLoading (uriString);
  408. }
  409. else
  410. {
  411. auto errorString = "Error code: " + String (errorStatus);
  412. if (owner.pageLoadHadNetworkError (errorString))
  413. owner.goToURL ("data:text/plain;charset=UTF-8," + errorString);
  414. }
  415. }
  416. return S_OK;
  417. }).Get(), &navigationCompletedToken);
  418. webView->AddWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
  419. webView->add_WebResourceRequested (Callback<ICoreWebView2WebResourceRequestedEventHandler> (
  420. [this] (ICoreWebView2*, ICoreWebView2WebResourceRequestedEventArgs* args) -> HRESULT
  421. {
  422. if (urlRequest.url.isEmpty())
  423. return S_OK;
  424. ComSmartPtr<ICoreWebView2WebResourceRequest> request;
  425. args->get_Request (request.resetAndGetPointerAddress());
  426. auto uriString = getUriStringFromArgs<ICoreWebView2WebResourceRequest> (request);
  427. if (uriString == urlRequest.url
  428. || (uriString.endsWith ("/") && uriString.upToLastOccurrenceOf ("/", false, false) == urlRequest.url))
  429. {
  430. String method ("GET");
  431. if (! urlRequest.postData.isEmpty())
  432. {
  433. method = "POST";
  434. ComSmartPtr<IStream> content (SHCreateMemStream ((BYTE*) urlRequest.postData.getData(),
  435. (UINT) urlRequest.postData.getSize()));
  436. request->put_Content (content);
  437. }
  438. if (! urlRequest.headers.isEmpty())
  439. {
  440. ComSmartPtr<ICoreWebView2HttpRequestHeaders> headers;
  441. request->get_Headers (headers.resetAndGetPointerAddress());
  442. for (auto& header : urlRequest.headers)
  443. {
  444. headers->SetHeader (header.upToFirstOccurrenceOf (":", false, false).trim().toWideCharPointer(),
  445. header.fromFirstOccurrenceOf (":", false, false).trim().toWideCharPointer());
  446. }
  447. }
  448. request->put_Method (method.toWideCharPointer());
  449. urlRequest = {};
  450. }
  451. return S_OK;
  452. }).Get(), &webResourceRequestedToken);
  453. }
  454. }
  455. void removeEventHandlers()
  456. {
  457. if (webView != nullptr)
  458. {
  459. if (navigationStartingToken.value != 0)
  460. webView->remove_NavigationStarting (navigationStartingToken);
  461. if (newWindowRequestedToken.value != 0)
  462. webView->remove_NewWindowRequested (newWindowRequestedToken);
  463. if (windowCloseRequestedToken.value != 0)
  464. webView->remove_WindowCloseRequested (windowCloseRequestedToken);
  465. if (navigationCompletedToken.value != 0)
  466. webView->remove_NavigationCompleted (navigationCompletedToken);
  467. if (webResourceRequestedToken.value != 0)
  468. {
  469. webView->RemoveWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
  470. webView->remove_WebResourceRequested (webResourceRequestedToken);
  471. }
  472. }
  473. }
  474. void setWebViewPreferences()
  475. {
  476. ComSmartPtr<ICoreWebView2Controller2> controller2;
  477. webViewController->QueryInterface (controller2.resetAndGetPointerAddress());
  478. if (controller2 != nullptr)
  479. {
  480. const auto bgColour = preferences.getBackgroundColour();
  481. controller2->put_DefaultBackgroundColor ({ (BYTE) bgColour.getAlpha(),
  482. (BYTE) bgColour.getRed(),
  483. (BYTE) bgColour.getGreen(),
  484. (BYTE) bgColour.getBlue() });
  485. }
  486. ComSmartPtr<ICoreWebView2Settings> settings;
  487. webView->get_Settings (settings.resetAndGetPointerAddress());
  488. if (settings != nullptr)
  489. {
  490. settings->put_IsStatusBarEnabled (! preferences.getIsStatusBarDisabled());
  491. settings->put_IsBuiltInErrorPageEnabled (! preferences.getIsBuiltInErrorPageDisabled());
  492. }
  493. }
  494. bool createWebViewEnvironment()
  495. {
  496. using CreateWebViewEnvironmentWithOptionsFunc = HRESULT (*) (PCWSTR, PCWSTR,
  497. ICoreWebView2EnvironmentOptions*,
  498. ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*);
  499. auto dllPath = preferences.getDLLLocation().getFullPathName();
  500. if (dllPath.isEmpty())
  501. dllPath = "WebView2Loader.dll";
  502. webView2LoaderHandle = LoadLibraryA (dllPath.toUTF8());
  503. if (webView2LoaderHandle == nullptr)
  504. return false;
  505. auto* createWebViewEnvironmentWithOptions = (CreateWebViewEnvironmentWithOptionsFunc) GetProcAddress (webView2LoaderHandle,
  506. "CreateCoreWebView2EnvironmentWithOptions");
  507. if (createWebViewEnvironmentWithOptions == nullptr)
  508. {
  509. // failed to load WebView2Loader.dll
  510. jassertfalse;
  511. return false;
  512. }
  513. auto options = Microsoft::WRL::Make<CoreWebView2EnvironmentOptions>();
  514. const auto userDataFolder = preferences.getUserDataFolder().getFullPathName();
  515. auto hr = createWebViewEnvironmentWithOptions (nullptr,
  516. userDataFolder.isNotEmpty() ? userDataFolder.toWideCharPointer() : nullptr,
  517. options.Get(),
  518. Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
  519. [weakThis = WeakReference<WebView2> { this }] (HRESULT, ICoreWebView2Environment* env) -> HRESULT
  520. {
  521. if (weakThis != nullptr)
  522. weakThis->webViewEnvironment = env;
  523. return S_OK;
  524. }).Get());
  525. return SUCCEEDED (hr);
  526. }
  527. void createWebView()
  528. {
  529. if (auto* peer = getPeer())
  530. {
  531. isCreating = true;
  532. WeakReference<WebView2> weakThis (this);
  533. webViewEnvironment->CreateCoreWebView2Controller ((HWND) peer->getNativeHandle(),
  534. Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler> (
  535. [weakThis = WeakReference<WebView2> { this }] (HRESULT, ICoreWebView2Controller* controller) -> HRESULT
  536. {
  537. if (weakThis != nullptr)
  538. {
  539. weakThis->isCreating = false;
  540. if (controller != nullptr)
  541. {
  542. weakThis->webViewController = controller;
  543. controller->get_CoreWebView2 (weakThis->webView.resetAndGetPointerAddress());
  544. if (weakThis->webView != nullptr)
  545. {
  546. weakThis->addEventHandlers();
  547. weakThis->setWebViewPreferences();
  548. weakThis->componentMovedOrResized (true, true);
  549. if (weakThis->urlRequest.url.isNotEmpty())
  550. weakThis->webView->Navigate (weakThis->urlRequest.url.toWideCharPointer());
  551. }
  552. }
  553. }
  554. return S_OK;
  555. }).Get());
  556. }
  557. }
  558. void closeWebView()
  559. {
  560. if (webViewController != nullptr)
  561. {
  562. webViewController->Close();
  563. webViewController = nullptr;
  564. webView = nullptr;
  565. }
  566. webViewEnvironment = nullptr;
  567. }
  568. //==============================================================================
  569. void setControlBounds (Rectangle<int> newBounds) const
  570. {
  571. if (webViewController != nullptr)
  572. {
  573. #if JUCE_WIN_PER_MONITOR_DPI_AWARE
  574. if (auto* peer = owner.getTopLevelComponent()->getPeer())
  575. newBounds = (newBounds.toDouble() * peer->getPlatformScaleFactor()).toNearestInt();
  576. #endif
  577. webViewController->put_Bounds({ newBounds.getX(), newBounds.getY(),
  578. newBounds.getRight(), newBounds.getBottom() });
  579. }
  580. }
  581. void setControlVisible (bool shouldBeVisible) const
  582. {
  583. if (webViewController != nullptr)
  584. webViewController->put_IsVisible (shouldBeVisible);
  585. }
  586. //==============================================================================
  587. WebBrowserComponent& owner;
  588. WebView2Preferences preferences;
  589. HMODULE webView2LoaderHandle = nullptr;
  590. ComSmartPtr<ICoreWebView2Environment> webViewEnvironment;
  591. ComSmartPtr<ICoreWebView2Controller> webViewController;
  592. ComSmartPtr<ICoreWebView2> webView;
  593. EventRegistrationToken navigationStartingToken { 0 },
  594. newWindowRequestedToken { 0 },
  595. windowCloseRequestedToken { 0 },
  596. navigationCompletedToken { 0 },
  597. webResourceRequestedToken { 0 };
  598. struct URLRequest
  599. {
  600. String url;
  601. StringArray headers;
  602. MemoryBlock postData;
  603. };
  604. URLRequest urlRequest;
  605. bool isCreating = false;
  606. //==============================================================================
  607. JUCE_DECLARE_WEAK_REFERENCEABLE (WebView2)
  608. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebView2)
  609. };
  610. #endif
  611. //==============================================================================
  612. class WebBrowserComponent::Pimpl
  613. {
  614. public:
  615. Pimpl (WebBrowserComponent& owner,
  616. const WebView2Preferences& preferences,
  617. bool useWebView2)
  618. {
  619. if (useWebView2)
  620. {
  621. #if JUCE_USE_WIN_WEBVIEW2
  622. try
  623. {
  624. internal.reset (new WebView2 (owner, preferences));
  625. }
  626. catch (const std::runtime_error&) {}
  627. #endif
  628. }
  629. ignoreUnused (preferences);
  630. if (internal == nullptr)
  631. internal.reset (new Win32WebView (owner));
  632. }
  633. InternalWebViewType& getInternalWebView()
  634. {
  635. return *internal;
  636. }
  637. private:
  638. std::unique_ptr<InternalWebViewType> internal;
  639. };
  640. //==============================================================================
  641. WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden)
  642. : browser (new Pimpl (*this, {}, false)),
  643. unloadPageWhenHidden (unloadWhenHidden)
  644. {
  645. setOpaque (true);
  646. }
  647. WebBrowserComponent::WebBrowserComponent (ConstructWithoutPimpl args)
  648. : unloadPageWhenHidden (args.unloadWhenHidden)
  649. {
  650. setOpaque (true);
  651. }
  652. WebBrowserComponent::~WebBrowserComponent()
  653. {
  654. }
  655. WindowsWebView2WebBrowserComponent::WindowsWebView2WebBrowserComponent (bool unloadWhenHidden,
  656. const WebView2Preferences& preferences)
  657. : WebBrowserComponent (ConstructWithoutPimpl { unloadWhenHidden })
  658. {
  659. browser = std::make_unique<Pimpl> (*this, preferences, true);
  660. }
  661. //==============================================================================
  662. void WebBrowserComponent::goToURL (const String& url,
  663. const StringArray* headers,
  664. const MemoryBlock* postData)
  665. {
  666. lastURL = url;
  667. if (headers != nullptr)
  668. lastHeaders = *headers;
  669. else
  670. lastHeaders.clear();
  671. if (postData != nullptr)
  672. lastPostData = *postData;
  673. else
  674. lastPostData.reset();
  675. blankPageShown = false;
  676. if (! browser->getInternalWebView().hasBrowserBeenCreated())
  677. checkWindowAssociation();
  678. browser->getInternalWebView().goToURL (url, headers, postData);
  679. }
  680. void WebBrowserComponent::stop()
  681. {
  682. browser->getInternalWebView().stop();
  683. }
  684. void WebBrowserComponent::goBack()
  685. {
  686. lastURL.clear();
  687. blankPageShown = false;
  688. browser->getInternalWebView().goBack();
  689. }
  690. void WebBrowserComponent::goForward()
  691. {
  692. lastURL.clear();
  693. browser->getInternalWebView().goForward();
  694. }
  695. void WebBrowserComponent::refresh()
  696. {
  697. browser->getInternalWebView().refresh();
  698. }
  699. //==============================================================================
  700. void WebBrowserComponent::paint (Graphics& g)
  701. {
  702. if (! browser->getInternalWebView().hasBrowserBeenCreated())
  703. {
  704. g.fillAll (Colours::white);
  705. checkWindowAssociation();
  706. }
  707. }
  708. void WebBrowserComponent::checkWindowAssociation()
  709. {
  710. if (isShowing())
  711. {
  712. if (! browser->getInternalWebView().hasBrowserBeenCreated() && getPeer() != nullptr)
  713. {
  714. browser->getInternalWebView().createBrowser();
  715. reloadLastURL();
  716. }
  717. else
  718. {
  719. if (blankPageShown)
  720. goBack();
  721. }
  722. }
  723. else
  724. {
  725. if (browser != nullptr && unloadPageWhenHidden && ! blankPageShown)
  726. {
  727. // when the component becomes invisible, some stuff like flash
  728. // carries on playing audio, so we need to force it onto a blank
  729. // page to avoid this..
  730. blankPageShown = true;
  731. browser->getInternalWebView().goToURL ("about:blank", nullptr, nullptr);
  732. }
  733. }
  734. }
  735. void WebBrowserComponent::reloadLastURL()
  736. {
  737. if (lastURL.isNotEmpty())
  738. {
  739. goToURL (lastURL, &lastHeaders, &lastPostData);
  740. lastURL.clear();
  741. }
  742. }
  743. void WebBrowserComponent::parentHierarchyChanged()
  744. {
  745. checkWindowAssociation();
  746. }
  747. void WebBrowserComponent::resized()
  748. {
  749. browser->getInternalWebView().setWebViewSize (getWidth(), getHeight());
  750. }
  751. void WebBrowserComponent::visibilityChanged()
  752. {
  753. checkWindowAssociation();
  754. }
  755. void WebBrowserComponent::focusGained (FocusChangeType)
  756. {
  757. browser->getInternalWebView().focusGained();
  758. }
  759. void WebBrowserComponent::clearCookies()
  760. {
  761. HeapBlock<::INTERNET_CACHE_ENTRY_INFOA> entry;
  762. ::DWORD entrySize = sizeof (::INTERNET_CACHE_ENTRY_INFOA);
  763. ::HANDLE urlCacheHandle = ::FindFirstUrlCacheEntryA ("cookie:", entry.getData(), &entrySize);
  764. if (urlCacheHandle == nullptr && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
  765. {
  766. entry.realloc (1, entrySize);
  767. urlCacheHandle = ::FindFirstUrlCacheEntryA ("cookie:", entry.getData(), &entrySize);
  768. }
  769. if (urlCacheHandle != nullptr)
  770. {
  771. for (;;)
  772. {
  773. ::DeleteUrlCacheEntryA (entry.getData()->lpszSourceUrlName);
  774. if (::FindNextUrlCacheEntryA (urlCacheHandle, entry.getData(), &entrySize) == 0)
  775. {
  776. if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
  777. {
  778. entry.realloc (1, entrySize);
  779. if (::FindNextUrlCacheEntryA (urlCacheHandle, entry.getData(), &entrySize) != 0)
  780. continue;
  781. }
  782. break;
  783. }
  784. }
  785. FindCloseUrlCache (urlCacheHandle);
  786. }
  787. }
  788. } // namespace juce