kandi background
Explore Kits

JsBridge | android java and javascript bridge

 by   lzyzsd Java Version: Current License: No License

 by   lzyzsd Java Version: Current License: No License

Download this library from

kandi X-RAY | JsBridge Summary

JsBridge is a Java library typically used in Telecommunications, Media, Telecom applications. JsBridge has no bugs, it has no vulnerabilities, it has build file available and it has medium support. You can download it from GitHub.
inspired and modified from this and wechat jsBridge file, with some bugs fix and feature enhancement. This project make a bridge between Java and JavaScript. It provides safe and convenient way to call Java code from js and call js code from java.
Support
Support
Quality
Quality
Security
Security
License
License
Reuse
Reuse

kandi-support Support

  • JsBridge has a medium active ecosystem.
  • It has 8460 star(s) with 1746 fork(s). There are 254 watchers for this library.
  • It had no major release in the last 12 months.
  • There are 158 open issues and 49 have been closed. On average issues are closed in 68 days. There are 7 open pull requests and 0 closed requests.
  • It has a neutral sentiment in the developer community.
  • The latest version of JsBridge is current.
JsBridge Support
Best in #Java
Average in #Java
JsBridge Support
Best in #Java
Average in #Java

quality kandi Quality

  • JsBridge has 0 bugs and 0 code smells.
JsBridge Quality
Best in #Java
Average in #Java
JsBridge Quality
Best in #Java
Average in #Java

securitySecurity

  • JsBridge has no vulnerabilities reported, and its dependent libraries have no vulnerabilities reported.
  • JsBridge code analysis shows 0 unresolved vulnerabilities.
  • There are 0 security hotspots that need review.
JsBridge Security
Best in #Java
Average in #Java
JsBridge Security
Best in #Java
Average in #Java

license License

  • JsBridge does not have a standard license declared.
  • Check the repository for any license declaration and review the terms closely.
  • Without a license, all rights are reserved, and you cannot use the library in your applications.
JsBridge License
Best in #Java
Average in #Java
JsBridge License
Best in #Java
Average in #Java

buildReuse

  • JsBridge releases are not available. You will need to build from source code and install.
  • Build file is available. You can build the component from source.
  • Installation instructions are not available. Examples and code snippets are available.
  • JsBridge saves you 439 person hours of effort in developing the same functionality from scratch.
  • It has 1046 lines of code, 95 functions and 20 files.
  • It has medium code complexity. Code complexity directly impacts maintainability of the code.
JsBridge Reuse
Best in #Java
Average in #Java
JsBridge Reuse
Best in #Java
Average in #Java
Top functions reviewed by kandi - BETA

kandi has reviewed JsBridge and discovered the below as its top functions. This is intended to give you an instant insight into JsBridge implemented functionality, and help decide if they suit your requirements.

  • flush message queue
  • Initializes the dialog .
  • Read asset file .
  • This method is called when an activity is received .
  • Sends a JSON response .
  • Checks if url should be overridden .
  • Initializes the bridge .
  • Called when a page is started .
  • This method is called when form submission has been submitted .
  • Loads the javascript from the web view .

JsBridge Key Features

android java and javascript bridge, inspired by wechat webview jsbridge

JitPack.io

copy iconCopydownload iconDownload
repositories {
    // ...
    maven { url "https://jitpack.io" }
}

dependencies {
    compile 'com.github.lzyzsd:jsbridge:1.0.4'
}

Register a Java handler function so that js can call

copy iconCopydownload iconDownload

    webView.registerHandler("submitFromWeb", new BridgeHandler() {
        @Override
        public void handler(String data, CallBackFunction function) {
            Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
            function.onCallBack("submitFromWeb exe, response data from Java");
        }
    });

Register a JavaScript handler function so that Java can call

copy iconCopydownload iconDownload

    WebViewJavascriptBridge.registerHandler("functionInJs", function(data, responseCallback) {
        document.getElementById("show").innerHTML = ("data from Java: = " + data);
        var responseData = "Javascript Says Right back aka!";
        responseCallback(responseData);
    });

Notice

copy iconCopydownload iconDownload

    if (window.WebViewJavascriptBridge) {
        //do your work here
    } else {
        document.addEventListener(
            'WebViewJavascriptBridgeReady'
            , function() {
                //do your work here
            },
            false
        );
    }

how to call c# code via react jsx in xamarin webview

copy iconCopydownload iconDownload
export default function test(data: string){
    eval(`invokeTest(${data})`);
}
export default function Button(props: ButtonProps){
    const dispatch = useAppDispatch()

    const handleClick = () =>{
        test('1234');
    }

    return(
        <ButtonWrapper heightValue={props.heightValue} widthValue={props.widthValue} marginValue={props.marginValue}>
            <Button variant="outlined" onClick={() => handleClick()} style={{height: '56px', width: '100%', fontSize: 'large'}}>
                send
            </Button>
        </ButtonWrapper>
    )
}
public class WebViewer : WebView
{
    public static readonly BindableProperty UriProperty = BindableProperty.Create(
            propertyName: "Uri",
            returnType: typeof(string),
            declaringType: typeof(WebViewer),
            defaultValue: default(string),
            propertyChanged: UriChanged);

    private static void UriChanged(BindableObject bindable, object oldValue, object newValue)
    {
         if (newValue != null && bindable is WebViewer webViewer)
         {
            if(webViewer.CookieList != null && (string)newValue == (string)oldValue)
            {
                Uri uri = new Uri((string)newValue, UriKind.RelativeOrAbsolute);

                CookieContainer cookieContainer = new CookieContainer();

                foreach (Cookie cookie in webViewer.CookieList)
                {
                    cookie.Domain = uri.Host;
                    cookie.Path = uri.PathAndQuery;
                    cookieContainer.Add(cookie);
                }
                webViewer.Cookies = cookieContainer;
             }
             webViewer.Source = (string)newValue;
          }
     }

     public string Uri
     {
         get => (string)GetValue(UriProperty);
         set => SetValue(UriProperty, value);
     }

     public static readonly BindableProperty CookieListProperty = BindableProperty.Create(
            propertyName: "Cookies",
            returnType: typeof(List<Cookie>),
            declaringType: typeof(WebViewer),
            defaultValue: null,
            propertyChanged: CookieListChanged);
        
     public List<Cookie> CookieList
     {
         get => (List<Cookie>)GetValue(CookieListProperty);
         set => SetValue(CookieListProperty, value);
     }
        
     private static void CookieListChanged(BindableObject bindable, object oldValue, object newValue)
     {
         if (newValue != null && bindable is WebViewer webViewer)
         {
             Uri uri = new Uri(webViewer.Uri, UriKind.RelativeOrAbsolute);

             CookieContainer cookieContainer = new CookieContainer();

             foreach (Cookie cookie in (List<Cookie>)newValue)
             {
                 cookie.Domain = uri.Host;
                 cookie.Path = uri.PathAndQuery;
                 cookieContainer.Add(cookie);
             }
             webViewer.Cookies = cookieContainer;

             if (webViewer.Uri != default(string))
             {
                 webViewer.Source = webViewer.Uri;
             }
         }
     }

     Action<string> action;
     public void RegisterAction(Action<string> callback)
     {
         action = callback;
     }

     public void Cleanup()
     {
         action = null;
     }

     public void InvokeAction(string data)
     {
         if (action == null || data == null)
         {
             return;
         }
         action.Invoke(data);
     }
}
using Android.Content;
using Android.Views;
using Android.Webkit;
using BDApp.Mobile.Droid.CustomRenderer;
using BDApp.Mobile.Views;
using Java.Interop;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewerRenderer))]
namespace BDApp.Mobile.Droid.CustomRenderer
{
    public class WebViewerRenderer : WebViewRenderer
    {
        const string JavascriptFunction = "function invokeTest(data){jsBridge.invokeAction(data);}";
        public WebViewerRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);

            if(e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                ((WebViewer)Element).Cleanup();
            }
            if(e.NewElement != null)
            {
                Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
                Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
            }
            Control.Settings.SetAppCacheEnabled(false);
            Control.Settings.CacheMode = CacheModes.NoCache;
        }

        public override bool DispatchTouchEvent(MotionEvent e)
        {
            Parent.RequestDisallowInterceptTouchEvent(true);
            return base.DispatchTouchEvent(e);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((WebViewer)Element).Cleanup();
            }
            base.Dispose(disposing);
        }
    }
    public class JavascriptWebViewClient : FormsWebViewClient
    {
        string _javascript;

        public JavascriptWebViewClient(WebViewerRenderer renderer, string javascript) : base(renderer)
        {
            _javascript = javascript;
        }

        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            base.OnPageFinished(view, url);
            view.EvaluateJavascript(_javascript, null);
        }
    }
    public class JSBridge : Java.Lang.Object
    {
        readonly WeakReference<WebViewerRenderer> hybridWebViewRenderer;

        public JSBridge(WebViewerRenderer hybridRenderer)
        {
            hybridWebViewRenderer = new WeakReference<WebViewerRenderer>(hybridRenderer);
        }

        [JavascriptInterface]
        [Export("invokeAction")]
        public void InvokeAction(string data)
        {
            WebViewerRenderer hybridRenderer;

            if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
            {
                ((WebViewer)hybridRenderer.Element).InvokeAction(data);
            }
        }
    }
}
<StackLayout>
        <views:WebViewer Uri="{Binding Uri}" x:Name="webView" CookieList="{Binding CookieList}"
                         HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/>
</StackLayout>
-----------------------
export default function test(data: string){
    eval(`invokeTest(${data})`);
}
export default function Button(props: ButtonProps){
    const dispatch = useAppDispatch()

    const handleClick = () =>{
        test('1234');
    }

    return(
        <ButtonWrapper heightValue={props.heightValue} widthValue={props.widthValue} marginValue={props.marginValue}>
            <Button variant="outlined" onClick={() => handleClick()} style={{height: '56px', width: '100%', fontSize: 'large'}}>
                send
            </Button>
        </ButtonWrapper>
    )
}
public class WebViewer : WebView
{
    public static readonly BindableProperty UriProperty = BindableProperty.Create(
            propertyName: "Uri",
            returnType: typeof(string),
            declaringType: typeof(WebViewer),
            defaultValue: default(string),
            propertyChanged: UriChanged);

    private static void UriChanged(BindableObject bindable, object oldValue, object newValue)
    {
         if (newValue != null && bindable is WebViewer webViewer)
         {
            if(webViewer.CookieList != null && (string)newValue == (string)oldValue)
            {
                Uri uri = new Uri((string)newValue, UriKind.RelativeOrAbsolute);

                CookieContainer cookieContainer = new CookieContainer();

                foreach (Cookie cookie in webViewer.CookieList)
                {
                    cookie.Domain = uri.Host;
                    cookie.Path = uri.PathAndQuery;
                    cookieContainer.Add(cookie);
                }
                webViewer.Cookies = cookieContainer;
             }
             webViewer.Source = (string)newValue;
          }
     }

     public string Uri
     {
         get => (string)GetValue(UriProperty);
         set => SetValue(UriProperty, value);
     }

     public static readonly BindableProperty CookieListProperty = BindableProperty.Create(
            propertyName: "Cookies",
            returnType: typeof(List<Cookie>),
            declaringType: typeof(WebViewer),
            defaultValue: null,
            propertyChanged: CookieListChanged);
        
     public List<Cookie> CookieList
     {
         get => (List<Cookie>)GetValue(CookieListProperty);
         set => SetValue(CookieListProperty, value);
     }
        
     private static void CookieListChanged(BindableObject bindable, object oldValue, object newValue)
     {
         if (newValue != null && bindable is WebViewer webViewer)
         {
             Uri uri = new Uri(webViewer.Uri, UriKind.RelativeOrAbsolute);

             CookieContainer cookieContainer = new CookieContainer();

             foreach (Cookie cookie in (List<Cookie>)newValue)
             {
                 cookie.Domain = uri.Host;
                 cookie.Path = uri.PathAndQuery;
                 cookieContainer.Add(cookie);
             }
             webViewer.Cookies = cookieContainer;

             if (webViewer.Uri != default(string))
             {
                 webViewer.Source = webViewer.Uri;
             }
         }
     }

     Action<string> action;
     public void RegisterAction(Action<string> callback)
     {
         action = callback;
     }

     public void Cleanup()
     {
         action = null;
     }

     public void InvokeAction(string data)
     {
         if (action == null || data == null)
         {
             return;
         }
         action.Invoke(data);
     }
}
using Android.Content;
using Android.Views;
using Android.Webkit;
using BDApp.Mobile.Droid.CustomRenderer;
using BDApp.Mobile.Views;
using Java.Interop;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewerRenderer))]
namespace BDApp.Mobile.Droid.CustomRenderer
{
    public class WebViewerRenderer : WebViewRenderer
    {
        const string JavascriptFunction = "function invokeTest(data){jsBridge.invokeAction(data);}";
        public WebViewerRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);

            if(e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                ((WebViewer)Element).Cleanup();
            }
            if(e.NewElement != null)
            {
                Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
                Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
            }
            Control.Settings.SetAppCacheEnabled(false);
            Control.Settings.CacheMode = CacheModes.NoCache;
        }

        public override bool DispatchTouchEvent(MotionEvent e)
        {
            Parent.RequestDisallowInterceptTouchEvent(true);
            return base.DispatchTouchEvent(e);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((WebViewer)Element).Cleanup();
            }
            base.Dispose(disposing);
        }
    }
    public class JavascriptWebViewClient : FormsWebViewClient
    {
        string _javascript;

        public JavascriptWebViewClient(WebViewerRenderer renderer, string javascript) : base(renderer)
        {
            _javascript = javascript;
        }

        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            base.OnPageFinished(view, url);
            view.EvaluateJavascript(_javascript, null);
        }
    }
    public class JSBridge : Java.Lang.Object
    {
        readonly WeakReference<WebViewerRenderer> hybridWebViewRenderer;

        public JSBridge(WebViewerRenderer hybridRenderer)
        {
            hybridWebViewRenderer = new WeakReference<WebViewerRenderer>(hybridRenderer);
        }

        [JavascriptInterface]
        [Export("invokeAction")]
        public void InvokeAction(string data)
        {
            WebViewerRenderer hybridRenderer;

            if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
            {
                ((WebViewer)hybridRenderer.Element).InvokeAction(data);
            }
        }
    }
}
<StackLayout>
        <views:WebViewer Uri="{Binding Uri}" x:Name="webView" CookieList="{Binding CookieList}"
                         HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/>
</StackLayout>
-----------------------
export default function test(data: string){
    eval(`invokeTest(${data})`);
}
export default function Button(props: ButtonProps){
    const dispatch = useAppDispatch()

    const handleClick = () =>{
        test('1234');
    }

    return(
        <ButtonWrapper heightValue={props.heightValue} widthValue={props.widthValue} marginValue={props.marginValue}>
            <Button variant="outlined" onClick={() => handleClick()} style={{height: '56px', width: '100%', fontSize: 'large'}}>
                send
            </Button>
        </ButtonWrapper>
    )
}
public class WebViewer : WebView
{
    public static readonly BindableProperty UriProperty = BindableProperty.Create(
            propertyName: "Uri",
            returnType: typeof(string),
            declaringType: typeof(WebViewer),
            defaultValue: default(string),
            propertyChanged: UriChanged);

    private static void UriChanged(BindableObject bindable, object oldValue, object newValue)
    {
         if (newValue != null && bindable is WebViewer webViewer)
         {
            if(webViewer.CookieList != null && (string)newValue == (string)oldValue)
            {
                Uri uri = new Uri((string)newValue, UriKind.RelativeOrAbsolute);

                CookieContainer cookieContainer = new CookieContainer();

                foreach (Cookie cookie in webViewer.CookieList)
                {
                    cookie.Domain = uri.Host;
                    cookie.Path = uri.PathAndQuery;
                    cookieContainer.Add(cookie);
                }
                webViewer.Cookies = cookieContainer;
             }
             webViewer.Source = (string)newValue;
          }
     }

