14

I am migrating our app from Xamarin to MAUI, and I am a bit struggling with migrating the code that handles JS/.NET interactions in a WebView on both Android and iOS. Let's focus on Android. It's especially about calling .NET code from JS in the WebView.

In Xamarin, we could do something like this (basically according to this tutorial https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/hybridwebview):

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

    if (e.OldElement != null)
    {
        Control.RemoveJavascriptInterface("jsBridge");
    }

    if (e.NewElement != null)
    {
        Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
        Control.AddJavascriptInterface(new JsBridge(this), "jsBridge");
    }
}

and

public class JavascriptWebViewClient : FormsWebViewClient
{
    private readonly string javascript;

    public JavascriptWebViewClient(HybridWebViewRenderer renderer, string javascript) : base(renderer)
    {
        this.javascript = javascript;
    }

    public override void OnPageFinished(WebView view, string url)
    {
        base.OnPageFinished(view, url);
        view.EvaluateJavascript(javascript, null);
    }
}

In .NET 6 with MAUI, this is deprecated. I tried to build it with handlers, but then the OnPageFinished is never called. The lack of examples is making it difficult to figure out what I miss.

Microsoft.Maui.Handlers.WebViewHandler.Mapper.AppendToMapping("MyCustomization", (handler, view) =>
  {
#if ANDROID
    handler.PlatformView.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
    handler.PlatformView.AddJavascriptInterface(new JsBridge(this), "jsBridge");
#endif
  });

with

public class JavascriptWebViewClient : WebViewClient
{
  private readonly string javascript;

  public JavascriptWebViewClient(string javascript) : base()
  {
    this.javascript = javascript;
  }

  public override void OnPageFinished(WebView view, string url)
  {
    base.OnPageFinished(view, url);
    view.EvaluateJavascript(javascript, null);
  }
}

Where should I put this code? Is this the correct way? What am I missing? I now put this in a subclassed WebView, but probably that's not the right way.

Kissaki
  • 8,810
  • 5
  • 40
  • 42
Danny
  • 411
  • 1
  • 3
  • 8

3 Answers3

12

Update: I developed a work-around for Windows. See below.

TL;DR - https://github.com/nmoschkin/MAUIWebViewExample

I have come up with a MAUI solution that work for both iOS and Android, using the new Handler pattern as described in:

Porting Custom Renderers To Handlers

The above documentation was somewhat poor, and did not feature an implementation for the iOS version. I provide that, here.

This adaptation also makes the Source property a BindableProperty. Unlike the example in the above link, I do not actually add the property to the PropertyMapper in the platform handler in the traditional way. Rather, we will be listening for an event to be fired by the property changed notification method of the bindable property.

This example implements a 100% custom WebView. If there are additional properties and methods you would like to port over from the native components, you will have to add that additional functionality, yourself.

Shared Code:

In the shared code file, you want to create your custom view by implementing the classes and interface as described in the above link in the following way (with additional classes provided for events that we will provide to the consumer):

public class SourceChangedEventArgs : EventArgs
{
    public WebViewSource Source
    {
        get;
        private set;
    }

    public SourceChangedEventArgs(WebViewSource source)
    {
        Source = source;
    }
}

public class JavaScriptActionEventArgs : EventArgs
{
    public string Payload { get; private set; }

    public JavaScriptActionEventArgs(string payload)
    {
        Payload = payload;
    }
}

public interface IHybridWebView : IView
{
    event EventHandler<SourceChangedEventArgs> SourceChanged;
    event EventHandler<JavaScriptActionEventArgs> JavaScriptAction;
    
    void Refresh();

    WebViewSource Source { get; set; }

    void Cleanup();

    void InvokeAction(string data);

}
    

public class HybridWebView : View, IHybridWebView
{
    public event EventHandler<SourceChangedEventArgs> SourceChanged;
    public event EventHandler<JavaScriptActionEventArgs> JavaScriptAction;

    public HybridWebView()
    {

    }

