Skip to content

Commit f31ad77

Browse files
authored
Create CDPSessionIdSupport.md
1 parent a56f59d commit f31ad77

1 file changed

Lines changed: 302 additions & 0 deletions

File tree

specs/CDPSessionIdSupport.md

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
sessionId support for DevToolsProtocol method call and event
2+
===
3+
4+
# Background
5+
A web page can have multiple DevToolsProtocol targets. Besides the default target for the top page, there are separate targets for iframes from different origin and web workers.
6+
7+
The underlying DevToolsProtocol supports interaction with other targets by attaching to them and then using sessionId of the attachment in DevToolsProtocol method call message.
8+
The received DevToolsProtocol event messages also have sessionId field to indicate which target the event comes from.
9+
See [DevToolsProtocol Target domain](https://chromedevtools.github.io/devtools-protocol/tot/Target/) for more details.
10+
11+
However, the current WebView2 DevToolsProtocol APIs like [CallDevToolsProtocolMethod](https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2#calldevtoolsprotocolmethod)
12+
and [DevToolsProtocolEventReceivedEvent](https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2devtoolsprotocoleventreceivedeventargs)
13+
doesn't support sessionId.
14+
15+
To support interaction with different parts of the page, we are adding support for specifying sessionId for DevToolsProtocol method call API
16+
and retrieving sessionId for received DevToolsProtocol events.
17+
18+
# Conceptual pages (How To)
19+
20+
To interact with different targets on the page, call DevToolsProtocol method `Target.setAutoAttach` with `flatten` set as `true`,
21+
and then listen to `Target.attachedToTarget` and `Target.detachedFromTarget` events to manage the sessionId for targets.
22+
23+
As shared worker will not be auto attached, to also interact with shared workers, call DevToolsProtocol method `Target.setDiscoverTargets`,
24+
and then call `Target.attachToTarget` for the shared worker target when `Target.targetCreated` event is received.
25+
26+
To update target info like url of a target, listen to `Target.targetInfoChanged` event.
27+
28+
# Examples
29+
30+
The example below illustrates how to collect messages logged by console.log calls by JavaScipt code from various parts of the web page.
31+
32+
## Win32 C++
33+
```cpp
34+
35+
void MyApp::HandleDevToolsProtocalPTargets()
36+
{
37+
// The sessions and targets descriptions are tracked by 2 maps:
38+
// SessionId to TargetId map:
39+
// std::map<std::wstring, std::wstring> m_devToolsSessionMap;
40+
// TargetId to description map, where description is "<target type>,<target url>".
41+
// std::map<std::wstring, std::wstring> m_devToolsTargetInfoMap;
42+
// GetJSONStringField is a helper function that can retrieve a string field from a json message.
43+
44+
// Enable Runtime events to receive Runtime.consoleAPICalled events.
45+
m_webView->CallDevToolsProtocolMethod(L"Runtime.enable", L"{}", nullptr);
46+
wil::com_ptr<ICoreWebView2DevToolsProtocolEventReceiver> receiver;
47+
CHECK_FAILURE(
48+
m_webView->GetDevToolsProtocolEventReceiver(L"Runtime.consoleAPICalled", &receiver));
49+
CHECK_FAILURE(receiver->add_DevToolsProtocolEventReceived(
50+
Callback<ICoreWebView2DevToolsProtocolEventReceivedEventHandler>(
51+
[this](
52+
ICoreWebView2* sender,
53+
ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT
54+
{
55+
wil::unique_cotaskmem_string parameterObjectAsJson;
56+
CHECK_FAILURE(args->get_ParameterObjectAsJson(&parameterObjectAsJson));
57+
std::wstring eventSource;
58+
std::wstring eventDetails = parameterObjectAsJson.get();
59+
wil::com_ptr<ICoreWebView2DevToolsProtocolEventReceivedEventArgs_2> args2;
60+
if (SUCCEEDED(args->QueryInterface(IID_PPV_ARGS(&args2))))
61+
{
62+
wil::unique_cotaskmem_string sessionId;
63+
CHECK_FAILURE(args2->get_SessionId(&sessionId));
64+
if (sessionId.get() && *sessionId.get())
65+
{
66+
std::wstring targetId = m_devToolsSessionMap[sessionId.get()];
67+
eventSource = m_devToolsTargetDescriptionMap[targetId];
68+
}
69+
}
70+
// App code to log these events.
71+
LogConsoleLogMessage(eventSource, eventDetails);
72+
return S_OK;
73+
})
74+
.Get(),
75+
&m_consoleAPICalledToken));
76+
receiver.reset();
77+
78+
// Track Target and session info via CDP events.
79+
CHECK_FAILURE(
80+
m_webView->GetDevToolsProtocolEventReceiver(L"Target.attachedToTarget", &receiver));
81+
CHECK_FAILURE(receiver->add_DevToolsProtocolEventReceived(
82+
Callback<ICoreWebView2DevToolsProtocolEventReceivedEventHandler>(
83+
[this](
84+
ICoreWebView2* sender,
85+
ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT
86+
{
87+
wil::unique_cotaskmem_string jsonMessage;
88+
CHECK_FAILURE(args->get_ParameterObjectAsJson(&jsonMessage));
89+
std::wstring sessionId = GetJSONStringField(jsonMessage.get(), L"sessionId");
90+
std::wstring targetId = GetJSONStringField(jsonMessage.get(), L"targetId");
91+
m_devToolsSessionMap[sessionId] = targetId;
92+
std::wstring type = GetJSONStringField(jsonMessage.get(), L"type");
93+
std::wstring url = GetJSONStringField(jsonMessage.get(), L"url");
94+
m_devToolsTargetDescriptionMap[targetId] = type + L"," + url;
95+
wil::com_ptr<ICoreWebView2_10> webview2 = m_webView.try_query<ICoreWebView2_10>();
96+
if (webview2)
97+
{
98+
// Auto attach to targets created from this target.
99+
webview2->CallDevToolsProtocolMethodForSession(
100+
sessionId.c_str(), L"Target.setAutoAttach",
101+
LR"({"autoAttach":true,"waitForDebuggerOnStart":false,"flatten":true})",
102+
nullptr);
103+
// Enable Runtime events for the target to receive Runtime.consoleAPICalled from it.
104+
webview2->CallDevToolsProtocolMethodForSession(
105+
sessionId.c_str(), L"Runtime.enable", L"{}", nullptr);
106+
}
107+
return S_OK;
108+
})
109+
.Get(),
110+
&m_targetAttachedToken));
111+
receiver.reset();
112+
CHECK_FAILURE(
113+
m_webView->GetDevToolsProtocolEventReceiver(L"Target.detachedFromTarget", &receiver));
114+
CHECK_FAILURE(receiver->add_DevToolsProtocolEventReceived(
115+
Callback<ICoreWebView2DevToolsProtocolEventReceivedEventHandler>(
116+
[this](
117+
ICoreWebView2* sender,
118+
ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT
119+
{
120+
wil::unique_cotaskmem_string jsonMessage;
121+
CHECK_FAILURE(args->get_ParameterObjectAsJson(&jsonMessage));
122+
std::wstring sessionId = GetJSONStringField(jsonMessage.get(), L"sessionId");
123+
auto session = m_devToolsSessionMap.find(sessionId);
124+
if (session != m_devToolsSessionMap.end())
125+
{
126+
m_devToolsTargetDescriptionMap.erase(session->second);
127+
m_devToolsSessionMap.erase(session);
128+
}
129+
return S_OK;
130+
})
131+
.Get(),
132+
&m_targetDetachedToken));
133+
receiver.reset();
134+
CHECK_FAILURE(
135+
m_webView->GetDevToolsProtocolEventReceiver(L"Target.targetInfoChanged", &receiver));
136+
CHECK_FAILURE(receiver->add_DevToolsProtocolEventReceived(
137+
Callback<ICoreWebView2DevToolsProtocolEventReceivedEventHandler>(
138+
[this](
139+
ICoreWebView2* sender,
140+
ICoreWebView2DevToolsProtocolEventReceivedEventArgs* args) -> HRESULT
141+
{
142+
wil::unique_cotaskmem_string jsonMessage;
143+
CHECK_FAILURE(args->get_ParameterObjectAsJson(&jsonMessage));
144+
std::wstring targetId = GetJSONStringField(jsonMessage.get(), L"targetId");
145+
if (m_devToolsTargetDescriptionMap.find(targetId) !=
146+
m_devToolsTargetDescriptionMap.end())
147+
{
148+
// This is a target that we are interested in, update description.
149+
std::wstring type = GetJSONStringField(jsonMessage.get(), L"type");
150+
std::wstring url = GetJSONStringField(jsonMessage.get(), L"url");
151+
m_devToolsTargetDescriptionMap[targetId] = type + L"," + url;
152+
}
153+
return S_OK;
154+
})
155+
.Get(),
156+
&m_targetInfoChangedToken));
157+
m_webView->CallDevToolsProtocolMethod(
158+
L"Target.setAutoAttach",
159+
LR"({"autoAttach":true,"waitForDebuggerOnStart":false,"flatten":true})", nullptr);
160+
}
161+
```
162+
## WinRT and .NET
163+
```c#
164+
// The sessions and targets descriptions are tracked by 2 dictionaries:
165+
// SessionId to TargetId dictionary: m_devToolsSessionMap;
166+
// TargetId to description dictionary, where description is "<target type>,<target url>": m_devToolsTargetInfoMap
167+
// GetJSONStringField is a helper function that can retrieve a string field from a json message.
168+
169+
private void CoreWebView2_ConsoleAPICalled(CoreWebView2 sender, CoreWebView2DevToolsProtocolEventReceivedEventArgs args)
170+
{
171+
string eventSource;
172+
string sessionId = args.SessionId;
173+
if (sessionId.Length > 0)
174+
{
175+
string targetId = m_devToolsSessionMap[sessionId];
176+
eventSource = m_devToolsTargetDescriptionMap[targetId];
177+
}
178+
// App code to log these events.
179+
LogConsoleLogMessage(eventSource, args.ParameterObjectAsJson);
180+
}
181+
182+
private void CoreWebView2_AttachedToTarget(CoreWebView2 sender, CoreWebView2DevToolsProtocolEventReceivedEventArgs args)
183+
{
184+
string jsonMessage = args.ParameterObjectAsJson;
185+
string sessionId = GetJSONStringField(jsonMessage, L"sessionId");
186+
string targetId = GetJSONStringField(jsonMessage, L"targetId");
187+
m_devToolsSessionMap[sessionId] = targetId;
188+
string type = GetJSONStringField(jsonMessage, L"type");
189+
string url = GetJSONStringField(jsonMessage, L"url");
190+
m_devToolsTargetDescriptionMap[targetId] = type + L"," + url;
191+
// Auto attach to targets created from this target.
192+
_ = m_webview.CallDevToolsProtocolMethodAsync(sessionId, "Target.setAutoAttach",
193+
@"{""autoAttach"":true,""waitForDebuggerOnStart"":false,""flatten"":true}");
194+
// Enable Runtime events to receive Runtime.consoleAPICalled from the target.
195+
m_webview.CallDevToolsProtocolMethodAsync(sessionId, "Runtime.enable", "{}");
196+
}
197+
198+
private void CoreWebView2_DetachedFromTarget(CoreWebView2 sender, CoreWebView2DevToolsProtocolEventReceivedEventArgs args)
199+
{
200+
string jsonMessage = args.ParameterObjectAsJson;
201+
string sessionId = GetJSONStringField(jsonMessage, L"sessionId");
202+
if (m_devToolsSessionMap.ContainsKey(sessionId))
203+
{
204+
m_devToolsTargetDescriptionMap.Remove(m_devToolsSessionMap[sessionId]);
205+
m_devToolsSessionMap.Remove(sessionId);
206+
}
207+
}
208+
209+
private void CoreWebView2_TargetInfoChanged(CoreWebView2 sender, CoreWebView2DevToolsProtocolEventReceivedEventArgs args)
210+
{
211+
string jsonMessage = args.ParameterObjectAsJson;
212+
string targetId = GetJSONStringField(jsonMessage, L"targetId");
213+
if (m_devToolsTargetDescriptionMap.ContainsKey(targetId))
214+
{
215+
// This is a target that we are interested in, update description.
216+
string type = GetJSONStringField(jsonMessage, L"type");
217+
string url = GetJSONStringField(jsonMessage, L"url");
218+
m_devToolsTargetDescriptionMap[targetId] = type + L"," + url;
219+
}
220+
}
221+
private void HandleDevToolsProtocalPTargets()
222+
{
223+
m_webview.GetDevToolsProtocolEventReceiver("Runtime.consoleAPICalled").DevToolsProtocolEventReceived += CoreWebView2_ConsoleAPICalled;
224+
m_webview.GetDevToolsProtocolEventReceiver("Target.attachedToTarget").DevToolsProtocolEventReceived += CoreWebView2_AttachedToTarget;
225+
m_webview.GetDevToolsProtocolEventReceiver("Target.detachedFromTarget").DevToolsProtocolEventReceived += CoreWebView2_DetachedFromTarget;
226+
m_webview.GetDevToolsProtocolEventReceiver("Target.targetInfoChanged").DevToolsProtocolEventReceived += CoreWebView2_TargetInfoChanged;
227+
_ = m_webview.CallDevToolsProtocolMethodAsync("Runtime.enable", "{}");
228+
_ = m_webview.CallDevToolsProtocolMethodAsync("Target.setAutoAttach",
229+
@"{""autoAttach"":true,""waitForDebuggerOnStart"":false,""flatten"":true}");
230+
}
231+
```
232+
233+
# API Details
234+
## Win32 C++
235+
```
236+
interface ICoreWebView2_10 : IUnknown {
237+
/// Runs an asynchronous `DevToolsProtocol` method for a specific session of
238+
/// an attached target.
239+
/// There could be multiple `DevToolsProtocol` targets in a WebView.
240+
/// Besides the top level page, iframes from different origin and web workers
241+
/// are also separate targets. Attaching to these targets allows interaction with them.
242+
/// These attachments to the targets are sessions and are identified by sessionId.
243+
/// To use this API, you should set `flatten` parameter to true when calling
244+
/// `Target.attachToTarget` or `Target.setAutoAttach` `DevToolsProtocol` method.
245+
/// Using `Target.setAutoAttach` is recommended as that would allow you to attach
246+
/// to dedicated worker target, which is not discoverable via other APIs like
247+
/// `Target.getTargets`.
248+
/// For more information about targets and sessions, navigate to
249+
/// \[DevTools Protocol Viewer\]\[GithubChromedevtoolsDevtoolsProtocolTotTarget\].
250+
/// For more information about available methods, navigate to
251+
/// \[DevTools Protocol Viewer\]\[GithubChromedevtoolsDevtoolsProtocolTot\]
252+
/// The `sessionId` parameter is the sessionId for an attached target.
253+
/// nullptr or empty string is treated as the session for the default target.
254+
/// The `methodName` parameter is the full name of the method in the
255+
/// `{domain}.{method}` format. The `parametersAsJson` parameter is a JSON
256+
/// formatted string containing the parameters for the corresponding method.
257+
/// The `Invoke` method of the `handler` is run when the method
258+
/// asynchronously completes. `Invoke` is run with the return object of the
259+
/// method as a JSON string.
260+
///
261+
/// \[GithubChromedevtoolsDevtoolsProtocolTot\]: https://chromedevtools.github.io/devtools-protocol/tot "latest (tip-of-tree) protocol - Chrome DevTools Protocol | GitHub"
262+
/// \[GithubChromedevtoolsDevtoolsProtocolTotTarget\]: https://chromedevtools.github.io/devtools-protocol/tot/Target "Chrome DevTools Protocol - Target domain"
263+
264+
HRESULT CallDevToolsProtocolMethodForSession(
265+
[in] LPCWSTR sessionId,
266+
[in] LPCWSTR methodName,
267+
[in] LPCWSTR parametersAsJson,
268+
[in] ICoreWebView2CallDevToolsProtocolMethodCompletedHandler* handler);
269+
}
270+
271+
interface ICoreWebView2DevToolsProtocolEventReceivedEventArgs_2 : IUnknown {
272+
273+
/// The sessionId of the target where the event originates from.
274+
/// Empty string is returned as sessionId if the event comes from the default session.
275+
/// \snippet ScriptComponent.cpp DevToolsProtocolEventReceivedSessionId
276+
[propget] HRESULT SessionId([out, retval] LPWSTR* sessionId);
277+
}
278+
```
279+
280+
## WinRT and .NET
281+
```c#
282+
namespace Microsoft.Web.WebView2.Core
283+
{
284+
runtimeclass CoreWebView2
285+
{
286+
// ...
287+
288+
// This is an overload for: public async Task<string> CallDevToolsProtocolMethodAsync(string methodName, string parametersAsJson);
289+
public async Task<string> CallDevToolsProtocolMethodAsync(string sessionId, string methodName, string parametersAsJson);
290+
}
291+
292+
runtimeclass CoreWebView2DevToolsProtocolEventReceivedEventArgs
293+
{
294+
// ...
295+
296+
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2DevToolsProtocolEventReceivedEventArgs_2")]
297+
{
298+
String SessionId { get; };
299+
}
300+
}
301+
}
302+
```

0 commit comments

Comments
 (0)