Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Remove UIWebView completely #12095

Merged
merged 2 commits into from
Sep 16, 2020
Merged

Remove UIWebView completely #12095

merged 2 commits into from
Sep 16, 2020

Conversation

PureWeen
Copy link
Contributor

@PureWeen PureWeen commented Sep 11, 2020

Description of Change

Any application referencing UIWebView will get rejected from the store. Even though our linker takes care of this we want to remove UIWebView for cases where linking isn't enable and to remove any doubt from users if they feel that the errant UIWebView is coming from Xamarin.Forms

API Changes

  • removed Xamarin.Forms.Platform.iOS.WebViewRenderer

Platforms Affected

  • iOS

Behavioral/Visual Changes

If users have to use UIWebView they can take the removed WebViewRenderer here and add it to their application

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Foundation;
using UIKit;
using Xamarin.Forms.Internals;
using Uri = System.Uri;
using Xamarin.Forms.Platform.iOS;
using Xamarin.Forms;

[assembly: Xamarin.Forms.ExportRenderer(typeof(WebView), typeof(WebViewRenderer))]
namespace Xamarin.Forms.Platform.iOS
{
	public class WebViewRenderer : UIWebView, IVisualElementRenderer, IWebViewDelegate, IEffectControlProvider, ITabStop
	{
		EventTracker _events;
		bool _ignoreSourceChanges;
		WebNavigationEvent _lastBackForwardEvent;
		VisualElementPackager _packager;
#pragma warning disable 0414
		VisualElementTracker _tracker;
#pragma warning restore 0414

		[Internals.Preserve(Conditional = true)]
		public WebViewRenderer() : base(RectangleF.Empty)
		{
		}

		WebView WebView => Element as WebView;

		public VisualElement Element { get; private set; }

		public event EventHandler<VisualElementChangedEventArgs> ElementChanged;

		public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
		{
			return NativeView.GetSizeRequest(widthConstraint, heightConstraint, 44, 44);
		}

		public void SetElement(VisualElement element)
		{
			var oldElement = Element;
			Element = element;
			Element.PropertyChanged += HandlePropertyChanged;
			WebView.EvalRequested += OnEvalRequested;
			WebView.EvaluateJavaScriptRequested += OnEvaluateJavaScriptRequested;
			WebView.GoBackRequested += OnGoBackRequested;
			WebView.GoForwardRequested += OnGoForwardRequested;
			WebView.ReloadRequested += OnReloadRequested;
			Delegate = new CustomWebViewDelegate(this);

			BackgroundColor = UIColor.Clear;

			AutosizesSubviews = true;

			_tracker = new VisualElementTracker(this);

			_packager = new VisualElementPackager(this);
			_packager.Load();

			_events = new EventTracker(this);
			_events.LoadEvents(this);

			Load();

			OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));

			EffectUtilities.RegisterEffectControlProvider(this, oldElement, element);

			if (Element != null && !string.IsNullOrEmpty(Element.AutomationId))
				AccessibilityIdentifier = Element.AutomationId;
		}

		public void SetElementSize(Size size)
		{
			Layout.LayoutChildIntoBoundingRegion(Element, new Rectangle(Element.X, Element.Y, size.Width, size.Height));
		}

		public void LoadHtml(string html, string baseUrl)
		{
			if (html != null)
				LoadHtmlString(html, baseUrl == null ? new NSUrl(NSBundle.MainBundle.BundlePath, true) : new NSUrl(baseUrl, true));
		}

		public void LoadUrl(string url)
		{
			var uri = new Uri(url);
			var safeHostUri = new Uri($"{uri.Scheme}://{uri.Authority}", UriKind.Absolute);
			var safeRelativeUri = new Uri($"{uri.PathAndQuery}{uri.Fragment}", UriKind.Relative);
			LoadRequest(new NSUrlRequest(new Uri(safeHostUri, safeRelativeUri)));
		}

		public override void LayoutSubviews()
		{
			base.LayoutSubviews();

			// ensure that inner scrollview properly resizes when frame of webview updated
			ScrollView.Frame = Bounds;
		}

		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (IsLoading)
					StopLoading();

				Element.PropertyChanged -= HandlePropertyChanged;
				WebView.EvalRequested -= OnEvalRequested;
				WebView.EvaluateJavaScriptRequested -= OnEvaluateJavaScriptRequested;
				WebView.GoBackRequested -= OnGoBackRequested;
				WebView.GoForwardRequested -= OnGoForwardRequested;
				WebView.ReloadRequested -= OnReloadRequested;

				_tracker?.Dispose();
				_packager?.Dispose();
			}

			base.Dispose(disposing);
		}

		protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
		{
			var changed = ElementChanged;
			if (changed != null)
				changed(this, e);
		}

		void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == WebView.SourceProperty.PropertyName)
				Load();
		}

		void Load()
		{
			if (_ignoreSourceChanges)
				return;

			if (((WebView)Element).Source != null)
				((WebView)Element).Source.Load(this);

			UpdateCanGoBackForward();
		}

		void OnEvalRequested(object sender, EvalRequested eventArg)
		{
			EvaluateJavascript(eventArg.Script);
		}

		async Task<string> OnEvaluateJavaScriptRequested(string script)
		{
			var tcr = new TaskCompletionSource<string>();
			var task = tcr.Task;

			Device.BeginInvokeOnMainThread(() => { tcr.SetResult(EvaluateJavascript(script)); });

			return await task.ConfigureAwait(false);
		}

		void OnGoBackRequested(object sender, EventArgs eventArgs)
		{
			if (CanGoBack)
			{
				_lastBackForwardEvent = WebNavigationEvent.Back;
				GoBack();
			}

			UpdateCanGoBackForward();
		}

		void OnGoForwardRequested(object sender, EventArgs eventArgs)
		{
			if (CanGoForward)
			{
				_lastBackForwardEvent = WebNavigationEvent.Forward;
				GoForward();
			}

			UpdateCanGoBackForward();
		}

		void OnReloadRequested(object sender, EventArgs eventArgs)
		{
			Reload();
		}

		void UpdateCanGoBackForward()
		{
			((IWebViewController)WebView).CanGoBack = CanGoBack;
			((IWebViewController)WebView).CanGoForward = CanGoForward;
		}

		class CustomWebViewDelegate : UIWebViewDelegate
		{
			readonly WebViewRenderer _renderer;
			WebNavigationEvent _lastEvent;

			public CustomWebViewDelegate(WebViewRenderer renderer)
			{
				if (renderer == null)
					throw new ArgumentNullException("renderer");
				_renderer = renderer;
			}

			WebView WebView
			{
				get { return (WebView)_renderer.Element; }
			}

			public override void LoadFailed(UIWebView webView, NSError error)
			{
				var url = GetCurrentUrl();
				WebView.SendNavigated(new WebNavigatedEventArgs(_lastEvent, new UrlWebViewSource { Url = url }, url, WebNavigationResult.Failure));

				_renderer.UpdateCanGoBackForward();
			}

			public override void LoadingFinished(UIWebView webView)
			{
				if (webView.IsLoading)
					return;

				var url = GetCurrentUrl();

				if (url == $"file://{NSBundle.MainBundle.BundlePath}/")
					return;

				_renderer._ignoreSourceChanges = true;
				WebView.SetValueFromRenderer(WebView.SourceProperty, new UrlWebViewSource { Url = url });
				_renderer._ignoreSourceChanges = false;

				var args = new WebNavigatedEventArgs(_lastEvent, WebView.Source, url, WebNavigationResult.Success);
				WebView.SendNavigated(args);

				_renderer.UpdateCanGoBackForward();
			}

			public override void LoadStarted(UIWebView webView)
			{
			}

			public override bool ShouldStartLoad(UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType)
			{
				var navEvent = WebNavigationEvent.NewPage;
				switch (navigationType)
				{
					case UIWebViewNavigationType.LinkClicked:
						navEvent = WebNavigationEvent.NewPage;
						break;
					case UIWebViewNavigationType.FormSubmitted:
						navEvent = WebNavigationEvent.NewPage;
						break;
					case UIWebViewNavigationType.BackForward:
						navEvent = _renderer._lastBackForwardEvent;
						break;
					case UIWebViewNavigationType.Reload:
						navEvent = WebNavigationEvent.Refresh;
						break;
					case UIWebViewNavigationType.FormResubmitted:
						navEvent = WebNavigationEvent.NewPage;
						break;
					case UIWebViewNavigationType.Other:
						navEvent = WebNavigationEvent.NewPage;
						break;
				}

				_lastEvent = navEvent;
				var lastUrl = request.Url.ToString();
				var args = new WebNavigatingEventArgs(navEvent, new UrlWebViewSource { Url = lastUrl }, lastUrl);

				var jCookies = WebView.Cookies?.GetCookies(request.Url);

				if (jCookies != null)
				{
					// Set cookies here
					var cookieJar = NSHttpCookieStorage.SharedStorage;
					cookieJar.AcceptPolicy = NSHttpCookieAcceptPolicy.Always;

					//clean up old cookies
					foreach (var aCookie in cookieJar.Cookies)
					{
						cookieJar.DeleteCookie(aCookie);
					}

					//set up the new cookies
					if (WebView.Cookies != null)
					{
						IList<NSHttpCookie> eCookies =
							(from object jCookie in jCookies
							 where jCookie != null
							 select (Cookie)jCookie
							 into netCookie
							 select new NSHttpCookie(netCookie)).ToList();

						cookieJar.SetCookies(eCookies.ToArray(), request.Url, request.Url);
					}
				}

				WebView.SendNavigating(args);
				_renderer.UpdateCanGoBackForward();
				return !args.Cancel;
			}

			string GetCurrentUrl()
			{
				return _renderer?.Request?.Url?.AbsoluteUrl?.ToString();
			}
		}

		#region IPlatformRenderer implementation

		public UIView NativeView
		{
			get { return this; }
		}

		public UIViewController ViewController
		{
			get { return null; }
		}

		UIView ITabStop.TabStop => this;

		#endregion

		void IEffectControlProvider.RegisterEffect(Effect effect)
		{
			VisualElementRenderer<VisualElement>.RegisterEffect(effect, this, NativeView);
		}
	}
}

Testing Procedure

  • make sure ui tests pass and code compiles
  • Make sure all references to UIWebView have in fact been removed

PR Checklist

  • Targets the correct branch
  • Tests are passing (or failures are unrelated)

Copy link

@spouliot spouliot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😺

@samhouts samhouts added breaking Changes behavior or appearance a/webview Core labels Sep 14, 2020
@PureWeen PureWeen merged commit 3fce598 into 5.0.0 Sep 16, 2020
@PureWeen PureWeen deleted the remove_uiwebview branch September 16, 2020 14:28
@samhouts samhouts added this to the 5.0.0 milestone Sep 19, 2020
@samhouts samhouts added breaking Changes behavior or appearance API-change Heads-up to reviewers that this PR may contain an API change deprecation Public API has been deprecated m/high impact ⬛ proposal-open t/enhancement ➕ roadmap and removed breaking Changes behavior or appearance labels Sep 22, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a/webview API-change Heads-up to reviewers that this PR may contain an API change breaking Changes behavior or appearance Core deprecation Public API has been deprecated m/high impact ⬛ p/iOS 🍎 proposal-open roadmap t/enhancement ➕
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Spec] Major Breaking Changes Proposed for Xamarin.Forms 5.0
5 participants