From 852c4b316b2e01b26969ca8fed43d3450ee93d45 Mon Sep 17 00:00:00 2001 From: WebView2 Github Bot Date: Tue, 23 Jul 2024 17:18:18 +0000 Subject: [PATCH 1/2] Updates for Win32, WPF, WinForms, UWP and WinUI3 sample apps from 128.0.2730.0 --- SampleApps/WebView2APISample/AppWindow.cpp | 15 + SampleApps/WebView2APISample/AppWindow.h | 15 +- .../WebView2APISample/ControlComponent.cpp | 8 +- .../ScenarioNotificationReceived.cpp | 34 +-- .../ScenarioNotificationReceived.h | 7 +- .../WebView2APISample/ScenarioSaveAs.cpp | 25 +- SampleApps/WebView2APISample/ScenarioSaveAs.h | 2 +- .../ScenarioThrottlingControl.cpp | 279 ++++++++++++++++++ .../ScenarioThrottlingControl.h | 38 +++ .../WebView2APISample/SettingsComponent.cpp | 2 +- SampleApps/WebView2APISample/Toolbar.cpp | 13 +- SampleApps/WebView2APISample/Toolbar.h | 2 + .../WebView2APISample/WebView2APISample.rc | 1 + .../WebView2APISample.vcxproj | 4 +- .../assets/ScenarioThrottlingControl.js | 16 +- .../ScenarioThrottlingControlMonitor.html | 6 +- SampleApps/WebView2APISample/resource.h | 1 + SampleApps/WebView2WpfBrowser/MainWindow.xaml | 9 + .../WebView2WpfBrowser/MainWindow.xaml.cs | 201 ++++++++++++- .../WebView2WpfBrowser/SaveAsDialog.xaml.cs | 2 - 20 files changed, 607 insertions(+), 73 deletions(-) diff --git a/SampleApps/WebView2APISample/AppWindow.cpp b/SampleApps/WebView2APISample/AppWindow.cpp index 3b062e75..be1b8e05 100644 --- a/SampleApps/WebView2APISample/AppWindow.cpp +++ b/SampleApps/WebView2APISample/AppWindow.cpp @@ -48,6 +48,7 @@ #include "ScenarioScreenCapture.h" #include "ScenarioSharedBuffer.h" #include "ScenarioSharedWorkerWRR.h" +#include "ScenarioThrottlingControl.h" #include "ScenarioFileTypePolicy.h" #include "ScenarioVirtualHostMappingForPopUpWindow.h" #include "ScenarioVirtualHostMappingForSW.h" @@ -426,6 +427,7 @@ bool AppWindow::HandleWindowMessage( break; //! [RestartManager] case WM_KEYDOWN: + case WM_SYSKEYDOWN: { // If bit 30 is set, it means the WM_KEYDOWN message is autorepeated. // We want to ignore it in that case. @@ -667,6 +669,11 @@ bool AppWindow::ExecuteWebViewCommands(WPARAM wParam, LPARAM lParam) NewComponent(this); return true; } + case IDM_SCENARIO_THROTTLING_CONTROL: + { + NewComponent(this); + return true; + } case IDM_SCENARIO_SCREEN_CAPTURE: { NewComponent(this); @@ -1249,6 +1256,14 @@ std::function AppWindow::GetAcceleratorKeyFunction(UINT key) return [this] { CloseWebView(); }; } } + if (GetKeyState(VK_MENU) < 0) // VK_MENU == Alt key + { + switch (key) + { + case 'D': // Alt+D focuses and selects the address bar, like the browser. + return [this] { m_toolbar.SelectAddressBar(); }; + } + } return nullptr; } diff --git a/SampleApps/WebView2APISample/AppWindow.h b/SampleApps/WebView2APISample/AppWindow.h index d37a1de1..d1fe92a4 100644 --- a/SampleApps/WebView2APISample/AppWindow.h +++ b/SampleApps/WebView2APISample/AppWindow.h @@ -87,10 +87,15 @@ class AppWindow { public: AppWindow( - UINT creationModeId, const WebViewCreateOption& opt, - const std::wstring& initialUri = L"", const std::wstring& userDataFolderParam = L"", - bool isMainWindow = false, std::function webviewCreatedCallback = nullptr, - bool customWindowRect = false, RECT windowRect = {0}, bool shouldHaveToolbar = true, + UINT creationModeId, + const WebViewCreateOption& opt, + const std::wstring& initialUri = L"", + const std::wstring& userDataFolderParam = L"", + bool isMainWindow = false, + std::function webviewCreatedCallback = nullptr, + bool customWindowRect = false, + RECT windowRect = {0}, + bool shouldHaveToolbar = true, bool isPopup = false); ~AppWindow(); @@ -210,7 +215,6 @@ class AppWindow SamplePrintSettings GetSelectedPrinterPrintSettings(std::wstring printerName); bool PrintToPdfStream(); void ToggleTrackingPrevention(); - std::wstring GetLocalPath(std::wstring path, bool keep_exe_path); void DeleteAllComponents(); @@ -273,7 +277,6 @@ class AppWindow bool m_isPopupWindow = false; void EnterFullScreen(); void ExitFullScreen(); - // Compositor creation helper methods HRESULT DCompositionCreateDevice2(IUnknown* renderingDevice, REFIID riid, void** ppv); HRESULT TryCreateDispatcherQueue(); diff --git a/SampleApps/WebView2APISample/ControlComponent.cpp b/SampleApps/WebView2APISample/ControlComponent.cpp index cc463df6..9633c49b 100644 --- a/SampleApps/WebView2APISample/ControlComponent.cpp +++ b/SampleApps/WebView2APISample/ControlComponent.cpp @@ -291,7 +291,7 @@ bool ControlComponent::HandleChildWindowMessage( // If not calling IsDialogMessage to handle tab traversal automatically, // detect tab traversal and cycle focus through address bar, go button, and // elements in WebView. - if (message == WM_KEYDOWN) + if (message == WM_KEYDOWN || message == WM_SYSKEYDOWN) { //! [MoveFocus1] if (wParam == VK_TAB) @@ -320,6 +320,12 @@ bool ControlComponent::HandleChildWindowMessage( NavigateToAddressBar(); return true; } + // Ctrl+A is SelectAll + else if ((GetKeyState(VK_CONTROL) < 0) && ((UINT)wParam == 'A')) + { + m_toolbar->SelectAll(); + return true; + } else { // If bit 30 is set, it means the WM_KEYDOWN message is autorepeated. diff --git a/SampleApps/WebView2APISample/ScenarioNotificationReceived.cpp b/SampleApps/WebView2APISample/ScenarioNotificationReceived.cpp index 940d5625..8d15ce7a 100644 --- a/SampleApps/WebView2APISample/ScenarioNotificationReceived.cpp +++ b/SampleApps/WebView2APISample/ScenarioNotificationReceived.cpp @@ -24,16 +24,15 @@ ScenarioNotificationReceived::ScenarioNotificationReceived(AppWindow* appWindow) : m_appWindow(appWindow), m_webView(appWindow->GetWebView()) { m_sampleUri = m_appWindow->GetLocalUri(c_samplePath); - m_webView2Experimental22 = m_webView.try_query(); - if (!m_webView2Experimental22) + m_webView2_24 = m_webView.try_query(); + if (!m_webView2_24) return; //! [NotificationReceived] // Register a handler for the NotificationReceived event. - CHECK_FAILURE(m_webView2Experimental22->add_NotificationReceived( - Callback( - [this]( - ICoreWebView2* sender, - ICoreWebView2ExperimentalNotificationReceivedEventArgs* args) -> HRESULT + CHECK_FAILURE(m_webView2_24->add_NotificationReceived( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2NotificationReceivedEventArgs* args) + -> HRESULT { // Block notifications from specific URIs and set Handled to // true so the the default notification UI will not be @@ -45,14 +44,13 @@ ScenarioNotificationReceived::ScenarioNotificationReceived(AppWindow* appWindow) wil::unique_cotaskmem_string origin; CHECK_FAILURE(args->get_SenderOrigin(&origin)); std::wstring originString = origin.get(); - Microsoft::WRL::ComPtr notification; + Microsoft::WRL::ComPtr notification; CHECK_FAILURE(args->get_Notification(¬ification)); notification->add_CloseRequested( - Callback( + Callback( [this, &sender]( - ICoreWebView2ExperimentalNotification* notification, - IUnknown* args) -> HRESULT + ICoreWebView2Notification* notification, IUnknown* args) -> HRESULT { // Remove the notification from the list of active // notifications. @@ -64,8 +62,8 @@ ScenarioNotificationReceived::ScenarioNotificationReceived(AppWindow* appWindow) m_appWindow->RunAsync( [this, - notificationCom = wil::make_com_ptr( - notification.Get()), + notificationCom = + wil::make_com_ptr(notification.Get()), deferral, originString]() { ShowNotification(notificationCom.get(), originString); @@ -95,7 +93,7 @@ bool ScenarioNotificationReceived::HandleWindowMessage( } void ScenarioNotificationReceived::ShowNotification( - ICoreWebView2ExperimentalNotification* notification, std::wstring origin) + ICoreWebView2Notification* notification, std::wstring origin) { ICoreWebView2* webView = m_webView.get(); wil::unique_cotaskmem_string title; @@ -139,8 +137,7 @@ void ScenarioNotificationReceived::ShowNotification( (response == IDOK) ? notification->ReportClicked() : notification->ReportClosed(); } -void ScenarioNotificationReceived::RemoveNotification( - ICoreWebView2ExperimentalNotification* notification) +void ScenarioNotificationReceived::RemoveNotification(ICoreWebView2Notification* notification) { // Close custom notification. @@ -155,9 +152,8 @@ void ScenarioNotificationReceived::NavigateToNotificationPage() ScenarioNotificationReceived::~ScenarioNotificationReceived() { - if (m_webView2Experimental22) + if (m_webView2_24) { - CHECK_FAILURE( - m_webView2Experimental22->remove_NotificationReceived(m_notificationReceivedToken)); + CHECK_FAILURE(m_webView2_24->remove_NotificationReceived(m_notificationReceivedToken)); } } diff --git a/SampleApps/WebView2APISample/ScenarioNotificationReceived.h b/SampleApps/WebView2APISample/ScenarioNotificationReceived.h index c814a5a9..de113afe 100644 --- a/SampleApps/WebView2APISample/ScenarioNotificationReceived.h +++ b/SampleApps/WebView2APISample/ScenarioNotificationReceived.h @@ -21,13 +21,12 @@ class ScenarioNotificationReceived : public ComponentBase private: void NavigateToNotificationPage(); - void ShowNotification( - ICoreWebView2ExperimentalNotification* notification, std::wstring origin); - void RemoveNotification(ICoreWebView2ExperimentalNotification* notification); + void ShowNotification(ICoreWebView2Notification* notification, std::wstring origin); + void RemoveNotification(ICoreWebView2Notification* notification); AppWindow* m_appWindow = nullptr; wil::com_ptr m_webView; - wil::com_ptr m_webView2Experimental22; + wil::com_ptr m_webView2_24; std::wstring m_sampleUri; EventRegistrationToken m_notificationReceivedToken = {}; EventRegistrationToken m_notificationCloseRequestedToken = {}; diff --git a/SampleApps/WebView2APISample/ScenarioSaveAs.cpp b/SampleApps/WebView2APISample/ScenarioSaveAs.cpp index 9fb562b3..8db6f5ef 100644 --- a/SampleApps/WebView2APISample/ScenarioSaveAs.cpp +++ b/SampleApps/WebView2APISample/ScenarioSaveAs.cpp @@ -20,7 +20,7 @@ ScenarioSaveAs::ScenarioSaveAs(AppWindow* appWindow) { if (m_webView) { - m_webView2Experimental25 = m_webView.try_query(); + m_webView2_25 = m_webView.try_query(); } } @@ -39,17 +39,16 @@ std::array saveAsUIResultString{ // This example hides the default save as dialog and shows a customized dialog. bool ScenarioSaveAs::ToggleSilent() { - if (!m_webView2Experimental25) + if (!m_webView2_25) return false; m_silentSaveAs = !m_silentSaveAs; if (m_silentSaveAs && m_saveAsUIShowingToken.value == 0) { // Register a handler for the `SaveAsUIShowing` event. - m_webView2Experimental25->add_SaveAsUIShowing( - Callback( - [this]( - ICoreWebView2* sender, - ICoreWebView2ExperimentalSaveAsUIShowingEventArgs* args) -> HRESULT + m_webView2_25->add_SaveAsUIShowing( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2SaveAsUIShowingEventArgs* args) + -> HRESULT { // Hide the system default save as dialog. CHECK_FAILURE(args->put_SuppressDefaultDialog(TRUE)); @@ -111,7 +110,7 @@ bool ScenarioSaveAs::ToggleSilent() else { // Unregister the handler for the `SaveAsUIShowing` event. - m_webView2Experimental25->remove_SaveAsUIShowing(m_saveAsUIShowingToken); + m_webView2_25->remove_SaveAsUIShowing(m_saveAsUIShowingToken); m_saveAsUIShowingToken.value = 0; } MessageBox( @@ -126,10 +125,10 @@ bool ScenarioSaveAs::ToggleSilent() // Call ShowSaveAsUI method to trigger the programmatic save as. bool ScenarioSaveAs::ProgrammaticSaveAs() { - if (!m_webView2Experimental25) + if (!m_webView2_25) return false; - m_webView2Experimental25->ShowSaveAsUI( - Callback( + m_webView2_25->ShowSaveAsUI( + Callback( [this](HRESULT errorCode, COREWEBVIEW2_SAVE_AS_UI_RESULT result) -> HRESULT { // Show ShowSaveAsUI returned result, optional. @@ -165,9 +164,9 @@ bool ScenarioSaveAs::HandleWindowMessage( ScenarioSaveAs::~ScenarioSaveAs() { - if (m_webView2Experimental25) + if (m_webView2_25) { - m_webView2Experimental25->remove_SaveAsUIShowing(m_saveAsUIShowingToken); + m_webView2_25->remove_SaveAsUIShowing(m_saveAsUIShowingToken); } } diff --git a/SampleApps/WebView2APISample/ScenarioSaveAs.h b/SampleApps/WebView2APISample/ScenarioSaveAs.h index 04b1aadb..ca9726cc 100644 --- a/SampleApps/WebView2APISample/ScenarioSaveAs.h +++ b/SampleApps/WebView2APISample/ScenarioSaveAs.h @@ -22,7 +22,7 @@ class ScenarioSaveAs : public ComponentBase ~ScenarioSaveAs() override; AppWindow* m_appWindow = nullptr; wil::com_ptr m_webView; - wil::com_ptr m_webView2Experimental25; + wil::com_ptr m_webView2_25; EventRegistrationToken m_saveAsUIShowingToken = {}; bool m_silentSaveAs = false; }; diff --git a/SampleApps/WebView2APISample/ScenarioThrottlingControl.cpp b/SampleApps/WebView2APISample/ScenarioThrottlingControl.cpp index d79cb3e6..8e75cf7e 100644 --- a/SampleApps/WebView2APISample/ScenarioThrottlingControl.cpp +++ b/SampleApps/WebView2APISample/ScenarioThrottlingControl.cpp @@ -10,3 +10,282 @@ #include "ScriptComponent.h" using namespace Microsoft::WRL; + +ScenarioThrottlingControl::ScenarioThrottlingControl(AppWindow* appWindow) + : m_appWindow(appWindow), m_webview(appWindow->GetWebView()) +{ +#pragma region init_monitor + m_appWindow->EnableHandlingNewWindowRequest(false); + CHECK_FAILURE(m_webview->add_NewWindowRequested( + Callback( + [this, + appWindow](ICoreWebView2* sender, ICoreWebView2NewWindowRequestedEventArgs* args) + { + wil::com_ptr deferral; + CHECK_FAILURE(args->GetDeferral(&deferral)); + + auto initCallback = [args, deferral, this]() + { + m_monitorWebview = m_monitorAppWindow->GetWebView(); + CHECK_FAILURE(args->put_NewWindow(m_monitorAppWindow->GetWebView())); + CHECK_FAILURE(args->put_Handled(TRUE)); + + CHECK_FAILURE(m_monitorWebview->add_WebMessageReceived( + Microsoft::WRL::Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2WebMessageReceivedEventArgs* args) + { + WebViewMessageReceived(args); + return S_OK; + }) + .Get(), + &m_webMessageReceivedToken)); + + CHECK_FAILURE(deferral->Complete()); + }; + + // passing "none" as uri as its a noinitialnavigation + m_monitorAppWindow = new AppWindow( + IDM_CREATION_MODE_WINDOWED, appWindow->GetWebViewOption(), L"none", + appWindow->GetUserDataFolder(), false /* isMainWindow */, + initCallback /* webviewCreatedCallback */, true /* customWindowRect */, + {100, 100, 1720, 1360}, false /* shouldHaveToolbar */, true /* isPopup */); + + m_monitorAppWindow->SetOnAppWindowClosing( + [&] { m_appWindow->DeleteComponent(this); }); + + return S_OK; + }) + .Get(), + nullptr)); +#pragma endregion init_monitor + +#pragma region init_target + // 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_targetUri) + { + m_appWindow->DeleteComponent(this); + } + return S_OK; + }) + .Get(), + &m_contentLoadingToken)); + + // While actual WebView creation in this sample app is outside the scope of + // this component, calling these functions here is equivalent to calling + // them upon WebView creation for the purposes of this feature. In a real + // application, you would usually call these after WebView is created and + // before the first navigation. + OnWebViewCreated(); + SetupIsolatedFramesHandler(); + + m_targetUri = m_appWindow->GetLocalUri(std::wstring(L"ScenarioThrottlingControl.html")); + m_webview->Navigate(m_targetUri.c_str()); +#pragma endregion init_target +} + +// App is responsible for calling this function. An app would usually call this +// function from within the callback passed to +// CreateCoreWebView2Controller(WithOptions). +void ScenarioThrottlingControl::OnWebViewCreated() +{ + wil::com_ptr settings; + m_webview->get_Settings(&settings); + auto settings9 = settings.try_query(); + + // Store the default values from the WebView2 Runtime so we can restore them + // later. + CHECK_FAILURE(settings9->get_PreferredForegroundTimerWakeIntervalInMilliseconds( + &m_defaultIntervalForeground)); + CHECK_FAILURE(settings9->get_PreferredBackgroundTimerWakeIntervalInMilliseconds( + &m_defaultIntervalBackground)); + CHECK_FAILURE(settings9->get_PreferredIntensiveTimerWakeIntervalInMilliseconds( + &m_defaultIntervalIntensive)); + CHECK_FAILURE(settings9->get_PreferredOverrideTimerWakeIntervalInMilliseconds( + &m_defaultIntervalOverride)); +} + +// The primary use-case here is an app embedding 3rd party content and wanting +// to be able to independently limit the performance impact of it. Generally, +// that's something like "low battery, throttle more" or "giving the frame N +// seconds to run some logic, throttle less". +void ScenarioThrottlingControl::SetupIsolatedFramesHandler() +{ + auto webview4 = m_webview.try_query(); + CHECK_FEATURE_RETURN_EMPTY(webview4); + + // You can use the frame properties to determine whether it should be + // marked to be throttled separately from main frame. + CHECK_FAILURE(webview4->add_FrameCreated( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args) -> HRESULT + { + wil::com_ptr webviewFrame; + CHECK_FAILURE(args->get_Frame(&webviewFrame)); + + auto webviewExperimentalFrame7 = + webviewFrame.try_query(); + CHECK_FEATURE_RETURN_HRESULT(webviewExperimentalFrame7); + + wil::unique_cotaskmem_string name; + CHECK_FAILURE(webviewFrame->get_Name(&name)); + if (wcscmp(name.get(), L"untrusted") == 0) + { + CHECK_FAILURE( + webviewExperimentalFrame7->put_UseOverrideTimerWakeInterval(TRUE)); + } + + return S_OK; + }) + .Get(), + &m_frameCreatedToken)); + + wil::com_ptr settings; + m_webview->get_Settings(&settings); + auto settings9 = settings.try_query(); + + // Restrict frames selected by the above callback to always match the default + // timer interval for background frames. + CHECK_FAILURE(settings9->put_PreferredOverrideTimerWakeIntervalInMilliseconds( + m_defaultIntervalBackground)); +} + +void ScenarioThrottlingControl::WebViewMessageReceived( + ICoreWebView2WebMessageReceivedEventArgs* args) +{ + // received command from monitor, handle + wil::unique_cotaskmem_string json; + CHECK_FAILURE(args->get_WebMessageAsJson(&json)); + + auto command = GetJSONStringField(json.get(), L"command"); + if (command.compare(L"set-interval") == 0) + { + auto category = GetJSONStringField(json.get(), L"priority"); + auto interval = GetJSONStringField(json.get(), L"intervalMs"); + + wil::com_ptr settings; + m_webview->get_Settings(&settings); + auto settings9 = settings.try_query(); + CHECK_FEATURE_RETURN_EMPTY(settings9); + + if (category.compare(L"foreground") == 0) + { + CHECK_FAILURE(settings9->put_PreferredForegroundTimerWakeIntervalInMilliseconds( + std::stoul(interval))); + } + else if (category.compare(L"background") == 0) + { + CHECK_FAILURE(settings9->put_PreferredBackgroundTimerWakeIntervalInMilliseconds( + std::stoul(interval))); + } + else if (category.compare(L"untrusted") == 0) + { + CHECK_FAILURE(settings9->put_PreferredOverrideTimerWakeIntervalInMilliseconds( + std::stoul(interval))); + } + } + else if (command.compare(L"toggle-visibility") == 0) + { + BOOL visible; + m_appWindow->GetWebViewController()->get_IsVisible(&visible); + m_appWindow->GetWebViewController()->put_IsVisible(!visible); + } + else if (command.compare(L"scenario") == 0) + { + auto label = GetJSONStringField(json.get(), L"label"); + if (label.compare(L"interaction-throttle") == 0) + { + OnNoUserInteraction(); + } + else if (label.compare(L"interaction-reset") == 0) + { + OnUserInteraction(); + } + else if (label.compare(L"hidden-unthrottle") == 0) + { + HideWebView(); + } + else if (label.compare(L"hidden-reset") == 0) + { + ShowWebView(); + } + } +} + +// This sample app calls this method when receiving a simulated event from its +// control monitor, but your app can decide how and when to go into this state. +void ScenarioThrottlingControl::OnNoUserInteraction() +{ + wil::com_ptr settings; + m_webview->get_Settings(&settings); + auto settings9 = settings.try_query(); + CHECK_FEATURE_RETURN_EMPTY(settings9); + + // User is not interactive, keep webview visible but throttle foreground + // timers to 500ms. + CHECK_FAILURE(settings9->put_PreferredForegroundTimerWakeIntervalInMilliseconds(500)); +} + +void ScenarioThrottlingControl::OnUserInteraction() +{ + wil::com_ptr settings; + m_webview->get_Settings(&settings); + auto settings9 = settings.try_query(); + CHECK_FEATURE_RETURN_EMPTY(settings9); + + // User is interactive again, set foreground timer interval back to its + // default value. + CHECK_FAILURE(settings9->put_PreferredForegroundTimerWakeIntervalInMilliseconds( + m_defaultIntervalForeground)); +} + +// Prepares the WebView to go into hidden mode with no background timer +// throttling. +void ScenarioThrottlingControl::HideWebView() +{ + wil::com_ptr settings; + m_webview->get_Settings(&settings); + auto settings9 = settings.try_query(); + CHECK_FEATURE_RETURN_EMPTY(settings9); + + // This WebView2 will remain hidden but needs to keep running timers. + // Unthrottle background timers. + CHECK_FAILURE(settings9->put_PreferredBackgroundTimerWakeIntervalInMilliseconds(0)); + // Effectively disable intensive throttling by overriding its timer interval. + CHECK_FAILURE(settings9->put_PreferredIntensiveTimerWakeIntervalInMilliseconds(0)); + + CHECK_FAILURE(m_appWindow->GetWebViewController()->put_IsVisible(FALSE)); +} + +// Shows the WebView and restores default throttling behavior. +void ScenarioThrottlingControl::ShowWebView() +{ + CHECK_FAILURE(m_appWindow->GetWebViewController()->put_IsVisible(TRUE)); + + wil::com_ptr settings; + m_webview->get_Settings(&settings); + auto settings9 = settings.try_query(); + CHECK_FEATURE_RETURN_EMPTY(settings9); + + CHECK_FAILURE(settings9->put_PreferredBackgroundTimerWakeIntervalInMilliseconds( + m_defaultIntervalBackground)); + CHECK_FAILURE(settings9->put_PreferredIntensiveTimerWakeIntervalInMilliseconds( + m_defaultIntervalIntensive)); +} + +ScenarioThrottlingControl::~ScenarioThrottlingControl() +{ + if (m_monitorAppWindow) + { + m_monitorAppWindow->SetOnAppWindowClosing(nullptr); + ::PostMessage(m_monitorAppWindow->GetMainWindow(), WM_CLOSE, NULL, NULL); + m_monitorAppWindow = nullptr; + } +} diff --git a/SampleApps/WebView2APISample/ScenarioThrottlingControl.h b/SampleApps/WebView2APISample/ScenarioThrottlingControl.h index 3368775a..5313305a 100644 --- a/SampleApps/WebView2APISample/ScenarioThrottlingControl.h +++ b/SampleApps/WebView2APISample/ScenarioThrottlingControl.h @@ -8,3 +8,41 @@ #include "AppWindow.h" #include "ComponentBase.h" + +class ScenarioThrottlingControl : public ComponentBase +{ +public: + ScenarioThrottlingControl(AppWindow* appWindow); + ~ScenarioThrottlingControl() override; + +private: + void WebViewMessageReceived(ICoreWebView2WebMessageReceivedEventArgs* args); + + // Owner/target AppWindow. + AppWindow* m_appWindow = nullptr; + wil::com_ptr m_webview; + void OnWebViewCreated(); + void SetupIsolatedFramesHandler(); + + int m_defaultIntervalForeground = 0; + int m_defaultIntervalBackground = 0; + int m_defaultIntervalIntensive = 0; + int m_defaultIntervalOverride = 0; + + // Monitor AppWindow + AppWindow* m_monitorAppWindow = nullptr; + wil::com_ptr m_monitorWebview; + + std::wstring m_targetUri; + EventRegistrationToken m_contentLoadingToken = {}; + EventRegistrationToken m_frameCreatedToken = {}; + + // Handles commands from the monitor AppWindow + EventRegistrationToken m_webMessageReceivedToken = {}; + + // Scenarios + void OnNoUserInteraction(); + void OnUserInteraction(); + void HideWebView(); + void ShowWebView(); +}; diff --git a/SampleApps/WebView2APISample/SettingsComponent.cpp b/SampleApps/WebView2APISample/SettingsComponent.cpp index 5ffadad3..5f658aa2 100644 --- a/SampleApps/WebView2APISample/SettingsComponent.cpp +++ b/SampleApps/WebView2APISample/SettingsComponent.cpp @@ -13,6 +13,7 @@ #include #include #include +#include using namespace Microsoft::WRL; @@ -1296,7 +1297,6 @@ bool SettingsComponent::HandleWindowMessage( wil::com_ptr settings; settings = m_settings.try_query(); CHECK_FEATURE_RETURN(settings); - CHECK_FAILURE( settings->get_IsNonClientRegionSupportEnabled(&nonClientRegionSupportEnabled)); if (nonClientRegionSupportEnabled) diff --git a/SampleApps/WebView2APISample/Toolbar.cpp b/SampleApps/WebView2APISample/Toolbar.cpp index da772891..35718d92 100644 --- a/SampleApps/WebView2APISample/Toolbar.cpp +++ b/SampleApps/WebView2APISample/Toolbar.cpp @@ -37,7 +37,7 @@ void Toolbar::Initialize(AppWindow* appWindow) L"button", L"Cancel", WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 0, 0, 0, 0, mainWindow, (HMENU)IDE_CANCEL, nullptr, 0); m_items[Item_AddressBar] = CreateWindow( - L"edit", nullptr, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 0, 0, 0, 0, + L"edit", nullptr, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL, 0, 0, 0, 0, mainWindow, (HMENU)IDE_ADDRESSBAR, nullptr, 0); m_items[Item_GoButton] = CreateWindow( L"button", L"Go", WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | BS_DEFPUSHBUTTON, @@ -143,3 +143,14 @@ void Toolbar::UpdateFont() StringCchCopy(logFont.lfFaceName, ARRAYSIZE(logFont.lfFaceName), s_fontName); m_font = CreateFontIndirect(&logFont); } + +void Toolbar::SelectAll() +{ + SendMessage(m_items[Item_AddressBar], EM_SETSEL, 0, -1); +} + +void Toolbar::SelectAddressBar() +{ + SetFocus(m_items[Item_AddressBar]); + SelectAll(); +} diff --git a/SampleApps/WebView2APISample/Toolbar.h b/SampleApps/WebView2APISample/Toolbar.h index db6660c3..88f89717 100644 --- a/SampleApps/WebView2APISample/Toolbar.h +++ b/SampleApps/WebView2APISample/Toolbar.h @@ -33,6 +33,8 @@ class Toolbar // Returns remaining available area for the WebView RECT Resize(RECT availableBounds); void UpdateDpiAndTextScale(); + void SelectAll(); + void SelectAddressBar(); private: int GetItemLogicalWidth(Item item, int clientLogicalWidth) const; diff --git a/SampleApps/WebView2APISample/WebView2APISample.rc b/SampleApps/WebView2APISample/WebView2APISample.rc index 760bc307..28aa4043 100644 --- a/SampleApps/WebView2APISample/WebView2APISample.rc +++ b/SampleApps/WebView2APISample/WebView2APISample.rc @@ -302,6 +302,7 @@ BEGIN MENUITEM "Programmatic Save As", IDM_SCENARIO_SAVE_AS_PROGRAMMATIC END MENUITEM "Accelerator Key Pressed", IDM_SCENARIO_ACCELERATOR_KEY_PRESSED + MENUITEM "Throttling Control", IDM_SCENARIO_THROTTLING_CONTROL MENUITEM "Screen Capture", IDM_SCENARIO_SCREEN_CAPTURE MENUITEM "File Type Policy", IDM_SCENARIO_FILE_TYPE_POLICY END diff --git a/SampleApps/WebView2APISample/WebView2APISample.vcxproj b/SampleApps/WebView2APISample/WebView2APISample.vcxproj index b1d682f5..5ef38a83 100644 --- a/SampleApps/WebView2APISample/WebView2APISample.vcxproj +++ b/SampleApps/WebView2APISample/WebView2APISample.vcxproj @@ -1,4 +1,4 @@ - + @@ -489,4 +489,4 @@ - + \ No newline at end of file diff --git a/SampleApps/WebView2APISample/assets/ScenarioThrottlingControl.js b/SampleApps/WebView2APISample/assets/ScenarioThrottlingControl.js index 1cdd50d4..36440131 100644 --- a/SampleApps/WebView2APISample/assets/ScenarioThrottlingControl.js +++ b/SampleApps/WebView2APISample/assets/ScenarioThrottlingControl.js @@ -3,24 +3,27 @@ const limitIterations = false; const iterations = 20; // steps per iteration -const steps = 20; +const steps = 80; +const target = steps + 1; // global state let iteration = 0; let counter = 0; -let start = performance.now(); +let first = performance.now(); let timerId = undefined; const frameId = document.querySelector('meta[name="frame-title"]').content; const logger = (frameId == 'main') ? window.open('/ScenarioThrottlingControlMonitor.html') : window.parent; const timerCallback = () => { - let now = performance.now(); - counter++; + if (++counter == 1) { + first = performance.now(); + } // compute average timer delay only after target steps - if (counter == steps) { - let avg = (now - start) / steps; + if (counter == target) { + let end = performance.now(); + let avg = (end - first) / steps; onIterationCompleted(avg); } } @@ -35,7 +38,6 @@ function reportAverageDelay(delay) { } function onIterationCompleted(delayAvg) { - start = performance.now(); counter = 0; if (++iteration == iterations && limitIterations) { diff --git a/SampleApps/WebView2APISample/assets/ScenarioThrottlingControlMonitor.html b/SampleApps/WebView2APISample/assets/ScenarioThrottlingControlMonitor.html index 2a951275..912d840c 100644 --- a/SampleApps/WebView2APISample/assets/ScenarioThrottlingControlMonitor.html +++ b/SampleApps/WebView2APISample/assets/ScenarioThrottlingControlMonitor.html @@ -19,19 +19,19 @@