    public void Refresh()
    {
        if (Source == null) return;
        var s = Source;
        Source = null;
        Source = s;
    }

    public WebViewSource Source
    {
        get { return (WebViewSource)GetValue(SourceProperty); }
        set { SetValue(SourceProperty, value); }
    }

    public static readonly BindableProperty SourceProperty = BindableProperty.Create(
      propertyName: "Source",
      returnType: typeof(WebViewSource),
      declaringType: typeof(HybridWebView),
      defaultValue: new UrlWebViewSource() { Url = "about:blank" },
      propertyChanged: OnSourceChanged);

    private static void OnSourceChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var view = bindable as HybridWebView;

        bindable.Dispatcher.Dispatch(() =>
        {
            view.SourceChanged?.Invoke(view, new SourceChangedEventArgs(newValue as WebViewSource));

        });
    }

    public void Cleanup()
    {
        JavaScriptAction = null;
    }

    public void InvokeAction(string data)
    {
        JavaScriptAction?.Invoke(this, new JavaScriptActionEventArgs(data));
    }
}

Then you would have to declare the handler for each platform, as follows:

Android Implementation:

public class HybridWebViewHandler : ViewHandler<IHybridWebView, Android.Webkit.WebView>
{
    public static PropertyMapper<IHybridWebView, HybridWebViewHandler> HybridWebViewMapper = new PropertyMapper<IHybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper);

    const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";

    private JSBridge jsBridgeHandler;

    public HybridWebViewHandler() : base(HybridWebViewMapper)
    {
    }

    private void VirtualView_SourceChanged(object sender, SourceChangedEventArgs e)
    {
        LoadSource(e.Source, PlatformView);
    }

    protected override Android.Webkit.WebView CreatePlatformView()
    {
        var webView = new Android.Webkit.WebView(Context);
        jsBridgeHandler = new JSBridge(this);

        webView.Settings.JavaScriptEnabled = true;

        webView.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
        webView.AddJavascriptInterface(jsBridgeHandler, "jsBridge");

        return webView;
    }

    protected override void ConnectHandler(Android.Webkit.WebView platformView)
    {
        base.ConnectHandler(platformView);

        if (VirtualView.Source != null)
        {
            LoadSource(VirtualView.Source, PlatformView);
        }

        VirtualView.SourceChanged += VirtualView_SourceChanged;
    }

    protected override void DisconnectHandler(Android.Webkit.WebView platformView)
    {
        base.DisconnectHandler(platformView);

        VirtualView.SourceChanged -= VirtualView_SourceChanged;
        VirtualView.Cleanup();

        jsBridgeHandler?.Dispose();
        jsBridgeHandler = null;
    }

    private static void LoadSource(WebViewSource source, Android.Webkit.WebView control)
    {
        try
        {
            if (source is HtmlWebViewSource html)
            {
                control.LoadDataWithBaseURL(html.BaseUrl, html.Html, null, "charset=UTF-8", null);
            }
            else if (source is UrlWebViewSource url)
            {
                control.LoadUrl(url.Url);
            }
        }
        catch { }
    }
}

public class JavascriptWebViewClient : WebViewClient
{
    string _javascript;

    public JavascriptWebViewClient(string javascript)
    {
        _javascript = javascript;
    }

    public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
    {
        base.OnPageStarted(view, url, favicon);
        view.EvaluateJavascript(_javascript, null);
    }
}

public class JSBridge : Java.Lang.Object
{
    readonly WeakReference<HybridWebViewHandler> hybridWebViewRenderer;

    internal JSBridge(HybridWebViewHandler hybridRenderer)
    {
        hybridWebViewRenderer = new WeakReference<HybridWebViewHandler>(hybridRenderer);
    }

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

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

iOS Implementation:

public class HybridWebViewHandler : ViewHandler<IHybridWebView, WKWebView>
{
    public static PropertyMapper<IHybridWebView, HybridWebViewHandler> HybridWebViewMapper = new PropertyMapper<IHybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper);

    const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";

