diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index 063aa99d5d51..d803ddf57ccb 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.7.0 + +* Handle WebView multi-window support. + ## 5.6.0 * Support Windows by default. diff --git a/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/WebViewActivity.java b/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/WebViewActivity.java index 5624d75b22eb..98c5613e6c62 100644 --- a/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/WebViewActivity.java +++ b/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/WebViewActivity.java @@ -1,5 +1,6 @@ package io.flutter.plugins.urllauncher; +import android.annotation.TargetApi; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; @@ -7,11 +8,14 @@ import android.content.IntentFilter; import android.os.Build; import android.os.Bundle; +import android.os.Message; import android.provider.Browser; import android.view.KeyEvent; +import android.webkit.WebChromeClient; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; +import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import java.util.HashMap; import java.util.Map; @@ -67,6 +71,39 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request private IntentFilter closeIntentFilter = new IntentFilter(ACTION_CLOSE); + // Verifies that a url opened by `Window.open` has a secure url. + private class FlutterWebChromeClient extends WebChromeClient { + @Override + public boolean onCreateWindow( + final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { + final WebViewClient webViewClient = + new WebViewClient() { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean shouldOverrideUrlLoading( + @NonNull WebView view, @NonNull WebResourceRequest request) { + webview.loadUrl(request.getUrl().toString()); + return true; + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + webview.loadUrl(url); + return true; + } + }; + + final WebView newWebView = new WebView(webview.getContext()); + newWebView.setWebViewClient(webViewClient); + + final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; + transport.setWebView(newWebView); + resultMsg.sendToTarget(); + + return true; + } + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -88,6 +125,10 @@ public void onCreate(Bundle savedInstanceState) { // Open new urls inside the webview itself. webview.setWebViewClient(webViewClient); + // Multi windows is set with FlutterWebChromeClient by default to handle internal bug: b/159892679. + webview.getSettings().setSupportMultipleWindows(true); + webview.setWebChromeClient(new FlutterWebChromeClient()); + // Register receiver that may finish this Activity. registerReceiver(broadcastReceiver, closeIntentFilter); } diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index 965a4318d0f8..a4061af8eadc 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher description: Flutter plugin for launching a URL on Android and iOS. Supports web, phone, SMS, and email schemes. homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher -version: 5.6.0 +version: 5.7.0 flutter: plugin: diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md index baa485ba5b17..e1f4c3c2d1c8 100644 --- a/packages/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.23 + +* Handle WebView multi-window support. + ## 0.3.22+2 * Update package:e2e reference to use the local version in the flutter/plugins diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java index f9659d9873f4..9bec8fa0ef13 100644 --- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java +++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java @@ -9,9 +9,14 @@ import android.hardware.display.DisplayManager; import android.os.Build; import android.os.Handler; +import android.os.Message; import android.view.View; +import android.webkit.WebChromeClient; +import android.webkit.WebResourceRequest; import android.webkit.WebStorage; +import android.webkit.WebView; import android.webkit.WebViewClient; +import androidx.annotation.NonNull; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -29,6 +34,46 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { private final FlutterWebViewClient flutterWebViewClient; private final Handler platformThreadHandler; + // Verifies that a url opened by `Window.open` has a secure url. + private class FlutterWebChromeClient extends WebChromeClient { + @Override + public boolean onCreateWindow( + final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { + final WebViewClient webViewClient = + new WebViewClient() { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean shouldOverrideUrlLoading( + @NonNull WebView view, @NonNull WebResourceRequest request) { + final String url = request.getUrl().toString(); + if (!flutterWebViewClient.shouldOverrideUrlLoading( + FlutterWebView.this.webView, request)) { + webView.loadUrl(url); + } + return true; + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (!flutterWebViewClient.shouldOverrideUrlLoading( + FlutterWebView.this.webView, url)) { + webView.loadUrl(url); + } + return true; + } + }; + + final WebView newWebView = new WebView(view.getContext()); + newWebView.setWebViewClient(webViewClient); + + final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; + transport.setWebView(newWebView); + resultMsg.sendToTarget(); + + return true; + } + } + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @SuppressWarnings("unchecked") FlutterWebView( @@ -50,6 +95,10 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { webView.getSettings().setDomStorageEnabled(true); webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); + // Multi windows is set with FlutterWebChromeClient by default to handle internal bug: b/159892679. + webView.getSettings().setSupportMultipleWindows(true); + webView.setWebChromeClient(new FlutterWebChromeClient()); + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id); methodChannel.setMethodCallHandler(this); diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java index 474435ca3dfe..24926bfc4117 100644 --- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java +++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java @@ -77,7 +77,7 @@ private static String errorCodeToString(int errorCode) { } @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { if (!hasNavigationDelegate) { return false; } @@ -97,7 +97,7 @@ private boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest reques return request.isForMainFrame(); } - private boolean shouldOverrideUrlLoading(WebView view, String url) { + boolean shouldOverrideUrlLoading(WebView view, String url) { if (!hasNavigationDelegate) { return false; } diff --git a/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart index 162ca2cdcd9a..53fc991c48f2 100644 --- a/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart +++ b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart @@ -828,6 +828,108 @@ void main() { final String currentUrl = await controller.currentUrl(); expect(currentUrl, 'about:blank'); }); + + testWidgets( + 'can open new window and go back', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageLoaded = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(); + }, + initialUrl: 'https://flutter.dev', + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await controller + .evaluateJavascript('window.open("https://www.google.com")'); + await pageLoaded.future; + expect(controller.currentUrl(), completion('https://www.google.com/')); + + await controller.goBack(); + expect(controller.currentUrl(), completion('https://www.flutter.dev')); + }, + skip: !Platform.isAndroid, + ); + + testWidgets( + 'javascript does not run in parent window', + (WidgetTester tester) async { + final String iframe = ''' + + + '''; + final String iframeTestBase64 = + base64Encode(const Utf8Encoder().convert(iframe)); + + final String openWindowTest = ''' + + + + XSS test + + + + + + '''; + final String openWindowTestBase64 = + base64Encode(const Utf8Encoder().convert(openWindowTest)); + final Completer controllerCompleter = + Completer(); + final Completer pageLoadCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + initialUrl: + 'data:text/html;charset=utf-8;base64,$openWindowTestBase64', + onPageFinished: (String url) { + pageLoadCompleter.complete(); + }, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageLoadCompleter.future; + + expect(controller.evaluateJavascript('iframeLoaded'), completion('true')); + expect( + controller.evaluateJavascript( + 'document.querySelector("p") && document.querySelector("p").textContent'), + completion('null'), + ); + }, + skip: !Platform.isAndroid, + ); } // JavaScript booleans evaluate to different string values on Android and iOS. diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml index ec811833c9b8..8a4a62ea76a6 100644 --- a/packages/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. -version: 0.3.22+2 +version: 0.3.23 homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter environment: