Skip to content

Commit 7fef919

Browse files
Merge pull request #4982 from MicrosoftEdge/NestedFrame
API Review: CoreWebView2Frame.FrameCreated
2 parents 521e85a + 4d00028 commit 7fef919

File tree

1 file changed

+352
-0
lines changed

1 file changed

+352
-0
lines changed

specs/NestedFrame.md

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
CoreWebView2Frame.FrameCreated API
2+
===
3+
4+
# Background
5+
At present, WebView2 enables developers to track only first-level
6+
iframes, which are the direct child iframes of the main frame.
7+
However, we see that WebView2 customers want to manage nested
8+
iframes, such as recording the navigation history for a second
9+
level iframe. To address this, we will introduce the
10+
`CoreWebView2Frame.FrameCreated` API. This new API will allow
11+
developers to subscribe to the nested iframe creation event,
12+
giving them access to all properties, methods, and events of
13+
[CoreWebView2Frame](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2frame)
14+
for the nested iframe.
15+
16+
With the new API, developers can manage iframe tracking on a page
17+
contains multiple levels of iframes. They can choose track only the
18+
main page and first-level iframes (the default behavior), a partial
19+
WebView2 frames tree with specific iframes of interest, or the full
20+
WebView2 frames tree.
21+
22+
# Examples
23+
## Track partial WebView2 Frames Tree
24+
### C++ Sample
25+
```cpp
26+
wil::com_ptr<ICoreWebView2> m_webview;
27+
std::map<UINT32, std::vector<std::wstring>> m_frame_navigation_urls;
28+
// In this example, we present a scenario where a WebView2 application wants to
29+
// manage the navigation of third-party content residing in second-level iframes
30+
// (Main frame -> First-level frame -> Second-level third-party frames).
31+
void TrackThirdPartyFrameNavigations()
32+
{
33+
auto webview2_4 = m_webView.try_query<ICoreWebView2_4>();
34+
if (webview2_4)
35+
{
36+
webview2_4->add_FrameCreated(
37+
Callback<ICoreWebView2FrameCreatedEventHandler>(
38+
[this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args)
39+
noexcept -> HRESULT
40+
{
41+
// [AddFrameCreated]
42+
wil::com_ptr<ICoreWebView2Frame> webviewFrame;
43+
CHECK_FAILURE(args->get_Frame(&webviewFrame));
44+
45+
// Track nested (second-level) webview frame.
46+
auto frame7 = webviewFrame.try_query<ICoreWebView2Frame7>();
47+
if (frame7)
48+
{
49+
frame7->add_FrameCreated(
50+
Callback<ICoreWebView2FrameChildFrameCreatedEventHandler>(
51+
[this](
52+
ICoreWebView2Frame* sender,
53+
ICoreWebView2FrameCreatedEventArgs* args) noexcept
54+
-> HRESULT
55+
{
56+
wil::com_ptr<ICoreWebView2Frame> webviewFrame;
57+
CHECK_FAILURE(args->get_Frame(&webviewFrame));
58+
59+
wil::com_ptr<ICoreWebView2Frame2> frame2 =
60+
webviewFrame.try_query<ICoreWebView2Frame2>();
61+
if (frame2)
62+
{
63+
// Subscribe to nested (second-level) webview frame
64+
// navigation starting event.
65+
frame2->add_NavigationStarting(
66+
Callback<
67+
ICoreWebView2FrameNavigationStartingEventHandler>(
68+
[this](
69+
ICoreWebView2Frame* sender,
70+
ICoreWebView2NavigationStartingEventArgs* args)
71+
noexcept -> HRESULT
72+
{
73+
// Manage the navigation, e.g. cancel the
74+
// navigation if it's on block list.
75+
76+
UINT32 frameId = 0;
77+
auto frame5 =
78+
wil::try_query<ICoreWebView2Frame5>(sender);
79+
if (frame5)
80+
{
81+
CHECK_FAILURE(
82+
frame5->get_FrameId(&frameId));
83+
}
84+
wil::unique_cotaskmem_string uri;
85+
CHECK_FAILURE(args->get_Uri(&uri));
86+
87+
// Log the navigation history per frame Id.
88+
m_frame_navigation_urls[frameId].push_back(uri.get());
89+
return S_OK;
90+
})
91+
.Get(),
92+
nullptr);
93+
}
94+
return S_OK;
95+
})
96+
.Get(),
97+
nullptr);
98+
}
99+
// [AddFrameCreated]
100+
return S_OK;
101+
})
102+
.Get(),
103+
nullptr);
104+
}
105+
}
106+
107+
```
108+
### C# Sample
109+
```c#
110+
var _frameNavigationUrls = new Dictionary<UINT32, List<string>>();
111+
// In this example, we present a scenario where a WebView2 application wants to
112+
// manage the navigation of third-party content residing in second-level iframes
113+
// (Main frame -> First-level frame -> second-level third-party frames).
114+
void TrackThirdPartyFrameNavigations()
115+
{
116+
webView.CoreWebView2.FrameCreated += (sender, args) =>
117+
{
118+
// Track nested (second-level) webview frame.
119+
args.Frame.FrameCreated += (frameCreatedSender, frameCreatedArgs) =>
120+
{
121+
CoreWebView2Frame childFrame = frameCreatedArgs.Frame;
122+
childFrame.NavigationStarting += OnFrameNavigationStarting;
123+
};
124+
};
125+
}
126+
127+
void OnFrameNavigationStarting(object sender,
128+
CoreWebView2NavigationStartingEventArgs args)
129+
{
130+
// Manage the navigation, e.g. cancel the navigation
131+
// if it's on block list.
132+
CoreWebView2Frame frame = (CoreWebView2Frame)sender;
133+
if (!_frameNavigationUrls.ContainsKey(frame.FrameId))
134+
{
135+
_frameNavigationUrls[frame.FrameId] = new List<string>();
136+
}
137+
// Log the navigation history per frame Id.
138+
_frameNavigationUrls[frame.FrameId].Add(args.Uri);
139+
}
140+
```
141+
142+
## Track entire WebView2 Frames Tree
143+
### C++ Sample
144+
```C++
145+
wil::com_ptr<ICoreWebView2> m_webview;
146+
std::map<UINT32, std::vector<std::wstring>> m_frame_navigation_urls;
147+
void OnFrameCreated(wil::com_ptr<ICoreWebView2Frame> webviewFrame);
148+
// In this example, we present a scenario where a WebView2 application
149+
// wants to manage the navigation in all iframes.
150+
void TrackAllFrameNavigations()
151+
{
152+
auto webview2_4 = m_webview.try_query<ICoreWebView2_4>();
153+
if (webview2_4)
154+
{
155+
webview2_4->add_FrameCreated(
156+
Callback<ICoreWebView2FrameCreatedEventHandler>(
157+
[this](ICoreWebView2* sender, ICoreWebView2FrameCreatedEventArgs* args)
158+
noexcept -> HRESULT
159+
{
160+
wil::com_ptr<ICoreWebView2Frame> webviewFrame;
161+
CHECK_FAILURE(args->get_Frame(&webviewFrame));
162+
// Track first-level webview frame.
163+
OnFrameCreated(webviewFrame);
164+
return S_OK;
165+
})
166+
.Get(),
167+
nullptr);
168+
}
169+
}
170+
171+
void OnFrameCreated(wil::com_ptr<ICoreWebView2Frame> webviewFrame)
172+
{
173+
auto frame7 = webviewFrame.try_query<ICoreWebView2Frame7>();
174+
if (frame7)
175+
{
176+
//! [AddFrameCreated]
177+
frame7->add_FrameCreated(
178+
Callback<ICoreWebView2FrameChildFrameCreatedEventHandler>(
179+
[this](
180+
ICoreWebView2Frame* sender,
181+
ICoreWebView2FrameCreatedEventArgs* args) noexcept -> HRESULT
182+
{
183+
wil::com_ptr<ICoreWebView2Frame> webviewFrame;
184+
CHECK_FAILURE(args->get_Frame(&webviewFrame));
185+
// Make a recursive call to track all nested
186+
// webview frame.
187+
OnFrameCreated(webviewFrame);
188+
return S_OK;
189+
})
190+
.Get(),
191+
nullptr);
192+
//! [AddFrameCreated]
193+
}
194+
195+
// Subscribe to webview frame navigation starting event.
196+
wil::com_ptr<ICoreWebView2Frame2> frame2 = webviewFrame.try_query<ICoreWebView2Frame2>();
197+
if (frame2)
198+
{
199+
frame2->add_NavigationStarting(
200+
Callback<ICoreWebView2FrameNavigationStartingEventHandler>(
201+
[this](
202+
ICoreWebView2Frame* sender,
203+
ICoreWebView2NavigationStartingEventArgs* args) noexcept -> HRESULT
204+
{
205+
// Manage the navigation, e.g. cancel the
206+
// navigation if it's on block list.
207+
208+
UINT32 frameId = 0;
209+
auto frame5 = wil::try_query<ICoreWebView2Frame5>(sender);
210+
if (frame5)
211+
{
212+
CHECK_FAILURE(frame5->get_FrameId(&frameId));
213+
}
214+
wil::unique_cotaskmem_string uri;
215+
CHECK_FAILURE(args->get_Uri(&uri));
216+
217+
// Log the navigation history per frame Id.
218+
m_frame_navigation_urls[frameId].push_back(uri.get());
219+
return S_OK;
220+
})
221+
.Get(),
222+
nullptr);
223+
}
224+
}
225+
```
226+
227+
### C# Sample
228+
```C#
229+
var _frameNavigationUrls = new Dictionary<UINT32, List<string>>();
230+
// In this example, we present a scenario where a WebView2 application
231+
// wants to manage the navigation in all iframes.
232+
void TrackAllFrameNavigations(object target, ExecutedRoutedEventArgs e)
233+
{
234+
webView.CoreWebView2.FrameCreated += OnFrameCreated;
235+
}
236+
237+
void OnFrameCreated(object sender, CoreWebView2FrameCreatedEventArgs args)
238+
{
239+
CoreWebView2Frame childFrame = args.Frame;
240+
// Make a recursive call to track all nested webview frames event.
241+
childFrame.FrameCreated += OnFrameCreated;
242+
childFrame.NavigationStarting += OnFrameNavigationStarting;
243+
}
244+
245+
void OnFrameNavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs args)
246+
{
247+
// Manage the navigation, e.g. cancel the navigation
248+
// if it's on block list.
249+
CoreWebView2Frame frame = (CoreWebView2Frame)sender;
250+
if (!_frameNavigationUrls.ContainsKey(frame.FrameId))
251+
{
252+
_frameNavigationUrls[frame.FrameId] = new List<string>();
253+
}
254+
// Log the navigation history per frame Id.
255+
_frameNavigationUrls[frame.FrameId].Add(args.Uri);
256+
}
257+
```
258+
259+
# API Details
260+
## C++
261+
```C++
262+
/// Receives `FrameCreated` events.
263+
interface ICoreWebView2FrameChildFrameCreatedEventHandler : IUnknown {
264+
/// Provides the event args for the corresponding event.
265+
HRESULT Invoke(
266+
[in] ICoreWebView2Frame* sender,
267+
[in] ICoreWebView2FrameCreatedEventArgs* args);
268+
}
269+
270+
/// This is the ICoreWebView2Frame interface.
271+
interface ICoreWebView2Frame7 : IUnknown {
272+
/// Adds an event handler for the `FrameCreated` event.
273+
/// Raised when a new direct descendant iframe is created.
274+
/// Handle this event to get access to `ICoreWebView2Frame` objects.
275+
/// Use `ICoreWebView2Frame::add_Destroyed` to listen for when this
276+
/// iframe goes away.
277+
///
278+
/// \snippet ScenarioWebViewEventMonitor.cpp AddFrameCreated
279+
HRESULT add_FrameCreated(
280+
[in] ICoreWebView2FrameChildFrameCreatedEventHandler* eventHandler,
281+
[out] EventRegistrationToken* token);
282+
283+
/// Removes an event handler previously added with `add_FrameCreated`.
284+
HRESULT remove_FrameCreated(
285+
[in] EventRegistrationToken token);
286+
}
287+
```
288+
289+
## C#
290+
```c#
291+
namespace Microsoft.Web.WebView2.Core
292+
{
293+
runtimeclass CoreWebView2Frame
294+
{
295+
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2Frame7")]
296+
{
297+
event Windows.Foundation.TypedEventHandler<CoreWebView2Frame, CoreWebView2FrameCreatedEventArgs> FrameCreated;
298+
}
299+
}
300+
}
301+
```
302+
303+
# Appendix
304+
## Impacted API
305+
### `CoreWebView2Frame.PermissionRequested` and `CoreWebView2Frame.ScreenCaptureStarting`
306+
In the current case of nested iframes, the [PermissionRequested](https://learn.microsoft.com/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame#permissionrequested)
307+
and [ScreenCaptureStarting](https://learn.microsoft.com/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2frame#screencapturestarting)
308+
events will be raised from the top-level iframe. With the support
309+
of tracking nested iframes, we can now handle these requests directly
310+
within the nested iframe. Specifically, these requests are raised to
311+
the nearest tracked frame, which is the `CoreWebView2Frame` closest
312+
to the frame that initiates the request (from bottom to top).
313+
```
314+
// Example:
315+
// A (main frame/CoreWebView2)
316+
// |
317+
// B (first-level iframe/CoreWebView2Frame)
318+
// |
319+
// C (nested iframe)
320+
// |
321+
// D (nested iframe)
322+
```
323+
Suppose there's a `PermissionRequest` comes from D.
324+
* If D is a tracked frame (`CoreWebView2Frame`), then D is the
325+
closest tracked frame from which the request will be raised from.
326+
* If D is not being tracked, and C is a tracked frame. Then C
327+
is the closest tracked frame from which the request will be
328+
raised from.
329+
* If neither C nor D is tracked, then B is the closest tracked
330+
frame from which the request will be raised. This case applies
331+
to current `PermissionRequested` developers, as they haven't
332+
subscribed to the `CoreWebView2Frame.FrameCreated` event.
333+
Consequently, there is no change in behavior, and requests
334+
originating from iframes will continue to be raised from the
335+
first-level iframe.
336+
337+
If the `PermissionRequested` event is not handled in the current
338+
tracked frame, the request will propagate to its parent
339+
`CoreWebView2Frame`, or to `CoreWebView2` if the parent frame
340+
is the main frame. For example, if frame D is tracked but does
341+
not handle the request, the request will bubble up to frame C.
342+
If frame C handles the request, it will not propagate further
343+
to its parent frame B.
344+
345+
### `CoreWebView2.ProcessFailed`
346+
With the support of tracking nested iframes, the processes
347+
which support these nested iframes will be also tracked by
348+
[ProcessFailed](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2.processfailed).
349+
As we only track processes running tracked iframes, existing
350+
developers will not receive any process failed events specific
351+
to nested iframes as they haven't subscribed to the
352+
`CoreWebView2Frame.FrameCreated` event.

0 commit comments

Comments
 (0)