diff --git a/packages/webview_flutter/analysis_options.yaml b/packages/webview_flutter/analysis_options.yaml deleted file mode 100644 index cda4f6e153e6..000000000000 --- a/packages/webview_flutter/analysis_options.yaml +++ /dev/null @@ -1 +0,0 @@ -include: ../../analysis_options_legacy.yaml diff --git a/packages/webview_flutter/webview_flutter/analysis_options.yaml b/packages/webview_flutter/webview_flutter/analysis_options.yaml new file mode 100644 index 000000000000..5aeb4e7c5e21 --- /dev/null +++ b/packages/webview_flutter/webview_flutter/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../../analysis_options_legacy.yaml diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart index 0baffba0edb7..0e7e3451d054 100644 --- a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart @@ -197,9 +197,11 @@ void main() { onPageFinished: () => onPageFinished.complete(), )); await onPageFinished.future; - if (Platform.isAndroid) { - await initialResizeCompleter.future; - } + // Wait for a potential call to resize after page is loaded. + await initialResizeCompleter.future.timeout( + const Duration(seconds: 3), + onTimeout: () => null, + ); resizeButtonTapped = true; await tester.tap(find.byKey(const ValueKey('resizeButton'))); diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index b241be63248c..09a797f4a1ed 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.0 + +* Replaces platform implementation with API built with pigeon. + ## 2.2.1 * Fix `NullPointerException` from a race condition when changing focus. This only affects `WebView` diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterDownloadListener.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterDownloadListener.java deleted file mode 100644 index cfad4e315514..000000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterDownloadListener.java +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import android.webkit.DownloadListener; -import android.webkit.WebView; - -/** DownloadListener to notify the {@link FlutterWebViewClient} of download starts */ -public class FlutterDownloadListener implements DownloadListener { - private final FlutterWebViewClient webViewClient; - private WebView webView; - - public FlutterDownloadListener(FlutterWebViewClient webViewClient) { - this.webViewClient = webViewClient; - } - - /** Sets the {@link WebView} that the result of the navigation delegate will be send to. */ - public void setWebView(WebView webView) { - this.webView = webView; - } - - @Override - public void onDownloadStart( - String url, - String userAgent, - String contentDisposition, - String mimetype, - long contentLength) { - webViewClient.notifyDownload(webView, url); - } -} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java deleted file mode 100644 index ed14107220b8..000000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java +++ /dev/null @@ -1,495 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import android.annotation.TargetApi; -import android.content.Context; -import android.hardware.display.DisplayManager; -import android.os.Build; -import android.os.Handler; -import android.os.Message; -import android.view.View; -import android.webkit.DownloadListener; -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 androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.platform.PlatformView; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class FlutterWebView implements PlatformView, MethodCallHandler { - - private static final String JS_CHANNEL_NAMES_FIELD = "javascriptChannelNames"; - private final WebView webView; - private final MethodChannel methodChannel; - 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; - } - - @Override - public void onProgressChanged(WebView view, int progress) { - flutterWebViewClient.onLoadingProgress(progress); - } - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - @SuppressWarnings("unchecked") - FlutterWebView( - final Context context, - MethodChannel methodChannel, - Map params, - View containerView) { - - DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy(); - DisplayManager displayManager = - (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); - displayListenerProxy.onPreWebViewInitialization(displayManager); - - this.methodChannel = methodChannel; - this.methodChannel.setMethodCallHandler(this); - - flutterWebViewClient = new FlutterWebViewClient(methodChannel); - - FlutterDownloadListener flutterDownloadListener = - new FlutterDownloadListener(flutterWebViewClient); - webView = - createWebView( - new WebViewBuilder(context, containerView), - params, - new FlutterWebChromeClient(), - flutterDownloadListener); - flutterDownloadListener.setWebView(webView); - - displayListenerProxy.onPostWebViewInitialization(displayManager); - - platformThreadHandler = new Handler(context.getMainLooper()); - - Map settings = (Map) params.get("settings"); - if (settings != null) { - applySettings(settings); - } - - if (params.containsKey(JS_CHANNEL_NAMES_FIELD)) { - List names = (List) params.get(JS_CHANNEL_NAMES_FIELD); - if (names != null) { - registerJavaScriptChannelNames(names); - } - } - - Integer autoMediaPlaybackPolicy = (Integer) params.get("autoMediaPlaybackPolicy"); - if (autoMediaPlaybackPolicy != null) { - updateAutoMediaPlaybackPolicy(autoMediaPlaybackPolicy); - } - if (params.containsKey("userAgent")) { - String userAgent = (String) params.get("userAgent"); - updateUserAgent(userAgent); - } - if (params.containsKey("initialUrl")) { - String url = (String) params.get("initialUrl"); - webView.loadUrl(url); - } - } - - /** - * Creates a {@link android.webkit.WebView} and configures it according to the supplied - * parameters. - * - *

The {@link WebView} is configured with the following predefined settings: - * - *

    - *
  • always enable the DOM storage API; - *
  • always allow JavaScript to automatically open windows; - *
  • always allow support for multiple windows; - *
  • always use the {@link FlutterWebChromeClient} as web Chrome client. - *
- * - *

