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.

1228 lines
44KB

  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 final : 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 final : 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. static std::vector<HWND> getDirectChildWindows (HWND hwnd)
  279. {
  280. std::vector<HWND> result;
  281. const auto getNextChildWindow = [hwnd, &result]
  282. {
  283. return FindWindowExA (hwnd, result.empty() ? nullptr : result.back(), nullptr, nullptr);
  284. };
  285. for (auto* next = getNextChildWindow(); next != nullptr; next = getNextChildWindow())
  286. result.push_back (next);
  287. return result;
  288. }
  289. static void forEachChildWindowRecursive (HWND hwnd, std::function<bool (HWND)> callback)
  290. {
  291. // EnumChildWindows itself provides the recursion
  292. EnumChildWindows (hwnd,
  293. [] (HWND hwnd, LPARAM lParam)
  294. {
  295. auto* callbackPtr = reinterpret_cast<std::function<bool (HWND)>*> (lParam);
  296. return (*callbackPtr) (hwnd) ? TRUE : FALSE;
  297. },
  298. reinterpret_cast<LPARAM> (&callback));
  299. }
  300. static bool anyChildWindow (HWND hwnd, std::function<bool (HWND)> predicate)
  301. {
  302. auto result = false;
  303. forEachChildWindowRecursive (hwnd,
  304. [&predicate, &result] (auto* child)
  305. {
  306. result = predicate (child);
  307. const auto keepGoing = ! result;
  308. return keepGoing;
  309. });
  310. return result;
  311. }
  312. class WebView2 final : public InternalWebViewType,
  313. public Component,
  314. public ComponentMovementWatcher,
  315. private AsyncUpdater
  316. {
  317. public:
  318. WebView2 (WebBrowserComponent& o, const WebBrowserComponent::Options& prefs)
  319. : ComponentMovementWatcher (&o),
  320. owner (o),
  321. preferences (prefs.getWinWebView2BackendOptions()),
  322. userAgent (prefs.getUserAgent())
  323. {
  324. if (auto handle = createWebViewHandle (preferences))
  325. webViewHandle = std::move (*handle);
  326. else
  327. throw std::runtime_error ("Failed to create the CoreWebView2Environment");
  328. owner.addAndMakeVisible (this);
  329. }
  330. void focusGainedWithDirection (FocusChangeType, FocusChangeDirection direction) override
  331. {
  332. if (inMoveFocusRequested)
  333. return;
  334. const auto moveFocusReason = [&]
  335. {
  336. if (direction == FocusChangeDirection::backward)
  337. return COREWEBVIEW2_MOVE_FOCUS_REASON_PREVIOUS;
  338. if (direction == FocusChangeDirection::forward)
  339. return COREWEBVIEW2_MOVE_FOCUS_REASON_NEXT;
  340. return COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC;
  341. }();
  342. if (webViewController != nullptr)
  343. webViewController->MoveFocus (moveFocusReason);
  344. }
  345. ~WebView2() override
  346. {
  347. removeEventHandlers();
  348. closeWebView();
  349. }
  350. void createBrowser() override
  351. {
  352. if (webView == nullptr)
  353. {
  354. jassert (webViewHandle.environment != nullptr);
  355. createWebView();
  356. }
  357. }
  358. bool hasBrowserBeenCreated() override
  359. {
  360. return webView != nullptr
  361. || webView2ConstructionHelper.webView2BeingCreated == this
  362. || webView2ConstructionHelper.viewsWaitingForCreation.count (this) > 0;
  363. }
  364. void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override
  365. {
  366. urlRequest = { url,
  367. headers != nullptr ? *headers : StringArray(),
  368. postData != nullptr && postData->getSize() > 0 ? *postData : MemoryBlock() };
  369. if (webView != nullptr)
  370. webView->Navigate (urlRequest.url.toWideCharPointer());
  371. }
  372. void stop() override
  373. {
  374. if (webView != nullptr)
  375. webView->Stop();
  376. }
  377. void goBack() override
  378. {
  379. if (webView != nullptr)
  380. {
  381. BOOL canGoBack = false;
  382. webView->get_CanGoBack (&canGoBack);
  383. if (canGoBack)
  384. webView->GoBack();
  385. }
  386. }
  387. void goForward() override
  388. {
  389. if (webView != nullptr)
  390. {
  391. BOOL canGoForward = false;
  392. webView->get_CanGoForward (&canGoForward);
  393. if (canGoForward)
  394. webView->GoForward();
  395. }
  396. }
  397. void refresh() override
  398. {
  399. if (webView != nullptr)
  400. webView->Reload();
  401. }
  402. void setWebViewSize (int width, int height) override
  403. {
  404. setSize (width, height);
  405. }
  406. void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
  407. {
  408. if (auto* peer = owner.getTopLevelComponent()->getPeer())
  409. setControlBounds (peer->getAreaCoveredBy (owner));
  410. }
  411. void componentPeerChanged() override
  412. {
  413. componentMovedOrResized (true, true);
  414. }
  415. void componentVisibilityChanged() override
  416. {
  417. setControlVisible (owner.isShowing());
  418. componentPeerChanged();
  419. owner.visibilityChanged();
  420. }
  421. std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
  422. {
  423. return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
  424. }
  425. //==============================================================================
  426. struct WebViewHandle
  427. {
  428. using LibraryRef = std::unique_ptr<typename std::pointer_traits<HMODULE>::element_type, decltype (&::FreeLibrary)>;
  429. LibraryRef loaderHandle {nullptr, &::FreeLibrary};
  430. ComSmartPtr<ICoreWebView2Environment> environment;
  431. };
  432. static std::optional<WebViewHandle> createWebViewHandle (const WebBrowserComponent::Options::WinWebView2& options)
  433. {
  434. using CreateWebViewEnvironmentWithOptionsFunc = HRESULT (*) (PCWSTR, PCWSTR,
  435. ICoreWebView2EnvironmentOptions*,
  436. ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*);
  437. auto dllPath = options.getDLLLocation().getFullPathName();
  438. if (dllPath.isEmpty())
  439. dllPath = "WebView2Loader.dll";
  440. WebViewHandle result;
  441. result.loaderHandle = WebViewHandle::LibraryRef (LoadLibraryA (dllPath.toUTF8()), &::FreeLibrary);
  442. if (result.loaderHandle == nullptr)
  443. return {};
  444. auto* createWebViewEnvironmentWithOptions = (CreateWebViewEnvironmentWithOptionsFunc) GetProcAddress (result.loaderHandle.get(),
  445. "CreateCoreWebView2EnvironmentWithOptions");
  446. if (createWebViewEnvironmentWithOptions == nullptr)
  447. return {};
  448. auto webViewOptions = Microsoft::WRL::Make<CoreWebView2EnvironmentOptions>();
  449. const auto userDataFolder = options.getUserDataFolder().getFullPathName();
  450. auto hr = createWebViewEnvironmentWithOptions (nullptr,
  451. userDataFolder.isNotEmpty() ? userDataFolder.toWideCharPointer() : nullptr,
  452. webViewOptions.Get(),
  453. Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
  454. [&result] (HRESULT, ICoreWebView2Environment* env) -> HRESULT
  455. {
  456. result.environment = env;
  457. return S_OK;
  458. }).Get());
  459. if (! SUCCEEDED (hr))
  460. return {};
  461. return result;
  462. }
  463. private:
  464. //==============================================================================
  465. template <class ArgType>
  466. static String getUriStringFromArgs (ArgType* args)
  467. {
  468. if (args != nullptr)
  469. {
  470. LPWSTR uri;
  471. args->get_Uri (&uri);
  472. return uri;
  473. }
  474. return {};
  475. }
  476. //==============================================================================
  477. void addEventHandlers()
  478. {
  479. if (webView != nullptr)
  480. {
  481. webView->add_NavigationStarting (Callback<ICoreWebView2NavigationStartingEventHandler> (
  482. [this] (ICoreWebView2*, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
  483. {
  484. auto uriString = getUriStringFromArgs (args);
  485. if (uriString.isNotEmpty() && ! owner.pageAboutToLoad (uriString))
  486. args->put_Cancel (true);
  487. return S_OK;
  488. }).Get(), &navigationStartingToken);
  489. webView->add_NewWindowRequested (Callback<ICoreWebView2NewWindowRequestedEventHandler> (
  490. [this] (ICoreWebView2*, ICoreWebView2NewWindowRequestedEventArgs* args) -> HRESULT
  491. {
  492. auto uriString = getUriStringFromArgs (args);
  493. if (uriString.isNotEmpty())
  494. {
  495. owner.newWindowAttemptingToLoad (uriString);
  496. args->put_Handled (true);
  497. }
  498. return S_OK;
  499. }).Get(), &newWindowRequestedToken);
  500. webView->add_WindowCloseRequested (Callback<ICoreWebView2WindowCloseRequestedEventHandler> (
  501. [this] (ICoreWebView2*, IUnknown*) -> HRESULT
  502. {
  503. owner.windowCloseRequest();
  504. return S_OK;
  505. }).Get(), &windowCloseRequestedToken);
  506. webView->add_NavigationCompleted (Callback<ICoreWebView2NavigationCompletedEventHandler> (
  507. [this] (ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT
  508. {
  509. LPWSTR uri;
  510. sender->get_Source (&uri);
  511. String uriString (uri);
  512. if (uriString.isNotEmpty())
  513. {
  514. BOOL success = false;
  515. args->get_IsSuccess (&success);
  516. COREWEBVIEW2_WEB_ERROR_STATUS errorStatus;
  517. args->get_WebErrorStatus (&errorStatus);
  518. if (success
  519. || errorStatus == COREWEBVIEW2_WEB_ERROR_STATUS_OPERATION_CANCELED) // this error seems to happen erroneously so ignore
  520. {
  521. owner.pageFinishedLoading (uriString);
  522. }
  523. else
  524. {
  525. auto errorString = "Error code: " + String (errorStatus);
  526. if (owner.pageLoadHadNetworkError (errorString))
  527. owner.goToURL ("data:text/plain;charset=UTF-8," + errorString);
  528. }
  529. }
  530. return S_OK;
  531. }).Get(), &navigationCompletedToken);
  532. webView->AddWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
  533. webView->add_WebResourceRequested (Callback<ICoreWebView2WebResourceRequestedEventHandler> (
  534. [this] (ICoreWebView2*, ICoreWebView2WebResourceRequestedEventArgs* args) -> HRESULT
  535. {
  536. if (urlRequest.url.isEmpty())
  537. return S_OK;
  538. ComSmartPtr<ICoreWebView2WebResourceRequest> request;
  539. args->get_Request (request.resetAndGetPointerAddress());
  540. auto uriString = getUriStringFromArgs<ICoreWebView2WebResourceRequest> (request);
  541. if (uriString == urlRequest.url
  542. || (uriString.endsWith ("/") && uriString.upToLastOccurrenceOf ("/", false, false) == urlRequest.url))
  543. {
  544. String method ("GET");
  545. if (! urlRequest.postData.isEmpty())
  546. {
  547. method = "POST";
  548. ComSmartPtr<IStream> content (SHCreateMemStream ((BYTE*) urlRequest.postData.getData(),
  549. (UINT) urlRequest.postData.getSize()));
  550. request->put_Content (content);
  551. }
  552. if (! urlRequest.headers.isEmpty())
  553. {
  554. ComSmartPtr<ICoreWebView2HttpRequestHeaders> headers;
  555. request->get_Headers (headers.resetAndGetPointerAddress());
  556. for (auto& header : urlRequest.headers)
  557. {
  558. headers->SetHeader (header.upToFirstOccurrenceOf (":", false, false).trim().toWideCharPointer(),
  559. header.fromFirstOccurrenceOf (":", false, false).trim().toWideCharPointer());
  560. }
  561. }
  562. request->put_Method (method.toWideCharPointer());
  563. urlRequest = {};
  564. }
  565. return S_OK;
  566. }).Get(), &webResourceRequestedToken);
  567. }
  568. if (webViewController != nullptr)
  569. {
  570. webViewController->add_MoveFocusRequested (Callback<ICoreWebView2MoveFocusRequestedEventHandler> (
  571. [this] (ICoreWebView2Controller*, ICoreWebView2MoveFocusRequestedEventArgs* args) -> HRESULT
  572. {
  573. ScopedValueSetter scope { inMoveFocusRequested, true };
  574. auto* comp = [&]() -> Component*
  575. {
  576. auto* c = owner.getParentComponent();
  577. if (c == nullptr)
  578. return nullptr;
  579. const auto traverser = c->createFocusTraverser();
  580. if (COREWEBVIEW2_MOVE_FOCUS_REASON reason;
  581. args->get_Reason (&reason) == S_OK && reason == COREWEBVIEW2_MOVE_FOCUS_REASON_PREVIOUS)
  582. {
  583. // The previous Component to the embedded WebView2 Component is the
  584. // WebBrowserComponent. Here we want to skip that and jump to the
  585. // Component that comes before it.
  586. return traverser->getPreviousComponent (&owner);
  587. }
  588. // The Component that comes immediately after the WebBrowserComponent is the
  589. // embedded WebView2. We want to jump to the Component that comes after that.
  590. return traverser->getNextComponent (this);
  591. }();
  592. if (comp != nullptr)
  593. comp->getAccessibilityHandler()->grabFocus();
  594. else
  595. giveAwayKeyboardFocus();
  596. return S_OK;
  597. }).Get(), &moveFocusRequestedToken);
  598. }
  599. }
  600. void removeEventHandlers()
  601. {
  602. if (webView != nullptr)
  603. {
  604. if (navigationStartingToken.value != 0)
  605. webView->remove_NavigationStarting (navigationStartingToken);
  606. if (newWindowRequestedToken.value != 0)
  607. webView->remove_NewWindowRequested (newWindowRequestedToken);
  608. if (windowCloseRequestedToken.value != 0)
  609. webView->remove_WindowCloseRequested (windowCloseRequestedToken);
  610. if (navigationCompletedToken.value != 0)
  611. webView->remove_NavigationCompleted (navigationCompletedToken);
  612. if (webResourceRequestedToken.value != 0)
  613. {
  614. webView->RemoveWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
  615. webView->remove_WebResourceRequested (webResourceRequestedToken);
  616. }
  617. }
  618. if (webViewController != nullptr)
  619. {
  620. if (moveFocusRequestedToken.value != 0)
  621. webViewController->remove_MoveFocusRequested (moveFocusRequestedToken);
  622. }
  623. }
  624. void setWebViewPreferences()
  625. {
  626. ComSmartPtr<ICoreWebView2Controller2> controller2;
  627. webViewController->QueryInterface (controller2.resetAndGetPointerAddress());
  628. if (controller2 != nullptr)
  629. {
  630. const auto bgColour = preferences.getBackgroundColour();
  631. controller2->put_DefaultBackgroundColor ({ (BYTE) bgColour.getAlpha(),
  632. (BYTE) bgColour.getRed(),
  633. (BYTE) bgColour.getGreen(),
  634. (BYTE) bgColour.getBlue() });
  635. }
  636. ComSmartPtr<ICoreWebView2Settings> settings;
  637. webView->get_Settings (settings.resetAndGetPointerAddress());
  638. if (settings != nullptr)
  639. {
  640. settings->put_IsStatusBarEnabled (! preferences.getIsStatusBarDisabled());
  641. settings->put_IsBuiltInErrorPageEnabled (! preferences.getIsBuiltInErrorPageDisabled());
  642. if (userAgent.isNotEmpty())
  643. {
  644. ComSmartPtr<ICoreWebView2Settings2> settings2;
  645. settings.QueryInterface (settings2);
  646. if (settings2 != nullptr)
  647. settings2->put_UserAgent (userAgent.toWideCharPointer());
  648. }
  649. }
  650. }
  651. void createWebView()
  652. {
  653. if (auto* peer = getPeer())
  654. {
  655. // We enforce the serial creation of WebView2 instances so that our HWND association
  656. // logic can work. Multiple HWNDs can belong to the same browser process, so the only
  657. // way to identify which belongs to which WebView2 is to associate them with each other
  658. // in the order of creation.
  659. if (webView2ConstructionHelper.webView2BeingCreated != nullptr)
  660. {
  661. webView2ConstructionHelper.viewsWaitingForCreation.insert (this);
  662. return;
  663. }
  664. webView2ConstructionHelper.viewsWaitingForCreation.erase (this);
  665. webView2ConstructionHelper.webView2BeingCreated = this;
  666. WeakReference<WebView2> weakThis (this);
  667. webViewHandle.environment->CreateCoreWebView2Controller ((HWND) peer->getNativeHandle(),
  668. Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler> (
  669. [weakThis = WeakReference<WebView2> { this }] (HRESULT, ICoreWebView2Controller* controller) -> HRESULT
  670. {
  671. if (weakThis != nullptr)
  672. {
  673. webView2ConstructionHelper.webView2BeingCreated = nullptr;
  674. if (controller != nullptr)
  675. {
  676. weakThis->webViewController = controller;
  677. controller->get_CoreWebView2 (weakThis->webView.resetAndGetPointerAddress());
  678. if (weakThis->webView != nullptr)
  679. {
  680. if (UINT32 browserProcessId;
  681. weakThis->webView->get_BrowserProcessId (&browserProcessId) == S_OK)
  682. {
  683. auto* self = weakThis.get();
  684. auto* webView2WindowHandle = static_cast<HWND> (self->getWindowHandle());
  685. // There is no WebView2 API for getting the HWND hosting
  686. // the WebView2 content. So we iterate over all child
  687. // windows of the JUCE peer HWND, and try to figure out
  688. // which one belongs to a WebView2. What we are looking for
  689. // is a window that has a child window that belongs to the
  690. // browserProcessId.
  691. const auto directChildWindows = getDirectChildWindows (webView2WindowHandle);
  692. for (auto* childWindow : directChildWindows)
  693. {
  694. if (self->webView2ConstructionHelper.associatedWebViewNativeWindows.count (childWindow) == 0)
  695. {
  696. if (anyChildWindow (childWindow,
  697. [browserProcessId] (auto* childOfChild)
  698. {
  699. if (DWORD procId; GetWindowThreadProcessId (childOfChild, &procId) != 0)
  700. return (UINT32) procId == browserProcessId;
  701. return false;
  702. }))
  703. {
  704. webView2ConstructionHelper.associatedWebViewNativeWindows.insert (childWindow);
  705. AccessibilityHandler::setNativeChildForComponent (*self, childWindow);
  706. }
  707. }
  708. }
  709. }
  710. weakThis->addEventHandlers();
  711. weakThis->setWebViewPreferences();
  712. weakThis->componentMovedOrResized (true, true);
  713. if (weakThis->urlRequest.url.isNotEmpty())
  714. weakThis->webView->Navigate (weakThis->urlRequest.url.toWideCharPointer());
  715. }
  716. }
  717. if (! weakThis->webView2ConstructionHelper.viewsWaitingForCreation.empty())
  718. (*weakThis->webView2ConstructionHelper.viewsWaitingForCreation.begin())->triggerAsyncUpdate();
  719. }
  720. return S_OK;
  721. }).Get());
  722. }
  723. }
  724. void closeWebView()
  725. {
  726. if (auto* webViewNativeWindow = AccessibilityHandler::getNativeChildForComponent (*this))
  727. webView2ConstructionHelper.associatedWebViewNativeWindows.erase (webViewNativeWindow);
  728. AccessibilityHandler::setNativeChildForComponent (*this, nullptr);
  729. if (webViewController != nullptr)
  730. {
  731. webViewController->Close();
  732. webViewController = nullptr;
  733. webView = nullptr;
  734. }
  735. webViewHandle.environment = nullptr;
  736. }
  737. //==============================================================================
  738. void handleAsyncUpdate() override
  739. {
  740. createWebView();
  741. }
  742. //==============================================================================
  743. void setControlBounds (Rectangle<int> newBounds) const
  744. {
  745. if (webViewController != nullptr)
  746. {
  747. #if JUCE_WIN_PER_MONITOR_DPI_AWARE
  748. if (auto* peer = owner.getTopLevelComponent()->getPeer())
  749. newBounds = (newBounds.toDouble() * peer->getPlatformScaleFactor()).toNearestInt();
  750. #endif
  751. webViewController->put_Bounds ({ newBounds.getX(), newBounds.getY(),
  752. newBounds.getRight(), newBounds.getBottom() });
  753. }
  754. }
  755. void setControlVisible (bool shouldBeVisible) const
  756. {
  757. if (webViewController != nullptr)
  758. webViewController->put_IsVisible (shouldBeVisible);
  759. }
  760. //==============================================================================
  761. WebBrowserComponent& owner;
  762. WebBrowserComponent::Options::WinWebView2 preferences;
  763. String userAgent;
  764. WebViewHandle webViewHandle;
  765. ComSmartPtr<ICoreWebView2Controller> webViewController;
  766. ComSmartPtr<ICoreWebView2> webView;
  767. EventRegistrationToken navigationStartingToken { 0 },
  768. newWindowRequestedToken { 0 },
  769. windowCloseRequestedToken { 0 },
  770. navigationCompletedToken { 0 },
  771. webResourceRequestedToken { 0 },
  772. moveFocusRequestedToken { 0 };
  773. bool inMoveFocusRequested = false;
  774. struct URLRequest
  775. {
  776. String url;
  777. StringArray headers;
  778. MemoryBlock postData;
  779. };
  780. URLRequest urlRequest;
  781. struct WebView2ConstructionHelper
  782. {
  783. WebView2* webView2BeingCreated;
  784. std::set<WebView2*> viewsWaitingForCreation;
  785. std::set<void*> associatedWebViewNativeWindows;
  786. };
  787. inline static WebView2ConstructionHelper webView2ConstructionHelper;
  788. NativeScaleFactorNotifier scaleFactorNotifier { this,
  789. [this] (auto)
  790. {
  791. componentMovedOrResized (true, true);
  792. } };
  793. //==============================================================================
  794. JUCE_DECLARE_WEAK_REFERENCEABLE (WebView2)
  795. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebView2)
  796. };
  797. #endif
  798. //==============================================================================
  799. class WebBrowserComponent::Pimpl
  800. {
  801. public:
  802. Pimpl (WebBrowserComponent& owner,
  803. [[maybe_unused]] const Options& preferences,
  804. bool useWebView2,
  805. const String& userAgent)
  806. {
  807. if (useWebView2)
  808. {
  809. #if JUCE_USE_WIN_WEBVIEW2
  810. try
  811. {
  812. internal.reset (new WebView2 (owner, preferences));
  813. }
  814. catch (const std::runtime_error&) {}
  815. #endif
  816. }
  817. if (internal == nullptr)
  818. internal.reset (new Win32WebView (owner, userAgent));
  819. }
  820. InternalWebViewType& getInternalWebView()
  821. {
  822. return *internal;
  823. }
  824. private:
  825. std::unique_ptr<InternalWebViewType> internal;
  826. };
  827. //==============================================================================
  828. WebBrowserComponent::WebBrowserComponent (const Options& options)
  829. : browser (new Pimpl (*this, options,
  830. options.getBackend() == Options::Backend::webview2, options.getUserAgent())),
  831. unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden())
  832. {
  833. setOpaque (true);
  834. }
  835. WebBrowserComponent::~WebBrowserComponent()
  836. {
  837. }
  838. //==============================================================================
  839. void WebBrowserComponent::goToURL (const String& url,
  840. const StringArray* headers,
  841. const MemoryBlock* postData)
  842. {
  843. lastURL = url;
  844. if (headers != nullptr)
  845. lastHeaders = *headers;
  846. else
  847. lastHeaders.clear();
  848. if (postData != nullptr)
  849. lastPostData = *postData;
  850. else
  851. lastPostData.reset();
  852. blankPageShown = false;
  853. if (! browser->getInternalWebView().hasBrowserBeenCreated())
  854. checkWindowAssociation();
  855. browser->getInternalWebView().goToURL (url, headers, postData);
  856. }
  857. void WebBrowserComponent::stop()
  858. {
  859. browser->getInternalWebView().stop();
  860. }
  861. void WebBrowserComponent::goBack()
  862. {
  863. lastURL.clear();
  864. blankPageShown = false;
  865. browser->getInternalWebView().goBack();
  866. }
  867. void WebBrowserComponent::goForward()
  868. {
  869. lastURL.clear();
  870. browser->getInternalWebView().goForward();
  871. }
  872. void WebBrowserComponent::refresh()
  873. {
  874. browser->getInternalWebView().refresh();
  875. }
  876. //==============================================================================
  877. void WebBrowserComponent::paint (Graphics& g)
  878. {
  879. if (! browser->getInternalWebView().hasBrowserBeenCreated())
  880. {
  881. g.fillAll (Colours::white);
  882. checkWindowAssociation();
  883. }
  884. }
  885. void WebBrowserComponent::checkWindowAssociation()
  886. {
  887. if (isShowing())
  888. {
  889. if (! browser->getInternalWebView().hasBrowserBeenCreated() && getPeer() != nullptr)
  890. {
  891. browser->getInternalWebView().createBrowser();
  892. reloadLastURL();
  893. }
  894. else
  895. {
  896. if (blankPageShown)
  897. goBack();
  898. }
  899. }
  900. else
  901. {
  902. if (browser != nullptr && unloadPageWhenHidden && ! blankPageShown)
  903. {
  904. // when the component becomes invisible, some stuff like flash
  905. // carries on playing audio, so we need to force it onto a blank
  906. // page to avoid this..
  907. blankPageShown = true;
  908. browser->getInternalWebView().goToURL ("about:blank", nullptr, nullptr);
  909. }
  910. }
  911. }
  912. void WebBrowserComponent::reloadLastURL()
  913. {
  914. if (lastURL.isNotEmpty())
  915. {
  916. goToURL (lastURL, &lastHeaders, &lastPostData);
  917. lastURL.clear();
  918. }
  919. }
  920. void WebBrowserComponent::parentHierarchyChanged()
  921. {
  922. checkWindowAssociation();
  923. }
  924. void WebBrowserComponent::resized()
  925. {
  926. browser->getInternalWebView().setWebViewSize (getWidth(), getHeight());
  927. }
  928. void WebBrowserComponent::visibilityChanged()
  929. {
  930. checkWindowAssociation();
  931. }
  932. void WebBrowserComponent::focusGainedWithDirection (FocusChangeType type, FocusChangeDirection dir)
  933. {
  934. ignoreUnused (type, dir);
  935. #if JUCE_USE_WIN_WEBVIEW2
  936. if (auto* webView2 = dynamic_cast<WebView2*> (&browser->getInternalWebView()))
  937. webView2->focusGainedWithDirection (type, dir);
  938. else
  939. #endif
  940. browser->getInternalWebView().focusGained();
  941. }
  942. void WebBrowserComponent::clearCookies()
  943. {
  944. HeapBlock<::INTERNET_CACHE_ENTRY_INFOA> entry;
  945. ::DWORD entrySize = sizeof (::INTERNET_CACHE_ENTRY_INFOA);
  946. ::HANDLE urlCacheHandle = ::FindFirstUrlCacheEntryA ("cookie:", entry.getData(), &entrySize);
  947. if (urlCacheHandle == nullptr && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
  948. {
  949. entry.realloc (1, entrySize);
  950. urlCacheHandle = ::FindFirstUrlCacheEntryA ("cookie:", entry.getData(), &entrySize);
  951. }
  952. if (urlCacheHandle != nullptr)
  953. {
  954. for (;;)
  955. {
  956. ::DeleteUrlCacheEntryA (entry.getData()->lpszSourceUrlName);
  957. if (::FindNextUrlCacheEntryA (urlCacheHandle, entry.getData(), &entrySize) == 0)
  958. {
  959. if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
  960. {
  961. entry.realloc (1, entrySize);
  962. if (::FindNextUrlCacheEntryA (urlCacheHandle, entry.getData(), &entrySize) != 0)
  963. continue;
  964. }
  965. break;
  966. }
  967. }
  968. FindCloseUrlCache (urlCacheHandle);
  969. }
  970. }
  971. //==============================================================================
  972. bool WebBrowserComponent::areOptionsSupported (const Options& options)
  973. {
  974. if (options.getBackend() == Options::Backend::defaultBackend || options.getBackend() == Options::Backend::ie)
  975. return true;
  976. #if JUCE_USE_WIN_WEBVIEW2
  977. if (options.getBackend() != Options::Backend::webview2)
  978. return false;
  979. if (auto webView = WebView2::createWebViewHandle (options.getWinWebView2BackendOptions()))
  980. return true;
  981. #endif
  982. return false;
  983. }
  984. } // namespace juce