     public string Uri
     {
         get => (string)GetValue(UriProperty);
         set => SetValue(UriProperty, value);
     }

     public static readonly BindableProperty CookieListProperty = BindableProperty.Create(
            propertyName: "Cookies",
            returnType: typeof(List<Cookie>),
            declaringType: typeof(WebViewer),
            defaultValue: null,
            propertyChanged: CookieListChanged);
        
     public List<Cookie> CookieList
     {
         get => (List<Cookie>)GetValue(CookieListProperty);
         set => SetValue(CookieListProperty, value);
     }
        
     private static void CookieListChanged(BindableObject bindable, object oldValue, object newValue)
     {
         if (newValue != null && bindable is WebViewer webViewer)
         {
             Uri uri = new Uri(webViewer.Uri, UriKind.RelativeOrAbsolute);

             CookieContainer cookieContainer = new CookieContainer();

             foreach (Cookie cookie in (List<Cookie>)newValue)
             {
                 cookie.Domain = uri.Host;
                 cookie.Path = uri.PathAndQuery;
                 cookieContainer.Add(cookie);
             }
             webViewer.Cookies = cookieContainer;

             if (webViewer.Uri != default(string))
             {
                 webViewer.Source = webViewer.Uri;
             }
         }
     }

     Action<string> action;
     public void RegisterAction(Action<string> callback)
     {
         action = callback;
     }

     public void Cleanup()
     {
         action = null;
     }

     public void InvokeAction(string data)
     {
         if (action == null || data == null)
         {
             return;
         }
         action.Invoke(data);
     }
}
using Android.Content;
using Android.Views;
using Android.Webkit;
using BDApp.Mobile.Droid.CustomRenderer;
using BDApp.Mobile.Views;
using Java.Interop;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewerRenderer))]
namespace BDApp.Mobile.Droid.CustomRenderer
{
    public class WebViewerRenderer : WebViewRenderer
    {
        const string JavascriptFunction = "function invokeTest(data){jsBridge.invokeAction(data);}";
        public WebViewerRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);

            if(e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                ((WebViewer)Element).Cleanup();
            }
            if(e.NewElement != null)
            {
                Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
                Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
            }
            Control.Settings.SetAppCacheEnabled(false);
            Control.Settings.CacheMode = CacheModes.NoCache;
        }

        public override bool DispatchTouchEvent(MotionEvent e)
        {
            Parent.RequestDisallowInterceptTouchEvent(true);
            return base.DispatchTouchEvent(e);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((WebViewer)Element).Cleanup();
            }
            base.Dispose(disposing);
        }
    }
    public class JavascriptWebViewClient : FormsWebViewClient
    {
        string _javascript;

        public JavascriptWebViewClient(WebViewerRenderer renderer, string javascript) : base(renderer)
        {
            _javascript = javascript;
        }

        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            base.OnPageFinished(view, url);
            view.EvaluateJavascript(_javascript, null);
        }
    }
    public class JSBridge : Java.Lang.Object
    {
        readonly WeakReference<WebViewerRenderer> hybridWebViewRenderer;

        public JSBridge(WebViewerRenderer hybridRenderer)
        {
            hybridWebViewRenderer = new WeakReference<WebViewerRenderer>(hybridRenderer);
        }

        [JavascriptInterface]
        [Export("invokeAction")]
        public void InvokeAction(string data)
        {
            WebViewerRenderer hybridRenderer;

            if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
            {
                ((WebViewer)hybridRenderer.Element).InvokeAction(data);
            }
        }
    }
}
<StackLayout>
        <views:WebViewer Uri="{Binding Uri}" x:Name="webView" CookieList="{Binding CookieList}"
                         HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/>
</StackLayout>
-----------------------
export default function test(data: string){
    eval(`invokeTest(${data})`);
}
export default function Button(props: ButtonProps){
    const dispatch = useAppDispatch()

    const handleClick = () =>{
        test('1234');
    }

    return(
        <ButtonWrapper heightValue={props.heightValue} widthValue={props.widthValue} marginValue={props.marginValue}>
            <Button variant="outlined" onClick={() => handleClick()} style={{height: '56px', width: '100%', fontSize: 'large'}}>
                send
            </Button>
        </ButtonWrapper>
    )
}
public class WebViewer : WebView
{
    public static readonly BindableProperty UriProperty = BindableProperty.Create(
            propertyName: "Uri",
            returnType: typeof(string),
            declaringType: typeof(WebViewer),
            defaultValue: default(string),
            propertyChanged: UriChanged);

    private static void UriChanged(BindableObject bindable, object oldValue, object newValue)
    {
         if (newValue != null && bindable is WebViewer webViewer)
         {
            if(webViewer.CookieList != null && (string)newValue == (string)oldValue)
            {
                Uri uri = new Uri((string)newValue, UriKind.RelativeOrAbsolute);

                CookieContainer cookieContainer = new CookieContainer();

                foreach (Cookie cookie in webViewer.CookieList)
                {
                    cookie.Domain = uri.Host;
                    cookie.Path = uri.PathAndQuery;
                    cookieContainer.Add(cookie);
                }
                webViewer.Cookies = cookieContainer;
             }
             webViewer.Source = (string)newValue;
          }
     }

     public string Uri
     {
         get => (string)GetValue(UriProperty);
         set => SetValue(UriProperty, value);
     }

     public static readonly BindableProperty CookieListProperty = BindableProperty.Create(
            propertyName: "Cookies",
            returnType: typeof(List<Cookie>),
            declaringType: typeof(WebViewer),
            defaultValue: null,
            propertyChanged: CookieListChanged);
        
     public List<Cookie> CookieList
     {
         get => (List<Cookie>)GetValue(CookieListProperty);
         set => SetValue(CookieListProperty, value);
     }
        
     private static void CookieListChanged(BindableObject bindable, object oldValue, object newValue)
     {
         if (newValue != null && bindable is WebViewer webViewer)
         {
             Uri uri = new Uri(webViewer.Uri, UriKind.RelativeOrAbsolute);

             CookieContainer cookieContainer = new CookieContainer();

             foreach (Cookie cookie in (List<Cookie>)newValue)
             {
                 cookie.Domain = uri.Host;
                 cookie.Path = uri.PathAndQuery;
                 cookieContainer.Add(cookie);
             }
             webViewer.Cookies = cookieContainer;

             if (webViewer.Uri != default(string))
             {
                 webViewer.Source = webViewer.Uri;
             }
         }
     }

     Action<string> action;
     public void RegisterAction(Action<string> callback)
     {
         action = callback;
     }

     public void Cleanup()
     {
         action = null;
     }

     public void InvokeAction(string data)
     {
         if (action == null || data == null)
         {
             return;
         }
         action.Invoke(data);
     }
}
using Android.Content;
using Android.Views;
using Android.Webkit;
using BDApp.Mobile.Droid.CustomRenderer;
using BDApp.Mobile.Views;
using Java.Interop;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewerRenderer))]
namespace BDApp.Mobile.Droid.CustomRenderer
{
    public class WebViewerRenderer : WebViewRenderer
    {
        const string JavascriptFunction = "function invokeTest(data){jsBridge.invokeAction(data);}";
        public WebViewerRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);

            if(e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                ((WebViewer)Element).Cleanup();
            }
            if(e.NewElement != null)
            {
                Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
                Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
            }
            Control.Settings.SetAppCacheEnabled(false);
            Control.Settings.CacheMode = CacheModes.NoCache;
        }

        public override bool DispatchTouchEvent(MotionEvent e)
        {
            Parent.RequestDisallowInterceptTouchEvent(true);
            return base.DispatchTouchEvent(e);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((WebViewer)Element).Cleanup();
            }
            base.Dispose(disposing);
        }
    }
    public class JavascriptWebViewClient : FormsWebViewClient
    {
        string _javascript;

        public JavascriptWebViewClient(WebViewerRenderer renderer, string javascript) : base(renderer)
        {
            _javascript = javascript;
        }

        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            base.OnPageFinished(view, url);
            view.EvaluateJavascript(_javascript, null);
        }
    }
    public class JSBridge : Java.Lang.Object
    {
        readonly WeakReference<WebViewerRenderer> hybridWebViewRenderer;

        public JSBridge(WebViewerRenderer hybridRenderer)
        {
            hybridWebViewRenderer = new WeakReference<WebViewerRenderer>(hybridRenderer);
        }

        [JavascriptInterface]
        [Export("invokeAction")]
        public void InvokeAction(string data)
        {
            WebViewerRenderer hybridRenderer;

            if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
            {
                ((WebViewer)hybridRenderer.Element).InvokeAction(data);
            }
        }
    }
}
<StackLayout>
        <views:WebViewer Uri="{Binding Uri}" x:Name="webView" CookieList="{Binding CookieList}"
                         HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/>
</StackLayout>
-----------------------
export default function test(data: string){
    eval(`invokeTest(${data})`);
}
export default function Button(props: ButtonProps){
    const dispatch = useAppDispatch()

    const handleClick = () =>{
        test('1234');
    }

    return(
        <ButtonWrapper heightValue={props.heightValue} widthValue={props.widthValue} marginValue={props.marginValue}>
            <Button variant="outlined" onClick={() => handleClick()} style={{height: '56px', width: '100%', fontSize: 'large'}}>
                send
            </Button>
        </ButtonWrapper>
    )
}
public class WebViewer : WebView
{
    public static readonly BindableProperty UriProperty = BindableProperty.Create(
            propertyName: "Uri",
            returnType: typeof(string),
            declaringType: typeof(WebViewer),
            defaultValue: default(string),
            propertyChanged: UriChanged);

    private static void UriChanged(BindableObject bindable, object oldValue, object newValue)
    {
         if (newValue != null && bindable is WebViewer webViewer)
         {
            if(webViewer.CookieList != null && (string)newValue == (string)oldValue)
            {
                Uri uri = new Uri((string)newValue, UriKind.RelativeOrAbsolute);

                CookieContainer cookieContainer = new CookieContainer();

                foreach (Cookie cookie in webViewer.CookieList)
                {
                    cookie.Domain = uri.Host;
                    cookie.Path = uri.PathAndQuery;
                    cookieContainer.Add(cookie);
                }
                webViewer.Cookies = cookieContainer;
             }
             webViewer.Source = (string)newValue;
          }
     }

     public string Uri
     {
         get => (string)GetValue(UriProperty);
         set => SetValue(UriProperty, value);
     }

     public static readonly BindableProperty CookieListProperty = BindableProperty.Create(
            propertyName: "Cookies",
            returnType: typeof(List<Cookie>),
            declaringType: typeof(WebViewer),
            defaultValue: null,
            propertyChanged: CookieListChanged);
        
     public List<Cookie> CookieList
     {
         get => (List<Cookie>)GetValue(CookieListProperty);
         set => SetValue(CookieListProperty, value);
     }
        
     private static void CookieListChanged(BindableObject bindable, object oldValue, object newValue)
     {
         if (newValue != null && bindable is WebViewer webViewer)
         {
             Uri uri = new Uri(webViewer.Uri, UriKind.RelativeOrAbsolute);

             CookieContainer cookieContainer = new CookieContainer();

             foreach (Cookie cookie in (List<Cookie>)newValue)
             {
                 cookie.Domain = uri.Host;
                 cookie.Path = uri.PathAndQuery;
                 cookieContainer.Add(cookie);
             }
             webViewer.Cookies = cookieContainer;

             if (webViewer.Uri != default(string))
             {
                 webViewer.Source = webViewer.Uri;
             }
         }
     }

     Action<string> action;
     public void RegisterAction(Action<string> callback)
     {
         action = callback;
     }

     public void Cleanup()
     {
         action = null;
     }

     public void InvokeAction(string data)
     {
         if (action == null || data == null)
         {
             return;
         }
         action.Invoke(data);
     }
}
using Android.Content;
using Android.Views;
using Android.Webkit;
using BDApp.Mobile.Droid.CustomRenderer;
using BDApp.Mobile.Views;
using Java.Interop;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewerRenderer))]
namespace BDApp.Mobile.Droid.CustomRenderer
{
    public class WebViewerRenderer : WebViewRenderer
    {
        const string JavascriptFunction = "function invokeTest(data){jsBridge.invokeAction(data);}";
        public WebViewerRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);

            if(e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                ((WebViewer)Element).Cleanup();
            }
            if(e.NewElement != null)
            {
                Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
                Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
            }
            Control.Settings.SetAppCacheEnabled(false);
            Control.Settings.CacheMode = CacheModes.NoCache;
        }

        public override bool DispatchTouchEvent(MotionEvent e)
        {
            Parent.RequestDisallowInterceptTouchEvent(true);
            return base.DispatchTouchEvent(e);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((WebViewer)Element).Cleanup();
            }
            base.Dispose(disposing);
        }
    }
    public class JavascriptWebViewClient : FormsWebViewClient
    {
        string _javascript;

        public JavascriptWebViewClient(WebViewerRenderer renderer, string javascript) : base(renderer)
        {
            _javascript = javascript;
        }

        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            base.OnPageFinished(view, url);
            view.EvaluateJavascript(_javascript, null);
        }
    }
    public class JSBridge : Java.Lang.Object
    {
        readonly WeakReference<WebViewerRenderer> hybridWebViewRenderer;

        public JSBridge(WebViewerRenderer hybridRenderer)
        {
            hybridWebViewRenderer = new WeakReference<WebViewerRenderer>(hybridRenderer);
        }

        [JavascriptInterface]
        [Export("invokeAction")]
        public void InvokeAction(string data)
        {
            WebViewerRenderer hybridRenderer;

            if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
            {
                ((WebViewer)hybridRenderer.Element).InvokeAction(data);
            }
        }
    }
}
<StackLayout>
        <views:WebViewer Uri="{Binding Uri}" x:Name="webView" CookieList="{Binding CookieList}"
                         HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/>
</StackLayout>

Xamarin Android WebView don't fire navigating if &quot;target=_blank&quot;

copy iconCopydownload iconDownload
Control.Settings.SetSupportMultipleWindows(true);
        public override bool OnCreateWindow(Android.Webkit.WebView view, bool isDialog, bool isUserGesture, Android.OS.Message resultMsg)
    {
        Android.Webkit.WebView newWebView = new Android.Webkit.WebView(_context);
        view.AddView(newWebView);
        Android.Webkit.WebView.WebViewTransport transport = (Android.Webkit.WebView.WebViewTransport) resultMsg.Obj;
        transport.WebView = newWebView;
        resultMsg.SendToTarget();
        return true;
    }
-----------------------
Control.Settings.SetSupportMultipleWindows(true);
        public override bool OnCreateWindow(Android.Webkit.WebView view, bool isDialog, bool isUserGesture, Android.OS.Message resultMsg)
    {
        Android.Webkit.WebView newWebView = new Android.Webkit.WebView(_context);
        view.AddView(newWebView);
        Android.Webkit.WebView.WebViewTransport transport = (Android.Webkit.WebView.WebViewTransport) resultMsg.Obj;
        transport.WebView = newWebView;
        resultMsg.SendToTarget();
        return true;
    }

RESCO : Set Look Up view dynamically on a lookup field on change of a field on the form using JSBridge