Important: This method is visible for testing purposes only and should - * never be called from outside this class. - * - * @param webViewBuilder a {@link WebViewBuilder} which is responsible for building the {@link - * WebView}. - * @param params creation parameters received over the method channel. - * @param webChromeClient an implementation of WebChromeClient This value may be null. - * @return The new {@link android.webkit.WebView} object. - */ - @VisibleForTesting - static WebView createWebView( - WebViewBuilder webViewBuilder, - Map params, - WebChromeClient webChromeClient, - @Nullable DownloadListener downloadListener) { - boolean usesHybridComposition = Boolean.TRUE.equals(params.get("usesHybridComposition")); - webViewBuilder - .setUsesHybridComposition(usesHybridComposition) - .setDomStorageEnabled(true) // Always enable DOM storage API. - .setJavaScriptCanOpenWindowsAutomatically( - true) // Always allow automatically opening of windows. - .setSupportMultipleWindows(true) // Always support multiple windows. - .setWebChromeClient( - webChromeClient) // Always use {@link FlutterWebChromeClient} as web Chrome client. - .setDownloadListener(downloadListener) - .setZoomControlsEnabled(true); // Always use built-in zoom mechanisms. - - return webViewBuilder.build(); - } - - @Override - public View getView() { - return webView; - } - - @Override - public void onInputConnectionUnlocked() { - if (webView instanceof InputAwareWebView) { - ((InputAwareWebView) webView).unlockInputConnection(); - } - } - - @Override - public void onInputConnectionLocked() { - if (webView instanceof InputAwareWebView) { - ((InputAwareWebView) webView).lockInputConnection(); - } - } - - @Override - public void onFlutterViewAttached(View flutterView) { - if (webView instanceof InputAwareWebView) { - ((InputAwareWebView) webView).setContainerView(flutterView); - } - } - - @Override - public void onFlutterViewDetached() { - if (webView instanceof InputAwareWebView) { - ((InputAwareWebView) webView).setContainerView(null); - } - } - - @Override - public void onMethodCall(MethodCall methodCall, Result result) { - switch (methodCall.method) { - case "loadUrl": - loadUrl(methodCall, result); - break; - case "updateSettings": - updateSettings(methodCall, result); - break; - case "canGoBack": - canGoBack(result); - break; - case "canGoForward": - canGoForward(result); - break; - case "goBack": - goBack(result); - break; - case "goForward": - goForward(result); - break; - case "reload": - reload(result); - break; - case "currentUrl": - currentUrl(result); - break; - case "evaluateJavascript": - case "runJavascriptReturningResult": - evaluateJavaScript(methodCall, result, true); - break; - case "runJavascript": - evaluateJavaScript(methodCall, result, false); - break; - case "addJavascriptChannels": - addJavaScriptChannels(methodCall, result); - break; - case "removeJavascriptChannels": - removeJavaScriptChannels(methodCall, result); - break; - case "clearCache": - clearCache(result); - break; - case "getTitle": - getTitle(result); - break; - case "scrollTo": - scrollTo(methodCall, result); - break; - case "scrollBy": - scrollBy(methodCall, result); - break; - case "getScrollX": - getScrollX(result); - break; - case "getScrollY": - getScrollY(result); - break; - default: - result.notImplemented(); - } - } - - @SuppressWarnings("unchecked") - private void loadUrl(MethodCall methodCall, Result result) { - Map request = (Map) methodCall.arguments; - String url = (String) request.get("url"); - Map headers = (Map) request.get("headers"); - if (headers == null) { - headers = Collections.emptyMap(); - } - webView.loadUrl(url, headers); - result.success(null); - } - - private void canGoBack(Result result) { - result.success(webView.canGoBack()); - } - - private void canGoForward(Result result) { - result.success(webView.canGoForward()); - } - - private void goBack(Result result) { - if (webView.canGoBack()) { - webView.goBack(); - } - result.success(null); - } - - private void goForward(Result result) { - if (webView.canGoForward()) { - webView.goForward(); - } - result.success(null); - } - - private void reload(Result result) { - webView.reload(); - result.success(null); - } - - private void currentUrl(Result result) { - result.success(webView.getUrl()); - } - - @SuppressWarnings("unchecked") - private void updateSettings(MethodCall methodCall, Result result) { - applySettings((Map) methodCall.arguments); - result.success(null); - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - private void evaluateJavaScript( - MethodCall methodCall, final Result result, final boolean returnValue) { - String jsString = (String) methodCall.arguments; - if (jsString == null) { - throw new UnsupportedOperationException("JavaScript string cannot be null"); - } - webView.evaluateJavascript( - jsString, - new android.webkit.ValueCallback() { - @Override - public void onReceiveValue(String value) { - if (returnValue) { - result.success(value); - } else { - result.success(null); - } - } - }); - } - - @SuppressWarnings("unchecked") - private void addJavaScriptChannels(MethodCall methodCall, Result result) { - List channelNames = (List) methodCall.arguments; - registerJavaScriptChannelNames(channelNames); - result.success(null); - } - - @SuppressWarnings("unchecked") - private void removeJavaScriptChannels(MethodCall methodCall, Result result) { - List channelNames = (List) methodCall.arguments; - for (String channelName : channelNames) { - webView.removeJavascriptInterface(channelName); - } - result.success(null); - } - - private void clearCache(Result result) { - webView.clearCache(true); - WebStorage.getInstance().deleteAllData(); - result.success(null); - } - - private void getTitle(Result result) { - result.success(webView.getTitle()); - } - - private void scrollTo(MethodCall methodCall, Result result) { - Map request = methodCall.arguments(); - int x = (int) request.get("x"); - int y = (int) request.get("y"); - - webView.scrollTo(x, y); - - result.success(null); - } - - private void scrollBy(MethodCall methodCall, Result result) { - Map request = methodCall.arguments(); - int x = (int) request.get("x"); - int y = (int) request.get("y"); - - webView.scrollBy(x, y); - result.success(null); - } - - private void getScrollX(Result result) { - result.success(webView.getScrollX()); - } - - private void getScrollY(Result result) { - result.success(webView.getScrollY()); - } - - private void applySettings(Map settings) { - for (String key : settings.keySet()) { - switch (key) { - case "jsMode": - Integer mode = (Integer) settings.get(key); - if (mode != null) { - updateJsMode(mode); - } - break; - case "hasNavigationDelegate": - final boolean hasNavigationDelegate = (boolean) settings.get(key); - - final WebViewClient webViewClient = - flutterWebViewClient.createWebViewClient(hasNavigationDelegate); - - webView.setWebViewClient(webViewClient); - break; - case "debuggingEnabled": - final boolean debuggingEnabled = (boolean) settings.get(key); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.setWebContentsDebuggingEnabled(debuggingEnabled); - } - break; - case "hasProgressTracking": - flutterWebViewClient.hasProgressTracking = (boolean) settings.get(key); - break; - case "gestureNavigationEnabled": - break; - case "userAgent": - updateUserAgent((String) settings.get(key)); - break; - case "allowsInlineMediaPlayback": - // no-op inline media playback is always allowed on Android. - break; - case "zoomEnabled": - setZoomEnabled((boolean) settings.get(key)); - break; - default: - throw new IllegalArgumentException("Unknown WebView setting: " + key); - } - } - } - - private void updateJsMode(int mode) { - switch (mode) { - case 0: // disabled - webView.getSettings().setJavaScriptEnabled(false); - break; - case 1: // unrestricted - webView.getSettings().setJavaScriptEnabled(true); - break; - default: - throw new IllegalArgumentException("Trying to set unknown JavaScript mode: " + mode); - } - } - - private void updateAutoMediaPlaybackPolicy(int mode) { - // This is the index of the AutoMediaPlaybackPolicy enum, index 1 is always_allow, for all - // other values we require a user gesture. - boolean requireUserGesture = mode != 1; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - webView.getSettings().setMediaPlaybackRequiresUserGesture(requireUserGesture); - } - } - - private void registerJavaScriptChannelNames(List channelNames) { - for (String channelName : channelNames) { - webView.addJavascriptInterface( - new JavaScriptChannel(methodChannel, channelName, platformThreadHandler), channelName); - } - } - - private void updateUserAgent(String userAgent) { - webView.getSettings().setUserAgentString(userAgent); - } - - private void setZoomEnabled(boolean shouldEnable) { - webView.getSettings().setSupportZoom(shouldEnable); - } - - @Override - public void dispose() { - methodChannel.setMethodCallHandler(null); - if (webView instanceof InputAwareWebView) { - ((InputAwareWebView) webView).dispose(); - } - webView.destroy(); - } -} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java deleted file mode 100644 index a86d3e8a4b63..000000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.graphics.Bitmap; -import android.os.Build; -import android.util.Log; -import android.view.KeyEvent; -import android.webkit.WebResourceError; -import android.webkit.WebResourceRequest; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.webkit.WebResourceErrorCompat; -import androidx.webkit.WebViewClientCompat; -import io.flutter.plugin.common.MethodChannel; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -// We need to use WebViewClientCompat to get -// shouldOverrideUrlLoading(WebView view, WebResourceRequest request) -// invoked by the webview on older Android devices, without it pages that use iframes will -// be broken when a navigationDelegate is set on Android version earlier than N. -class FlutterWebViewClient { - private static final String TAG = "FlutterWebViewClient"; - private final MethodChannel methodChannel; - private boolean hasNavigationDelegate; - boolean hasProgressTracking; - - FlutterWebViewClient(MethodChannel methodChannel) { - this.methodChannel = methodChannel; - } - - static String errorCodeToString(int errorCode) { - switch (errorCode) { - case WebViewClient.ERROR_AUTHENTICATION: - return "authentication"; - case WebViewClient.ERROR_BAD_URL: - return "badUrl"; - case WebViewClient.ERROR_CONNECT: - return "connect"; - case WebViewClient.ERROR_FAILED_SSL_HANDSHAKE: - return "failedSslHandshake"; - case WebViewClient.ERROR_FILE: - return "file"; - case WebViewClient.ERROR_FILE_NOT_FOUND: - return "fileNotFound"; - case WebViewClient.ERROR_HOST_LOOKUP: - return "hostLookup"; - case WebViewClient.ERROR_IO: - return "io"; - case WebViewClient.ERROR_PROXY_AUTHENTICATION: - return "proxyAuthentication"; - case WebViewClient.ERROR_REDIRECT_LOOP: - return "redirectLoop"; - case WebViewClient.ERROR_TIMEOUT: - return "timeout"; - case WebViewClient.ERROR_TOO_MANY_REQUESTS: - return "tooManyRequests"; - case WebViewClient.ERROR_UNKNOWN: - return "unknown"; - case WebViewClient.ERROR_UNSAFE_RESOURCE: - return "unsafeResource"; - case WebViewClient.ERROR_UNSUPPORTED_AUTH_SCHEME: - return "unsupportedAuthScheme"; - case WebViewClient.ERROR_UNSUPPORTED_SCHEME: - return "unsupportedScheme"; - } - - final String message = - String.format(Locale.getDefault(), "Could not find a string for errorCode: %d", errorCode); - throw new IllegalArgumentException(message); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - if (!hasNavigationDelegate) { - return false; - } - notifyOnNavigationRequest( - request.getUrl().toString(), request.getRequestHeaders(), view, request.isForMainFrame()); - // We must make a synchronous decision here whether to allow the navigation or not, - // if the Dart code has set a navigation delegate we want that delegate to decide whether - // to navigate or not, and as we cannot get a response from the Dart delegate synchronously we - // return true here to block the navigation, if the Dart delegate decides to allow the - // navigation the plugin will later make an addition loadUrl call for this url. - // - // Since we cannot call loadUrl for a subframe, we currently only allow the delegate to stop - // navigations that target the main frame, if the request is not for the main frame - // we just return false to allow the navigation. - // - // For more details see: https://github.com/flutter/flutter/issues/25329#issuecomment-464863209 - return request.isForMainFrame(); - } - - boolean shouldOverrideUrlLoading(WebView view, String url) { - if (!hasNavigationDelegate) { - return false; - } - // This version of shouldOverrideUrlLoading is only invoked by the webview on devices with - // webview versions earlier than 67(it is also invoked when hasNavigationDelegate is false). - // On these devices we cannot tell whether the navigation is targeted to the main frame or not. - // We proceed assuming that the navigation is targeted to the main frame. If the page had any - // frames they will be loaded in the main frame instead. - Log.w( - TAG, - "Using a navigationDelegate with an old webview implementation, pages with frames or iframes will not work"); - notifyOnNavigationRequest(url, null, view, true); - return true; - } - - /** - * Notifies the Flutter code that a download should start when a navigation delegate is set. - * - * @param view the webView the result of the navigation delegate will be send to. - * @param url the download url - * @return A boolean whether or not the request is forwarded to the Flutter code. - */ - boolean notifyDownload(WebView view, String url) { - if (!hasNavigationDelegate) { - return false; - } - - notifyOnNavigationRequest(url, null, view, true); - return true; - } - - private void onPageStarted(WebView view, String url) { - Map args = new HashMap<>(); - args.put("url", url); - methodChannel.invokeMethod("onPageStarted", args); - } - - private void onPageFinished(WebView view, String url) { - Map args = new HashMap<>(); - args.put("url", url); - methodChannel.invokeMethod("onPageFinished", args); - } - - void onLoadingProgress(int progress) { - if (hasProgressTracking) { - Map args = new HashMap<>(); - args.put("progress", progress); - methodChannel.invokeMethod("onProgress", args); - } - } - - private void onWebResourceError( - final int errorCode, final String description, final String failingUrl) { - final Map args = new HashMap<>(); - args.put("errorCode", errorCode); - args.put("description", description); - args.put("errorType", FlutterWebViewClient.errorCodeToString(errorCode)); - args.put("failingUrl", failingUrl); - methodChannel.invokeMethod("onWebResourceError", args); - } - - private void notifyOnNavigationRequest( - String url, Map headers, WebView webview, boolean isMainFrame) { - HashMap args = new HashMap<>(); - args.put("url", url); - args.put("isForMainFrame", isMainFrame); - if (isMainFrame) { - methodChannel.invokeMethod( - "navigationRequest", args, new OnNavigationRequestResult(url, headers, webview)); - } else { - methodChannel.invokeMethod("navigationRequest", args); - } - } - - // This method attempts to avoid using WebViewClientCompat due to bug - // https://bugs.chromium.org/p/chromium/issues/detail?id=925887. Also, see - // https://github.com/flutter/flutter/issues/29446. - WebViewClient createWebViewClient(boolean hasNavigationDelegate) { - this.hasNavigationDelegate = hasNavigationDelegate; - - if (!hasNavigationDelegate || android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return internalCreateWebViewClient(); - } - - return internalCreateWebViewClientCompat(); - } - - private WebViewClient internalCreateWebViewClient() { - return new WebViewClient() { - @TargetApi(Build.VERSION_CODES.N) - @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request); - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - FlutterWebViewClient.this.onPageStarted(view, url); - } - - @Override - public void onPageFinished(WebView view, String url) { - FlutterWebViewClient.this.onPageFinished(view, url); - } - - @TargetApi(Build.VERSION_CODES.M) - @Override - public void onReceivedError( - WebView view, WebResourceRequest request, WebResourceError error) { - if (request.isForMainFrame()) { - FlutterWebViewClient.this.onWebResourceError( - error.getErrorCode(), error.getDescription().toString(), request.getUrl().toString()); - } - } - - @Override - public void onReceivedError( - WebView view, int errorCode, String description, String failingUrl) { - FlutterWebViewClient.this.onWebResourceError(errorCode, description, failingUrl); - } - - @Override - public void onUnhandledKeyEvent(WebView view, KeyEvent event) { - // Deliberately empty. Occasionally the webview will mark events as having failed to be - // handled even though they were handled. We don't want to propagate those as they're not - // truly lost. - } - }; - } - - private WebViewClientCompat internalCreateWebViewClientCompat() { - return new WebViewClientCompat() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, url); - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - FlutterWebViewClient.this.onPageStarted(view, url); - } - - @Override - public void onPageFinished(WebView view, String url) { - FlutterWebViewClient.this.onPageFinished(view, url); - } - - // This method is only called when the WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR feature is - // enabled. The deprecated method is called when a device doesn't support this. - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - @SuppressLint("RequiresFeature") - @Override - public void onReceivedError( - @NonNull WebView view, - @NonNull WebResourceRequest request, - @NonNull WebResourceErrorCompat error) { - if (request.isForMainFrame()) { - FlutterWebViewClient.this.onWebResourceError( - error.getErrorCode(), error.getDescription().toString(), request.getUrl().toString()); - } - } - - @Override - public void onReceivedError( - WebView view, int errorCode, String description, String failingUrl) { - FlutterWebViewClient.this.onWebResourceError(errorCode, description, failingUrl); - } - - @Override - public void onUnhandledKeyEvent(WebView view, KeyEvent event) { - // Deliberately empty. Occasionally the webview will mark events as having failed to be - // handled even though they were handled. We don't want to propagate those as they're not - // truly lost. - } - }; - } - - private static class OnNavigationRequestResult implements MethodChannel.Result { - private final String url; - private final Map headers; - private final WebView webView; - - private OnNavigationRequestResult(String url, Map headers, WebView webView) { - this.url = url; - this.headers = headers; - this.webView = webView; - } - - @Override - public void success(Object shouldLoad) { - Boolean typedShouldLoad = (Boolean) shouldLoad; - if (typedShouldLoad) { - loadUrl(); - } - } - - @Override - public void error(String errorCode, String s1, Object o) { - throw new IllegalStateException("navigationRequest calls must succeed"); - } - - @Override - public void notImplemented() { - throw new IllegalStateException( - "navigationRequest must be implemented by the webview method channel"); - } - - private void loadUrl() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - webView.loadUrl(url, headers); - } else { - webView.loadUrl(url); - } - } - } -} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewFactory.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewFactory.java index 8fe58104a0fb..9b3cd471bb83 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewFactory.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewFactory.java @@ -5,29 +5,24 @@ package io.flutter.plugins.webviewflutter; import android.content.Context; -import android.view.View; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.platform.PlatformView; import io.flutter.plugin.platform.PlatformViewFactory; -import java.util.Map; -public final class FlutterWebViewFactory extends PlatformViewFactory { - private final BinaryMessenger messenger; - private final View containerView; +class FlutterWebViewFactory extends PlatformViewFactory { + private final InstanceManager instanceManager; - FlutterWebViewFactory(BinaryMessenger messenger, View containerView) { + FlutterWebViewFactory(InstanceManager instanceManager) { super(StandardMessageCodec.INSTANCE); - this.messenger = messenger; - this.containerView = containerView; + this.instanceManager = instanceManager; } - @SuppressWarnings("unchecked") @Override public PlatformView create(Context context, int id, Object args) { - Map params = (Map) args; - MethodChannel methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id); - return new FlutterWebView(context, methodChannel, params, containerView); + final PlatformView view = (PlatformView) instanceManager.getInstance((Integer) args); + if (view == null) { + throw new IllegalStateException("Unable to find WebView instance: " + args); + } + return view; } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java index 96ae3ce1fd4d..ce6f2b81ed1c 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java @@ -9,8 +9,6 @@ import android.webkit.JavascriptInterface; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import io.flutter.plugin.common.MethodChannel; -import java.util.HashMap; /** * Added as a JavaScript interface to the WebView for any JavaScript channel that the Dart code sets @@ -22,24 +20,10 @@ *

No messages are sent to Dart after {@link JavaScriptChannel#release} is called. */ public class JavaScriptChannel implements Releasable { - private final MethodChannel methodChannel; private final Handler platformThreadHandler; final String javaScriptChannelName; @Nullable private JavaScriptChannelFlutterApiImpl flutterApi; - /** - * @param methodChannel the Flutter WebView method channel to which JS messages are sent - * @param javaScriptChannelName the name of the JavaScript channel, this is sent over the method - * channel with each message to let the Dart code know which JavaScript channel the message - * was sent through - */ - JavaScriptChannel( - MethodChannel methodChannel, String javaScriptChannelName, Handler platformThreadHandler) { - this.methodChannel = methodChannel; - this.javaScriptChannelName = javaScriptChannelName; - this.platformThreadHandler = platformThreadHandler; - } - /** * Creates a {@link JavaScriptChannel} that passes arguments of callback methods to Dart. * @@ -54,36 +38,23 @@ public JavaScriptChannel( this.flutterApi = flutterApi; this.javaScriptChannelName = channelName; this.platformThreadHandler = platformThreadHandler; - methodChannel = null; } // Suppressing unused warning as this is invoked from JavaScript. @SuppressWarnings("unused") @JavascriptInterface public void postMessage(final String message) { - Runnable postMessageRunnable = null; - - if (flutterApi != null) { - postMessageRunnable = () -> flutterApi.postMessage(this, message, reply -> {}); - } else if (methodChannel != null) { - postMessageRunnable = - new Runnable() { - @Override - public void run() { - HashMap arguments = new HashMap<>(); - arguments.put("channel", javaScriptChannelName); - arguments.put("message", message); - methodChannel.invokeMethod("javascriptChannelMessage", arguments); - } - }; - } + final Runnable postMessageRunnable = + () -> { + if (flutterApi != null) { + flutterApi.postMessage(JavaScriptChannel.this, message, reply -> {}); + } + }; - if (postMessageRunnable != null) { - if (platformThreadHandler.getLooper() == Looper.myLooper()) { - postMessageRunnable.run(); - } else { - platformThreadHandler.post(postMessageRunnable); - } + if (platformThreadHandler.getLooper() == Looper.myLooper()) { + postMessageRunnable.run(); + } else { + platformThreadHandler.post(postMessageRunnable); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java index ac67a817179e..3055c9fc7f40 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java @@ -16,7 +16,8 @@ public class JavaScriptChannelHostApiImpl implements JavaScriptChannelHostApi { private final InstanceManager instanceManager; private final JavaScriptChannelCreator javaScriptChannelCreator; private final JavaScriptChannelFlutterApiImpl flutterApi; - private final Handler platformThreadHandler; + + private Handler platformThreadHandler; /** Handles creating {@link JavaScriptChannel}s for a {@link JavaScriptChannelHostApiImpl}. */ public static class JavaScriptChannelCreator { @@ -55,6 +56,15 @@ public JavaScriptChannelHostApiImpl( this.platformThreadHandler = platformThreadHandler; } + /** + * Sets the platformThreadHandler to make callbacks + * + * @param platformThreadHandler the new thread handler + */ + public void setPlatformThreadHandler(Handler platformThreadHandler) { + this.platformThreadHandler = platformThreadHandler; + } + @Override public void create(Long instanceId, String channelName) { final JavaScriptChannel javaScriptChannel = diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java index f8bc5129a3ca..d2e1e59ce179 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java @@ -13,6 +13,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import androidx.annotation.VisibleForTesting; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientHostApi; /** @@ -44,32 +45,56 @@ public WebChromeClientImpl( this.webViewClient = webViewClient; } - // Verifies that a url opened by `Window.open` has a secure url. @Override public boolean onCreateWindow( final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { - final WebViewClient newWindowWebViewClient = + return onCreateWindow(view, resultMsg, new WebView(view.getContext())); + } + + /** + * Verifies that a url opened by `Window.open` has a secure url. + * + * @param view the WebView from which the request for a new window originated. + * @param resultMsg the message to send when once a new WebView has been created. resultMsg.obj + * is a {@link WebView.WebViewTransport} object. This should be used to transport the new + * WebView, by calling WebView.WebViewTransport.setWebView(WebView) + * @param onCreateWindowWebView the temporary WebView used to verify the url is secure + * @return this method should return true if the host application will create a new window, in + * which case resultMsg should be sent to its target. Otherwise, this method should return + * false. Returning false from this method but also sending resultMsg will result in + * undefined behavior + */ + @VisibleForTesting + boolean onCreateWindow( + final WebView view, Message resultMsg, @Nullable WebView onCreateWindowWebView) { + final WebViewClient windowWebViewClient = new WebViewClient() { @RequiresApi(api = Build.VERSION_CODES.N) @Override public boolean shouldOverrideUrlLoading( - @NonNull WebView view, @NonNull WebResourceRequest request) { - webViewClient.shouldOverrideUrlLoading(view, request); + @NonNull WebView windowWebView, @NonNull WebResourceRequest request) { + if (!webViewClient.shouldOverrideUrlLoading(view, request)) { + view.loadUrl(request.getUrl().toString()); + } return true; } @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - webViewClient.shouldOverrideUrlLoading(view, url); + public boolean shouldOverrideUrlLoading(WebView windowWebView, String url) { + if (!webViewClient.shouldOverrideUrlLoading(view, url)) { + view.loadUrl(url); + } return true; } }; - final WebView newWebView = new WebView(view.getContext()); - newWebView.setWebViewClient(newWindowWebViewClient); + if (onCreateWindowWebView == null) { + onCreateWindowWebView = new WebView(view.getContext()); + } + onCreateWindowWebView.setWebViewClient(windowWebViewClient); final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; - transport.setWebView(newWebView); + transport.setWebView(onCreateWindowWebView); resultMsg.sendToTarget(); return true; diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewBuilder.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewBuilder.java deleted file mode 100644 index e0d5e8815f31..000000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewBuilder.java +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import android.content.Context; -import android.view.View; -import android.webkit.DownloadListener; -import android.webkit.WebChromeClient; -import android.webkit.WebSettings; -import android.webkit.WebView; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** Builder used to create {@link android.webkit.WebView} objects. */ -public class WebViewBuilder { - - /** Factory used to create a new {@link android.webkit.WebView} instance. */ - static class WebViewFactory { - - /** - * Creates a new {@link android.webkit.WebView} instance. - * - * @param context an Activity Context to access application assets. This value cannot be null. - * @param usesHybridComposition If {@code false} a {@link InputAwareWebView} instance is - * returned. - * @param containerView must be supplied when the {@code useHybridComposition} parameter is set - * to {@code false}. Used to create an InputConnection on the WebView's dedicated input, or - * IME, thread (see also {@link InputAwareWebView}) - * @return A new instance of the {@link android.webkit.WebView} object. - */ - static WebView create(Context context, boolean usesHybridComposition, View containerView) { - return usesHybridComposition - ? new WebView(context) - : new InputAwareWebView(context, containerView); - } - } - - private final Context context; - private final View containerView; - - private boolean enableDomStorage; - private boolean javaScriptCanOpenWindowsAutomatically; - private boolean supportMultipleWindows; - private boolean usesHybridComposition; - private WebChromeClient webChromeClient; - private DownloadListener downloadListener; - private boolean enableBuiltInZoomControls; - - /** - * Constructs a new {@link WebViewBuilder} object with a custom implementation of the {@link - * WebViewFactory} object. - * - * @param context an Activity Context to access application assets. This value cannot be null. - * @param containerView must be supplied when the {@code useHybridComposition} parameter is set to - * {@code false}. Used to create an InputConnection on the WebView's dedicated input, or IME, - * thread (see also {@link InputAwareWebView}) - */ - WebViewBuilder(@NonNull final Context context, View containerView) { - this.context = context; - this.containerView = containerView; - } - - /** - * Sets whether the DOM storage API is enabled. The default value is {@code false}. - * - * @param flag {@code true} is {@link android.webkit.WebView} should use the DOM storage API. - * @return This builder. This value cannot be {@code null}. - */ - public WebViewBuilder setDomStorageEnabled(boolean flag) { - this.enableDomStorage = flag; - return this; - } - - /** - * Sets whether JavaScript is allowed to open windows automatically. This applies to the - * JavaScript function {@code window.open()}. The default value is {@code false}. - * - * @param flag {@code true} if JavaScript is allowed to open windows automatically. - * @return This builder. This value cannot be {@code null}. - */ - public WebViewBuilder setJavaScriptCanOpenWindowsAutomatically(boolean flag) { - this.javaScriptCanOpenWindowsAutomatically = flag; - return this; - } - - /** - * Sets whether the {@link WebView} supports multiple windows. If set to {@code true}, {@link - * WebChromeClient#onCreateWindow} must be implemented by the host application. The default is - * {@code false}. - * - * @param flag {@code true} if multiple windows are supported. - * @return This builder. This value cannot be {@code null}. - */ - public WebViewBuilder setSupportMultipleWindows(boolean flag) { - this.supportMultipleWindows = flag; - return this; - } - - /** - * Sets whether the hybrid composition should be used. - * - *

If set to {@code true} a standard {@link WebView} is created. If set to {@code false} the - * {@link WebViewBuilder} will create a {@link InputAwareWebView} to workaround issues using the - * {@link WebView} on Android versions below N. - * - * @param flag {@code true} if uses hybrid composition. The default is {@code false}. - * @return This builder. This value cannot be {@code null} - */ - public WebViewBuilder setUsesHybridComposition(boolean flag) { - this.usesHybridComposition = flag; - return this; - } - - /** - * Sets the chrome handler. This is an implementation of WebChromeClient for use in handling - * JavaScript dialogs, favicons, titles, and the progress. This will replace the current handler. - * - * @param webChromeClient an implementation of WebChromeClient This value may be null. - * @return This builder. This value cannot be {@code null}. - */ - public WebViewBuilder setWebChromeClient(@Nullable WebChromeClient webChromeClient) { - this.webChromeClient = webChromeClient; - return this; - } - - /** - * Registers the interface to be used when content can not be handled by the rendering engine, and - * should be downloaded instead. This will replace the current handler. - * - * @param downloadListener an implementation of DownloadListener This value may be null. - * @return This builder. This value cannot be {@code null}. - */ - public WebViewBuilder setDownloadListener(@Nullable DownloadListener downloadListener) { - this.downloadListener = downloadListener; - return this; - } - - /** - * Sets whether the {@link WebView} should use its built-in zoom mechanisms. The default value is - * {@code true}. - * - * @param flag {@code true} if built in zoom controls are allowed. - * @return This builder. This value cannot be {@code null}. - */ - public WebViewBuilder setZoomControlsEnabled(boolean flag) { - this.enableBuiltInZoomControls = flag; - return this; - } - - /** - * Build the {@link android.webkit.WebView} using the current settings. - * - * @return The {@link android.webkit.WebView} using the current settings. - */ - public WebView build() { - WebView webView = WebViewFactory.create(context, usesHybridComposition, containerView); - - WebSettings webSettings = webView.getSettings(); - webSettings.setDomStorageEnabled(enableDomStorage); - webSettings.setJavaScriptCanOpenWindowsAutomatically(javaScriptCanOpenWindowsAutomatically); - webSettings.setSupportMultipleWindows(supportMultipleWindows); - webSettings.setLoadWithOverviewMode(true); - webSettings.setUseWideViewPort(true); - webSettings.setDisplayZoomControls(false); - webSettings.setBuiltInZoomControls(enableBuiltInZoomControls); - webView.setWebChromeClient(webChromeClient); - webView.setDownloadListener(downloadListener); - return webView; - } -} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java index 268d35a1e04c..2b174ff35e6f 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java @@ -4,20 +4,34 @@ package io.flutter.plugins.webviewflutter; +import android.content.Context; +import android.os.Handler; +import android.view.View; +import androidx.annotation.NonNull; import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.platform.PlatformViewRegistry; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerHostApi; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelHostApi; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientHostApi; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebSettingsHostApi; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewClientHostApi; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewHostApi; /** * Java platform implementation of the webview_flutter plugin. * *

Register this in an add to app scenario to gracefully handle activity and context changes. * - *

Call {@link #registerWith(Registrar)} to use the stable {@code io.flutter.plugin.common} - * package instead. + *

Call {@link #registerWith} to use the stable {@code io.flutter.plugin.common} package instead. */ -public class WebViewFlutterPlugin implements FlutterPlugin { - +public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware { + private FlutterPluginBinding pluginBinding; private FlutterCookieManager flutterCookieManager; + private WebViewHostApiImpl webViewHostApi; + private JavaScriptChannelHostApiImpl javaScriptChannelHostApi; /** * Add an instance of this to {@link io.flutter.embedding.engine.plugins.PluginRegistry} to @@ -26,7 +40,7 @@ public class WebViewFlutterPlugin implements FlutterPlugin { *

THIS PLUGIN CODE PATH DEPENDS ON A NEWER VERSION OF FLUTTER THAN THE ONE DEFINED IN THE * PUBSPEC.YAML. Text input will fail on some Android devices unless this is used with at least * flutter/flutter@1d4d63ace1f801a022ea9ec737bf8c15395588b9. Use the V1 embedding with {@link - * #registerWith(Registrar)} to use this plugin with older Flutter versions. + * #registerWith} to use this plugin with older Flutter versions. * *

Registration should eventually be handled automatically by v2 of the * GeneratedPluginRegistrant. https://github.com/flutter/flutter/issues/42694 @@ -38,31 +52,79 @@ public WebViewFlutterPlugin() {} * package. * *

Calling this automatically initializes the plugin. However plugins initialized this way - * won't react to changes in activity or context, unlike {@link CameraPlugin}. + * won't react to changes in activity or context, unlike {@link WebViewFlutterPlugin}. */ - @SuppressWarnings("deprecation") + @SuppressWarnings({"unused", "deprecation"}) public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - registrar - .platformViewRegistry() - .registerViewFactory( - "plugins.flutter.io/webview", - new FlutterWebViewFactory(registrar.messenger(), registrar.view())); + new WebViewFlutterPlugin() + .setUp( + registrar.messenger(), + registrar.platformViewRegistry(), + registrar.activity(), + registrar.view()); new FlutterCookieManager(registrar.messenger()); } + private void setUp( + BinaryMessenger binaryMessenger, + PlatformViewRegistry viewRegistry, + Context context, + View containerView) { + new FlutterCookieManager(binaryMessenger); + + InstanceManager instanceManager = new InstanceManager(); + + viewRegistry.registerViewFactory( + "plugins.flutter.io/webview", new FlutterWebViewFactory(instanceManager)); + + webViewHostApi = + new WebViewHostApiImpl( + instanceManager, new WebViewHostApiImpl.WebViewProxy(), context, containerView); + javaScriptChannelHostApi = + new JavaScriptChannelHostApiImpl( + instanceManager, + new JavaScriptChannelHostApiImpl.JavaScriptChannelCreator(), + new JavaScriptChannelFlutterApiImpl(binaryMessenger, instanceManager), + new Handler(context.getMainLooper())); + + WebViewHostApi.setup(binaryMessenger, webViewHostApi); + JavaScriptChannelHostApi.setup(binaryMessenger, javaScriptChannelHostApi); + WebViewClientHostApi.setup( + binaryMessenger, + new WebViewClientHostApiImpl( + instanceManager, + new WebViewClientHostApiImpl.WebViewClientCreator(), + new WebViewClientFlutterApiImpl(binaryMessenger, instanceManager))); + WebChromeClientHostApi.setup( + binaryMessenger, + new WebChromeClientHostApiImpl( + instanceManager, + new WebChromeClientHostApiImpl.WebChromeClientCreator(), + new WebChromeClientFlutterApiImpl(binaryMessenger, instanceManager))); + DownloadListenerHostApi.setup( + binaryMessenger, + new DownloadListenerHostApiImpl( + instanceManager, + new DownloadListenerHostApiImpl.DownloadListenerCreator(), + new DownloadListenerFlutterApiImpl(binaryMessenger, instanceManager))); + WebSettingsHostApi.setup( + binaryMessenger, + new WebSettingsHostApiImpl( + instanceManager, new WebSettingsHostApiImpl.WebSettingsCreator())); + } + @Override - public void onAttachedToEngine(FlutterPluginBinding binding) { - BinaryMessenger messenger = binding.getBinaryMessenger(); - binding - .getPlatformViewRegistry() - .registerViewFactory( - "plugins.flutter.io/webview", - new FlutterWebViewFactory(messenger, /*containerView=*/ null)); - flutterCookieManager = new FlutterCookieManager(messenger); + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + pluginBinding = binding; + setUp( + binding.getBinaryMessenger(), + binding.getPlatformViewRegistry(), + binding.getApplicationContext(), + null); } @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) { + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { if (flutterCookieManager == null) { return; } @@ -70,4 +132,30 @@ public void onDetachedFromEngine(FlutterPluginBinding binding) { flutterCookieManager.dispose(); flutterCookieManager = null; } + + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { + updateContext(activityPluginBinding.getActivity()); + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + updateContext(pluginBinding.getApplicationContext()); + } + + @Override + public void onReattachedToActivityForConfigChanges( + @NonNull ActivityPluginBinding activityPluginBinding) { + updateContext(activityPluginBinding.getActivity()); + } + + @Override + public void onDetachedFromActivity() { + updateContext(pluginBinding.getApplicationContext()); + } + + private void updateContext(Context context) { + webViewHostApi.setContext(context); + javaScriptChannelHostApi.setPlatformThreadHandler(new Handler(context.getMainLooper())); + } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java index 2d96fc3f8a60..86bef0fab563 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java @@ -35,10 +35,11 @@ public class WebViewHostApiImpl implements WebViewHostApi { private final InstanceManager instanceManager; private final WebViewProxy webViewProxy; - private final Context context; // Only used with WebView using virtual displays. @Nullable private final View containerView; + private Context context; + /** Handles creating and calling static methods for {@link WebView}s. */ public static class WebViewProxy { /** @@ -317,6 +318,15 @@ public WebViewHostApiImpl( this.containerView = containerView; } + /** + * Sets the context to construct {@link WebView}s. + * + * @param context the new context. + */ + public void setContext(Context context) { + this.context = context; + } + @Override public void create(Long instanceId, Boolean useHybridComposition) { DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy(); @@ -335,9 +345,10 @@ public void create(Long instanceId, Boolean useHybridComposition) { @Override public void dispose(Long instanceId) { - final WebView instance = (WebView) instanceManager.removeInstanceWithId(instanceId); + final WebView instance = (WebView) instanceManager.getInstance(instanceId); if (instance != null) { ((Releasable) instance).release(); + instanceManager.removeInstance(instance); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterDownloadListenerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterDownloadListenerTest.java deleted file mode 100644 index 2c918584ba83..000000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterDownloadListenerTest.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.webkit.WebView; -import org.junit.Before; -import org.junit.Test; - -public class FlutterDownloadListenerTest { - private FlutterWebViewClient webViewClient; - private WebView webView; - - @Before - public void before() { - webViewClient = mock(FlutterWebViewClient.class); - webView = mock(WebView.class); - } - - @Test - public void onDownloadStart_should_notify_webViewClient() { - String url = "testurl.com"; - FlutterDownloadListener downloadListener = new FlutterDownloadListener(webViewClient); - downloadListener.onDownloadStart(url, "test", "inline", "data/text", 0); - verify(webViewClient).notifyDownload(nullable(WebView.class), eq(url)); - } - - @Test - public void onDownloadStart_should_pass_webView() { - FlutterDownloadListener downloadListener = new FlutterDownloadListener(webViewClient); - downloadListener.setWebView(webView); - downloadListener.onDownloadStart("testurl.com", "test", "inline", "data/text", 0); - verify(webViewClient).notifyDownload(eq(webView), anyString()); - } -} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewClientTest.java deleted file mode 100644 index 86346ac08f16..000000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewClientTest.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -import android.webkit.WebView; -import io.flutter.plugin.common.MethodChannel; -import java.util.HashMap; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -public class FlutterWebViewClientTest { - - MethodChannel mockMethodChannel; - WebView mockWebView; - - @Before - public void before() { - mockMethodChannel = mock(MethodChannel.class); - mockWebView = mock(WebView.class); - } - - @Test - public void notify_download_should_notifyOnNavigationRequest_when_navigationDelegate_is_set() { - final String url = "testurl.com"; - - FlutterWebViewClient client = new FlutterWebViewClient(mockMethodChannel); - client.createWebViewClient(true); - - client.notifyDownload(mockWebView, url); - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Object.class); - verify(mockMethodChannel) - .invokeMethod( - eq("navigationRequest"), argumentCaptor.capture(), any(MethodChannel.Result.class)); - HashMap map = (HashMap) argumentCaptor.getValue(); - assertEquals(map.get("url"), url); - assertEquals(map.get("isForMainFrame"), true); - } - - @Test - public void - notify_download_should_not_notifyOnNavigationRequest_when_navigationDelegate_is_not_set() { - final String url = "testurl.com"; - - FlutterWebViewClient client = new FlutterWebViewClient(mockMethodChannel); - client.createWebViewClient(false); - - client.notifyDownload(mockWebView, url); - verifyNoInteractions(mockMethodChannel); - } -} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java deleted file mode 100644 index f26a0ea6b9cc..000000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.webkit.DownloadListener; -import android.webkit.WebChromeClient; -import android.webkit.WebView; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import java.util.HashMap; -import java.util.Map; -import org.junit.Before; -import org.junit.Test; -import org.mockito.MockedStatic; - -public class FlutterWebViewTest { - private WebChromeClient mockWebChromeClient; - private DownloadListener mockDownloadListener; - private WebViewBuilder mockWebViewBuilder; - private WebView mockWebView; - private MethodChannel.Result mockResult; - private Context mockContext; - private MethodChannel mockMethodChannel; - - @Before - public void before() { - - mockWebChromeClient = mock(WebChromeClient.class); - mockWebViewBuilder = mock(WebViewBuilder.class); - mockWebView = mock(WebView.class); - mockDownloadListener = mock(DownloadListener.class); - mockResult = mock(MethodChannel.Result.class); - mockContext = mock(Context.class); - mockMethodChannel = mock(MethodChannel.class); - - when(mockWebViewBuilder.setDomStorageEnabled(anyBoolean())).thenReturn(mockWebViewBuilder); - when(mockWebViewBuilder.setJavaScriptCanOpenWindowsAutomatically(anyBoolean())) - .thenReturn(mockWebViewBuilder); - when(mockWebViewBuilder.setSupportMultipleWindows(anyBoolean())).thenReturn(mockWebViewBuilder); - when(mockWebViewBuilder.setUsesHybridComposition(anyBoolean())).thenReturn(mockWebViewBuilder); - when(mockWebViewBuilder.setZoomControlsEnabled(anyBoolean())).thenReturn(mockWebViewBuilder); - when(mockWebViewBuilder.setWebChromeClient(any(WebChromeClient.class))) - .thenReturn(mockWebViewBuilder); - when(mockWebViewBuilder.setDownloadListener(any(DownloadListener.class))) - .thenReturn(mockWebViewBuilder); - when(mockWebViewBuilder.build()).thenReturn(mockWebView); - } - - @Test - public void createWebView_shouldCreateWebViewWithDefaultConfiguration() { - FlutterWebView.createWebView( - mockWebViewBuilder, createParameterMap(false), mockWebChromeClient, mockDownloadListener); - - verify(mockWebViewBuilder, times(1)).setDomStorageEnabled(true); - verify(mockWebViewBuilder, times(1)).setJavaScriptCanOpenWindowsAutomatically(true); - verify(mockWebViewBuilder, times(1)).setSupportMultipleWindows(true); - verify(mockWebViewBuilder, times(1)).setUsesHybridComposition(false); - verify(mockWebViewBuilder, times(1)).setWebChromeClient(mockWebChromeClient); - verify(mockWebViewBuilder, times(1)).setZoomControlsEnabled(true); - } - - @Test(expected = UnsupportedOperationException.class) - public void evaluateJavaScript_shouldThrowForNullString() { - try (MockedStatic mockedFlutterWebView = mockStatic(FlutterWebView.class)) { - // Setup - mockedFlutterWebView - .when( - new MockedStatic.Verification() { - @Override - public void apply() throws Throwable { - FlutterWebView.createWebView( - (WebViewBuilder) any(), - (Map) any(), - (WebChromeClient) any(), - (DownloadListener) any()); - } - }) - .thenReturn(mockWebView); - FlutterWebView flutterWebView = - new FlutterWebView(mockContext, mockMethodChannel, new HashMap(), null); - - // Run - flutterWebView.onMethodCall(new MethodCall("runJavascript", null), mockResult); - } - } - - @Test - public void evaluateJavaScript_shouldReturnValueOnSuccessForReturnValue() { - try (MockedStatic mockedFlutterWebView = mockStatic(FlutterWebView.class)) { - // Setup - mockedFlutterWebView - .when( - () -> - FlutterWebView.createWebView( - (WebViewBuilder) any(), - (Map) any(), - (WebChromeClient) any(), - (DownloadListener) any())) - .thenReturn(mockWebView); - doAnswer( - invocation -> { - android.webkit.ValueCallback callback = invocation.getArgument(1); - callback.onReceiveValue("Test JavaScript Result"); - return null; - }) - .when(mockWebView) - .evaluateJavascript(eq("Test JavaScript String"), any()); - FlutterWebView flutterWebView = - new FlutterWebView(mockContext, mockMethodChannel, new HashMap(), null); - - // Run - flutterWebView.onMethodCall( - new MethodCall("runJavascriptReturningResult", "Test JavaScript String"), mockResult); - - // Verify - verify(mockResult, times(1)).success("Test JavaScript Result"); - } - } - - @Test - public void evaluateJavaScript_shouldReturnNilOnSuccessForNoReturnValue() { - try (MockedStatic mockedFlutterWebView = mockStatic(FlutterWebView.class)) { - // Setup - mockedFlutterWebView - .when( - () -> - FlutterWebView.createWebView( - (WebViewBuilder) any(), - (Map) any(), - (WebChromeClient) any(), - (DownloadListener) any())) - .thenReturn(mockWebView); - doAnswer( - invocation -> { - android.webkit.ValueCallback callback = invocation.getArgument(1); - callback.onReceiveValue("Test JavaScript Result"); - return null; - }) - .when(mockWebView) - .evaluateJavascript(eq("Test JavaScript String"), any()); - FlutterWebView flutterWebView = - new FlutterWebView(mockContext, mockMethodChannel, new HashMap(), null); - - // Run - flutterWebView.onMethodCall( - new MethodCall("runJavascript", "Test JavaScript String"), mockResult); - - // Verify - verify(mockResult, times(1)).success(isNull()); - } - } - - private Map createParameterMap(boolean usesHybridComposition) { - Map params = new HashMap<>(); - params.put("usesHybridComposition", usesHybridComposition); - - return params; - } -} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java index d6593ab24462..63cd31043799 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java @@ -4,20 +4,29 @@ package io.flutter.plugins.webviewflutter; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.net.Uri; +import android.os.Message; import android.webkit.WebChromeClient; +import android.webkit.WebResourceRequest; import android.webkit.WebView; +import android.webkit.WebView.WebViewTransport; import android.webkit.WebViewClient; import io.flutter.plugins.webviewflutter.WebChromeClientHostApiImpl.WebChromeClientCreator; import io.flutter.plugins.webviewflutter.WebChromeClientHostApiImpl.WebChromeClientImpl; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -66,4 +75,43 @@ public void onProgressChanged() { webChromeClient.onProgressChanged(mockWebView, 11); verify(mockFlutterApi, never()).onProgressChanged((WebChromeClient) any(), any(), any(), any()); } + + @Test + public void onCreateWindow() { + final WebView mockOnCreateWindowWebView = mock(WebView.class); + + // Create a fake message to transport requests to onCreateWindowWebView. + final Message message = new Message(); + message.obj = mock(WebViewTransport.class); + + assertTrue(webChromeClient.onCreateWindow(mockWebView, message, mockOnCreateWindowWebView)); + + /// Capture the WebViewClient used with onCreateWindow WebView. + final ArgumentCaptor webViewClientCaptor = + ArgumentCaptor.forClass(WebViewClient.class); + verify(mockOnCreateWindowWebView).setWebViewClient(webViewClientCaptor.capture()); + final WebViewClient onCreateWindowWebViewClient = webViewClientCaptor.getValue(); + assertNotNull(onCreateWindowWebViewClient); + + /// Create a WebResourceRequest with a Uri. + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.getUrl()).thenReturn(mock(Uri.class)); + when(mockRequest.getUrl().toString()).thenReturn("https://www.google.com"); + + // Test when the forwarding WebViewClient is overriding all url loading. + when(mockWebViewClient.shouldOverrideUrlLoading(any(), any(WebResourceRequest.class))) + .thenReturn(true); + assertTrue( + onCreateWindowWebViewClient.shouldOverrideUrlLoading( + mockOnCreateWindowWebView, mockRequest)); + verify(mockWebView, never()).loadUrl(any()); + + // Test when the forwarding WebViewClient is NOT overriding all url loading. + when(mockWebViewClient.shouldOverrideUrlLoading(any(), any(WebResourceRequest.class))) + .thenReturn(false); + assertTrue( + onCreateWindowWebViewClient.shouldOverrideUrlLoading( + mockOnCreateWindowWebView, mockRequest)); + verify(mockWebView).loadUrl("https://www.google.com"); + } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewBuilderTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewBuilderTest.java deleted file mode 100644 index 423cb210c392..000000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewBuilderTest.java +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.*; - -import android.content.Context; -import android.view.View; -import android.webkit.DownloadListener; -import android.webkit.WebChromeClient; -import android.webkit.WebSettings; -import android.webkit.WebView; -import io.flutter.plugins.webviewflutter.WebViewBuilder.WebViewFactory; -import java.io.IOException; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.MockedStatic; -import org.mockito.MockedStatic.Verification; - -public class WebViewBuilderTest { - private Context mockContext; - private View mockContainerView; - private WebView mockWebView; - private MockedStatic mockedStaticWebViewFactory; - - @Before - public void before() { - mockContext = mock(Context.class); - mockContainerView = mock(View.class); - mockWebView = mock(WebView.class); - mockedStaticWebViewFactory = mockStatic(WebViewFactory.class); - - mockedStaticWebViewFactory - .when( - new Verification() { - @Override - public void apply() { - WebViewFactory.create(mockContext, false, mockContainerView); - } - }) - .thenReturn(mockWebView); - } - - @After - public void after() { - mockedStaticWebViewFactory.close(); - } - - @Test - public void ctor_test() { - WebViewBuilder builder = new WebViewBuilder(mockContext, mockContainerView); - - assertNotNull(builder); - } - - @Test - public void build_should_set_values() throws IOException { - WebSettings mockWebSettings = mock(WebSettings.class); - WebChromeClient mockWebChromeClient = mock(WebChromeClient.class); - DownloadListener mockDownloadListener = mock(DownloadListener.class); - - when(mockWebView.getSettings()).thenReturn(mockWebSettings); - - WebViewBuilder builder = - new WebViewBuilder(mockContext, mockContainerView) - .setDomStorageEnabled(true) - .setJavaScriptCanOpenWindowsAutomatically(true) - .setSupportMultipleWindows(true) - .setWebChromeClient(mockWebChromeClient) - .setDownloadListener(mockDownloadListener); - - WebView webView = builder.build(); - - assertNotNull(webView); - verify(mockWebSettings).setDomStorageEnabled(true); - verify(mockWebSettings).setJavaScriptCanOpenWindowsAutomatically(true); - verify(mockWebSettings).setSupportMultipleWindows(true); - verify(mockWebView).setWebChromeClient(mockWebChromeClient); - verify(mockWebView).setDownloadListener(mockDownloadListener); - } - - @Test - public void build_should_use_default_values() throws IOException { - WebSettings mockWebSettings = mock(WebSettings.class); - WebChromeClient mockWebChromeClient = mock(WebChromeClient.class); - - when(mockWebView.getSettings()).thenReturn(mockWebSettings); - - WebViewBuilder builder = new WebViewBuilder(mockContext, mockContainerView); - - WebView webView = builder.build(); - - assertNotNull(webView); - verify(mockWebSettings).setDomStorageEnabled(false); - verify(mockWebSettings).setJavaScriptCanOpenWindowsAutomatically(false); - verify(mockWebSettings).setSupportMultipleWindows(false); - verify(mockWebView).setWebChromeClient(null); - verify(mockWebView).setDownloadListener(null); - } -} diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index 454e2c959680..91fc860dffae 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart @@ -192,7 +192,6 @@ void main() { bool resizeButtonTapped = false; await tester.pumpWidget(ResizableWebView( onResize: (_) { - print(resizeButtonTapped); if (resizeButtonTapped) { buttonTapResizeCompleter.complete(); } else { @@ -202,10 +201,14 @@ void main() { onPageFinished: () => onPageFinished.complete(), )); await onPageFinished.future; - await initialResizeCompleter.future; + // Wait for a potential call to resize after page is loaded. + await initialResizeCompleter.future.timeout( + const Duration(seconds: 3), + onTimeout: () => null, + ); resizeButtonTapped = true; - await tester.tap(find.byKey(const ValueKey('resizeButton'))); + await tester.tap(find.byKey(const ValueKey('resizeButton'))); await tester.pumpAndSettle(); expect(buttonTapResizeCompleter.future, completes); }); @@ -461,10 +464,10 @@ void main() { testWidgets('Video plays inline when allowsInlineMediaPlayback is true', (WidgetTester tester) async { - Completer controllerCompleter = + final Completer controllerCompleter = Completer(); - Completer pageLoaded = Completer(); - Completer videoPlaying = Completer(); + final Completer pageLoaded = Completer(); + final Completer videoPlaying = Completer(); await tester.pumpWidget( Directionality( @@ -495,7 +498,7 @@ void main() { ), ), ); - WebViewController controller = await controllerCompleter.future; + final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; // Pump once to trigger the video play. @@ -504,7 +507,7 @@ void main() { // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; - String fullScreen = + final String fullScreen = await controller.runJavascriptReturningResult('isFullScreen();'); expect(fullScreen, _webviewBool(false)); }); @@ -682,7 +685,7 @@ void main() { }); testWidgets('getTitle', (WidgetTester tester) async { - final String getTitleTest = ''' + const String getTitleTest = ''' Some title @@ -726,7 +729,7 @@ void main() { group('Programmatic Scroll', () { // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757. testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { - final String scrollTestPage = ''' + const String scrollTestPage = ''' @@ -773,7 +776,7 @@ void main() { final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; - await tester.pumpAndSettle(Duration(seconds: 3)); + await tester.pumpAndSettle(const Duration(seconds: 3)); int scrollPosX = await controller.getScrollX(); int scrollPosY = await controller.getScrollY(); @@ -813,7 +816,7 @@ void main() { // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757. testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { - final String scrollTestPage = ''' + const String scrollTestPage = ''' @@ -860,7 +863,7 @@ void main() { final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; - await tester.pumpAndSettle(Duration(seconds: 3)); + await tester.pumpAndSettle(const Duration(seconds: 3)); // Check scrollTo() const int X_SCROLL = 123; @@ -883,7 +886,7 @@ void main() { // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757. testWidgets('inputs are scrolled into view when focused', (WidgetTester tester) async { - final String scrollTestPage = ''' + const String scrollTestPage = ''' @@ -935,7 +938,7 @@ void main() { ), ), ); - await Future.delayed(Duration(milliseconds: 20)); + await Future.delayed(const Duration(milliseconds: 20)); await tester.pump(); }); @@ -944,7 +947,7 @@ void main() { final String viewportRectJSON = await _runJavaScriptReturningResult( controller, 'JSON.stringify(viewport.getBoundingClientRect())'); final Map viewportRectRelativeToViewport = - jsonDecode(viewportRectJSON); + jsonDecode(viewportRectJSON) as Map; // Check that the input is originally outside of the viewport. @@ -952,7 +955,7 @@ void main() { await _runJavaScriptReturningResult( controller, 'JSON.stringify(inputEl.getBoundingClientRect())'); final Map initialInputClientRectRelativeToViewport = - jsonDecode(initialInputClientRectJSON); + jsonDecode(initialInputClientRectJSON) as Map; expect( initialInputClientRectRelativeToViewport['bottom'] <= @@ -967,7 +970,7 @@ void main() { await _runJavaScriptReturningResult( controller, 'JSON.stringify(inputEl.getBoundingClientRect())'); final Map lastInputClientRectRelativeToViewport = - jsonDecode(lastInputClientRectJSON); + jsonDecode(lastInputClientRectJSON) as Map; expect( lastInputClientRectRelativeToViewport['top'] >= @@ -990,7 +993,7 @@ void main() { }); group('NavigationDelegate', () { - final String blankPage = ""; + const String blankPage = ''; final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + base64Encode(const Utf8Encoder().convert(blankPage)); @@ -1081,7 +1084,7 @@ void main() { testWidgets( 'onWebResourceError only called for main frame', (WidgetTester tester) async { - final String iframeTest = ''' + const String iframeTest = ''' @@ -1294,7 +1297,7 @@ void main() { testWidgets( 'JavaScript does not run in parent window', (WidgetTester tester) async { - final String iframe = ''' + const String iframe = '''