Async Javascript to Native C# with Params ???

2,289 views
Skip to first unread message

AdP

unread,
Aug 12, 2014, 10:57:26 AM8/12/14
to cef...@googlegroups.com
I have been experimenting with CEFGlue in C# recently particularly from the JS>Native integration.

I have tried 3 approaches but none seems to be appropriate.

1 - Through XDR and Custom SchemeHandler - generally great but seems to blocks both the renderer and browser for long running tasks.
2 - Through V8 callbacks - awesome but blocks the renderer for long running tasks
3 - Through cefQuery aka Asynchronous Bindings - does not block the renderer or browser but does not seem to have support for passing any parameters.

I have a trivial requirement it seems.

1 - Asynchronous execution i.e. neither browser nor renderer are to block on long running tasks.
2 - I need to pass parameters and process return values in Javascript.

Is there a feature of CEFGlue that I can experiment with that will allow me to accomplish this task?

Many thanks in advance.

Balázs Szántó

unread,
Aug 18, 2014, 8:36:58 AM8/18/14
to cef...@googlegroups.com
What's up with this? I'd be very interested in about your thoughts on this.

AdP

unread,
Aug 18, 2014, 6:20:09 PM8/18/14
to cef...@googlegroups.com
I found a very suitable and what looks like a intuitive solution for both C# to JS and JS to C# queries including a JS to C# subscription.

For C# to JS I use the following call schema

  1. Invocation starts in browser process. I use sendProcessMessage to form a JS function invocation with structured parameters and send it off to the renderer process. This is asynchronous.
  2. In rederer process I receive this message in the RendererProcessHandler, transform the parameters into a JS call and invoke in synchronous fashion using the V8Context and tryEval. I get the return value and now use sendProcessMessage to send the results back to the browser processs. You can easily set up a scheme to either pass a callback or some other patter so that you can match up original send to the receive in the browser.
  3. Browser process receives the message from renderer in the BrowserClient and handles is as a return value form original call.

For JS to C# I use the following call scheme

  1. In C# RendererProcessHandler I create JS function handles (these are known up front) and register a custom handler for them. Assume one of these is called testJS2Native
  2. JS simply invokes window.testJS2Native.
  3. Call gets intercepted in the handler I registered in #1, I spawn user thread (Task) and do my processing in renderer process. At the end I post the results back to JS via CefTaskRunner and PostTask. Since this is async, I set up 2 call back in my original JS functions - success and failure. PostTask simply does executeFunctionWithCallback on those as needed.
I can't take credit for this solution - Simon Kay worked it out here - http://www.magpcss.org/ceforum/viewtopic.php?f=14&t=11132

I also have JS subscription callback working. I wanted to register subscriptions for certain topics form JS into C# and then have C# call back several times on indicated functions when proper data had arrived from elsewhere. This can be easily done using the same pattern as described in #2 except you have to hold on to both the V8Value (which is your JS function callback) and the V8Context - you get both when your register function is invoked.


What I like about this set up is that I do not have to code anything special in JS...it just works in regular syntax and sematics...and all calls use structured parameter passing.

I can post some code as well if there is interest...

Cheers,
Andrew

Mark Morgan

unread,
Aug 18, 2014, 10:39:02 PM8/18/14
to cef...@googlegroups.com
Hi Andrew,

I would certainly be interested in seeing some code for it.  Have you found the performance is OK?

Regards,

Mark Morgan


--
You received this message because you are subscribed to the Google Groups "CefGlue" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cefglue+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Balázs Szántó

unread,
Sep 9, 2014, 4:39:18 PM9/9/14
to cef...@googlegroups.com
Thanks Andrew! Based on your post I created the following proof of concept app. I found the performance OK. I passed a 5MB image, which took 1sec to transform into base64 string in the c# code, and plus 0,5sec to pass to the js code. It'd be great to have typed arrays support for this reason. If you don't pass around a lot of data, then you're more than fine I guess. How's your experiences?

My CefApp:

using System.Drawing;

using System.Drawing.Imaging;

using System.IO;

using System.Threading;

using System;


namespace Xilium.CefGlue.Client

{

    internal sealed class DemoApp : CefApp

    {

        private CefRenderProcessHandler renderProcessHandler = new DemoRenderProcessHandler();


        protected override CefRenderProcessHandler GetRenderProcessHandler()

        {

            return renderProcessHandler;

        }

    }