copy iconCopydownload iconDownload
onLoad: function () {

 MobileCRM.UI.EntityForm.requestObject(function (entityForm) {


 var detailView = entityForm.getDetailView("Booking");

 var itemIndex = detailView.getItemIndex("vWO.msdyn_substatus"); //Not required, Used this to get the index of my lookup control on the form


 var customXMLView = '<fetch version="1.0"><entity name="msdyn_workordersubstatus">' +
 '<filter type="and"><condition attribute="msdyn_workordersubstatusid" operator="eq" 
 value="{90F7A06F-CA1C-EA11-A811-000D3A6AACAF}"/></filter></entity></fetch>';

 /// Create inline lookup setup (This shows when you click inside lookup without opening the dialog)
inlineSetup = new MobileCRM.UI.DetailViewItems.LookupSetup();
inlineSetup.addFilter("msdyn_workordersubstatus", customXMLView);

//Dialog Look Up (This shows when we click on the plus button)
 var dialogSetup = new MobileCRM.UI.DetailViewItems.LookupSetup();
dialogSetup.addView("msdyn_workordersubstatus", "WorkOrderSubStatusList", true);


var dialogOnly = false; // Allow both inline lookup and expanded lookup dialog


 detailView.updateLinkItemViews(11, dialogSetup, inlineSetup, dialogOnly); //here the 
 first parameter is the index of the  lookup field


 });
},
detailView.updateLinkItemViews(0, dialogSetup, inlineSetup, dialogOnly);
detailView.updateLinkItemViews(11, dialogSetup, inlineSetup, dialogOnly);
-----------------------
onLoad: function () {

 MobileCRM.UI.EntityForm.requestObject(function (entityForm) {


 var detailView = entityForm.getDetailView("Booking");

 var itemIndex = detailView.getItemIndex("vWO.msdyn_substatus"); //Not required, Used this to get the index of my lookup control on the form


 var customXMLView = '<fetch version="1.0"><entity name="msdyn_workordersubstatus">' +
 '<filter type="and"><condition attribute="msdyn_workordersubstatusid" operator="eq" 
 value="{90F7A06F-CA1C-EA11-A811-000D3A6AACAF}"/></filter></entity></fetch>';

 /// Create inline lookup setup (This shows when you click inside lookup without opening the dialog)
inlineSetup = new MobileCRM.UI.DetailViewItems.LookupSetup();
inlineSetup.addFilter("msdyn_workordersubstatus", customXMLView);

//Dialog Look Up (This shows when we click on the plus button)
 var dialogSetup = new MobileCRM.UI.DetailViewItems.LookupSetup();
dialogSetup.addView("msdyn_workordersubstatus", "WorkOrderSubStatusList", true);


var dialogOnly = false; // Allow both inline lookup and expanded lookup dialog


 detailView.updateLinkItemViews(11, dialogSetup, inlineSetup, dialogOnly); //here the 
 first parameter is the index of the  lookup field


 });
},
detailView.updateLinkItemViews(0, dialogSetup, inlineSetup, dialogOnly);
detailView.updateLinkItemViews(11, dialogSetup, inlineSetup, dialogOnly);
-----------------------
onLoad: function () {

 MobileCRM.UI.EntityForm.requestObject(function (entityForm) {


 var detailView = entityForm.getDetailView("Booking");

 var itemIndex = detailView.getItemIndex("vWO.msdyn_substatus"); //Not required, Used this to get the index of my lookup control on the form


 var customXMLView = '<fetch version="1.0"><entity name="msdyn_workordersubstatus">' +
 '<filter type="and"><condition attribute="msdyn_workordersubstatusid" operator="eq" 
 value="{90F7A06F-CA1C-EA11-A811-000D3A6AACAF}"/></filter></entity></fetch>';

 /// Create inline lookup setup (This shows when you click inside lookup without opening the dialog)
inlineSetup = new MobileCRM.UI.DetailViewItems.LookupSetup();
inlineSetup.addFilter("msdyn_workordersubstatus", customXMLView);

//Dialog Look Up (This shows when we click on the plus button)
 var dialogSetup = new MobileCRM.UI.DetailViewItems.LookupSetup();
dialogSetup.addView("msdyn_workordersubstatus", "WorkOrderSubStatusList", true);


var dialogOnly = false; // Allow both inline lookup and expanded lookup dialog


 detailView.updateLinkItemViews(11, dialogSetup, inlineSetup, dialogOnly); //here the 
 first parameter is the index of the  lookup field


 });
},
detailView.updateLinkItemViews(0, dialogSetup, inlineSetup, dialogOnly);
detailView.updateLinkItemViews(11, dialogSetup, inlineSetup, dialogOnly);

How to access foreground service from WebView Javascript Bridge

copy iconCopydownload iconDownload
Intent startServiceIntent = new Intent(this, typeof(NotificationService));
BindService(startServiceIntent, serviceConnection, Bind.AutoCreate);
StartService(startServiceIntent);

Xamarin.Forms ERR_FILE_NOT_FOUND (file:///android_asset/Content/https://stackoverflow.com) using HybridWebViewSample

copy iconCopydownload iconDownload
Control.LoadUrl($"file:///android_asset/Content/{((HybridWebView)Element).Uri}");
UrlWebViewSource source = Element.Source as UrlWebViewSource;
Control.LoadUrl(source.Url);
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{           
    base.OnElementChanged(e);

    if (e.OldElement != null)
    {
        Control.RemoveJavascriptInterface("jsBridge");
        ((HybridWebView)Element).Cleanup();
    }
    if (e.NewElement != null)
    {
        Control.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
        Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
        UrlWebViewSource source = Element.Source as UrlWebViewSource;
        Control.LoadUrl(source.Url);
    }
} 
-----------------------
Control.LoadUrl($"file:///android_asset/Content/{((HybridWebView)Element).Uri}");
UrlWebViewSource source = Element.Source as UrlWebViewSource;
Control.LoadUrl(source.Url);
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{           
    base.OnElementChanged(e);

    if (e.OldElement != null)
    {
        Control.RemoveJavascriptInterface("jsBridge");
        ((HybridWebView)Element).Cleanup();
    }
    if (e.NewElement != null)
    {
        Control.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
        Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
        UrlWebViewSource source = Element.Source as UrlWebViewSource;
        Control.LoadUrl(source.Url);
    }
} 
-----------------------
Control.LoadUrl($"file:///android_asset/Content/{((HybridWebView)Element).Uri}");
UrlWebViewSource source = Element.Source as UrlWebViewSource;
Control.LoadUrl(source.Url);
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{           
    base.OnElementChanged(e);

    if (e.OldElement != null)
    {
        Control.RemoveJavascriptInterface("jsBridge");
        ((HybridWebView)Element).Cleanup();
    }
    if (e.NewElement != null)
    {
        Control.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
        Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
        UrlWebViewSource source = Element.Source as UrlWebViewSource;
        Control.LoadUrl(source.Url);
    }
} 

How to call wpf function via JS when visiting https link

copy iconCopydownload iconDownload
// Add
webView.AddWebAllowedObject("wtjs", new MyBridge(this));
webView.AddWebAllowedObject("myBridge", new MyOtherBridge());

// Remove
webView.RemoveWebAllowedObject("wtjs");
// Call C# object method (no return value)
wtjs.hello('hello', 'world', 666);
myBridge.saySomething('天猫精灵,叫爸爸!');

// Call C# object method (return value)
wtjs.add(10, 20).then(function (result) { console.log(result); });

// Get C# object property
wtjs.backgroundColor.then(function (color) { console.log(color); });

// Set C# object property
wtjs.niubility = true;
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using Microsoft.Toolkit.Wpf.UI.Controls;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Text;

namespace WpfApp3
{
    // Source: https://github.com/dotnet/orleans/issues/1269#issuecomment-171233788
    public static class JsonHelper
    {
        private static readonly Type[] _specialNumericTypes = { typeof(ulong), typeof(uint), typeof(ushort), typeof(sbyte) };

        public static object ConvertWeaklyTypedValue(object value, Type targetType)
        {
            if (targetType == null)
                throw new ArgumentNullException(nameof(targetType));

            if (value == null)
                return null;

            if (targetType.IsInstanceOfType(value))
                return value;

            var paramType = Nullable.GetUnderlyingType(targetType) ?? targetType;

            if (paramType.IsEnum)
            {
                if (value is string)
                    return Enum.Parse(paramType, (string)value);
                else
                    return Enum.ToObject(paramType, value);
            }

            if (paramType == typeof(Guid))
            {
                return Guid.Parse((string)value);
            }

            if (_specialNumericTypes.Contains(paramType))
            {
                if (value is BigInteger)
                    return (ulong)(BigInteger)value;
                else
                    return Convert.ChangeType(value, paramType);
            }

            if (value is long || value is double)
            {
                return Convert.ChangeType(value, paramType);
            }

            return value;
        }
    }

    public enum WebViewInteropType
    {
        Notify = 0,
        InvokeMethod = 1,
        InvokeMethodWithReturn = 2,
        GetProperty = 3,
        SetProperty = 4
    }

    public class WebAllowedObject
    {
        public WebAllowedObject(WebView webview, string name)
        {
            WebView = webview;
            Name = name;
        }

        public WebView WebView { get; private set; }

        public string Name { get; private set; }

        public ConcurrentDictionary<(string, WebViewInteropType), object> FeaturesMap { get; } = new ConcurrentDictionary<(string, WebViewInteropType), object>();

        public EventHandler<WebViewControlNavigationCompletedEventArgs> NavigationCompletedHandler { get; set; }

        public EventHandler<WebViewControlScriptNotifyEventArgs> ScriptNotifyHandler { get; set; }
    }

    public static class WebViewExtensions
    {
        public static bool IsNotification(this WebViewControlScriptNotifyEventArgs e)
        {
            try
            {
                var message = JsonConvert.DeserializeObject<dynamic>(e.Value);

                if (message["___magic___"] != null)
                {
                    return false;
                }
            }
            catch (Exception) { }

            return true;
        }

        public static void AddWebAllowedObject(this WebView webview, string name, object targetObject)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));

            if (targetObject == null)
                throw new ArgumentNullException(nameof(targetObject));

            if (webview.Tag == null)
            {
                webview.Tag = new ConcurrentDictionary<string, WebAllowedObject>();
            }
            else if (!(webview.Tag is ConcurrentDictionary<string, WebAllowedObject>))
            {
                throw new InvalidOperationException("WebView.Tag property is already being used for other purpose.");
            }

            var webAllowedObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;

            var webAllowedObject = new WebAllowedObject(webview, name);

            if (webAllowedObjectsMap.TryAdd(name, webAllowedObject))
            {
                var objectType = targetObject.GetType();
                var methods = objectType.GetMethods();
                var properties = objectType.GetProperties();

                var jsStringBuilder = new StringBuilder();

                jsStringBuilder.Append("(function () {");
                jsStringBuilder.Append("window['");
                jsStringBuilder.Append(name);
                jsStringBuilder.Append("'] = {");

                jsStringBuilder.Append("__callback: {},");
                jsStringBuilder.Append("__newUuid: function () { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function (c) { return (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16); }); },");

                foreach (var method in methods)
                {
                    if (!method.IsSpecialName)
                    {
                        if (method.ReturnType == typeof(void))
                        {
                            webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethod), method);
                        }
                        else
                        {
                            webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethodWithReturn), method);
                        }

                        var parameters = method.GetParameters();
                        var parametersInString = string.Join(",", parameters.Select(x => x.Position).Select(x => "$$" + x.ToString()));

                        jsStringBuilder.Append(method.Name);
                        jsStringBuilder.Append(": function (");
                        jsStringBuilder.Append(parametersInString);
                        jsStringBuilder.Append(") {");

                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
                        }

                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(method.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("parameters: [");
                        jsStringBuilder.Append(parametersInString);
                        jsStringBuilder.Append("]");

                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append(",");
                            jsStringBuilder.Append("callbackId: callbackId");
                        }

                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((method.ReturnType == typeof(void)) ? (int)WebViewInteropType.InvokeMethod : (int)WebViewInteropType.InvokeMethodWithReturn);
                        jsStringBuilder.Append(");");


                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
                            jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
                            jsStringBuilder.Append("});");

                            jsStringBuilder.Append("return promise;");
                        }

                        jsStringBuilder.Append("},");
                    }
                }

                jsStringBuilder.Append("};");

                foreach (var property in properties)
                {
                    jsStringBuilder.Append("Object.defineProperty(");
                    jsStringBuilder.Append("window['");
                    jsStringBuilder.Append(name);
                    jsStringBuilder.Append("'], '");
                    jsStringBuilder.Append(property.Name);
                    jsStringBuilder.Append("', {");

                    if (property.CanRead)
                    {
                        webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.GetProperty), property);

                        jsStringBuilder.Append("get: function () {");
                        jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(property.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("callbackId: callbackId,");
                        jsStringBuilder.Append("parameters: []");
                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((int)WebViewInteropType.GetProperty);
                        jsStringBuilder.Append(");");

                        jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
                        jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
                        jsStringBuilder.Append("});");

                        jsStringBuilder.Append("return promise;");

                        jsStringBuilder.Append("},");
                    }

                    if (property.CanWrite)
                    {
                        webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.SetProperty), property);

                        jsStringBuilder.Append("set: function ($$v) {");
                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(property.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("parameters: [$$v]");
                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((int)WebViewInteropType.SetProperty);
                        jsStringBuilder.Append(");");
                        jsStringBuilder.Append("},");
                    }

                    jsStringBuilder.Append("});");
                }

                jsStringBuilder.Append("})();");

                var jsString = jsStringBuilder.ToString();

                webAllowedObject.NavigationCompletedHandler = (sender, e) =>
                {
                    var isExternalObjectCustomized = webview.InvokeScript("eval", new string[] { "window.external.hasOwnProperty('isCustomized').toString();" }).Equals("true");

                    if (!isExternalObjectCustomized)
                    {
                        webview.InvokeScript("eval", new string[] { @"
                            (function () {
                                var originalExternal = window.external;
                                var customExternal = {
                                    notify: function (message, type = 0) {
                                        if (type === 0) {
                                            originalExternal.notify(message);
                                        } else {
                                            originalExternal.notify(JSON.stringify({
                                                ___magic___: true,
                                                type: type,
                                                interop: message
                                            }));
                                        }
                                    },
                                    isCustomized: true
                                };
                                window.external = customExternal;
                            })();" });
                    }

                    webview.InvokeScript("eval", new string[] { jsString });
                };

                webAllowedObject.ScriptNotifyHandler = (sender, e) =>
                {
                    try
                    {
                        var message = JsonConvert.DeserializeObject<dynamic>(e.Value);

                        if (message["___magic___"] != null)
                        {
                            var interopType = (WebViewInteropType)message.type;
                            var interop = JsonConvert.DeserializeObject<dynamic>(message.interop.ToString());
                            var source = (string)interop.source.ToString();
                            var target = (string)interop.target.ToString();
                            var parameters = (object[])interop.parameters.ToObject<object[]>();

                            if (interopType == WebViewInteropType.InvokeMethod)
                            {
                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
                                    {
                                        var method = (MethodInfo)methodObject;

                                        var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();

                                        var convertedParameters = new object[parameters.Length];

                                        for (var i = 0; i < parameters.Length; i++)
                                        {
                                            convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
                                        }

                                        method.Invoke(targetObject, convertedParameters);
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.InvokeMethodWithReturn)
                            {
                                var callbackId = interop.callbackId.ToString();

                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
                                    {
                                        var method = (MethodInfo)methodObject;

                                        var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();

                                        var convertedParameters = new object[parameters.Length];

                                        for (var i = 0; i < parameters.Length; i++)
                                        {
                                            convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
                                        }

                                        var invokeResult = method.Invoke(targetObject, convertedParameters);

                                        webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(invokeResult)) });
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.GetProperty)
                            {
                                var callbackId = interop.callbackId.ToString();

                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
                                    {
                                        var property = (PropertyInfo)propertyObject;

                                        var getResult = property.GetValue(targetObject);

                                        webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(getResult)) });
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.SetProperty)
                            {
                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
                                    {
                                        var property = (PropertyInfo)propertyObject;

                                        property.SetValue(targetObject, JsonHelper.ConvertWeaklyTypedValue(parameters[0], property.PropertyType));
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        // Do nothing
                    }
                };

                webview.NavigationCompleted += webAllowedObject.NavigationCompletedHandler;
                webview.ScriptNotify += webAllowedObject.ScriptNotifyHandler;
            }
            else
            {
                throw new InvalidOperationException("Object with the identical name is already exist.");
            }
        }

        public static void RemoveWebAllowedObject(this WebView webview, string name)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));

            var allowedWebObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;

            if (allowedWebObjectsMap != null)
            {
                if (allowedWebObjectsMap.TryRemove(name, out WebAllowedObject webAllowedObject))
                {
                    webview.NavigationCompleted -= webAllowedObject.NavigationCompletedHandler;
                    webview.ScriptNotify -= webAllowedObject.ScriptNotifyHandler;

                    webview.InvokeScript("eval", new string[] { "delete window['" + name + "'];" });
                }
            }
        }
    }
}
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using System;
using System.Diagnostics;
using System.Windows;

namespace WpfApp3
{
    public partial class MainWindow : Window
    {
        public class MyBridge
        {
            private readonly MainWindow _window;

            public MyBridge(MainWindow window)
            {
                _window = window;
            }

            public void setTitle(string title)
            {
                Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));

                _window.setTitle(title);
            }

            public void playTTS(string tts)
            {
                Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
            }
        }

        public MainWindow()
        {
            this.InitializeComponent();

            this.wv.IsScriptNotifyAllowed = true;
            this.wv.ScriptNotify += Wv_ScriptNotify;
            this.wv.AddWebAllowedObject("wtjs", new MyBridge(this));

            this.Loaded += MainPage_Loaded;
        }

        private void Wv_ScriptNotify(object sender, WebViewControlScriptNotifyEventArgs e)
        {
            if (e.IsNotification())
            {
                Debug.WriteLine(e.Value);
            }
        }

        private void setTitle(string str)
        {
            textBlock.Text = str;
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.wv.Source = new Uri("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=");
        }
    }
}
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf" 
        x:Class="WpfApp3.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid x:Name="grid">
        <cefSharp:ChromiumWebBrowser x:Name="wv" HorizontalAlignment="Left" Height="405" Margin="50,0,0,0" VerticalAlignment="Top" Width="725" RenderTransformOrigin="-0.45,-0.75" />
        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="30,30,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Height="60" Width="335"/>
    </Grid>
</Window>
using CefSharp;
using System.Diagnostics;
using System.Windows;

namespace WpfApp3
{
    public partial class MainWindow : Window
    {
        public class MyBridge
        {
            private readonly MainWindow _window;

            public MyBridge(MainWindow window)
            {
                _window = window;
            }

            public void setTitle(string title)
            {
                Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));

                _window.setTitle(title);
            }

            public void playTTS(string tts)
            {
                Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
            }
        }

        public MainWindow()
        {
            this.InitializeComponent();

            this.wv.JavascriptObjectRepository.Register("wtjs", new MyBridge(this), true, new BindingOptions() { CamelCaseJavascriptNames = false });
            this.wv.FrameLoadStart += Wv_FrameLoadStart;

            this.Loaded += MainPage_Loaded;
        }

        private void Wv_FrameLoadStart(object sender, FrameLoadStartEventArgs e)
        {
            if (e.Url.StartsWith("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather"))
            {
                e.Browser.MainFrame.ExecuteJavaScriptAsync("CefSharp.BindObjectAsync('wtjs');");
            }
        }

        private void setTitle(string str)
        {
            this.Dispatcher.Invoke(() =>
            {
                textBlock.Text = str;
            });
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.wv.Address = "https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=";
        }
    }
}
-----------------------
// Add
webView.AddWebAllowedObject("wtjs", new MyBridge(this));
webView.AddWebAllowedObject("myBridge", new MyOtherBridge());

// Remove
webView.RemoveWebAllowedObject("wtjs");
// Call C# object method (no return value)
wtjs.hello('hello', 'world', 666);
myBridge.saySomething('天猫精灵,叫爸爸!');

// Call C# object method (return value)
wtjs.add(10, 20).then(function (result) { console.log(result); });

// Get C# object property
wtjs.backgroundColor.then(function (color) { console.log(color); });

// Set C# object property
wtjs.niubility = true;
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using Microsoft.Toolkit.Wpf.UI.Controls;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Text;

namespace WpfApp3
{
    // Source: https://github.com/dotnet/orleans/issues/1269#issuecomment-171233788
    public static class JsonHelper
    {
        private static readonly Type[] _specialNumericTypes = { typeof(ulong), typeof(uint), typeof(ushort), typeof(sbyte) };

        public static object ConvertWeaklyTypedValue(object value, Type targetType)
        {
            if (targetType == null)
                throw new ArgumentNullException(nameof(targetType));

            if (value == null)
                return null;

            if (targetType.IsInstanceOfType(value))
                return value;

            var paramType = Nullable.GetUnderlyingType(targetType) ?? targetType;

            if (paramType.IsEnum)
            {
                if (value is string)
                    return Enum.Parse(paramType, (string)value);
                else
                    return Enum.ToObject(paramType, value);
            }

            if (paramType == typeof(Guid))
            {
                return Guid.Parse((string)value);
            }

            if (_specialNumericTypes.Contains(paramType))
            {
                if (value is BigInteger)
                    return (ulong)(BigInteger)value;
                else
                    return Convert.ChangeType(value, paramType);
            }

            if (value is long || value is double)
            {
                return Convert.ChangeType(value, paramType);
            }

            return value;
        }
    }

    public enum WebViewInteropType
    {
        Notify = 0,
        InvokeMethod = 1,
        InvokeMethodWithReturn = 2,
        GetProperty = 3,
        SetProperty = 4
    }

    public class WebAllowedObject
    {
        public WebAllowedObject(WebView webview, string name)
        {
            WebView = webview;
            Name = name;
        }

        public WebView WebView { get; private set; }

        public string Name { get; private set; }

        public ConcurrentDictionary<(string, WebViewInteropType), object> FeaturesMap { get; } = new ConcurrentDictionary<(string, WebViewInteropType), object>();

        public EventHandler<WebViewControlNavigationCompletedEventArgs> NavigationCompletedHandler { get; set; }

        public EventHandler<WebViewControlScriptNotifyEventArgs> ScriptNotifyHandler { get; set; }
    }

    public static class WebViewExtensions
    {
        public static bool IsNotification(this WebViewControlScriptNotifyEventArgs e)
        {
            try
            {
                var message = JsonConvert.DeserializeObject<dynamic>(e.Value);

                if (message["___magic___"] != null)
                {
                    return false;
                }
            }
            catch (Exception) { }

            return true;
        }

        public static void AddWebAllowedObject(this WebView webview, string name, object targetObject)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));

            if (targetObject == null)
                throw new ArgumentNullException(nameof(targetObject));

            if (webview.Tag == null)
            {
                webview.Tag = new ConcurrentDictionary<string, WebAllowedObject>();
            }
            else if (!(webview.Tag is ConcurrentDictionary<string, WebAllowedObject>))
            {
                throw new InvalidOperationException("WebView.Tag property is already being used for other purpose.");
            }

            var webAllowedObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;

            var webAllowedObject = new WebAllowedObject(webview, name);

            if (webAllowedObjectsMap.TryAdd(name, webAllowedObject))
            {
                var objectType = targetObject.GetType();
                var methods = objectType.GetMethods();
                var properties = objectType.GetProperties();

                var jsStringBuilder = new StringBuilder();

                jsStringBuilder.Append("(function () {");
                jsStringBuilder.Append("window['");
                jsStringBuilder.Append(name);
                jsStringBuilder.Append("'] = {");

                jsStringBuilder.Append("__callback: {},");
                jsStringBuilder.Append("__newUuid: function () { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function (c) { return (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16); }); },");

                foreach (var method in methods)
                {
                    if (!method.IsSpecialName)
                    {
                        if (method.ReturnType == typeof(void))
                        {
                            webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethod), method);
                        }
                        else
                        {
                            webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethodWithReturn), method);
                        }

                        var parameters = method.GetParameters();
                        var parametersInString = string.Join(",", parameters.Select(x => x.Position).Select(x => "$$" + x.ToString()));

                        jsStringBuilder.Append(method.Name);
                        jsStringBuilder.Append(": function (");
                        jsStringBuilder.Append(parametersInString);
                        jsStringBuilder.Append(") {");

                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
                        }

                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(method.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("parameters: [");
                        jsStringBuilder.Append(parametersInString);
                        jsStringBuilder.Append("]");

                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append(",");
                            jsStringBuilder.Append("callbackId: callbackId");
                        }

                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((method.ReturnType == typeof(void)) ? (int)WebViewInteropType.InvokeMethod : (int)WebViewInteropType.InvokeMethodWithReturn);
                        jsStringBuilder.Append(");");


                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
                            jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
                            jsStringBuilder.Append("});");

                            jsStringBuilder.Append("return promise;");
                        }

                        jsStringBuilder.Append("},");
                    }
                }

                jsStringBuilder.Append("};");

                foreach (var property in properties)
                {
                    jsStringBuilder.Append("Object.defineProperty(");
                    jsStringBuilder.Append("window['");
                    jsStringBuilder.Append(name);
                    jsStringBuilder.Append("'], '");
                    jsStringBuilder.Append(property.Name);
                    jsStringBuilder.Append("', {");

                    if (property.CanRead)
                    {
                        webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.GetProperty), property);

                        jsStringBuilder.Append("get: function () {");
                        jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(property.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("callbackId: callbackId,");
                        jsStringBuilder.Append("parameters: []");
                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((int)WebViewInteropType.GetProperty);
                        jsStringBuilder.Append(");");

                        jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
                        jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
                        jsStringBuilder.Append("});");

                        jsStringBuilder.Append("return promise;");

                        jsStringBuilder.Append("},");
                    }

                    if (property.CanWrite)
                    {
                        webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.SetProperty), property);

                        jsStringBuilder.Append("set: function ($$v) {");
                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(property.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("parameters: [$$v]");
                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((int)WebViewInteropType.SetProperty);
                        jsStringBuilder.Append(");");
                        jsStringBuilder.Append("},");
                    }

                    jsStringBuilder.Append("});");
                }

                jsStringBuilder.Append("})();");

                var jsString = jsStringBuilder.ToString();

                webAllowedObject.NavigationCompletedHandler = (sender, e) =>
                {
                    var isExternalObjectCustomized = webview.InvokeScript("eval", new string[] { "window.external.hasOwnProperty('isCustomized').toString();" }).Equals("true");

                    if (!isExternalObjectCustomized)
                    {
                        webview.InvokeScript("eval", new string[] { @"
                            (function () {
                                var originalExternal = window.external;
                                var customExternal = {
                                    notify: function (message, type = 0) {
                                        if (type === 0) {
                                            originalExternal.notify(message);
                                        } else {
                                            originalExternal.notify(JSON.stringify({
                                                ___magic___: true,
                                                type: type,
                                                interop: message
                                            }));
                                        }
                                    },
                                    isCustomized: true
                                };
                                window.external = customExternal;
                            })();" });
                    }

                    webview.InvokeScript("eval", new string[] { jsString });
                };

                webAllowedObject.ScriptNotifyHandler = (sender, e) =>
                {
                    try
                    {
                        var message = JsonConvert.DeserializeObject<dynamic>(e.Value);

                        if (message["___magic___"] != null)
                        {
                            var interopType = (WebViewInteropType)message.type;
                            var interop = JsonConvert.DeserializeObject<dynamic>(message.interop.ToString());
                            var source = (string)interop.source.ToString();
                            var target = (string)interop.target.ToString();
                            var parameters = (object[])interop.parameters.ToObject<object[]>();

                            if (interopType == WebViewInteropType.InvokeMethod)
                            {
                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
                                    {
                                        var method = (MethodInfo)methodObject;

                                        var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();

                                        var convertedParameters = new object[parameters.Length];

                                        for (var i = 0; i < parameters.Length; i++)
                                        {
                                            convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
                                        }

                                        method.Invoke(targetObject, convertedParameters);
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.InvokeMethodWithReturn)
                            {
                                var callbackId = interop.callbackId.ToString();

                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
                                    {
                                        var method = (MethodInfo)methodObject;

                                        var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();

                                        var convertedParameters = new object[parameters.Length];

                                        for (var i = 0; i < parameters.Length; i++)
                                        {
                                            convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
                                        }

                                        var invokeResult = method.Invoke(targetObject, convertedParameters);

                                        webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(invokeResult)) });
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.GetProperty)
                            {
                                var callbackId = interop.callbackId.ToString();

                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
                                    {
                                        var property = (PropertyInfo)propertyObject;

                                        var getResult = property.GetValue(targetObject);

                                        webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(getResult)) });
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.SetProperty)
                            {
                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
                                    {
                                        var property = (PropertyInfo)propertyObject;

                                        property.SetValue(targetObject, JsonHelper.ConvertWeaklyTypedValue(parameters[0], property.PropertyType));
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        // Do nothing
                    }
                };

                webview.NavigationCompleted += webAllowedObject.NavigationCompletedHandler;
                webview.ScriptNotify += webAllowedObject.ScriptNotifyHandler;
            }
            else
            {
                throw new InvalidOperationException("Object with the identical name is already exist.");
            }
        }

        public static void RemoveWebAllowedObject(this WebView webview, string name)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));

            var allowedWebObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;

            if (allowedWebObjectsMap != null)
            {
                if (allowedWebObjectsMap.TryRemove(name, out WebAllowedObject webAllowedObject))
                {
                    webview.NavigationCompleted -= webAllowedObject.NavigationCompletedHandler;
                    webview.ScriptNotify -= webAllowedObject.ScriptNotifyHandler;

                    webview.InvokeScript("eval", new string[] { "delete window['" + name + "'];" });
                }
            }
        }
    }
}
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using System;
using System.Diagnostics;
using System.Windows;

namespace WpfApp3
{
    public partial class MainWindow : Window
    {
        public class MyBridge
        {
            private readonly MainWindow _window;

            public MyBridge(MainWindow window)
            {
                _window = window;
            }

            public void setTitle(string title)
            {
                Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));

                _window.setTitle(title);
            }

            public void playTTS(string tts)
            {
                Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
            }
        }

        public MainWindow()
        {
            this.InitializeComponent();

            this.wv.IsScriptNotifyAllowed = true;
            this.wv.ScriptNotify += Wv_ScriptNotify;
            this.wv.AddWebAllowedObject("wtjs", new MyBridge(this));

            this.Loaded += MainPage_Loaded;
        }

        private void Wv_ScriptNotify(object sender, WebViewControlScriptNotifyEventArgs e)
        {
            if (e.IsNotification())
            {
                Debug.WriteLine(e.Value);
            }
        }

        private void setTitle(string str)
        {
            textBlock.Text = str;
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.wv.Source = new Uri("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=");
        }
    }
}
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf" 
        x:Class="WpfApp3.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid x:Name="grid">
        <cefSharp:ChromiumWebBrowser x:Name="wv" HorizontalAlignment="Left" Height="405" Margin="50,0,0,0" VerticalAlignment="Top" Width="725" RenderTransformOrigin="-0.45,-0.75" />
        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="30,30,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Height="60" Width="335"/>
    </Grid>
</Window>
using CefSharp;
using System.Diagnostics;
using System.Windows;

namespace WpfApp3
{
    public partial class MainWindow : Window
    {
        public class MyBridge
        {
            private readonly MainWindow _window;

            public MyBridge(MainWindow window)
            {
                _window = window;
            }

            public void setTitle(string title)
            {
                Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));

                _window.setTitle(title);
            }

            public void playTTS(string tts)
            {
                Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
            }
        }

        public MainWindow()
        {
            this.InitializeComponent();

            this.wv.JavascriptObjectRepository.Register("wtjs", new MyBridge(this), true, new BindingOptions() { CamelCaseJavascriptNames = false });
            this.wv.FrameLoadStart += Wv_FrameLoadStart;

            this.Loaded += MainPage_Loaded;
        }

        private void Wv_FrameLoadStart(object sender, FrameLoadStartEventArgs e)
        {
            if (e.Url.StartsWith("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather"))
            {
                e.Browser.MainFrame.ExecuteJavaScriptAsync("CefSharp.BindObjectAsync('wtjs');");
            }
        }

        private void setTitle(string str)
        {
            this.Dispatcher.Invoke(() =>
            {
                textBlock.Text = str;
            });
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.wv.Address = "https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=";
        }
    }
}
-----------------------
// Add
webView.AddWebAllowedObject("wtjs", new MyBridge(this));
webView.AddWebAllowedObject("myBridge", new MyOtherBridge());

