Skip to content

Instantly share code, notes, and snippets.

@artemious7
Last active February 1, 2020 18:30
Show Gist options
  • Save artemious7/bddb0b3f3d842032cffe5c3fae68d590 to your computer and use it in GitHub Desktop.
Save artemious7/bddb0b3f3d842032cffe5c3fae68d590 to your computer and use it in GitHub Desktop.
Xamarin.Forms UWP WebViewRenderer bug fix (issue #6679)
// for more info see https://stackoverflow.com/a/60019880/1830814
// this is a fix for Xamarin.Forms v4.4.0-sr3 and prior versions
using System;
using System.ComponentModel;
using Windows.UI.Core;
using Windows.UI.Xaml.Controls;
using Xamarin.Forms.Internals;
using static System.String;
using Xamarin.Forms.PlatformConfiguration.WindowsSpecific;
using System.Threading.Tasks;
namespace Xamarin.Forms.Platform.UWP
{
public class WebViewRenderer_fix : ViewRenderer<WebView, Windows.UI.Xaml.Controls.WebView>, IWebViewDelegate
{
WebNavigationEvent _eventState;
bool _updating;
private Windows.UI.Xaml.Controls.WebView _internalWebView;
const string LocalScheme = "ms-appx-web:///";
// Script to insert a <base> tag into an HTML document
const string BaseInsertionScript = @"
var head = document.getElementsByTagName('head')[0];
var bases = head.getElementsByTagName('base');
if(bases.length == 0){
head.innerHTML = 'baseTag' + head.innerHTML;
}";
public void LoadHtml(string html, string baseUrl)
{
if (IsNullOrEmpty(baseUrl))
{
baseUrl = LocalScheme;
}
// Generate a base tag for the document
var baseTag = $"<base href=\"{baseUrl}\"></base>";
string htmlWithBaseTag;
// Set up an internal WebView we can use to load and parse the original HTML string
_internalWebView = new Windows.UI.Xaml.Controls.WebView();
// When the 'navigation' to the original HTML string is done, we can modify it to include our <base> tag
_internalWebView.NavigationCompleted += async (sender, args) =>
{
// Generate a version of the <base> script with the correct <base> tag
var script = BaseInsertionScript.Replace("baseTag", baseTag);
// Run it and retrieve the updated HTML from our WebView
await sender.InvokeScriptAsync("eval", new[] { script });
htmlWithBaseTag = await sender.InvokeScriptAsync("eval", new[] { "document.documentElement.outerHTML;" });
// Set the HTML for the 'real' WebView to the updated HTML
Control.NavigateToString(!IsNullOrEmpty(htmlWithBaseTag) ? htmlWithBaseTag : html);
};
// Kick off the initial navigation
_internalWebView.NavigateToString(html);
}
public void LoadUrl(string url)
{
Uri uri = new Uri(url, UriKind.RelativeOrAbsolute);
if (!uri.IsAbsoluteUri)
{
uri = new Uri(LocalScheme + url, UriKind.RelativeOrAbsolute);
}
Control.Source = uri;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (Control != null)
{
Control.NavigationStarting -= OnNavigationStarted;
Control.NavigationCompleted -= OnNavigationCompleted;
Control.NavigationFailed -= OnNavigationFailed;
Control.ScriptNotify -= OnScriptNotify;
}
}
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
var oldElement = e.OldElement;
oldElement.EvalRequested -= OnEvalRequested;
oldElement.EvaluateJavaScriptRequested -= OnEvaluateJavaScriptRequested;
oldElement.GoBackRequested -= OnGoBackRequested;
oldElement.GoForwardRequested -= OnGoForwardRequested;
oldElement.ReloadRequested -= OnReloadRequested;
}
if (e.NewElement != null)
{
if (Control == null)
{
var webView = new Windows.UI.Xaml.Controls.WebView();
webView.NavigationStarting += OnNavigationStarted;
webView.NavigationCompleted += OnNavigationCompleted;
webView.NavigationFailed += OnNavigationFailed;
webView.ScriptNotify += OnScriptNotify;
SetNativeControl(webView);
}
var newElement = e.NewElement;
newElement.EvalRequested += OnEvalRequested;
newElement.EvaluateJavaScriptRequested += OnEvaluateJavaScriptRequested;
newElement.GoForwardRequested += OnGoForwardRequested;
newElement.GoBackRequested += OnGoBackRequested;
newElement.ReloadRequested += OnReloadRequested;
Load();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == WebView.SourceProperty.PropertyName)
{
if (!_updating)
Load();
}
}
void Load()
{
if (Element.Source != null)
Element.Source.Load(this);
UpdateCanGoBackForward();
}
async void OnEvalRequested(object sender, EvalRequested eventArg)
{
await Control.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => await Control.InvokeScriptAsync("eval", new[] { eventArg.Script }));
}
async Task<string> OnEvaluateJavaScriptRequested(string script)
{
return await Control.InvokeScriptAsync("eval", new[] { script });
}
void OnGoBackRequested(object sender, EventArgs eventArgs)
{
if (Control.CanGoBack)
{
_eventState = WebNavigationEvent.Back;
Control.GoBack();
}
UpdateCanGoBackForward();
}
void OnGoForwardRequested(object sender, EventArgs eventArgs)
{
if (Control.CanGoForward)
{
_eventState = WebNavigationEvent.Forward;
Control.GoForward();
}
UpdateCanGoBackForward();
}
void OnReloadRequested(object sender, EventArgs eventArgs)
{
Control.Refresh();
}
async void OnNavigationCompleted(Windows.UI.Xaml.Controls.WebView sender, WebViewNavigationCompletedEventArgs e)
{
if (e.Uri != null)
SendNavigated(new UrlWebViewSource { Url = e.Uri.AbsoluteUri }, _eventState, WebNavigationResult.Success);
UpdateCanGoBackForward();
if (Element.OnThisPlatform().IsJavaScriptAlertEnabled())
await Control.InvokeScriptAsync("eval", new string[] { "window.alert = function(message){ window.external.notify(message); };" });
}
void OnNavigationFailed(object sender, WebViewNavigationFailedEventArgs e)
{
if (e.Uri != null)
SendNavigated(new UrlWebViewSource { Url = e.Uri.AbsoluteUri }, _eventState, WebNavigationResult.Failure);
}
void OnNavigationStarted(Windows.UI.Xaml.Controls.WebView sender, WebViewNavigationStartingEventArgs e)
{
Uri uri = e.Uri;
if (uri != null)
{
var args = new WebNavigatingEventArgs(_eventState, new UrlWebViewSource { Url = uri.AbsoluteUri }, uri.AbsoluteUri);
Element.SendNavigating(args);
e.Cancel = args.Cancel;
// reset in this case because this is the last event we will get
if (args.Cancel)
_eventState = WebNavigationEvent.NewPage;
}
}
async void OnScriptNotify(object sender, NotifyEventArgs e)
{
if (Element.OnThisPlatform().IsJavaScriptAlertEnabled())
await new Windows.UI.Popups.MessageDialog(e.Value).ShowAsync();
}
void SendNavigated(UrlWebViewSource source, WebNavigationEvent evnt, WebNavigationResult result)
{
_updating = true;
((IElementController)Element).SetValueFromRenderer(WebView.SourceProperty, source);
_updating = false;
Element.SendNavigated(new WebNavigatedEventArgs(evnt, source, source.Url, result));
UpdateCanGoBackForward();
_eventState = WebNavigationEvent.NewPage;
}
void UpdateCanGoBackForward()
{
((IWebViewController)Element).CanGoBack = Control.CanGoBack;
((IWebViewController)Element).CanGoForward = Control.CanGoForward;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment