-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add spec: Throttling Control - Script #4156
base: main
Are you sure you want to change the base?
Changes from 4 commits
03831fb
84a31c0
bb4deee
855667f
e6e1466
a477d20
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,291 @@ | ||||||
Throttling Control - Script Throttling | ||||||
=== | ||||||
|
||||||
# Background | ||||||
Web content in WebView2 is generally subject to the same Web Platform | ||||||
restrictions as in the Microsoft Edge browser. However, some of the scenarios | ||||||
for WebView2 applications differ from the scenarios in the browser. For this | ||||||
reason, we're providing a set of APIs to fine-tune performance of scripts | ||||||
running in WebView2. These APIs allow WebView2 applications to achieve two | ||||||
things: | ||||||
|
||||||
* Customize script timers (`setTimeout` and `setInterval`) throttling under | ||||||
different page states (foreground, background, and background with intensive | ||||||
throttling) | ||||||
* Throttle script timers in select hosted iframes | ||||||
|
||||||
# Examples | ||||||
|
||||||
## Throttle timers in visible WebView | ||||||
|
||||||
Throttling Control APIs allow you to throttle JavaScript timers in scenarios | ||||||
where the WebView2 control in your application needs to remain visible, but | ||||||
consume less resources, for example, when the user is not interactive. | ||||||
|
||||||
```c# | ||||||
void OnNoUserInteraction() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like an event callback, but it is implied business logic of the sample app, not something rooted in the wv2 API surface, correct? Is there a prescribed way app should determine when the customer isn't "interactive"? (Or, is this concept already present in our samples elsewhere?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No prescribed way to implement detection logic, this is up to the app. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a comment to make it clear how the app would call this method. |
||||||
{ | ||||||
// User is not interactive, keep webview visible but throttle timers to 500ms. | ||||||
webView.CoreWebView2.SetThrottlingIntervalPreference(CoreWebView2ThrottlingCategory.Foreground, 500); | ||||||
} | ||||||
|
||||||
void OnUserInteraction() | ||||||
{ | ||||||
// User is interactive again, unthrottle foreground timers. | ||||||
webView.CoreWebView2.SetThrottlingIntervalPreference(CoreWebView2ThrottlingCategory.Foreground, 0); | ||||||
} | ||||||
``` | ||||||
|
||||||
```cpp | ||||||
void ScenarioThrottlingControl::OnNoUserInteraction() | ||||||
{ | ||||||
auto webView21 = m_webview.try_query<ICoreWebView2_21>(); | ||||||
CHECK_FEATURE_RETURN_EMPTY(webView21); | ||||||
|
||||||
// User is not interactive, keep webview visible but throttle timers to | ||||||
// 500ms. | ||||||
CHECK_FAILURE(webView21->SetThrottlingIntervalPreference( | ||||||
COREWEBVIEW2_THROTTLING_CATEGORY_FOREGROUND, 500)); | ||||||
} | ||||||
|
||||||
void ScenarioThrottlingControl::OnUserInteraction() | ||||||
{ | ||||||
auto webView21 = m_webview.try_query<ICoreWebView2_21>(); | ||||||
CHECK_FEATURE_RETURN_EMPTY(webView21); | ||||||
|
||||||
// User is interactive again, unthrottle foreground timers. | ||||||
CHECK_FAILURE(webView21->SetThrottlingIntervalPreference( | ||||||
COREWEBVIEW2_THROTTLING_CATEGORY_FOREGROUND, 0)); | ||||||
} | ||||||
``` | ||||||
|
||||||
## Unthrottle timers in hidden WebView | ||||||
|
||||||
Throttling Control APIs allow you to set a custom throttling interval for timers | ||||||
on hidden WebViews. For example, if there's logic in your app that runs in | ||||||
JavaScript but doesn't need to render content, you can keep the WebView hidden | ||||||
and unthrottle its timers. | ||||||
|
||||||
```C# | ||||||
void SetupHiddenWebViewCore() | ||||||
{ | ||||||
// This WebView2 will remain hidden but needs to keep running timers. | ||||||
// Unthrottle background timers. | ||||||
webView.CoreWebView2.SetThrottlingIntervalPreference(CoreWebView2ThrottlingCategory.Background, 0); | ||||||
// Effectively disable intensive throttling by overriding its timer interval. | ||||||
webView.CoreWebView2.SetThrottlingIntervalPreference(CoreWebView2ThrottlingCategory.Intensive, 0); | ||||||
webView.Visibility = System.Windows.Visibility.Hidden; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want the throttling changes to take effect immediately, do we need to do a fake "navigate to self"? (Though that would destroy any page state, so maybe not?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. n/a see above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the hosted JS know that it is invisible but there is a desire for it to still run in this state? Or would this info have to come through a different channel, or is the expectation that hosted code will always try to run if it can and won't itself, say, stop regular updates or communication with its backing service if the web platform says it's not visible? Another way of putting it is, how is "visible as far as the web platform is concerned" differentiated from "host app has set the WV2 element to be visible" from "content is actually visible" both from the perspective of the host app calling these APIs and the hosted code having access to the web platform APIs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JavaScript context only has knowledge of "visible as far as the web platform is concerned" through Page Visibility API. From the host app side, this is controlled by CoreWebView2Controller.IsVisible property. For WebView2 controls in .NET/WinRT, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding explicit end to end description of how API fits into chromium/web platform feature to the ref docs and examples of how an end dev might use it |
||||||
} | ||||||
|
||||||
void DisableHiddenWebViewCore() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method name is confusing. The name says that it's disabling the hidden webview, but really it's showing the webview and setting new values. Is this code trying to restore defaults? If so, it should save the previous values of the properties in SetupHiddenWebViewCore so that it can restore them here, rather than hard-coding what it believes to be the defaults. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Name is sort of double negative. Better would be Hide/ShowWebView |
||||||
{ | ||||||
webView.Visibility = System.Windows.Visibility.Visible; | ||||||
webView.CoreWebView2.SetThrottlingIntervalPreference(CoreWebView2ThrottlingCategory.Background, 1000); | ||||||
webView.CoreWebView2.SetThrottlingIntervalPreference(CoreWebView2ThrottlingCategory.Intensive, 60000); | ||||||
} | ||||||
``` | ||||||
|
||||||
```cpp | ||||||
void ScenarioThrottlingControl::SetupHiddenWebViewCore() | ||||||
{ | ||||||
auto webView21 = m_webview.try_query<ICoreWebView2_21>(); | ||||||
CHECK_FEATURE_RETURN_EMPTY(webView21); | ||||||
|
||||||
// This WebView2 will remain hidden but needs to keep running timers. | ||||||
// Unthrottle background timers. | ||||||
CHECK_FAILURE(webView21->SetThrottlingIntervalPreference( | ||||||
COREWEBVIEW2_THROTTLING_CATEGORY_BACKGROUND, 0)); | ||||||
// Effectively disable intensive throttling by overriding its timer interval. | ||||||
CHECK_FAILURE(webView21->SetThrottlingIntervalPreference( | ||||||
COREWEBVIEW2_THROTTLING_CATEGORY_INTENSIVE, 0)); | ||||||
|
||||||
CHECK_FAILURE(m_appWindow->GetWebViewController()->put_IsVisible(FALSE)); | ||||||
} | ||||||
|
||||||
void ScenarioThrottlingControl::DisableHiddenWebViewCore() | ||||||
{ | ||||||
CHECK_FAILURE(m_appWindow->GetWebViewController()->put_IsVisible(TRUE)); | ||||||
|
||||||
auto webView21 = m_webview.try_query<ICoreWebView2_21>(); | ||||||
CHECK_FEATURE_RETURN_EMPTY(webView21); | ||||||
|
||||||
CHECK_FAILURE(webView21->SetThrottlingIntervalPreference( | ||||||
COREWEBVIEW2_THROTTLING_CATEGORY_BACKGROUND, 1000)); | ||||||
CHECK_FAILURE(webView21->SetThrottlingIntervalPreference( | ||||||
COREWEBVIEW2_THROTTLING_CATEGORY_INTENSIVE, 60000)); | ||||||
} | ||||||
``` | ||||||
|
||||||
## Throttle timers in hosted iframes | ||||||
|
||||||
Throttling Control APIs allow you to throttle timers in specific frames within | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So every frame is either a 'regular' frame or an 'isolated' frame. Regular frames are controlled by Is there a reason why an isolated frame does not have Foreground/Background/Intensive properties? Or is that not an interesting scenario? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Valid question - Mostly not an interesting enough scenario to add the complexity. 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". The case where they'd want to put it in an isolated group while still managing foreground/background/intensive independently of the non-isolated timers would be a niche of a niche that even our most micromanage-y apps wouldn't want that granularity. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this value takes precedence over the other values? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update sample to set isolated to at least match background to help demonstrate intended scenario / usage of isolated. Use Andy's comment to update the sample code. Other names to consider:
Use the following: |
||||||
the WebView2 control. For example, if your application uses iframes to host 3rd | ||||||
party content, you can select and mark these frames to be throttled separately | ||||||
from the main frame and other regular, unmarked frames. | ||||||
|
||||||
```C# | ||||||
void SetupUntrustedFramesHandler() | ||||||
{ | ||||||
webView.CoreWebView2.FrameCreated += (sender, args) => | ||||||
{ | ||||||
// You can use the frame properties to determine whether it should be | ||||||
// marked to be throttled separately from main frame. | ||||||
if (args.Frame.Name == "untrusted") | ||||||
{ | ||||||
args.Frame.IsUntrusted = true; | ||||||
} | ||||||
}; | ||||||
|
||||||
webView.CoreWebView2.SetThrottlingIntervalPreference(CoreWebView2ThrottlingCategory.UntrustedFrame, 500); | ||||||
} | ||||||
``` | ||||||
|
||||||
```cpp | ||||||
void ScenarioThrottlingControl::SetupUntrustedFramesHandler() | ||||||
{ | ||||||
auto webview4 = m_webview.try_query<ICoreWebView2_4>(); | ||||||
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<ICoreWebView2FrameCreatedEventHandler>( | ||||||
[this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args) -> HRESULT | ||||||
{ | ||||||
wil::com_ptr<ICoreWebView2Frame> webviewFrame; | ||||||
CHECK_FAILURE(args->get_Frame(&webviewFrame)); | ||||||
|
||||||
auto webviewFrame6 = | ||||||
webviewFrame.try_query<ICoreWebView2Frame6>(); | ||||||
CHECK_FEATURE_RETURN_HRESULT(webviewFrame6); | ||||||
|
||||||
wil::unique_cotaskmem_string name; | ||||||
CHECK_FAILURE(webviewFrame->get_Name(&name)); | ||||||
if (wcscmp(name.get(), L"untrusted") == 0) | ||||||
{ | ||||||
CHECK_FAILURE(webviewFrame6->put_IsUntrusted(TRUE)); | ||||||
} | ||||||
|
||||||
return S_OK; | ||||||
}) | ||||||
.Get(), | ||||||
&m_frameCreatedToken)); | ||||||
|
||||||
auto webView21 = m_webview.try_query<ICoreWebView2_21>(); | ||||||
CHECK_FAILURE(webView21->SetThrottlingIntervalPreference( | ||||||
COREWEBVIEW2_THROTTLING_CATEGORY_UNTRUSTED_FRAME, 500)); | ||||||
} | ||||||
``` | ||||||
|
||||||
# API Details | ||||||
```cpp | ||||||
[v1_enum] | ||||||
typedef enum COREWEBVIEW2_THROTTLING_CATEGORY { | ||||||
david-risney marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// Applies to frames whose WebView is in foreground state. WebViews whose | ||||||
david-risney marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// `IsVisible` property is `TRUE` are in this state. The default value is a | ||||||
/// constant determined by the running version of the WebView2 Runtime. | ||||||
COREWEBVIEW2_THROTTLING_CATEGORY_FOREGROUND, | ||||||
|
||||||
/// Applies to frames whose WebView is in background state. WebViews whose | ||||||
/// `IsVisible` property is `FALSE` are in this state. The default value is a | ||||||
/// constant determined by the running version of the WebView2 Runtime. | ||||||
/// All other background state policies (including intensive throttling) are | ||||||
/// effective independently of this setting. | ||||||
COREWEBVIEW2_THROTTLING_CATEGORY_BACKGROUND, | ||||||
|
||||||
/// Applies to frames whose WebView is being intensively throttled (a | ||||||
david-risney marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// sub-state of background state). For more details about intensive | ||||||
/// throttling, see [Intensive throttling of Javascript timer wake ups](https://chromestatus.com/feature/4718288976216064). | ||||||
/// The default value is a constant determined by the running version of the | ||||||
/// WebView2 Runtime. | ||||||
COREWEBVIEW2_THROTTLING_CATEGORY_INTENSIVE, | ||||||
|
||||||
/// Applies to frames that have been marked untrusted by the host app. | ||||||
/// This is a category specific to WebView2 with no corresponding state in the | ||||||
/// Chromium tab state model. The default value is a constant determined by | ||||||
/// the running version of the WebView2 Runtime. | ||||||
COREWEBVIEW2_THROTTLING_CATEGORY_UNTRUSTED_FRAME | ||||||
} COREWEBVIEW2_THROTTLING_CATEGORY; | ||||||
|
||||||
/// A continuation of the `ICoreWebView2` interface to support ThrottlingPreference. | ||||||
[uuid(00f1b5fb-91ed-4722-9404-e0f8fd1e6b0a), object, pointer_default(unique)] | ||||||
interface ICoreWebView2_21 : ICoreWebView2_20 { | ||||||
/// Get the preferred wake up interval (in milliseconds) for throttleable | ||||||
/// JavaScript tasks (`setTimeout` and `setInterval`), for the given | ||||||
david-risney marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// throttling category. A wake up interval is the amount of time that needs | ||||||
david-risney marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// to pass before the WebView2 Runtime checks for new timer tasks to run. | ||||||
/// The default interval values are constants determined by the running | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarified meaning of "determined by the WebView2 runtime". |
||||||
/// version of the WebView2 Runtime. | ||||||
HRESULT GetThrottlingIntervalPreference( | ||||||
[in] COREWEBVIEW2_THROTTLING_CATEGORY category, | ||||||
[out, retval] UINT32* intervalInMilliseconds); | ||||||
|
||||||
/// Sets the preferred wake up interval (in milliseconds) for throttleable | ||||||
/// JavaScript tasks (`setTimeout` and `setInterval`), for the given | ||||||
/// throttling category. A wake up interval is the amount of time that needs | ||||||
/// to pass before the WebView2 Runtime checks for new timer tasks to run. For | ||||||
/// example, an application might use a foreground value of 30 ms for moderate | ||||||
/// throttling scenarios, or match the default background value (usually 1000 | ||||||
/// ms). The WebView2 Runtime will try to respect the preferred interval set | ||||||
david-risney marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// by the application, but the effective value will be constrained by | ||||||
/// resource and platform limitations. Setting a value of `0` means no | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [pending] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated. |
||||||
/// throttling will be applied. | ||||||
HRESULT SetThrottlingIntervalPreference( | ||||||
[in] COREWEBVIEW2_THROTTLING_CATEGORY category, | ||||||
david-risney marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
[in] UINT32 intervalInMilliseconds); | ||||||
david-risney marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
|
||||||
/// A continuation of the `ICoreWebView2Frame` interface to support IsUntrusted property. | ||||||
[uuid(5b7d1b96-699b-44a2-b9f1-b8e88f9ac2be), object, pointer_default(unique)] | ||||||
interface ICoreWebView2Frame6 : ICoreWebView2Frame5 { | ||||||
/// The `IsUntrusted` property indicates whether the frame has been marked | ||||||
/// untrusted by the host app. Untrusted frames will receive a different | ||||||
/// script throttling category as compared to regular frames. Defaults to | ||||||
/// `FALSE` unless set otherwise. When `FALSE`, and for main frame, throttling | ||||||
/// category will be determined by page state. The corresponding preferred | ||||||
/// interval will apply (set through `SetThrottlingIntervalPreference`). | ||||||
[propget] HRESULT IsUntrusted([out, retval] BOOL* value); | ||||||
|
||||||
/// Marks the frame as untrusted, for script throttling purposes. | ||||||
[propput] HRESULT IsUntrusted([in] BOOL value); | ||||||
} | ||||||
|
||||||
``` | ||||||
|
||||||
```C# | ||||||
namespace Microsoft.Web.WebView2.Core | ||||||
{ | ||||||
enum CoreWebView2ThrottlingCategory | ||||||
{ | ||||||
Foreground = 0, | ||||||
Background = 1, | ||||||
Intensive = 2, | ||||||
UntrustedFrame = 3, | ||||||
}; | ||||||
|
||||||
runtimeclass CoreWebView2 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: class here should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please fix! |
||||||
{ | ||||||
// ... | ||||||
|
||||||
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2_21")] | ||||||
{ | ||||||
UInt32 GetThrottlingIntervalPreference(CoreWebView2ThrottlingCategory category); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if instead of two methods on the CoreWebView2, we should make this four get/set properties on the CoreWebView2Settings. Reading through your document it doesn't seem like its useful to have a method that takes this as an enum because the caller will always be explicitly changing the value of a specific category. In which case its probably easier to have individual properties for each category rather than a method where you choose the category. And then the whole scenario seems very uncommon so rather than put this on the CoreWebView2 we can move it at least to the CoreWebView2Settings. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have no strong opinion on whether to put these in For enum vs methods. I don't have a strong preference either, but I'm not sure I understand the reasoning:
isn't this what we do for other APIs taking non-flags enum types? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this is what it would look like moving as properties to
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Undecided on the name pattern though. I think we'd want them to show up together so maybe the pattern should be something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Giving them the same prefix does help group them although the name is less readable. Let's try that in the API review and see what they say There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just pushed the update moving to properties in settings object. Thanks! |
||||||
|
||||||
void SetThrottlingIntervalPreference(CoreWebView2ThrottlingCategory category, UInt32 intervalInMilliseconds); | ||||||
} | ||||||
} | ||||||
|
||||||
runtimeclass CoreWebView2Frame | ||||||
{ | ||||||
// ... | ||||||
|
||||||
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2Frame6")] | ||||||
{ | ||||||
Boolean IsUntrusted { get; set; }; | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
``` | ||||||
|
||||||
# Appendix |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be helpful to elaborate a bit on what 'throttling' means in this case. If I set a throttling interval of 500ms, that means that timers will file at most once every 500ms - is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Especially because the Chromium docs call out two stages of throttling.
Stage 1: Timers run at normal speed for X seconds.
Stage 2: After X seconds, timers run at maximum speed Y.
It may not be clear whether this setting controls X or Y. I think it controls Y.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This paragraph is for API review only. Proper documentation for "throttling" meaning is in the API:
I can use similar wording to the question above in the "For example" portion of the docs:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two stages of throttling from the referred document map as follows:
ThrottlingIntervalPreferenceForeground
ThrottlingIntervalPreferenceBackground
ThrottlingIntervalPreferenceIntensive
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please update docs. See discussion later on for changing name to be clearer.