// Remove
webView.RemoveWebAllowedObject("wtjs");
// Call C# object method (no return value)
wtjs.hello('hello', 'world', 666);
myBridge.saySomething('天猫精灵,叫爸爸!');

// Call C# object method (return value)
wtjs.add(10, 20).then(function (result) { console.log(result); });

// Get C# object property
wtjs.backgroundColor.then(function (color) { console.log(color); });

// Set C# object property
wtjs.niubility = true;
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using Microsoft.Toolkit.Wpf.UI.Controls;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Text;

namespace WpfApp3
{
    // Source: https://github.com/dotnet/orleans/issues/1269#issuecomment-171233788
    public static class JsonHelper
    {
        private static readonly Type[] _specialNumericTypes = { typeof(ulong), typeof(uint), typeof(ushort), typeof(sbyte) };

        public static object ConvertWeaklyTypedValue(object value, Type targetType)
        {
            if (targetType == null)
                throw new ArgumentNullException(nameof(targetType));

            if (value == null)
                return null;

            if (targetType.IsInstanceOfType(value))
                return value;

            var paramType = Nullable.GetUnderlyingType(targetType) ?? targetType;

            if (paramType.IsEnum)
            {
                if (value is string)
                    return Enum.Parse(paramType, (string)value);
                else
                    return Enum.ToObject(paramType, value);
            }

            if (paramType == typeof(Guid))
            {
                return Guid.Parse((string)value);
            }

            if (_specialNumericTypes.Contains(paramType))
            {
                if (value is BigInteger)
                    return (ulong)(BigInteger)value;
                else
                    return Convert.ChangeType(value, paramType);
            }

            if (value is long || value is double)
            {
                return Convert.ChangeType(value, paramType);
            }

            return value;
        }
    }

    public enum WebViewInteropType
    {
        Notify = 0,
        InvokeMethod = 1,
        InvokeMethodWithReturn = 2,
        GetProperty = 3,
        SetProperty = 4
    }

    public class WebAllowedObject
    {
        public WebAllowedObject(WebView webview, string name)
        {
            WebView = webview;
            Name = name;
        }

        public WebView WebView { get; private set; }

        public string Name { get; private set; }

        public ConcurrentDictionary<(string, WebViewInteropType), object> FeaturesMap { get; } = new ConcurrentDictionary<(string, WebViewInteropType), object>();

        public EventHandler<WebViewControlNavigationCompletedEventArgs> NavigationCompletedHandler { get; set; }

        public EventHandler<WebViewControlScriptNotifyEventArgs> ScriptNotifyHandler { get; set; }
    }

    public static class WebViewExtensions
    {
        public static bool IsNotification(this WebViewControlScriptNotifyEventArgs e)
        {
            try
            {
                var message = JsonConvert.DeserializeObject<dynamic>(e.Value);

                if (message["___magic___"] != null)
                {
                    return false;
                }
            }
            catch (Exception) { }

            return true;
        }

        public static void AddWebAllowedObject(this WebView webview, string name, object targetObject)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));

            if (targetObject == null)
                throw new ArgumentNullException(nameof(targetObject));

            if (webview.Tag == null)
            {
                webview.Tag = new ConcurrentDictionary<string, WebAllowedObject>();
            }
            else if (!(webview.Tag is ConcurrentDictionary<string, WebAllowedObject>))
            {
                throw new InvalidOperationException("WebView.Tag property is already being used for other purpose.");
            }

            var webAllowedObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;

            var webAllowedObject = new WebAllowedObject(webview, name);

            if (webAllowedObjectsMap.TryAdd(name, webAllowedObject))
            {
                var objectType = targetObject.GetType();
                var methods = objectType.GetMethods();
                var properties = objectType.GetProperties();

                var jsStringBuilder = new StringBuilder();

                jsStringBuilder.Append("(function () {");
                jsStringBuilder.Append("window['");
                jsStringBuilder.Append(name);
                jsStringBuilder.Append("'] = {");

                jsStringBuilder.Append("__callback: {},");
                jsStringBuilder.Append("__newUuid: function () { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function (c) { return (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16); }); },");

                foreach (var method in methods)
                {
                    if (!method.IsSpecialName)
                    {
                        if (method.ReturnType == typeof(void))
                        {
                            webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethod), method);
                        }
                        else
                        {
                            webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethodWithReturn), method);
                        }

                        var parameters = method.GetParameters();
                        var parametersInString = string.Join(",", parameters.Select(x => x.Position).Select(x => "$$" + x.ToString()));

                        jsStringBuilder.Append(method.Name);
                        jsStringBuilder.Append(": function (");
                        jsStringBuilder.Append(parametersInString);
                        jsStringBuilder.Append(") {");

                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
                        }

                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(method.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("parameters: [");
                        jsStringBuilder.Append(parametersInString);
                        jsStringBuilder.Append("]");

                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append(",");
                            jsStringBuilder.Append("callbackId: callbackId");
                        }

                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((method.ReturnType == typeof(void)) ? (int)WebViewInteropType.InvokeMethod : (int)WebViewInteropType.InvokeMethodWithReturn);
                        jsStringBuilder.Append(");");


                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
                            jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
                            jsStringBuilder.Append("});");

                            jsStringBuilder.Append("return promise;");
                        }

                        jsStringBuilder.Append("},");
                    }
                }

                jsStringBuilder.Append("};");

                foreach (var property in properties)
                {
                    jsStringBuilder.Append("Object.defineProperty(");
                    jsStringBuilder.Append("window['");
                    jsStringBuilder.Append(name);
                    jsStringBuilder.Append("'], '");
                    jsStringBuilder.Append(property.Name);
                    jsStringBuilder.Append("', {");

                    if (property.CanRead)
                    {
                        webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.GetProperty), property);

                        jsStringBuilder.Append("get: function () {");
                        jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(property.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("callbackId: callbackId,");
                        jsStringBuilder.Append("parameters: []");
                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((int)WebViewInteropType.GetProperty);
                        jsStringBuilder.Append(");");

                        jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
                        jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
                        jsStringBuilder.Append("});");

                        jsStringBuilder.Append("return promise;");

                        jsStringBuilder.Append("},");
                    }

                    if (property.CanWrite)
                    {
                        webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.SetProperty), property);

                        jsStringBuilder.Append("set: function ($$v) {");
                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(property.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("parameters: [$$v]");
                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((int)WebViewInteropType.SetProperty);
                        jsStringBuilder.Append(");");
                        jsStringBuilder.Append("},");
                    }

                    jsStringBuilder.Append("});");
                }

                jsStringBuilder.Append("})();");

                var jsString = jsStringBuilder.ToString();

                webAllowedObject.NavigationCompletedHandler = (sender, e) =>
                {
                    var isExternalObjectCustomized = webview.InvokeScript("eval", new string[] { "window.external.hasOwnProperty('isCustomized').toString();" }).Equals("true");

                    if (!isExternalObjectCustomized)
                    {
                        webview.InvokeScript("eval", new string[] { @"
                            (function () {
                                var originalExternal = window.external;
                                var customExternal = {
                                    notify: function (message, type = 0) {
                                        if (type === 0) {
                                            originalExternal.notify(message);
                                        } else {
                                            originalExternal.notify(JSON.stringify({
                                                ___magic___: true,
                                                type: type,
                                                interop: message
                                            }));
                                        }
                                    },
                                    isCustomized: true
                                };
                                window.external = customExternal;
                            })();" });
                    }

                    webview.InvokeScript("eval", new string[] { jsString });
                };

                webAllowedObject.ScriptNotifyHandler = (sender, e) =>
                {
                    try
                    {
                        var message = JsonConvert.DeserializeObject<dynamic>(e.Value);

                        if (message["___magic___"] != null)
                        {
                            var interopType = (WebViewInteropType)message.type;
                            var interop = JsonConvert.DeserializeObject<dynamic>(message.interop.ToString());
                            var source = (string)interop.source.ToString();
                            var target = (string)interop.target.ToString();
                            var parameters = (object[])interop.parameters.ToObject<object[]>();

                            if (interopType == WebViewInteropType.InvokeMethod)
                            {
                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
                                    {
                                        var method = (MethodInfo)methodObject;

                                        var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();

                                        var convertedParameters = new object[parameters.Length];

                                        for (var i = 0; i < parameters.Length; i++)
                                        {
                                            convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
                                        }

                                        method.Invoke(targetObject, convertedParameters);
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.InvokeMethodWithReturn)
                            {
                                var callbackId = interop.callbackId.ToString();

                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
                                    {
                                        var method = (MethodInfo)methodObject;

                                        var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();

                                        var convertedParameters = new object[parameters.Length];

                                        for (var i = 0; i < parameters.Length; i++)
                                        {
                                            convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
                                        }

                                        var invokeResult = method.Invoke(targetObject, convertedParameters);

                                        webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(invokeResult)) });
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.GetProperty)
                            {
                                var callbackId = interop.callbackId.ToString();

                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
                                    {
                                        var property = (PropertyInfo)propertyObject;

                                        var getResult = property.GetValue(targetObject);

                                        webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(getResult)) });
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.SetProperty)
                            {
                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
                                    {
                                        var property = (PropertyInfo)propertyObject;

                                        property.SetValue(targetObject, JsonHelper.ConvertWeaklyTypedValue(parameters[0], property.PropertyType));
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        // Do nothing
                    }
                };

                webview.NavigationCompleted += webAllowedObject.NavigationCompletedHandler;
                webview.ScriptNotify += webAllowedObject.ScriptNotifyHandler;
            }
            else
            {
                throw new InvalidOperationException("Object with the identical name is already exist.");
            }
        }

        public static void RemoveWebAllowedObject(this WebView webview, string name)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));

            var allowedWebObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;

            if (allowedWebObjectsMap != null)
            {
                if (allowedWebObjectsMap.TryRemove(name, out WebAllowedObject webAllowedObject))
                {
                    webview.NavigationCompleted -= webAllowedObject.NavigationCompletedHandler;
                    webview.ScriptNotify -= webAllowedObject.ScriptNotifyHandler;

                    webview.InvokeScript("eval", new string[] { "delete window['" + name + "'];" });
                }
            }
        }
    }
}
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using System;
using System.Diagnostics;
using System.Windows;

namespace WpfApp3
{
    public partial class MainWindow : Window
    {
        public class MyBridge
        {
            private readonly MainWindow _window;

            public MyBridge(MainWindow window)
            {
                _window = window;
            }

            public void setTitle(string title)
            {
                Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));

                _window.setTitle(title);
            }

            public void playTTS(string tts)
            {
                Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
            }
        }

        public MainWindow()
        {
            this.InitializeComponent();

            this.wv.IsScriptNotifyAllowed = true;
            this.wv.ScriptNotify += Wv_ScriptNotify;
            this.wv.AddWebAllowedObject("wtjs", new MyBridge(this));

            this.Loaded += MainPage_Loaded;
        }

        private void Wv_ScriptNotify(object sender, WebViewControlScriptNotifyEventArgs e)
        {
            if (e.IsNotification())
            {
                Debug.WriteLine(e.Value);
            }
        }

        private void setTitle(string str)
        {
            textBlock.Text = str;
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.wv.Source = new Uri("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=");
        }
    }
}
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf" 
        x:Class="WpfApp3.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid x:Name="grid">
        <cefSharp:ChromiumWebBrowser x:Name="wv" HorizontalAlignment="Left" Height="405" Margin="50,0,0,0" VerticalAlignment="Top" Width="725" RenderTransformOrigin="-0.45,-0.75" />
        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="30,30,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Height="60" Width="335"/>
    </Grid>
</Window>
using CefSharp;
using System.Diagnostics;
using System.Windows;

namespace WpfApp3
{
    public partial class MainWindow : Window
    {
        public class MyBridge
        {
            private readonly MainWindow _window;

            public MyBridge(MainWindow window)
            {
                _window = window;
            }

            public void setTitle(string title)
            {
                Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));

                _window.setTitle(title);
            }

            public void playTTS(string tts)
            {
                Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
            }
        }

        public MainWindow()
        {
            this.InitializeComponent();

            this.wv.JavascriptObjectRepository.Register("wtjs", new MyBridge(this), true, new BindingOptions() { CamelCaseJavascriptNames = false });
            this.wv.FrameLoadStart += Wv_FrameLoadStart;

            this.Loaded += MainPage_Loaded;
        }

        private void Wv_FrameLoadStart(object sender, FrameLoadStartEventArgs e)
        {
            if (e.Url.StartsWith("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather"))
            {
                e.Browser.MainFrame.ExecuteJavaScriptAsync("CefSharp.BindObjectAsync('wtjs');");
            }
        }

        private void setTitle(string str)
        {
            this.Dispatcher.Invoke(() =>
            {
                textBlock.Text = str;
            });
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.wv.Address = "https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=";
        }
    }
}
-----------------------
// Add
webView.AddWebAllowedObject("wtjs", new MyBridge(this));
webView.AddWebAllowedObject("myBridge", new MyOtherBridge());

// Remove
webView.RemoveWebAllowedObject("wtjs");
// Call C# object method (no return value)
wtjs.hello('hello', 'world', 666);
myBridge.saySomething('天猫精灵,叫爸爸!');

// Call C# object method (return value)
wtjs.add(10, 20).then(function (result) { console.log(result); });

// Get C# object property
wtjs.backgroundColor.then(function (color) { console.log(color); });

// Set C# object property
wtjs.niubility = true;
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using Microsoft.Toolkit.Wpf.UI.Controls;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Text;

namespace WpfApp3
{
    // Source: https://github.com/dotnet/orleans/issues/1269#issuecomment-171233788
    public static class JsonHelper
    {
        private static readonly Type[] _specialNumericTypes = { typeof(ulong), typeof(uint), typeof(ushort), typeof(sbyte) };

        public static object ConvertWeaklyTypedValue(object value, Type targetType)
        {
            if (targetType == null)
                throw new ArgumentNullException(nameof(targetType));

            if (value == null)
                return null;

            if (targetType.IsInstanceOfType(value))
                return value;

            var paramType = Nullable.GetUnderlyingType(targetType) ?? targetType;

            if (paramType.IsEnum)
            {
                if (value is string)
                    return Enum.Parse(paramType, (string)value);
                else
                    return Enum.ToObject(paramType, value);
            }

            if (paramType == typeof(Guid))
            {
                return Guid.Parse((string)value);
            }

            if (_specialNumericTypes.Contains(paramType))
            {
                if (value is BigInteger)
                    return (ulong)(BigInteger)value;
                else
                    return Convert.ChangeType(value, paramType);
            }

            if (value is long || value is double)
            {
                return Convert.ChangeType(value, paramType);
            }

            return value;
        }
    }

    public enum WebViewInteropType
    {
        Notify = 0,
        InvokeMethod = 1,
        InvokeMethodWithReturn = 2,
        GetProperty = 3,
        SetProperty = 4
    }

    public class WebAllowedObject
    {
        public WebAllowedObject(WebView webview, string name)
        {
            WebView = webview;
            Name = name;
        }

        public WebView WebView { get; private set; }

        public string Name { get; private set; }

        public ConcurrentDictionary<(string, WebViewInteropType), object> FeaturesMap { get; } = new ConcurrentDictionary<(string, WebViewInteropType), object>();

        public EventHandler<WebViewControlNavigationCompletedEventArgs> NavigationCompletedHandler { get; set; }

        public EventHandler<WebViewControlScriptNotifyEventArgs> ScriptNotifyHandler { get; set; }
    }

    public static class WebViewExtensions
    {
        public static bool IsNotification(this WebViewControlScriptNotifyEventArgs e)
        {
            try
            {
                var message = JsonConvert.DeserializeObject<dynamic>(e.Value);

                if (message["___magic___"] != null)
                {
                    return false;
                }
            }
            catch (Exception) { }

            return true;
        }

