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.

974 lines
32KB

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