From 132f3f64ddc71778b74c86f877532a02f38a9e0b Mon Sep 17 00:00:00 2001 From: Wangsong Jin Date: Tue, 26 Nov 2024 14:35:36 -0800 Subject: [PATCH 1/8] Added Spec for CoreWebView2Frame::FrameCreated API --- specs/NestedFrame.md | 269 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 specs/NestedFrame.md diff --git a/specs/NestedFrame.md b/specs/NestedFrame.md new file mode 100644 index 00000000..6cb6ee7b --- /dev/null +++ b/specs/NestedFrame.md @@ -0,0 +1,269 @@ +CoreWebView2Frame.FrameCreated API +=== + +# Background +At present, WebView2 enables developers to track only +first-level iframes, which are the direct child frames +of the main frame. However, we're noticing WebView2 +customers want to manage nested iframes, such as +recording the navigation history for a nested iframe. +To address this, we will introduce the +`CoreWebView2Frame.FrameCreated` API. This new API will allow +developers to subscribe to the event when a nested WebView frame +is created, giving them access to all properties, methods, and +events of [CoreWebView2Frame](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2frame) +for the nested WebView frame. + +To prevent unnecessary performance overhead, WebView2 +does not track any nested WebView frames by default. +It only tracks a webview2 frame if its parent webview2 +frame has subscribed to the `CoreWebView2Frame.FrameCreated` +API. Therefore, WebView2 developers have the flexibility +to choose whether they want to track specific branches of +the frame tree or all webview2 frames. + +# Examples +### C++ Sample +```cpp +EventRegistrationToken m_frameCreatedToken = {}; + +void PostFrameCreatedEventMessage(wil::com_ptr webviewFrame); + +void ScenarioWebViewEventMonitor::InitializeEventView(ICoreWebView2* webviewEventView) +{ + auto webviewEventView4 = webviewEventView.try_query(); + webviewEventView4->add_FrameCreated( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args) + -> HRESULT { + wil::com_ptr webviewFrame; + CHECK_FAILURE(args->get_Frame(&webviewFrame)); + // Track first-level webview frame events. + InitializeFrameEventView(webviewFrame); + PostFrameCreatedEventMessage(webviewFrame); + return S_OK; + }) + .Get(), + &m_frameCreatedToken); +} + +// Track the creation, destruction, and navigation events of webview frames. +void ScenarioWebViewEventMonitor::InitializeFrameEventView( + wil::com_ptr webviewFrame) { + auto frame7 = webviewFrame.try_query(); + if (frame7) + { + //! [AddFrameCreated] + frame7->add_FrameCreated( + Callback( + [this]( + ICoreWebView2Frame* sender, + ICoreWebView2FrameCreatedEventArgs* args) -> HRESULT + { + wil::com_ptr webviewFrame; + CHECK_FAILURE(args->get_Frame(&webviewFrame)); + // Make a recursive call to track all nested + // webview frame events. + InitializeFrameEventView(webviewFrame); + PostFrameCreatedEventMessage(webviewFrame); + return S_OK; + }) + .Get(), + &m_frameCreatedToken); + //! [AddFrameCreated] + } + + // Subscribe to webview frame destroyed event. + webviewFrame->add_Destroyed( + Callback( + [this](ICoreWebView2Frame* sender, IUnknown* args) -> HRESULT + { + wil::unique_cotaskmem_string name; + CHECK_FAILURE(sender->get_Name(&name)); + std::wstring message = L"{ \"kind\": \"event\", \"name\": " + L"\"CoreWebView2Frame::Destroyed\", \"args\": {"; + message += L"\"frame name\": " + EncodeQuote(name.get()); + message += + L"}" + WebViewPropertiesToJsonString(m_webviewEventSource.get()) + L"}"; + PostEventMessage(message); + return S_OK; + }) + .Get(), + NULL); + + // Subscribe to webview frame navigation events. + wil::com_ptr frame2 = webviewFrame.try_query(); + if (frame2) + { + frame2->add_NavigationStarting( + Callback( + [this]( + ICoreWebView2Frame* sender, + ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT { + std::wstring message = NavigationStartingArgsToJsonString( + m_webviewEventSource.get(), args, + L"CoreWebView2Frame::NavigationStarting"); + PostEventMessage(message); + return S_OK; + }) + .Get(), + NULL); + + frame2->add_ContentLoading( + Callback( + [this](ICoreWebView2Frame* sender, ICoreWebView2ContentLoadingEventArgs* args) + -> HRESULT { + std::wstring message = ContentLoadingArgsToJsonString( + m_webviewEventSource.get(), args, L"CoreWebView2Frame::ContentLoading"); + PostEventMessage(message); + return S_OK; + }) + .Get(), + NULL); + + frame2->add_DOMContentLoaded( + Callback( + [this](ICoreWebView2Frame* sender, ICoreWebView2DOMContentLoadedEventArgs* args) + -> HRESULT { + std::wstring message = DOMContentLoadedArgsToJsonString( + m_webviewEventSource.get(), args, + L"CoreWebView2Frame::DOMContentLoaded"); + PostEventMessage(message); + return S_OK; + }) + .Get(), + NULL); + + frame2->add_NavigationCompleted( + Callback( + [this]( + ICoreWebView2Frame* sender, + ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT { + std::wstring message = NavigationCompletedArgsToJsonString( + m_webviewEventSource.get(), args, + L"CoreWebView2Frame::NavigationCompleted"); + PostEventMessage(message); + return S_OK; + }) + .Get(), + NULL); + } +} + +void ScenarioWebViewEventMonitor::PostFrameCreatedEventMessage(wil::com_ptr webviewFrame) { + wil::unique_cotaskmem_string name; + CHECK_FAILURE(webviewFrame->get_Name(&name)); + auto frame5 = webviewFrame.try_query(); + if (frame5) + { + UINT32 frameId = 0; + CHECK_FAILURE(frame5->get_FrameId(&frameId)); + } + + std::wstring message = + L"{ \"kind\": \"event\", \"name\": \"FrameCreated\", \"args\": {"; + + message += L"\"frame\": " + EncodeQuote(name.get()); + message += L",\"webview frame Id\": " + std::to_wstring((int)frameId) + L"}"; + message += + WebViewPropertiesToJsonString(m_webview.get()); + message += L"}"; + PostEventMessage(message); +} +``` +### C# Sample +```c# +// Track first-level webview frame created event. +void ChildFrameEventsExecuted(object target, ExecutedRoutedEventArgs e) +{ + webView.CoreWebView2.FrameCreated += HandleChildFrameCreated; +} + +// Track the creation, destruction, and navigation events of webview frames. +void HandleChildFrameCreated(object sender, CoreWebView2FrameCreatedEventArgs args) +{ + CoreWebView2Frame childFrame = args.Frame; + string name = String.IsNullOrEmpty(childFrame.Name) ? "none" : childFrame.Name; + MessageBox.Show(this, "Id: " + childFrame.FrameId + " name: " + name, "Child frame created", MessageBoxButton.OK); + // Make a recursive call to track all nested webview2 frames events. + childFrame.FrameCreated += HandleChildFrameCreated; + childFrame.NavigationStarting += HandleChildFrameNavigationStarting; + childFrame.ContentLoading += HandleChildFrameContentLoading; + childFrame.DOMContentLoaded += HandleChildFrameDOMContentLoaded; + childFrame.NavigationCompleted += HandleChildFrameNavigationCompleted; + childFrame.Destroyed += HandleChildFrameDestroyed; +} + +void HandleChildFrameDestroyed(object sender, object args) { + CoreWebView2Frame frame = (CoreWebView2Frame)sender; + MessageBox.Show(this, "Id: " + frame.FrameId + " FrameDestroyed", "Child frame Destroyed", MessageBoxButton.OK); +} + +void HandleChildFrameNavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs args) +{ + CoreWebView2Frame frame = (CoreWebView2Frame)sender; + MessageBox.Show(this, "Id: " + frame.FrameId + " NavigationStarting", "Child frame Navigation", MessageBoxButton.OK); +} + +void HandleChildFrameContentLoading(object sender, CoreWebView2ContentLoadingEventArgs args) +{ + CoreWebView2Frame frame = (CoreWebView2Frame)sender; + MessageBox.Show(this, "Id: " + frame.FrameId + " ContentLoading", "Child frame Content Loading", MessageBoxButton.OK); +} + +void HandleChildFrameDOMContentLoaded(object sender, CoreWebView2DOMContentLoadedEventArgs args) +{ + CoreWebView2Frame frame = (CoreWebView2Frame)sender; + MessageBox.Show(this, "Id: " + frame.FrameId + " DOMContentLoaded", "Child frame DOM Content Loaded", MessageBoxButton.OK); +} + +void HandleChildFrameNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs args) +{ + CoreWebView2Frame frame = (CoreWebView2Frame)sender; + MessageBox.Show(this, "Id: " + frame.FrameId + " NavigationCompleted", "Child frame Navigation Completed", MessageBoxButton.OK); +} +``` + +# API Details +## C++ +``` +/// Receives `FrameCreated` events. +interface ICoreWebView2FrameNestedFrameCreatedEventHandler : IUnknown { + /// Provides the event args for the corresponding event. + HRESULT Invoke( + [in] ICoreWebView2Frame* sender, + [in] ICoreWebView2FrameCreatedEventArgs* args); +} + +/// This is the ICoreWebView2Frame interface. +interface ICoreWebView2Frame7 : IUnknown { + /// Adds an event handler for the `FrameCreated` event. + /// Raised when a new direct descendant iframe is created. + /// Handle this event to get access to ICoreWebView2Frame objects. + /// Use `ICoreWebView2Frame.add_Destroyed` to listen for when this + /// iframe goes away. + /// + /// \snippet ScenarioWebViewEventMonitor.cpp FrameCreated1 + HRESULT add_FrameCreated( + [in] ICoreWebView2FrameNestedFrameCreatedEventHandler* eventHandler, + [out] EventRegistrationToken* token); + + /// Removes an event handler previously added with `add_FrameCreated`. + HRESULT remove_FrameCreated( + [in] EventRegistrationToken token); +} +``` + +C# +```c# +namespace Microsoft.Web.WebView2.Core +{ + runtimeclass CoreWebView2Frame + { + [interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2Frame7")] + { + event Windows.Foundation.TypedEventHandler FrameCreated; + } + } +} +``` From cc43133110bb482c4b38afab4b8eef90041c2a29 Mon Sep 17 00:00:00 2001 From: Wangsong Jin Date: Tue, 26 Nov 2024 14:41:30 -0800 Subject: [PATCH 2/8] Fixed typo --- specs/NestedFrame.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/NestedFrame.md b/specs/NestedFrame.md index 6cb6ee7b..def042dc 100644 --- a/specs/NestedFrame.md +++ b/specs/NestedFrame.md @@ -243,7 +243,7 @@ interface ICoreWebView2Frame7 : IUnknown { /// Use `ICoreWebView2Frame.add_Destroyed` to listen for when this /// iframe goes away. /// - /// \snippet ScenarioWebViewEventMonitor.cpp FrameCreated1 + /// \snippet ScenarioWebViewEventMonitor.cpp FrameCreated HRESULT add_FrameCreated( [in] ICoreWebView2FrameNestedFrameCreatedEventHandler* eventHandler, [out] EventRegistrationToken* token); From 44eedbe37d61e7190479f42c4d115654742e0810 Mon Sep 17 00:00:00 2001 From: Wangsong Jin Date: Mon, 2 Dec 2024 10:30:19 -0800 Subject: [PATCH 3/8] fixed format and nits --- specs/NestedFrame.md | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/specs/NestedFrame.md b/specs/NestedFrame.md index def042dc..c0953d99 100644 --- a/specs/NestedFrame.md +++ b/specs/NestedFrame.md @@ -2,31 +2,28 @@ CoreWebView2Frame.FrameCreated API === # Background -At present, WebView2 enables developers to track only -first-level iframes, which are the direct child frames -of the main frame. However, we're noticing WebView2 -customers want to manage nested iframes, such as -recording the navigation history for a nested iframe. -To address this, we will introduce the +At present, WebView2 enables developers to track only first-level +iframes, which are the direct child iframes of the main frame. +However, we're noticing WebView2 customers want to manage nested +iframes, such as recording the navigation history for a nested +iframe. To address this, we will introduce the `CoreWebView2Frame.FrameCreated` API. This new API will allow -developers to subscribe to the event when a nested WebView frame -is created, giving them access to all properties, methods, and -events of [CoreWebView2Frame](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2frame) -for the nested WebView frame. +developers to subscribe to the nested iframe creation event, +giving them access to all properties, methods, and events of +[CoreWebView2Frame](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2frame) +for the nested iframe. -To prevent unnecessary performance overhead, WebView2 -does not track any nested WebView frames by default. -It only tracks a webview2 frame if its parent webview2 -frame has subscribed to the `CoreWebView2Frame.FrameCreated` -API. Therefore, WebView2 developers have the flexibility -to choose whether they want to track specific branches of -the frame tree or all webview2 frames. +To prevent unnecessary performance overhead, WebView2 does not track +any nested iframes by default. It only tracks a nested iframe if its +parent iframe(`CoreWebView2Frame`) has subscribed to the +`CoreWebView2Frame.FrameCreated` API. Therefore, WebView2 developers +have the flexibility to choose whether they want to track specific +branches of the iframe tree or all WebView2 iframes. # Examples ### C++ Sample ```cpp EventRegistrationToken m_frameCreatedToken = {}; - void PostFrameCreatedEventMessage(wil::com_ptr webviewFrame); void ScenarioWebViewEventMonitor::InitializeEventView(ICoreWebView2* webviewEventView) @@ -243,7 +240,7 @@ interface ICoreWebView2Frame7 : IUnknown { /// Use `ICoreWebView2Frame.add_Destroyed` to listen for when this /// iframe goes away. /// - /// \snippet ScenarioWebViewEventMonitor.cpp FrameCreated + /// \snippet ScenarioWebViewEventMonitor.cpp AddFrameCreated HRESULT add_FrameCreated( [in] ICoreWebView2FrameNestedFrameCreatedEventHandler* eventHandler, [out] EventRegistrationToken* token); From 8d67f7793f0aedb6ed2c559ca7e33381d0ba2674 Mon Sep 17 00:00:00 2001 From: Wangsong Jin Date: Mon, 2 Dec 2024 11:53:29 -0800 Subject: [PATCH 4/8] format and rephase --- specs/NestedFrame.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/specs/NestedFrame.md b/specs/NestedFrame.md index c0953d99..e774b462 100644 --- a/specs/NestedFrame.md +++ b/specs/NestedFrame.md @@ -4,7 +4,7 @@ CoreWebView2Frame.FrameCreated API # Background At present, WebView2 enables developers to track only first-level iframes, which are the direct child iframes of the main frame. -However, we're noticing WebView2 customers want to manage nested +However, we see that WebView2 customers want to manage nested iframes, such as recording the navigation history for a nested iframe. To address this, we will introduce the `CoreWebView2Frame.FrameCreated` API. This new API will allow @@ -13,12 +13,14 @@ giving them access to all properties, methods, and events of [CoreWebView2Frame](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2frame) for the nested iframe. -To prevent unnecessary performance overhead, WebView2 does not track -any nested iframes by default. It only tracks a nested iframe if its -parent iframe(`CoreWebView2Frame`) has subscribed to the -`CoreWebView2Frame.FrameCreated` API. Therefore, WebView2 developers -have the flexibility to choose whether they want to track specific -branches of the iframe tree or all WebView2 iframes. +To prevent unnecessary performance implication, WebView2 does +not track any nested iframes by default. It only tracks a nested +iframe if its parent iframe(`CoreWebView2Frame`) has subscribed +to the `CoreWebView2Frame.FrameCreated` API. For a page with +multi-level iframes, developers can choose to track only the +main page and first-level iframes (the default behavior), a +partial WebView2 frames tree with specific iframes of interest, +or the full WebView2 frames tree. # Examples ### C++ Sample @@ -182,7 +184,7 @@ void HandleChildFrameCreated(object sender, CoreWebView2FrameCreatedEventArgs ar CoreWebView2Frame childFrame = args.Frame; string name = String.IsNullOrEmpty(childFrame.Name) ? "none" : childFrame.Name; MessageBox.Show(this, "Id: " + childFrame.FrameId + " name: " + name, "Child frame created", MessageBoxButton.OK); - // Make a recursive call to track all nested webview2 frames events. + // Make a recursive call to track all nested webview frames events. childFrame.FrameCreated += HandleChildFrameCreated; childFrame.NavigationStarting += HandleChildFrameNavigationStarting; childFrame.ContentLoading += HandleChildFrameContentLoading; From 6e42f58a5f6090342c3309c5c5be447240f1656b Mon Sep 17 00:00:00 2001 From: Wangsong Jin Date: Wed, 4 Dec 2024 15:48:31 -0800 Subject: [PATCH 5/8] refined the sample code and added impact APIs description --- specs/NestedFrame.md | 315 +++++++++++++++++-------------------------- 1 file changed, 127 insertions(+), 188 deletions(-) diff --git a/specs/NestedFrame.md b/specs/NestedFrame.md index e774b462..230a4a2c 100644 --- a/specs/NestedFrame.md +++ b/specs/NestedFrame.md @@ -5,8 +5,8 @@ CoreWebView2Frame.FrameCreated API At present, WebView2 enables developers to track only first-level iframes, which are the direct child iframes of the main frame. However, we see that WebView2 customers want to manage nested -iframes, such as recording the navigation history for a nested -iframe. To address this, we will introduce the +iframes, such as recording the navigation history for a second +level iframe. To address this, we will introduce the `CoreWebView2Frame.FrameCreated` API. This new API will allow developers to subscribe to the nested iframe creation event, giving them access to all properties, methods, and events of @@ -15,7 +15,7 @@ for the nested iframe. To prevent unnecessary performance implication, WebView2 does not track any nested iframes by default. It only tracks a nested -iframe if its parent iframe(`CoreWebView2Frame`) has subscribed +iframe if its parent iframe (`CoreWebView2Frame`) has subscribed to the `CoreWebView2Frame.FrameCreated` API. For a page with multi-level iframes, developers can choose to track only the main page and first-level iframes (the default behavior), a @@ -25,209 +25,106 @@ or the full WebView2 frames tree. # Examples ### C++ Sample ```cpp -EventRegistrationToken m_frameCreatedToken = {}; -void PostFrameCreatedEventMessage(wil::com_ptr webviewFrame); - -void ScenarioWebViewEventMonitor::InitializeEventView(ICoreWebView2* webviewEventView) -{ - auto webviewEventView4 = webviewEventView.try_query(); - webviewEventView4->add_FrameCreated( +wil::com_ptr m_webview; +std::map> m_frame_navigation_urls; +// In this example, a WebView2 application wants to manage the +// navigation of third-party content residing in second-level iframes +// (Main frame -> First-level frame -> Second-level third-party frames). +HRESULT RecordThirdPartyFrameNavigation() { + auto webview2_4 = m_webView.try_query(); + // Track the first-level webview frame. + webview2_4->add_FrameCreated( Callback( [this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args) -> HRESULT { + // [AddFrameCreated] wil::com_ptr webviewFrame; CHECK_FAILURE(args->get_Frame(&webviewFrame)); - // Track first-level webview frame events. - InitializeFrameEventView(webviewFrame); - PostFrameCreatedEventMessage(webviewFrame); + // Track nested (second-level) webview frame. + auto frame7 = webviewFrame.try_query(); + frame7->add_FrameCreated( + Callback( + [this]( + ICoreWebView2Frame* sender, + ICoreWebView2FrameCreatedEventArgs* args) -> HRESULT + { + wil::com_ptr webviewFrame; + CHECK_FAILURE(args->get_Frame(&webviewFrame)); + wil::com_ptr frame2 = + webviewFrame.try_query(); + if (frame2) + { + // Subscribe to nested (second-level) webview frame navigation + // starting event. + frame2->add_NavigationStarting( + Callback( + [this]( + ICoreWebView2Frame* sender, + ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT { + // Manage the navigation, e.g. cancel the + // navigation if it's on block list. + UINT32 frameId = 0; + auto frame5 = wil::com_ptr(sender) + .try_query(); + CHECK_FAILURE(frame5->get_FrameId(&frameId)); + wil::unique_cotaskmem_string uri; + CHECK_FAILURE(args->get_Uri(&uri)); + // Log the navigation history per frame Id. + m_frame_navigation_urls[(int)frameId].push_back(uri.get()); + return S_OK; + }) + .Get(), + nullptr); + } + return S_OK; + }) + .Get(), + nullptr); + // [AddFrameCreated] return S_OK; }) .Get(), - &m_frameCreatedToken); -} - -// Track the creation, destruction, and navigation events of webview frames. -void ScenarioWebViewEventMonitor::InitializeFrameEventView( - wil::com_ptr webviewFrame) { - auto frame7 = webviewFrame.try_query(); - if (frame7) - { - //! [AddFrameCreated] - frame7->add_FrameCreated( - Callback( - [this]( - ICoreWebView2Frame* sender, - ICoreWebView2FrameCreatedEventArgs* args) -> HRESULT - { - wil::com_ptr webviewFrame; - CHECK_FAILURE(args->get_Frame(&webviewFrame)); - // Make a recursive call to track all nested - // webview frame events. - InitializeFrameEventView(webviewFrame); - PostFrameCreatedEventMessage(webviewFrame); - return S_OK; - }) - .Get(), - &m_frameCreatedToken); - //! [AddFrameCreated] - } - - // Subscribe to webview frame destroyed event. - webviewFrame->add_Destroyed( - Callback( - [this](ICoreWebView2Frame* sender, IUnknown* args) -> HRESULT - { - wil::unique_cotaskmem_string name; - CHECK_FAILURE(sender->get_Name(&name)); - std::wstring message = L"{ \"kind\": \"event\", \"name\": " - L"\"CoreWebView2Frame::Destroyed\", \"args\": {"; - message += L"\"frame name\": " + EncodeQuote(name.get()); - message += - L"}" + WebViewPropertiesToJsonString(m_webviewEventSource.get()) + L"}"; - PostEventMessage(message); - return S_OK; - }) - .Get(), - NULL); - - // Subscribe to webview frame navigation events. - wil::com_ptr frame2 = webviewFrame.try_query(); - if (frame2) - { - frame2->add_NavigationStarting( - Callback( - [this]( - ICoreWebView2Frame* sender, - ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT { - std::wstring message = NavigationStartingArgsToJsonString( - m_webviewEventSource.get(), args, - L"CoreWebView2Frame::NavigationStarting"); - PostEventMessage(message); - return S_OK; - }) - .Get(), - NULL); - - frame2->add_ContentLoading( - Callback( - [this](ICoreWebView2Frame* sender, ICoreWebView2ContentLoadingEventArgs* args) - -> HRESULT { - std::wstring message = ContentLoadingArgsToJsonString( - m_webviewEventSource.get(), args, L"CoreWebView2Frame::ContentLoading"); - PostEventMessage(message); - return S_OK; - }) - .Get(), - NULL); - - frame2->add_DOMContentLoaded( - Callback( - [this](ICoreWebView2Frame* sender, ICoreWebView2DOMContentLoadedEventArgs* args) - -> HRESULT { - std::wstring message = DOMContentLoadedArgsToJsonString( - m_webviewEventSource.get(), args, - L"CoreWebView2Frame::DOMContentLoaded"); - PostEventMessage(message); - return S_OK; - }) - .Get(), - NULL); - - frame2->add_NavigationCompleted( - Callback( - [this]( - ICoreWebView2Frame* sender, - ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT { - std::wstring message = NavigationCompletedArgsToJsonString( - m_webviewEventSource.get(), args, - L"CoreWebView2Frame::NavigationCompleted"); - PostEventMessage(message); - return S_OK; - }) - .Get(), - NULL); - } -} - -void ScenarioWebViewEventMonitor::PostFrameCreatedEventMessage(wil::com_ptr webviewFrame) { - wil::unique_cotaskmem_string name; - CHECK_FAILURE(webviewFrame->get_Name(&name)); - auto frame5 = webviewFrame.try_query(); - if (frame5) - { - UINT32 frameId = 0; - CHECK_FAILURE(frame5->get_FrameId(&frameId)); - } - - std::wstring message = - L"{ \"kind\": \"event\", \"name\": \"FrameCreated\", \"args\": {"; - - message += L"\"frame\": " + EncodeQuote(name.get()); - message += L",\"webview frame Id\": " + std::to_wstring((int)frameId) + L"}"; - message += - WebViewPropertiesToJsonString(m_webview.get()); - message += L"}"; - PostEventMessage(message); + nullptr); } ``` ### C# Sample ```c# -// Track first-level webview frame created event. -void ChildFrameEventsExecuted(object target, ExecutedRoutedEventArgs e) -{ - webView.CoreWebView2.FrameCreated += HandleChildFrameCreated; -} - -// Track the creation, destruction, and navigation events of webview frames. -void HandleChildFrameCreated(object sender, CoreWebView2FrameCreatedEventArgs args) -{ - CoreWebView2Frame childFrame = args.Frame; - string name = String.IsNullOrEmpty(childFrame.Name) ? "none" : childFrame.Name; - MessageBox.Show(this, "Id: " + childFrame.FrameId + " name: " + name, "Child frame created", MessageBoxButton.OK); - // Make a recursive call to track all nested webview frames events. - childFrame.FrameCreated += HandleChildFrameCreated; - childFrame.NavigationStarting += HandleChildFrameNavigationStarting; - childFrame.ContentLoading += HandleChildFrameContentLoading; - childFrame.DOMContentLoaded += HandleChildFrameDOMContentLoaded; - childFrame.NavigationCompleted += HandleChildFrameNavigationCompleted; - childFrame.Destroyed += HandleChildFrameDestroyed; -} - -void HandleChildFrameDestroyed(object sender, object args) { - CoreWebView2Frame frame = (CoreWebView2Frame)sender; - MessageBox.Show(this, "Id: " + frame.FrameId + " FrameDestroyed", "Child frame Destroyed", MessageBoxButton.OK); -} - -void HandleChildFrameNavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs args) -{ - CoreWebView2Frame frame = (CoreWebView2Frame)sender; - MessageBox.Show(this, "Id: " + frame.FrameId + " NavigationStarting", "Child frame Navigation", MessageBoxButton.OK); -} - -void HandleChildFrameContentLoading(object sender, CoreWebView2ContentLoadingEventArgs args) -{ - CoreWebView2Frame frame = (CoreWebView2Frame)sender; - MessageBox.Show(this, "Id: " + frame.FrameId + " ContentLoading", "Child frame Content Loading", MessageBoxButton.OK); -} - -void HandleChildFrameDOMContentLoaded(object sender, CoreWebView2DOMContentLoadedEventArgs args) -{ - CoreWebView2Frame frame = (CoreWebView2Frame)sender; - MessageBox.Show(this, "Id: " + frame.FrameId + " DOMContentLoaded", "Child frame DOM Content Loaded", MessageBoxButton.OK); +var _frameNavigationUrls = new Dictionary>(); +// In this example, a WebView2 application wants to manage the +// navigation of third-party content residing in second-level iframes +// (Main frame -> First-level frame -> second-level third-party frames). +void RecordThirdPartyFrameNavigation() { + webView.CoreWebView2.FrameCreated += (sender, args) => + { + // Track nested (second-level) webview frame. + args.Frame.FrameCreated += (frameCreatedSender, frameCreatedArgs) => + { + CoreWebView2Frame childFrame = frameCreatedArgs.Frame; + childFrame.NavigationStarting += HandleChildFrameNavigationStarting; + } + } } -void HandleChildFrameNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs args) +void HandleChildFrameNavigationStarting(object sender, + CoreWebView2NavigationStartingEventArgs args) { + // Manage the navigation, e.g. cancel the navigation + // if it's on block list. CoreWebView2Frame frame = (CoreWebView2Frame)sender; - MessageBox.Show(this, "Id: " + frame.FrameId + " NavigationCompleted", "Child frame Navigation Completed", MessageBoxButton.OK); + if (!_frameNavigationUrls.ContainsKey(frame.FrameId)) + { + _frameNavigationUrls[frame.FrameId] = new List(); + } + // Log the navigation history per frame Id. + _frameNavigationUrls[frame.FrameId].Add(args.Uri); } ``` # API Details ## C++ -``` +```C++ /// Receives `FrameCreated` events. -interface ICoreWebView2FrameNestedFrameCreatedEventHandler : IUnknown { +interface ICoreWebView2FrameChildFrameCreatedEventHandler : IUnknown { /// Provides the event args for the corresponding event. HRESULT Invoke( [in] ICoreWebView2Frame* sender, @@ -238,13 +135,13 @@ interface ICoreWebView2FrameNestedFrameCreatedEventHandler : IUnknown { interface ICoreWebView2Frame7 : IUnknown { /// Adds an event handler for the `FrameCreated` event. /// Raised when a new direct descendant iframe is created. - /// Handle this event to get access to ICoreWebView2Frame objects. - /// Use `ICoreWebView2Frame.add_Destroyed` to listen for when this + /// Handle this event to get access to `ICoreWebView2Frame` objects. + /// Use `ICoreWebView2Frame::add_Destroyed` to listen for when this /// iframe goes away. /// /// \snippet ScenarioWebViewEventMonitor.cpp AddFrameCreated HRESULT add_FrameCreated( - [in] ICoreWebView2FrameNestedFrameCreatedEventHandler* eventHandler, + [in] ICoreWebView2FrameChildFrameCreatedEventHandler* eventHandler, [out] EventRegistrationToken* token); /// Removes an event handler previously added with `add_FrameCreated`. @@ -253,7 +150,7 @@ interface ICoreWebView2Frame7 : IUnknown { } ``` -C# +## C# ```c# namespace Microsoft.Web.WebView2.Core { @@ -266,3 +163,45 @@ namespace Microsoft.Web.WebView2.Core } } ``` + +# Appendix +## Impacted API +### `CoreWebView2Frame.PermissionRequested` and `CoreWebView2Frame.ScreenCaptureStarting` +In the current case of nested iframes, the [PermissionRequested](https://learn.microsoft.com/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame#permissionrequested) +and [ScreenCaptureStarting](https://learn.microsoft.com/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame#screencapturestarting) +events will be raised from the top-level iframe. With the support +of tracking nested iframes, this request can be handled directly +by the nested iframe. Therefore, we now raise these requests to +the nearest tracked frame, which is the `CoreWebView2Frame` closest +to the frame that initiates the request (from bottom to top). +``` +// Example: +// A (main frame/CoreWebView2) +// | +// B (first-level iframe/CoreWebView2Frame) +// | +// C (nested iframe) +// | +// D (nested iframe) +``` +Suppose there's a `PermissionRequest` comes from D. +* If D is a tracked frame (`CoreWebView2Frame`), then D is the +closet tracked frame from which the request will be raised from. +* If D is not being tracked, and C is a tracked frame. Then C +is the closet tracked frame from which the request will be +raised from. +* If neither C nor D is tracked, then B is the closet tracked +frame from which the request will be raised. This case applies +to current `PermissionRequested` developers, as they haven't +subscribe to the `CoreWebView2Frame.FrameCreated` event. +Therefore, requests originating from iframes will still be +raised from the first-level iframe. + +### `CoreWebView2.ProcessFailed` +With the support of tracking nested iframes, the processes +which support these nested iframes will be also tracked by +[ProcessFailed](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2.processfailed) +As we only track processes running tracked iframes, existing +developers will not receive any process failed events specific +to nested iframes as they haven't subscribe to the +`CoreWebView2Frame.FrameCreated` event. From 1fd53bc44d291581f8c95c8d7ac2240bf9f67b4a Mon Sep 17 00:00:00 2001 From: Wangsong Jin Date: Wed, 4 Dec 2024 15:52:07 -0800 Subject: [PATCH 6/8] fixed nit --- specs/NestedFrame.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/NestedFrame.md b/specs/NestedFrame.md index 230a4a2c..5840d69d 100644 --- a/specs/NestedFrame.md +++ b/specs/NestedFrame.md @@ -200,7 +200,7 @@ raised from the first-level iframe. ### `CoreWebView2.ProcessFailed` With the support of tracking nested iframes, the processes which support these nested iframes will be also tracked by -[ProcessFailed](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2.processfailed) +[ProcessFailed](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2.processfailed). As we only track processes running tracked iframes, existing developers will not receive any process failed events specific to nested iframes as they haven't subscribe to the From 04b1b948f644e170f4c94592fa74a98febd826dd Mon Sep 17 00:00:00 2001 From: Wangsong Jin Date: Thu, 5 Dec 2024 11:22:49 -0800 Subject: [PATCH 7/8] added more description for impacted API --- specs/NestedFrame.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/specs/NestedFrame.md b/specs/NestedFrame.md index 5840d69d..aca036e2 100644 --- a/specs/NestedFrame.md +++ b/specs/NestedFrame.md @@ -170,8 +170,8 @@ namespace Microsoft.Web.WebView2.Core In the current case of nested iframes, the [PermissionRequested](https://learn.microsoft.com/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame#permissionrequested) and [ScreenCaptureStarting](https://learn.microsoft.com/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame#screencapturestarting) events will be raised from the top-level iframe. With the support -of tracking nested iframes, this request can be handled directly -by the nested iframe. Therefore, we now raise these requests to +of tracking nested iframes, we can now handle these requests directly +within the nested iframe. Specifically, these requests are raised to the nearest tracked frame, which is the `CoreWebView2Frame` closest to the frame that initiates the request (from bottom to top). ``` @@ -197,6 +197,14 @@ subscribe to the `CoreWebView2Frame.FrameCreated` event. Therefore, requests originating from iframes will still be raised from the first-level iframe. +If the `PermissionRequested` event is not handled in the current +tracked frame, the request will propagate to its parent +`CoreWebView2Frame`, or to `CoreWebView2` if the parent frame +is the main frame. For example, if frame D is tracked but does +not handle the request, the request will bubble up to frame C. +If frame C handles the request, it will not propagate further +to its parent frame B. + ### `CoreWebView2.ProcessFailed` With the support of tracking nested iframes, the processes which support these nested iframes will be also tracked by From 4d000288719462770103db1b99d7d50a8f5bef7b Mon Sep 17 00:00:00 2001 From: Wangsong Jin Date: Thu, 12 Dec 2024 16:59:02 -0800 Subject: [PATCH 8/8] addressed comments from API review meeting --- specs/NestedFrame.md | 275 ++++++++++++++++++++++++++++++++----------- 1 file changed, 206 insertions(+), 69 deletions(-) diff --git a/specs/NestedFrame.md b/specs/NestedFrame.md index aca036e2..35356a8c 100644 --- a/specs/NestedFrame.md +++ b/specs/NestedFrame.md @@ -13,99 +13,118 @@ giving them access to all properties, methods, and events of [CoreWebView2Frame](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2frame) for the nested iframe. -To prevent unnecessary performance implication, WebView2 does -not track any nested iframes by default. It only tracks a nested -iframe if its parent iframe (`CoreWebView2Frame`) has subscribed -to the `CoreWebView2Frame.FrameCreated` API. For a page with -multi-level iframes, developers can choose to track only the -main page and first-level iframes (the default behavior), a -partial WebView2 frames tree with specific iframes of interest, -or the full WebView2 frames tree. +With the new API, developers can manage iframe tracking on a page +contains multiple levels of iframes. They can choose track only the +main page and first-level iframes (the default behavior), a partial +WebView2 frames tree with specific iframes of interest, or the full +WebView2 frames tree. # Examples +## Track partial WebView2 Frames Tree ### C++ Sample -```cpp +```cpp wil::com_ptr m_webview; -std::map> m_frame_navigation_urls; -// In this example, a WebView2 application wants to manage the -// navigation of third-party content residing in second-level iframes +std::map> m_frame_navigation_urls; +// In this example, we present a scenario where a WebView2 application wants to +// manage the navigation of third-party content residing in second-level iframes // (Main frame -> First-level frame -> Second-level third-party frames). -HRESULT RecordThirdPartyFrameNavigation() { +void TrackThirdPartyFrameNavigations() +{ auto webview2_4 = m_webView.try_query(); - // Track the first-level webview frame. - webview2_4->add_FrameCreated( + if (webview2_4) + { + webview2_4->add_FrameCreated( Callback( - [this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args) - -> HRESULT { + [this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args) + noexcept -> HRESULT + { // [AddFrameCreated] wil::com_ptr webviewFrame; CHECK_FAILURE(args->get_Frame(&webviewFrame)); + // Track nested (second-level) webview frame. auto frame7 = webviewFrame.try_query(); - frame7->add_FrameCreated( - Callback( - [this]( - ICoreWebView2Frame* sender, - ICoreWebView2FrameCreatedEventArgs* args) -> HRESULT - { - wil::com_ptr webviewFrame; - CHECK_FAILURE(args->get_Frame(&webviewFrame)); - wil::com_ptr frame2 = - webviewFrame.try_query(); - if (frame2) + if (frame7) + { + frame7->add_FrameCreated( + Callback( + [this]( + ICoreWebView2Frame* sender, + ICoreWebView2FrameCreatedEventArgs* args) noexcept + -> HRESULT { - // Subscribe to nested (second-level) webview frame navigation - // starting event. - frame2->add_NavigationStarting( - Callback( - [this]( - ICoreWebView2Frame* sender, - ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT { - // Manage the navigation, e.g. cancel the - // navigation if it's on block list. - UINT32 frameId = 0; - auto frame5 = wil::com_ptr(sender) - .try_query(); - CHECK_FAILURE(frame5->get_FrameId(&frameId)); - wil::unique_cotaskmem_string uri; - CHECK_FAILURE(args->get_Uri(&uri)); - // Log the navigation history per frame Id. - m_frame_navigation_urls[(int)frameId].push_back(uri.get()); - return S_OK; - }) - .Get(), - nullptr); - } - return S_OK; - }) - .Get(), - nullptr); + wil::com_ptr webviewFrame; + CHECK_FAILURE(args->get_Frame(&webviewFrame)); + + wil::com_ptr frame2 = + webviewFrame.try_query(); + if (frame2) + { + // Subscribe to nested (second-level) webview frame + // navigation starting event. + frame2->add_NavigationStarting( + Callback< + ICoreWebView2FrameNavigationStartingEventHandler>( + [this]( + ICoreWebView2Frame* sender, + ICoreWebView2NavigationStartingEventArgs* args) + noexcept -> HRESULT + { + // Manage the navigation, e.g. cancel the + // navigation if it's on block list. + + UINT32 frameId = 0; + auto frame5 = + wil::try_query(sender); + if (frame5) + { + CHECK_FAILURE( + frame5->get_FrameId(&frameId)); + } + wil::unique_cotaskmem_string uri; + CHECK_FAILURE(args->get_Uri(&uri)); + + // Log the navigation history per frame Id. + m_frame_navigation_urls[frameId].push_back(uri.get()); + return S_OK; + }) + .Get(), + nullptr); + } + return S_OK; + }) + .Get(), + nullptr); + } // [AddFrameCreated] return S_OK; }) .Get(), - nullptr); + nullptr); + } } + ``` ### C# Sample ```c# var _frameNavigationUrls = new Dictionary>(); -// In this example, a WebView2 application wants to manage the -// navigation of third-party content residing in second-level iframes +// In this example, we present a scenario where a WebView2 application wants to +// manage the navigation of third-party content residing in second-level iframes // (Main frame -> First-level frame -> second-level third-party frames). -void RecordThirdPartyFrameNavigation() { +void TrackThirdPartyFrameNavigations() +{ webView.CoreWebView2.FrameCreated += (sender, args) => { // Track nested (second-level) webview frame. args.Frame.FrameCreated += (frameCreatedSender, frameCreatedArgs) => { CoreWebView2Frame childFrame = frameCreatedArgs.Frame; - childFrame.NavigationStarting += HandleChildFrameNavigationStarting; - } - } + childFrame.NavigationStarting += OnFrameNavigationStarting; + }; + }; } -void HandleChildFrameNavigationStarting(object sender, +void OnFrameNavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs args) { // Manage the navigation, e.g. cancel the navigation @@ -120,6 +139,123 @@ void HandleChildFrameNavigationStarting(object sender, } ``` +## Track entire WebView2 Frames Tree +### C++ Sample +```C++ +wil::com_ptr m_webview; +std::map> m_frame_navigation_urls; +void OnFrameCreated(wil::com_ptr webviewFrame); +// In this example, we present a scenario where a WebView2 application +// wants to manage the navigation in all iframes. +void TrackAllFrameNavigations() +{ + auto webview2_4 = m_webview.try_query(); + if (webview2_4) + { + webview2_4->add_FrameCreated( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args) + noexcept -> HRESULT + { + wil::com_ptr webviewFrame; + CHECK_FAILURE(args->get_Frame(&webviewFrame)); + // Track first-level webview frame. + OnFrameCreated(webviewFrame); + return S_OK; + }) + .Get(), + nullptr); + } +} + +void OnFrameCreated(wil::com_ptr webviewFrame) +{ + auto frame7 = webviewFrame.try_query(); + if (frame7) + { + //! [AddFrameCreated] + frame7->add_FrameCreated( + Callback( + [this]( + ICoreWebView2Frame* sender, + ICoreWebView2FrameCreatedEventArgs* args) noexcept -> HRESULT + { + wil::com_ptr webviewFrame; + CHECK_FAILURE(args->get_Frame(&webviewFrame)); + // Make a recursive call to track all nested + // webview frame. + OnFrameCreated(webviewFrame); + return S_OK; + }) + .Get(), + nullptr); + //! [AddFrameCreated] + } + + // Subscribe to webview frame navigation starting event. + wil::com_ptr frame2 = webviewFrame.try_query(); + if (frame2) + { + frame2->add_NavigationStarting( + Callback( + [this]( + ICoreWebView2Frame* sender, + ICoreWebView2NavigationStartingEventArgs* args) noexcept -> HRESULT + { + // Manage the navigation, e.g. cancel the + // navigation if it's on block list. + + UINT32 frameId = 0; + auto frame5 = wil::try_query(sender); + if (frame5) + { + CHECK_FAILURE(frame5->get_FrameId(&frameId)); + } + wil::unique_cotaskmem_string uri; + CHECK_FAILURE(args->get_Uri(&uri)); + + // Log the navigation history per frame Id. + m_frame_navigation_urls[frameId].push_back(uri.get()); + return S_OK; + }) + .Get(), + nullptr); + } +} +``` + +### C# Sample +```C# +var _frameNavigationUrls = new Dictionary>(); +// In this example, we present a scenario where a WebView2 application +// wants to manage the navigation in all iframes. +void TrackAllFrameNavigations(object target, ExecutedRoutedEventArgs e) +{ + webView.CoreWebView2.FrameCreated += OnFrameCreated; +} + +void OnFrameCreated(object sender, CoreWebView2FrameCreatedEventArgs args) +{ + CoreWebView2Frame childFrame = args.Frame; + // Make a recursive call to track all nested webview frames event. + childFrame.FrameCreated += OnFrameCreated; + childFrame.NavigationStarting += OnFrameNavigationStarting; +} + +void OnFrameNavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs args) +{ + // Manage the navigation, e.g. cancel the navigation + // if it's on block list. + CoreWebView2Frame frame = (CoreWebView2Frame)sender; + if (!_frameNavigationUrls.ContainsKey(frame.FrameId)) + { + _frameNavigationUrls[frame.FrameId] = new List(); + } + // Log the navigation history per frame Id. + _frameNavigationUrls[frame.FrameId].Add(args.Uri); +} +``` + # API Details ## C++ ```C++ @@ -186,16 +322,17 @@ to the frame that initiates the request (from bottom to top). ``` Suppose there's a `PermissionRequest` comes from D. * If D is a tracked frame (`CoreWebView2Frame`), then D is the -closet tracked frame from which the request will be raised from. +closest tracked frame from which the request will be raised from. * If D is not being tracked, and C is a tracked frame. Then C -is the closet tracked frame from which the request will be +is the closest tracked frame from which the request will be raised from. -* If neither C nor D is tracked, then B is the closet tracked +* If neither C nor D is tracked, then B is the closest tracked frame from which the request will be raised. This case applies to current `PermissionRequested` developers, as they haven't -subscribe to the `CoreWebView2Frame.FrameCreated` event. -Therefore, requests originating from iframes will still be -raised from the first-level iframe. +subscribed to the `CoreWebView2Frame.FrameCreated` event. +Consequently, there is no change in behavior, and requests +originating from iframes will continue to be raised from the +first-level iframe. If the `PermissionRequested` event is not handled in the current tracked frame, the request will propagate to its parent @@ -211,5 +348,5 @@ which support these nested iframes will be also tracked by [ProcessFailed](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2.processfailed). As we only track processes running tracked iframes, existing developers will not receive any process failed events specific -to nested iframes as they haven't subscribe to the +to nested iframes as they haven't subscribed to the `CoreWebView2Frame.FrameCreated` event.