        public static void AddWebAllowedObject(this WebView webview, string name, object targetObject)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));

            if (targetObject == null)
                throw new ArgumentNullException(nameof(targetObject));

            if (webview.Tag == null)
            {
                webview.Tag = new ConcurrentDictionary<string, WebAllowedObject>();
            }
            else if (!(webview.Tag is ConcurrentDictionary<string, WebAllowedObject>))
            {
                throw new InvalidOperationException("WebView.Tag property is already being used for other purpose.");
            }

            var webAllowedObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;

            var webAllowedObject = new WebAllowedObject(webview, name);

            if (webAllowedObjectsMap.TryAdd(name, webAllowedObject))
            {
                var objectType = targetObject.GetType();
                var methods = objectType.GetMethods();
                var properties = objectType.GetProperties();

                var jsStringBuilder = new StringBuilder();

                jsStringBuilder.Append("(function () {");
                jsStringBuilder.Append("window['");
                jsStringBuilder.Append(name);
                jsStringBuilder.Append("'] = {");

                jsStringBuilder.Append("__callback: {},");
                jsStringBuilder.Append("__newUuid: function () { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function (c) { return (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16); }); },");

                foreach (var method in methods)
                {
                    if (!method.IsSpecialName)
                    {
                        if (method.ReturnType == typeof(void))
                        {
                            webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethod), method);
                        }
                        else
                        {
                            webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethodWithReturn), method);
                        }

                        var parameters = method.GetParameters();
                        var parametersInString = string.Join(",", parameters.Select(x => x.Position).Select(x => "$$" + x.ToString()));

                        jsStringBuilder.Append(method.Name);
                        jsStringBuilder.Append(": function (");
                        jsStringBuilder.Append(parametersInString);
                        jsStringBuilder.Append(") {");

                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
                        }

                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(method.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("parameters: [");
                        jsStringBuilder.Append(parametersInString);
                        jsStringBuilder.Append("]");

                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append(",");
                            jsStringBuilder.Append("callbackId: callbackId");
                        }

                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((method.ReturnType == typeof(void)) ? (int)WebViewInteropType.InvokeMethod : (int)WebViewInteropType.InvokeMethodWithReturn);
                        jsStringBuilder.Append(");");


                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
                            jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
                            jsStringBuilder.Append("});");

                            jsStringBuilder.Append("return promise;");
                        }

                        jsStringBuilder.Append("},");
                    }
                }

                jsStringBuilder.Append("};");

                foreach (var property in properties)
                {
                    jsStringBuilder.Append("Object.defineProperty(");
                    jsStringBuilder.Append("window['");
                    jsStringBuilder.Append(name);
                    jsStringBuilder.Append("'], '");
                    jsStringBuilder.Append(property.Name);
                    jsStringBuilder.Append("', {");

                    if (property.CanRead)
                    {
                        webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.GetProperty), property);

                        jsStringBuilder.Append("get: function () {");
                        jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(property.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("callbackId: callbackId,");
                        jsStringBuilder.Append("parameters: []");
                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((int)WebViewInteropType.GetProperty);
                        jsStringBuilder.Append(");");

                        jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
                        jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
                        jsStringBuilder.Append("});");

                        jsStringBuilder.Append("return promise;");

                        jsStringBuilder.Append("},");
                    }

                    if (property.CanWrite)
                    {
                        webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.SetProperty), property);

                        jsStringBuilder.Append("set: function ($$v) {");
                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(property.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("parameters: [$$v]");
                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((int)WebViewInteropType.SetProperty);
                        jsStringBuilder.Append(");");
                        jsStringBuilder.Append("},");
                    }

                    jsStringBuilder.Append("});");
                }

                jsStringBuilder.Append("})();");

                var jsString = jsStringBuilder.ToString();

                webAllowedObject.NavigationCompletedHandler = (sender, e) =>
                {
                    var isExternalObjectCustomized = webview.InvokeScript("eval", new string[] { "window.external.hasOwnProperty('isCustomized').toString();" }).Equals("true");

                    if (!isExternalObjectCustomized)
                    {
                        webview.InvokeScript("eval", new string[] { @"
                            (function () {
                                var originalExternal = window.external;
                                var customExternal = {
                                    notify: function (message, type = 0) {
                                        if (type === 0) {
                                            originalExternal.notify(message);
                                        } else {
                                            originalExternal.notify(JSON.stringify({
                                                ___magic___: true,
                                                type: type,
                                                interop: message
                                            }));
                                        }
                                    },
                                    isCustomized: true
                                };
                                window.external = customExternal;
                            })();" });
                    }

                    webview.InvokeScript("eval", new string[] { jsString });
                };

                webAllowedObject.ScriptNotifyHandler = (sender, e) =>
                {
                    try
                    {
                        var message = JsonConvert.DeserializeObject<dynamic>(e.Value);

                        if (message["___magic___"] != null)
                        {
                            var interopType = (WebViewInteropType)message.type;
                            var interop = JsonConvert.DeserializeObject<dynamic>(message.interop.ToString());
                            var source = (string)interop.source.ToString();
                            var target = (string)interop.target.ToString();
                            var parameters = (object[])interop.parameters.ToObject<object[]>();

                            if (interopType == WebViewInteropType.InvokeMethod)
                            {
                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
                                    {
                                        var method = (MethodInfo)methodObject;

                                        var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();

                                        var convertedParameters = new object[parameters.Length];

                                        for (var i = 0; i < parameters.Length; i++)
                                        {
                                            convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
                                        }

                                        method.Invoke(targetObject, convertedParameters);
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.InvokeMethodWithReturn)
                            {
                                var callbackId = interop.callbackId.ToString();

                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
                                    {
                                        var method = (MethodInfo)methodObject;

                                        var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();

                                        var convertedParameters = new object[parameters.Length];

                                        for (var i = 0; i < parameters.Length; i++)
                                        {
                                            convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
                                        }

                                        var invokeResult = method.Invoke(targetObject, convertedParameters);

                                        webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(invokeResult)) });
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.GetProperty)
                            {
                                var callbackId = interop.callbackId.ToString();

                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
                                    {
                                        var property = (PropertyInfo)propertyObject;

                                        var getResult = property.GetValue(targetObject);

                                        webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(getResult)) });
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.SetProperty)
                            {
                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
                                    {
                                        var property = (PropertyInfo)propertyObject;

                                        property.SetValue(targetObject, JsonHelper.ConvertWeaklyTypedValue(parameters[0], property.PropertyType));
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        // Do nothing
                    }
                };

                webview.NavigationCompleted += webAllowedObject.NavigationCompletedHandler;
                webview.ScriptNotify += webAllowedObject.ScriptNotifyHandler;
            }
            else
            {
                throw new InvalidOperationException("Object with the identical name is already exist.");
            }
        }

        public static void RemoveWebAllowedObject(this WebView webview, string name)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));

            var allowedWebObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;

            if (allowedWebObjectsMap != null)
            {
                if (allowedWebObjectsMap.TryRemove(name, out WebAllowedObject webAllowedObject))
                {
                    webview.NavigationCompleted -= webAllowedObject.NavigationCompletedHandler;
                    webview.ScriptNotify -= webAllowedObject.ScriptNotifyHandler;

                    webview.InvokeScript("eval", new string[] { "delete window['" + name + "'];" });
                }
            }
        }
    }
}
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using System;
using System.Diagnostics;
using System.Windows;

namespace WpfApp3
{
    public partial class MainWindow : Window
    {
        public class MyBridge
        {
            private readonly MainWindow _window;

            public MyBridge(MainWindow window)
            {
                _window = window;
            }

            public void setTitle(string title)
            {
                Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));

                _window.setTitle(title);
            }

            public void playTTS(string tts)
            {
                Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
            }
        }

        public MainWindow()
        {
            this.InitializeComponent();

            this.wv.IsScriptNotifyAllowed = true;
            this.wv.ScriptNotify += Wv_ScriptNotify;
            this.wv.AddWebAllowedObject("wtjs", new MyBridge(this));

            this.Loaded += MainPage_Loaded;
        }

        private void Wv_ScriptNotify(object sender, WebViewControlScriptNotifyEventArgs e)
        {
            if (e.IsNotification())
            {
                Debug.WriteLine(e.Value);
            }
        }

        private void setTitle(string str)
        {
            textBlock.Text = str;
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.wv.Source = new Uri("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=");
        }
    }
}
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf" 
        x:Class="WpfApp3.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid x:Name="grid">
        <cefSharp:ChromiumWebBrowser x:Name="wv" HorizontalAlignment="Left" Height="405" Margin="50,0,0,0" VerticalAlignment="Top" Width="725" RenderTransformOrigin="-0.45,-0.75" />
        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="30,30,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Height="60" Width="335"/>
    </Grid>
</Window>
using CefSharp;
using System.Diagnostics;
using System.Windows;

namespace WpfApp3
{
    public partial class MainWindow : Window
    {
        public class MyBridge
        {
            private readonly MainWindow _window;

            public MyBridge(MainWindow window)
            {
                _window = window;
            }

            public void setTitle(string title)
            {
                Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));

                _window.setTitle(title);
            }

            public void playTTS(string tts)
            {
                Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
            }
        }

        public MainWindow()
        {
            this.InitializeComponent();

            this.wv.JavascriptObjectRepository.Register("wtjs", new MyBridge(this), true, new BindingOptions() { CamelCaseJavascriptNames = false });
            this.wv.FrameLoadStart += Wv_FrameLoadStart;

            this.Loaded += MainPage_Loaded;
        }

        private void Wv_FrameLoadStart(object sender, FrameLoadStartEventArgs e)
        {
            if (e.Url.StartsWith("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather"))
            {
                e.Browser.MainFrame.ExecuteJavaScriptAsync("CefSharp.BindObjectAsync('wtjs');");
            }
        }

        private void setTitle(string str)
        {
            this.Dispatcher.Invoke(() =>
            {
                textBlock.Text = str;
            });
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.wv.Address = "https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=";
        }
    }
}
-----------------------
// Add
webView.AddWebAllowedObject("wtjs", new MyBridge(this));
webView.AddWebAllowedObject("myBridge", new MyOtherBridge());

// Remove
webView.RemoveWebAllowedObject("wtjs");
// Call C# object method (no return value)
wtjs.hello('hello', 'world', 666);
myBridge.saySomething('天猫精灵,叫爸爸!');

// Call C# object method (return value)
wtjs.add(10, 20).then(function (result) { console.log(result); });

// Get C# object property
wtjs.backgroundColor.then(function (color) { console.log(color); });

// Set C# object property
wtjs.niubility = true;
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using Microsoft.Toolkit.Wpf.UI.Controls;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Text;

namespace WpfApp3
{
    // Source: https://github.com/dotnet/orleans/issues/1269#issuecomment-171233788
    public static class JsonHelper
    {
        private static readonly Type[] _specialNumericTypes = { typeof(ulong), typeof(uint), typeof(ushort), typeof(sbyte) };

        public static object ConvertWeaklyTypedValue(object value, Type targetType)
        {
            if (targetType == null)
                throw new ArgumentNullException(nameof(targetType));

            if (value == null)
                return null;

            if (targetType.IsInstanceOfType(value))
                return value;

            var paramType = Nullable.GetUnderlyingType(targetType) ?? targetType;

            if (paramType.IsEnum)
            {
                if (value is string)
                    return Enum.Parse(paramType, (string)value);
                else
                    return Enum.ToObject(paramType, value);
            }

            if (paramType == typeof(Guid))
            {
                return Guid.Parse((string)value);
            }

            if (_specialNumericTypes.Contains(paramType))
            {
                if (value is BigInteger)
                    return (ulong)(BigInteger)value;
                else
                    return Convert.ChangeType(value, paramType);
            }

            if (value is long || value is double)
            {
                return Convert.ChangeType(value, paramType);
            }

            return value;
        }
    }

    public enum WebViewInteropType
    {
        Notify = 0,
        InvokeMethod = 1,
        InvokeMethodWithReturn = 2,
        GetProperty = 3,
        SetProperty = 4
    }

    public class WebAllowedObject
    {
        public WebAllowedObject(WebView webview, string name)
        {
            WebView = webview;
            Name = name;
        }

        public WebView WebView { get; private set; }

        public string Name { get; private set; }

        public ConcurrentDictionary<(string, WebViewInteropType), object> FeaturesMap { get; } = new ConcurrentDictionary<(string, WebViewInteropType), object>();

        public EventHandler<WebViewControlNavigationCompletedEventArgs> NavigationCompletedHandler { get; set; }

        public EventHandler<WebViewControlScriptNotifyEventArgs> ScriptNotifyHandler { get; set; }
    }

    public static class WebViewExtensions
    {
        public static bool IsNotification(this WebViewControlScriptNotifyEventArgs e)
        {
            try
            {
                var message = JsonConvert.DeserializeObject<dynamic>(e.Value);

                if (message["___magic___"] != null)
                {
                    return false;
                }
            }
            catch (Exception) { }

            return true;
        }

