diff --git a/SampleApps/WebView2APISample/AppWindow.cpp b/SampleApps/WebView2APISample/AppWindow.cpp index c72f3b26..7e90c823 100644 --- a/SampleApps/WebView2APISample/AppWindow.cpp +++ b/SampleApps/WebView2APISample/AppWindow.cpp @@ -38,6 +38,7 @@ #include "ScenarioExtensionsManagement.h" #include "ScenarioIFrameDevicePermission.h" #include "ScenarioNavigateWithWebResourceRequest.h" +#include "ScenarioSharedBuffer.h" #include "ScenarioSharedWorkerWRR.h" #include "ScenarioVirtualHostMappingForPopUpWindow.h" #include "ScenarioVirtualHostMappingForSW.h" @@ -47,6 +48,7 @@ #include "SettingsComponent.h" #include "TextInputDialog.h" #include "ViewComponent.h" + using namespace Microsoft::WRL; static constexpr size_t s_maxLoadString = 100; static constexpr UINT s_runAsyncWindowMessage = WM_APP; @@ -562,6 +564,11 @@ bool AppWindow::ExecuteWebViewCommands(WPARAM wParam, LPARAM lParam) NewComponent(this); return true; } + case IDM_SCENARIO_SHARED_BUFFER: + { + NewComponent(this); + return true; + } case IDM_SCENARIO_DOM_CONTENT_LOADED: { NewComponent(this); diff --git a/SampleApps/WebView2APISample/ScenarioSharedBuffer.cpp b/SampleApps/WebView2APISample/ScenarioSharedBuffer.cpp new file mode 100644 index 00000000..75a0013b --- /dev/null +++ b/SampleApps/WebView2APISample/ScenarioSharedBuffer.cpp @@ -0,0 +1,233 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "stdafx.h" + +#include +#include + +#include "ScenarioSharedBuffer.h" + +#include "AppWindow.h" +#include "CheckFailure.h" + +using namespace Microsoft::WRL; + +static constexpr WCHAR c_samplePath[] = L"ScenarioSharedBuffer.html"; + +ScenarioSharedBuffer::ScenarioSharedBuffer(AppWindow* appWindow) + : m_appWindow(appWindow), m_webView(appWindow->GetWebView()) +{ + m_webView18 = m_webView.try_query(); + if (!m_webView18) + { + // Feature not supported. + return; + } + + m_sampleUri = m_appWindow->GetLocalUri(c_samplePath); + + // Turn off this scenario if we navigate away from the sample page + CHECK_FAILURE(m_webView->add_ContentLoading( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2ContentLoadingEventArgs* args) -> HRESULT + { + wil::unique_cotaskmem_string uri; + sender->get_Source(&uri); + if (uri.get() != m_sampleUri) + { + m_appWindow->DeleteComponent(this); + } + return S_OK; + }) + .Get(), + &m_contentLoadingToken)); + + CHECK_FAILURE(m_webView->add_WebMessageReceived( + Microsoft::WRL::Callback( + [this](ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args) + { + WebViewMessageReceived(args, false); + return S_OK; + }) + .Get(), + &m_webMessageReceivedToken)); + + wil::com_ptr webview2_4 = m_webView.try_query(); + if (webview2_4) + { + CHECK_FAILURE(webview2_4->add_FrameCreated( + Callback( + [this]( + ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args) -> HRESULT + { + wil::com_ptr webviewFrame; + CHECK_FAILURE(args->get_Frame(&webviewFrame)); + wil::com_ptr webviewFrame2 = + webviewFrame.try_query(); + if (!webviewFrame2) + { + return S_OK; + } + CHECK_FAILURE(webviewFrame2->add_WebMessageReceived( + Microsoft::WRL::Callback< + ICoreWebView2FrameWebMessageReceivedEventHandler>( + [this]( + ICoreWebView2Frame* sender, + ICoreWebView2WebMessageReceivedEventArgs* args) + { + WebViewMessageReceived(args, true); + return S_OK; + }) + .Get(), + nullptr)); + m_webviewFrame4 = webviewFrame.try_query(); + return S_OK; + }) + .Get(), + &m_frameCreatedToken)); + } + + // Changes to CoreWebView2 settings apply to the next document to which we navigate. + CHECK_FAILURE(m_webView->Navigate(m_sampleUri.c_str())); +} + +void ScenarioSharedBuffer::DisplaySharedBufferData() +{ + if (!m_sharedBuffer) + { + m_appWindow->AsyncMessageBox(L"Share Buffer not setup", L"Shared Buffer Data"); + return; + } + BYTE* buffer = nullptr; + UINT64 size = 0; + CHECK_FAILURE(m_sharedBuffer->get_Buffer(&buffer)); + CHECK_FAILURE(m_sharedBuffer->get_Size(&size)); + // Display first 256 bytes of the data + std::wstringstream message; + message << L"Share Buffer Data:" << std::endl; + for (int i = 0; i < size && i < 256; ++i) + { + if (isprint(buffer[i])) + message << (char)buffer[i]; + else + message << "0x" << std::hex << std::setfill(L'0') << std::setw(2) << buffer[i]; + } + message << std::endl; + m_appWindow->AsyncMessageBox(std::move(message.str()), L"Shared Buffer Data"); +} + +void ScenarioSharedBuffer::WebViewMessageReceived( + ICoreWebView2WebMessageReceivedEventArgs* args, bool fromFrame) +{ + wil::unique_cotaskmem_string uri; + CHECK_FAILURE(args->get_Source(&uri)); + + // Always validate that the origin of the message is what you expect. + if (uri.get() != m_sampleUri) + { + // Ignore messages from untrusted sources. + return; + } + wil::unique_cotaskmem_string messageRaw; + HRESULT hr = args->TryGetWebMessageAsString(&messageRaw); + if (hr == E_INVALIDARG) + { + // Was not a string message. Ignore. + return; + } + // Any other problems are fatal. + CHECK_FAILURE(hr); + std::wstring message = messageRaw.get(); + + if (message == L"SharedBufferDataUpdated") + { + // Shared buffer updated, display it. + DisplaySharedBufferData(); + } + else if (message == L"RequestShareBuffer") + { + EnsureSharedBuffer(); + if (fromFrame) + { + m_webviewFrame4->PostSharedBufferToScript( + m_sharedBuffer.get(), COREWEBVIEW2_SHARED_BUFFER_ACCESS_READ_WRITE, nullptr); + } + else + { + m_webView18->PostSharedBufferToScript( + m_sharedBuffer.get(), COREWEBVIEW2_SHARED_BUFFER_ACCESS_READ_WRITE, nullptr); + } + } + else if (message == L"RequestOneTimeShareBuffer") + { + const UINT64 bufferSize = 128; + BYTE data[] = "some read only data"; + //! [OneTimeShareBuffer] + wil::com_ptr environment; + CHECK_FAILURE( + m_appWindow->GetWebViewEnvironment()->QueryInterface(IID_PPV_ARGS(&environment))); + + wil::com_ptr sharedBuffer; + CHECK_FAILURE(environment->CreateSharedBuffer(bufferSize, &sharedBuffer)); + // Set data into the shared memory via IStream. + wil::com_ptr stream; + CHECK_FAILURE(sharedBuffer->OpenStream(&stream)); + CHECK_FAILURE(stream->Write(data, sizeof(data), nullptr)); + PCWSTR additionalDataAsJson = L"{\"myBufferType\":\"bufferType1\"}"; + if (fromFrame) + { + m_webviewFrame4->PostSharedBufferToScript( + sharedBuffer.get(), COREWEBVIEW2_SHARED_BUFFER_ACCESS_READ_ONLY, + additionalDataAsJson); + } + else + { + m_webView18->PostSharedBufferToScript( + sharedBuffer.get(), COREWEBVIEW2_SHARED_BUFFER_ACCESS_READ_ONLY, + additionalDataAsJson); + } + // Explicitly close the one time shared buffer to ensure that the resource is released. + sharedBuffer->Close(); + //! [OneTimeShareBuffer] + } + else + { + // Ignore unrecognized messages, but log for further investigation + // since it suggests a mismatch between the web content and the host. + OutputDebugString( + std::wstring(L"Unexpected message from main page:" + message).c_str()); + } +} + +void ScenarioSharedBuffer::EnsureSharedBuffer() +{ + if (m_sharedBuffer) + { + // already created + return; + } + wil::com_ptr environment; + CHECK_FAILURE( + m_appWindow->GetWebViewEnvironment()->QueryInterface(IID_PPV_ARGS(&environment))); + + const UINT64 size = 128; + CHECK_FAILURE(environment->CreateSharedBuffer(size, &m_sharedBuffer)); + // Set some data into the shared memory + BYTE* buffer = nullptr; + CHECK_FAILURE(m_sharedBuffer->get_Buffer(&buffer)); + BYTE data[] = "some app data"; + memcpy(buffer, data, sizeof(data)); +} + +ScenarioSharedBuffer::~ScenarioSharedBuffer() +{ + m_webView->remove_ContentLoading(m_contentLoadingToken); + m_webView->remove_WebMessageReceived(m_webMessageReceivedToken); + wil::com_ptr webview2_4 = m_webView.try_query(); + if (webview2_4) + { + webview2_4->remove_FrameCreated(m_frameCreatedToken); + } +} diff --git a/SampleApps/WebView2APISample/ScenarioSharedBuffer.h b/SampleApps/WebView2APISample/ScenarioSharedBuffer.h new file mode 100644 index 00000000..9d8b8dce --- /dev/null +++ b/SampleApps/WebView2APISample/ScenarioSharedBuffer.h @@ -0,0 +1,35 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "stdafx.h" + +#include + +#include "AppWindow.h" +#include "ComponentBase.h" + +class ScenarioSharedBuffer : public ComponentBase +{ +public: + ScenarioSharedBuffer(AppWindow* appWindow); + + ~ScenarioSharedBuffer() override; + +private: + void WebViewMessageReceived(ICoreWebView2WebMessageReceivedEventArgs* args, bool fromFrame); + void EnsureSharedBuffer(); + void DisplaySharedBufferData(); + + AppWindow* m_appWindow; + wil::com_ptr m_webView; + wil::com_ptr m_webView18; + wil::com_ptr m_webviewFrame4; + wil::com_ptr m_sharedBuffer; + std::wstring m_sampleUri; + EventRegistrationToken m_webMessageReceivedToken = {}; + EventRegistrationToken m_contentLoadingToken = {}; + EventRegistrationToken m_frameCreatedToken = {}; +}; diff --git a/SampleApps/WebView2APISample/ScriptComponent.cpp b/SampleApps/WebView2APISample/ScriptComponent.cpp index 26866953..07ce7d45 100644 --- a/SampleApps/WebView2APISample/ScriptComponent.cpp +++ b/SampleApps/WebView2APISample/ScriptComponent.cpp @@ -156,6 +156,9 @@ bool ScriptComponent::HandleWindowMessage( case IDM_POST_WEB_MESSAGE_JSON_FRAME: SendJsonWebMessageIFrame(); return true; + case IDM_INJECT_SCRIPT_WITH_RESULT: + ExecuteScriptWithResult(); + return true; } } return false; @@ -823,6 +826,161 @@ void ScriptComponent::AddSiteEmbeddingIFrame() } } +//! [ExecuteScriptWithResult] +void ScriptComponent::ExecuteScriptWithResult() +{ + TextInputDialog dialog( + m_appWindow->GetMainWindow(), L"Execute Script With Result", L"Enter script code:", + L"Enter the JavaScript code to run in the webview.", L""); + if (dialog.confirmed) + { + wil::com_ptr webview2 = + m_webView.try_query(); + + if (!webview2) + { + MessageBox( + nullptr, L"Get webview2 failed!", L"ExecuteScriptWithResult Result", MB_OK); + return; + } + + // The main interface for excute script, the first param is the string + // which user want to execute, the second param is the callback to process + // the result, here use a lamada to the param. + webview2->ExecuteScriptWithResult( + dialog.input.c_str(), + // The callback function has two param, the first one is the status of call. + // it will always be the S_OK for now, and the second is the result struct. + Callback( + [this](HRESULT errorCode, ICoreWebView2ExperimentalExecuteScriptResult* result) + -> HRESULT + { + if (errorCode != S_OK || result == nullptr) + { + MessageBox( + nullptr, L"Call interface failed!", + L"ExecuteScriptWithResult Result", MB_OK); + return S_OK; + } + else + { + wil::com_ptr exception; + BOOL isSuccess; + + // User should always invoke the get_Success firstly to get if execute + // success. + if (result->get_Succeeded(&isSuccess) != S_OK) + { + MessageBox( + nullptr, L"Get execute status failed!", + L"ExecuteScriptWithResult Result", MB_OK); + return S_OK; + } + + // If execute success, then we can get the raw json data, and try to get + // the string. + if (isSuccess) + { + wil::unique_cotaskmem_string rawJsonData; + // Get the raw json. + if (result->get_ResultAsJson(&rawJsonData) == S_OK) + { + MessageBox( + nullptr, rawJsonData.get(), + L"ExecuteScriptWithResult Json Result", MB_OK); + } + else + { + MessageBox( + nullptr, L"Get raw json data failed", + L"ExecuteScriptWithResult Json Result", MB_OK); + } + + // Get the string, and if the result is not the string type, + // it will return the E_INVALIDARG. + wil::unique_cotaskmem_string stringData; + BOOL isString = FALSE; + if (result->TryGetResultAsString(&stringData, &isString) == S_OK && + isString) + { + MessageBox( + nullptr, stringData.get(), + L"ExecuteScriptWithResult String Result", MB_OK); + } + else + { + MessageBox( + nullptr, L"Get string failed", + L"ExecuteScriptWithResult String Result", MB_OK); + } + } + else // If execute failed, then we can get the exception struct to get + // the reason of failed. + { + if (result->get_Exception(&exception) == S_OK) + { + // Get the exception name, this could return the empty string, + // such as `throw 1`. + wil::unique_cotaskmem_string exceptionName; + if (exception && exception->get_Name(&exceptionName) == S_OK) + { + MessageBox( + nullptr, exceptionName.get(), + L"ExecuteScriptWithResult Exception Name", MB_OK); + } + + // Get the exception message, this could return the empty + // string, such as `throw 1`. + wil::unique_cotaskmem_string exceptionMessage; + if (exception && + exception->get_Message(&exceptionMessage) == S_OK) + { + MessageBox( + nullptr, exceptionMessage.get(), + L"ExecuteScriptWithResult Exception Message", MB_OK); + } + + // Get the exception detail, it's a json struct data with all + // exception infomation , we can parse it and get the detail + // what we need. + wil::unique_cotaskmem_string exceptionDetail; + if (exception && + exception->get_ToJson(&exceptionDetail) == S_OK) + { + MessageBox( + nullptr, exceptionDetail.get(), + L"ExecuteScriptWithResult Exception Detail", MB_OK); + } + + uint32_t lineNumber = 0; + uint32_t columnNumber = 0; + if (exception && + exception->get_LineNumber(&lineNumber) == S_OK && + exception->get_ColumnNumber(&columnNumber) == S_OK) + { + auto exceptionLocationInfo = + L"LineNumber:" + std::to_wstring(lineNumber) + + L", ColumnNumber:" + std::to_wstring(columnNumber); + MessageBox( + nullptr, exceptionLocationInfo.c_str(), + L"ExecuteScriptWithResult Exception Location", MB_OK); + } + } + else + { + MessageBox( + nullptr, L"Get exception failed", + L"ExecuteScriptWithResult Result", MB_OK); + } + } + } + return S_OK; + }) + .Get()); + } +} +//! [ExecuteScriptWithResult] + void ScriptComponent::HandleIFrames() { wil::com_ptr webview2_4 = m_webView.try_query(); diff --git a/SampleApps/WebView2APISample/ScriptComponent.h b/SampleApps/WebView2APISample/ScriptComponent.h index 9ef0159a..34f2020d 100644 --- a/SampleApps/WebView2APISample/ScriptComponent.h +++ b/SampleApps/WebView2APISample/ScriptComponent.h @@ -47,8 +47,8 @@ class ScriptComponent : public ComponentBase void SendJsonWebMessageIFrame(); void AddSiteEmbeddingIFrame(); + void ExecuteScriptWithResult(); ~ScriptComponent() override; - void HandleIFrames(); std::wstring IFramesToString(); std::vector> m_frames; diff --git a/SampleApps/WebView2APISample/WebView2APISample.rc b/SampleApps/WebView2APISample/WebView2APISample.rc index 053d218d..f09ba5b3 100644 --- a/SampleApps/WebView2APISample/WebView2APISample.rc +++ b/SampleApps/WebView2APISample/WebView2APISample.rc @@ -64,6 +64,7 @@ BEGIN BEGIN MENUITEM "Inject Script", IDM_INJECT_SCRIPT MENUITEM "Inject Script Into IFrame", IDM_INJECT_SCRIPT_FRAME + MENUITEM "Inject Script With Result", IDM_INJECT_SCRIPT_WITH_RESULT MENUITEM "Add Initialize Script", ID_ADD_INITIALIZE_SCRIPT MENUITEM "Remove Initialize Script", ID_REMOVE_INITIALIZE_SCRIPT MENUITEM SEPARATOR @@ -266,6 +267,7 @@ BEGIN MENUITEM "Virtual Host Mapping For Pop Up Window", IDM_SCENARIO_VIRTUAL_HOST_MAPPING_POP_UP_WINDOW MENUITEM "Web Messaging", IDM_SCENARIO_POST_WEB_MESSAGE MENUITEM "WebView Event Monitor", IDM_SCENARIO_WEB_VIEW_EVENT_MONITOR + MENUITEM "Shared Buffer", IDM_SCENARIO_SHARED_BUFFER END POPUP "&Audio" BEGIN diff --git a/SampleApps/WebView2APISample/WebView2APISample.vcxproj b/SampleApps/WebView2APISample/WebView2APISample.vcxproj index 38fb98f2..06fcdcd1 100644 --- a/SampleApps/WebView2APISample/WebView2APISample.vcxproj +++ b/SampleApps/WebView2APISample/WebView2APISample.vcxproj @@ -412,6 +412,7 @@ + @@ -451,6 +452,7 @@ + @@ -507,6 +509,9 @@ $(OutDir)\assets + + $(OutDir)\assets + $(OutDir)\assets @@ -557,13 +562,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + diff --git a/SampleApps/WebView2APISample/assets/ScenarioSharedBuffer.html b/SampleApps/WebView2APISample/assets/ScenarioSharedBuffer.html new file mode 100644 index 00000000..f1c941f6 --- /dev/null +++ b/SampleApps/WebView2APISample/assets/ScenarioSharedBuffer.html @@ -0,0 +1,123 @@ + + + + ScenarioSharedBuffer + + + + + +