    internal class DemoRenderProcessHandler : CefRenderProcessHandler

    {

        MyCustomCefV8Handler myCefV8Handler = new MyCustomCefV8Handler();


        protected override void OnWebKitInitialized()

        {

            base.OnWebKitInitialized();


            var nativeFunction = @"nativeImplementation = function(onSuccess) {

                native function MyNativeFunction(onSuccess);

                return MyNativeFunction(onSuccess);

            };";


            CefRuntime.RegisterExtension("myExtension", nativeFunction, myCefV8Handler);

        }

    }


    internal class MyCustomCefV8Handler : CefV8Handler

    {

        protected override bool Execute(string name, CefV8Value obj, CefV8Value[] arguments, out CefV8Value returnValue,

            out string exception)

        {

            //Debugger.Launch();


            var context = CefV8Context.GetCurrentContext();

            var taskRunner = CefTaskRunner.GetForCurrentThread();

            var callback = arguments[0];

            new Thread(() =>

            {

                //Sleep a bit: to test whether the app remains responsive

                Thread.Sleep(3000);

                taskRunner.PostTask(new CefCallbackTask(context, callback));

            }).Start();


            returnValue = CefV8Value.CreateBool(true);

            exception = null;

            return true;

        }

    }


    internal class CefCallbackTask : CefTask

    {

        private readonly CefV8Context context;

        private readonly CefV8Value callback;


        public CefCallbackTask(CefV8Context context, CefV8Value callback)

        {

            this.context = context;

            this.callback = callback;

        }


        protected override void Execute()

        {

            var callbackArguments = CreateCallbackArguments();

            callback.ExecuteFunctionWithContext(context, null, callbackArguments);

        }


        private CefV8Value[] CreateCallbackArguments()

        {

            var imageInBase64EncodedString = LoadImage(@"C:\hamb.jpg");


            context.Enter();


            var imageV8String = CefV8Value.CreateString(imageInBase64EncodedString);

            var featureV8Object = CefV8Value.CreateObject(null);

            var listOfFeaturesV8Array = CefV8Value.CreateArray(1);


            featureV8Object.SetValue("name", CefV8Value.CreateString("V8"), CefV8PropertyAttribute.None);

            featureV8Object.SetValue("isEnabled", CefV8Value.CreateInt(0), CefV8PropertyAttribute.None);

            featureV8Object.SetValue("isFromJSCode", CefV8Value.CreateBool(false), CefV8PropertyAttribute.None);


            listOfFeaturesV8Array.SetValue(0, featureV8Object);


            context.Exit();


            return new [] { listOfFeaturesV8Array, imageV8String };

        }


        private string LoadImage(string fileName)

        {

            using (var memoryStream = new MemoryStream())

            {

                var image = Bitmap.FromFile(fileName);

                image.Save(memoryStream, ImageFormat.Png);

                byte[] imageBytes = memoryStream.ToArray();

                return Convert.ToBase64String(imageBytes);

            }

        }

    }

}

The html file, that I loaded at the first place:

<!DOCTYPE html>


<html lang="en" xmlns="http://www.w3.org/1999/xhtml">

    <head>

        <meta charset="utf-8" />

        <title>C# and JS experiments</title>

        <script src="index.js"></script>

    </head>

    <body>

        <h1>C# and JS are best friends</h1>

        <div id="features">

        </div>

        

        <div id="image">

        </div>

    </body>

</html>

The javascript code:

function Browser() {

}


Browser.prototype.ListAllFeatures = function (onSuccess) {

    return nativeImplementation(onSuccess);

}


function App(browser) {

    this.browser = browser;

}


App.prototype.Run = function () {

    var beforeRun = new Date().getTime();


    this.browser.ListAllFeatures(function(features, imageInBase64EncodedString) {

        var feautersListString = '';

        for (var i = 0; i < features.length; i++) {

            var f = features[i];

            feautersListString += ('<p>' + 'Name: ' + f.name + ', is enabled: ' + f.isEnabled + ', is called from js code: ' + f.isFromJSCode + '</p>');

        }


        feautersListString += '<p> The image: </p>';

        feautersListString += '<p>' + imageInBase64EncodedString + '</p>';


        document.getElementById("features").innerHTML = feautersListString;

        var afterRun = new Date().getTime();


        document.getElementById("image").innerHTML = '<img src="data:image/png;base64,' + imageInBase64EncodedString + '" />';

        var afterLoadedImage = new Date().getTime();


        console.log("ELAPSED TIME - INSIDE LIST ALL FEATURES: " + (afterRun - beforeRun));

        console.log("ELAPSED TIME - IMAGE IS LOADED TO THE <img> TAG: " + (afterLoadedImage - beforeRun));

    });

}