    private WKUserContentController userController;
    private JSBridge jsBridgeHandler;

    public HybridWebViewHandler() : base(HybridWebViewMapper)
    {
    }

    private void VirtualView_SourceChanged(object sender, SourceChangedEventArgs e)
    {
        LoadSource(e.Source, PlatformView);
    }

    protected override WKWebView CreatePlatformView()
    {

        jsBridgeHandler = new JSBridge(this);
        userController = new WKUserContentController();

        var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);

        userController.AddUserScript(script);
        userController.AddScriptMessageHandler(jsBridgeHandler, "invokeAction");

        var config = new WKWebViewConfiguration { UserContentController = userController };
        var webView = new WKWebView(CGRect.Empty, config);

        return webView;            
    }

    protected override void ConnectHandler(WKWebView platformView)
    {
        base.ConnectHandler(platformView);

        if (VirtualView.Source != null)
        {
            LoadSource(VirtualView.Source, PlatformView);
        }

        VirtualView.SourceChanged += VirtualView_SourceChanged;
    }

    protected override void DisconnectHandler(WKWebView platformView)
    {
        base.DisconnectHandler(platformView);

        VirtualView.SourceChanged -= VirtualView_SourceChanged;

        userController.RemoveAllUserScripts();
        userController.RemoveScriptMessageHandler("invokeAction");
    
        jsBridgeHandler?.Dispose();
        jsBridgeHandler = null;
    }


    private static void LoadSource(WebViewSource source, WKWebView control)
    {
        if (source is HtmlWebViewSource html)
        {
            control.LoadHtmlString(html.Html, new NSUrl(html.BaseUrl ?? "http://localhost", true));
        }
        else if (source is UrlWebViewSource url)
        {
            control.LoadRequest(new NSUrlRequest(new NSUrl(url.Url)));
        }

    }

}

public class JSBridge : NSObject, IWKScriptMessageHandler
{
    readonly WeakReference<HybridWebViewHandler> hybridWebViewRenderer;

    internal JSBridge(HybridWebViewHandler hybridRenderer)
    {
        hybridWebViewRenderer = new WeakReference<HybridWebViewHandler>(hybridRenderer);
    }

    public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
    {
        HybridWebViewHandler hybridRenderer;

        if (hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
        {
            hybridRenderer.VirtualView?.InvokeAction(message.Body.ToString());
        }
    }
}

As you can see, I'm listening for the event to change out the source, which will then perform the platform-specific steps necessary to change it.

Also note that in both implementations of JSBridge I am using a WeakReference to track the control. I am not certain of any situations where disposal might deadlock, but I did this out of an abundance of caution.

Windows Implementation:

So. According to various articles I read, the current WinUI3 iteration of WebView2 for MAUI is not yet allowing us to invoke AddHostObjectToScript. They plan this for a future release.

But, then I remembered it was Windows, so I created a work-around that most certainly emulates the same behavior and achieves the same result, with a somewhat unorthodox solution: by using an HttpListener.

internal class HybridSocket
{
    private HttpListener listener;
    private HybridWebViewHandler handler;
    bool token = false;
    
    public HybridSocket(HybridWebViewHandler handler)
    {
        this.handler = handler;
        CreateSocket();
    }

    private void CreateSocket()
    {
        listener = new HttpListener();
        listener.Prefixes.Add("http://localhost:32000/");
    }

    public void StopListening()
    {
        token = false;
    }

    private void SendToNative(string json)
    {
        handler.VirtualView.InvokeAction(json);
    }