Shared Buffer Data

+
+
+
+
+
+
+
+ +
+ + + + diff --git a/SampleApps/WebView2APISample/documentation/Testing-Instructions.md b/SampleApps/WebView2APISample/documentation/Testing-Instructions.md index 3cab7e0d..f53407fb 100644 --- a/SampleApps/WebView2APISample/documentation/Testing-Instructions.md +++ b/SampleApps/WebView2APISample/documentation/Testing-Instructions.md @@ -15,6 +15,7 @@ These are instructions for manually testing all the features of the WebView2 API * [Exit](#Exit) * [Script](#Script) * [Inject Script](#Inject-Script) + * [Inject Script With Result](#Inject-Script-With-Result) * [Post Message String](#Post-Message-String) * [Post Message JSON](#Post-Message-JSON) * [Add Initialize Script](#addremove-initialize-script) @@ -175,6 +176,23 @@ Test that prompts the user for some script to run in the WebView 11. Click OK inside the popup dialog 12. Expected: dialog closed +#### Inject Script With Result + +Test that prompts the user for some script to run in the WebView, and get the error reason when the execution fails + +1. Launch the sample app. +2. Go to `Script -> Inject Script With Result` +3. Expected: Text Input Dialog that prompts the user for JavaScript code to execute in the current top level document rendered in the WebView +4. Click `Cancel` +5. Repeat steps 2-3 +6. Type `"abc"` in the text input box and click `OK` +7. Expected: ExecuteScriptWithResult Json Result popup that says `"abc"` +8. Click OK inside the popup dialog +9. Expected: dialog closed +10. Expected: ExecuteScriptWithResult String Result popup that says `abc` +11. Click OK inside the popup dialog +12. Expected: dialog closed + #### Post Message string Test that prompts the user for some string web message to the top level document diff --git a/SampleApps/WebView2APISample/packages.config b/SampleApps/WebView2APISample/packages.config index dbf870c8..418229da 100644 --- a/SampleApps/WebView2APISample/packages.config +++ b/SampleApps/WebView2APISample/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/SampleApps/WebView2APISample/resource.h b/SampleApps/WebView2APISample/resource.h index 253f4c2d..09f83568 100644 --- a/SampleApps/WebView2APISample/resource.h +++ b/SampleApps/WebView2APISample/resource.h @@ -115,6 +115,7 @@ #define IDC_EDIT_DOWNLOAD_PATH 242 #define IDM_CALL_CDP_METHOD_FOR_SESSION 243 #define IDM_COLLECT_HEAP_MEMORY_VIA_CDP 244 +#define IDM_INJECT_SCRIPT_WITH_RESULT 245 #define IDM_TOGGLE_CUSTOM_CRASH_REPORTING 246 #define IDM_GET_FAILURE_REPORT_FOLDER 247 #define IDM_TOGGLE_TOPMOST_WINDOW 300 @@ -161,6 +162,7 @@ #define IDM_SCENARIO_PRINT_TO_DEFAULT_PRINTER 2031 #define IDM_SCENARIO_PRINT_TO_PRINTER 2032 #define IDM_SCENARIO_PRINT_TO_PDF_STREAM 2033 +#define IDM_SCENARIO_SHARED_BUFFER 2034 #define ID_BLOCKEDSITES 32773 #define ID_SETTINGS_NAVIGATETOSTRING 32774 #define ID_ADD_INITIALIZE_SCRIPT 32775 diff --git a/SampleApps/WebView2WindowsFormsBrowser/README.md b/SampleApps/WebView2WindowsFormsBrowser/README.md index c7a1360c..63946f70 100644 --- a/SampleApps/WebView2WindowsFormsBrowser/README.md +++ b/SampleApps/WebView2WindowsFormsBrowser/README.md @@ -24,9 +24,9 @@ The WebView2WindowsFormsBrowser is an example of an application that embeds a We The API Sample showcases a selection of WebView2's event handlers and API methods that allow a Windows Forms application to directly interact with a WebView and vice versa. -If this is your first time using WebView, we recommend first following the [Getting Started](https://learn.microsoft.com/microsoft-edge/webview2/gettingstarted/winforms) guide, which goes over how to create a WebView2 and walks through some basic WebView2 functionality. +If this is your first time using WebView, we recommend first following the [Getting Started](https://docs.microsoft.com/microsoft-edge/webview2/gettingstarted/winforms) guide, which goes over how to create a WebView2 and walks through some basic WebView2 functionality. -To learn more specifics about events and API Handlers in WebView2, you can refer to the [WebView2 Reference Documentation](https://learn.microsoft.com/microsoft-edge/webview2/webview2-api-reference). +To learn more specifics about events and API Handlers in WebView2, you can refer to the [WebView2 Reference Documentation](https://docs.microsoft.com/microsoft-edge/webview2/webview2-api-reference). ## Prerequisites diff --git a/SampleApps/WebView2WindowsFormsBrowser/WebView2WindowsFormsBrowser.csproj b/SampleApps/WebView2WindowsFormsBrowser/WebView2WindowsFormsBrowser.csproj index 0fc52b05..4f90119c 100644 --- a/SampleApps/WebView2WindowsFormsBrowser/WebView2WindowsFormsBrowser.csproj +++ b/SampleApps/WebView2WindowsFormsBrowser/WebView2WindowsFormsBrowser.csproj @@ -19,7 +19,7 @@ AnyCPU - + diff --git a/SampleApps/WebView2WpfBrowser/MainWindow.xaml b/SampleApps/WebView2WpfBrowser/MainWindow.xaml index 809e85ac..06113da3 100644 --- a/SampleApps/WebView2WpfBrowser/MainWindow.xaml +++ b/SampleApps/WebView2WpfBrowser/MainWindow.xaml @@ -29,6 +29,12 @@ found in the LICENSE file. + + + + + + @@ -64,12 +70,23 @@ found in the LICENSE file. + + + + + + + + + + + @@ -79,20 +96,34 @@ found in the LICENSE file. + + + + + + + + + + + + + + @@ -104,11 +135,13 @@ found in the LICENSE file. - + + + @@ -166,6 +199,7 @@ found in the LICENSE file. + diff --git a/SampleApps/WebView2WpfBrowser/MainWindow.xaml.cs b/SampleApps/WebView2WpfBrowser/MainWindow.xaml.cs index e0a90844..ab27c7aa 100644 --- a/SampleApps/WebView2WpfBrowser/MainWindow.xaml.cs +++ b/SampleApps/WebView2WpfBrowser/MainWindow.xaml.cs @@ -10,7 +10,9 @@ using System.IO; using System.Linq; using System.Net; +using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -21,6 +23,7 @@ using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; +using System.Windows.Threading; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Wpf; @@ -33,6 +36,7 @@ public partial class MainWindow : Window { public static RoutedCommand InjectScriptCommand = new RoutedCommand(); public static RoutedCommand InjectScriptIFrameCommand = new RoutedCommand(); + public static RoutedCommand InjectScriptWithResultCommand = new RoutedCommand(); public static RoutedCommand PrintToPdfCommand = new RoutedCommand(); public static RoutedCommand NavigateWithWebResourceRequestCommand = new RoutedCommand(); public static RoutedCommand DOMContentLoadedCommand = new RoutedCommand(); @@ -68,15 +72,42 @@ public partial class MainWindow : Window public static RoutedCommand CustomServerCertificateSupportCommand = new RoutedCommand(); public static RoutedCommand ClearServerCertificateErrorActionsCommand = new RoutedCommand(); public static RoutedCommand NewWindowWithOptionsCommand = new RoutedCommand(); + public static RoutedCommand CreateNewThreadCommand = new RoutedCommand(); public static RoutedCommand PrintDialogCommand = new RoutedCommand(); public static RoutedCommand PrintToDefaultPrinterCommand = new RoutedCommand(); public static RoutedCommand PrintToPrinterCommand = new RoutedCommand(); public static RoutedCommand PrintToPdfStreamCommand = new RoutedCommand(); // Commands(V2) public static RoutedCommand AboutCommand = new RoutedCommand(); + + + + public static RoutedCommand CrashBrowserProcessCommand = new RoutedCommand(); + public static RoutedCommand CrashRenderProcessCommand = new RoutedCommand(); + + public static RoutedCommand GetDocumentTitleCommand = new RoutedCommand(); + public static RoutedCommand GetUserDataFolderCommand = new RoutedCommand(); + public static RoutedCommand SharedBufferRequestedCommand = new RoutedCommand(); + public static RoutedCommand PostMessageStringCommand = new RoutedCommand(); + public static RoutedCommand PostMessageJSONCommand = new RoutedCommand(); + + + public static RoutedCommand HostObjectsAllowedCommand = new RoutedCommand(); + public static RoutedCommand BrowserAcceleratorKeyEnabledCommand = new RoutedCommand(); + + public static RoutedCommand AddInitializeScriptCommand = new RoutedCommand(); + public static RoutedCommand RemoveInitializeScriptCommand = new RoutedCommand(); + + public static RoutedCommand CallCdpMethodCommand = new RoutedCommand(); + public static RoutedCommand OpenDevToolsCommand = new RoutedCommand(); + public static RoutedCommand OpenTaskManagerCommand = new RoutedCommand(); + bool _isNavigating = false; + // for add/remove initialize script + string m_lastInitializeScriptId; + CoreWebView2Settings _webViewSettings; CoreWebView2Settings WebViewSettings { @@ -119,6 +150,9 @@ CoreWebView2Profile WebViewProfile List _webViewFrames = new List(); IReadOnlyList _processList = new List(); + IDictionary<(string, CoreWebView2PermissionKind, bool), bool> _cachedPermissions = + new Dictionary<(string, CoreWebView2PermissionKind, bool), bool>(); + public CoreWebView2CreationProperties CreationProperties { get; set; } = null; public MainWindow() @@ -127,7 +161,7 @@ public MainWindow() InitializeComponent(); AttachControlEventHandlers(webView); // Set background transparent - // webView.DefaultBackgroundColor = System.Drawing.Color.Transparent; + //webView.DefaultBackgroundColor = System.Drawing.Color.Transparent; } public MainWindow(CoreWebView2CreationProperties creationProperties = null) @@ -137,7 +171,7 @@ public MainWindow(CoreWebView2CreationProperties creationProperties = null) InitializeComponent(); AttachControlEventHandlers(webView); // Set background transparent - // webView.DefaultBackgroundColor = System.Drawing.Color.Transparent; + //webView.DefaultBackgroundColor = System.Drawing.Color.Transparent; } void AttachControlEventHandlers(WebView2 control) @@ -164,6 +198,13 @@ bool IsWebViewValid() } } + void AssertCondition(bool condition, string message) + { + if (condition) + return; + MessageBox.Show(message, "Assertion Failed"); + } + void NewCmdExecuted(object sender, ExecutedRoutedEventArgs e) { new MainWindow().Show(); @@ -324,112 +365,94 @@ WebView2 GetReplacementControl(bool useNewEnvironment) void WebView_ProcessFailed(object sender, CoreWebView2ProcessFailedEventArgs e) { - void ReinitIfSelectedByUser(CoreWebView2ProcessFailedKind kind) + void ReinitIfSelectedByUser(string caption, string message) { - string caption; - string message; - if (kind == CoreWebView2ProcessFailedKind.BrowserProcessExited) - { - caption = "Browser process exited"; - message = "WebView2 Runtime's browser process exited unexpectedly. Recreate WebView?"; - } - else + this.Dispatcher.InvokeAsync(() => { - caption = "Web page unresponsive"; - message = "WebView2 Runtime's render process stopped responding. Recreate WebView?"; - } - - var selection = MessageBox.Show(message, caption, MessageBoxButton.YesNo); - if (selection == MessageBoxResult.Yes) - { - // The control cannot be re-initialized so we setup a new instance to replace it. - // Note the previous instance of the control is disposed of and removed from the - // visual tree before attaching the new one. - if (_isControlInVisualTree) + var selection = MessageBox.Show(message, caption, MessageBoxButton.YesNo); + if (selection == MessageBoxResult.Yes) { - RemoveControlFromVisualTree(webView); + // The control cannot be re-initialized so we setup a new instance to replace it. + // Note the previous instance of the control is disposed of and removed from the + // visual tree before attaching the new one. + if (_isControlInVisualTree) + { + RemoveControlFromVisualTree(webView); + } + webView.Dispose(); + webView = GetReplacementControl(false); + AttachControlToVisualTree(webView); + // Set background transparent + webView.DefaultBackgroundColor = System.Drawing.Color.Transparent; } - webView.Dispose(); - webView = GetReplacementControl(false); - AttachControlToVisualTree(webView); - } + }); } - void ReloadIfSelectedByUser(CoreWebView2ProcessFailedKind kind) + void ReloadIfSelectedByUser(string caption, string message) { - string caption; - string message; - if (kind == CoreWebView2ProcessFailedKind.RenderProcessExited) - { - caption = "Web page unresponsive"; - message = "WebView2 Runtime's render process exited unexpectedly. Reload page?"; - } - else + this.Dispatcher.InvokeAsync(() => { - caption = "App content frame unresponsive"; - message = "WebView2 Runtime's render process for app frame exited unexpectedly. Reload page?"; - } - - var selection = MessageBox.Show(message, caption, MessageBoxButton.YesNo); - if (selection == MessageBoxResult.Yes) - { - webView.Reload(); - } + var selection = MessageBox.Show(message, caption, MessageBoxButton.YesNo); + if (selection == MessageBoxResult.Yes) + { + webView.Reload(); + // Set background transparent + webView.DefaultBackgroundColor = System.Drawing.Color.Transparent; + } + }); } bool IsAppContentUri(Uri source) { // Sample virtual host name for the app's content. - // See CoreWebView2.SetVirtualHostNameToFolderMapping: https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2.setvirtualhostnametofoldermapping + // See CoreWebView2.SetVirtualHostNameToFolderMapping: https://docs.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2.setvirtualhostnametofoldermapping return source.Host == "appassets.example"; } - switch (e.ProcessFailedKind) + if (e.ProcessFailedKind == CoreWebView2ProcessFailedKind.FrameRenderProcessExited) { - case CoreWebView2ProcessFailedKind.BrowserProcessExited: - // Once the WebView2 Runtime's browser process has crashed, - // the control becomes virtually unusable as the process exit - // moves the CoreWebView2 to its Closed state. Most calls will - // become invalid as they require a backing browser process. - // Remove the control from the visual tree so the framework does - // not attempt to redraw it, which would call the invalid methods. - RemoveControlFromVisualTree(webView); - goto case CoreWebView2ProcessFailedKind.RenderProcessUnresponsive; - case CoreWebView2ProcessFailedKind.RenderProcessUnresponsive: - System.Threading.SynchronizationContext.Current.Post((_) => - { - ReinitIfSelectedByUser(e.ProcessFailedKind); - }, null); - break; - case CoreWebView2ProcessFailedKind.RenderProcessExited: - System.Threading.SynchronizationContext.Current.Post((_) => - { - ReloadIfSelectedByUser(e.ProcessFailedKind); - }, null); - break; - case CoreWebView2ProcessFailedKind.FrameRenderProcessExited: - // A frame-only renderer has exited unexpectedly. Check if reload is needed. - // In this sample we only reload if the app's content has been impacted. - foreach (CoreWebView2FrameInfo frameInfo in e.FrameInfosForFailedProcess) + // A frame-only renderer has exited unexpectedly. Check if reload is needed. + // In this sample we only reload if the app's content has been impacted. + foreach (CoreWebView2FrameInfo frameInfo in e.FrameInfosForFailedProcess) + { + if (IsAppContentUri(new System.Uri(frameInfo.Source))) { - if (IsAppContentUri(new System.Uri(frameInfo.Source))) + System.Threading.SynchronizationContext.Current.Post((_) => { - goto case CoreWebView2ProcessFailedKind.RenderProcessExited; - } + ReloadIfSelectedByUser("App content frame unresponsive", + "Browser render process for app frame exited unexpectedly. Reload page?"); + }, null); } - break; - default: - // Show the process failure details. Apps can collect info for their logging purposes. - StringBuilder messageBuilder = new StringBuilder(); - messageBuilder.AppendLine($"Process kind: {e.ProcessFailedKind}"); - messageBuilder.AppendLine($"Reason: {e.Reason}"); - messageBuilder.AppendLine($"Exit code: {e.ExitCode}"); - messageBuilder.AppendLine($"Process description: {e.ProcessDescription}"); - System.Threading.SynchronizationContext.Current.Post((_) => - { - MessageBox.Show(messageBuilder.ToString(), "Child process failed", MessageBoxButton.OK); - }, null); - break; + } + + return; + } + + // Show the process failure details. Apps can collect info for their logging purposes. + this.Dispatcher.InvokeAsync(() => + { + StringBuilder messageBuilder = new StringBuilder(); + messageBuilder.AppendLine($"Process kind: {e.ProcessFailedKind}"); + messageBuilder.AppendLine($"Reason: {e.Reason}"); + messageBuilder.AppendLine($"Exit code: {e.ExitCode}"); + messageBuilder.AppendLine($"Process description: {e.ProcessDescription}"); + MessageBox.Show(messageBuilder.ToString(), "Child process failed", MessageBoxButton.OK); + }); + + if (e.ProcessFailedKind == CoreWebView2ProcessFailedKind.BrowserProcessExited) + { + ReinitIfSelectedByUser("Browser process exited", + "Browser process exited unexpectedly. Recreate webview?"); + } + else if (e.ProcessFailedKind == CoreWebView2ProcessFailedKind.RenderProcessUnresponsive) + { + ReinitIfSelectedByUser("Web page unresponsive", + "Browser render process has stopped responding. Recreate webview?"); + } + else if (e.ProcessFailedKind == CoreWebView2ProcessFailedKind.RenderProcessExited) + { + ReloadIfSelectedByUser("Web page unresponsive", + "Browser render process exited unexpectedly. Reload page?"); } } @@ -566,7 +589,7 @@ async void PrintToPdfCmdExecuted(object target, ExecutedRoutedEventArgs e) } } - // Shows the user a print dialog. If `printDialogKind` is browser print preview, + // Shows the user a print dialog. If `printDialogKind` is browser print preview, // opens a browser print preview dialog, CoreWebView2PrintDialogKind.System opens a system print dialog. void ShowPrintUI(object target, ExecutedRoutedEventArgs e) { @@ -615,6 +638,7 @@ async void PrintToDefaultPrinter () } } + // // Function to get printer name by displaying printer text input dialog to the user. // User has to specify the desired printer name by querying the installed printers list on the // OS to print the web page. @@ -634,14 +658,14 @@ string GetPrinterName() return printerName; // or - // + // // Use GetPrintQueues() of LocalPrintServer from System.Printing to get list of locally installed printers. // Display the printer list to the user and get the desired printer to print. // Return the user selected printer name. } // Function to get print settings for the selected printer. - // You may also choose get the capabilties from the native printer API, display to the user to get + // You may also choose get the capabilties from the native printer API, display to the user to get // the print settings for the current web page and for the selected printer. CoreWebView2PrintSettings GetSelectedPrinterPrintSettings(string printerName) { @@ -653,7 +677,7 @@ CoreWebView2PrintSettings GetSelectedPrinterPrintSettings(string printerName) return printSettings; // or - // + // // Get PrintQueue for the selected printer and use GetPrintCapabilities() of PrintQueue from System.Printing // to get the capabilities of the selected printer. // Display the printer capabilities to the user along with the page settings. @@ -696,7 +720,9 @@ async void PrintToPrinter() } } + // + // // This example prints the Pdf data of the current web page to a stream. async void PrintToPdfStream() { @@ -706,7 +732,6 @@ async void PrintToPdfStream() // Passing null for `PrintSettings` results in default print settings used. System.IO.Stream stream = await webView.CoreWebView2.PrintToPdfStreamAsync(null); - DisplayPdfDataInPrintDialog(stream); MessageBox.Show(this, "Printing" + title + " document to PDF Stream " + ((stream != null) ? "succeeded" : "failed"), "Print To PDF Stream"); } @@ -722,6 +747,7 @@ void DisplayPdfDataInPrintDialog(Stream pdfData) { // You can display the printable pdf data in a custom print preview dialog to the end user. } + // async void GetCookiesCmdExecuted(object target, ExecutedRoutedEventArgs e) { @@ -777,52 +803,52 @@ void WebMessagesCmdExecuted(object target, ExecutedRoutedEventArgs e) webView.Source = new Uri("https://appassets.example/webMessages.html"); } - String GetWindowBounds(CoreWebView2WebMessageReceivedEventArgs args) + + void HandleWebMessage(CoreWebView2WebMessageReceivedEventArgs args) { - if (args.Source != "https://appassets.example/webMessages.html") - { - // Ignore messages from untrusted sources. - return null; - } - string message; try { - message = args.TryGetWebMessageAsString(); - } - catch (ArgumentException) - { - // Ignore messages that aren't strings, but log for further investigation - // since it suggests a mismatch between the web content and the host. - Debug.WriteLine($"Non-string message received"); - return null; - } + if (args.Source != "https://appassets.example/webMessages.html") + { + // Throw exception from untrusted sources. + throw new Exception(); + } - if (message == "GetWindowBounds") - { - String reply = "{\"WindowBounds\":\"Left:" + 0 + - "\\nTop:" + 0 + - "\\nRight:" + webView.ActualWidth + - "\\nBottom:" + webView.ActualHeight + - "\"}"; - return reply; + string message = args.TryGetWebMessageAsString(); + + if (message.Contains("SetTitleText")) + { + int msgLength = "SetTitleText".Length; + this.Title = message.Substring(msgLength); + + } + + else if (message == "GetWindowBounds") + { + string reply = "{\"WindowBounds\":\"Left:" + 0 + + "\\nTop:" + 0 + + "\\nRight:" + webView.ActualWidth + + "\\nBottom:" + webView.ActualHeight + + "\"}"; + + webView.CoreWebView2.PostWebMessageAsJson(reply); + } + else + { + // Ignore unrecognized messages, but log them + // since it suggests a mismatch between the web content and the host. + Debug.WriteLine($"Unexpected message received: {message}"); + } } - else + catch (Exception e) { - // Ignore unrecognized messages, but log them - // since it suggests a mismatch between the web content and the host. - Debug.WriteLine($"Unexpected message received: {message}"); + MessageBox.Show($"Unexpected message received: {e.Message}"); } - - return null; } void WebView_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs args) { - String reply = GetWindowBounds(args); - if (!String.IsNullOrEmpty(reply)) - { - webView.CoreWebView2.PostWebMessageAsJson(reply); - } + HandleWebMessage(args); } // @@ -830,15 +856,12 @@ void WebView_FrameCreatedWebMessages(object sender, CoreWebView2FrameCreatedEven { args.Frame.WebMessageReceived += (WebMessageReceivedSender, WebMessageReceivedArgs) => { - String reply = GetWindowBounds(WebMessageReceivedArgs); - if (!String.IsNullOrEmpty(reply)) - { - args.Frame.PostWebMessageAsJson(reply); - } + HandleWebMessage(WebMessageReceivedArgs); }; } // + // void DOMContentLoadedCmdExecuted(object target, ExecutedRoutedEventArgs e) { @@ -1042,7 +1065,7 @@ void CustomContextMenuCmdExecuted(object target, ExecutedRoutedEventArgs e) void AuthenticationCmdExecuted(object target, ExecutedRoutedEventArgs e) { - // + // webView.CoreWebView2.BasicAuthenticationRequested += delegate (object sender, CoreWebView2BasicAuthenticationRequestedEventArgs args) { // [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Demo credentials in https://authenticationtest.com")] @@ -1051,7 +1074,7 @@ void AuthenticationCmdExecuted(object target, ExecutedRoutedEventArgs e) args.Response.Password = "pass"; }; webView.CoreWebView2.Navigate("https://authenticationtest.com/HTTPAuth"); - // + // } private bool _isFaviconChanged = false; @@ -1673,11 +1696,11 @@ private string GetStartPageUri(CoreWebView2 webView2) { return uri; } - String sdkBuildVersion = GetSdkBuildVersion(), + string sdkBuildVersion = GetSdkBuildVersion(), runtimeVersion = GetRuntimeVersion(webView2), appPath = GetAppPath(), runtimePath = GetRuntimePath(webView2); - String newUri = $"{uri}?sdkBuild={sdkBuildVersion}&runtimeVersion={runtimeVersion}" + + string newUri = $"{uri}?sdkBuild={sdkBuildVersion}&runtimeVersion={runtimeVersion}" + $"&appPath={appPath}&runtimePath={runtimePath}"; return newUri; } @@ -1703,6 +1726,9 @@ void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2Init // webView.CoreWebView2.IsMutedChanged += WebView_IsMutedChanged; // + // + webView.CoreWebView2.PermissionRequested += WebView_PermissionRequested; + // // The CoreWebView2Environment instance is reused when re-assigning CoreWebView2CreationProperties // to the replacement control. We don't need to re-attach the event handlers unless the environment @@ -1793,9 +1819,9 @@ string WebViewFrames_ToString() { if (i > 0) result += "; "; result += i.ToString() + " " + - (string.IsNullOrEmpty(_webViewFrames[i].Name) ? "" : _webViewFrames[i].Name); + (String.IsNullOrEmpty(_webViewFrames[i].Name) ? "" : _webViewFrames[i].Name); } - return string.IsNullOrEmpty(result) ? "no iframes available." : result; + return String.IsNullOrEmpty(result) ? "no iframes available." : result; } // @@ -2097,6 +2123,52 @@ void NewWindowWithOptionsCmdExecuted(object target, ExecutedRoutedEventArgs e) new MainWindow(dialog.CreationProperties).Show(); } } + + private void ThreadProc(string browserExecutableFolder, string userDataFolder, string language, string additionalBrowserArguments, string profileName, bool? isInPrivateModeEnabled) + { + try + { + // Some class members are global static properties, and VerifyAccess() will throw exceptions when accessing across threads. So prepare copies of them. + // See DispatcherObject Class on MSDN: Only the thread that the Dispatcher was created on may access the DispatcherObject directly. + CoreWebView2CreationProperties creationProperties = new CoreWebView2CreationProperties(); + creationProperties.BrowserExecutableFolder = browserExecutableFolder; + creationProperties.UserDataFolder = userDataFolder; + creationProperties.Language = language; + creationProperties.AdditionalBrowserArguments = additionalBrowserArguments; + creationProperties.ProfileName = profileName; + creationProperties.IsInPrivateModeEnabled = isInPrivateModeEnabled; + var tempWindow = new MainWindow(creationProperties); + tempWindow.Show(); + // Causes dispatcher to shutdown when window is closed. + tempWindow.Closed += (s, e) => + { + System.Windows.Threading.Dispatcher.CurrentDispatcher.InvokeShutdown(); + }; + // Run the message pump + System.Windows.Threading.Dispatcher.Run(); + } + catch (Exception exception) + { + MessageBox.Show("Create New Thread Failed: " + exception.Message, "Create New Thread"); + } + } + + void CreateNewThreadCmdExecuted(object target, ExecutedRoutedEventArgs e) + { + string browserExecutableFolder = webView.CreationProperties.BrowserExecutableFolder; + string userDataFolder = webView.CreationProperties.UserDataFolder; + string language = webView.CreationProperties.Language; + string additionalBrowserArguments = webView.CreationProperties.AdditionalBrowserArguments; + string profileName = webView.CreationProperties.ProfileName; + bool? isInPrivateModeEnabled = webView.CreationProperties.IsInPrivateModeEnabled; + Thread newWindowThread = new Thread(() => + { + ThreadProc(browserExecutableFolder, userDataFolder, language, additionalBrowserArguments, profileName, isInPrivateModeEnabled); + }); + newWindowThread.SetApartmentState(ApartmentState.STA); + newWindowThread.IsBackground = false; + newWindowThread.Start(); + } // Commands(V2) void AboutCommandExecuted(object target, ExecutedRoutedEventArgs e) { @@ -2111,9 +2183,401 @@ private void SmartScreenEnabledExecuted(object sender, ExecutedRoutedEventArgs e MessageBox.Show("SmartScreen is" + (WebViewSettings.IsReputationCheckingRequired ? " enabled " : " disabled ") + "after the next navigation."); } + + void PostMessageStringCommandExecuted(object target, ExecutedRoutedEventArgs e) + { + var dialog = new TextInputDialog( + title: "Post Web Message String", + description: "Web message string:\r\nEnter the web message as a string."); + + try + { + if (dialog.ShowDialog() == true) + { + webView.CoreWebView2.PostWebMessageAsString(dialog.Input.Text); + } + } + catch (NotImplementedException exception) + { + MessageBox.Show(this, "PostMessageAsString Failed: " + exception.Message, + "Post Message As String"); + } + } + + + void PostMessageJSONCommandExecuted(object target, ExecutedRoutedEventArgs e) + { + var dialog = new TextInputDialog( + title: "Post Web Message JSON", + description: "Web message JSON:\r\nEnter the web message as JSON.", + defaultInput: "{\"SetColor\":\"blue\"}"); + + try + { + if (dialog.ShowDialog() == true) + { + webView.CoreWebView2.PostWebMessageAsJson(dialog.Input.Text); + } + } + catch (NotImplementedException exception) + { + MessageBox.Show(this, "PostMessageAsJSON Failed: " + exception.Message, + "Post Message As JSON"); + } + } + void GetDocumentTitleCommandExecuted(object target, ExecutedRoutedEventArgs e) { MessageBox.Show(webView.CoreWebView2.DocumentTitle, "Document Title"); } + + void GetUserDataFolderCmdExecuted(object target, ExecutedRoutedEventArgs e) + { + try + { + MessageBox.Show(WebViewEnvironment.UserDataFolder, "User Data Folder"); + } + catch (Exception exception) + { + MessageBox.Show(this, "Get User Data Folder Failed: " + exception.Message, "User Data Folder"); + } + } + + // + private CoreWebView2SharedBuffer sharedBuffer = null; + void SharedBufferRequestedCmdExecuted(object target, ExecutedRoutedEventArgs e) + { + webView.CoreWebView2.WebMessageReceived += WebView_WebMessageReceivedSharedBuffer; + webView.CoreWebView2.FrameCreated += WebView_FrameCreatedSharedBuffer; + webView.CoreWebView2.SetVirtualHostNameToFolderMapping( + "appassets.example", "assets", CoreWebView2HostResourceAccessKind.DenyCors); + webView.Source = new Uri("https://appassets.example/sharedBuffer.html"); + } + + void EnsureSharedBuffer() + { + ulong bufferSize = 128; + if (sharedBuffer == null) + { + sharedBuffer = WebViewEnvironment.CreateSharedBuffer(bufferSize); + + using (Stream stream = sharedBuffer.OpenStream()) + { + using (StreamWriter writer = new StreamWriter(stream)) + { + writer.Write("some data from .NET"); + } + } + } + } + + void WebView_WebMessageReceivedSharedBuffer(object sender, CoreWebView2WebMessageReceivedEventArgs args) + { + bool forWebView = (sender is CoreWebView2); + if (args.TryGetWebMessageAsString() == "RequestShareBuffer") + { + EnsureSharedBuffer(); + if (forWebView) + { + ((CoreWebView2)sender).PostSharedBufferToScript(sharedBuffer, CoreWebView2SharedBufferAccess.ReadWrite, null); + } + else + { + ((CoreWebView2Frame)sender).PostSharedBufferToScript(sharedBuffer, CoreWebView2SharedBufferAccess.ReadWrite, null); + } + } + else if (args.TryGetWebMessageAsString() == "RequestOneTimeShareBuffer") + { + ulong bufferSize = 128; + string data = "some read only data"; + // + SafeHandle fileMappingHandle; + using (CoreWebView2SharedBuffer buffer = WebViewEnvironment.CreateSharedBuffer(bufferSize)) + { + fileMappingHandle = buffer.FileMappingHandle; + AssertCondition(fileMappingHandle.IsInvalid == false, "FileMappingHandle of a valid shared buffer should be valid"); + using (Stream stream = buffer.OpenStream()) + { + using (StreamWriter writer = new StreamWriter(stream)) + { + writer.Write(data); + } + } + string additionalDataAsJson = "{\"myBufferType\":\"bufferType1\"}"; + if (forWebView) + { + ((CoreWebView2)sender).PostSharedBufferToScript(buffer, CoreWebView2SharedBufferAccess.ReadOnly, additionalDataAsJson); + } + else + { + ((CoreWebView2Frame)sender).PostSharedBufferToScript(buffer, CoreWebView2SharedBufferAccess.ReadOnly, additionalDataAsJson); + } + } + AssertCondition(fileMappingHandle.IsInvalid == true, "FileMappingHandle of a disposed shared buffer should be invalid"); + // + } + else if (args.TryGetWebMessageAsString() == "SharedBufferDataUpdated") + { + ShowSharedBuffer(); + } + } + + void WebView_FrameCreatedSharedBuffer(object sender, CoreWebView2FrameCreatedEventArgs args) + { + args.Frame.WebMessageReceived += (WebMessageReceivedSender, WebMessageReceivedArgs) => + { + WebView_WebMessageReceivedSharedBuffer(WebMessageReceivedSender, WebMessageReceivedArgs); + }; + } + + void ShowSharedBuffer() + { + using (Stream stream = sharedBuffer.OpenStream()) + { + using (StreamReader reader = new StreamReader(stream)) + { + string text_content = reader.ReadLine(); + MessageBox.Show(text_content, "shared buffer updated"); + } + } + } + // + + // Prompt the user for some script and register it to execute whenever a new page loads. + private async void AddInitializeScriptCmdExecuted(object sender, ExecutedRoutedEventArgs e) { + TextInputDialog dialog = new TextInputDialog( + title: "Add Initialize Script", + description: "Enter the JavaScript code to run as the initialization script that runs before any script in the HTML document.", + // This example script stops child frames from opening new windows. Because + // the initialization script runs before any script in the HTML document, we + // can trust the results of our checks on window.parent and window.top. + defaultInput: "if (window.parent !== window.top) {\r\n" + + " delete window.open;\r\n" + + "}"); + if (dialog.ShowDialog() == true) + { + try + { + string scriptId = await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(dialog.Input.Text); + m_lastInitializeScriptId = scriptId; + MessageBox.Show(this, scriptId, "AddScriptToExecuteOnDocumentCreated Id"); + } + catch (Exception ex) + { + MessageBox.Show(this, ex.ToString(), "AddScriptToExecuteOnDocumentCreated failed"); + } + } + } + + // Prompt the user for an initialization script ID and deregister that script. + private void RemoveInitializeScriptCmdExecuted(object sender, ExecutedRoutedEventArgs e) { + TextInputDialog dialog = new TextInputDialog( + title: "Remove Initialize Script", + description: "Enter the ID created from Add Initialize Script.", + defaultInput: m_lastInitializeScriptId); + if (dialog.ShowDialog() == true) + { + string scriptId = dialog.Input.Text; + // check valid + try + { + Int64 result = Int64.Parse(scriptId); + Int64 lastId = Int64.Parse(m_lastInitializeScriptId); + if (result > lastId) { + MessageBox.Show(this, scriptId, "Invalid ScriptId, should be less or equal than " + m_lastInitializeScriptId); + } else { + webView.CoreWebView2.RemoveScriptToExecuteOnDocumentCreated(scriptId); + if (result == lastId && lastId >= 2) { + m_lastInitializeScriptId = (lastId - 1).ToString(); + } + MessageBox.Show(this, scriptId, "RemoveScriptToExecuteOnDocumentCreated Id"); + } + } + catch (FormatException) { + MessageBox.Show(this, scriptId, "Invalid ScriptId, should be Integer"); + } + } + } + // Prompt the user for the name and parameters of a CDP method, then call it. + private async void CallCdpMethodCmdExecuted(object sender, ExecutedRoutedEventArgs e) + { + TextInputDialog dialog = new TextInputDialog( + title: "Call CDP Method", + description: "Enter the CDP method name to call, followed by a space,\r\n" + + "followed by the parameters in JSON format.", + defaultInput: "Runtime.evaluate {\"expression\":\"alert(\\\"test\\\")\"}" + ); + if (dialog.ShowDialog() == true) + { + string[] words = dialog.Input.Text.Trim().Split(' '); + if (words.Length == 1 && words[0] == "") { + MessageBox.Show(this, "Invalid argument:" + dialog.Input.Text, "CDP Method call failed"); + return; + } + string methodName = words[0]; + string methodParams = (words.Length == 2 ? words[1] : "{}"); + + try + { + string cdpResult = await webView.CoreWebView2.CallDevToolsProtocolMethodAsync(methodName, methodParams); + MessageBox.Show(this, cdpResult, "CDP method call successfully"); + } + catch (Exception ex) + { + MessageBox.Show(this, ex.ToString(), "CDP method call failed"); + } + } + } + + private void OpenDevToolsCmdExecuted(object sender, ExecutedRoutedEventArgs e) + { + try + { + webView.CoreWebView2.OpenDevToolsWindow(); + } + catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Open DevTools Window failed"); + } + } + + private void OpenTaskManagerCmdExecuted(object sender, ExecutedRoutedEventArgs e) + { + try + { + webView.CoreWebView2.OpenTaskManagerWindow(); + } + catch (Exception ex) { + MessageBox.Show(this, ex.ToString(), "Open Task Manager Window failed"); + } + } + + + void CrashBrowserProcessCommandExecuted(object target, ExecutedRoutedEventArgs e) + { + webView.CoreWebView2.Navigate("edge://inducebrowsercrashforrealz"); + } + + void CrashRenderProcessCommandExecuted(object target, ExecutedRoutedEventArgs e) + { + webView.CoreWebView2.Navigate("edge://kill"); + } + + + + void HostObjectsAllowedCommandExecuted(object target, ExecutedRoutedEventArgs e) + { + // + WebViewSettings.AreHostObjectsAllowed = !WebViewSettings.AreHostObjectsAllowed; + // + MessageBox.Show("Access to host objects will be" + (WebViewSettings.AreHostObjectsAllowed ? " allowed " : " denied ") + "after the next navigation."); + } + + void BrowserAcceleratorKeyEnabledCommandExecuted(object target, ExecutedRoutedEventArgs e) + { + // + WebViewSettings.AreBrowserAcceleratorKeysEnabled = !WebViewSettings.AreBrowserAcceleratorKeysEnabled; + // + MessageBox.Show("Browser-specific accelerator keys will be" + (WebViewSettings.AreBrowserAcceleratorKeysEnabled ? " enabled " : " disabled ") + "after the next navigation."); + } + + + async void InjectScriptWithResultCmdExecuted(object target, ExecutedRoutedEventArgs e) + { + // + var dialog = new TextInputDialog( + title: "Inject Script With Result", + description: "Enter some JavaScript to be executed in the context of this page, and get the error info when execution failed", + defaultInput: "" + ); + if (dialog.ShowDialog() == true) { + var result = await webView.CoreWebView2.ExecuteScriptWithResultAsync(dialog.Input.Text); + if (result.Succeeded) + { + MessageBox.Show(this, result.ResultAsJson, "ExecuteScript Json Result"); + int is_success = 0; + string str = ""; + result.TryGetResultAsString(out str, out is_success); + if (is_success == 1) + { + MessageBox.Show(this, str, "ExecuteScript String Result"); + } + else { + MessageBox.Show(this, "Get string failed", "ExecuteScript String Result"); + } + } + else { + var exception = result.Exception; + MessageBox.Show(this, exception.Name, "ExecuteScript Exception Name"); + MessageBox.Show(this, exception.Message, "ExecuteScript Exception Message"); + MessageBox.Show(this, exception.ToJson, "ExecuteScript Exception Detail"); + var location_info = "LineNumber:" + exception.LineNumber + ", ColumnNumber:" + exception.ColumnNumber; + MessageBox.Show(this, location_info, "ExecuteScript Exception Location"); + } + } + // + } + + string NameOfPermissionKind(CoreWebView2PermissionKind kind) + { + switch (kind) + { + case CoreWebView2PermissionKind.Microphone: + return "Microphone"; + case CoreWebView2PermissionKind.Camera: + return "Camera"; + case CoreWebView2PermissionKind.Geolocation: + return "Geolocation"; + case CoreWebView2PermissionKind.Notifications: + return "Notifications"; + case CoreWebView2PermissionKind.OtherSensors: + return "OtherSensors"; + case CoreWebView2PermissionKind.ClipboardRead: + return "ClipboardRead"; + default: + return "Unknown"; + } + } + + // + void WebView_PermissionRequested(object sender, CoreWebView2PermissionRequestedEventArgs args) + { + CoreWebView2Deferral deferral = args.GetDeferral(); + + System.Threading.SynchronizationContext.Current.Post((_) => + { + using (deferral) + { + (string, CoreWebView2PermissionKind, bool) cachedKey = (args.Uri, args.PermissionKind, args.IsUserInitiated); + if (_cachedPermissions.ContainsKey(cachedKey)) + { + args.State = _cachedPermissions[cachedKey] + ? CoreWebView2PermissionState.Allow + : CoreWebView2PermissionState.Deny; + } + else + { + string message = "An iframe has requested device permission for "; + message += NameOfPermissionKind(args.PermissionKind) + " to the website at "; + message += args.Uri + ".\n\nDo you want to grant permission?\n"; + message += args.IsUserInitiated ? "This request came from a user gesture." : "This request did not come from a user gesture."; + var selection = MessageBox.Show( + message, "Permission Request", MessageBoxButton.YesNoCancel); + switch (selection) + { + case MessageBoxResult.Yes: + args.State = CoreWebView2PermissionState.Allow; + break; + case MessageBoxResult.No: + args.State = CoreWebView2PermissionState.Deny; + break; + case MessageBoxResult.Cancel: + args.State = CoreWebView2PermissionState.Default; + break; + } + } + } + }, null); + } + // } } diff --git a/SampleApps/WebView2WpfBrowser/README.md b/SampleApps/WebView2WpfBrowser/README.md index add20dbb..48ed97e1 100644 --- a/SampleApps/WebView2WpfBrowser/README.md +++ b/SampleApps/WebView2WpfBrowser/README.md @@ -24,9 +24,9 @@ The WebView2WpfBrowser is an example of an application that embeds a WebView wit The API Sample showcases a selection of WebView2's event handlers and API methods that allow a WPF application to directly interact with a WebView and vice versa. -If this is your first time using WebView, we recommend first following the [Getting Started](https://learn.microsoft.com/microsoft-edge/webview2/gettingstarted/wpf) guide, which goes over how to create a WebView2 and walks through some basic WebView2 functionality. +If this is your first time using WebView, we recommend first following the [Getting Started](https://docs.microsoft.com/microsoft-edge/webview2/gettingstarted/wpf) guide, which goes over how to create a WebView2 and walks through some basic WebView2 functionality. -To learn more specifics about events and API Handlers in WebView2, you can refer to the [WebView2 Reference Documentation](https://learn.microsoft.com/microsoft-edge/webview2/webview2-api-reference). +To learn more specifics about events and API Handlers in WebView2, you can refer to the [WebView2 Reference Documentation](https://docs.microsoft.com/microsoft-edge/webview2/webview2-api-reference). ## Prerequisites @@ -51,7 +51,7 @@ That's it! Everything should be ready to just launch the app. ## Using Fixed version -If you want to use [fixed version](https://learn.microsoft.com/microsoft-edge/webview2/concepts/distribution#fixed-version-distribution-mode), i.e. Bring-Your-Own (BYO), follow the instructions in MainWindow.xaml and App.xaml. +If you want to use [fixed version](https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#fixed-version-distribution-mode), i.e. Bring-Your-Own (BYO), follow the instructions in MainWindow.xaml and App.xaml. ## Code of Conduct diff --git a/SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj b/SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj index 2ea3c5b3..20670f26 100644 --- a/SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj +++ b/SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj @@ -40,7 +40,22 @@ - + + PreserveNewest + + + + + PreserveNewest + + + + + PreserveNewest + + + + \ No newline at end of file diff --git a/SampleApps/WebView2WpfBrowser/assets/hostObject.html b/SampleApps/WebView2WpfBrowser/assets/hostObject.html new file mode 100644 index 00000000..8188cef3 --- /dev/null +++ b/SampleApps/WebView2WpfBrowser/assets/hostObject.html @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SampleApps/WebView2WpfBrowser/assets/sharedBuffer.html b/SampleApps/WebView2WpfBrowser/assets/sharedBuffer.html new file mode 100644 index 00000000..8e3824a5 --- /dev/null +++ b/SampleApps/WebView2WpfBrowser/assets/sharedBuffer.html @@ -0,0 +1,125 @@ + + + + ScenarioSharedBuffer + + + + + +

Shared Buffer Data

+
+
+
+
+
+
+
+ +
+ + + + diff --git a/SampleApps/WebView2WpfBrowser/assets/webMessages.html b/SampleApps/WebView2WpfBrowser/assets/webMessages.html new file mode 100644 index 00000000..e9e8fd4d --- /dev/null +++ b/SampleApps/WebView2WpfBrowser/assets/webMessages.html @@ -0,0 +1,101 @@ + + + + ScenarioWebMessage + + + + + +

Posting Messages

+

+ Messages can be posted from the host app to this using the + functions ::PostWebMessageAsJson and + ::PostWebMessageAsString. Try using the menu item + "Script->" to send the message {"SetColor":"blue"}. + It should change the text color of this paragraph. +

+ +

Receiving Messages

+

The host app can receive messages by registering an event handler with + ::HandleWebMessage. If you enter text and click + "Send", this will send a message to the host app which will change the text of + the title bar.

+ + + +

Round trip

+

+ The host app can send messages back in response to received messages. If you click + "Get window bounds", the host app will report back the bounds of its window, which will + appear in the text box. +

+
+ + + + +