2

Currently I am developing a mobile app which makes use auf the UNO platform. For showing a 3D model I make use of the WebView control and some ThreeJS JavaScript code. This works quite well with UWP and also with WASM. (The Workaround for WebView in WASM build can also be found here on StackOverflow.) However, the Android build makes me getting a headache: The HTML page loads, the debug output shows me that the WebGL script parts are being executed, but the user interface does not show anything.

I already made sure that (in my opinion) the correct WebView control is used. In addition, I tested with an alternative WebView from "Chrome" which I downloaded from the "Play Store" (Version 74.x.x.x). The WebView's HTTP user agent is correct, so the WebView is used according to the Android system's developer settings. Hardware accelaration is set to "TRUE" in both, the activity and in the AndroidManifest file.

I also made a very simple test with an "Android-XAML-App" which is working out of the box. The cube is shown almost instantly.

MainView.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:WebViewTestAndroid"
             x:Class="WebViewTestAndroid.MainPage">
    <Grid>
        <WebView Source="https://threejs.org/examples/?q=cube#webgl_geometry_cube" />        
    </Grid>
</ContentPage>

This is an example of the UNO main page which does not work. The page is loading but the 3D cube is not shown.

MainPage.xaml

<Page
    x:Class="UnoWebViewTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UnoWebViewTest"
    xmlns:ios="http://uno.ui/ios"
    xmlns:android="http://uno.ui/android"
    xmlns:xamarin="http://uno.ui/xamarin"
    xmlns:wasm="http://uno.ui/wasm"
    mc:Ignorable="d ios android xamarin wasm"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <android:WebView Source="https://threejs.org/examples/?q=cube#webgl_geometry_cube" />
    </Grid>
</Page>

I compared the manifest files but so far I did not come to a conclusion I could investigate any further. I would expect that UNO is using the native Android WebView control, but maybe there is missing some setting or it isn't even the same control?

Rubyist
  • 6,486
  • 10
  • 51
  • 86
Syntaxrabbit
  • 126
  • 8

2 Answers2

4

Indeed this is due to Uno's WebView disabling hardware acceleration. Apart from this, Uno and Xamarin.Forms are similarly using Android.WebKit.WebView under the hood.

The hardware acceleration can be reenabled in Uno easily using an attached property:

    public static class WebViewHardware
    {
        public static bool GetIsEnabled(WebView obj)
        {
            return (bool)obj.GetValue(IsEnabledProperty);
        }

        public static void SetIsEnabled(WebView obj, bool value)
        {
            obj.SetValue(IsEnabledProperty, value);
        }

        // Using a DependencyProperty as the backing store for IsEnabled.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsEnabledProperty =
            DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(WebViewHardware), new PropertyMetadata(false, OnIsEnabledChanged));

        private static void OnIsEnabledChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
        {
            var webView = (WebView)dependencyObject;

            var isEnabled = (bool)args.NewValue;

#if __ANDROID__
            webView.Loaded += OnLoaded;

            void OnLoaded(object sender, RoutedEventArgs e)
            {
                webView.Loaded -= OnLoaded;
                var layerType = isEnabled ? Android.Views.LayerType.Hardware : Android.Views.LayerType.Software;

                webView.SetLayerType(layerType, null);
            }
#endif
        }

This can then be used in XAML like so:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <WebView Source="https://threejs.org/examples/?q=cube#webgl_geometry_cube" local:WebViewHardware.IsEnabled="True"/>
</Grid>
David Oliver
  • 2,251
  • 12
  • 12
1

This problem started driving me nuts, so I spent all day downloading the complete Xamarin and UNO source and debugging all the things from scratch.

First of all, UNO sets "HardwareAcceleration" to false for the WebView control. Second, the missing clue is this line:

view.LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);

The problem is, that it is not really possible to set this from outside, so we create a new class inheriting from Android.Webkit.WebView:

public partial class MyCustomWebView : Android.Webkit.WebView
{
    protected MyCustomWebView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
    {
        MakeSettings();
    }

    public MyCustomWebView(Context context) : base(context)
    {
        MakeSettings();
    }

    public MyCustomWebView(Context context, IAttributeSet attrs) : base(context, attrs)
    {
        MakeSettings();
    }

    public MyCustomWebView(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
    {
        MakeSettings();
    }

    public MyCustomWebView(Context context, IAttributeSet attrs, int defStyleAttr, bool privateBrowsing) : base(context, attrs, defStyleAttr, privateBrowsing)
    {
        MakeSettings();
    }

    public MyCustomWebView(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes)
    {
        MakeSettings();
    }

    private void MakeSettings()
    {
        SetLayerType(LayerType.Hardware, null);
        ForceHasOverlappingRendering(true);

        SetWebViewClient(new MyCustomWebViewClient(this));
        SetWebChromeClient(new WebChromeClient());

        Settings.AllowFileAccessFromFileURLs = true;
        Settings.AllowUniversalAccessFromFileURLs = true;
        Settings.JavaScriptEnabled = true;
        Settings.DomStorageEnabled = true;
        Settings.AllowFileAccess = true;
        Settings.CacheMode = CacheModes.NoCache;
        Settings.MediaPlaybackRequiresUserGesture = false;
        Settings.SetPluginState(WebSettings.PluginState.On);
    }


    public string HtmlContent
    {
        get { return string.Empty; }
        set { LoadUrl(value); }
    }
}

public class MyCustomWebViewClient : Android.Webkit.WebViewClient
{
    public MyCustomWebViewClient(WebView view)
    {
        var test = view.IsHardwareAccelerated;
        view.SetLayerType(LayerType.Hardware, null);
    }

    public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
    {
        view.LoadUrl(request.Url.ToString());
        return true;
    }

    public override void OnPageStarted(WebView view, string url, Bitmap favicon)
    {
        // The native webview control requires to have LayoutParameters to function properly.
        view.LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);

        base.OnPageStarted(view, url, favicon);
    }
}

Using this custom control in XAML makes it possible to use hardware accelerated WebGL in the WebView:

<android:MyCustomWebView HtmlContent="https://threejs.org/examples/?q=cube#webgl_geometry_cube" />
Syntaxrabbit
  • 126
  • 8