Skip to content

Commit 4113f8d

Browse files
authored
Merge pull request #2744 from MicrosoftEdge/shared-buffer-draft
Shared buffer API draft
2 parents a51796a + 4a0beb7 commit 4113f8d

File tree

1 file changed

+333
-0
lines changed

1 file changed

+333
-0
lines changed

specs/SharedBuffer.md

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
Shared Buffer Between Native Application Code and Script
2+
===
3+
4+
# Background
5+
For some advanced scenarios, there is a need to exchange large amounts of data
6+
between the WebView2 application process and trusted web pages that are considered
7+
as part of the app. Some examples:
8+
- Web page generates a large amount of data, and passes it to the native side to be
9+
further processed or fed to other parts of the app or OS. For example, the web page
10+
generates 100MBs of high DPI images to be printed and needs to pass that to the native
11+
code to print. See https://github.com/MicrosoftEdge/WebView2Feedback/issues/89.
12+
- Native side generates a large amount of data for the web side to consume. The data
13+
might or might not come directly from files. For example the native side has generated
14+
terrabytes of data to produce different graphs on the web side.
15+
See https://github.com/MicrosoftEdge/WebView2Feedback/issues/1005.
16+
17+
To support these scenarios, we are adding an Edge WebView2 API to support sharing
18+
buffers between the WebView2 host app process and WebView2 renderer process, based
19+
on shared memory from the OS.
20+
21+
The application code can use the APIs to create a shared buffer object, and share to
22+
scripts as [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) object.
23+
Then both the native application code and the script will be able to access the same memory.
24+
25+
# Conceptual pages (How To)
26+
27+
Besides using the memory address directly, the shared buffer object can be accessed
28+
from native side via an IStream* object that you can get from the shared buffer object.
29+
30+
When the application code calls `PostSharedBufferToScript`, the script side will
31+
receive a `SharedBufferReceived` event containing the buffer as an `ArrayBuffer` object.
32+
After receiving the shared buffer object, it can access it the same way as any other
33+
ArrayBuffer object, including transering to a web worker to process the data on
34+
the worker thread.
35+
36+
As shared buffer normally represent a large memory, instead of waiting for garbage
37+
collection to release the memory along with the owning objects, the application
38+
should try to release the buffer as soon as it doesn't need access to it.
39+
This can be done by calling Close() method on the shared buffer object, or
40+
`chrome.webview.releaseBuffer` from script.
41+
42+
As the memory could contain sensitive information and corrupted memory could crash
43+
the application, the application should only share buffer with trusted sites.
44+
45+
The application can use other messaging channel like `PostWebMessageAsJson` and
46+
`chrome.webview.postMessage` to inform the other side the desire to have a shared
47+
buffer and the status of the buffer (like data is produced or consumed).
48+
49+
# Examples
50+
51+
The example below illustrates how to send data from application to script for one time read only consumption.
52+
53+
The script code will look like this:
54+
```
55+
window.onload = function () {
56+
window.chrome.webview.addEventListener("SharedBufferReceived", e => {
57+
SharedBufferReceived(e);
58+
});
59+
}
60+
61+
function SharedBufferReceived(e) {
62+
if (e.data && e.data.readOnly) {
63+
// This is the one time read only buffer
64+
let oneTimeSharedBuffer = e.sharedBuffer;
65+
// Consume the data from the buffer
66+
DisplaySharedBufferData(oneTimeSharedBuffer);
67+
// Release the buffer after consuming the data.
68+
chrome.webview.releaseBuffer(oneTimeSharedBuffer);
69+
}
70+
}
71+
```
72+
## Win32 C++
73+
```cpp
74+
75+
wil::com_ptr<ICoreWebView2SharedBuffer> sharedBuffer;
76+
CHECK_FAILURE(webviewEnvironment->CreateSharedBuffer(bufferSize, &sharedBuffer));
77+
// Fill data into the shared memory via IStream.
78+
wil::com_ptr<IStream> stream;
79+
CHECK_FAILURE(sharedBuffer->GetStream(&stream));
80+
CHECK_FAILURE(stream->Write(data, dataSize, nullptr));
81+
PCWSTR additionalDataAsJson = L"{\"readOnly\":true}";
82+
if (forFrame)
83+
{
84+
m_webviewFrame->PostSharedBufferToScript(
85+
sharedBuffer.get(), /*isReadOnlyToScript*/TRUE, additionalDataAsJson);
86+
}
87+
else
88+
{
89+
m_webView->PostSharedBufferToScript(
90+
sharedBuffer.get(), /*isReadOnlyToScript*/TRUE, additionalDataAsJson);
91+
}
92+
// Explicitly close the one time shared buffer to ensure that the resource is released timely.
93+
sharedBuffer->Close();
94+
95+
```
96+
## WinRT and .NET
97+
```c#
98+
using (CoreWebView2SharedBuffer sharedBuffer = WebViewEnvironment.CreateSharedBuffer(bufferSize))
99+
{
100+
// Fill data using access Stream
101+
using (Stream stream = sharedBuffer.GetStream())
102+
{
103+
using (StreamWriter writer = new StreamWriter(stream))
104+
{
105+
writer.Write(data);
106+
}
107+
}
108+
string additionalDataAsJson = "{\"readOnly\":true}";
109+
if (forFrame)
110+
{
111+
m_webviewFrame.PostSharedBufferToScript(sharedBuffer, /*isReadOnlyToScript*/true, additionalDataAsJson);
112+
}
113+
else
114+
{
115+
m_webview.PostSharedBufferToScript(sharedBuffer, /*isReadOnlyToScript*/true, additionalDataAsJson);
116+
}
117+
}
118+
```
119+
120+
# API Details
121+
## Win32 C++
122+
```
123+
interface ICoreWebView2Environment11 : IUnknown {
124+
/// Create a shared memory based buffer with the specified size in bytes.
125+
/// The buffer can be shared with web contents in WebView by calling
126+
/// `PostSharedBufferToScript` on `CoreWebView2` or `CoreWebViewFrame` object.
127+
/// Once shared, the same content of the buffer will be accessible from both
128+
/// the app process and script in WebView. Modification to the content will be visible
129+
/// to all parties that have access to the buffer.
130+
/// For 32bit application, the creation will fail with E_INVALIDARG if `size` is larger than 4GB.
131+
HRESULT CreateSharedBuffer(
132+
[in] UINT64 size,
133+
[out, retval] ICoreWebView2SharedBuffer** shared_buffer);
134+
}
135+
136+
interface ICoreWebView2SharedBuffer : IUnknown {
137+
/// The size of the shared buffer in bytes.
138+
[propget] HRESULT Size([out, retval] UINT64* value);
139+
140+
/// The memory address of the shared buffer.
141+
[propget] HRESULT Buffer([out, retval] BYTE** value);
142+
143+
/// Get an IStream object that can be used to access the shared buffer.
144+
HRESULT GetStream([out, retval] IStream** value);
145+
146+
/// The file mapping handle of the shared memory of the buffer.
147+
/// Normal app should use `Buffer` or `GetStream` to get memory address
148+
/// or IStream object to access the buffer.
149+
/// For advanced scenarios, you could duplicate this handle to another application
150+
/// process and create a mapping from the duplicated handle in that process to access
151+
/// the buffer from that separate process.
152+
[propget] HRESULT Handle([out, retval] HANDLE* value);
153+
154+
/// Release the backing shared memory. The application should call this API when no
155+
/// access to the buffer is needed any more, to ensure that the underlying resources
156+
/// are released timely even if the shared buffer object itself is not released due to
157+
/// some leaked reference.
158+
/// After the shared buffer is closed, accessing properties of the object will fail with
159+
/// `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`. Operations like Read or Write on the IStream
160+
/// objects returned from `GetStream` will fail with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`.
161+
/// `PostSharedBufferToScript` will also fail with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`.
162+
///
163+
/// The script code should call `chrome.webview.releaseBuffer` with
164+
/// the shared buffer as the parameter to release underlying resources as soon
165+
/// as it does not need access the shared buffer any more.
166+
/// When script tries to access the buffer after calling `chrome.webview.releaseBuffer`,
167+
/// JavaScript `TypeError` exception will be raised complaining about accessing a
168+
/// detached ArrayBuffer, the same exception when trying to access a transferred ArrayBuffer.
169+
///
170+
/// Closing the buffer object on native side doesn't impact access from Script and releasing
171+
/// the buffer from script doesn't impact access to the buffer from native side.
172+
/// The underlying shared memory will be released by the OS when both native and script side
173+
/// release the buffer.
174+
HRESULT Close();
175+
}
176+
177+
interface ICoreWebView2_14 : IUnknown {
178+
/// Share a shared buffer object with script of the main frame in the WebView.
179+
/// The script will receive a `SharedBufferReceived` event from chrome.webview.
180+
/// The event arg for that event will have the following properties:
181+
/// `sharedBuffer`: an ArrayBuffer object with the backing content from the shared buffer.
182+
/// `data`: an object as the result of parsing `additionalDataAsJson` as JSON string.
183+
/// This property will be `undefined` if `additionalDataAsJson` is nullptr or empty string.
184+
/// `source`: with a value set as `chrome.webview` object.
185+
/// If a string is provided as `additionalDataAsJson` but it is not a valid JSON string,
186+
/// the API will fail with `E_INVALIDARG`.
187+
/// If `isReadOnlyToScript` is true, the script will only have read access to the buffer.
188+
/// If the script tries to modify the content in a read only buffer, it will cause an access
189+
/// violation in WebView renderer process and crash the renderer process.
190+
/// If the shared buffer is already closed, the API will fail with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`.
191+
///
192+
/// The script code should call `chrome.webview.releaseBuffer` with
193+
/// the shared buffer as the parameter to release underlying resources as soon
194+
/// as it does not need access to the shared buffer any more.
195+
///
196+
/// Sharing a buffer to script has security risk. You should only share buffer with trusted site.
197+
/// If a buffer is shared to a untrusted site, possible sensitive information could be leaked.
198+
/// If a buffer is shared as modifiable by the script and the script modifies it in an unexpected way,
199+
/// it could result in corrupted data that might even crash the application.
200+
HRESULT PostSharedBufferToScript(
201+
[in] ICoreWebView2SharedBuffer* sharedBuffer,
202+
[in] BOOL isReadOnlyToScript,
203+
[in] LPCWSTR additionalDataAsJson);
204+
}
205+
206+
interface ICoreWebView2Frame4 : IUnknown {
207+
/// Share a shared buffer object with script of the iframe in the WebView.
208+
/// The script will receive a `SharedBufferReceived` event from chrome.webview.
209+
/// The event arg for that event will have the following properties:
210+
/// `sharedBuffer`: an ArrayBuffer object with the backing content from the shared buffer.
211+
/// `data`: an object as the result of parsing `additionalDataAsJson` as JSON string.
212+
/// This property will be `undefined` if `additionalDataAsJson` is nullptr or empty string.
213+
/// `source`: with a value set as `chrome.webview` object.
214+
/// If a string is provided as `additionalDataAsJson` but it is not a valid JSON string,
215+
/// the API will fail with `E_INVALIDARG`.
216+
/// If `isReadOnlyToScript` is true, the script will only have read access to the buffer.
217+
/// If the script tries to modify the content in a read only buffer, it will cause an access
218+
/// violation in WebView renderer process and crash the renderer process.
219+
/// If the shared buffer is already closed, the API will fail with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`.
220+
///
221+
/// The script code should call `chrome.webview.releaseBuffer` with
222+
/// the shared buffer as the parameter to release underlying resources as soon
223+
/// as it does not need access to the shared buffer any more.
224+
///
225+
/// Sharing a buffer to script has security risk. You should only share buffer with trusted site.
226+
/// If a buffer is shared to a untrusted site, possible sensitive information could be leaked.
227+
/// If a buffer is shared as modifiable by the script and the script modifies it in an unexpected way,
228+
/// it could result in corrupted data that might even crash the application.
229+
HRESULT PostSharedBufferToScript(
230+
[in] ICoreWebView2SharedBuffer* sharedBuffer,
231+
[in] BOOL isReadOnlyToScript,
232+
[in] LPCWSTR additionalDataAsJson);
233+
}
234+
235+
```
236+
237+
## .NET
238+
```c#
239+
namespace Microsoft.Web.WebView2.Core
240+
{
241+
242+
class CoreWebView2Environment
243+
{
244+
public CoreWebView2SharedBuffer CreateSharedBuffer(ulong size);
245+
}
246+
247+
class CoreWebView2SharedBuffer : System.IDisposable
248+
{
249+
public ulong Size { get; };
250+
251+
/// The raw memory address of the buffer.
252+
/// You can cast it to pointer to real data types like byte* to access the memory
253+
/// from `unsafe` code region.
254+
/// Normal app should use `GetStream` to get a Stream object to access the buffer.
255+
public IntPtr Buffer { get; };
256+
257+
/// The native file mapping handle of the shared memory of the buffer.
258+
/// Normal app should use `GetStream` to get a Stream object to access the buffer.
259+
/// For advanced scenario, you could use native APIs to duplicate this handle to
260+
/// another application process and create a mapping from the duplicated handle in
261+
/// that process to access the buffer from that separate process.
262+
public IntPtr Handle { get; };
263+
264+
public Stream GetStream();
265+
266+
void Close();
267+
}
268+
269+
runtimeclass CoreWebView2
270+
{
271+
public void PostSharedBufferToScript(
272+
CoreWebView2SharedBuffer sharedBuffer, bool isReadOnlyToScript, string additionalDataAsJson);
273+
}
274+
275+
class CoreWebView2Frame
276+
{
277+
public void PostSharedBufferToScript(
278+
CoreWebView2SharedBuffer sharedBuffer, bool isReadOnlyToScript, string additionalDataAsJson);
279+
}
280+
}
281+
282+
```
283+
## WinRT
284+
```c#
285+
namespace Microsoft.Web.WebView2.Core
286+
{
287+
288+
runtimeclass CoreWebView2Environment
289+
{
290+
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2Environment11")]
291+
{
292+
CoreWebView2SharedBuffer CreateSharedBuffer(UInt64 size);
293+
}
294+
}
295+
296+
runtimeclass CoreWebView2SharedBuffer : Windows.Foundation.IClosable
297+
{
298+
UInt64 Size { get; };
299+
300+
Windows.Storage.Streams.IRandomAccessStream GetStream();
301+
302+
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2SharedBuffer_Manual")]
303+
{
304+
/// A reference to the underlying memory of the shared buffer.
305+
/// You can get IMemoryBufferByteAccess from the object to access the memory as an array of bytes.
306+
/// See [I](https://docs.microsoft.com/en-us/uwp/api/windows.foundation.imemorybufferreference?view=winrt-22621)
307+
/// for more details.
308+
Windows.Foundation.IMemoryBufferReference Buffer { get; };
309+
}
310+
311+
// Note that we are not exposing Handle from WinRT API.
312+
}
313+
314+
runtimeclass CoreWebView2
315+
{
316+
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2_14")]
317+
{
318+
void PostSharedBufferToScript(
319+
CoreWebView2SharedBuffer sharedBuffer, Boolean isReadOnlyToScript, String additionalDataAsJson);
320+
}
321+
}
322+
323+
runtimeclass CoreWebView2Frame
324+
{
325+
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2Frame4")]
326+
{
327+
void PostSharedBufferToScript(
328+
CoreWebView2SharedBuffer sharedBuffer, Boolean isReadOnlyToScript, String additionalDataAsJson);
329+
}
330+
}
331+
}
332+
333+
```

0 commit comments

Comments
 (0)