Skip to content

Commit a8d7435

Browse files
author
Çağrı Kaan Yıldırım
authored
API Review for WebMessage objects and ICoreWebView2File. (#3006)
API Spec for WebMessage objects and CoreWebView2File.
1 parent a1a7553 commit a8d7435

File tree

1 file changed

+374
-0
lines changed

1 file changed

+374
-0
lines changed

specs/WebMessageObjects.md

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
# Background
2+
This is a proposal for a new API that will provide a framework for native representation of DOM
3+
objects from page content in WebView2 and its implementation for DOM File objects. The [specific
4+
ask from WebView2](https://github.com/MicrosoftEdge/WebView2Feedback/issues/501) is to be able get
5+
to the paths for DOM file objects, which is not accessible to page content. We also have asks to
6+
expose other DOM objects, including iframes, <object> objects, etc. which will be added under this
7+
WebMessageObjects framework in future.
8+
9+
Also in future we want to be able to inject DOM objects into WebView2 content constructed via the
10+
app and via the CoreWebView2.PostWebMessage API in the other direction. This API surface needs to be
11+
compatible with that. (See Appendix)
12+
13+
# Conceptual pages (How To)
14+
WebMessageObjects are representations of DOM objects that can be passed via the [WebView2 WebMessage
15+
API](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2webmessagereceivedeventargs).
16+
You can examine supported DOM objects using native reflections of the types.
17+
18+
Currently the only supported DOM object type with this API is:
19+
- [File](https://developer.mozilla.org/docs/Web/API/File)
20+
21+
Page content in WebView2 can pass objects to the app via the
22+
`chrome.webview.postMessageWithAdditionalObjects(string message, ArrayLike<object> objects)` content API
23+
that takes in the array of such supported DOM objects or you can also use
24+
[ExecuteScript](https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.winforms.webview2.executescriptasync)
25+
with same API. `null` or `undefined` objects will be passed as `null`. Otherwise, if an invalid or unsupported object is
26+
passed via this API, an exception will be thrown to the caller and the message will fail to post.
27+
28+
On the WebMessageReceived event handler, the app will retrieve the native representation of objects
29+
via `AdditionalObjects` property and cast the passed objects to their native types. For example, the
30+
DOM File object will be exposed as a `CoreWebView2File` object, allowing the app to read the path of
31+
the passed DOM File object.
32+
33+
# Examples
34+
## Read the File path of a DOM File
35+
36+
Use this API with a DOM File object to be able to get the path of files dropped on WebView2. The
37+
HTML and JavaScript snippets are part of the page content code running inside WebView2 and are used
38+
by both the C++ and C# sample code below.
39+
40+
```html
41+
<!-- File upload location -->
42+
<input type="file" id="files" />
43+
```
44+
45+
```javascript
46+
const input = document.getElementById('files');
47+
input.addEventListener('change', function() {
48+
// Note that postMessageWithAdditionalObjects does not accept a single object,
49+
// but only accepts an ArrayLike object.
50+
// However, input.files is type FileList, which is already an ArrayLike object so
51+
// no conversion to array is needed.
52+
const currentFiles = input.files;
53+
chrome.webview.postMessageWithAdditionalObjects("FilesDropped", currentFiles);
54+
});
55+
```
56+
57+
```cpp
58+
CHECK_FAILURE(m_webView->add_WebMessageReceived(
59+
Callback<ICoreWebView2WebMessageReceivedEventHandler>(
60+
[this](ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args) noexcept
61+
{
62+
wil::unique_cotaskmem_string message;
63+
CHECK_FAILURE(args->TryGetWebMessageAsString(&message));
64+
if (std::wstring(L"FilesDropped") == message.get())
65+
{
66+
wil::com_ptr<ICoreWebView2WebMessageReceivedEventArgs2> args2 =
67+
wil::com_ptr<ICoreWebView2WebMessageReceivedEventArgs>(args)
68+
.query<ICoreWebView2WebMessageReceivedEventArgs2>();
69+
if (args2)
70+
{
71+
wil::com_ptr<ICoreWebView2WebMessageObjectCollectionView>
72+
objectsCollection;
73+
CHECK_FAILURE(args2->get_AdditionalObjects(&objectsCollection));
74+
unsigned int length;
75+
CHECK_FAILURE(objectsCollection->get_Count(&length));
76+
std::vector<std::wstring> paths;
77+
78+
for (unsigned int i = 0; i < length; i++)
79+
{
80+
wil::com_ptr<IUnknown> object;
81+
CHECK_FAILURE(objectsCollection->GetValueAtIndex(i, &object));
82+
// Note that objects can be null.
83+
if (object)
84+
{
85+
wil::com_ptr<ICoreWebView2File> file =
86+
object.query<ICoreWebView2File>();
87+
if (file)
88+
{
89+
// Add the file to message to be sent back to webview
90+
wil::unique_cotaskmem_string path;
91+
CHECK_FAILURE(file->get_Path(&path));
92+
paths.push_back(path.get());
93+
}
94+
}
95+
}
96+
ProcessPaths(paths);
97+
}
98+
99+
}
100+
return S_OK;
101+
})
102+
.Get(),
103+
&m_webMessageReceivedToken));
104+
```
105+
106+
```c#
107+
webView.CoreWebView2.WebMessageReceived += WebView_WebMessageReceivedHandler;
108+
void WebView_WebMessageReceivedHandler(object sender, CoreWebView2WebMessageReceivedEventArgs args)
109+
{
110+
List<string> paths = new List<string>();
111+
foreach (var additionalObject in args.AdditionalObjects)
112+
{
113+
if (additionalObject is CoreWebView2File file)
114+
{
115+
paths.Add(file.Path);
116+
}
117+
}
118+
ProcessPaths(paths);
119+
}
120+
```
121+
122+
# API Details
123+
## SDK API
124+
```c#
125+
/// Representation of a DOM
126+
/// [File](https://developer.mozilla.org/docs/Web/API/File) object
127+
/// passed via WebMessage. You can use this object to obtain the path of a
128+
/// File dropped on WebView2.
129+
/// \snippet ScenarioDragDrop.cpp DroppedFilePath
130+
[uuid(f2c19559-6bc1-4583-a757-90021be9afec), object, pointer_default(unique)]
131+
interface ICoreWebView2File : IUnknown {
132+
/// Get the absolute file path.
133+
[propget] HRESULT Path([out, retval] LPWSTR* path);
134+
}
135+
136+
/// Read-only collection of generic objects
137+
[uuid(b547d2d4-b8d5-4278-a544-c54e66b5f45d), object, pointer_default(unique)]
138+
interface ICoreWebView2ObjectCollectionView : IUnknown {
139+
/// Gets the number of items in the collection.
140+
[propget] HRESULT Count([out, retval] UINT32* value);
141+
142+
/// Gets the object at the specified index. Cast the object to the native type
143+
/// to access its specific properties.
144+
HRESULT GetValueAtIndex([in] UINT32 index,
145+
[out, retval] IUnknown** value);
146+
}
147+
148+
[uuid(50a798ac-40fa-4634-8fb7-9419e5e8a3f8), object, pointer_default(unique)]
149+
interface ICoreWebView2WebMessageReceivedEventArgs2 : ICoreWebView2WebMessageReceivedEventArgs {
150+
/// Additional received WebMessage objects. To pass `additionalObjects` via
151+
/// WebMessage to the app, use the
152+
/// `chrome.webview.postMessageWithAdditionalObjects` content API.
153+
/// Any DOM object type that can be natively representable that has been
154+
/// passed in to `additionalObjects` parameter will be accessible here.
155+
/// Currently a WebMessage object can be the following type:
156+
/// - `ICoreWebView2File`.
157+
[propget] HRESULT AdditionalObjects(
158+
[out, retval] ICoreWebView2ObjectCollectionView** value);
159+
}
160+
```
161+
162+
```c#
163+
namespace Microsoft.Web.WebView2.Core
164+
{
165+
runtimeclass CoreWebView2WebMessageReceivedEventArgs
166+
{
167+
...
168+
/// Additional received WebMessage objects. To pass additionalObjects via
169+
/// WebMessage to the app, use the
170+
/// `chrome.webview.postMessageWithAdditionalObjects' content API.
171+
/// Any DOM object type that can be natively representable that has been passed in to
172+
/// `additionalObjects` parameter will be accessible here.
173+
/// Currently a WebMessage object can be the following type:
174+
/// $net$
175+
/// - System.IO.FileInfo
176+
/// $end$
177+
/// $winrt$
178+
/// - Windows.Storage.StorageFile
179+
/// $end$
180+
/// Cast the object to the native type to access its specific properties.
181+
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2WebMessageReceivedEventArgs2")]
182+
{
183+
IVectorView<Object> AdditionalObjects { get; };
184+
}
185+
}
186+
}
187+
```
188+
## JS API for page content
189+
We named the JS API `chrome.webview.postMessageWithAdditionalObjects`. We considered making it an
190+
overload of the existing `chrome.webview.postMessage(message)`, but that method is patterned after
191+
the existing `MessagePort.postMessage(message, transferList)` DOM method and we're worried about
192+
confusion or future compat mixing up the `additionalObjects` array with the `transferList` array.
193+
194+
```ts
195+
interface WebView extends EventTarget {
196+
...
197+
/**
198+
* When the page calls `postMessageWithAdditionalObjects`, the `message`
199+
* parameter is sent to WebView2 in same fashion as 'postMessage'.
200+
* Objects passed as 'additionalObjects' are converted to their native types
201+
* and will be available in
202+
* `CoreWebView2WebMessageReceivedEventArgs.AdditionalObjects` property.
203+
* @param message The message to send to the WebView2 host. This can be any
204+
* object that can be serialized to JSON.
205+
* @param additionalObjects A sequence of DOM objects that have native
206+
* representations in WebView2. This parameter needs to be ArrayLike.
207+
* The following DOM types are mapped to native:
208+
* DOM | Win32 | .NET | WinRT
209+
* -------- | ------------|----------| --------
210+
* [File](https://developer.mozilla.org/docs/Web/API/File] | ICoreWebView2File | [System.IO.FileInfo](https://learn.microsoft.com/dotnet/api/system.io.fileinfo) | [Windows.Storage.StorageFile](https://learn.microsoft.com/uwp/api/windows.storage.storagefile)
211+
* If an invalid or unsupported object is passed via this API, an exception
212+
* will be thrown and the message will fail to post.
213+
* @example
214+
* Post a message with File objects from input element to the CoreWebView2:
215+
* ```javascript
216+
* const input = document.getElementById('files');
217+
* input.addEventListener('change', function() {
218+
* // Note that postMessageWithAdditionalObjects does not accept a single object,
219+
* // but only accepts an ArrayLike object.
220+
* // However, input.files is type FileList, which is already an ArrayLike object so
221+
* // no conversion to array is needed.
222+
* const currentFiles = input.files;
223+
* chrome.webview.postMessageWithAdditionalObjects("FilesDropped",
224+
* currentFiles);
225+
* });
226+
* ```
227+
*/
228+
postMessageWithAdditionalObjects(message: any, additionalObjects: ArrayLike<any>) : void;
229+
}
230+
```
231+
232+
# Appendix
233+
## Draft API Proposal to post DOM Objects injected to WebView2 Content
234+
```c#
235+
/// Representation of a DOM
236+
/// [File](https://developer.mozilla.org/docs/Web/API/File) object
237+
/// passed via WebMessage. You can use this object to obtain the path of a
238+
/// File dropped on WebView2 or pass a File to WebView2 content.
239+
/// \snippet ScenarioDragDrop.cpp DroppedFilePath
240+
[uuid(f2c19559-6bc1-4583-a757-90021be9afec), object, pointer_default(unique)]
241+
interface ICoreWebView2File : IUnknown {
242+
/// Get the absolute file path.
243+
[propget] HRESULT Path([out, retval] LPWSTR* path);
244+
}
245+
246+
/// This interface is used to create `ICoreWebView2File` object, which
247+
/// can be passed as a parameter to PostWebMessageWithAdditionalObjects
248+
[uuid(b571b60f-def2-41d2-a5eb-05d7ccc81981), object, pointer_default(unique)]
249+
interface ICoreWebView2Environment12 : ICoreWebView2Environment11 {
250+
/// Create a new ICoreWebView2File from a path. The path must point an existing file in disk.
251+
/// An invalid file path will return E_INVALIDARG.
252+
HRESULT CreateCoreWebView2File(
253+
[in] LPCWSTR path,
254+
[out, retval] ICoreWebView2File** file);
255+
}
256+
257+
/// A continuation of the `ICoreWebView2` interface to support posting WebMessage
258+
/// with additional objects.
259+
[uuid(fd4e150b-97d4-4baf-ab35-d9899575e700), object, pointer_default(unique)]
260+
interface ICoreWebView2_17 : IUnknown {
261+
/// Post the specified webMessage to the top level document in this WebView
262+
/// along with any additional objects that can be injected to WebView content.
263+
/// The main page receives the message by subscribing to the `message` event of the
264+
/// `window.chrome.webview` of the page document.
265+
///
266+
/// ```cpp
267+
/// window.chrome.webview.addEventListener('message', handler)
268+
/// window.chrome.webview.removeEventListener('message', handler)
269+
/// ```
270+
///
271+
/// The event args is an instance of `WebViewMessageEvent`, which extends`MessageEvent`. The
272+
/// `ICoreWebView2Settings::IsWebMessageEnabled` setting must be `TRUE` or
273+
/// this method fails with `E_INVALIDARG`. The `data` property of the event
274+
/// arg is the `webMessage` string parameter parsed as a JSON string into a
275+
/// JavaScript object. The `additionalObjects` property of event arg will contain
276+
/// a sequence of WebMessage objects as their DOM types.
277+
/// Currently the only supported WebMessage object type that is injectable to content is:
278+
/// - ICoreWebView2File
279+
/// The `source` property of the event arg is a reference
280+
/// to the `window.chrome.webview` object. For information about sending
281+
/// messages from the HTML document in the WebView to the host, navigate to
282+
/// [add_WebMessageReceived](/microsoft-edge/webview2/reference/win32/icorewebview2#add_webmessagereceived).
283+
/// The message is delivered asynchronously. If a navigation occurs before
284+
/// the message is posted to the page, the message is discarded. If there
285+
/// are any invalid or unsupported additionalObjects, the method fails
286+
/// with `E_INVALIDARG`.
287+
///
288+
///
289+
HRESULT PostWebMessageAsJsonWithAdditionalObjects(
290+
[in] LPCWSTR webMessageAsJson,
291+
[in] ICoreWebView2ObjectCollectionView additionalObjects);
292+
}
293+
```
294+
295+
```c#
296+
namespace Microsoft.Web.WebView2.Core
297+
{
298+
runtimeclass CoreWebView2
299+
{
300+
...
301+
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2_17")]
302+
{
303+
// Posts the specified <c>webMessageAsJson</c> to the top level document in this WebView
304+
// along with any additional objects that can be injected to WebView content.
305+
// <param name="webMessageAsJson">The web message to be posted to the top level document in
306+
// this WebView.</param>
307+
// <param name="additionalObjects">The list of additional WebMessage objects that can be injected
308+
// to WebView content.
309+
// Currently the only supported WebMessage object type that is injectable to content is:
310+
// $net$
311+
// - System.IO.FileInfo
312+
// $end$
313+
// $winrt$
314+
// - Windows.Storage.StorageFile
315+
// $end$
316+
// </param>
317+
// <remarks>
318+
// The event args is an instance of <c>WebViewMessageEvent</c>, which extends from
319+
// <c>MessageEvent</c>.
320+
// The <see cref="CoreWebView2Settings.IsWebMessageEnabled"/> setting must be
321+
// <c>true</c>or this method will fail with E_INVALIDARG. The event arg's
322+
// <c>data</c> property of the event arg is the <c>webMessageAsJson</c> string
323+
// parameter parsed as a JSON string into a JavaScript object. The event arg's
324+
// <c>source</c> property of the event arg is a reference
325+
// to the <c>window.chrome.webview</c> object. The <c>additionalObjects</c> property of
326+
// event arg will contain a sequence of WebMessage objects as their DOM types. For
327+
// information about sending messages from the HTML document in the WebView to the
328+
// host, navigate to <see cref="CoreWebView2.WebMessageReceived"/>. The message is sent
329+
// asynchronously. If a navigation occurs before the message is posted to the page,
330+
// the message is not be sent.
331+
// </remarks>
332+
// <example>
333+
// Runs the message event of the <c>window.chrome.webview</c> of the top-level
334+
// document. JavaScript in that document may subscribe and unsubscribe to the event
335+
// using the following code:
336+
// $net$
337+
// <code>
338+
// window.chrome.webview.addEventListener('message', handler)
339+
// window.chrome.webview.removeEventListener('message', handler)
340+
// </code>
341+
// $end$
342+
// $winrt$
343+
// ```javascript
344+
// window.chrome.webview.addEventListener('message', handler)
345+
// window.chrome.webview.removeEventListener('message', handler)
346+
// ```
347+
// $end$
348+
// </example>
349+
// <seealso cref="CoreWebView2Settings.IsWebMessageEnabled"/>
350+
// <seealso cref="WebMessageReceived"/>
351+
// <seealso cref="PostWebMessageAsString"/>
352+
void PostWebMessageAsJsonWithAdditionalObjects(
353+
String webMessageAsJson,
354+
IVectorView<Object> additionalObjects);
355+
}
356+
}
357+
}
358+
```
359+
360+
```ts
361+
interface WebViewMessageEvent: MessageEvent {
362+
/* When a WebMessage is posted via PostWebMessageAsJsonWithAdditionalObjects
363+
* this property will contain the DOM objects that are injected to the content.
364+
* Currently the following types are supported:
365+
* - File
366+
*/
367+
additionalObjects: ArrayLike<any>
368+
}
369+
370+
interface WebViewEventMap {
371+
"message": WebViewMessageEvent;
372+
...
373+
}
374+
```

0 commit comments

Comments
 (0)