Throttling Control

- +
- +
- +
diff --git a/SampleApps/WebView2APISample/resource.h b/SampleApps/WebView2APISample/resource.h index 33093c45..9bc7ab78 100644 --- a/SampleApps/WebView2APISample/resource.h +++ b/SampleApps/WebView2APISample/resource.h @@ -225,6 +225,7 @@ #define IDC_CHECK_USE_OS_REGION 32803 #define ID_CUSTOM_DATA_PARTITION 32804 #define ID_SETTINGS_NON_CLIENT_REGION_SUPPORT_ENABLED 32805 +#define IDM_SCENARIO_THROTTLING_CONTROL 32807 #define IDM_SCENARIO_SCREEN_CAPTURE 32809 #define IDC_STATIC -1 // Next default values for new objects diff --git a/SampleApps/WebView2WpfBrowser/MainWindow.xaml b/SampleApps/WebView2WpfBrowser/MainWindow.xaml index a963646a..33db0071 100644 --- a/SampleApps/WebView2WpfBrowser/MainWindow.xaml +++ b/SampleApps/WebView2WpfBrowser/MainWindow.xaml @@ -101,9 +101,13 @@ found in the LICENSE file. + + + + @@ -233,6 +237,11 @@ found in the LICENSE file. + + + + + diff --git a/SampleApps/WebView2WpfBrowser/MainWindow.xaml.cs b/SampleApps/WebView2WpfBrowser/MainWindow.xaml.cs index 6427fd2c..96d39d4f 100644 --- a/SampleApps/WebView2WpfBrowser/MainWindow.xaml.cs +++ b/SampleApps/WebView2WpfBrowser/MainWindow.xaml.cs @@ -80,6 +80,11 @@ public partial class MainWindow : Window public static RoutedCommand CreateNewThreadCommand = new RoutedCommand(); public static RoutedCommand ExtensionsCommand = new RoutedCommand(); public static RoutedCommand TrackingPreventionLevelCommand = new RoutedCommand(); + public static RoutedCommand EnhancedSecurityModeLevelCommand = new RoutedCommand(); + public static RoutedCommand EnhancedSecurityModeGetBypassListCommand = new RoutedCommand(); + public static RoutedCommand EnhancedSecurityModeSetBypassListCommand = new RoutedCommand(); + public static RoutedCommand EnhancedSecurityModeGetEnforceListCommand = new RoutedCommand(); + public static RoutedCommand EnhancedSecurityModeSetEnforceListCommand = new RoutedCommand(); public static RoutedCommand PrintDialogCommand = new RoutedCommand(); public static RoutedCommand PrintToDefaultPrinterCommand = new RoutedCommand(); public static RoutedCommand PrintToPrinterCommand = new RoutedCommand(); @@ -198,7 +203,6 @@ CoreWebView2Profile WebViewProfile CoreWebView2PermissionState.Default }; -#if USE_WEBVIEW2_EXPERIMENTAL List _saveAsKindList = new List { CoreWebView2SaveAsKind.Default, @@ -206,7 +210,6 @@ CoreWebView2Profile WebViewProfile CoreWebView2SaveAsKind.SingleFile, CoreWebView2SaveAsKind.Complete, }; -#endif public CoreWebView2CreationProperties CreationProperties { get; set; } = null; @@ -854,6 +857,22 @@ void SetTrackingPreventionLevel(CoreWebView2TrackingPreventionLevel value) } // + + void EnhancedSecurityModeLevelCommandExecuted(object target, ExecutedRoutedEventArgs e) + { + } + void EnhancedSecurityModeGetBypassListCommandExecuted(object target, ExecutedRoutedEventArgs e) + { + } + void EnhancedSecurityModeSetBypassListCommandExecuted(object target, ExecutedRoutedEventArgs e) + { + } + void EnhancedSecurityModeGetEnforceListCommandExecuted(object target, ExecutedRoutedEventArgs e) + { + } + void EnhancedSecurityModeSetEnforceListCommandExecuted(object target, ExecutedRoutedEventArgs e) + { + } async void GetCookiesCmdExecuted(object target, ExecutedRoutedEventArgs e) { // @@ -2089,7 +2108,6 @@ void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2Init } shouldAttachEnvironmentEventHandlers = false; } - webView.CoreWebView2.FrameCreated += WebView_HandleIFrames; SetDefaultDownloadDialogPosition(); @@ -2132,8 +2150,17 @@ void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2Init main_window.Show(); } }; +#if USE_WEBVIEW2_EXPERIMENTAL + // Store the default values from the WebView2 Runtime so we can restore them later. + DefaultTimerIntervalForeground = webView.CoreWebView2.Settings.PreferredForegroundTimerWakeInterval; + DefaultTimerIntervalBackground = webView.CoreWebView2.Settings.PreferredBackgroundTimerWakeInterval; + DefaultTimerIntervalIntensive = webView.CoreWebView2.Settings.PreferredIntensiveTimerWakeInterval; + DefaultTimerIntervalOverride = webView.CoreWebView2.Settings.PreferredOverrideTimerWakeInterval; + +#endif return; } + // ERROR_DELETE_PENDING(0x8007012f) if (e.InitializationException.HResult == -2147024593) { @@ -3351,7 +3378,6 @@ void WebView_NotificationReceived(object sender, CoreWebView2NotificationReceive // async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) { -#if USE_WEBVIEW2_EXPERIMENTAL try { // @@ -3363,30 +3389,22 @@ async void ProgrammaticSaveAsExecuted(object target, ExecutedRoutedEventArgs e) { MessageBox.Show(this, "Programmatic Save As Failed: " + exception.Message); } -#else - await Task.Delay(0); -#endif } // // -#if USE_WEBVIEW2_EXPERIMENTAL private bool isSilentSaveAs = false; -#endif void ToggleSilentExecuted(object target, ExecutedRoutedEventArgs e) { -#if USE_WEBVIEW2_EXPERIMENTAL isSilentSaveAs = !isSilentSaveAs; if (isSilentSaveAs) webView.CoreWebView2.SaveAsUIShowing += WebView_SaveAsUIShowing; else webView.CoreWebView2.SaveAsUIShowing -= WebView_SaveAsUIShowing; MessageBox.Show(isSilentSaveAs? "Silent Save As Enabled":"Silent Save As Disabled" , "Info"); -#endif } // -#if USE_WEBVIEW2_EXPERIMENTAL // void WebView_SaveAsUIShowing(object sender, CoreWebView2SaveAsUIShowingEventArgs args) { @@ -3427,7 +3445,6 @@ void WebView_SaveAsUIShowing(object sender, CoreWebView2SaveAsUIShowingEventArgs } // -#endif // Simple function to retrieve fields from a JSON message. // For production code, you should use a real JSON parser library. @@ -3459,7 +3476,165 @@ void FileExplorerExecuted(object target, ExecutedRoutedEventArgs e) void ThrottlingControlExecuted(object target, ExecutedRoutedEventArgs e) { +#if USE_WEBVIEW2_EXPERIMENTAL + webView.CoreWebView2.NewWindowRequested += delegate ( + object webview2, CoreWebView2NewWindowRequestedEventArgs args) + { + if (args.OriginalSourceFrameInfo?.Source == "https://appassets.example/ScenarioThrottlingControl.html") + { + CoreWebView2Deferral deferral = args.GetDeferral(); + MainWindow monitorWindow = new MainWindow( + webView.CreationProperties, true /*isNewWindowRequest*/); + monitorWindow.OnWebViewFirstInitialized = () => + { + using (deferral) + { + args.Handled = true; + args.NewWindow = monitorWindow.webView.CoreWebView2; + + // handle messages from throttling control monitor. + monitorWindow.webView.CoreWebView2.WebMessageReceived += WebView_WebMessageReceivedThrottlingControl; + } + }; + monitorWindow.Show(); + } + }; + + + SetupIsolatedFramesHandler(); + webView.Source = new Uri("https://appassets.example/ScenarioThrottlingControl.html"); +#endif } + +#if USE_WEBVIEW2_EXPERIMENTAL + void WebView_WebMessageReceivedThrottlingControl(object sender, CoreWebView2WebMessageReceivedEventArgs args) + { + var json = args.WebMessageAsJson; + var command = GetJSONStringField(json, "command"); + Debug.WriteLine($"[throttling control] command: {command}"); + + if (command == "set-interval") + { + var category = GetJSONStringField(json, "priority"); + var interval = new TimeSpan(0, 0, 0, 0, Int32.Parse(GetJSONStringField(json, "intervalMs"))); + + if (category == "foreground") + { + Debug.WriteLine("foreground"); + // webView is the WPF WebView2 control class. + webView.CoreWebView2.Settings.PreferredForegroundTimerWakeInterval = interval; + } + else if (category == "background") + { + Debug.WriteLine("background"); + // webView is the WPF WebView2 control class. + webView.CoreWebView2.Settings.PreferredBackgroundTimerWakeInterval = interval; + } + else if (category == "untrusted") + { + Debug.WriteLine("untrusted"); + // webView is the WPF WebView2 control class. + webView.CoreWebView2.Settings.PreferredOverrideTimerWakeInterval = interval; + } + } + else if (command == "toggle-visibility") + { + webView.Visibility = webView.Visibility == System.Windows.Visibility.Visible ? System.Windows.Visibility.Hidden : System.Windows.Visibility.Visible; + } + else if (command == "scenario") + { + var label = GetJSONStringField(json, "label"); + + if (label == "interaction-throttle") + { + OnNoUserInteraction(); + } + else if (label == "interaction-reset") + { + OnUserInteraction(); + } + else if (label == "hidden-unthrottle") + { + HideWebView(); + } + else if (label == "hidden-reset") + { + ShowWebView(); + } + } + } + + // + TimeSpan DefaultTimerIntervalForeground; + TimeSpan DefaultTimerIntervalBackground; + TimeSpan DefaultTimerIntervalIntensive; + TimeSpan DefaultTimerIntervalOverride; + + // The primary use-case here is an app embedding 3rd party content and wanting + // to be able to independently limit the performance impact of it. Generally, + // that's something like "low battery, throttle more" or "giving the frame N + // seconds to run some logic, throttle less". + void SetupIsolatedFramesHandler() + { + // You can use the frame properties to determine whether it should be + // marked to be throttled separately from main frame. + webView.CoreWebView2.FrameCreated += (sender, args) => + { + if (args.Frame.Name == "untrusted") + { + args.Frame.UseOverrideTimerWakeInterval = true; + } + }; + + // Restrict frames selected by the above callback to always match the default + // timer interval for background frames. + // webView is the WPF WebView2 control class. + webView.CoreWebView2.Settings.PreferredOverrideTimerWakeInterval = DefaultTimerIntervalBackground; + } + + // This sample app calls this method when receiving a simulated event from its + // control monitor, but your app can decide how and when to go into this state. + void OnNoUserInteraction() + { + // User is not interactive, keep webview visible but throttle foreground + // timers to 500ms. + // webView is the WPF WebView2 control class. + webView.CoreWebView2.Settings.PreferredForegroundTimerWakeInterval = new TimeSpan(0, 0, 0, 0, 500); + } + + void OnUserInteraction() + { + // User is interactive again, set foreground timer interval back to its + // default value. + // webView is the WPF WebView2 control class. + webView.CoreWebView2.Settings.PreferredForegroundTimerWakeInterval = DefaultTimerIntervalForeground; + } + + // Prepares the WebView to go into hidden mode with no background timer + // throttling. + void HideWebView() + { + // This WebView2 will remain hidden but needs to keep running timers. + // Unthrottle background timers. + // webView is the WPF WebView2 control class. + webView.CoreWebView2.Settings.PreferredBackgroundTimerWakeInterval = new TimeSpan(0); + // Effectively disable intensive throttling by overriding its timer interval. + // webView is the WPF WebView2 control class. + webView.CoreWebView2.Settings.PreferredIntensiveTimerWakeInterval = new TimeSpan(0); + webView.Visibility = System.Windows.Visibility.Hidden; + } + + // Shows the WebView and restores default throttling behavior. + void ShowWebView() + { + webView.Visibility = System.Windows.Visibility.Visible; + // webView is the WPF WebView2 control class. + webView.CoreWebView2.Settings.PreferredBackgroundTimerWakeInterval = DefaultTimerIntervalBackground; + webView.CoreWebView2.Settings.PreferredIntensiveTimerWakeInterval = DefaultTimerIntervalIntensive; + } + // +#endif + // #if USE_WEBVIEW2_EXPERIMENTAL private bool isScreenCaptureEnabled = true; diff --git a/SampleApps/WebView2WpfBrowser/SaveAsDialog.xaml.cs b/SampleApps/WebView2WpfBrowser/SaveAsDialog.xaml.cs index 72536d7e..99c0d3e1 100644 --- a/SampleApps/WebView2WpfBrowser/SaveAsDialog.xaml.cs +++ b/SampleApps/WebView2WpfBrowser/SaveAsDialog.xaml.cs @@ -12,13 +12,11 @@ namespace WebView2WpfBrowser { /// Interaction logic for SaveAsDialog.xaml /// public partial class SaveAsDialog : Window { -#if USE_WEBVIEW2_EXPERIMENTAL public SaveAsDialog(List kind = null) { InitializeComponent(); SaveAsKind.ItemsSource = kind; SaveAsKind.SelectedIndex = 0; } -#endif void OK_Clicked(object sender, RoutedEventArgs args) { this.DialogResult = true; } void CANCEL_Clicked(object sender, RoutedEventArgs args) { this.DialogResult = false; } } From ccd0e8f689e9ae016537ca25197de6a4d9cbb617 Mon Sep 17 00:00:00 2001 From: WebView2 Github Bot Date: Tue, 23 Jul 2024 17:18:47 +0000 Subject: [PATCH 2/2] Updated package version for Win32, WPF and WinForms sample apps to 1.0.2730-prerelease --- SampleApps/WebView2APISample/WebView2APISample.vcxproj | 8 ++++---- SampleApps/WebView2APISample/packages.config | 2 +- .../WebView2WindowsFormsBrowser.csproj | 2 +- SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/SampleApps/WebView2APISample/WebView2APISample.vcxproj b/SampleApps/WebView2APISample/WebView2APISample.vcxproj index 5ef38a83..882eb5f4 100644 --- a/SampleApps/WebView2APISample/WebView2APISample.vcxproj +++ b/SampleApps/WebView2APISample/WebView2APISample.vcxproj @@ -1,4 +1,4 @@ - + @@ -480,13 +480,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}. - + - \ No newline at end of file + diff --git a/SampleApps/WebView2APISample/packages.config b/SampleApps/WebView2APISample/packages.config index 7620352d..715bfe9c 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/WebView2WindowsFormsBrowser/WebView2WindowsFormsBrowser.csproj b/SampleApps/WebView2WindowsFormsBrowser/WebView2WindowsFormsBrowser.csproj index 4541477a..380678ef 100644 --- a/SampleApps/WebView2WindowsFormsBrowser/WebView2WindowsFormsBrowser.csproj +++ b/SampleApps/WebView2WindowsFormsBrowser/WebView2WindowsFormsBrowser.csproj @@ -25,7 +25,7 @@ AnyCPU - + diff --git a/SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj b/SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj index 4e3ba86b..3631fc58 100644 --- a/SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj +++ b/SampleApps/WebView2WpfBrowser/WebView2WpfBrowser.csproj @@ -61,7 +61,7 @@ - +