зеркало из https://github.com/DeGsoft/maui-linux.git
[iOS] Add JavaScript dialog delegate to WkWebView (#4254)
* basic implementation of js alerts for wkwebview * add ui test and adjust to mimic uiwebview alerts * fix proj file * rename navigation delegate * Add WkWebView Gallery * input cancel should return false; dedup logic * Load correct galley for WkWebView Co-Authored-By: alanag13 <alan.grgic@gmail.com> * ensure wkwebview and webview galleries render similarly * corrections - Fixes #4253
This commit is contained in:
Родитель
288c73d3f4
Коммит
63b371422d
|
@ -42,3 +42,4 @@ using Xamarin.Forms.Controls;
|
|||
// Deliberately broken image source and handler so we can test handling of image loading errors
|
||||
[assembly: ExportImageSourceHandler(typeof(FailImageSource), typeof(BrokenImageSourceHandler))]
|
||||
[assembly: ExportRenderer(typeof(WkWebView), typeof(Xamarin.Forms.Platform.iOS.WkWebViewRenderer))]
|
||||
|
||||
|
|
|
@ -29,6 +29,10 @@ namespace Xamarin.Forms.Controls.Issues
|
|||
var buttonState = new Button() { Text = "?", BackgroundColor = Color.LightBlue, AutomationId = "buttonState" };
|
||||
var buttonStop = new Button() { Text = "X", BackgroundColor = Color.LightBlue, AutomationId = "buttonStop" };
|
||||
|
||||
var buttonJsAlert = new Button() { Text = "ALERT", BackgroundColor = Color.LightBlue, AutomationId = "buttonJsAlert" };
|
||||
var buttonJsPrompt = new Button() { Text = "PROMPT", BackgroundColor = Color.LightBlue, AutomationId = "buttonJsPrompt" };
|
||||
var buttonJsConfirm = new Button() { Text = "CONFIRM", BackgroundColor = Color.LightBlue, AutomationId = "buttonJsConfirm" };
|
||||
|
||||
var buttonA = new Button() { Text = "GO", BackgroundColor = Color.LightBlue, AutomationId = "buttonA" };
|
||||
var buttonB = new Button() { Text = "HTML", BackgroundColor = Color.LightBlue, AutomationId = "buttonB" };
|
||||
var buttonC = new Button() { Text = "EVAL", BackgroundColor = Color.LightBlue, AutomationId = "buttonC" };
|
||||
|
@ -52,9 +56,13 @@ namespace Xamarin.Forms.Controls.Issues
|
|||
var entry = new Entry() { AutomationId = "entry" };
|
||||
entry.BackgroundColor = Color.Wheat;
|
||||
|
||||
var jsAlerts = new Grid();
|
||||
jsAlerts.Children.AddHorizontal(new[] { buttonJsAlert, buttonJsPrompt, buttonJsConfirm });
|
||||
|
||||
var buttons = new Grid();
|
||||
buttons.Children.AddVertical(vcr);
|
||||
buttons.Children.AddVertical(evals);
|
||||
buttons.Children.AddVertical(jsAlerts);
|
||||
buttons.Children.AddVertical(entry);
|
||||
|
||||
var console = new Label()
|
||||
|
@ -108,6 +116,10 @@ namespace Xamarin.Forms.Controls.Issues
|
|||
log($"Source: {webView.Source.ToString()}");
|
||||
};
|
||||
|
||||
buttonJsAlert.Clicked += async (s, e) => { await webView.EvaluateJavaScriptAsync("alert('foo')"); };
|
||||
buttonJsPrompt.Clicked += async (s, e) => { log($"{await webView.EvaluateJavaScriptAsync("prompt('enter something:')")} was enterred"); };
|
||||
buttonJsConfirm.Clicked += async (s, e) => { log($"{await webView.EvaluateJavaScriptAsync("confirm('choose')")} was chosen"); };
|
||||
|
||||
bool navigating = false;
|
||||
webView.Navigating += (s, e) =>
|
||||
{
|
||||
|
|
|
@ -324,6 +324,7 @@ namespace Xamarin.Forms.Controls
|
|||
new GalleryPageFactory(() => new TableViewCoreGalleryPage(), "TableView Gallery"),
|
||||
new GalleryPageFactory(() => new TimePickerCoreGalleryPage(), "TimePicker Gallery"),
|
||||
new GalleryPageFactory(() => new WebViewCoreGalleryPage(), "WebView Gallery"),
|
||||
new GalleryPageFactory(() => new WkWebViewCoreGalleryPage(), "WkWebView Gallery"),
|
||||
//pages
|
||||
new GalleryPageFactory(() => new RootContentPage ("Content"), "RootPages Gallery"),
|
||||
new GalleryPageFactory(() => new MasterDetailPageTabletPage(), "MasterDetailPage Tablet Page"),
|
||||
|
|
|
@ -30,7 +30,10 @@ namespace Xamarin.Forms.Controls
|
|||
}
|
||||
);
|
||||
|
||||
const string html = "<html><div class=\"test\"><h2>I am raw html</h2></div></html>";
|
||||
const string html = "<!DOCTYPE html><html>" +
|
||||
"<head><meta name='viewport' content='width=device-width,initial-scale=1.0'></head>" +
|
||||
"<body><div class=\"test\"><h2>I am raw html</h2></div></body></html>";
|
||||
|
||||
var htmlWebViewSourceContainer = new ViewContainer<WebView> (Test.WebView.HtmlWebViewSource,
|
||||
new WebView {
|
||||
Source = new HtmlWebViewSource { Html = html },
|
||||
|
@ -40,9 +43,11 @@ namespace Xamarin.Forms.Controls
|
|||
|
||||
var htmlFileWebSourceContainer = new ViewContainer<WebView> (Test.WebView.LoadHtml,
|
||||
new WebView {
|
||||
Source = new HtmlWebViewSource {
|
||||
Html = @"<html>
|
||||
Source = new HtmlWebViewSource
|
||||
{
|
||||
Html = @"<!DOCTYPE html><html>
|
||||
<head>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1.0'>
|
||||
<link rel=""stylesheet"" href=""default.css"">
|
||||
</head>
|
||||
<body>
|
||||
|
@ -90,8 +95,9 @@ namespace Xamarin.Forms.Controls
|
|||
{
|
||||
Source = new HtmlWebViewSource
|
||||
{
|
||||
Html = @"<html>
|
||||
Html = @"<!DOCTYPE html><html>
|
||||
<head>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1.0'>
|
||||
<link rel=""stylesheet"" href=""default.css"">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
using Xamarin.Forms.CustomAttributes;
|
||||
using Xamarin.Forms.PlatformConfiguration;
|
||||
using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
|
||||
using Xamarin.Forms.PlatformConfiguration.WindowsSpecific;
|
||||
|
||||
namespace Xamarin.Forms.Controls
|
||||
{
|
||||
internal class WkWebViewCoreGalleryPage : CoreGalleryPage<WkWebView>
|
||||
{
|
||||
protected override bool SupportsFocus
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
protected override void InitializeElement (WkWebView element)
|
||||
{
|
||||
element.HeightRequest = 200;
|
||||
|
||||
element.Source = new UrlWebViewSource { Url = "http://xamarin.com/" };
|
||||
}
|
||||
|
||||
protected override void Build (StackLayout stackLayout)
|
||||
{
|
||||
base.Build (stackLayout);
|
||||
|
||||
var urlWebViewSourceContainer = new ViewContainer<WkWebView> (Test.WebView.UrlWebViewSource,
|
||||
new WkWebView {
|
||||
Source = new UrlWebViewSource { Url = "https://www.google.com/" },
|
||||
HeightRequest = 200
|
||||
}
|
||||
);
|
||||
|
||||
const string html = "<!DOCTYPE html><html>" +
|
||||
"<head><meta name='viewport' content='width=device-width,initial-scale=1.0'></head>" +
|
||||
"<body><div class=\"test\"><h2>I am raw html</h2></div></body></html>";
|
||||
|
||||
var htmlWebViewSourceContainer = new ViewContainer<WkWebView> (Test.WebView.HtmlWebViewSource,
|
||||
new WkWebView {
|
||||
Source = new HtmlWebViewSource { Html = html },
|
||||
HeightRequest = 200
|
||||
}
|
||||
);
|
||||
|
||||
var htmlFileWebSourceContainer = new ViewContainer<WkWebView> (Test.WebView.LoadHtml,
|
||||
new WkWebView {
|
||||
Source = new HtmlWebViewSource {
|
||||
Html = @"<!DOCTYPE html><html>
|
||||
<head>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1.0'>
|
||||
<link rel=""stylesheet"" href=""default.css"">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Xamarin.Forms</h1>
|
||||
<p>The CSS and image are loaded from local files!</p>
|
||||
<img src='WebImages/XamarinLogo.png'/>
|
||||
<p><a href=""local.html"">next page</a></p>
|
||||
</body>
|
||||
</html>"
|
||||
},
|
||||
HeightRequest = 200
|
||||
}
|
||||
);
|
||||
|
||||
// NOTE: Currently the ability to programmatically enable/disable mixed content only exists on Android
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
var mixedContentTestPage = "https://mixed-content-test.appspot.com/";
|
||||
|
||||
var mixedContentDisallowedWebView = new WkWebView() { HeightRequest = 1000 };
|
||||
mixedContentDisallowedWebView.On<Android>().SetMixedContentMode(MixedContentHandling.NeverAllow);
|
||||
mixedContentDisallowedWebView.Source = new UrlWebViewSource
|
||||
{
|
||||
Url = mixedContentTestPage
|
||||
};
|
||||
|
||||
var mixedContentAllowedWebView = new WkWebView() { HeightRequest = 1000 };
|
||||
mixedContentAllowedWebView.On<Android>().SetMixedContentMode(MixedContentHandling.AlwaysAllow);
|
||||
mixedContentAllowedWebView.Source = new UrlWebViewSource
|
||||
{
|
||||
Url = mixedContentTestPage
|
||||
};
|
||||
|
||||
var mixedContentDisallowedContainer = new ViewContainer<WkWebView>(Test.WebView.MixedContentDisallowed,
|
||||
mixedContentDisallowedWebView);
|
||||
var mixedContentAllowedContainer = new ViewContainer<WkWebView>(Test.WebView.MixedContentAllowed,
|
||||
mixedContentAllowedWebView);
|
||||
|
||||
Add(mixedContentDisallowedContainer);
|
||||
Add(mixedContentAllowedContainer);
|
||||
}
|
||||
|
||||
|
||||
var jsAlertWebView = new WkWebView
|
||||
{
|
||||
Source = new HtmlWebViewSource
|
||||
{
|
||||
Html = @"<!DOCTYPE html><html>
|
||||
<head>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1.0'>
|
||||
<link rel=""stylesheet"" href=""default.css"">
|
||||
</head>
|
||||
<body>
|
||||
<button onclick=""window.alert('foo');"">Click</button>
|
||||
</body>
|
||||
</html>"
|
||||
},
|
||||
HeightRequest = 200
|
||||
};
|
||||
|
||||
jsAlertWebView.On<Windows>().SetIsJavaScriptAlertEnabled(true);
|
||||
|
||||
var javascriptAlertWebSourceContainer = new ViewContainer<WkWebView>(Test.WebView.JavaScriptAlert,
|
||||
jsAlertWebView
|
||||
);
|
||||
|
||||
var evaluateJsWebView = new WkWebView
|
||||
{
|
||||
Source = new UrlWebViewSource { Url = "https://www.google.com/" },
|
||||
HeightRequest = 50
|
||||
};
|
||||
var evaluateJsWebViewSourceContainer = new ViewContainer<WkWebView>(Test.WebView.EvaluateJavaScript,
|
||||
evaluateJsWebView
|
||||
);
|
||||
|
||||
var resultsLabel = new Label();
|
||||
var execButton = new Button();
|
||||
execButton.Text = "Evaluate Javascript";
|
||||
execButton.Command = new Command(async() => resultsLabel.Text = await evaluateJsWebView.EvaluateJavaScriptAsync(
|
||||
"var test = function(){ return 'This string came from Javascript!'; }; test();"));
|
||||
|
||||
evaluateJsWebViewSourceContainer.ContainerLayout.Children.Add(resultsLabel);
|
||||
evaluateJsWebViewSourceContainer.ContainerLayout.Children.Add(execButton);
|
||||
|
||||
|
||||
Add (urlWebViewSourceContainer);
|
||||
Add (htmlWebViewSourceContainer);
|
||||
Add (htmlFileWebSourceContainer);
|
||||
Add (javascriptAlertWebSourceContainer);
|
||||
Add (evaluateJsWebViewSourceContainer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ using System.ComponentModel;
|
|||
using System.Drawing;
|
||||
using System.Threading.Tasks;
|
||||
using Foundation;
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
using WebKit;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
@ -44,7 +45,8 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
WebView.GoBackRequested += OnGoBackRequested;
|
||||
WebView.GoForwardRequested += OnGoForwardRequested;
|
||||
WebView.ReloadRequested += OnReloadRequested;
|
||||
NavigationDelegate = new CustomWebViewDelegate(this);
|
||||
NavigationDelegate = new CustomWebViewNavigationDelegate(this);
|
||||
UIDelegate = new CustomWebViewUIDelegate();
|
||||
|
||||
BackgroundColor = UIColor.Clear;
|
||||
|
||||
|
@ -151,7 +153,7 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
async Task<string> OnEvaluateJavaScriptRequested(string script)
|
||||
{
|
||||
var result = await EvaluateJavaScriptAsync(script);
|
||||
return result.ToString();
|
||||
return result?.ToString();
|
||||
}
|
||||
|
||||
void OnGoBackRequested(object sender, EventArgs eventArgs)
|
||||
|
@ -187,12 +189,12 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
((IWebViewController)WebView).CanGoForward = CanGoForward;
|
||||
}
|
||||
|
||||
class CustomWebViewDelegate : WKNavigationDelegate
|
||||
class CustomWebViewNavigationDelegate : WKNavigationDelegate
|
||||
{
|
||||
readonly WkWebViewRenderer _renderer;
|
||||
WebNavigationEvent _lastEvent;
|
||||
|
||||
public CustomWebViewDelegate(WkWebViewRenderer renderer)
|
||||
public CustomWebViewNavigationDelegate(WkWebViewRenderer renderer)
|
||||
{
|
||||
if (renderer == null)
|
||||
throw new ArgumentNullException("renderer");
|
||||
|
@ -274,6 +276,106 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
}
|
||||
}
|
||||
|
||||
class CustomWebViewUIDelegate : WKUIDelegate
|
||||
{
|
||||
static string LocalOK = NSBundle.FromIdentifier("com.apple.UIKit").GetLocalizedString("OK");
|
||||
static string LocalCancel = NSBundle.FromIdentifier("com.apple.UIKit").GetLocalizedString("Cancel");
|
||||
|
||||
public override void RunJavaScriptAlertPanel(WKWebView webView, string message, WKFrameInfo frame, Action completionHandler)
|
||||
{
|
||||
PresentAlertController(
|
||||
webView,
|
||||
message,
|
||||
okAction: _ => completionHandler()
|
||||
);
|
||||
}
|
||||
|
||||
public override void RunJavaScriptConfirmPanel(WKWebView webView, string message, WKFrameInfo frame, Action<bool> completionHandler)
|
||||
{
|
||||
PresentAlertController(
|
||||
webView,
|
||||
message,
|
||||
okAction: _ => completionHandler(true),
|
||||
cancelAction: _ => completionHandler(false)
|
||||
);
|
||||
}
|
||||
|
||||
public override void RunJavaScriptTextInputPanel(
|
||||
WKWebView webView, string prompt, string defaultText, WKFrameInfo frame, Action<string> completionHandler)
|
||||
{
|
||||
PresentAlertController(
|
||||
webView,
|
||||
prompt,
|
||||
defaultText: defaultText,
|
||||
okAction: x => completionHandler(x.TextFields[0].Text),
|
||||
cancelAction: _ => completionHandler(null)
|
||||
);
|
||||
}
|
||||
|
||||
static string GetJsAlertTitle(WKWebView webView)
|
||||
{
|
||||
// Emulate the behavior of UIWebView dialogs.
|
||||
// The scheme and host are used unless local html content is what the webview is displaying,
|
||||
// in which case the bundle file name is used.
|
||||
|
||||
if (webView.Url != null && webView.Url.AbsoluteString != $"file://{NSBundle.MainBundle.BundlePath}/")
|
||||
return $"{webView.Url.Scheme}://{webView.Url.Host}";
|
||||
|
||||
return new NSString(NSBundle.MainBundle.BundlePath).LastPathComponent;
|
||||
}
|
||||
|
||||
static UIAlertAction AddOkAction(UIAlertController controller, Action handler)
|
||||
{
|
||||
var action = UIAlertAction.Create(LocalOK, UIAlertActionStyle.Default, (_) => handler());
|
||||
controller.AddAction(action);
|
||||
controller.PreferredAction = action;
|
||||
return action;
|
||||
}
|
||||
|
||||
static UIAlertAction AddCancelAction(UIAlertController controller, Action handler)
|
||||
{
|
||||
var action = UIAlertAction.Create(LocalCancel, UIAlertActionStyle.Cancel, (_) => handler());
|
||||
controller.AddAction(action);
|
||||
return action;
|
||||
}
|
||||
|
||||
static void PresentAlertController(
|
||||
WKWebView webView,
|
||||
string message,
|
||||
string defaultText = null,
|
||||
Action<UIAlertController> okAction = null,
|
||||
Action<UIAlertController> cancelAction = null)
|
||||
{
|
||||
var controller = UIAlertController.Create(GetJsAlertTitle(webView), message, UIAlertControllerStyle.Alert);
|
||||
|
||||
if (defaultText != null)
|
||||
controller.AddTextField((textField) => textField.Text = defaultText);
|
||||
|
||||
if (okAction != null)
|
||||
AddOkAction(controller, () => okAction(controller));
|
||||
|
||||
if (cancelAction != null)
|
||||
AddCancelAction(controller, () => cancelAction(controller));
|
||||
|
||||
GetTopViewController(UIApplication.SharedApplication.KeyWindow.RootViewController)
|
||||
.PresentViewController(controller, true, null);
|
||||
}
|
||||
|
||||
static UIViewController GetTopViewController(UIViewController viewController)
|
||||
{
|
||||
if (viewController is UINavigationController navigationController)
|
||||
return GetTopViewController(navigationController.VisibleViewController);
|
||||
|
||||
if (viewController is UITabBarController tabBarController)
|
||||
return GetTopViewController(tabBarController.SelectedViewController);
|
||||
|
||||
if (viewController.PresentedViewController != null)
|
||||
return GetTopViewController(viewController.PresentedViewController);
|
||||
|
||||
return viewController;
|
||||
}
|
||||
}
|
||||
|
||||
#region IPlatformRenderer implementation
|
||||
|
||||
public UIView NativeView
|
||||
|
|
Загрузка…
Ссылка в новой задаче