Is it possible to use WebView2 on HoloLens2 with Xamarin ?

Guillaume Dewaghe 21 Reputation points
2023-06-07T10:02:52.72+00:00

I'm working on a cross platform application on Xamarin and more particularly on the UWP side with the HoloLens2.
Since some months ago, some web pages doesn't display (infinite load) with WebView.
From what I understand, the WebView which is use on UWP is based on the Edge pre-Chronium so it can explain why those pages doesn't load anymore.
In order to fix that, I try to change the renderer from Windows.UI.Xaml.Controls.WebView into WebView2 by using Microsoft.UI.Xaml.Controls.
I did some tests on PC and everything works fine but when I tested it on HoloLens2, I had a error while WebView2 initializes CoreWebView2 :
=> Class not registered
Is it possible to have WebView2 working on HoloLens2 with Xamarin ?
There is my code of the renderer for WebView2 on UWP

using System;
using System.ComponentModel;
using SvMobile.CustomControls;
using SvMobile.Lib.UWP.Renderers;
using SvMobile.Pages.WebVue;
using Xamarin.Forms;
using Xamarin.Forms.Platform.UWP;
using System.Collections.Generic;
using Size = Xamarin.Forms.Size;
using WebView2 = Microsoft.UI.Xaml.Controls.WebView2;
using Net.Shared.Helpers;

[assembly: ExportRenderer(typeof(SvWebView), typeof(SvWebViewRenderer))]
namespace SvMobile.Lib.UWP.Renderers
{
    /// <summary>
    /// Code is partially taken from:
    /// https://github.com/XLabs/Xamarin-Forms-Labs/wiki/HybridWebView
    /// </summary>
    public class SvWebViewRenderer : ViewRenderer<SvWebView, WebView2>
    {
        private bool disposed = false;

        public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
        {
            return new SizeRequest(Size.Zero, Size.Zero);
        }

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

            if (e.OldElement != null)
            {
                Unbind(e.OldElement);
                HandleCleanup();
            }

            if (e.NewElement == null)
                return;

            if (Control == null)
            {
                var webView = new WebView2();
                await webView.EnsureCoreWebView2Async();
                SetNativeControl(webView);
            }

            Bind();
        }

        private void Bind()
        {
            if (Element == null) return;
            if (Element.Uri != null)
            {
                Load(Element.Uri);
            }
            else
            {
                LoadSource();
            }

            // There should only be one renderer and thus only one event handler registered.
            // Otherwise, when Xamarin creates a new renderer, the old one stays attached
            // and crashes when called!
            Element.JavaScriptLoadRequested = OnInjectRequest;
            Element.LoadFromContentRequested = LoadFromContent;
            Element.LoadContentRequested = LoadContent;
            //Element.Navigating += OnNavigating;
            Element.PropertyChanged += OnElementPropertyChanged;
        }

        private void Unbind(SvWebView oldElement)
        {
            if (oldElement == null) return;
            oldElement.JavaScriptLoadRequested -= OnInjectRequest;
            oldElement.LoadFromContentRequested -= LoadFromContent;
            oldElement.LoadContentRequested -= LoadContent;
            //oldElement.Navigating -= OnNavigating;
            oldElement.PropertyChanged -= OnElementPropertyChanged;
        }

        private void HandleCleanup()
        {
            if (Control == null)
                return;

            Messager.LogTrace("[SvWebViewRenderer]", $"Stop Loading");
            Control.CoreWebView2.Stop();
            //Control.StopLoading();

            Control.NavigateToString("about:blank");

            Messager.LogTrace("[SvWebViewRenderer]", $"Destroy web view");
            //Control.Dispose(); /// JS: ???? --> Control = null ???
        }

        protected override void Dispose(bool disposing)
        {
            disposed = true;

            /*if (disposing)
            {
                ((SvWebView)Element).Cleanup();
            }*/
            base.Dispose(disposing);
        }

