From 87fcf2f35378560b4ceb3602c8cda708167478c5 Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 12 Jun 2020 14:21:01 +0100 Subject: [PATCH] Windows: Added support for Chromium-based WebView2 browser in WebBrowserComponent and removed WinRT webview --- docs/CMake API.txt | 11 +- .../ProjectSaving/jucer_ProjectExport_MSVC.h | 108 ++-- modules/juce_events/juce_events.cpp | 2 +- modules/juce_gui_extra/juce_gui_extra.cpp | 15 +- modules/juce_gui_extra/juce_gui_extra.h | 23 +- .../misc/juce_WebBrowserComponent.h | 7 +- .../native/juce_win32_WebBrowserComponent.cpp | 520 +++++++----------- 7 files changed, 308 insertions(+), 378 deletions(-) diff --git a/docs/CMake API.txt b/docs/CMake API.txt index b9bba735c2..fc5ef0e388 100644 --- a/docs/CMake API.txt +++ b/docs/CMake API.txt @@ -6,6 +6,7 @@ - Plugin projects require CMake 3.15 or higher. - All iOS targets require CMake 3.14 or higher (3.15 or higher for plugins targeting iOS). - Android targets are not currently supported. +- WebView2 on Windows via JUCE_USE_WIN_WEBVIEW2 flag in juce_gui_extra is not currently supported. Most system package managers have packages for CMake, but we recommend using the most recent release from https://cmake.org/download. You should always use a CMake that's newer than your build @@ -15,11 +16,11 @@ In addition to CMake you'll need a build toolchain for your platform, such as Xc ## Getting Started -In this directory, you'll find example projects for a GUI app, a console app, and an audio plugin. -You can simply copy one of these subdirectories out of the JUCE repo, add JUCE as a submodule, and -uncomment the call to `add_subdirectory` where indicated in the CMakeLists.txt. Alternatively, if -you've installed JUCE using a package manager or the CMake install target, you can uncomment the -call to `find_package`. +In the JUCE/examples/CMake directory, you'll find example projects for a GUI app, a console app, +and an audio plugin. You can simply copy one of these subdirectories out of the JUCE repo, add JUCE +as a submodule, and uncomment the call to `add_subdirectory` where indicated in the CMakeLists.txt. +Alternatively, if you've installed JUCE using a package manager or the CMake install target, you can +uncomment the call to `find_package`. Once your project is set up, you can generate a build tree for it in the normal way. To get started, you might invoke CMake like this, from the new directory you created. diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h index d46794be10..d773592130 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h @@ -53,27 +53,10 @@ public: "Specifies the version of the platform toolset that will be used when building this project."); } - void addIPPLibraryProperty (PropertyListBuilder& props) - { - props.add (new ChoicePropertyComponent (IPPLibraryValue, "Use IPP Library", - { "No", "Yes (Default Linking)", "Multi-Threaded Static Library", "Single-Threaded Static Library", "Multi-Threaded DLL", "Single-Threaded DLL" }, - { var(), "true", "Parallel_Static", "Sequential", "Parallel_Dynamic", "Sequential_Dynamic" }), - "Enable this to use Intel's Integrated Performance Primitives library."); - } - - void addWindowsTargetPlatformProperties (PropertyListBuilder& props) - { - auto isWindows10SDK = getVisualStudioVersion() > 14; - - props.add (new TextPropertyComponent (targetPlatformVersion, "Windows Target Platform", 20, false), - String ("Specifies the version of the Windows SDK that will be used when building this project. ") - + (isWindows10SDK ? "Leave this field empty to use the latest Windows 10 SDK installed on the build machine." - : "The default value for this exporter is " + getDefaultWindowsTargetPlatformVersion())); - } - void create (const OwnedArray&) const override { createResourcesAndIcon(); + createPackagesConfigFile(); for (int i = 0; i < targets.size(); ++i) if (auto* target = targets[i]) @@ -720,6 +703,12 @@ public: e->setAttribute ("Include", prependDot (getOwner().iconFile.getFileName())); } + if (getOwner().packagesConfigFile.existsAsFile()) + { + auto* e = otherFilesGroup->createNewChildElement ("None"); + e->setAttribute ("Include", getOwner().packagesConfigFile.getFileName()); + } + if (otherFilesGroup->getFirstChildElement() != nullptr) projectXml.addChildElement (otherFilesGroup.release()); @@ -736,8 +725,18 @@ public: } { - auto* e = projectXml.createNewChildElement ("ImportGroup"); - e->setAttribute ("Label", "ExtensionTargets"); + auto* importGroup = projectXml.createNewChildElement ("ImportGroup"); + importGroup->setAttribute ("Label", "ExtensionTargets"); + + if (owner.shouldAddWebView2Package()) + { + auto packageTargetsPath = "packages\\" + getWebView2PackageName() + "." + getWebView2PackageVersion() + + "\\build\\native\\" + getWebView2PackageName() + ".targets"; + + auto* e = importGroup->createNewChildElement ("Import"); + e->setAttribute ("Project", packageTargetsPath); + e->setAttribute ("Condition", "Exists('" + packageTargetsPath + "')"); + } } } @@ -892,6 +891,12 @@ public: e->createNewChildElement ("Filter")->addTextElement (ProjectSaver::getJuceCodeGroupName()); } + if (getOwner().packagesConfigFile.existsAsFile()) + { + auto* e = otherFilesGroup->createNewChildElement ("None"); + e->setAttribute ("Include", getOwner().packagesConfigFile.getFileName()); + } + if (otherFilesGroup->getFirstChildElement() != nullptr) filterXml.addChildElement (otherFilesGroup.release()); @@ -1375,6 +1380,20 @@ public: { props.add (new TextPropertyComponent (manifestFileValue, "Manifest file", 8192, false), "Path to a manifest input file which should be linked into your binary (path is relative to jucer file)."); + + props.add (new ChoicePropertyComponent (IPPLibraryValue, "Use IPP Library", + { "No", "Yes (Default Linking)", "Multi-Threaded Static Library", "Single-Threaded Static Library", "Multi-Threaded DLL", "Single-Threaded DLL" }, + { var(), "true", "Parallel_Static", "Sequential", "Parallel_Dynamic", "Sequential_Dynamic" }), + "Enable this to use Intel's Integrated Performance Primitives library."); + + { + auto isWindows10SDK = getVisualStudioVersion() > 14; + + props.add (new TextPropertyComponent (targetPlatformVersion, "Windows Target Platform", 20, false), + String ("Specifies the version of the Windows SDK that will be used when building this project. ") + + (isWindows10SDK ? "Leave this field empty to use the latest Windows 10 SDK installed on the build machine." + : "The default value for this exporter is " + getDefaultWindowsTargetPlatformVersion())); + } } enum OptimisationLevel @@ -1447,7 +1466,7 @@ private: protected: //============================================================================== - mutable File rcFile, iconFile; + mutable File rcFile, iconFile, packagesConfigFile; OwnedArray targets; ValueWithDefault IPPLibraryValue, platformToolsetValue, targetPlatformVersion, manifestFileValue; @@ -1586,6 +1605,35 @@ protected: } } + bool shouldAddWebView2Package() const + { + return project.getEnabledModules().isModuleEnabled ("juce_gui_extra") + && project.isConfigFlagEnabled ("JUCE_USE_WIN_WEBVIEW2", false); + } + + static String getWebView2PackageName() { return "Microsoft.Web.WebView2"; } + static String getWebView2PackageVersion() { return "0.9.488"; } + + void createPackagesConfigFile() const + { + if (shouldAddWebView2Package()) + { + packagesConfigFile = getTargetFolder().getChildFile ("packages.config"); + + build_tools::writeStreamToFile (packagesConfigFile, [] (MemoryOutputStream& mo) + { + mo.setNewLineString ("\r\n"); + + mo << "" << newLine + << "" << newLine + << "\t" << "" << newLine + << "" << newLine; + }); + } + } + static String prependDot (const String& filename) { return build_tools::isAbsolutePath (filename) ? filename @@ -1643,15 +1691,11 @@ public: void createExporterProperties (PropertyListBuilder& props) override { - MSVCProjectExporterBase::createExporterProperties (props); - static const char* toolsetNames[] = { "v140", "v140_xp", "CTP_Nov2013" }; const var toolsets[] = { "v140", "v140_xp", "CTP_Nov2013" }; addToolsetProperty (props, toolsetNames, toolsets, numElementsInArray (toolsets)); - addIPPLibraryProperty (props); - - addWindowsTargetPlatformProperties (props); + MSVCProjectExporterBase::createExporterProperties (props); } JUCE_DECLARE_NON_COPYABLE (MSVCProjectExporterVC2015) @@ -1690,15 +1734,11 @@ public: void createExporterProperties (PropertyListBuilder& props) override { - MSVCProjectExporterBase::createExporterProperties (props); - static const char* toolsetNames[] = { "v140", "v140_xp", "v141", "v141_xp" }; const var toolsets[] = { "v140", "v140_xp", "v141", "v141_xp" }; addToolsetProperty (props, toolsetNames, toolsets, numElementsInArray (toolsets)); - addIPPLibraryProperty (props); - - addWindowsTargetPlatformProperties (props); + MSVCProjectExporterBase::createExporterProperties (props); } JUCE_DECLARE_NON_COPYABLE (MSVCProjectExporterVC2017) @@ -1737,15 +1777,11 @@ public: void createExporterProperties (PropertyListBuilder& props) override { - MSVCProjectExporterBase::createExporterProperties (props); - static const char* toolsetNames[] = { "v140", "v140_xp", "v141", "v141_xp", "v142" }; const var toolsets[] = { "v140", "v140_xp", "v141", "v141_xp", "v142" }; addToolsetProperty (props, toolsetNames, toolsets, numElementsInArray (toolsets)); - addIPPLibraryProperty (props); - - addWindowsTargetPlatformProperties (props); + MSVCProjectExporterBase::createExporterProperties (props); } JUCE_DECLARE_NON_COPYABLE (MSVCProjectExporterVC2019) diff --git a/modules/juce_events/juce_events.cpp b/modules/juce_events/juce_events.cpp index 943e7073c9..884db8d743 100644 --- a/modules/juce_events/juce_events.cpp +++ b/modules/juce_events/juce_events.cpp @@ -35,7 +35,7 @@ #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 #define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 -#if JUCE_USE_WINRT_MIDI || JUCE_USE_WINRT_WEBVIEW +#if JUCE_USE_WINRT_MIDI || JUCE_USE_WIN_WEBVIEW2 #define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1 #endif diff --git a/modules/juce_gui_extra/juce_gui_extra.cpp b/modules/juce_gui_extra/juce_gui_extra.cpp index ea7066cc77..b50f8dbad5 100644 --- a/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/modules/juce_gui_extra/juce_gui_extra.cpp @@ -33,7 +33,7 @@ #define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1 #define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 -#if JUCE_USE_WINRT_WEBVIEW +#if JUCE_USE_WIN_WEBVIEW2 #define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1 #endif @@ -85,9 +85,8 @@ #if JUCE_WEB_BROWSER #include #include - #if JUCE_USE_WINRT_WEBVIEW - #include - #include + + #if JUCE_USE_WIN_WEBVIEW2 #include #include @@ -96,7 +95,15 @@ #include #include #pragma warning (pop) + + #include "WebView2.h" + + #pragma warning (push) + #pragma warning (disable: 4458) + #include "WebView2EnvironmentOptions.h" + #pragma warning (pop) #endif + #endif //============================================================================== diff --git a/modules/juce_gui_extra/juce_gui_extra.h b/modules/juce_gui_extra/juce_gui_extra.h index 6d37f5a33f..1423f0f935 100644 --- a/modules/juce_gui_extra/juce_gui_extra.h +++ b/modules/juce_gui_extra/juce_gui_extra.h @@ -57,19 +57,20 @@ #define JUCE_WEB_BROWSER 1 #endif -/** Config: JUCE_USE_WINRT_WEBVIEW - Enables the use of the EdgeHTML browser engine on Windows. This will use - the Windows Runtime API on Windows 10 version 1809 (October 2018 Update) - and later. If you enable this flag then older versions of Windows will - automatically fall back to using the regualar Win32 web view. +/** Config: JUCE_USE_WIN_WEBVIEW2 + Enables the use of the Microsoft Edge (Chromium) WebView2 browser on Windows, + currently in developer preview. This requires Microsoft Edge (minimum version + 82.0.488.0) to be installed on the user's machine at runtime. - You will need version 10.0.14393.0 of the Windows Standalone SDK to compile - and you may need to add the path to the WinRT headers. The path to the - headers will be something similar to - "C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt". + If using the Projucer, the Microsoft.Web.WebView2 package will be added to the + project solution if this flag is enabled. If you are building using CMake you + will need to manually add the package via the Visual Studio package manager. + + If the required components are not available at runtime it will fall back to the + IE-based Win32 web view. */ -#ifndef JUCE_USE_WINRT_WEBVIEW - #define JUCE_USE_WINRT_WEBVIEW 0 +#ifndef JUCE_USE_WIN_WEBVIEW2 + #define JUCE_USE_WIN_WEBVIEW2 0 #endif /** Config: JUCE_ENABLE_LIVE_CONSTANT_EDITOR diff --git a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h index 47a56a5744..cf96a2894d 100644 --- a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h +++ b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h @@ -26,9 +26,10 @@ namespace juce A component that displays an embedded web browser. The browser itself will be platform-dependent. On Mac and iOS it will be - WebKit, if you have enabled JUCE_USE_WINRT_WEBVIEW on Windows 10 it will be - EdgeHTML otherwise IE, on Android it will be Chrome, and on Linux it will be - WebKit. + WebKit, on Android it will be Chrome, and on Linux it will be WebKit. On + Windows, if the JUCE_USE_WIN_WEBVIEW2 flag is enabled, it will either be + Microsoft Edge (Chromium) or IE depending on availability of the Edge + runtime. @tags{GUI} */ diff --git a/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp b/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp index e5b926cd74..003855c58c 100644 --- a/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp +++ b/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp @@ -310,114 +310,97 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EventHandler) }; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32WebView) }; -#if JUCE_USE_WINRT_WEBVIEW - -extern RTL_OSVERSIONINFOW getWindowsVersionInfo(); +#if JUCE_USE_WIN_WEBVIEW2 using namespace Microsoft::WRL; -using namespace ABI::Windows::Foundation; -using namespace ABI::Windows::Storage::Streams; -using namespace ABI::Windows::Web; -using namespace ABI::Windows::Web::UI; -using namespace ABI::Windows::Web::UI::Interop; -using namespace ABI::Windows::Web::Http; -using namespace ABI::Windows::Web::Http::Headers; - -//============================================================================== -class WinRTWebView : public InternalWebViewType, - public Component, - public ComponentMovementWatcher +class WebView2 : public InternalWebViewType, + public Component, + public ComponentMovementWatcher { public: - WinRTWebView (WebBrowserComponent& o) - : ComponentMovementWatcher (&o), - owner (o) + WebView2 (WebBrowserComponent& o) + : ComponentMovementWatcher (&o), + owner (o) { if (! WinRTWrapper::getInstance()->isInitialised()) throw std::runtime_error ("Failed to initialise the WinRT wrapper"); - if (! createWebViewProcess()) - throw std::runtime_error ("Failed to create the WebViewControlProcess"); + if (! createWebViewEnvironment()) + throw std::runtime_error ("Failed to create the CoreWebView2Environemnt"); owner.addAndMakeVisible (this); } - ~WinRTWebView() override + ~WebView2() override { - if (webViewControl != nullptr) - webViewControl->Stop(); - removeEventHandlers(); - - webViewProcess->Terminate(); + closeWebView(); } void createBrowser() override { - if (webViewControl == nullptr) - createWebViewControl(); + if (webView == nullptr) + { + jassert (webViewEnvironment != nullptr); + createWebView(); + } } bool hasBrowserBeenCreated() override { - return webViewControl != nullptr || isCreating; + return webView != nullptr || isCreating; } void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override { - if (webViewControl != nullptr) - { - if ((headers != nullptr && ! headers->isEmpty()) - || (postData != nullptr && postData->getSize() > 0)) - { - auto requestMessage = createHttpRequestMessage (url, headers, postData); - webViewControl->NavigateWithHttpRequestMessage (requestMessage.get()); - } - else - { - auto uri = createURI (url); - webViewControl->Navigate (uri.get()); - } - } + urlRequest = { url, + headers != nullptr ? *headers : StringArray(), + postData != nullptr && postData->getSize() > 0 ? *postData : MemoryBlock() }; + + if (webView != nullptr) + webView->Navigate (urlRequest.url.toWideCharPointer()); } void stop() override { - if (webViewControl != nullptr) - webViewControl->Stop(); + if (webView != nullptr) + webView->Stop(); } void goBack() override { - if (webViewControl != nullptr) + if (webView != nullptr) { - boolean canGoBack = false; - webViewControl->get_CanGoBack (&canGoBack); + BOOL canGoBack = false; + webView->get_CanGoBack (&canGoBack); if (canGoBack) - webViewControl->GoBack(); + webView->GoBack(); } } void goForward() override { - if (webViewControl != nullptr) + if (webView != nullptr) { - boolean canGoForward = false; - webViewControl->get_CanGoForward (&canGoForward); + BOOL canGoForward = false; + webView->get_CanGoForward (&canGoForward); if (canGoForward) - webViewControl->GoForward(); + webView->GoForward(); } } void refresh() override { - if (webViewControl != nullptr) - webViewControl->Refresh(); + if (webView != nullptr) + webView->Reload(); } void setWebViewSize (int width, int height) override @@ -446,117 +429,40 @@ public: private: //============================================================================== - template - static HRESULT waitForCompletion (IAsyncOperation* op, ResultsType* results) + template + static String getUriStringFromArgs (ArgType* args) { - using OperationType = IAsyncOperation; - using DelegateType = IAsyncOperationCompletedHandler; - - struct EventDelegate : public RuntimeClass, - DelegateType, - FtmBase> + if (args != nullptr) { - EventDelegate() = default; + LPWSTR uri; + args->get_Uri (&uri); - ~EventDelegate() - { - CloseHandle (eventCompleted); - } - - HRESULT RuntimeClassInitialize() - { - eventCompleted = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS); - return eventCompleted == nullptr ? HRESULT_FROM_WIN32 (GetLastError()) : S_OK; - } - - HRESULT Invoke (OperationType*, AsyncStatus newStatus) - { - status = newStatus; - SetEvent (eventCompleted); - - return S_OK; - } - - AsyncStatus status = AsyncStatus::Started; - HANDLE eventCompleted = nullptr; - }; - - WinRTWrapper::ComPtr operation = op; - WinRTWrapper::ComPtr eventCallback; - - auto hr = MakeAndInitialize (eventCallback.resetAndGetPointerAddress()); - - if (SUCCEEDED (hr)) - { - hr = operation->put_Completed (eventCallback.get()); - - if (SUCCEEDED (hr)) - { - HANDLE waitForEvents[1] { eventCallback->eventCompleted }; - auto handleCount = (ULONG) ARRAYSIZE (waitForEvents); - DWORD handleIndex = 0; - - hr = CoWaitForMultipleHandles (COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE, - INFINITE, handleCount, waitForEvents, &handleIndex); - - if (SUCCEEDED (hr)) - { - if (eventCallback->status == AsyncStatus::Completed) - { - hr = operation->GetResults (results); - } - else - { - WinRTWrapper::ComPtr asyncInfo; - - if (SUCCEEDED (operation->QueryInterface (asyncInfo.resetAndGetPointerAddress()))) - asyncInfo->get_ErrorCode (&hr); - } - } - } - } - - return hr; - } - - //============================================================================== - template - String getURIStringFromArgs (ArgsType& args) - { - WinRTWrapper::ComPtr uri; - args.get_Uri (uri.resetAndGetPointerAddress()); - - if (uri != nullptr) - { - HSTRING uriString; - uri->get_AbsoluteUri (&uriString); - - return WinRTWrapper::getInstance()->hStringToString (uriString); + return uri; } return {}; } + //============================================================================== void addEventHandlers() { - if (webViewControl != nullptr) + if (webView != nullptr) { - webViewControl->add_NavigationStarting (Callback> ( - [this] (IWebViewControl*, IWebViewControlNavigationStartingEventArgs* args) + webView->add_NavigationStarting (Callback ( + [this] (ICoreWebView2*, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT { - auto uriString = getURIStringFromArgs (*args); + auto uriString = getUriStringFromArgs (args); - if (uriString.isNotEmpty()) - args->put_Cancel (! owner.pageAboutToLoad (uriString)); + if (uriString.isNotEmpty() && ! owner.pageAboutToLoad (uriString)) + args->put_Cancel (true); return S_OK; - } - ).Get(), &navigationStartingToken); + }).Get(), &navigationStartingToken); - webViewControl->add_NewWindowRequested (Callback> ( - [this] (IWebViewControl*, IWebViewControlNewWindowRequestedEventArgs* args) + webView->add_NewWindowRequested (Callback ( + [this] (ICoreWebView2*, ICoreWebView2NewWindowRequestedEventArgs* args) -> HRESULT { - auto uriString = getURIStringFromArgs (*args); + auto uriString = getUriStringFromArgs (args); if (uriString.isNotEmpty()) { @@ -565,237 +471,220 @@ private: } return S_OK; - } - ).Get(), &newWindowRequestedToken); + }).Get(), &newWindowRequestedToken); - webViewControl->add_NavigationCompleted (Callback> ( - [this] (IWebViewControl*, IWebViewControlNavigationCompletedEventArgs* args) + webView->add_WindowCloseRequested (Callback ( + [this] (ICoreWebView2*, IUnknown*) -> HRESULT { - auto uriString = getURIStringFromArgs (*args); + owner.windowCloseRequest(); + return S_OK; + }).Get(), &windowCloseRequestedToken); + + webView->add_NavigationCompleted (Callback ( + [this] (ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT + { + LPWSTR uri; + sender->get_Source (&uri); + + String uriString (uri); if (uriString.isNotEmpty()) { - boolean success; + BOOL success = false; args->get_IsSuccess (&success); - if (success) + COREWEBVIEW2_WEB_ERROR_STATUS errorStatus; + args->get_WebErrorStatus (&errorStatus); + + if (success + || errorStatus == COREWEBVIEW2_WEB_ERROR_STATUS_OPERATION_CANCELED) // this error seems to happen erroneously so ignore { owner.pageFinishedLoading (uriString); } else { - WebErrorStatus status; - args->get_WebErrorStatus (&status); + auto errorString = "Error code: " + String (errorStatus); - owner.pageLoadHadNetworkError ("Error code: " + String (status)); + if (owner.pageLoadHadNetworkError (errorString)) + owner.goToURL ("data:text/plain;charset=UTF-8," + errorString); } } return S_OK; - } - ).Get(), &navigationCompletedToken); + }).Get(), &navigationCompletedToken); + + webView->AddWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT); + + webView->add_WebResourceRequested (Callback ( + [this] (ICoreWebView2*, ICoreWebView2WebResourceRequestedEventArgs* args) -> HRESULT + { + if (urlRequest.url.isEmpty()) + return S_OK; + + WinRTWrapper::ComPtr request; + args->get_Request (request.resetAndGetPointerAddress()); + + auto uriString = getUriStringFromArgs (request.get()); + + if (uriString == urlRequest.url + || (uriString.endsWith ("/") && uriString.upToLastOccurrenceOf ("/", false, false) == urlRequest.url)) + { + String method ("GET"); + + if (urlRequest.postData.getSize() > 0) + { + method = "POST"; + + WinRTWrapper::ComPtr content (SHCreateMemStream ((BYTE*) urlRequest.postData.getData(), + (UINT) urlRequest.postData.getSize())); + request->put_Content (content.get()); + } + + if (! urlRequest.headers.isEmpty()) + { + WinRTWrapper::ComPtr headers; + request->get_Headers (headers.resetAndGetPointerAddress()); + + for (auto& header : urlRequest.headers) + { + headers->SetHeader (header.upToFirstOccurrenceOf (":", false, false).trim().toWideCharPointer(), + header.fromFirstOccurrenceOf (":", false, false).trim().toWideCharPointer()); + } + } + + request->put_Method (method.toWideCharPointer()); + + urlRequest = {}; + } + + return S_OK; + }).Get(), &webResourceRequestedToken); } } void removeEventHandlers() { - if (webViewControl != nullptr) + if (webView != nullptr) { if (navigationStartingToken.value != 0) - webViewControl->remove_NavigationStarting (navigationStartingToken); + webView->remove_NavigationStarting (navigationStartingToken); if (newWindowRequestedToken.value != 0) - webViewControl->remove_NewWindowRequested (newWindowRequestedToken); + webView->remove_NewWindowRequested (newWindowRequestedToken); + + if (windowCloseRequestedToken.value != 0) + webView->remove_WindowCloseRequested (windowCloseRequestedToken); if (navigationCompletedToken.value != 0) - webViewControl->remove_NavigationCompleted (navigationCompletedToken); + webView->remove_NavigationCompleted (navigationCompletedToken); + + if (webResourceRequestedToken.value != 0) + { + webView->RemoveWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT); + webView->remove_WebResourceRequested (webResourceRequestedToken); + } } } - bool createWebViewProcess() + bool createWebViewEnvironment() { - auto webViewControlProcessFactory - = WinRTWrapper::getInstance()->getWRLFactory (RuntimeClass_Windows_Web_UI_Interop_WebViewControlProcess); + auto options = Microsoft::WRL::Make(); - if (webViewControlProcessFactory == nullptr) - { - jassertfalse; - return false; - } + auto hr = CreateCoreWebView2EnvironmentWithOptions (nullptr, nullptr, options.Get(), + Callback( + [this] (HRESULT, ICoreWebView2Environment* env) -> HRESULT + { + webViewEnvironment = env; + return S_OK; + }).Get()); - auto webViewProcessOptions - = WinRTWrapper::getInstance()->activateInstance (RuntimeClass_Windows_Web_UI_Interop_WebViewControlProcessOptions, - __uuidof (IWebViewControlProcessOptions)); - - webViewProcessOptions->put_PrivateNetworkClientServerCapability (WebViewControlProcessCapabilityState_Enabled); - webViewControlProcessFactory->CreateWithOptions (webViewProcessOptions.get(), webViewProcess.resetAndGetPointerAddress()); - - return webViewProcess != nullptr; + return SUCCEEDED (hr); } - void createWebViewControl() + void createWebView() { + isCreating = true; + if (auto* peer = getPeer()) { - ScopedValueSetter svs (isCreating, true); + webViewEnvironment->CreateCoreWebView2Controller ((HWND) peer->getNativeHandle(), + Callback ( + [this] (HRESULT, ICoreWebView2Controller* controller) -> HRESULT + { + webViewController = controller; + controller->get_CoreWebView2 (webView.resetAndGetPointerAddress()); - WinRTWrapper::ComPtr> createWebViewAsyncOperation; + isCreating = false; - webViewProcess->CreateWebViewControlAsync ((INT64) peer->getNativeHandle(), {}, - createWebViewAsyncOperation.resetAndGetPointerAddress()); + addEventHandlers(); + componentMovedOrResized (true, true); - waitForCompletion (createWebViewAsyncOperation.get(), webViewControl.resetAndGetPointerAddress()); + if (webView != nullptr && urlRequest.url.isNotEmpty()) + webView->Navigate (urlRequest.url.toWideCharPointer()); - addEventHandlers(); - componentMovedOrResized (true, true); + return S_OK; + }).Get()); } } - //============================================================================== - WinRTWrapper::ComPtr createURI (const String& url) + void closeWebView() { - auto uriRuntimeFactory - = WinRTWrapper::getInstance()->getWRLFactory (RuntimeClass_Windows_Foundation_Uri); - - if (uriRuntimeFactory == nullptr) + if (webViewController.get() != nullptr) { - jassertfalse; - return {}; + webViewController->Close(); + webViewController = nullptr; + webView = nullptr; } - WinRTWrapper::ScopedHString hstr (url); - WinRTWrapper::ComPtr uriRuntimeClass; - uriRuntimeFactory->CreateUri (hstr.get(), uriRuntimeClass.resetAndGetPointerAddress()); - - return uriRuntimeClass; - } - - WinRTWrapper::ComPtr getPOSTContent (const MemoryBlock& postData) - { - auto factory = WinRTWrapper::getInstance()->getWRLFactory (RuntimeClass_Windows_Web_Http_HttpStringContent); - - if (factory == nullptr) - { - jassertfalse; - return {}; - } - - WinRTWrapper::ScopedHString hStr (postData.toString()); - - WinRTWrapper::ComPtr content; - factory->CreateFromString (hStr.get(), content.resetAndGetPointerAddress()); - - return content; - } - - WinRTWrapper::ComPtr getMethod (bool isPOST) - { - auto methodFactory = WinRTWrapper::getInstance()->getWRLFactory (RuntimeClass_Windows_Web_Http_HttpMethod); - - if (methodFactory == nullptr) - { - jassertfalse; - return {}; - } - - WinRTWrapper::ComPtr method; - - if (isPOST) - methodFactory->get_Post (method.resetAndGetPointerAddress()); - else - methodFactory->get_Get (method.resetAndGetPointerAddress()); - - return method; - } - - void addHttpHeaders (WinRTWrapper::ComPtr& requestMessage, const StringArray& headers) - { - WinRTWrapper::ComPtr headerCollection; - requestMessage->get_Headers (headerCollection.resetAndGetPointerAddress()); - - for (int i = 0; i < headers.size(); ++i) - { - WinRTWrapper::ScopedHString headerName (headers[i].upToFirstOccurrenceOf (":", false, false).trim()); - WinRTWrapper::ScopedHString headerValue (headers[i].fromFirstOccurrenceOf (":", false, false).trim()); - - headerCollection->Append (headerName.get(), headerValue.get()); - } - } - - WinRTWrapper::ComPtr createHttpRequestMessage (const String& url, - const StringArray* headers, - const MemoryBlock* postData) - { - auto requestFactory - = WinRTWrapper::getInstance()->getWRLFactory (RuntimeClass_Windows_Web_Http_HttpRequestMessage); - - if (requestFactory == nullptr) - { - jassertfalse; - return {}; - } - - bool isPOSTRequest = (postData != nullptr && postData->getSize() > 0); - auto method = getMethod (isPOSTRequest); - - auto uri = createURI (url); - - WinRTWrapper::ComPtr requestMessage; - requestFactory->Create (method.get(), uri.get(), requestMessage.resetAndGetPointerAddress()); - - if (isPOSTRequest) - { - auto content = getPOSTContent (*postData); - requestMessage->put_Content (content.get()); - } - - if (headers != nullptr && ! headers->isEmpty()) - addHttpHeaders (requestMessage, *headers); - - return requestMessage; + webViewEnvironment = nullptr; } //============================================================================== void setControlBounds (Rectangle newBounds) const { - if (webViewControl != nullptr) + if (webViewController != nullptr) { #if JUCE_WIN_PER_MONITOR_DPI_AWARE if (auto* peer = owner.getTopLevelComponent()->getPeer()) newBounds = (newBounds.toDouble() * peer->getPlatformScaleFactor()).toNearestInt(); #endif - WinRTWrapper::ComPtr site; - - if (SUCCEEDED (webViewControl->QueryInterface (site.resetAndGetPointerAddress()))) - site->put_Bounds ({ static_cast (newBounds.getX()), static_cast (newBounds.getY()), - static_cast (newBounds.getWidth()), static_cast (newBounds.getHeight()) }); + webViewController->put_Bounds({ newBounds.getX(), newBounds.getY(), + newBounds.getRight(), newBounds.getBottom() }); } - } void setControlVisible (bool shouldBeVisible) const { - if (webViewControl != 0) - { - WinRTWrapper::ComPtr site; - - if (SUCCEEDED (webViewControl->QueryInterface (site.resetAndGetPointerAddress()))) - site->put_IsVisible (shouldBeVisible); - } + if (webViewController != nullptr) + webViewController->put_IsVisible (shouldBeVisible); } //============================================================================== WebBrowserComponent& owner; - WinRTWrapper::ComPtr webViewProcess; - WinRTWrapper::ComPtr webViewControl; + WinRTWrapper::ComPtr webViewEnvironment; + WinRTWrapper::ComPtr webViewController; + WinRTWrapper::ComPtr webView; - EventRegistrationToken navigationStartingToken { 0 }, - newWindowRequestedToken { 0 }, - navigationCompletedToken { 0 }; + EventRegistrationToken navigationStartingToken { 0 }, + newWindowRequestedToken { 0 }, + windowCloseRequestedToken { 0 }, + navigationCompletedToken { 0 }, + webResourceRequestedToken { 0 }; + + struct URLRequest + { + String url; + StringArray headers; + MemoryBlock postData; + }; + URLRequest urlRequest; bool isCreating = false; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebView2) }; #endif @@ -806,18 +695,13 @@ class WebBrowserComponent::Pimpl public: Pimpl (WebBrowserComponent& owner) { - #if JUCE_USE_WINRT_WEBVIEW - auto windowsVersionInfo = getWindowsVersionInfo(); - - if (windowsVersionInfo.dwMajorVersion >= 10 && windowsVersionInfo.dwBuildNumber >= 17763) - { - try - { - internal.reset (new WinRTWebView (owner)); - } - catch (std::runtime_error&) {} - } - #endif + #if JUCE_USE_WIN_WEBVIEW2 + try + { + internal.reset (new WebView2 (owner)); + } + catch (std::runtime_error&) {} + #endif if (internal == nullptr) internal.reset (new Win32WebView (owner));