Sharing Buffers from UWP to WebView2
At PSPDFKit, we like to experiment with cutting Edge technologies (pun intended!) and incorporate them into our products to give the best performance and features to our customers. Earlier this year, while working on updating the PSPDFKit UWP project dependencies, we were also evaluating the best features in those updated dependencies. One such update was moving from WebView to WebView2, which is a newer Chromium-based version of Edge from Microsoft.
When I was setting up communication between the Universal Windows Platform (UWP) application and WebView2, I came across some scenarios where I had to share large buffers from UWP to web. Unfortunately, to do this at that time, I could only use primitive types with WebView2. This meant sharing a buffer had to be done by converting it to a string, passing it over to the web, and then creating an array from it on the web end. In turn, this decreased the overall performance of the app.
While searching for a better solution, I came across SharedBuffer
, an experimental API added by Microsoft to WebView2 in prerelease version 1.0.1466 (this API was marked stable in WebView2 version 1.0.1661.34). Microsoft came up with this API to support sharing buffers between the WebView2 host app process and the WebView2 renderer process, based on shared memory from the operating system. After a bit of experimentation, I found out that this way of sharing large data was much faster than using primitive types, especially as the size of data increased.
So in this blog, we’ll walk through the procedure of setting up communication between UWP and WebView2 using shared buffers. For this, we’ll create a UWP app that hosts WebView2
and displays an image in it.
Getting Started
-
Create a UWP project and name it UsingSharedBufferWithUWP.
-
Select the default Target version and Minimum version.
-
The initial solution file tree will look similar to what’s shown in the image below.
-
Install the Microsoft.UI.Xaml NuGet package.
-
Internally, Microsoft.UI.Xaml refers to WebView2. At the time of writing the blog, it refers to version 1.0.1264.42 of WebView2.
SharedBuffer
was only added to WebView2 after version 1.0.1661.34, so be sure to install this version of the WebView2 NuGet package. You can skip this step if, in your case, Microsoft.UI.Xaml refers to any version of WebView2 released after version 1.0.1661.34.
-
In
MainPage.xaml
:
a. Add the XML namespace xmlns:controls="using:Microsoft.UI.Xaml.Controls"
.
b. Subscribe to the main page’s Loaded
event by assigning MainPage_OnLoaded
, i.e. Loaded="MainPage_OnLoaded"
, to the event handler.
c. Add a Button
and WebView2
controls to Grid
.
<Grid> <Grid.RowDefinitions> <RowDefinition Height="100"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Button Margin="10" Click="LoadImageButtonClicked">Load Image</Button> <controls:WebView2 x:Name="WebView2" Grid.Row="1"/> </Grid>
The resulting MainPage.xaml
will look like this:
<Page x:Class="UsingSharedBufferWithUWP.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:UsingSharedBufferWithUWP" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="using:Microsoft.UI.Xaml.Controls" Loaded="MainPage_OnLoaded" mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="100"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Button Margin="10" Click="LoadImageButtonClicked">Load Image</Button> <controls:WebView2 x:Name="WebView2" Grid.Row="1"/> </Grid> </Page>
-
In
MainPage.xaml.cs
:
a. Add the MainPage_OnLoaded
function.
private async void MainPage_OnLoaded(object sender, RoutedEventArgs e) { await WebView2.EnsureCoreWebView2Async(); WebView2.CoreWebView2.SetVirtualHostNameToFolderMapping( "pspdfkit.uwp.example", "Assets/html", CoreWebView2HostResourceAccessKind.Allow); WebView2.Source = new Uri("http://pspdfkit.uwp.example/index.html"); }
b. Add the LoadImageButtonClicked
function.
private async void LoadImageButtonClicked(object sender, RoutedEventArgs e) { // Show Open File Dialog to select image. FileOpenPicker fileOpenPicker = new FileOpenPicker(); fileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary; fileOpenPicker.FileTypeFilter.Add(".png"); fileOpenPicker.FileTypeFilter.Add(".jpg"); fileOpenPicker.ViewMode = PickerViewMode.Thumbnail; var inputFile = await fileOpenPicker.PickSingleFileAsync(); // Store selected image in a buffer. var buffer = await FileIO.ReadBufferAsync(inputFile); var environment = await CoreWebView2Environment.CreateAsync(); // Create shared buffer. using (var sharedBuffer = environment.CreateSharedBuffer(buffer.Length)) { using (var stream = sharedBuffer.OpenStream()) { // Write image to the shared buffer. using (DataWriter writer = new DataWriter(stream.GetOutputStreamAt(0))) { writer.WriteBuffer(buffer); await writer.StoreAsync(); } string additionalDataAsJson = ""; // Can provide some extra information when we share the buffer. // Send and notify web of shared buffer. WebView2.CoreWebView2.PostSharedBufferToScript(sharedBuffer, CoreWebView2SharedBufferAccess.ReadOnly, additionalDataAsJson); } } }
The resulting MainPage.xaml.cs
will look like this:
using Microsoft.Web.WebView2.Core; using System; using Windows.Storage; using Windows.Storage.Pickers; using Windows.Storage.Streams; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace UsingSharedBufferWithUWP { public sealed partial class MainPage : Page { public MainPage() { InitializeComponent(); } private async void MainPage_OnLoaded(object sender, RoutedEventArgs e) { await WebView2.EnsureCoreWebView2Async(); WebView2.CoreWebView2.SetVirtualHostNameToFolderMapping( "pspdfkit.uwp.example", "Assets/html", CoreWebView2HostResourceAccessKind.Allow); WebView2.Source = new Uri("http://pspdfkit.uwp.example/index.html"); } private async void LoadImageButtonClicked(object sender, RoutedEventArgs e) { // Show Open File Dialog to select image. FileOpenPicker fileOpenPicker = new FileOpenPicker(); fileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary; fileOpenPicker.FileTypeFilter.Add(".png"); fileOpenPicker.FileTypeFilter.Add(".jpg"); fileOpenPicker.ViewMode = PickerViewMode.Thumbnail; var inputFile = await fileOpenPicker.PickSingleFileAsync(); // Store selected image in a buffer. var buffer = await FileIO.ReadBufferAsync(inputFile); var environment = await CoreWebView2Environment.CreateAsync(); // Create shared buffer. using (var sharedBuffer = environment.CreateSharedBuffer(buffer.Length)) { using (var stream = sharedBuffer.OpenStream()) { // Write image to the shared buffer. using (DataWriter writer = new DataWriter(stream.GetOutputStreamAt(0))) { writer.WriteBuffer(buffer); await writer.StoreAsync(); } string additionalDataAsJson = ""; // Can provide some extra information when we share the // Send and notify web of shared buffer. WebView2.CoreWebView2.PostSharedBufferToScript(sharedBuffer, CoreWebView2SharedBufferAccess.ReadOnly, additionalDataAsJson); } } } } }
-
Add an
html
folder in the Assets folder. -
Add a new
index.html
file in the newly addedhtml
folder. -
In the
index.html
file, subscribe to thesharedbufferreceived
event to get theSharedBuffer
sent by UWP. The finalindex.html
will have the following code:
<!DOCTYPE html> <html lang="en"> <body> <img id="image" /> <script> window.onload = function () { // This event is invoked when a `SharedBuffer` is posted to JavaScript. window.chrome.webview.addEventListener( 'sharedbufferreceived', (e) => { let displayBuffer = e.getBuffer(); // Consume the data from the buffer (in the form of an `ArrayBuffer`). let displayBufferArray = new Uint8Array( displayBuffer, ); var image = document.getElementById('image'); image.src = URL.createObjectURL( new Blob([displayBufferArray.buffer], { type: 'image/jpg', }), ); // Release the buffer after consuming the data. window.chrome.webview.releaseBuffer(displayBuffer); }, ); }; </script> </body> </html>
Result
After building and running the application, you’ll see a window like what’s shown below.
Now, click the Load Image button and select an image you’d like to load. You’ll see it displayed in the WebView2
control inside the app. I chose a spectacular image taken by the James Webb Space Telescope.
Conclusion
This was a step-by-step tutorial on sharing data buffers from a Universal Windows Platform (UWP) application to a WebView2
control using Microsoft’s SharedBuffer
API. The tutorial covered setting up a UWP project, installing required packages, adding XML namespaces, and writing code for loading an image into WebView2
using SharedBuffer
.
I added a project to GitHub, PSPDFKit-labs/UsingSharedBufferWithUWP, which contains all the code above for your reference. For fun, you can compare the speed of sending data to a webpage as a string versus using the SharedBuffer
API. Observing how much faster the SharedBuffer
API is compared to sending data as a string will be particularly interesting, especially as the size of the data increases.
Links
Here are some additional links worth checking out: