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.

1031 lines
35KB

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