From a83baa548a33d02ef57d0a69929430fa037b6b41 Mon Sep 17 00:00:00 2001 From: Jessica Chen <55165503+peiche-jessica@users.noreply.github.com> Date: Wed, 22 Jul 2020 17:01:56 -0700 Subject: [PATCH] Move projects to use latest WebView2 SDK 0.9.579-prerelease (#46) * Updated testing instructions and added missing images * Move win32 sample to use latest WebView2 prerelease SDK 0.9.579 * Move wpf sample to use latest WebView2 prerelease SDK 0.9.579 * Updated .gitignore * Move winforms sample to use latest WebView2 prerelease SDK 0.9.579 * Added VSCode debugging setup * Added screenshots that should be included in the previous commit * Improved script debugging attach and removed targeted * Moved WPF to use 0.9.579-prerelease --- .gitignore | 12 +- GettingStartedGuides/.gitignore | 9 - .../WPF_GettingStarted/WPFSample.csproj | 24 +- SampleApps/WebView2APISample/.gitignore | 1 + SampleApps/WebView2APISample/App.cpp | 138 +++------ SampleApps/WebView2APISample/AppWindow.cpp | 103 +++++-- SampleApps/WebView2APISample/AppWindow.h | 14 +- .../WebView2APISample/ControlComponent.cpp | 37 +-- .../WebView2APISample/ControlComponent.h | 1 + SampleApps/WebView2APISample/DpiUtil.cpp | 99 +++++++ SampleApps/WebView2APISample/DpiUtil.h | 22 ++ SampleApps/WebView2APISample/README.md | 10 +- .../ScenarioAuthentication.cpp | 57 ++++ .../ScenarioAuthentication.h | 22 ++ .../ScenarioWebViewEventMonitor.cpp | 206 ++++++++++++- .../ScenarioWebViewEventMonitor.h | 4 + .../ScenarioWebViewEventMonitor.html | 12 +- SampleApps/WebView2APISample/Toolbar.cpp | 181 ++++++++---- SampleApps/WebView2APISample/Toolbar.h | 43 ++- .../WebView2APISample/ViewComponent.cpp | 26 +- SampleApps/WebView2APISample/ViewComponent.h | 24 +- .../WebView2APISample/WebView2APISample.rc | 2 + .../WebView2APISample.vcxproj | 14 +- .../WebView2APISample.vcxproj.filters | 12 + .../documentation/Testing-Instructions.md | 270 ++++++++---------- .../screenshots/debugger-dropdown.png | Bin 0 -> 139625 bytes .../screenshots/new-script-debugging-tool.png | Bin 0 -> 40391 bytes .../screenshots/old-script-debugging-tool.png | Bin 0 -> 90969 bytes .../screenshots/on-add-click-breakpoint.png | Bin 0 -> 22309 bytes .../screenshots/on-add-header-breakpoint.png | Bin 0 -> 18949 bytes .../screenshots/script-debugging-reg-key.png | Bin 0 -> 26042 bytes .../screenshots/vs-javascript-diagnostics.png | Bin 0 -> 90552 bytes .../vs-script-debugging-set-up.png | Bin 0 -> 87982 bytes SampleApps/WebView2APISample/packages.config | 2 +- SampleApps/WebView2APISample/resource.h | 13 +- SampleApps/WebView2APISample/stdafx.h | 4 +- .../WebView2WindowsFormsBrowser/.gitignore | 12 - .../WebView2WindowsFormsBrowser.csproj | 2 +- SampleApps/WebView2WpfBrowser/.gitignore | 12 - SampleApps/WebView2WpfBrowser/MainWindow.xaml | 11 +- .../WebView2WpfBrowser/MainWindow.xaml.cs | 63 +++- .../WebView2WpfBrowser/TextInputDialog.xaml | 27 ++ .../TextInputDialog.xaml.cs | 43 +++ .../WebView2WpfBrowser.csproj | 2 +- 44 files changed, 1090 insertions(+), 444 deletions(-) delete mode 100644 GettingStartedGuides/.gitignore create mode 100644 SampleApps/WebView2APISample/DpiUtil.cpp create mode 100644 SampleApps/WebView2APISample/DpiUtil.h create mode 100644 SampleApps/WebView2APISample/ScenarioAuthentication.cpp create mode 100644 SampleApps/WebView2APISample/ScenarioAuthentication.h create mode 100644 SampleApps/WebView2APISample/documentation/screenshots/debugger-dropdown.png create mode 100644 SampleApps/WebView2APISample/documentation/screenshots/new-script-debugging-tool.png create mode 100644 SampleApps/WebView2APISample/documentation/screenshots/old-script-debugging-tool.png create mode 100644 SampleApps/WebView2APISample/documentation/screenshots/on-add-click-breakpoint.png create mode 100644 SampleApps/WebView2APISample/documentation/screenshots/on-add-header-breakpoint.png create mode 100644 SampleApps/WebView2APISample/documentation/screenshots/script-debugging-reg-key.png create mode 100644 SampleApps/WebView2APISample/documentation/screenshots/vs-javascript-diagnostics.png create mode 100644 SampleApps/WebView2APISample/documentation/screenshots/vs-script-debugging-set-up.png delete mode 100644 SampleApps/WebView2WindowsFormsBrowser/.gitignore delete mode 100644 SampleApps/WebView2WpfBrowser/.gitignore create mode 100644 SampleApps/WebView2WpfBrowser/TextInputDialog.xaml create mode 100644 SampleApps/WebView2WpfBrowser/TextInputDialog.xaml.cs diff --git a/.gitignore b/.gitignore index 5ec0e3a1..55e7c962 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,14 @@ packages/ *.csproj.user -*.vcxproj.user \ No newline at end of file +*.vcxproj.user + +Debug/ +Release/ +x64/ +x86/ +Win32/ +ARM64/ + +bin/ +obj/ \ No newline at end of file diff --git a/GettingStartedGuides/.gitignore b/GettingStartedGuides/.gitignore deleted file mode 100644 index 6ef4b65f..00000000 --- a/GettingStartedGuides/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -Debug/ -Release/ -ARM64/ -x64/ -.vs/ -packages/ - -obj/ -bin/ diff --git a/GettingStartedGuides/WPF_GettingStarted/WPFSample.csproj b/GettingStartedGuides/WPF_GettingStarted/WPFSample.csproj index 425675c0..eb8d9127 100644 --- a/GettingStartedGuides/WPF_GettingStarted/WPFSample.csproj +++ b/GettingStartedGuides/WPF_GettingStarted/WPFSample.csproj @@ -1,13 +1,13 @@ - - - - WinExe - netcoreapp3.1 - true - - - - - - + + + + WinExe + netcoreapp3.1 + true + + + + + + \ No newline at end of file diff --git a/SampleApps/WebView2APISample/.gitignore b/SampleApps/WebView2APISample/.gitignore index 8b00102c..0d2f925a 100644 --- a/SampleApps/WebView2APISample/.gitignore +++ b/SampleApps/WebView2APISample/.gitignore @@ -15,6 +15,7 @@ packages/ !*.sln !*.vcxproj !*.vcxproj.filters +!*.props # Ignore the binary generated version of the resource (.rc) file *.aps diff --git a/SampleApps/WebView2APISample/App.cpp b/SampleApps/WebView2APISample/App.cpp index d3bfe502..44923378 100644 --- a/SampleApps/WebView2APISample/App.cpp +++ b/SampleApps/WebView2APISample/App.cpp @@ -7,11 +7,13 @@ #include "App.h" #include +#include #include #include #include #include #include "AppWindow.h" +#include "DpiUtil.h" HINSTANCE g_hInstance; int g_nCmdShow; @@ -22,6 +24,9 @@ static int RunMessagePump(); static DWORD WINAPI ThreadProc(void* pvParam); static void WaitForOtherThreads(); +#define NEXT_PARAM_CONTAINS(command) \ + _wcsnicmp(nextParam.c_str(), command, ARRAYSIZE(command) - 1) == 0 + int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, @@ -35,130 +40,80 @@ int APIENTRY wWinMain(HINSTANCE hInstance, // override this. DPI_AWARENESS_CONTEXT dpiAwarenessContext = DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2; - // Same but for older OS versions that don't support per-monitor v2 - PROCESS_DPI_AWARENESS oldDpiAwareness = PROCESS_PER_MONITOR_DPI_AWARE; std::wstring appId(L"EBWebView.SampleApp"); std::wstring initialUri(L"https://www.bing.com"); + DWORD creationModeId = IDM_CREATION_MODE_WINDOWED; + if (lpCmdLine && lpCmdLine[0]) { - bool commandLineError = false; - - PWSTR nextParam = lpCmdLine; - - if (nextParam[0] == L'-') + int paramCount = 0; + LPWSTR* params = CommandLineToArgvW(lpCmdLine, ¶mCount); + for (int i = 0; i < paramCount; ++i) { - ++nextParam; - if (nextParam[0] == L'-') + std::wstring nextParam; + if (params[i][0] == L'-') { - ++nextParam; + if (params[i][1] == L'-') + { + nextParam.assign(params[i] + 2); + } + else + { + nextParam.assign(params[i] + 1); + } } - if (_wcsnicmp(nextParam, L"dpiunaware", ARRAYSIZE(L"dpiunaware") - 1) == - 0) + if (NEXT_PARAM_CONTAINS(L"dpiunaware")) { dpiAwarenessContext = DPI_AWARENESS_CONTEXT_UNAWARE; - oldDpiAwareness = PROCESS_DPI_UNAWARE; } - else if (_wcsnicmp(nextParam, L"dpisystemaware", - ARRAYSIZE(L"dpisystemaware") - 1) == 0) + else if (NEXT_PARAM_CONTAINS(L"dpisystemaware")) { dpiAwarenessContext = DPI_AWARENESS_CONTEXT_SYSTEM_AWARE; - oldDpiAwareness = PROCESS_SYSTEM_DPI_AWARE; } - else if (_wcsnicmp(nextParam, L"dpipermonitorawarev2", - ARRAYSIZE(L"dpipermonitorawarev2") - 1) == 0) + else if (NEXT_PARAM_CONTAINS(L"dpipermonitorawarev2")) { dpiAwarenessContext = DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2; - oldDpiAwareness = PROCESS_PER_MONITOR_DPI_AWARE; } - else if (_wcsnicmp(nextParam, L"dpipermonitoraware", - ARRAYSIZE(L"dpipermonitoraware") - 1) == 0) + else if (NEXT_PARAM_CONTAINS(L"dpipermonitoraware")) { dpiAwarenessContext = DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE; - oldDpiAwareness = PROCESS_PER_MONITOR_DPI_AWARE; } - else if (_wcsnicmp(nextParam, L"noinitialnavigation", - ARRAYSIZE(L"noinitialnavigation") - 1) == 0) + else if (NEXT_PARAM_CONTAINS(L"noinitialnavigation")) { initialUri = L""; } - else if (_wcsnicmp(nextParam, L"appid=", - ARRAYSIZE(L"appid=") - 1) == 0) + else if (NEXT_PARAM_CONTAINS(L"appid=")) { - PWSTR appidStart = nextParam + ARRAYSIZE(L"appid="); - size_t len = 0; - while (appidStart[len] && (appidStart[len] != ' ')) - ++len; - appId = std::wstring(appidStart, len); + appId = nextParam.substr(nextParam.find(L'=') + 1); } - else if (_wcsnicmp(nextParam, L"initialUri=", - ARRAYSIZE(L"initialUri=") - 1) == 0) + else if (NEXT_PARAM_CONTAINS(L"initialUri=")) { - PWSTR uriStart = nextParam + ARRAYSIZE(L"initialUri=") - 1; - size_t len = 0; - while (uriStart[len] && (uriStart[len] != ' ')) - ++len; - initialUri = std::wstring(uriStart, len); + initialUri = nextParam.substr(nextParam.find(L'=') + 1); } - else + else if (NEXT_PARAM_CONTAINS(L"creationmode=")) { - // --edge-webview-switches is a supported switch to pass additional - // command line switches to WebView's browser process. - // For example, adding - // --edge-webview-switches="--remote-debugging-port=9222" - // enables remote debugging for webview. - // And adding - // --edge-webview-switches="--auto-open-devtools-for-tabs" - // causes dev tools to open automatically for the WebView. - commandLineError = - (wcsncmp(nextParam, L"edge-webview-switches", - ARRAYSIZE(L"edge-webview-switches") - 1) != 0) && - (wcsncmp(nextParam, L"restore", - ARRAYSIZE(L"restore") - 1) != 0); + nextParam = nextParam.substr(nextParam.find(L'=') + 1); + if (NEXT_PARAM_CONTAINS(L"windowed")) + { + creationModeId = IDM_CREATION_MODE_WINDOWED; + } + else if (NEXT_PARAM_CONTAINS(L"visualdcomp")) + { + creationModeId = IDM_CREATION_MODE_VISUAL_DCOMP; + } + else if (NEXT_PARAM_CONTAINS(L"visualwincomp")) + { + creationModeId = IDM_CREATION_MODE_VISUAL_WINCOMP; + } } } - else - { - commandLineError = true; - } - - if (commandLineError) - { - MessageBox(nullptr, - L"Valid command line " - L"parameters:\n\t-DPIUnaware\n\t-DPISystemAware\n\t-" - L"DPIPerMonitorAware\n\t-DPIPerMonitorAwareV2", - L"Command Line Parameters", MB_OK); - } + LocalFree(params); } - SetCurrentProcessExplicitAppUserModelID(appId.c_str()); - // Call the latest DPI awareness function possible - HMODULE user32 = LoadLibraryA("User32.dll"); - auto func = reinterpret_cast( - GetProcAddress(user32, "SetProcessDpiAwarenessContext")); - if (func) - { - // Windows 10 1703+: SetProcessDpiAwarenessContext - func(dpiAwarenessContext); - } - else { - HMODULE shcore = LoadLibraryA("Shcore.dll"); - auto func = reinterpret_cast( - GetProcAddress(shcore, "SetProcessDpiAwareness")); - if (func) - { - // Windows 8.1+: SetProcessDpiAwareness - func(oldDpiAwareness); - } - else if (dpiAwarenessContext != DPI_AWARENESS_CONTEXT_UNAWARE) - { - // Windows 7+: SetProcessDPIAware - SetProcessDPIAware(); - } - } + DpiUtil::SetProcessDpiAwarenessContext(dpiAwarenessContext); - new AppWindow(IDM_CREATION_MODE_WINDOWED, initialUri); + new AppWindow(creationModeId, initialUri); int retVal = RunMessagePump(); @@ -215,6 +170,7 @@ void CreateNewThread(UINT creationModeId) STACK_SIZE_PARAM_IS_A_RESERVATION, &threadId); s_threads.insert(std::pair(threadId, thread)); } + // This function is the starting point for new threads. It will open a new app window. static DWORD WINAPI ThreadProc(void* pvParam) { diff --git a/SampleApps/WebView2APISample/AppWindow.cpp b/SampleApps/WebView2APISample/AppWindow.cpp index 384506e8..8aac803f 100644 --- a/SampleApps/WebView2APISample/AppWindow.cpp +++ b/SampleApps/WebView2APISample/AppWindow.cpp @@ -15,17 +15,18 @@ #include "App.h" #include "CheckFailure.h" #include "ControlComponent.h" +#include "DpiUtil.h" #include "FileComponent.h" #include "ProcessComponent.h" #include "Resource.h" #include "ScenarioAddHostObject.h" +#include "ScenarioAuthentication.h" #include "ScenarioWebMessage.h" #include "ScenarioWebViewEventMonitor.h" #include "ScriptComponent.h" #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; @@ -67,9 +68,17 @@ AppWindow::AppWindow( SetWindowLongPtr(m_mainWindow, GWLP_USERDATA, (LONG_PTR)this); + //! [TextScaleChanged1] + if (winrt::try_get_activation_factory()) + { + m_uiSettings = winrt::Windows::UI::ViewManagement::UISettings(); + m_uiSettings.TextScaleFactorChanged({ this, &AppWindow::OnTextScaleChanged }); + } + //! [TextScaleChanged1] + if (shouldHaveToolbar) { - m_toolbar.Initialize(m_mainWindow); + m_toolbar.Initialize(this); } UpdateCreationModeMenu(); @@ -149,6 +158,22 @@ bool AppWindow::HandleWindowMessage( } } break; + //! [DPIChanged] + case WM_DPICHANGED: + { + m_toolbar.UpdateDpiAndTextScale(); + RECT* const newWindowSize = reinterpret_cast(lParam); + SetWindowPos(hWnd, + nullptr, + newWindowSize->left, + newWindowSize->top, + newWindowSize->right - newWindowSize->left, + newWindowSize->bottom - newWindowSize->top, + SWP_NOZORDER | SWP_NOACTIVATE); + return true; + } + break; + //! [DPIChanged] case WM_PAINT: { PAINTSTRUCT ps; @@ -276,12 +301,10 @@ bool AppWindow::ExecuteWebViewCommands(WPARAM wParam, LPARAM lParam) WCHAR c_scriptPath[] = L"ScenarioTypeScriptDebugIndex.html"; std::wstring m_scriptUri = GetLocalUri(c_scriptPath); CHECK_FAILURE(m_webView->Navigate(m_scriptUri.c_str())); - return true; } } return false; } - // Handle commands not related to the WebView, which will work even if the WebView // is not currently initialized. bool AppWindow::ExecuteAppCommands(WPARAM wParam, LPARAM lParam) @@ -332,6 +355,9 @@ bool AppWindow::ExecuteAppCommands(WPARAM wParam, LPARAM lParam) case IDM_SET_LANGUAGE: ChangeLanguage(); return true; + case IDM_TOGGLE_AAD_SSO: + ToggleAADSSO(); + return true; } return false; } @@ -349,6 +375,20 @@ void AppWindow::ChangeLanguage() } } +// Toggle AAD SSO enabled +void AppWindow::ToggleAADSSO() +{ + m_AADSSOEnabled = !m_AADSSOEnabled; + MessageBox( + nullptr, + m_AADSSOEnabled ? L"AAD single sign on will be enabled for new WebView " + L"created after all webviews are closed." : + L"AAD single sign on will be disabled for new WebView" + L" created after all webviews are closed.", + L"AAD SSO change", + MB_OK); +} + // Message handler for about dialog. INT_PTR CALLBACK AppWindow::About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { @@ -438,8 +478,10 @@ void AppWindow::InitializeWebView() } } //! [CreateCoreWebView2EnvironmentWithOptions] - auto options = Microsoft::WRL::Make(); - if(!m_language.empty()) + auto options = Microsoft::WRL::Make(); + CHECK_FAILURE(options->put_IsSingleSignOnUsingOSPrimaryAccountEnabled( + m_AADSSOEnabled ? TRUE : FALSE)); + if (!m_language.empty()) CHECK_FAILURE(options->put_Language(m_language.c_str())); HRESULT hr = CreateCoreWebView2EnvironmentWithOptions( subFolder, nullptr, options.Get(), @@ -798,11 +840,11 @@ void AppWindow::CloseWebView(bool cleanupUserDataFolder) // developers specify userDataFolder during WebView environment // creation, they would need to pass in that explicit value here. // For more information about userDataFolder: - // https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-488/webview2-idl#createwebview2environmentwithoptions + // https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-538/webview2-idl#createcorewebview2environmentwithoptions WCHAR userDataFolder[MAX_PATH] = L""; // Obtain the absolute path for relative paths that include "./" or "../" _wfullpath( - userDataFolder, GetLocalPath(L"WebView2APISample.exe.WebView2").c_str(), MAX_PATH); + userDataFolder, GetLocalPath(L".WebView2", true).c_str(), MAX_PATH); std::wstring userDataFolderPath(userDataFolder); std::wstring message = L"Are you sure you want to clean up the user data folder at\n"; @@ -900,19 +942,25 @@ RECT AppWindow::GetWindowBounds() return hwndBounds; } -std::wstring AppWindow::GetLocalPath(std::wstring relativePath) +std::wstring AppWindow::GetLocalPath(std::wstring relativePath, bool keep_exe_path) { WCHAR rawPath[MAX_PATH]; GetModuleFileNameW(g_hInstance, rawPath, MAX_PATH); std::wstring path(rawPath); - - std::size_t index = path.find_last_of(L"\\") + 1; - path.replace(index, path.length(), relativePath); + if (keep_exe_path) + { + path.append(relativePath); + } + else + { + std::size_t index = path.find_last_of(L"\\") + 1; + path.replace(index, path.length(), relativePath); + } return path; } std::wstring AppWindow::GetLocalUri(std::wstring relativePath) { - std::wstring path = GetLocalPath(relativePath); + std::wstring path = GetLocalPath(relativePath, false); wil::com_ptr uri; CHECK_FAILURE(CreateUri(path.c_str(), Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME, 0, &uri)); @@ -934,7 +982,7 @@ void AppWindow::EnterFullScreen() MONITORINFO monitor_info = {sizeof(monitor_info)}; m_hMenu = ::GetMenu(m_mainWindow); ::SetMenu(m_mainWindow, nullptr); - if (GetWindowPlacement(m_mainWindow, &m_previousPlacement) && + if (GetWindowRect(m_mainWindow, &m_previousWindowRect) && GetMonitorInfo( MonitorFromWindow(m_mainWindow, MONITOR_DEFAULTTOPRIMARY), &monitor_info)) { @@ -952,10 +1000,11 @@ void AppWindow::ExitFullScreen() DWORD style = GetWindowLong(m_mainWindow, GWL_STYLE); ::SetMenu(m_mainWindow, m_hMenu); SetWindowLong(m_mainWindow, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); - SetWindowPlacement(m_mainWindow, &m_previousPlacement); SetWindowPos( - m_mainWindow, NULL, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + m_mainWindow, NULL, m_previousWindowRect.left, m_previousWindowRect.top, + m_previousWindowRect.right - m_previousWindowRect.left, + m_previousWindowRect.bottom - m_previousWindowRect.top, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); } // We have our own implementation of DCompositionCreateDevice2 that dynamically @@ -1015,6 +1064,16 @@ HRESULT AppWindow::CreateWinCompCompositor() return hr; } +//! [TextScaleChanged2] +void AppWindow::OnTextScaleChanged( + winrt::Windows::UI::ViewManagement::UISettings const& settings, + winrt::Windows::Foundation::IInspectable const& args) +{ + RunAsync([this] { + m_toolbar.UpdateDpiAndTextScale(); + }); +} +//! [TextScaleChanged2] void AppWindow::UpdateCreationModeMenu() { HMENU hMenu = GetMenu(m_mainWindow); @@ -1025,3 +1084,13 @@ void AppWindow::UpdateCreationModeMenu() m_creationModeId, MF_BYCOMMAND); } + +double AppWindow::GetDpiScale() +{ + return DpiUtil::GetDpiForWindow(m_mainWindow) * 1.0f / USER_DEFAULT_SCREEN_DPI; +} + +double AppWindow::GetTextScale() +{ + return m_uiSettings ? m_uiSettings.TextScaleFactor() : 1.0f; +} diff --git a/SampleApps/WebView2APISample/AppWindow.h b/SampleApps/WebView2APISample/AppWindow.h index f29188d6..145e4c15 100644 --- a/SampleApps/WebView2APISample/AppWindow.h +++ b/SampleApps/WebView2APISample/AppWindow.h @@ -17,6 +17,7 @@ #include #include #include "WebView2APISample_WinCompHelper/WebView2APISample_WinCompHelper.h" +#include class SettingsComponent; @@ -47,6 +48,8 @@ class AppWindow RECT GetWindowBounds(); std::wstring GetLocalUri(std::wstring path); std::function GetAcceleratorKeyFunction(UINT key); + double GetDpiScale(); + double GetTextScale(); void ReinitializeWebView(); @@ -83,7 +86,11 @@ class AppWindow void CloseAppWindow(); void ChangeLanguage(); void UpdateCreationModeMenu(); - std::wstring GetLocalPath(std::wstring path); + void ToggleAADSSO(); + void OnTextScaleChanged( + winrt::Windows::UI::ViewManagement::UISettings const& uiSettings, + winrt::Windows::Foundation::IInspectable const& args); + std::wstring GetLocalPath(std::wstring path, bool keep_exe_path); void DeleteAllComponents(); template std::unique_ptr MoveComponent(); @@ -107,8 +114,10 @@ class AppWindow std::wstring m_language; + bool m_AADSSOEnabled = false; + // Fullscreen related code - WINDOWPLACEMENT m_previousPlacement; + RECT m_previousWindowRect; HMENU m_hMenu; BOOL m_containsFullscreenElement = FALSE; bool m_fullScreenAllowed = true; @@ -122,6 +131,7 @@ class AppWindow wil::com_ptr m_dcompDevice; wil::com_ptr m_wincompHelper; + winrt::Windows::UI::ViewManagement::UISettings m_uiSettings{ nullptr }; }; template void AppWindow::NewComponent(Args&&... args) diff --git a/SampleApps/WebView2APISample/ControlComponent.cpp b/SampleApps/WebView2APISample/ControlComponent.cpp index 4c72fe59..a9c8f1d2 100644 --- a/SampleApps/WebView2APISample/ControlComponent.cpp +++ b/SampleApps/WebView2APISample/ControlComponent.cpp @@ -16,17 +16,16 @@ ControlComponent::ControlComponent(AppWindow* appWindow, Toolbar* toolbar) : m_appWindow(appWindow), m_controller(appWindow->GetWebViewController()), m_webView(appWindow->GetWebView()), m_toolbar(toolbar) { - m_toolbar->SetEnabled(true); - EnableWindow(m_toolbar->backWindow, false); - EnableWindow(m_toolbar->forwardWindow, false); + m_toolbar->SetItemEnabled(Toolbar::Item_AddressBar, true); + m_toolbar->SetItemEnabled(Toolbar::Item_GoButton, true); // Register a handler for the NavigationStarting event. // This handler just enables the Cancel button. CHECK_FAILURE(m_webView->add_NavigationStarting( Callback( [this](ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args) - -> HRESULT { - EnableWindow(m_toolbar->cancelWindow, TRUE); + -> HRESULT { + m_toolbar->SetItemEnabled(Toolbar::Item_CancelButton, true); return S_OK; }) .Get(), @@ -46,7 +45,7 @@ ControlComponent::ControlComponent(AppWindow* appWindow, Toolbar* toolbar) { uri = wil::make_cotaskmem_string(L""); } - SetWindowText(m_toolbar->addressBarWindow, uri.get()); + SetWindowText(GetAddressBar(), uri.get()); return S_OK; }) @@ -64,8 +63,8 @@ ControlComponent::ControlComponent(AppWindow* appWindow, Toolbar* toolbar) BOOL canGoForward; sender->get_CanGoBack(&canGoBack); sender->get_CanGoForward(&canGoForward); - EnableWindow(m_toolbar->backWindow, canGoBack); - EnableWindow(m_toolbar->forwardWindow, canGoForward); + m_toolbar->SetItemEnabled(Toolbar::Item_BackButton, canGoBack); + m_toolbar->SetItemEnabled(Toolbar::Item_ForwardButton, canGoForward); return S_OK; }) @@ -94,7 +93,8 @@ ControlComponent::ControlComponent(AppWindow* appWindow, Toolbar* toolbar) // display its own error page automatically. } } - EnableWindow(m_toolbar->cancelWindow, FALSE); + m_toolbar->SetItemEnabled(Toolbar::Item_CancelButton, false); + m_toolbar->SetItemEnabled(Toolbar::Item_ReloadButton, true); return S_OK; }) .Get(), @@ -164,9 +164,7 @@ ControlComponent::ControlComponent(AppWindow* appWindow, Toolbar* toolbar) //! [MoveFocusRequested] // Replace the window procs on some toolbar elements to customize their behavior - for (auto hwnd : - {m_toolbar->backWindow, m_toolbar->forwardWindow, m_toolbar->reloadWindow, - m_toolbar->cancelWindow, m_toolbar->addressBarWindow, m_toolbar->goWindow}) + for (auto hwnd : m_toolbar->GetItems()) { SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(this)); auto originalWndProc = @@ -311,7 +309,7 @@ bool ControlComponent::HandleChildWindowMessage( } } //! [MoveFocus1] - else if ((wParam == VK_RETURN) && (hWnd == m_toolbar->addressBarWindow)) + else if ((wParam == VK_RETURN) && (hWnd == GetAddressBar())) { // Handle pressing Enter in address bar NavigateToAddressBar(); @@ -350,10 +348,10 @@ bool ControlComponent::HandleChildWindowMessage( //! [Navigate] void ControlComponent::NavigateToAddressBar() { - int length = GetWindowTextLength(m_toolbar->addressBarWindow); + int length = GetWindowTextLength(GetAddressBar()); std::wstring uri(length, 0); PWSTR buffer = const_cast(uri.data()); - GetWindowText(m_toolbar->addressBarWindow, buffer, length + 1); + GetWindowText(GetAddressBar(), buffer, length + 1); HRESULT hr = m_webView->Navigate(uri.c_str()); if (hr == E_INVALIDARG) @@ -429,6 +427,11 @@ ControlComponent::~ControlComponent() SetWindowLongPtr(pair.first, GWLP_WNDPROC, (LONG_PTR)pair.second); } - SetWindowText(m_toolbar->addressBarWindow, L""); - m_toolbar->SetEnabled(false); + SetWindowText(GetAddressBar(), L""); + m_toolbar->DisableAllItems(); +} + +HWND ControlComponent::GetAddressBar() +{ + return m_toolbar->GetItem(Toolbar::Item_AddressBar); } diff --git a/SampleApps/WebView2APISample/ControlComponent.h b/SampleApps/WebView2APISample/ControlComponent.h index bca6603a..5820ea2a 100644 --- a/SampleApps/WebView2APISample/ControlComponent.h +++ b/SampleApps/WebView2APISample/ControlComponent.h @@ -35,6 +35,7 @@ class ControlComponent : public ComponentBase ~ControlComponent() override; private: + HWND GetAddressBar(); AppWindow* m_appWindow; wil::com_ptr m_controller; wil::com_ptr m_webView; diff --git a/SampleApps/WebView2APISample/DpiUtil.cpp b/SampleApps/WebView2APISample/DpiUtil.cpp new file mode 100644 index 00000000..484105a4 --- /dev/null +++ b/SampleApps/WebView2APISample/DpiUtil.cpp @@ -0,0 +1,99 @@ +// 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 "CheckFailure.h" +#include "DpiUtil.h" + +void DpiUtil::SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT dpiAwarenessContext) +{ + // Call the latest DPI awareness function possible + static auto SetProcessDpiAwarenessContextFunc = []() { + return reinterpret_cast( + ::GetProcAddress(GetUser32Module(), "SetProcessDpiAwarenessContext")); + }(); + if (SetProcessDpiAwarenessContextFunc) + { + // Windows 10 1703+: SetProcessDpiAwarenessContext + SetProcessDpiAwarenessContextFunc(dpiAwarenessContext); + } + else + { + static auto SetProcessDpiAwarenessFunc = []() { + return reinterpret_cast( + ::GetProcAddress(GetShcoreModule(), "SetProcessDpiAwareness")); + }(); + if (SetProcessDpiAwarenessFunc) + { + // Windows 8.1+: SetProcessDpiAwareness + SetProcessDpiAwarenessFunc( + ProcessDpiAwarenessFromDpiAwarenessContext(dpiAwarenessContext)); + } + else if (dpiAwarenessContext != DPI_AWARENESS_CONTEXT_UNAWARE) + { + // Windows 7+: SetProcessDPIAware + ::SetProcessDPIAware(); + } + } +} + +int DpiUtil::GetDpiForWindow(HWND window) +{ + static auto GetDpiForMonitorFunc = []() { + return reinterpret_cast( + ::GetProcAddress(GetShcoreModule(), "GetDpiForMonitor")); + }(); + if (GetDpiForMonitorFunc) + { + UINT dpi_x, dpi_y; + HMONITOR monitor = ::MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST); + CHECK_FAILURE(GetDpiForMonitorFunc(monitor, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y)); + return dpi_x; + } + else + { + return GetDeviceCaps(GetDC(nullptr), LOGPIXELSX); + } +} + +HMODULE DpiUtil::GetUser32Module() +{ + static HMODULE user32Module = nullptr; + if (user32Module == nullptr) + { + user32Module = LoadLibraryA("User32.dll"); + } + return user32Module; +} + +HMODULE DpiUtil::GetShcoreModule() +{ + static HMODULE shcoreModule = nullptr; + if (shcoreModule == nullptr) + { + shcoreModule = LoadLibraryA("Shcore.dll"); + } + return shcoreModule; +} + +PROCESS_DPI_AWARENESS DpiUtil::ProcessDpiAwarenessFromDpiAwarenessContext( + DPI_AWARENESS_CONTEXT dpiAwarenessContext) +{ + if (dpiAwarenessContext == DPI_AWARENESS_CONTEXT_UNAWARE || + dpiAwarenessContext == DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED) + { + return PROCESS_DPI_UNAWARE; + } + if (dpiAwarenessContext == DPI_AWARENESS_CONTEXT_SYSTEM_AWARE) + { + return PROCESS_SYSTEM_DPI_AWARE; + } + if (dpiAwarenessContext == DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE || + dpiAwarenessContext == DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) + { + return PROCESS_PER_MONITOR_DPI_AWARE; + } + // All DPI awarenes contexts should be covered above. + FAIL_FAST(); +} diff --git a/SampleApps/WebView2APISample/DpiUtil.h b/SampleApps/WebView2APISample/DpiUtil.h new file mode 100644 index 00000000..c0b3d1a6 --- /dev/null +++ b/SampleApps/WebView2APISample/DpiUtil.h @@ -0,0 +1,22 @@ +// 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 + +class DpiUtil +{ +public: + static void SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT dpiAwarenessContext); + static int GetDpiForWindow(HWND window); + +private: + static HMODULE GetUser32Module(); + static HMODULE GetShcoreModule(); + static PROCESS_DPI_AWARENESS ProcessDpiAwarenessFromDpiAwarenessContext( + DPI_AWARENESS_CONTEXT dpiAwarenessContext); +}; + diff --git a/SampleApps/WebView2APISample/README.md b/SampleApps/WebView2APISample/README.md index 825ad2fb..5e3885ab 100644 --- a/SampleApps/WebView2APISample/README.md +++ b/SampleApps/WebView2APISample/README.md @@ -97,7 +97,7 @@ The section below briefly explains some of the key functions in the Sample App. #### InitializeWebView() -In the AppWindow file, we use the InitializeWebView() function to create the WebView2 environment by using [CreateCoreWebView2EnvironmentWithOptions](https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-488/webview2-idl#createcorewebview2environmentwithoptions). +In the AppWindow file, we use the InitializeWebView() function to create the WebView2 environment by using [CreateCoreWebView2EnvironmentWithOptions](https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-538/webview2-idl#createcorewebview2environmentwithoptions). Once we've created the environment, we create the WebView by using `CreateCoreWebView2Controller`. @@ -155,7 +155,7 @@ This callback function is passed to `CreateCoreWebView2Controller` in `Initializ This function is called within `CreateCoreWebView2Controller`. It sets up some of the event handlers used by the application, and adds them to the WebView. -To read more about event handlers in WebView2, you can refer to this [documentation](https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-488/icorewebview2). +To read more about event handlers in WebView2, you can refer to this [documentation](https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-538/icorewebview2). Below is a code snippet from `RegisterEventHandlers()`, where we set up an event handler for the `NewWindowRequested` event. This event is fired when JavaScript in the webpage calls `window.open()`, and our handler makes a new `AppWindow` and passes the new window's WebView back to the browser so it can return it from the `window.open()` call. Unlike our calls to `CreateCoreWebView2EnvironmentWithOptions` and `CreateCoreWebView2Controller`, instead of providing a method for the callback, we just provide a C++ lambda right then and there. @@ -215,7 +215,7 @@ The text under Posting Messages should now be blue. Here's how it works: -1. In `ScriptComponent.cpp`, we use [PostWebMessageAsJson](https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-488/icorewebview2#postwebmessageasjson) to post user input to the `ScenarioMessage.html` web application. +1. In `ScriptComponent.cpp`, we use [PostWebMessageAsJson](https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-538/icorewebview2#postwebmessageasjson) to post user input to the `ScenarioMessage.html` web application. ```cpp // Prompt the user for some JSON and then post it as a web message. @@ -265,7 +265,7 @@ function SetTitleText() { } ``` -2. Within `ScenarioWebMessage.cpp`, we use [add_WebMessageReceived](https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-488/icorewebview2#add_webmessagereceived) to register the event handler. When we receive the event, after validating the input, we change the title of the App Window. +2. Within `ScenarioWebMessage.cpp`, we use [add_WebMessageReceived](https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-538/icorewebview2#add_webmessagereceived) to register the event handler. When we receive the event, after validating the input, we change the title of the App Window. ```cpp // Setup the web message received event handler before navigating to @@ -312,7 +312,7 @@ function GetWindowBounds() { } ``` -2. Within `ScenarioWebMessage.cpp`, we use [add_WebMessageReceived](https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-488/icorewebview2#add_webmessagereceived) to register the received event handler. After validating the input, the event handler gets window bounds from the App Window. [PostWebMessageAsJson](https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-488/icorewebview2#postwebmessageasjson) sends the bounds to the web application. +2. Within `ScenarioWebMessage.cpp`, we use [add_WebMessageReceived](https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-538/icorewebview2#add_webmessagereceived) to register the received event handler. After validating the input, the event handler gets window bounds from the App Window. [PostWebMessageAsJson](https://docs.microsoft.com/microsoft-edge/webview2/reference/win32/0-9-538/icorewebview2#postwebmessageasjson) sends the bounds to the web application. ```cpp if (message.compare(L"GetWindowBounds") == 0) diff --git a/SampleApps/WebView2APISample/ScenarioAuthentication.cpp b/SampleApps/WebView2APISample/ScenarioAuthentication.cpp new file mode 100644 index 00000000..ed85de14 --- /dev/null +++ b/SampleApps/WebView2APISample/ScenarioAuthentication.cpp @@ -0,0 +1,57 @@ +// 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 "ScenarioAuthentication.h" + +#include "AppWindow.h" +#include "CheckFailure.h" + +using namespace Microsoft::WRL; + +ScenarioAuthentication::ScenarioAuthentication(AppWindow* appWindow) : m_appWindow(appWindow) +{ + MessageBox( + nullptr, L"Authentication scenario:\n Click HTML/NTLM Auth to get Authentication headers", + nullptr, MB_OK); + //! [WebResourceResponseReceived] + wil::com_ptr webviewExperimental; + CHECK_FAILURE(m_appWindow->GetWebView()->QueryInterface(IID_PPV_ARGS(&webviewExperimental))); + CHECK_FAILURE(webviewExperimental->add_WebResourceResponseReceived( + Callback( + [this]( + ICoreWebView2Experimental* sender, + ICoreWebView2ExperimentalWebResourceResponseReceivedEventArgs* args) { + wil::com_ptr request; + CHECK_FAILURE(args->get_Request(&request)); + wil::unique_cotaskmem_string uri; + CHECK_FAILURE(request->get_Uri(&uri)); + if (wcscmp(uri.get(), L"https://authenticationtest.com/HTTPAuth/") == 0) + { + wil::com_ptr requestHeaders; + CHECK_FAILURE(request->get_Headers(&requestHeaders)); + + wil::unique_cotaskmem_string authHeaderValue; + if (requestHeaders->GetHeader(L"Authorization", &authHeaderValue) == S_OK) + { + std::wstring message(L"Authorization: "); + message += authHeaderValue.get(); + MessageBox(nullptr, message.c_str(), nullptr, MB_OK); + m_appWindow->DeleteComponent(this); + } + } + + return S_OK; + }) + .Get(), + &m_webResourceResponseReceivedToken)); + //! [WebResourceResponseReceived] + CHECK_FAILURE(m_appWindow->GetWebView()->Navigate(L"https://authenticationtest.com")); +} + +ScenarioAuthentication::~ScenarioAuthentication() { + wil::com_ptr webviewExperimental; + CHECK_FAILURE(m_appWindow->GetWebView()->QueryInterface(IID_PPV_ARGS(&webviewExperimental))); + CHECK_FAILURE(webviewExperimental->remove_WebResourceResponseReceived(m_webResourceResponseReceivedToken)); +} diff --git a/SampleApps/WebView2APISample/ScenarioAuthentication.h b/SampleApps/WebView2APISample/ScenarioAuthentication.h new file mode 100644 index 00000000..a3dee1d1 --- /dev/null +++ b/SampleApps/WebView2APISample/ScenarioAuthentication.h @@ -0,0 +1,22 @@ +// 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 ScenarioAuthentication : public ComponentBase +{ +public: + ScenarioAuthentication(AppWindow* appWindow); + ~ScenarioAuthentication() override; + +private: + EventRegistrationToken m_webResourceResponseReceivedToken = {}; + AppWindow* m_appWindow = nullptr; +}; diff --git a/SampleApps/WebView2APISample/ScenarioWebViewEventMonitor.cpp b/SampleApps/WebView2APISample/ScenarioWebViewEventMonitor.cpp index 925ddf0b..275e0d39 100644 --- a/SampleApps/WebView2APISample/ScenarioWebViewEventMonitor.cpp +++ b/SampleApps/WebView2APISample/ScenarioWebViewEventMonitor.cpp @@ -4,13 +4,16 @@ #include "stdafx.h" +#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING 1 + #include "AppWindow.h" #include "CheckFailure.h" #include "ScenarioWebViewEventMonitor.h" #include #include #include -#include +#include +#include using namespace Microsoft::WRL; using namespace std; @@ -28,6 +31,7 @@ ScenarioWebViewEventMonitor::ScenarioWebViewEventMonitor(AppWindow* appWindowEve [this]() -> void { InitializeEventView(m_appWindowEventView->GetWebView()); }); + m_webviewEventSourceExperimental = m_webviewEventSource.query(); } ScenarioWebViewEventMonitor::~ScenarioWebViewEventMonitor() @@ -41,6 +45,7 @@ ScenarioWebViewEventMonitor::~ScenarioWebViewEventMonitor() m_webviewEventSource->remove_WebMessageReceived(m_webMessageReceivedToken); m_webviewEventSource->remove_NewWindowRequested(m_newWindowRequestedToken); EnableWebResourceRequestedEvent(false); + EnableWebResourceResponseReceivedEvent(false); m_webviewEventView->remove_WebMessageReceived(m_eventViewWebMessageReceivedToken); } @@ -84,7 +89,43 @@ std::wstring BoolToString(BOOL value) std::wstring EncodeQuote(std::wstring raw) { - return L"\"" + regex_replace(raw, wregex(L"\""), L"\\\"") + L"\""; + std::wstring encoded; + // Allocate 10 more chars to reduce memory re-allocation + // due to adding potential escaping chars. + encoded.reserve(raw.length() + 10); + encoded.push_back(L'"'); + for (int i = 0; i < raw.length(); ++i) + { + // Escape chars as listed in https://tc39.es/ecma262/#sec-json.stringify. + switch (raw[i]) + { + case '\b': + encoded.append(L"\\b"); + break; + case '\f': + encoded.append(L"\\f"); + break; + case '\n': + encoded.append(L"\\n"); + break; + case '\r': + encoded.append(L"\\r"); + break; + case '\t': + encoded.append(L"\\t"); + break; + case '\\': + encoded.append(L"\\\\"); + break; + case '"': + encoded.append(L"\\\""); + break; + default: + encoded.push_back(raw[i]); + } + } + encoded.push_back(L'"'); + return encoded; } //! [HttpRequestHeaderIterator] @@ -116,6 +157,32 @@ std::wstring RequestHeadersToJsonString(ICoreWebView2HttpRequestHeaders* request } //! [HttpRequestHeaderIterator] +std::wstring ResponseHeadersToJsonString(ICoreWebView2HttpResponseHeaders* responseHeaders) +{ + wil::com_ptr iterator; + CHECK_FAILURE(responseHeaders->GetIterator(&iterator)); + BOOL hasCurrent = FALSE; + std::wstring result = L"["; + + while (SUCCEEDED(iterator->get_HasCurrentHeader(&hasCurrent)) && hasCurrent) + { + wil::unique_cotaskmem_string name; + wil::unique_cotaskmem_string value; + + CHECK_FAILURE(iterator->GetCurrentHeader(&name, &value)); + result += EncodeQuote(std::wstring(name.get()) + L": " + value.get()); + + BOOL hasNext = FALSE; + CHECK_FAILURE(iterator->MoveNext(&hasNext)); + if (hasNext) + { + result += L", "; + } + } + + return result + L"]"; +} + std::wstring RequestToJsonString(ICoreWebView2WebResourceRequest* request) { wil::com_ptr content; @@ -142,6 +209,78 @@ std::wstring RequestToJsonString(ICoreWebView2WebResourceRequest* request) return result; } +std::wstring GetPreviewOfContent(IStream* content, bool& readAll) +{ + char buffer[50]; + unsigned long read; + content->Read(buffer, 50U, &read); + readAll = read < 50; + + WCHAR converted[50]; + CHECK_FAILURE(MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, buffer, 50, converted, 50)); + return std::wstring(converted); +} + +std::wstring ResponseToJsonString(ICoreWebView2WebResourceResponse* response) +{ + wil::com_ptr content; + CHECK_FAILURE(response->get_Content(&content)); + wil::com_ptr headers; + CHECK_FAILURE(response->get_Headers(&headers)); + int statusCode; + CHECK_FAILURE(response->get_StatusCode(&statusCode)); + wil::unique_cotaskmem_string reasonPhrase; + CHECK_FAILURE(response->get_ReasonPhrase(&reasonPhrase)); + BOOL containsContentType = FALSE; + headers->Contains(L"Content-Type", &containsContentType); + wil::unique_cotaskmem_string contentType; + bool isBinaryContent = true; + if (containsContentType) + { + headers->GetHeader(L"Content-Type", &contentType); + if (wcsncmp(L"text/", contentType.get(), ARRAYSIZE(L"text/")) == 0) + { + isBinaryContent = false; + } + } + std::wstring result = L"{"; + + result += L"\"content\": "; + if (!content) + { + result += L"null"; + } + else + { + if (isBinaryContent) + { + result += EncodeQuote(L"BINARY_DATA"); + } + else + { + bool readAll = false; + result += EncodeQuote(GetPreviewOfContent(content.get(), readAll)); + if (!readAll) + { + result += L"..."; + } + } + } + result += L", "; + + result += L"\"headers\": " + ResponseHeadersToJsonString(headers.get()) + L", "; + result += L"\"status\": "; + WCHAR statusCodeString[4]; + _itow_s(statusCode, statusCodeString, 4, 10); + result += statusCodeString; + result += L", "; + result += L"\"reason\": " + EncodeQuote(reasonPhrase.get()) + L" "; + + result += L"}"; + + return result; +} + std::wstring WebViewPropertiesToJsonString(ICoreWebView2* webview) { wil::unique_cotaskmem_string documentTitle; @@ -157,6 +296,51 @@ std::wstring WebViewPropertiesToJsonString(ICoreWebView2* webview) return result; } +void ScenarioWebViewEventMonitor::EnableWebResourceResponseReceivedEvent(bool enable) { + if (!enable && m_webResourceResponseReceivedToken.value != 0) + { + m_webviewEventSourceExperimental->remove_WebResourceResponseReceived(m_webResourceResponseReceivedToken); + m_webResourceResponseReceivedToken.value = 0; + } + else if (enable && m_webResourceResponseReceivedToken.value == 0) + { + m_webviewEventSourceExperimental->add_WebResourceResponseReceived( + Callback( + [this](ICoreWebView2Experimental* webview, ICoreWebView2ExperimentalWebResourceResponseReceivedEventArgs* args) + -> HRESULT { + wil::com_ptr webResourceRequest; + CHECK_FAILURE(args->get_Request(&webResourceRequest)); + wil::com_ptr webResourceResponse; + CHECK_FAILURE(args->get_Response(&webResourceResponse)); + //! [PopulateResponseContent] + args->PopulateResponseContent( + Callback< + ICoreWebView2ExperimentalWebResourceResponseReceivedEventArgsPopulateResponseContentCompletedHandler>( + [this, webResourceRequest, webResourceResponse](HRESULT result) { + std::wstring message = + L"{ \"kind\": \"event\", \"name\": " + L"\"WebResourceResponseReceived\", \"args\": {" + L"\"request\": " + + RequestToJsonString(webResourceRequest.get()) + + L", " + L"\"response\": " + + ResponseToJsonString(webResourceResponse.get()) + L"}"; + + message += + WebViewPropertiesToJsonString(m_webviewEventSource.get()); + message += L"}"; + PostEventMessage(message); + return S_OK; + }) + .Get()); + //! [PopulateResponseContent] + return S_OK; + }) + .Get(), + &m_webResourceResponseReceivedToken); + } +} + void ScenarioWebViewEventMonitor::EnableWebResourceRequestedEvent(bool enable) { if (!enable && m_webResourceRequestedToken.value != 0) @@ -213,11 +397,19 @@ void ScenarioWebViewEventMonitor::InitializeEventView(ICoreWebView2* webviewEven { EnableWebResourceRequestedEvent(true); } - else if ( - wcscmp(webMessageAsString.get(), L"webResourceRequested,off") == 0) + else if (wcscmp(webMessageAsString.get(), L"webResourceRequested,off") == 0) { EnableWebResourceRequestedEvent(false); } + else if (wcscmp(webMessageAsString.get(), L"webResourceResponseReceived,on") == 0) + { + EnableWebResourceResponseReceivedEvent(true); + } + else if ( + wcscmp(webMessageAsString.get(), L"webResourceResponseReceived,off") == 0) + { + EnableWebResourceResponseReceivedEvent(false); + } } } @@ -465,5 +657,9 @@ void ScenarioWebViewEventMonitor::InitializeEventView(ICoreWebView2* webviewEven void ScenarioWebViewEventMonitor::PostEventMessage(std::wstring message) { - m_webviewEventView->PostWebMessageAsJson(message.c_str()); + HRESULT hr = m_webviewEventView->PostWebMessageAsJson(message.c_str()); + if (FAILED(hr)) + { + ShowFailure(hr, L"PostWebMessageAsJson failed:\n" + message); + } } diff --git a/SampleApps/WebView2APISample/ScenarioWebViewEventMonitor.h b/SampleApps/WebView2APISample/ScenarioWebViewEventMonitor.h index 9695c611..17ec8d91 100644 --- a/SampleApps/WebView2APISample/ScenarioWebViewEventMonitor.h +++ b/SampleApps/WebView2APISample/ScenarioWebViewEventMonitor.h @@ -25,6 +25,8 @@ class ScenarioWebViewEventMonitor : public ComponentBase // Because WebResourceRequested fires so much more often than // all other events, we default to it off and it is configurable. void EnableWebResourceRequestedEvent(bool enable); + + void EnableWebResourceResponseReceivedEvent(bool enable); // Send information about an event to the event view. void PostEventMessage(std::wstring messageAsJson); @@ -37,6 +39,7 @@ class ScenarioWebViewEventMonitor : public ComponentBase // The event source objects fire the events. AppWindow* m_appWindowEventSource; wil::com_ptr m_webviewEventSource; + wil::com_ptr m_webviewEventSourceExperimental; // The events we register on the event source EventRegistrationToken m_frameNavigationStartingToken = {}; @@ -49,6 +52,7 @@ class ScenarioWebViewEventMonitor : public ComponentBase EventRegistrationToken m_webMessageReceivedToken = {}; EventRegistrationToken m_webResourceRequestedToken = {}; EventRegistrationToken m_newWindowRequestedToken = {}; + EventRegistrationToken m_webResourceResponseReceivedToken = {}; // This event is registered with the event viewer so they // can communicate back to us for toggling the WebResourceRequested diff --git a/SampleApps/WebView2APISample/ScenarioWebViewEventMonitor.html b/SampleApps/WebView2APISample/ScenarioWebViewEventMonitor.html index 345fb2c4..43d5983c 100644 --- a/SampleApps/WebView2APISample/ScenarioWebViewEventMonitor.html +++ b/SampleApps/WebView2APISample/ScenarioWebViewEventMonitor.html @@ -47,7 +47,8 @@

WebView Event Monitor

- + +