    public void Listen()
    {
        var s = listener;
        try
        {
            token = true;
            s.Start();
            
            while (token)
            {                
                HttpListenerContext ctx = listener.GetContext();                
                using HttpListenerResponse resp = ctx.Response;

                    resp.AddHeader("Access-Control-Allow-Origin", "null");
                    resp.AddHeader("Access-Control-Allow-Headers", "content-type");

                    var req = ctx.Request;

                    Stream body = req.InputStream;
                    Encoding encoding = req.ContentEncoding;

                    using (StreamReader reader = new StreamReader(body, encoding))
                    {
                        var json = reader.ReadToEnd();

                        if (ctx.Request.HttpMethod == "POST")
                        {
                            SendToNative(json);
                        }
                    }

                resp.StatusCode = (int)HttpStatusCode.OK;
                resp.StatusDescription = "Status OK";
            }
                       
            CreateSocket();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }
}

public class HybridWebViewHandler : ViewHandler<IHybridWebView, WebView2>
{
    public static PropertyMapper<IHybridWebView, HybridWebViewHandler> HybridWebViewMapper = new PropertyMapper<IHybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper);

    const string JavascriptFunction = @"function invokeCSharpAction(data)
                {
                    var http = new XMLHttpRequest();
                    var url = 'http://localhost:32000';
                    http.open('POST', url, true);
                    http.setRequestHeader('Content-type', 'application/json');
                    http.send(JSON.stringify(data));
                }";

    static SynchronizationContext sync;
    private HybridSocket jssocket;

    public HybridWebViewHandler() : base(HybridWebViewMapper)
    {
        sync = SynchronizationContext.Current;
        jssocket = new HybridSocket(this);

        Task.Run(() => jssocket.Listen());
    }

    ~HybridWebViewHandler()
    {
        jssocket.StopListening();
    }

    private void OnWebSourceChanged(object sender, SourceChangedEventArgs e)
    {
        LoadSource(e.Source, PlatformView);
    }

    protected override WebView2 CreatePlatformView()
    { 
        sync = sync ?? SynchronizationContext.Current;
        var webView = new WebView2();

        webView.NavigationCompleted += WebView_NavigationCompleted;

       
        return webView;
    }

    private void WebView_NavigationCompleted(WebView2 sender, CoreWebView2NavigationCompletedEventArgs args)
    {
        var req = new EvaluateJavaScriptAsyncRequest(JavascriptFunction);
        PlatformView.EvaluateJavaScript(req);
    }

    protected override void ConnectHandler(WebView2 platformView)
    {
        base.ConnectHandler(platformView);

        if (VirtualView.Source != null)
        {
            LoadSource(VirtualView.Source, PlatformView);
        }

        VirtualView.SourceChanged += OnWebSourceChanged;
    }

    protected override void DisconnectHandler(WebView2 platformView)
    {
        base.DisconnectHandler(platformView);

        VirtualView.SourceChanged -= OnWebSourceChanged;
        VirtualView.Cleanup();
    }

    private static void LoadSource(WebViewSource source, WebView2 control)
    {
        try
        {
            if (control.CoreWebView2 == null)
            {
                control.EnsureCoreWebView2Async().AsTask().ContinueWith((t) =>
                {
                    sync.Post((o) => LoadSource(source, control), null);
                });
            }
            else
            {
                if (source is HtmlWebViewSource html)
                {
                    control.CoreWebView2.NavigateToString(html.Html);
                }
                else if (source is UrlWebViewSource url)
                {
                    control.CoreWebView2.Navigate(url.Url);
                }
            }
        }
        catch { }
    }
}

Finally, you will need to initialize the MAUI application by adding ConfigureMauiHandlers to the app builder:

Initialize the MAUI Application in MauiProgram.cs

public static MauiApp CreateMauiApp()
{

    var builder = MauiApp.CreateBuilder();

    builder
        .UseMauiApp<App>()
        .ConfigureFonts(fonts =>
        {
            fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
        })
        .ConfigureMauiHandlers(handlers =>
        {
            handlers.AddHandler(typeof(HybridWebView), typeof(HybridWebViewHandler));
        });


    return builder.Build();
}

Add The Control To XAML

<controls:HybridWebView
            x:Name="MyWebView"
            HeightRequest="128"
            HorizontalOptions="Fill"
            Source="{Binding Source}"
            VerticalOptions="FillAndExpand"
            WidthRequest="512"
            />

Finally, I have added all of the above to a full example MAUI project in a repository on GitHub:

https://github.com/nmoschkin/MAUIWebViewExample

The GitHub repo example also includes a ViewModel that contains the WebViewSource to which the control is bound in markup.

Nathan M
  • 873
  • 5
  • 18
  • Great job - works great!!! I tried the same for windows but I can not find a function like AddUserScript() and CoreWebView2.AddHostObjectToScript(...) throws the error: 'The group or resource is not in the correct state to perform the requested operation.' Could you PLEASE extend you example for Windows? – Markus Aug 11 '22 at 04:54
  • Sure! I can do my best. – Nathan M Aug 11 '22 at 11:27
  • @Markus - can you link me to what you've already got going for the Windows piece? I'd hate to repeat effort. – Nathan M Aug 11 '22 at 12:05
  • Nathan: I start an issue on your github project... – Markus Aug 12 '22 at 07:13
  • @Markus okay I've triaged the GitHub issue. I might not get to it til Tuesday. In the middle of doing a major rollout for my company. – Nathan M Aug 14 '22 at 18:51
  • @NathanM thanks for this! How do you implement EvaluateJavaScriptAsync in this? I tried to change it to HybridWebView : WebView, IHybridWebView, but it does not work. – Morten OC Aug 27 '22 at 21:29
  • Not sure i follow... – Nathan M Aug 28 '22 at 23:24
  • @NathanM In a normal WebView, you can call EvaluateJavaScriptAsync(), but this method is gone in your HybridWebView. I can get the method back by inherit from WebView instaed of View, but EvaluateJavaScriptAsync() does not fire anything anymore – Morten OC Aug 29 '22 at 10:04
  • It needs to be natively reimplemented. Fortunely were already in the general area with all of this. – Nathan M Aug 29 '22 at 16:42
  • Great job! eagerly waiting for windows implementation. – Manjoor Sep 08 '22 at 22:16
  • 1
    @Manjoor So. It just dawned on me that it was Windows, which means we could jury rig something else up. I found a work-around, and got Windows to work. Please check the Github page. – Nathan M Sep 29 '22 at 04:06
  • 1
    For Windows you can use this - WebView2.WebMessageReceived, with js invocation script being - window.chrome.webview.postMessage – ac-lap May 13 '23 at 17:56
3

OK, figured it out. Adding information for those looking to the same problem.

What you need to do:

  1. Override WebView client.

Generic:

public partial class CustomWebView : WebView
{
    partial void ChangedHandler(object sender);
    partial void ChangingHandler(object sender, HandlerChangingEventArgs e);

    protected override void OnHandlerChanging(HandlerChangingEventArgs args)
    {
        base.OnHandlerChanging(args);
        ChangingHandler(this, args);
    }

    protected override void OnHandlerChanged()
    {
        base.OnHandlerChanged();
        ChangedHandler(this);
    }

    public void InvokeAction(string data)
    {
        // your custom code
    }
}

Android:

public partial class CustomWebView
{
    const string JavascriptFunction = "function invokeActionInCS(data){jsBridge.invokeAction(data);}";

    partial void ChangedHandler(object sender)
    {
        if (sender is not WebView { Handler: { PlatformView: Android.Webkit.WebView nativeWebView } }) return;

        nativeWebView.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
        nativeWebView.AddJavascriptInterface(new JsBridge(this), "jsBridge");
    }

    partial void ChangingHandler(object sender, HandlerChangingEventArgs e)
    {
        if (e.OldHandler != null)
        {
            if (sender is not WebView { Handler: { PlatformView: Android.Webkit.WebView nativeWebView } }) return;
            nativeWebView.RemoveJavascriptInterface("jsBridge");
        }
    }
}
  1. Add this custom view to your XAML
<views:CustomWebView x:Name="CustomWebViewName"/>
  1. Modify the JS Bridge
public class JsBridge : Java.Lang.Object
    {
        private readonly HarmonyWebView webView;

