[webview_flutter] [url_launcher] Handle Multiwindows in WebViews (#2991)
diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md
index 063aa99..d803ddf 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 5624d75..98c5613 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 @@
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 @@
// 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 965a431..a4061af 100644
--- a/packages/url_launcher/url_launcher/pubspec.yaml
+++ b/packages/url_launcher/url_launcher/pubspec.yaml
@@ -2,7 +2,7 @@
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 baa485b..e1f4c3c 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 f9659d9..9bec8fa 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 @@
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 @@
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 474435c..24926bf 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 @@
}
@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 @@
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 162ca2c..53fc991 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 @@
final String currentUrl = await controller.currentUrl();
expect(currentUrl, 'about:blank');
});
+
+ testWidgets(
+ 'can open new window and go back',
+ (WidgetTester tester) async {
+ final Completer<WebViewController> controllerCompleter =
+ Completer<WebViewController>();
+ final Completer<void> pageLoaded = Completer<void>();
+ 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 = '''
+ <!DOCTYPE html>
+ <script>
+ window.onload = () => {
+ window.open(`javascript:
+ var elem = document.createElement("p");
+ elem.innerHTML = "<b>Executed JS in parent origin: " + window.location.origin + "</b>";
+ document.body.append(elem);
+ `);
+ };
+ </script>
+ ''';
+ final String iframeTestBase64 =
+ base64Encode(const Utf8Encoder().convert(iframe));
+
+ final String openWindowTest = '''
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <title>XSS test</title>
+ </head>
+ <body>
+ <iframe
+ onload="window.iframeLoaded = true;"
+ src="data:text/html;charset=utf-8;base64,$iframeTestBase64"></iframe>
+ </body>
+ </html>
+ ''';
+ final String openWindowTestBase64 =
+ base64Encode(const Utf8Encoder().convert(openWindowTest));
+ final Completer<WebViewController> controllerCompleter =
+ Completer<WebViewController>();
+ final Completer<void> pageLoadCompleter = Completer<void>();
+
+ 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 ec81183..8a4a62e 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: