xamarin.formswebviewxamarin.androidwebviewclient

Xamarin.Forms WebView: e.Cancel = true in Navigating event is not working on Android when using custom WebViewRenderer


I want to redirect opened URLs to an external browser in my WebView, so I hooked its Navigated event:

webView.Navigating += (s, e) =>
{
    if (IsExternalUrl(e.Url))
    {
        try
        {
            var uri = new Uri(e.Url);
            Device.OpenUri(uri);        // show external links in external browser
        }
        catch (Exception)
        {
        }
            
        e.Cancel = true;   // <- not having any effects on Android
    }
};

Under Android, this leads to the URL being opened on Chrome and in the WebView at the same time. On iOS e.Cancel = true does work as expected.

I searched extensively on the web but found nothing that helped me, including this Xamarin forum thread: https://forums.xamarin.com/discussion/144314/using-webviewrenderer-and-webviewclient-causes-cancel-navigation-not-working

I now have a workaround, using the back navigation:

XAML:

<local:HybridWebView x:Name="webView" CanGoBack="True"  WidthRequest="1000" HeightRequest="1000" />

Code behind:

webView.Navigated += (s, e) =>
{
    if (e.Result == WebNavigationResult.Success)
    {
        if (Device.RuntimePlatform == Device.Android)  // on Android, prohibit webview from mirroring urls 
            if (IsExternalUrl(e.Url))                  // that are shown on external browser
                webView.GoBack();                      // this is necessary because e.Cancel = true doesn't 
    }                                                  // work in webView.Navigating() event
};

Note: Initially, the Navigated event wasn't firing, so I pimped my HybridWebView with a WebViewClient featuring this override:

public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
    RaisePageFinishedEvent(url, view.Title);

    ...
    ...

    var source = new UrlWebViewSource { Url = url };
    var args = new WebNavigatedEventArgs(WebNavigationEvent.NewPage, source, url, WebNavigationResult.Success);
    _renderer.ElementController.SendNavigated(args);
}

I'm currently using Xamarin.Forms 4.5.

Pressing the back button programmatically is a really crude workaround. So a solution where the url open event is actually canceled is much appreciated.

Update:

I had to remove CanGoBack="True" in the XAML and hard-code the property in OnElementChanged of my WebViewRenderer:

(Element as IWebViewController).CanGoBack = true;

Setting the CanGoBack property in XAML worked fine while the VS 2019 debugger was running attached but if running stand-alone, the app shut down immediately. Can be reproduced on a simple WebView without custom renderer:

ApplyPropertiesVisitor.SetPropertyValue (System.Object xamlelement, Xamarin.Forms.Xaml.XmlName propertyName, System.Object value, System.Object rootElement, Xamarin.Forms.Xaml.INode node, Xamarin.Forms.Xaml.HydrationContext context, System.Xml.IXmlLineInfo lineInfo)
ApplyPropertiesVisitor.Visit (Xamarin.Forms.Xaml.ValueNode node, Xamarin.Forms.Xaml.INode parentNode)
ValueNode.Accept (Xamarin.Forms.Xaml.IXamlNodeVisitor visitor, Xamarin.Forms.Xaml.INode parentNode)
ElementNode.Accept (Xamarin.Forms.Xaml.IXamlNodeVisitor visitor, Xamarin.Forms.Xaml.INode parentNode)
ElementNode.Accept (Xamarin.Forms.Xaml.IXamlNodeVisitor visitor, Xamarin.Forms.Xaml.INode parentNode)
RootNode.Accept (Xamarin.Forms.Xaml.IXamlNodeVisitor visitor, Xamarin.Forms.Xaml.INode parentNode)
XamlLoader.Visit (Xamarin.Forms.Xaml.RootNode rootnode, Xamarin.Forms.Xaml.HydrationContext visitorContext, System.Boolean useDesignProperties)
XamlLoader.Load (System.Object view, System.String xaml, System.Reflection.Assembly rootAssembly, System.Boolean useDesignProperties)
XamlLoader.Load (System.Object view, System.String xaml, System.Boolean useDesignProperties)
XamlLoader.Load (System.Object view, System.Type callingType)
Extensions.LoadFromXaml[TXaml] (TXaml view, System.Type callingType)
WebPageCollabora.InitializeComponent ()
CloudplanMobileClient.WebPageCollabora..ctor (System.String url) [0x00031] in <6b79d357cd4641c5bd9a69278958d871>:0
WebPage+<>c__DisplayClass9_0.<OpenNewPage>b__0 ()
Thread+RunnableImplementor.Run ()
IRunnableInvoker.n_Run (System.IntPtr jnienv, System.IntPtr native__this)
(wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.27(intptr,intptr)

Solution

  • I was wondering why the bug obviously has been fixed in 2020 and still I was experiencing the problem. I then noticed that the fix was applied only to FormsWebViewClient but not to the native Xamarin.Android class WebViewClient. I solved the issue just by deriving my web client used in my HybridWebViewRenderer from FormsWebViewClient instead of WebViewClient and modified the constructor a bit:

    using Xamarin.Forms.Platform.Android;
    
    ...
    
    namespace MyApp.Droid
    {    
        // note: class was derived from 'WebViewClient' before
        public class JavascriptWebViewClient : FormsWebViewClient  
        {
            HybridWebViewRenderer _renderer;
            string _javascript;
    
            // note: now also calling base class constructor with renderer as parameter
            public JavascriptWebViewClient(string javascript, HybridWebViewRenderer renderer) : base(renderer)
            {
                _javascript = javascript;
                _renderer = renderer ?? throw new ArgumentNullException("renderer");
            }
    
        ...
    
        }
    }