Last active
February 1, 2020 18:30
-
-
Save artemious7/bddb0b3f3d842032cffe5c3fae68d590 to your computer and use it in GitHub Desktop.
Xamarin.Forms UWP WebViewRenderer bug fix (issue #6679)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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