        private async void Load(Uri uri, IDictionary<string, string> extraHeaders = null)
        {
            if (uri == null)
                return;

            if (Control.CoreWebView2 == null)
                await Control.EnsureCoreWebView2Async();

            var requestMsg = new Windows.Web.Http.HttpRequestMessage(Windows.Web.Http.HttpMethod.Get, uri);
            Control.CoreWebView2.AddWebResourceRequestedFilter(uri.OriginalString, Microsoft.Web.WebView2.Core.CoreWebView2WebResourceContext.All);

            if (extraHeaders != null)
            {
                foreach (var extraHeader in extraHeaders)
                {
                    requestMsg.Headers.Add(extraHeader.Key, extraHeader.Value);
                    Control.CoreWebView2.WebResourceRequested += (obj, args) =>
                    {
                        args.Request.Headers.SetHeader(extraHeader.Key, extraHeader.Value);
                    };
                }
            }

            Device.BeginInvokeOnMainThread(() =>
            {
                if (disposed)
                    return;
                //Control?.NavigateWithHttpRequestMessage(requestMsg);
                Control?.CoreWebView2.Navigate(uri.OriginalString);
                Control?.NavigateToString(uri.OriginalString);
            });
        }

        private void LoadFromString(string html)
        {
            Device.BeginInvokeOnMainThread(() =>
            {
                if (disposed)
                    return;
                Control?.NavigateToString(html);
            });
        }

        private void LoadSource()
        {
            if (Element == null) return;
            if (Element.Source is HtmlWebViewSource htmlSource && htmlSource.Html != null)
            {
                LoadFromString(htmlSource.Html);
                return;
            }
            if (Element.Source is SvUrlWebViewSource svWebViewSource && svWebViewSource.Url != null)
            {
                Load(new Uri(svWebViewSource.Url), svWebViewSource.Headers);
                return;
            }
            if (!(Element.Source is UrlWebViewSource webViewSource)) return;
            Load(new Uri(webViewSource.Url));
        }

        private void LoadFromContent(object sender, string contentFullName)
        {
            //Element.Uri = new Uri(NSBundle.MainBundle.BundlePath + "/" + contentFullName);
        }

        private void LoadContent(object sender, string contentFullName)
        {
            //Control.LoadHtmlString(new NSString(contentFullName), new NSUrl(NSBundle.MainBundle.BundlePath, true));
        }

        private void OnInjectRequest(object sender, string script)
        {
            Inject(script);
        }

        private void Inject(string script)
        {
            //Log.Warn("SvWebViewRenderer", "Inject script: {0}", script);
            Device.BeginInvokeOnMainThread(() =>
            {
                if (disposed)
                    return;

                Control?.NavigateToString($"javascript: {script}");
            });
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Messager.LogTrace("[SvWebViewRenderer]", $"OnElementPropertyChanged; Control={Control != null}; Element={Element != null}");
            base.OnElementPropertyChanged(sender, e);

            // Xamarin will changed the renderer attached to a view so it is possible that
            // an old renderer gets a property updated.  In this case the Element will be null.
            // In that case, try to clear the property event handler and exit.
            if (Element == null)
            {
                if (sender is SvWebView wv) Unbind(wv);
                return;
            }
            if (e.PropertyName == SvWebView.UriProperty.PropertyName)
            {
                Load(Element.Uri);
            }
            else if (e.PropertyName == SvWebView.SourceProperty.PropertyName)
            {
                LoadSource();
            }
        }
    }
}

Thanks in advance and have a good day !

Developer technologies | .NET | Xamarin
Developer technologies | Universal Windows Platform (UWP)
HoloLens | Development
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Rob Caplan - MSFT 6,037 Reputation points Microsoft Employee Moderator
    2023-06-07T21:18:08.08+00:00

    Update 7 June 2023 WebView2 preview is now available in today's Windows Holographic, version 23H1 - June 2023 Update . The getting started links below still link to the latest documentation.


    A WebView2 preview is currently available in HoloLens 2 Insider Previews. WebView2 is not yet in the main HoloLens 2 OS release.

    When it's available in the main release it will be noted in the HoloLens Release Notes

    1 person found this answer helpful.
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.