Wpf : webbrowser call async silverlight method synchronously in javascript-Collection of common programming errors

You need to pass a callback delegate from C# to JavaScript. This delegate is to be called back when the asynchronous operation is complete on the JavaScript side. What signals the completion is naturally specific to your script logic. It could be an AJAX, DOM or Silverlight event.

As @StephenCleary pointed out in the comments, you could further use a Deferred object on the script side to conveniently wrap the completion event in JavaScript, in a similar way you use a Task (or TaskCompletionSource) to wrap an event in C#. You’d still need to pass a C# callback which you’d invoke when the deferred would get resolved.

In the code below I use JavaScript timer as an example of asynchronous operation. For simplicity, the code doesn’t use deferreds.

C#:

using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;

namespace WpfWebBrowser
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            SetBrowserFeatureControl();
            InitializeComponent();

            this.Loaded += MainWindow_Loaded;
        }

        async void MainWindow_Loaded(object sender, RoutedEventArgs eventArg)
        {
            // navigate the browser
            System.Windows.Navigation.LoadCompletedEventHandler loadedHandler = null;
            var loadedTcs = new TaskCompletionSource();

            loadedHandler = (s, e) =>
            {
                this.webBrowser.LoadCompleted -= loadedHandler;
                loadedTcs.SetResult(true);
            };

            this.webBrowser.LoadCompleted += loadedHandler;
            this.webBrowser.Navigate("http://localhost:81/callback.html");
            await loadedTcs.Task;

            // call the script method "scriptFuncAsync"
            var asyncScriptTcs = new TaskCompletionSource();
            var oncompleted = new ScriptEventHandler((ref object returnResult, object[] args) => 
            {
                // we are here when the script has called us back
                asyncScriptTcs.SetResult(args[0]);
            });

            this.webBrowser.InvokeScript("scriptFuncAsync", oncompleted);
            await asyncScriptTcs.Task;

            // show the result of the asyc call
            dynamic result = asyncScriptTcs.Task.Result;
            MessageBox.Show(result.outcome.ToString());
        }


        /// 
        /// ScriptEventHandler - an adaptor to call C# back from JavaScript or DOM event handlers
        /// 
        [ComVisible(true), ClassInterface(ClassInterfaceType.AutoDispatch)]
        public class ScriptEventHandler
        {
            [ComVisible(false)]
            public delegate void Callback(ref object returnResult, params object[] args);

            [ComVisible(false)]
            private Callback _callback;

            [DispId(0)]
            public object Method(params object[] args)
            {
                var returnResult = Type.Missing; // Type.Missing is "undefined" in JavaScript
                _callback(ref returnResult, args);
                return returnResult;
            }

            public ScriptEventHandler(Callback callback)
            {
                _callback = callback;
            }
        }

        /// 
        /// WebBrowser version control to enable HTML5
        /// http://msdn.microsoft.com/en-us/library/ee330730(v=vs.85).aspx#browser_emulation
        /// 
        void SetBrowserFeatureControl()
        {
            // http://msdn.microsoft.com/en-us/library/ee330720(v=vs.85).aspx

            // FeatureControl settings are per-process
            var fileName = System.IO.Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName);

            // make the control is not running inside Visual Studio Designer
            if (String.Compare(fileName, "devenv.exe", true) == 0 || String.Compare(fileName, "XDesProc.exe", true) == 0)
                return;

            // Webpages containing standards-based !DOCTYPE directives are displayed in IE9/IE10 Standards mode.
            SetBrowserFeatureControlKey("FEATURE_BROWSER_EMULATION", fileName, 9000);
        }

        void SetBrowserFeatureControlKey(string feature, string appName, uint value)
        {
            using (var key = Registry.CurrentUser.CreateSubKey(
                String.Concat(@"Software\Microsoft\Internet Explorer\Main\FeatureControl\", feature),
                RegistryKeyPermissionCheck.ReadWriteSubTree))
            {
                key.SetValue(appName, (UInt32)value, RegistryValueKind.DWord);
            }
        }

    }
}

XAML:



    

http://localhost:81/callback.html:




    
    
    
        window.scriptFuncAsync = function (oncompleted) {
            // do something async, use timer
            setTimeout(function () {
                info.innerHTML = "Async operation completed.";
                oncompleted({ outcome: 42 });
            }, 2000);
        }
    


    Async Script Test Page

[UPDATE]:

Below is a little more complex script illustrating the use of jQuery’s Deferred/Promise pattern. It executes two asynchronous operations (in parallel), then calls back C# when both operations have completed (so their promises have been resolved).




    
    
      
    
        window.scriptFuncAsync = function (oncompleted) {
            // do something async, use timer
            var promise1 = (function () {
                var deferred = $.Deferred();
                setTimeout(function () {
                    deferred.resolve("Async timeout completed.");
                }, 3000);
                return deferred.promise();
            })();

            promise1.done(function () {
                output("promise1 resolved.");
            });

            var promise2 = $.ajax({ url: "http://www.html5rocks.com/en/resources" });

            promise2.done(function () {
                output("promise2 resolved.");
            });

            $.when(promise1, promise2).then(function(result1, result2) {
                output("result1: " + result1);
                output("result2: " + result2.toString().substr(0, 100) + "...");
                oncompleted({ outcome: 42 });
            });

            function output(str) {
                if (!info.firstChild)
                    info.appendChild(document.createTextNode(""));
                info.firstChild.data += str + "\n";
            }
        };
    


    Async Script Test Page