window.onload = function () {

    var browser = new Browser();

    var application = new App(browser);


    //Lets measure

    var beforeRun = new Date().getTime();

    application.Run();

    var afterRun = new Date().getTime();

    console.log("ELAPSED TIME - INSIDE ONLOAD: " + (afterRun - beforeRun));

AdP

unread,
Sep 10, 2014, 2:18:12 AM9/10/14
to cef...@googlegroups.com
Hi Balazs,

Indeed I found the performance acceptable and I use the interface for passing around data including inline images base 64 encoded just like you have in your POC.

In my case I went with a simple JS method registration in C# native V8 instead of going through the Extension mechanism. This approach allowed me absolutely no custom JS code (including forward declaration of native implementation) but this is personal decision.

Basically JS code looked somewhat like this:

function pasteSomeImage{
  var retVal = window.nativeImagePasteFromClipboard(
    function onSuccess(someValue) { ...},
    function onFailure(someValue( {...});
}

I callback on those functions through V8Context depending on how the operation goes.

Also, I marked by background Thread with single compartment mode (STA mode) in order to allow it to access the Clipboard which is not possible otherwise it seems :)

Cheers,
Andrew

BBird40

unread,
Sep 18, 2014, 4:28:06 PM9/18/14
to cef...@googlegroups.com
Andrew this is good stuff. I would love to see your sample code, if you have time.

I'm still trying to wrap my head around V8, RenderProcessHandling, CefV8Handling, and CefTask and what they all do and what part do they play in this scenario.

AdP

unread,
Oct 19, 2014, 2:16:09 AM10/19/14
to cef...@googlegroups.com
Code sample is in the post I referenced

shijeesh c

unread,
Aug 26, 2016, 2:15:38 AM8/26/16
to CefGlue
I am developing a wpf application by using Xilium.CefGlue and Xilium.CefGlue.WPF. My WPF application is getting crashed after implementing Xilium.CefGlue.CefApp.GetRenderProcessHandler() in SampleCefApp. Before this impementation the application was working fine without any crashes. Actually I need to call a C# function from html local page by javascript. This functionality is working fine in 32 bit version but not in 64 bit. The following is my implementation.

internal sealed class SampleCefApp : CefApp
    {
        public SampleCefApp()
        {
             
        }
        private CefRenderProcessHandler renderProcessHandler = new Views.DemoRenderProcessHandler();
        protected override CefRenderProcessHandler GetRenderProcessHandler()
        {

            return renderProcessHandler;

        }
    }

the following message was showing for app crash
-<ProblemSignatures>

<EventType>APPCRASH</EventType>

<Parameter0>StreetMap.vshost.exe</Parameter0>

<Parameter1>14.0.23107.0</Parameter1>

<Parameter2>559b788a</Parameter2>

<Parameter3>libcef.DLL</Parameter3>

<Parameter4>3.2743.1449.0</Parameter4>

<Parameter5>57bbfe66</Parameter5>

<Parameter6>80000003</Parameter6>

<Parameter7>0000000000b68267</Parameter7>

</ProblemSignatures>


Is ther any issues for libcef dll while working with 64 bit. Is anybody can help for implementing JS to C# call by using Xilium.CefGlue and Xilium.CefGlue.WPF.
The following reference code i am using for this from the link
Any help is appreciated.

Dmitry Azaraev

unread,
Aug 26, 2016, 6:27:09 AM8/26/16
to CefGlue

Provide symbolized call stack please if crash have place. Looks like you something do wrong.

shijeesh c

unread,
Aug 26, 2016, 6:38:36 AM8/26/16
to CefGlue, dmitry....@gmail.com
Hi,
The following app crash message i am getting from system temp folder.

<?xml version="1.0" encoding="UTF-16"?>
<WERReportMetadata>
<OSVersionInformation>
<WindowsNTVersion>6.1</WindowsNTVersion>
<Build>7601 Service Pack 1</Build>
<Product>(0x30): Windows 7 Professional</Product>
<Edition>Professional</Edition>
<BuildString>7601.23418.amd64fre.win7sp1_ldr.160408-2045</BuildString>
<Revision>1130</Revision>
<Flavor>Multiprocessor Free</Flavor>
<Architecture>X64</Architecture>
<LCID>1033</LCID>
</OSVersionInformation>
<ParentProcessInformation>
<ParentProcessId>1752</ParentProcessId>
<ParentProcessPath>C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe</ParentProcessPath>
<ParentProcessCmdLine>&quot;C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe&quot; </ParentProcessCmdLine>
</ParentProcessInformation>
<ProblemSignatures>
<EventType>APPCRASH</EventType>
<Parameter0>StreetMap.vshost.exe</Parameter0>
<Parameter1>14.0.23107.0</Parameter1>
<Parameter2>559b788a</Parameter2>
<Parameter3>libcef.DLL</Parameter3>
<Parameter4>3.2743.1449.0</Parameter4>
<Parameter5>57bbfe66</Parameter5>
<Parameter6>80000003</Parameter6>
<Parameter7>0000000000b68267</Parameter7>
</ProblemSignatures>
<DynamicSignatures>
<Parameter1>6.1.7601.2.1.0.256.48</Parameter1>
<Parameter2>16393</Parameter2>
<Parameter22>ef8e</Parameter22>
<Parameter23>ef8e8506743e2a69f2d557289b35932d</Parameter23>
<Parameter24>b40c</Parameter24>
<Parameter25>b40c830ed7acea71880ec08f0c6e7069</Parameter25>
</DynamicSignatures>
<SystemInformation>
<MarkerFile>1028_Dell_WOR_3620</MarkerFile>
<SystemManufacturer>Dell Inc.</SystemManufacturer>
<SystemProductName>Precision Tower 3620</SystemProductName>
<BIOSVersion>1.3.4</BIOSVersion>
</SystemInformation>
</WERReportMetadata>

Dmitry Azaraev

unread,
Aug 26, 2016, 7:28:29 AM8/26/16
to shijeesh c, CefGlue

Enable cef logging, it may contain useful info. Symbolized call stack is crash call stack, which can be obtained from log (if available) or from crash minidump. WER can create minidumps, but i'm not sure. Anyway provided info is WER's metadata, which doesn't provide any useful info.

Also I see that crash happens in vshost process. Disable visual studio debug hosting process in project properties - it is too weak to deal with child processes correctly.

shijeesh c

unread,
Aug 26, 2016, 7:54:31 AM8/26/16
to CefGlue, shij...@gmail.com, dmitry....@gmail.com
Hi,

I enabled the cef logging. It shows the following log
[0826/171951:ERROR:proxy_service_factory.cc(128)] Cannot use V8 Proxy resolver in single process mode.
.So I changed the SingleProcess=false in CeffSetting. Now the crashing issue is solved  and then the requested webpage is not showing in cefwpfbrowser. Can you please suggest anything is missed in the setting object.

var cefSettings = new CefSettings
                {
                    // BrowserSubprocessPath = browserSubprocessPath,
                    SingleProcess = false,
                    MultiThreadedMessageLoop = true,
                    LogSeverity = CefLogSeverity.Error,
                    LogFile = Path.Combine(AppLogger.GetLogsDirectory(), "CEFEngine.log")
                };

shijeesh c

unread,
Aug 26, 2016, 8:12:58 AM8/26/16
to CefGlue, shij...@gmail.com, dmitry....@gmail.com
Hi,

Now I am getting the following log message.

[0826/173636:VERBOSE1:pref_proxy_config_tracker_impl.cc(151)] 000000001B2A7CC0: set chrome proxy config service to 000000001B234F60
[0826/173636:VERBOSE1:pref_proxy_config_tracker_impl.cc(276)] 000000001B2A7CC0: Done pushing proxy to UpdateProxyConfig
[0826/173637:VERBOSE1:webrtc_internals.cc(85)] Could not get the download directory.

Now the crashing issue is solved  and then the requested webpage is not showing in cefwpfbrowser. Can you please suggest anything is missed in the setting object.


Dmitry Azaraev

unread,
Aug 29, 2016, 11:12:16 AM8/29/16
to shijeesh c, CefGlue
Comment your additions until you get it worked. Then read docs carefully around methods what's you use/add.

PS: single process mode for debugging purposes only.
Reply all
Reply to author
Forward
0 new messages