        public JsBridge(HarmonyWebView webView)
        {
            this.webView = webView;
        }

        [JavascriptInterface]
        [Export("invokeAction")]
        public void InvokeAction(string data)
        {
            webView.InvokeAction(data);
        }
    }
Danny
  • 411
  • 1
  • 3
  • 8
  • I actually had been preparing an answer for you that worked with both iOS and Android as I had the need to completely override the control in one of my projects. I was just in the middle of testing the last bits (the new version of MAUI dropped yesterday and I had to sort of re-arrange everything) I actually just subclass ContentView and create the entire web view from scratch, allows for more flexibility. – Nathan M Aug 10 '22 at 13:04
  • Thanks for your effort, much appreciated! I am still looking forward to see your solution, happy to see if it is better than mine. – Danny Aug 10 '22 at 15:01
  • I'm still working on it. I will post it, here, when I'm done. – Nathan M Aug 10 '22 at 16:09
  • Done! Validated working in both Android and iOS – Nathan M Aug 10 '22 at 19:25
1

I solved it finnaly. It's not completely solution however its work well.

First, we define 2 variables globally on the javascipt side and define 2 functions waiting for each other.

var _flagformobiledata = false;
var _dataformobiledata = "";

const waitUntilMobileData = (condition, checkInterval = 100) => {
    return new Promise(resolve => {
        let interval = setInterval(() => {
            if (!condition()) return;
            clearInterval(interval);
            resolve();
        }, checkInterval)
    })
}


async function mobileajax(functionName, params) {
    window.location.href = "https://runcsharp." + functionName + "?" + params;
    await waitUntilMobileData(() => _flagformobiledata == true);
    _flagformobiledata = false;
    return _dataformobiledata;
}

function setmobiledata(aData) {

    _dataformobiledata = aData;
    _flagformobiledata = true;


}

Then in MainPage.xaml.cs file define a function named WebViewNavigation

private async void WebViewNavigation(object sender, WebNavigatingEventArgs e)
{

    var urlParts = e.Url.Split("runcsharp.");
    if (urlParts.Length == 2)
    {
        Console.WriteLine(urlParts);
        var funcToCall = urlParts[1].Split("?");
        var methodName = funcToCall[0];
        var funcParams = funcToCall[1];

        e.Cancel = true;


        if (methodName.Contains("login"))
        {

            var phonenumber = funcParams.Split("=");
            var ActualPhoneNumber = "";
            if (phonenumber.Length == 2)
            {

                ActualPhoneNumber = Regex.Replace(phonenumber[1].Replace("%20", "").ToString(), @"[^\d]", "");
            }

            var response = _authService.GetMobileLicanceInfo(ActualPhoneNumber);
            if (response.status == 200)
            {

                PhoneGlobal = ActualPhoneNumber;
                string maui = "setmobiledata('" + "OK" + "')"; // this is function to set global return data

                await CustomWebView.EvaluateJavaScriptAsync(maui); // then run the script
            }

            else
            {
                await DisplayAlert("Error", response.message, "OK");
            }



        }

    }

}

Then your Mainpage.xaml file:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DinamoMobile.MainPage">
   <WebView Navigating="WebViewNavigation" x:Name="CustomWebView">


   </WebView>
</ContentPage>

After all that coding usage must be like:

<script>const login = document.querySelector(".js-login-btnCls");
    login.addEventListener("click", logincallapi);
    async function logincallapi() {


        var phone = $("#phone").val();
        if (phone == "") {
            alert("Phone is required");
            return;
        }

        var isOK = await mobileajax("login", "phone=" + phone);

        if (isOK == "OK") {
            window.location.href = "verification.html";
        }
        else {
            alert("Invalid Phone Number.");
        }


    }</script>

Algorithm:

  1. Write an asynchronous function that waits for data on the javascript side.
  2. Go with the URL of the desired function on the Javascript side.
  3. Set data to global variable with EvulateJavascript function.
  4. The function waiting for the data will continue in this way.
Berk Ayvaz
  • 17
  • 3