Skip to content

Commit e80865a

Browse files
[webview_flutter] [url_launcher] Handle Multiwindows in WebViews (flutter#2991)
1 parent 052a915 commit e80865a

8 files changed

Lines changed: 204 additions & 4 deletions

File tree

packages/url_launcher/url_launcher/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 5.7.0
2+
3+
* Handle WebView multi-window support.
4+
15
## 5.6.0
26

37
* Support Windows by default.

packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/WebViewActivity.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
package io.flutter.plugins.urllauncher;
22

3+
import android.annotation.TargetApi;
34
import android.app.Activity;
45
import android.content.BroadcastReceiver;
56
import android.content.Context;
67
import android.content.Intent;
78
import android.content.IntentFilter;
89
import android.os.Build;
910
import android.os.Bundle;
11+
import android.os.Message;
1012
import android.provider.Browser;
1113
import android.view.KeyEvent;
14+
import android.webkit.WebChromeClient;
1215
import android.webkit.WebResourceRequest;
1316
import android.webkit.WebView;
1417
import android.webkit.WebViewClient;
18+
import androidx.annotation.NonNull;
1519
import androidx.annotation.RequiresApi;
1620
import java.util.HashMap;
1721
import java.util.Map;
@@ -67,6 +71,39 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request
6771

6872
private IntentFilter closeIntentFilter = new IntentFilter(ACTION_CLOSE);
6973

74+
// Verifies that a url opened by `Window.open` has a secure url.
75+
private class FlutterWebChromeClient extends WebChromeClient {
76+
@Override
77+
public boolean onCreateWindow(
78+
final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
79+
final WebViewClient webViewClient =
80+
new WebViewClient() {
81+
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
82+
@Override
83+
public boolean shouldOverrideUrlLoading(
84+
@NonNull WebView view, @NonNull WebResourceRequest request) {
85+
webview.loadUrl(request.getUrl().toString());
86+
return true;
87+
}
88+
89+
@Override
90+
public boolean shouldOverrideUrlLoading(WebView view, String url) {
91+
webview.loadUrl(url);
92+
return true;
93+
}
94+
};
95+
96+
final WebView newWebView = new WebView(webview.getContext());
97+
newWebView.setWebViewClient(webViewClient);
98+
99+
final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
100+
transport.setWebView(newWebView);
101+
resultMsg.sendToTarget();
102+
103+
return true;
104+
}
105+
}
106+
70107
@Override
71108
public void onCreate(Bundle savedInstanceState) {
72109
super.onCreate(savedInstanceState);
@@ -88,6 +125,10 @@ public void onCreate(Bundle savedInstanceState) {
88125
// Open new urls inside the webview itself.
89126
webview.setWebViewClient(webViewClient);
90127

128+
// Multi windows is set with FlutterWebChromeClient by default to handle internal bug: b/159892679.
129+
webview.getSettings().setSupportMultipleWindows(true);
130+
webview.setWebChromeClient(new FlutterWebChromeClient());
131+
91132
// Register receiver that may finish this Activity.
92133
registerReceiver(broadcastReceiver, closeIntentFilter);
93134
}

packages/url_launcher/url_launcher/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: url_launcher
22
description: Flutter plugin for launching a URL on Android and iOS. Supports
33
web, phone, SMS, and email schemes.
44
homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher
5-
version: 5.6.0
5+
version: 5.7.0
66

77
flutter:
88
plugin:

packages/webview_flutter/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.3.23
2+
3+
* Handle WebView multi-window support.
4+
15
## 0.3.22+2
26

37
* Update package:e2e reference to use the local version in the flutter/plugins

packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@
99
import android.hardware.display.DisplayManager;
1010
import android.os.Build;
1111
import android.os.Handler;
12+
import android.os.Message;
1213
import android.view.View;
14+
import android.webkit.WebChromeClient;
15+
import android.webkit.WebResourceRequest;
1316
import android.webkit.WebStorage;
17+
import android.webkit.WebView;
1418
import android.webkit.WebViewClient;
19+
import androidx.annotation.NonNull;
1520
import io.flutter.plugin.common.BinaryMessenger;
1621
import io.flutter.plugin.common.MethodCall;
1722
import io.flutter.plugin.common.MethodChannel;
@@ -29,6 +34,46 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
2934
private final FlutterWebViewClient flutterWebViewClient;
3035
private final Handler platformThreadHandler;
3136

37+
// Verifies that a url opened by `Window.open` has a secure url.
38+
private class FlutterWebChromeClient extends WebChromeClient {
39+
@Override
40+
public boolean onCreateWindow(
41+
final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
42+
final WebViewClient webViewClient =
43+
new WebViewClient() {
44+
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
45+
@Override
46+
public boolean shouldOverrideUrlLoading(
47+
@NonNull WebView view, @NonNull WebResourceRequest request) {
48+
final String url = request.getUrl().toString();
49+
if (!flutterWebViewClient.shouldOverrideUrlLoading(
50+
FlutterWebView.this.webView, request)) {
51+
webView.loadUrl(url);
52+
}
53+
return true;
54+
}
55+
56+
@Override
57+
public boolean shouldOverrideUrlLoading(WebView view, String url) {
58+
if (!flutterWebViewClient.shouldOverrideUrlLoading(
59+
FlutterWebView.this.webView, url)) {
60+
webView.loadUrl(url);
61+
}
62+
return true;
63+
}
64+
};
65+
66+
final WebView newWebView = new WebView(view.getContext());
67+
newWebView.setWebViewClient(webViewClient);
68+
69+
final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
70+
transport.setWebView(newWebView);
71+
resultMsg.sendToTarget();
72+
73+
return true;
74+
}
75+
}
76+
3277
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
3378
@SuppressWarnings("unchecked")
3479
FlutterWebView(
@@ -50,6 +95,10 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
5095
webView.getSettings().setDomStorageEnabled(true);
5196
webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
5297

98+
// Multi windows is set with FlutterWebChromeClient by default to handle internal bug: b/159892679.
99+
webView.getSettings().setSupportMultipleWindows(true);
100+
webView.setWebChromeClient(new FlutterWebChromeClient());
101+
53102
methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id);
54103
methodChannel.setMethodCallHandler(this);
55104

packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ private static String errorCodeToString(int errorCode) {
7777
}
7878

7979
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
80-
private boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
80+
boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
8181
if (!hasNavigationDelegate) {
8282
return false;
8383
}
@@ -97,7 +97,7 @@ private boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest reques
9797
return request.isForMainFrame();
9898
}
9999

100-
private boolean shouldOverrideUrlLoading(WebView view, String url) {
100+
boolean shouldOverrideUrlLoading(WebView view, String url) {
101101
if (!hasNavigationDelegate) {
102102
return false;
103103
}

packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,108 @@ void main() {
828828
final String currentUrl = await controller.currentUrl();
829829
expect(currentUrl, 'about:blank');
830830
});
831+
832+
testWidgets(
833+
'can open new window and go back',
834+
(WidgetTester tester) async {
835+
final Completer<WebViewController> controllerCompleter =
836+
Completer<WebViewController>();
837+
final Completer<void> pageLoaded = Completer<void>();
838+
await tester.pumpWidget(
839+
Directionality(
840+
textDirection: TextDirection.ltr,
841+
child: WebView(
842+
key: GlobalKey(),
843+
onWebViewCreated: (WebViewController controller) {
844+
controllerCompleter.complete(controller);
845+
},
846+
javascriptMode: JavascriptMode.unrestricted,
847+
onPageFinished: (String url) {
848+
pageLoaded.complete();
849+
},
850+
initialUrl: 'https://flutter.dev',
851+
),
852+
),
853+
);
854+
final WebViewController controller = await controllerCompleter.future;
855+
await controller
856+
.evaluateJavascript('window.open("https://www.google.com")');
857+
await pageLoaded.future;
858+
expect(controller.currentUrl(), completion('https://www.google.com/'));
859+
860+
await controller.goBack();
861+
expect(controller.currentUrl(), completion('https://www.flutter.dev'));
862+
},
863+
skip: !Platform.isAndroid,
864+
);
865+
866+
testWidgets(
867+
'javascript does not run in parent window',
868+
(WidgetTester tester) async {
869+
final String iframe = '''
870+
<!DOCTYPE html>
871+
<script>
872+
window.onload = () => {
873+
window.open(`javascript:
874+
var elem = document.createElement("p");
875+
elem.innerHTML = "<b>Executed JS in parent origin: " + window.location.origin + "</b>";
876+
document.body.append(elem);
877+
`);
878+
};
879+
</script>
880+
''';
881+
final String iframeTestBase64 =
882+
base64Encode(const Utf8Encoder().convert(iframe));
883+
884+
final String openWindowTest = '''
885+
<!DOCTYPE html>
886+
<html>
887+
<head>
888+
<title>XSS test</title>
889+
</head>
890+
<body>
891+
<iframe
892+
onload="window.iframeLoaded = true;"
893+
src="data:text/html;charset=utf-8;base64,$iframeTestBase64"></iframe>
894+
</body>
895+
</html>
896+
''';
897+
final String openWindowTestBase64 =
898+
base64Encode(const Utf8Encoder().convert(openWindowTest));
899+
final Completer<WebViewController> controllerCompleter =
900+
Completer<WebViewController>();
901+
final Completer<void> pageLoadCompleter = Completer<void>();
902+
903+
await tester.pumpWidget(
904+
Directionality(
905+
textDirection: TextDirection.ltr,
906+
child: WebView(
907+
key: GlobalKey(),
908+
onWebViewCreated: (WebViewController controller) {
909+
controllerCompleter.complete(controller);
910+
},
911+
javascriptMode: JavascriptMode.unrestricted,
912+
initialUrl:
913+
'data:text/html;charset=utf-8;base64,$openWindowTestBase64',
914+
onPageFinished: (String url) {
915+
pageLoadCompleter.complete();
916+
},
917+
),
918+
),
919+
);
920+
921+
final WebViewController controller = await controllerCompleter.future;
922+
await pageLoadCompleter.future;
923+
924+
expect(controller.evaluateJavascript('iframeLoaded'), completion('true'));
925+
expect(
926+
controller.evaluateJavascript(
927+
'document.querySelector("p") && document.querySelector("p").textContent'),
928+
completion('null'),
929+
);
930+
},
931+
skip: !Platform.isAndroid,
932+
);
831933
}
832934

833935
// JavaScript booleans evaluate to different string values on Android and iOS.

packages/webview_flutter/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: webview_flutter
22
description: A Flutter plugin that provides a WebView widget on Android and iOS.
3-
version: 0.3.22+2
3+
version: 0.3.23
44
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter
55

66
environment:

0 commit comments

Comments
 (0)