        public static void AddWebAllowedObject(this WebView webview, string name, object targetObject)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));

            if (targetObject == null)
                throw new ArgumentNullException(nameof(targetObject));

            if (webview.Tag == null)
            {
                webview.Tag = new ConcurrentDictionary<string, WebAllowedObject>();
            }
            else if (!(webview.Tag is ConcurrentDictionary<string, WebAllowedObject>))
            {
                throw new InvalidOperationException("WebView.Tag property is already being used for other purpose.");
            }

            var webAllowedObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;

            var webAllowedObject = new WebAllowedObject(webview, name);

            if (webAllowedObjectsMap.TryAdd(name, webAllowedObject))
            {
                var objectType = targetObject.GetType();
                var methods = objectType.GetMethods();
                var properties = objectType.GetProperties();

                var jsStringBuilder = new StringBuilder();

                jsStringBuilder.Append("(function () {");
                jsStringBuilder.Append("window['");
                jsStringBuilder.Append(name);
                jsStringBuilder.Append("'] = {");

                jsStringBuilder.Append("__callback: {},");
                jsStringBuilder.Append("__newUuid: function () { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function (c) { return (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16); }); },");

                foreach (var method in methods)
                {
                    if (!method.IsSpecialName)
                    {
                        if (method.ReturnType == typeof(void))
                        {
                            webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethod), method);
                        }
                        else
                        {
                            webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethodWithReturn), method);
                        }

                        var parameters = method.GetParameters();
                        var parametersInString = string.Join(",", parameters.Select(x => x.Position).Select(x => "$$" + x.ToString()));

                        jsStringBuilder.Append(method.Name);
                        jsStringBuilder.Append(": function (");
                        jsStringBuilder.Append(parametersInString);
                        jsStringBuilder.Append(") {");

                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
                        }

                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(method.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("parameters: [");
                        jsStringBuilder.Append(parametersInString);
                        jsStringBuilder.Append("]");

                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append(",");
                            jsStringBuilder.Append("callbackId: callbackId");
                        }

                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((method.ReturnType == typeof(void)) ? (int)WebViewInteropType.InvokeMethod : (int)WebViewInteropType.InvokeMethodWithReturn);
                        jsStringBuilder.Append(");");


                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
                            jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
                            jsStringBuilder.Append("});");

                            jsStringBuilder.Append("return promise;");
                        }

                        jsStringBuilder.Append("},");
                    }
                }

                jsStringBuilder.Append("};");

                foreach (var property in properties)
                {
                    jsStringBuilder.Append("Object.defineProperty(");
                    jsStringBuilder.Append("window['");
                    jsStringBuilder.Append(name);
                    jsStringBuilder.Append("'], '");
                    jsStringBuilder.Append(property.Name);
                    jsStringBuilder.Append("', {");

                    if (property.CanRead)
                    {
                        webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.GetProperty), property);

                        jsStringBuilder.Append("get: function () {");
                        jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(property.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("callbackId: callbackId,");
                        jsStringBuilder.Append("parameters: []");
                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((int)WebViewInteropType.GetProperty);
                        jsStringBuilder.Append(");");

                        jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
                        jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
                        jsStringBuilder.Append("});");

                        jsStringBuilder.Append("return promise;");

                        jsStringBuilder.Append("},");
                    }

                    if (property.CanWrite)
                    {
                        webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.SetProperty), property);

                        jsStringBuilder.Append("set: function ($$v) {");
                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(property.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("parameters: [$$v]");
                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((int)WebViewInteropType.SetProperty);
                        jsStringBuilder.Append(");");
                        jsStringBuilder.Append("},");
                    }

                    jsStringBuilder.Append("});");
                }

                jsStringBuilder.Append("})();");

                var jsString = jsStringBuilder.ToString();

                webAllowedObject.NavigationCompletedHandler = (sender, e) =>
                {
                    var isExternalObjectCustomized = webview.InvokeScript("eval", new string[] { "window.external.hasOwnProperty('isCustomized').toString();" }).Equals("true");

                    if (!isExternalObjectCustomized)
                    {
                        webview.InvokeScript("eval", new string[] { @"
                            (function () {
                                var originalExternal = window.external;
                                var customExternal = {
                                    notify: function (message, type = 0) {
                                        if (type === 0) {
                                            originalExternal.notify(message);
                                        } else {
                                            originalExternal.notify(JSON.stringify({
                                                ___magic___: true,
                                                type: type,
                                                interop: message
                                            }));
                                        }
                                    },
                                    isCustomized: true
                                };
                                window.external = customExternal;
                            })();" });
                    }

                    webview.InvokeScript("eval", new string[] { jsString });
                };

                webAllowedObject.ScriptNotifyHandler = (sender, e) =>
                {
                    try
                    {
                        var message = JsonConvert.DeserializeObject<dynamic>(e.Value);

                        if (message["___magic___"] != null)
                        {
                            var interopType = (WebViewInteropType)message.type;
                            var interop = JsonConvert.DeserializeObject<dynamic>(message.interop.ToString());
                            var source = (string)interop.source.ToString();
                            var target = (string)interop.target.ToString();
                            var parameters = (object[])interop.parameters.ToObject<object[]>();

                            if (interopType == WebViewInteropType.InvokeMethod)
                            {
                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
                                    {
                                        var method = (MethodInfo)methodObject;

                                        var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();

                                        var convertedParameters = new object[parameters.Length];

                                        for (var i = 0; i < parameters.Length; i++)
                                        {
                                            convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
                                        }

                                        method.Invoke(targetObject, convertedParameters);
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.InvokeMethodWithReturn)
                            {
                                var callbackId = interop.callbackId.ToString();

                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
                                    {
                                        var method = (MethodInfo)methodObject;

                                        var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();

                                        var convertedParameters = new object[parameters.Length];

                                        for (var i = 0; i < parameters.Length; i++)
                                        {
                                            convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
                                        }

                                        var invokeResult = method.Invoke(targetObject, convertedParameters);

                                        webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(invokeResult)) });
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.GetProperty)
                            {
                                var callbackId = interop.callbackId.ToString();

                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
                                    {
                                        var property = (PropertyInfo)propertyObject;

                                        var getResult = property.GetValue(targetObject);

                                        webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(getResult)) });
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.SetProperty)
                            {
                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
                                    {
                                        var property = (PropertyInfo)propertyObject;

                                        property.SetValue(targetObject, JsonHelper.ConvertWeaklyTypedValue(parameters[0], property.PropertyType));
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        // Do nothing
                    }
                };

                webview.NavigationCompleted += webAllowedObject.NavigationCompletedHandler;
                webview.ScriptNotify += webAllowedObject.ScriptNotifyHandler;
            }
            else
            {
                throw new InvalidOperationException("Object with the identical name is already exist.");
            }
        }

        public static void RemoveWebAllowedObject(this WebView webview, string name)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));

            var allowedWebObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;

            if (allowedWebObjectsMap != null)
            {
                if (allowedWebObjectsMap.TryRemove(name, out WebAllowedObject webAllowedObject))
                {
                    webview.NavigationCompleted -= webAllowedObject.NavigationCompletedHandler;
                    webview.ScriptNotify -= webAllowedObject.ScriptNotifyHandler;

                    webview.InvokeScript("eval", new string[] { "delete window['" + name + "'];" });
                }
            }
        }
    }
}
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using System;
using System.Diagnostics;
using System.Windows;

namespace WpfApp3
{
    public partial class MainWindow : Window
    {
        public class MyBridge
        {
            private readonly MainWindow _window;

            public MyBridge(MainWindow window)
            {
                _window = window;
            }

            public void setTitle(string title)
            {
                Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));

                _window.setTitle(title);
            }

            public void playTTS(string tts)
            {
                Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
            }
        }

        public MainWindow()
        {
            this.InitializeComponent();

            this.wv.IsScriptNotifyAllowed = true;
            this.wv.ScriptNotify += Wv_ScriptNotify;
            this.wv.AddWebAllowedObject("wtjs", new MyBridge(this));

            this.Loaded += MainPage_Loaded;
        }

        private void Wv_ScriptNotify(object sender, WebViewControlScriptNotifyEventArgs e)
        {
            if (e.IsNotification())
            {
                Debug.WriteLine(e.Value);
            }
        }

        private void setTitle(string str)
        {
            textBlock.Text = str;
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.wv.Source = new Uri("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=");
        }
    }
}
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf" 
        x:Class="WpfApp3.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid x:Name="grid">
        <cefSharp:ChromiumWebBrowser x:Name="wv" HorizontalAlignment="Left" Height="405" Margin="50,0,0,0" VerticalAlignment="Top" Width="725" RenderTransformOrigin="-0.45,-0.75" />
        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="30,30,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Height="60" Width="335"/>
    </Grid>
</Window>
using CefSharp;
using System.Diagnostics;
using System.Windows;

namespace WpfApp3
{
    public partial class MainWindow : Window
    {
        public class MyBridge
        {
            private readonly MainWindow _window;

            public MyBridge(MainWindow window)
            {
                _window = window;
            }

            public void setTitle(string title)
            {
                Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));

                _window.setTitle(title);
            }

            public void playTTS(string tts)
            {
                Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
            }
        }

        public MainWindow()
        {
            this.InitializeComponent();

            this.wv.JavascriptObjectRepository.Register("wtjs", new MyBridge(this), true, new BindingOptions() { CamelCaseJavascriptNames = false });
            this.wv.FrameLoadStart += Wv_FrameLoadStart;

            this.Loaded += MainPage_Loaded;
        }

        private void Wv_FrameLoadStart(object sender, FrameLoadStartEventArgs e)
        {
            if (e.Url.StartsWith("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather"))
            {
                e.Browser.MainFrame.ExecuteJavaScriptAsync("CefSharp.BindObjectAsync('wtjs');");
            }
        }

        private void setTitle(string str)
        {
            this.Dispatcher.Invoke(() =>
            {
                textBlock.Text = str;
            });
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.wv.Address = "https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=";
        }
    }
}
-----------------------
// Add
webView.AddWebAllowedObject("wtjs", new MyBridge(this));
webView.AddWebAllowedObject("myBridge", new MyOtherBridge());

// Remove
webView.RemoveWebAllowedObject("wtjs");
// Call C# object method (no return value)
wtjs.hello('hello', 'world', 666);
myBridge.saySomething('天猫精灵,叫爸爸!');

// Call C# object method (return value)
wtjs.add(10, 20).then(function (result) { console.log(result); });

// Get C# object property
wtjs.backgroundColor.then(function (color) { console.log(color); });

// Set C# object property
wtjs.niubility = true;
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using Microsoft.Toolkit.Wpf.UI.Controls;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Text;

namespace WpfApp3
{
    // Source: https://github.com/dotnet/orleans/issues/1269#issuecomment-171233788
    public static class JsonHelper
    {
        private static readonly Type[] _specialNumericTypes = { typeof(ulong), typeof(uint), typeof(ushort), typeof(sbyte) };

        public static object ConvertWeaklyTypedValue(object value, Type targetType)
        {
            if (targetType == null)
                throw new ArgumentNullException(nameof(targetType));

            if (value == null)
                return null;

            if (targetType.IsInstanceOfType(value))
                return value;

            var paramType = Nullable.GetUnderlyingType(targetType) ?? targetType;

            if (paramType.IsEnum)
            {
                if (value is string)
                    return Enum.Parse(paramType, (string)value);
                else
                    return Enum.ToObject(paramType, value);
            }

            if (paramType == typeof(Guid))
            {
                return Guid.Parse((string)value);
            }

            if (_specialNumericTypes.Contains(paramType))
            {
                if (value is BigInteger)
                    return (ulong)(BigInteger)value;
                else
                    return Convert.ChangeType(value, paramType);
            }

            if (value is long || value is double)
            {
                return Convert.ChangeType(value, paramType);
            }

            return value;
        }
    }

    public enum WebViewInteropType
    {
        Notify = 0,
        InvokeMethod = 1,
        InvokeMethodWithReturn = 2,
        GetProperty = 3,
        SetProperty = 4
    }

    public class WebAllowedObject
    {
        public WebAllowedObject(WebView webview, string name)
        {
            WebView = webview;
            Name = name;
        }

        public WebView WebView { get; private set; }

        public string Name { get; private set; }

        public ConcurrentDictionary<(string, WebViewInteropType), object> FeaturesMap { get; } = new ConcurrentDictionary<(string, WebViewInteropType), object>();

        public EventHandler<WebViewControlNavigationCompletedEventArgs> NavigationCompletedHandler { get; set; }

        public EventHandler<WebViewControlScriptNotifyEventArgs> ScriptNotifyHandler { get; set; }
    }

    public static class WebViewExtensions
    {
        public static bool IsNotification(this WebViewControlScriptNotifyEventArgs e)
        {
            try
            {
                var message = JsonConvert.DeserializeObject<dynamic>(e.Value);

                if (message["___magic___"] != null)
                {
                    return false;
                }
            }
            catch (Exception) { }

            return true;
        }

        public static void AddWebAllowedObject(this WebView webview, string name, object targetObject)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));

            if (targetObject == null)
                throw new ArgumentNullException(nameof(targetObject));

            if (webview.Tag == null)
            {
                webview.Tag = new ConcurrentDictionary<string, WebAllowedObject>();
            }
            else if (!(webview.Tag is ConcurrentDictionary<string, WebAllowedObject>))
            {
                throw new InvalidOperationException("WebView.Tag property is already being used for other purpose.");
            }

            var webAllowedObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;

            var webAllowedObject = new WebAllowedObject(webview, name);

            if (webAllowedObjectsMap.TryAdd(name, webAllowedObject))
            {
                var objectType = targetObject.GetType();
                var methods = objectType.GetMethods();
                var properties = objectType.GetProperties();

                var jsStringBuilder = new StringBuilder();

                jsStringBuilder.Append("(function () {");
                jsStringBuilder.Append("window['");
                jsStringBuilder.Append(name);
                jsStringBuilder.Append("'] = {");

                jsStringBuilder.Append("__callback: {},");
                jsStringBuilder.Append("__newUuid: function () { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function (c) { return (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16); }); },");

                foreach (var method in methods)
                {
                    if (!method.IsSpecialName)
                    {
                        if (method.ReturnType == typeof(void))
                        {
                            webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethod), method);
                        }
                        else
                        {
                            webAllowedObject.FeaturesMap.TryAdd((method.Name, WebViewInteropType.InvokeMethodWithReturn), method);
                        }

                        var parameters = method.GetParameters();
                        var parametersInString = string.Join(",", parameters.Select(x => x.Position).Select(x => "$$" + x.ToString()));

                        jsStringBuilder.Append(method.Name);
                        jsStringBuilder.Append(": function (");
                        jsStringBuilder.Append(parametersInString);
                        jsStringBuilder.Append(") {");

                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
                        }

                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(method.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("parameters: [");
                        jsStringBuilder.Append(parametersInString);
                        jsStringBuilder.Append("]");

                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append(",");
                            jsStringBuilder.Append("callbackId: callbackId");
                        }

                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((method.ReturnType == typeof(void)) ? (int)WebViewInteropType.InvokeMethod : (int)WebViewInteropType.InvokeMethodWithReturn);
                        jsStringBuilder.Append(");");


                        if (method.ReturnType != typeof(void))
                        {
                            jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
                            jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
                            jsStringBuilder.Append("});");

                            jsStringBuilder.Append("return promise;");
                        }

                        jsStringBuilder.Append("},");
                    }
                }

                jsStringBuilder.Append("};");

                foreach (var property in properties)
                {
                    jsStringBuilder.Append("Object.defineProperty(");
                    jsStringBuilder.Append("window['");
                    jsStringBuilder.Append(name);
                    jsStringBuilder.Append("'], '");
                    jsStringBuilder.Append(property.Name);
                    jsStringBuilder.Append("', {");

                    if (property.CanRead)
                    {
                        webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.GetProperty), property);

                        jsStringBuilder.Append("get: function () {");
                        jsStringBuilder.Append("var callbackId = window['" + name + "'].__newUuid();");
                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(property.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("callbackId: callbackId,");
                        jsStringBuilder.Append("parameters: []");
                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((int)WebViewInteropType.GetProperty);
                        jsStringBuilder.Append(");");

                        jsStringBuilder.Append("var promise = new Promise(function (resolve, reject) {");
                        jsStringBuilder.Append("window['" + name + "'].__callback[callbackId] = { resolve, reject };");
                        jsStringBuilder.Append("});");

                        jsStringBuilder.Append("return promise;");

                        jsStringBuilder.Append("},");
                    }

                    if (property.CanWrite)
                    {
                        webAllowedObject.FeaturesMap.TryAdd((property.Name, WebViewInteropType.SetProperty), property);

                        jsStringBuilder.Append("set: function ($$v) {");
                        jsStringBuilder.Append("window.external.notify(JSON.stringify({");
                        jsStringBuilder.Append("source: '");
                        jsStringBuilder.Append(name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("target: '");
                        jsStringBuilder.Append(property.Name);
                        jsStringBuilder.Append("',");
                        jsStringBuilder.Append("parameters: [$$v]");
                        jsStringBuilder.Append("}), ");
                        jsStringBuilder.Append((int)WebViewInteropType.SetProperty);
                        jsStringBuilder.Append(");");
                        jsStringBuilder.Append("},");
                    }

                    jsStringBuilder.Append("});");
                }

                jsStringBuilder.Append("})();");

                var jsString = jsStringBuilder.ToString();

                webAllowedObject.NavigationCompletedHandler = (sender, e) =>
                {
                    var isExternalObjectCustomized = webview.InvokeScript("eval", new string[] { "window.external.hasOwnProperty('isCustomized').toString();" }).Equals("true");

                    if (!isExternalObjectCustomized)
                    {
                        webview.InvokeScript("eval", new string[] { @"
                            (function () {
                                var originalExternal = window.external;
                                var customExternal = {
                                    notify: function (message, type = 0) {
                                        if (type === 0) {
                                            originalExternal.notify(message);
                                        } else {
                                            originalExternal.notify(JSON.stringify({
                                                ___magic___: true,
                                                type: type,
                                                interop: message
                                            }));
                                        }
                                    },
                                    isCustomized: true
                                };
                                window.external = customExternal;
                            })();" });
                    }

                    webview.InvokeScript("eval", new string[] { jsString });
                };

                webAllowedObject.ScriptNotifyHandler = (sender, e) =>
                {
                    try
                    {
                        var message = JsonConvert.DeserializeObject<dynamic>(e.Value);

                        if (message["___magic___"] != null)
                        {
                            var interopType = (WebViewInteropType)message.type;
                            var interop = JsonConvert.DeserializeObject<dynamic>(message.interop.ToString());
                            var source = (string)interop.source.ToString();
                            var target = (string)interop.target.ToString();
                            var parameters = (object[])interop.parameters.ToObject<object[]>();

                            if (interopType == WebViewInteropType.InvokeMethod)
                            {
                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
                                    {
                                        var method = (MethodInfo)methodObject;

                                        var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();

                                        var convertedParameters = new object[parameters.Length];

                                        for (var i = 0; i < parameters.Length; i++)
                                        {
                                            convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
                                        }

                                        method.Invoke(targetObject, convertedParameters);
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.InvokeMethodWithReturn)
                            {
                                var callbackId = interop.callbackId.ToString();

                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object methodObject))
                                    {
                                        var method = (MethodInfo)methodObject;

                                        var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();

                                        var convertedParameters = new object[parameters.Length];

                                        for (var i = 0; i < parameters.Length; i++)
                                        {
                                            convertedParameters[i] = JsonHelper.ConvertWeaklyTypedValue(parameters[i], parameterTypes[i]);
                                        }

                                        var invokeResult = method.Invoke(targetObject, convertedParameters);

                                        webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(invokeResult)) });
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.GetProperty)
                            {
                                var callbackId = interop.callbackId.ToString();

                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
                                    {
                                        var property = (PropertyInfo)propertyObject;

                                        var getResult = property.GetValue(targetObject);

                                        webview.InvokeScript("eval", new string[] { string.Format("window['{0}'].__callback['{1}'].resolve({2}); delete window['{0}'].__callback['{1}'];", source, callbackId, JsonConvert.SerializeObject(getResult)) });
                                    }
                                }
                            }
                            else if (interopType == WebViewInteropType.SetProperty)
                            {
                                if (webAllowedObjectsMap.TryGetValue(source, out WebAllowedObject storedWebAllowedObject))
                                {
                                    if (storedWebAllowedObject.FeaturesMap.TryGetValue((target, interopType), out object propertyObject))
                                    {
                                        var property = (PropertyInfo)propertyObject;

                                        property.SetValue(targetObject, JsonHelper.ConvertWeaklyTypedValue(parameters[0], property.PropertyType));
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        // Do nothing
                    }
                };

                webview.NavigationCompleted += webAllowedObject.NavigationCompletedHandler;
                webview.ScriptNotify += webAllowedObject.ScriptNotifyHandler;
            }
            else
            {
                throw new InvalidOperationException("Object with the identical name is already exist.");
            }
        }

        public static void RemoveWebAllowedObject(this WebView webview, string name)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));

            var allowedWebObjectsMap = webview.Tag as ConcurrentDictionary<string, WebAllowedObject>;

            if (allowedWebObjectsMap != null)
            {
                if (allowedWebObjectsMap.TryRemove(name, out WebAllowedObject webAllowedObject))
                {
                    webview.NavigationCompleted -= webAllowedObject.NavigationCompletedHandler;
                    webview.ScriptNotify -= webAllowedObject.ScriptNotifyHandler;

                    webview.InvokeScript("eval", new string[] { "delete window['" + name + "'];" });
                }
            }
        }
    }
}
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using System;
using System.Diagnostics;
using System.Windows;

namespace WpfApp3
{
    public partial class MainWindow : Window
    {
        public class MyBridge
        {
            private readonly MainWindow _window;

            public MyBridge(MainWindow window)
            {
                _window = window;
            }

            public void setTitle(string title)
            {
                Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));

                _window.setTitle(title);
            }

            public void playTTS(string tts)
            {
                Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
            }
        }

        public MainWindow()
        {
            this.InitializeComponent();

            this.wv.IsScriptNotifyAllowed = true;
            this.wv.ScriptNotify += Wv_ScriptNotify;
            this.wv.AddWebAllowedObject("wtjs", new MyBridge(this));

            this.Loaded += MainPage_Loaded;
        }

        private void Wv_ScriptNotify(object sender, WebViewControlScriptNotifyEventArgs e)
        {
            if (e.IsNotification())
            {
                Debug.WriteLine(e.Value);
            }
        }

        private void setTitle(string str)
        {
            textBlock.Text = str;
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.wv.Source = new Uri("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=");
        }
    }
}
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf" 
        x:Class="WpfApp3.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid x:Name="grid">
        <cefSharp:ChromiumWebBrowser x:Name="wv" HorizontalAlignment="Left" Height="405" Margin="50,0,0,0" VerticalAlignment="Top" Width="725" RenderTransformOrigin="-0.45,-0.75" />
        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="30,30,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Height="60" Width="335"/>
    </Grid>
</Window>
using CefSharp;
using System.Diagnostics;
using System.Windows;

namespace WpfApp3
{
    public partial class MainWindow : Window
    {
        public class MyBridge
        {
            private readonly MainWindow _window;

            public MyBridge(MainWindow window)
            {
                _window = window;
            }

            public void setTitle(string title)
            {
                Debug.WriteLine(string.Format("SetTitle is executing...title = {0}", title));

                _window.setTitle(title);
            }

            public void playTTS(string tts)
            {
                Debug.WriteLine(string.Format("PlayTTS is executing...tts = {0}", tts));
            }
        }

        public MainWindow()
        {
            this.InitializeComponent();

            this.wv.JavascriptObjectRepository.Register("wtjs", new MyBridge(this), true, new BindingOptions() { CamelCaseJavascriptNames = false });
            this.wv.FrameLoadStart += Wv_FrameLoadStart;

            this.Loaded += MainPage_Loaded;
        }

        private void Wv_FrameLoadStart(object sender, FrameLoadStartEventArgs e)
        {
            if (e.Url.StartsWith("https://cmsdev.lenovo.com.cn/musichtml/leHome/weather"))
            {
                e.Browser.MainFrame.ExecuteJavaScriptAsync("CefSharp.BindObjectAsync('wtjs');");
            }
        }

        private void setTitle(string str)
        {
            this.Dispatcher.Invoke(() =>
            {
                textBlock.Text = str;
            });
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.wv.Address = "https://cmsdev.lenovo.com.cn/musichtml/leHome/weather/index.html?date=&city=&mark=0&speakerId=&reply=";
        }
    }
}

Community Discussions

Trending Discussions on JsBridge
  • how to call c# code via react jsx in xamarin webview
  • Is there a way to create bi-directional communication between a shell app and a webview in .NET 6 MAUI?
  • Upcalls from JavaScript to JavaFX not working due to VLCJ
  • Xamarin Android WebView don't fire navigating if &quot;target=_blank&quot;
  • RESCO : Set Look Up view dynamically on a lookup field on change of a field on the form using JSBridge
  • How to access foreground service from WebView Javascript Bridge
  • Xamarin.Forms ERR_FILE_NOT_FOUND (file:///android_asset/Content/https://stackoverflow.com) using HybridWebViewSample
  • How to call wpf function via JS when visiting https link
  • SyncFusion SfRichTextEditor doesn't return edited text using Prism, Xamarin.Forms on Android
Trending Discussions on JsBridge

QUESTION

how to call c# code via react jsx in xamarin webview

Asked 2021-Oct-26 at 07:57

https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/hybridwebview

I saw above msdn document about HybridWebView of xamarin

I understood the way communicate to c# and vanillajs

HybridWebViewRenderer.cs

using Android.Content;
using CustomRenderer;
using CustomRenderer.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.Droid
{
    public class HybridWebViewRenderer : WebViewRenderer
    {
        const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
        Context _context;

        public HybridWebViewRenderer(Context context) : base(context)
        {
            _context = context;
        }

        protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                ((HybridWebView)Element).Cleanup();
            }
            if (e.NewElement != null)
            {
                Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
                Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
                Control.LoadUrl($"file:///android_asset/Content/{((HybridWebView)Element).Uri}");
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((HybridWebView)Element).Cleanup();
            }
            base.Dispose(disposing);
        }
    }
}

index.html

<html>
<body>
    <script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
    <h1>HybridWebView Test</h1>
    <br />
    Enter name: <input type="text" id="name">
    <br />
    <br />
    <button type="button" onclick="javascript: invokeCSCode($('#name').val());">Invoke C# Code</button>
    <br />
    <p id="result">Result:</p>
    <script type="text/javascript">function log(str) {
            $('#result').text($('#result').text() + " " + str);
        }

        function invokeCSCode(data) {
            try {
                log("Sending Data:" + data);
                invokeCSharpAction(data);
            }
            catch (err) {
                log(err);
            }
        }</script>
</body>
</html>

but i can't think way calling c# code from jsx because As I understand it, in order for JavaScript to execute C# code, it has to execute a function with the same name as the JavaScript function in the string state passed when the JavaScript webview client class is initialized in html.

Usually, if you use the above method in React, you will get an error message.

I don't know much about react

If anyone has ever handled a react page through a Xamarin webview, please give me some tips or advice.

The error message I get when I try through react, or when I separate the js file and import it into html and run it

enter image description here

<html>
<body>
    <script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
    <h1>HybridWebView Test</h1>
    <br />
    Enter name: <input type="text" id="name">
    <br />
    <br />
    <button type="button" onclick="javascript: invokeCSCode('1234');">Invoke C# Code</button>
    <br />
    <p id="result">Result:</p>
    <script type="text/javascript" src="test.js"/>
</body>
</html>

index.html

function invokeCSCode(data) {
    invokeCSharpAction(data);
}

test.js

ANSWER

Answered 2021-Oct-26 at 07:57

I modified the React part after seeing the comments of the ToolmakerSteve.

At first I thought that the function could not be found because the function was renamed during a production build of react, but I found that it works regardless (It was just my misunderstanding that the method executed in JavaScript and the method executed in C# had the same name.)

Also I didn't know eval() how to do <button type="button" onclick="javascript: invokeCSCode(data);" >Invoke C# Code</button> in jsx I was wondering if I could use a method like

Below is an example of the code I have successfully received data from react.

React Part

TestFunctions.ts

export default function test(data: string){
    eval(`invokeTest(${data})`);
}

SendButton.tsx

export default function Button(props: ButtonProps){
    const dispatch = useAppDispatch()

    const handleClick = () =>{
        test('1234');
    }

    return(
        <ButtonWrapper heightValue={props.heightValue} widthValue={props.widthValue} marginValue={props.marginValue}>
            <Button variant="outlined" onClick={() => handleClick()} style={{height: '56px', width: '100%', fontSize: 'large'}}>
                send
            </Button>
        </ButtonWrapper>
    )
}

Xamarin Part

WebViewer.cs

public class WebViewer : WebView
{
    public static readonly BindableProperty UriProperty = BindableProperty.Create(
            propertyName: "Uri",
            returnType: typeof(string),
            declaringType: typeof(WebViewer),
            defaultValue: default(string),
            propertyChanged: UriChanged);

    private static void UriChanged(BindableObject bindable, object oldValue, object newValue)
    {
         if (newValue != null && bindable is WebViewer webViewer)
         {
            if(webViewer.CookieList != null && (string)newValue == (string)oldValue)
            {
                Uri uri = new Uri((string)newValue, UriKind.RelativeOrAbsolute);

                CookieContainer cookieContainer = new CookieContainer();

                foreach (Cookie cookie in webViewer.CookieList)
                {
                    cookie.Domain = uri.Host;
                    cookie.Path = uri.PathAndQuery;
                    cookieContainer.Add(cookie);
                }
                webViewer.Cookies = cookieContainer;
             }
             webViewer.Source = (string)newValue;
          }
     }

     public string Uri
     {
         get => (string)GetValue(UriProperty);
         set => SetValue(UriProperty, value);
     }

     public static readonly BindableProperty CookieListProperty = BindableProperty.Create(
            propertyName: "Cookies",
            returnType: typeof(List<Cookie>),
            declaringType: typeof(WebViewer),
            defaultValue: null,
            propertyChanged: CookieListChanged);
        
     public List<Cookie> CookieList
     {
         get => (List<Cookie>)GetValue(CookieListProperty);
         set => SetValue(CookieListProperty, value);
     }
        
     private static void CookieListChanged(BindableObject bindable, object oldValue, object newValue)
     {
         if (newValue != null && bindable is WebViewer webViewer)
         {
             Uri uri = new Uri(webViewer.Uri, UriKind.RelativeOrAbsolute);

             CookieContainer cookieContainer = new CookieContainer();

             foreach (Cookie cookie in (List<Cookie>)newValue)
             {
                 cookie.Domain = uri.Host;
                 cookie.Path = uri.PathAndQuery;
                 cookieContainer.Add(cookie);
             }
             webViewer.Cookies = cookieContainer;

             if (webViewer.Uri != default(string))
             {
                 webViewer.Source = webViewer.Uri;
             }
         }
     }

     Action<string> action;
     public void RegisterAction(Action<string> callback)
     {
         action = callback;
     }

     public void Cleanup()
     {
         action = null;
     }

     public void InvokeAction(string data)
     {
         if (action == null || data == null)
         {
             return;
         }
         action.Invoke(data);
     }
}

WebViewerRenderer.cs

using Android.Content;
using Android.Views;
using Android.Webkit;
using BDApp.Mobile.Droid.CustomRenderer;
using BDApp.Mobile.Views;
using Java.Interop;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewerRenderer))]
namespace BDApp.Mobile.Droid.CustomRenderer
{
    public class WebViewerRenderer : WebViewRenderer
    {
        const string JavascriptFunction = "function invokeTest(data){jsBridge.invokeAction(data);}";
        public WebViewerRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);

            if(e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                ((WebViewer)Element).Cleanup();
            }
            if(e.NewElement != null)
            {
                Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
                Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
            }
            Control.Settings.SetAppCacheEnabled(false);
            Control.Settings.CacheMode = CacheModes.NoCache;
        }

        public override bool DispatchTouchEvent(MotionEvent e)
        {
            Parent.RequestDisallowInterceptTouchEvent(true);
            return base.DispatchTouchEvent(e);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ((WebViewer)Element).Cleanup();
            }
            base.Dispose(disposing);
        }
    }
    public class JavascriptWebViewClient : FormsWebViewClient
    {
        string _javascript;

        public JavascriptWebViewClient(WebViewerRenderer renderer, string javascript) : base(renderer)
        {
            _javascript = javascript;
        }

        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            base.OnPageFinished(view, url);
            view.EvaluateJavascript(_javascript, null);
        }
    }
    public class JSBridge : Java.Lang.Object
    {
        readonly WeakReference<WebViewerRenderer> hybridWebViewRenderer;

        public JSBridge(WebViewerRenderer hybridRenderer)
        {
            hybridWebViewRenderer = new WeakReference<WebViewerRenderer>(hybridRenderer);
        }

        [JavascriptInterface]
        [Export("invokeAction")]
        public void InvokeAction(string data)
        {
            WebViewerRenderer hybridRenderer;

            if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
            {
                ((WebViewer)hybridRenderer.Element).InvokeAction(data);
            }
        }
    }
}

TestWebViewPage.xaml

<StackLayout>
        <views:WebViewer Uri="{Binding Uri}" x:Name="webView" CookieList="{Binding CookieList}"
                         HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/>
</StackLayout>

Source https://stackoverflow.com/questions/69672926

Community Discussions, Code Snippets contain sources that include Stack Exchange Network

Vulnerabilities

No vulnerabilities reported

Install JsBridge

You can download it from GitHub.
You can use JsBridge like any standard Java library. Please include the the jar files in your classpath. You can also use any IDE and you can run and debug the JsBridge component as you would do with any other Java program. Best practice is to use a build tool that supports dependency management such as Maven or Gradle. For Maven installation, please refer maven.apache.org. For Gradle installation, please refer gradle.org .

Support

For any new features, suggestions and bugs create an issue on GitHub. If you have any questions check and ask questions on community page Stack Overflow .

DOWNLOAD this Library from

Find, review, and download reusable Libraries, Code Snippets, Cloud APIs from
over 430 million Knowledge Items
Find more libraries
Reuse Solution Kits and Libraries Curated by Popular Use Cases

Save this library and start creating your kit

Share this Page

share link
Find, review, and download reusable Libraries, Code Snippets, Cloud APIs from
over 430 million Knowledge Items
Find more libraries
Reuse Solution Kits and Libraries Curated by Popular Use Cases

Save this library and start creating your kit

  • © 2022 Open Weaver Inc.