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.

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