Search code examples
javascriptc#.netwpfwebview2

WebView2: Setting object properties in C# from Javascript code


Here's a followup to this question.

I'm porting a WPF app from CEFSharp to WebView2. I have a HostObject that needs to be accessible from js in the WebView2 window. This is it, stripped down.

using System;
using System.Runtime.InteropServices;

namespace webview2Demo
{
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)]
    public class Api
    {
        public string Username { get; set; }
        public string Version = "1.1.1";
        public Api()  //ctor
        {
        }
    }
}

I can use this line successfully in the WebView2 control's NavigationStarting event to make the object visible from Javascript. So far so good.

webView.CoreWebView2.AddHostObjectToScript("api", new API());

I can retrieve public properties and members like this. So far so good.

(async function foo () {
  const api = chrome.webview.hostObjects.api
  const ver = await api.Version
  alert (ver)
})();

My problem: Can I reliably, without any sort of asynchronous race-condition or deadlock risk, set properties like this? api.Username = 'whoever' It seems to work but I haven't found it documented.

(async function foo () {
  const api = chrome.webview.hostObjects.api
  api.Username = 'whoever'
  const user = await api.Username
  alert (user)
})();

The documentation says the HostObject is exposed through Promises. Am I hitting the setter correctly?


Solution

  • When you set a property on a JavaScript proxy for a host object created via CoreWebView2.AddHostObjectToScript, unlike getting a property or calling a method, the assignment returns the same value that was assigned and not a promise representing completion of that assignment. The property assignment does generate a message sent back to the WebView2 host app process and the property will be assigned, you just won't have a promise to tell you when its complete.

    setHostProperty

    Alternatively, you can use setHostProperty, a method that will perform the property assignment and return a promise that will resolve when the property assignment is done.

    (async function foo () {
      const api = chrome.webview.hostObjects.api
      await api.setHostProperty('Username', 'whoever');
      const user = await api.Username
      alert (user)
    })();
    

    Sync proxies

    There's also sync version of the proxies. Everywhere an async proxy returns a promise the sync proxy instead blocks the JavaScript thread waiting for a response from the WebView2 host process. Generally you don't want to block the JavaScript thread waiting for a cross process call but under some circumstances it may be more practical or acceptable. Async proxies have a sync method that will return the synchronous version of the proxy (asynchronously).

    (async function foo () {
      // Note the one await call to get from async proxies to sync proxies
      const syncHostObjects = await chrome.webview.hostObjects.sync;
      const api = syncHostObjects.api;
      api.Username = 'whoever';
      const user = api.Username
      alert (user)
    })();
    

    Relying on the queue

    Since all the JavaScript proxy messages travel on the same queue, and must be handled on the same thread in the WebView2 host app process it may be that using a property setter and not waiting for it to complete and then calling the getter will still force the get message to wait for the set to complete and that your original code is fine.