[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: