Skip to content

Advanced Async JavaScript Binding (JSB)

Noam Honig edited this page Aug 25, 2023 · 8 revisions

Work In progress, more coming soon!

Before starting here please make sure you've read https://github.com/cefsharp/CefSharp/wiki/General-Usage#async-javascript-binding-jsb as it's important you understand the fundamentals of how to bind an object as they are not covered in this article.

Javascript Callback

  • Can Execute at a later point in time
  • Can return a result
  • Can pass in simply structs/classes as param
  • Takes param array so multiple arguments can be passed

A IJavascriptCallback is a proxy for a method in Javascript. You can call a bound method and pass in a reference to a function, then at some later point in your .Net code you can execute the IJavascriptCallback.ExecuteAsync method and the Javascript function will be called.

public void TestCallback(IJavascriptCallback javascriptCallback)
{
	const int taskDelay = 1500;

	Task.Run(async () =>
	{
		await Task.Delay(taskDelay);

		using (javascriptCallback)
		{
			await javascriptCallback.ExecuteAsync("This callback from C# was delayed " + taskDelay + "ms");
		}
	});
}
function MyCallback(string msg)
{
	alert("My callback returned - " + msg):
}

boundAsync.testCallback(MyCallback);

Javascript Callback with Promise

Starting in M108 it's possible for IJavascriptCallback.ExecuteAsync to execute a function that returns a Promise. (Prior versions it was possible with lots of extra code).

let callback = function ()
{
	return new Promise((resolve, reject) =>
	{
		setTimeout(() =>
		{
			resolve('Hello from Javascript');
		}, 300);
	});
};

//Call our bound object passing in our callback function
const result = await boundAsync.javascriptCallbackEvalPromise(callback);

In your .Net App you simply need to call the IJavascriptCallback.ExecuteAsync.

public async Task<string> JavascriptCallbackEvalPromise(IJavascriptCallback callback)
{
  //Do some processing then execute our callback
  //The Task will be resolved when the Promise has either been resolved or rejected
  var response = await callback.ExecuteAsync(msg);

  //For this very simple example the result is 'Hello from Javascript'.
  return (string)response.Result;
}

Method Parameters

By default passing primitive types is supported when passing parameters to methods. To support passing POCO objects you register your object with BindingOptions.DefaultBinder passed as the options param. Your could would look like:

//For .Net 4.x
browser.JavascriptObjectRepository.Register("mathService", new MathService(), isAsync: true, options: BindingOptions.DefaultBinder);
//For .Net Core/Net 5.0 (only async object registration is supported)
browser.JavascriptObjectRepository.Register("mathService", new MathService(), options: BindingOptions.DefaultBinder);

Supported types include:

  • Primitives (int, string, bool, etc)
  • Arrays of Primitives (int[], string[], bool[], etc)
  • Plain old C# Objects (POCO) (simple request response objects, sub classes are supported)
  • Structs
  • Arrays/Lists/Dictionaries

POCO Param Example

A simple MathService class that performs a multiplication in c#, you can of course do much more complex things, make database calls, etc

//A simple service implemented in C#
public class MathService
{
  public MultiplyResponse Multiple(MultiplyRequest request)
  {
    if (request == null)
    {
      return new MultiplyResponse { Success = false };
    }

    var result = request.Num1 * request.Num2;

    return new MultiplyResponse { Success = true, Result = result };
  }
}

public class MultiplyRequest
{
  public int Num1 { get; set; }
  public int Num2 { get; set; }
}

public class MultiplyResponse
{
  public bool Success { get; set; }
  public int Result { get; set; }
}

//For legacy reasons objects returned from methods aren't converted to camel case, we use the new CamelCaseJavascriptNameConverter
//If you don't want any name conversion then set browser.JavascriptObjectRepository.NameConverter = null
browser.JavascriptObjectRepository.NameConverter = new CefSharp.JavascriptBinding.CamelCaseJavascriptNameConverter();

//For .Net 4.x
browser.JavascriptObjectRepository.Register("mathService", new MathService(), isAsync: true, options: BindingOptions.DefaultBinder);
//For .Net Core/Net 5.0 (only async object registration is supported)
browser.JavascriptObjectRepository.Register("mathService", new MathService(), options: BindingOptions.DefaultBinder);
(async function()
{
  //Bind the mathService object we registered in C#
  await CefSharp.BindObjectAsync("mathService");
  //We now have a mathService object available in the global scope, can also be accessed via window.mathService
  let response = await mathService.multiple(({Num1:12,Num2:12}));
  
  //Without specifying browser.JavascriptObjectRepository.NameConverter = new CefSharp.JavascriptBinding.CamelCaseJavascriptNameConverter(); in C# above
  // you would have response.Result as the property name here.
  var result = response.result;
})();

Exception Handling

TODO: