Blog Post

Sharing Buffers from UWP to WebView2

Illustration: 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

  1. Create a UWP project and name it UsingSharedBufferWithUWP. Visual Studio startup window

  2. Select the default Target version and Minimum version. Target and Minimum Platform version

  3. The initial solution file tree will look similar to what’s shown in the image below. Initial project tree

  4. Install the Microsoft.UI.Xaml NuGet package. Initial project tree

  5. Internally, Microsoft.UI.Xaml refers to WebView2. At the time of writing the blog, it refers to version 1.0.1264.42 of WebView2. WebView2 version refered in WinUI2

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. WebView2 nuget package

  1. 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>
  1. 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);
                }
            }
        }
    }
}
  1. Add an html folder in the Assets folder.

  2. Add a new index.html file in the newly added html folder. Adding index.html

  3. In the index.html file, subscribe to the sharedbufferreceived event to get the SharedBuffer sent by UWP. The final index.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.

Initial app window

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.

App window after loading image

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.

Here are some additional links worth checking out:

Author
Shantanu Methikar .NET Engineer

Shantanu likes to explore and experiment — be it with technology, traveling, food, or almost anything else.

Related products

PSPDFKit for Windows

Product page Guides API docs

Share post
Free trial Ready to get started?
Free trial