From 3350c717191a984fce4a0b3529cd158682780a51 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Wed, 10 Nov 2021 17:51:08 -0800 Subject: [PATCH 01/33] implementation of webview_widget --- .../webviewflutter/FlutterWebViewFactory.java | 25 +- .../webviewflutter/WebViewFlutterPlugin.java | 108 +++- .../lib/webview_android.dart | 64 ++- .../lib/webview_surface_android.dart | 80 +-- .../lib/webview_widget.dart | 506 ++++++++++++++++++ .../test/webview_widget_test.dart | 429 +++++++++++++++ .../test/webview_widget_test.mocks.dart | 353 ++++++++++++ 7 files changed, 1461 insertions(+), 104 deletions(-) create mode 100644 packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart create mode 100644 packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart create mode 100644 packages/webview_flutter/webview_flutter_android/test/webview_widget_test.mocks.dart 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..b5b3c2616485 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; } -} +} \ No newline at end of file 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..7c5f779ee7af 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,19 +4,33 @@ package io.flutter.plugins.webviewflutter; +import android.app.Activity; +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} + *

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; /** @@ -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,62 @@ 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())); + setUp(registrar.messenger(), registrar.platformViewRegistry(), registrar.activity(), registrar.view()); new FlutterCookieManager(registrar.messenger()); } + private static void setUp( + BinaryMessenger binaryMessenger, PlatformViewRegistry viewRegistry, Activity activity, View containerView) { + InstanceManager instanceManager = new InstanceManager(); + viewRegistry.registerViewFactory( + "plugins.flutter.io/webview", new FlutterWebViewFactory(instanceManager)); + WebViewHostApi.setup( + binaryMessenger, + new WebViewHostApiImpl(instanceManager, new WebViewHostApiImpl.WebViewProxy(), activity, containerView)); + 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))); + JavaScriptChannelHostApi.setup( + binaryMessenger, + new JavaScriptChannelHostApiImpl( + instanceManager, + new JavaScriptChannelHostApiImpl.JavaScriptChannelCreator(), + new JavaScriptChannelFlutterApiImpl(binaryMessenger, instanceManager), + new Handler(activity.getMainLooper()))); + WebSettingsHostApi.setup( + binaryMessenger, + new WebSettingsHostApiImpl( + instanceManager, new WebSettingsHostApiImpl.WebSettingsCreator())); + new FlutterCookieManager(binaryMessenger); + } + @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); + this.pluginBinding = binding; + flutterCookieManager = new FlutterCookieManager(binding.getBinaryMessenger()); } @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) { + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { if (flutterCookieManager == null) { return; } @@ -70,4 +115,27 @@ public void onDetachedFromEngine(FlutterPluginBinding binding) { flutterCookieManager.dispose(); flutterCookieManager = null; } -} + + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { + setUp( + pluginBinding.getBinaryMessenger(), + pluginBinding.getPlatformViewRegistry(), + activityPluginBinding.getActivity(), null); + } + + @Override + public void onDetachedFromActivityForConfigChanges() {} + + @Override + public void onReattachedToActivityForConfigChanges( + @NonNull ActivityPluginBinding activityPluginBinding) { + setUp( + pluginBinding.getBinaryMessenger(), + pluginBinding.getPlatformViewRegistry(), + activityPluginBinding.getActivity(), null); + } + + @Override + public void onDetachedFromActivity() {} +} \ No newline at end of file diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart index a48e457d55ad..ff2bcce2ffb9 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart @@ -10,6 +10,9 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'webview_widget.dart'; +import 'src/instance_manager.dart'; + /// Builds an Android webview. /// /// This is used as the default implementation for [WebView.platform] on Android. It uses @@ -25,35 +28,38 @@ class AndroidWebView implements WebViewPlatform { WebViewPlatformCreatedCallback? onWebViewPlatformCreated, Set>? gestureRecognizers, }) { - assert(webViewPlatformCallbacksHandler != null); - return GestureDetector( - // We prevent text selection by intercepting the long press event. - // This is a temporary stop gap due to issues with text selection on Android: - // https://github.com/flutter/flutter/issues/24585 - the text selection - // dialog is not responding to touch events. - // https://github.com/flutter/flutter/issues/24584 - the text selection - // handles are not showing. - // TODO(amirh): remove this when the issues above are fixed. - onLongPress: () {}, - excludeFromSemantics: true, - child: AndroidView( - viewType: 'plugins.flutter.io/webview', - onPlatformViewCreated: (int id) { - if (onWebViewPlatformCreated == null) { - return; - } - onWebViewPlatformCreated(MethodChannelWebViewPlatform( - id, - webViewPlatformCallbacksHandler, - javascriptChannelRegistry, - )); - }, - gestureRecognizers: gestureRecognizers, - layoutDirection: Directionality.maybeOf(context) ?? TextDirection.rtl, - creationParams: - MethodChannelWebViewPlatform.creationParamsToMap(creationParams), - creationParamsCodec: const StandardMessageCodec(), - ), + return AndroidWebViewWidget( + creationParams: creationParams, + webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, + javascriptChannelRegistry: javascriptChannelRegistry, + useHybridComposition: false, + onBuildWidget: (AndroidWebViewPlatformController platformController) { + return GestureDetector( + // We prevent text selection by intercepting the long press event. + // This is a temporary stop gap due to issues with text selection on Android: + // https://github.com/flutter/flutter/issues/24585 - the text selection + // dialog is not responding to touch events. + // https://github.com/flutter/flutter/issues/24584 - the text selection + // handles are not showing. + // TODO(amirh): remove this when the issues above are fixed. + onLongPress: () {}, + excludeFromSemantics: true, + child: AndroidView( + viewType: 'plugins.flutter.io/webview', + onPlatformViewCreated: (int id) { + if (onWebViewPlatformCreated != null) { + onWebViewPlatformCreated(platformController); + } + }, + gestureRecognizers: gestureRecognizers, + layoutDirection: + Directionality.maybeOf(context) ?? TextDirection.rtl, + creationParams: InstanceManager.instance + .getInstanceId(platformController.webView), + creationParamsCodec: const StandardMessageCodec(), + ), + ); + }, ); } diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart index 6beae105e2e5..90e6fb1e3ac8 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart @@ -9,6 +9,8 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'src/instance_manager.dart'; +import 'webview_widget.dart'; import 'webview_android.dart'; /// Android [WebViewPlatform] that uses [AndroidViewSurface] to build the [WebView] widget. @@ -30,48 +32,46 @@ class SurfaceAndroidWebView extends AndroidWebView { Set>? gestureRecognizers, required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, }) { - assert(webViewPlatformCallbacksHandler != null); - return PlatformViewLink( - viewType: 'plugins.flutter.io/webview', - surfaceFactory: ( - BuildContext context, - PlatformViewController controller, - ) { - return AndroidViewSurface( - controller: controller as AndroidViewController, - gestureRecognizers: gestureRecognizers ?? - const >{}, - hitTestBehavior: PlatformViewHitTestBehavior.opaque, - ); - }, - onCreatePlatformView: (PlatformViewCreationParams params) { - return PlatformViewsService.initSurfaceAndroidView( - id: params.id, + return AndroidWebViewWidget( + creationParams: creationParams, + webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, + javascriptChannelRegistry: javascriptChannelRegistry, + useHybridComposition: true, + onBuildWidget: (AndroidWebViewPlatformController platformController) { + return PlatformViewLink( viewType: 'plugins.flutter.io/webview', - // WebView content is not affected by the Android view's layout direction, - // we explicitly set it here so that the widget doesn't require an ambient - // directionality. - layoutDirection: TextDirection.rtl, - creationParams: MethodChannelWebViewPlatform.creationParamsToMap( - creationParams, - usesHybridComposition: true, - ), - creationParamsCodec: const StandardMessageCodec(), - ) - ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) - ..addOnPlatformViewCreatedListener((int id) { - if (onWebViewPlatformCreated == null) { - return; - } - onWebViewPlatformCreated( - MethodChannelWebViewPlatform( - id, - webViewPlatformCallbacksHandler, - javascriptChannelRegistry, - ), + surfaceFactory: ( + BuildContext context, + PlatformViewController controller, + ) { + return AndroidViewSurface( + controller: controller as AndroidViewController, + gestureRecognizers: gestureRecognizers ?? + const >{}, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, ); - }) - ..create(); + }, + onCreatePlatformView: (PlatformViewCreationParams params) { + return PlatformViewsService.initSurfaceAndroidView( + id: params.id, + viewType: 'plugins.flutter.io/webview', + // WebView content is not affected by the Android view's layout direction, + // we explicitly set it here so that the widget doesn't require an ambient + // directionality. + layoutDirection: TextDirection.rtl, + creationParams: InstanceManager.instance + .getInstanceId(platformController.webView), + creationParamsCodec: const StandardMessageCodec(), + ) + ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) + ..addOnPlatformViewCreatedListener((int id) { + if (onWebViewPlatformCreated != null) { + onWebViewPlatformCreated(platformController); + } + }) + ..create(); + }, + ); }, ); } diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart new file mode 100644 index 000000000000..ba2bb1a0204c --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart @@ -0,0 +1,506 @@ +// 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. + +import 'dart:async'; + +import 'package:flutter/widgets.dart'; + +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'src/android_webview.dart' as android_webview; + +/// Creates a [Widget] with a [android_webview.WebView]. +class AndroidWebViewWidget extends StatefulWidget { + /// Constructs a [AndroidWebViewWidget]. + AndroidWebViewWidget({ + required this.creationParams, + required this.webViewPlatformCallbacksHandler, + required this.javascriptChannelRegistry, + required this.useHybridComposition, + required this.onBuildWidget, + }); + + /// Initial parameters used to setup the WebView. + final CreationParams creationParams; + + /// Handles callbacks that are made by the created [AndroidWebViewPlatformController]. + final WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler; + + /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. + final JavascriptChannelRegistry javascriptChannelRegistry; + + /// Whether the Widget will be rendered with Hybrid Composition. + final bool useHybridComposition; + + /// Callback to build a widget once [android_webview.WebView] has been initialized. + final Widget Function(AndroidWebViewPlatformController platformController) + onBuildWidget; + + @override + State createState() => _AndroidWebViewWidgetState(); +} + +class _AndroidWebViewWidgetState extends State { + late android_webview.WebView webView; + late AndroidWebViewPlatformController platformController; + + @override + void initState() { + super.initState(); + webView = android_webview.WebView( + useHybridComposition: widget.useHybridComposition, + ); + webView.settings.setDomStorageEnabled(true); + webView.settings.setJavaScriptCanOpenWindowsAutomatically(true); + webView.settings.setSupportMultipleWindows(true); + webView.settings.setLoadWithOverviewMode(true); + webView.settings.setUseWideViewPort(true); + webView.settings.setDisplayZoomControls(false); + webView.settings.setBuiltInZoomControls(true); + + platformController = AndroidWebViewPlatformController( + webView: webView, + callbacksHandler: widget.webViewPlatformCallbacksHandler, + javascriptChannelRegistry: widget.javascriptChannelRegistry, + hasNavigationDelegate: + widget.creationParams.webSettings?.hasNavigationDelegate ?? false, + ); + + setCreationParams(widget.creationParams); + } + + void setCreationParams(CreationParams creationParams) { + final WebSettings? webSettings = creationParams.webSettings; + if (webSettings != null) { + platformController.updateSettings(webSettings); + } + + final String? userAgent = creationParams.userAgent; + if (userAgent != null) { + webView.settings.setUserAgentString(userAgent); + } + + final AutoMediaPlaybackPolicy autoMediaPlaybackPolicy = + creationParams.autoMediaPlaybackPolicy; + switch (autoMediaPlaybackPolicy) { + case AutoMediaPlaybackPolicy.always_allow: + webView.settings.setMediaPlaybackRequiresUserGesture(false); + break; + default: + webView.settings.setMediaPlaybackRequiresUserGesture(true); + } + + platformController.addJavascriptChannels( + creationParams.javascriptChannelNames, + ); + + final String? initialUrl = creationParams.initialUrl; + if (initialUrl != null) { + platformController.loadUrl(initialUrl, {}); + } + } + + @override + void dispose() { + super.dispose(); + platformController.webView.release(); + } + + @override + Widget build(BuildContext context) { + return widget.onBuildWidget(platformController); + } +} + +/// Implementation of [WebViewPlatformController] with the Android WebView api. +class AndroidWebViewPlatformController extends WebViewPlatformController { + /// Construct a [AndroidWebViewPlatformController]. + AndroidWebViewPlatformController({ + required this.webView, + required this.callbacksHandler, + required this.javascriptChannelRegistry, + required bool hasNavigationDelegate, + }) : super(callbacksHandler) { + _webViewClient = _WebViewClientImpl( + callbacksHandler: callbacksHandler, + loadUrl: loadUrl, + hasNavigationDelegate: hasNavigationDelegate, + ); + _downloadListener = _DownloadListenerImpl( + callbacksHandler: callbacksHandler, + loadUrl: loadUrl, + ); + _webChromeClient = _WebChromeClientImpl(callbacksHandler: callbacksHandler); + webView.setWebViewClient(_webViewClient); + webView.setDownloadListener(_downloadListener); + webView.setWebChromeClient(_webChromeClient); + } + + /// Represents the WebView maintained by platform code. + final android_webview.WebView webView; + + /// Handles callbacks that are made by [android_webview.WebViewClient], [android_webview.DownloadListener], and [android_webview.WebChromeClient]. + final WebViewPlatformCallbacksHandler callbacksHandler; + + /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. + final JavascriptChannelRegistry javascriptChannelRegistry; + + final Map _javaScriptChannels = + {}; + late final _DownloadListenerImpl _downloadListener; + late final _WebChromeClientImpl _webChromeClient; + late _WebViewClientImpl _webViewClient; + + @override + Future loadUrl( + String url, + Map? headers, + ) { + return webView.loadUrl(url, headers ?? {}); + } + + @override + Future currentUrl() => webView.getUrl(); + + @override + Future canGoBack() => webView.canGoBack(); + + @override + Future canGoForward() => webView.canGoForward(); + + @override + Future goBack() => webView.goBack(); + + @override + Future goForward() => webView.goForward(); + + @override + Future reload() => webView.reload(); + + @override + Future clearCache() => webView.clearCache(true); + + @override + Future updateSettings(WebSettings settings) { + return Future.wait(>[ + _trySetHasProgressTracking(settings.hasProgressTracking), + _trySetHasNavigationDelegate(settings.hasNavigationDelegate), + _trySetJavaScriptMode(settings.javascriptMode), + _trySetDebuggingEnabled(settings.debuggingEnabled), + _trySetUserAgent(settings.userAgent), + _trySetZoomEnabled(settings.zoomEnabled), + ]); + } + + @override + Future evaluateJavascript(String javascriptString) async { + return await webView.evaluateJavascript(javascriptString) ?? ''; + } + + @override + Future addJavascriptChannels(Set javascriptChannelNames) { + return Future.wait( + javascriptChannelNames.where( + (String channelName) { + return !_javaScriptChannels.containsKey(channelName); + }, + ).map>( + (String channelName) { + final _JavaScriptChannelImpl javaScriptChannel = + _JavaScriptChannelImpl(channelName, javascriptChannelRegistry); + _javaScriptChannels[channelName] = javaScriptChannel; + return webView.addJavaScriptChannel(javaScriptChannel); + }, + ), + ); + } + + @override + Future removeJavascriptChannels( + Set javascriptChannelNames, + ) { + return Future.wait( + javascriptChannelNames.where( + (String channelName) { + return _javaScriptChannels.containsKey(channelName); + }, + ).map>( + (String channelName) { + final _JavaScriptChannelImpl javaScriptChannel = + _javaScriptChannels[channelName]!; + _javaScriptChannels.remove(channelName); + return webView.removeJavaScriptChannel(javaScriptChannel); + }, + ), + ); + } + + @override + Future getTitle() => webView.getTitle(); + + @override + Future scrollTo(int x, int y) => webView.scrollTo(x, y); + + @override + Future scrollBy(int x, int y) => webView.scrollBy(x, y); + + @override + Future getScrollX() => webView.getScrollX(); + + @override + Future getScrollY() => webView.getScrollY(); + + Future _trySetHasProgressTracking(bool? hasProgressTracking) { + if (hasProgressTracking != null) { + _webChromeClient.hasProgressTracking = hasProgressTracking; + } + + return Future.sync(() => null); + } + + Future _trySetHasNavigationDelegate(bool? hasNavigationDelegate) { + if (hasNavigationDelegate == null) return Future.sync(() => null); + + _downloadListener.hasNavigationDelegate = hasNavigationDelegate; + if (_webViewClient.hasNavigationDelegate != hasNavigationDelegate) { + _webViewClient = _WebViewClientImpl( + callbacksHandler: callbacksHandler, + loadUrl: loadUrl, + hasNavigationDelegate: hasNavigationDelegate, + ); + return webView.setWebViewClient(_webViewClient); + } + + return Future.sync(() => null); + } + + Future _trySetJavaScriptMode(JavascriptMode? mode) async { + if (mode == null) return Future.sync(() => null); + + switch (mode) { + case JavascriptMode.disabled: + return webView.settings.setJavaScriptEnabled(false); + case JavascriptMode.unrestricted: + return webView.settings.setJavaScriptEnabled(true); + } + } + + Future _trySetDebuggingEnabled(bool? debuggingEnabled) async { + if (debuggingEnabled == null) return Future.sync(() => null); + return android_webview.WebView.setWebContentsDebuggingEnabled( + debuggingEnabled, + ); + } + + Future _trySetUserAgent(WebSetting userAgent) { + if (userAgent.isPresent && userAgent.value != null) { + return webView.settings.setUserAgentString(userAgent.value!); + } + + return webView.settings.setUserAgentString(''); + } + + Future _trySetZoomEnabled(bool? zoomEnabled) async { + if (zoomEnabled == null) return Future.sync(() => null); + return webView.settings.setSupportZoom(zoomEnabled); + } +} + +class _JavaScriptChannelImpl extends android_webview.JavaScriptChannel { + _JavaScriptChannelImpl(String channelName, this.javascriptChannelRegistry) + : super(channelName); + + final JavascriptChannelRegistry javascriptChannelRegistry; + + @override + void postMessage(String message) { + javascriptChannelRegistry.onJavascriptChannelMessage(channelName, message); + } +} + +class _DownloadListenerImpl extends android_webview.DownloadListener { + _DownloadListenerImpl({ + required this.callbacksHandler, + required this.loadUrl, + }); + + final WebViewPlatformCallbacksHandler callbacksHandler; + final Future Function(String url, Map? headers) loadUrl; + bool hasNavigationDelegate = false; + + @override + void onDownloadStart( + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) { + if (!hasNavigationDelegate) return; + + final FutureOr returnValue = callbacksHandler.onNavigationRequest( + url: url, + isForMainFrame: true, + ); + + if (returnValue is bool && returnValue) { + loadUrl(url, {}); + } else { + (returnValue as Future).then((bool shouldLoadUrl) { + if (shouldLoadUrl) { + loadUrl(url, {}); + } + }); + } + } +} + +class _WebViewClientImpl extends android_webview.WebViewClient { + _WebViewClientImpl({ + required this.callbacksHandler, + required this.loadUrl, + required this.hasNavigationDelegate, + }) : super(shouldOverrideUrlLoading: hasNavigationDelegate); + + final WebViewPlatformCallbacksHandler callbacksHandler; + final Future Function(String url, Map? headers) loadUrl; + final bool hasNavigationDelegate; + + static WebResourceErrorType _errorCodeToErrorType(int errorCode) { + switch (errorCode) { + case android_webview.WebViewClient.errorAuthentication: + return WebResourceErrorType.authentication; + case android_webview.WebViewClient.errorBadUrl: + return WebResourceErrorType.badUrl; + case android_webview.WebViewClient.errorConnect: + return WebResourceErrorType.connect; + case android_webview.WebViewClient.errorFailedSslHandshake: + return WebResourceErrorType.failedSslHandshake; + case android_webview.WebViewClient.errorFile: + return WebResourceErrorType.file; + case android_webview.WebViewClient.errorFileNotFound: + return WebResourceErrorType.fileNotFound; + case android_webview.WebViewClient.errorHostLookup: + return WebResourceErrorType.hostLookup; + case android_webview.WebViewClient.errorIO: + return WebResourceErrorType.io; + case android_webview.WebViewClient.errorProxyAuthentication: + return WebResourceErrorType.proxyAuthentication; + case android_webview.WebViewClient.errorRedirectLoop: + return WebResourceErrorType.redirectLoop; + case android_webview.WebViewClient.errorTimeout: + return WebResourceErrorType.timeout; + case android_webview.WebViewClient.errorTooManyRequests: + return WebResourceErrorType.tooManyRequests; + case android_webview.WebViewClient.errorUnknown: + return WebResourceErrorType.unknown; + case android_webview.WebViewClient.errorUnsafeResource: + return WebResourceErrorType.unsafeResource; + case android_webview.WebViewClient.errorUnsupportedAuthScheme: + return WebResourceErrorType.unsupportedAuthScheme; + case android_webview.WebViewClient.errorUnsupportedScheme: + return WebResourceErrorType.unsupportedScheme; + } + + throw ArgumentError( + 'Could not find a WebResourceErrorType for errorCode: $errorCode', + ); + } + + @override + void onPageStarted(android_webview.WebView webView, String url) { + callbacksHandler.onPageStarted(url); + } + + @override + void onPageFinished(android_webview.WebView webView, String url) { + callbacksHandler.onPageFinished(url); + } + + @override + void onReceivedError( + android_webview.WebView webView, + int errorCode, + String description, + String failingUrl, + ) { + callbacksHandler.onWebResourceError(WebResourceError( + errorCode: errorCode, + description: description, + failingUrl: failingUrl, + errorType: _errorCodeToErrorType(errorCode), + )); + } + + @override + void onReceivedRequestError( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + ) { + if (request.isForMainFrame) { + callbacksHandler.onWebResourceError(WebResourceError( + errorCode: error.errorCode, + description: error.description, + failingUrl: request.url, + errorType: _errorCodeToErrorType(error.errorCode), + )); + } + } + + @override + void urlLoading(android_webview.WebView webView, String url) { + if (!hasNavigationDelegate) return; + + final FutureOr returnValue = callbacksHandler.onNavigationRequest( + url: url, + isForMainFrame: true, + ); + + if (returnValue is bool && returnValue) { + loadUrl(url, {}); + } else { + (returnValue as Future).then((bool shouldLoadUrl) { + if (shouldLoadUrl) { + loadUrl(url, {}); + } + }); + } + } + + @override + void requestLoading( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + ) { + if (!hasNavigationDelegate) return; + + final FutureOr returnValue = callbacksHandler.onNavigationRequest( + url: request.url, + isForMainFrame: request.isForMainFrame, + ); + + if (returnValue is bool && returnValue) { + loadUrl(request.url, {}); + } else { + (returnValue as Future).then((bool shouldLoadUrl) { + if (shouldLoadUrl) { + loadUrl(request.url, {}); + } + }); + } + } +} + +class _WebChromeClientImpl extends android_webview.WebChromeClient { + _WebChromeClientImpl({required this.callbacksHandler}); + + final WebViewPlatformCallbacksHandler callbacksHandler; + bool hasProgressTracking = false; + + @override + void onProgressChanged(android_webview.WebView webView, int progress) { + if (hasProgressTracking) callbacksHandler.onProgress(progress); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart new file mode 100644 index 000000000000..e82915a8bc54 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart @@ -0,0 +1,429 @@ +// 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. + +import 'dart:async'; + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter_android/src/android_webview.dart' + as android_webview; +import 'package:webview_flutter_android/src/android_webview_api_impls.dart'; +import 'package:webview_flutter_android/src/instance_manager.dart'; +import 'package:webview_flutter_android/webview_widget.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'android_webview.pigeon.dart'; +import 'webview_widget_test.mocks.dart'; + +@GenerateMocks([ + TestWebViewHostApi, + TestWebSettingsHostApi, + TestWebViewClientHostApi, + TestWebChromeClientHostApi, + TestJavaScriptChannelHostApi, + TestDownloadListenerHostApi, + WebViewPlatformCallbacksHandler, + JavascriptChannelRegistry, +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$AndroidWebViewWidget', () { + late MockTestWebViewHostApi mockWebViewHostApi; + late MockTestWebSettingsHostApi mockWebSettingsHostApi; + late MockTestWebViewClientHostApi mockWebViewClientHostApi; + late MockTestWebChromeClientHostApi mockWebChromeClientHostApi; + late MockTestJavaScriptChannelHostApi mockJavaScriptChannelHostApi; + late MockTestDownloadListenerHostApi mockDownloadListenerHostApi; + + late WebViewPlatformCallbacksHandler mockCallbacksHandler; + late JavascriptChannelRegistry mockJavascriptChannelRegistry; + + setUp(() { + mockWebViewHostApi = MockTestWebViewHostApi(); + mockWebSettingsHostApi = MockTestWebSettingsHostApi(); + mockWebViewClientHostApi = MockTestWebViewClientHostApi(); + mockWebChromeClientHostApi = MockTestWebChromeClientHostApi(); + mockJavaScriptChannelHostApi = MockTestJavaScriptChannelHostApi(); + mockDownloadListenerHostApi = MockTestDownloadListenerHostApi(); + + TestWebViewHostApi.setup(mockWebViewHostApi); + TestWebSettingsHostApi.setup(mockWebSettingsHostApi); + TestWebViewClientHostApi.setup(mockWebViewClientHostApi); + TestWebChromeClientHostApi.setup(mockWebChromeClientHostApi); + TestJavaScriptChannelHostApi.setup(mockJavaScriptChannelHostApi); + TestDownloadListenerHostApi.setup(mockDownloadListenerHostApi); + + mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); + mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); + + final InstanceManager instanceManager = InstanceManager(); + android_webview.WebView.api = WebViewHostApiImpl( + instanceManager: instanceManager, + ); + android_webview.WebSettings.api = WebSettingsHostApiImpl( + instanceManager: instanceManager, + ); + android_webview.JavaScriptChannel.api = JavaScriptChannelHostApiImpl( + instanceManager: instanceManager, + ); + android_webview.WebViewClient.api = WebViewClientHostApiImpl( + instanceManager: instanceManager, + ); + android_webview.DownloadListener.api = DownloadListenerHostApiImpl( + instanceManager: instanceManager, + ); + android_webview.WebChromeClient.api = WebChromeClientHostApiImpl( + instanceManager: instanceManager, + ); + }); + + // Builds a AndroidWebViewWidget with default parameters. + Future buildWidget( + WidgetTester tester, { + Widget Function(AndroidWebViewPlatformController platformController)? + onBuildWidget, + CreationParams? creationParams, + WebViewPlatformCallbacksHandler? webViewPlatformCallbacksHandler, + JavascriptChannelRegistry? javascriptChannelRegistry, + bool? useHybridComposition, + }) async { + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget( + AndroidWebViewWidget( + onBuildWidget: onBuildWidget ?? + (AndroidWebViewPlatformController controller) { + controllerCompleter.complete(controller); + return Container(); + }, + creationParams: creationParams ?? CreationParams(), + webViewPlatformCallbacksHandler: + webViewPlatformCallbacksHandler ?? mockCallbacksHandler, + javascriptChannelRegistry: + javascriptChannelRegistry ?? mockJavascriptChannelRegistry, + useHybridComposition: useHybridComposition ?? false, + ), + ); + + return controllerCompleter.future; + } + + testWidgets('Create Widget', (WidgetTester tester) async { + await buildWidget(tester); + + verify(mockWebSettingsHostApi.setDomStorageEnabled(1, true)); + verify(mockWebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically( + 1, + true, + )); + verify(mockWebSettingsHostApi.setSupportMultipleWindows(1, true)); + verify(mockWebSettingsHostApi.setLoadWithOverviewMode(1, true)); + verify(mockWebSettingsHostApi.setUseWideViewPort(1, true)); + verify(mockWebSettingsHostApi.setDisplayZoomControls(1, false)); + verify(mockWebSettingsHostApi.setBuiltInZoomControls(1, true)); + + verifyInOrder([ + mockWebViewHostApi.create(0, false), + mockWebViewHostApi.setWebViewClient(0, any), + mockWebViewHostApi.setDownloadListener(0, any), + mockWebViewHostApi.setWebChromeClient(0, any), + ]); + }); + + testWidgets( + 'Create Widget with Hybrid Composition', + (WidgetTester tester) async { + await buildWidget(tester, useHybridComposition: true); + verify(mockWebViewHostApi.create(0, true)); + }, + ); + + group('CreationParams', () { + testWidgets('initialUrl', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams(initialUrl: 'https://www.google.com'), + ); + verify(mockWebViewHostApi.loadUrl( + 0, + 'https://www.google.com', + {}, + )); + }); + + testWidgets('userAgent', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams(userAgent: 'MyUserAgent'), + ); + + verify(mockWebSettingsHostApi.setUserAgentString(1, 'MyUserAgent')); + }); + + testWidgets('autoMediaPlaybackPolicy true', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + autoMediaPlaybackPolicy: + AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, + ), + ); + + verify( + mockWebSettingsHostApi.setMediaPlaybackRequiresUserGesture(any, true), + ); + }); + + testWidgets('autoMediaPlaybackPolicy false', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ); + + verify(mockWebSettingsHostApi.setMediaPlaybackRequiresUserGesture( + any, + false, + )); + }); + + testWidgets('javascriptChannelNames', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + javascriptChannelNames: {'a', 'b'}, + ), + ); + + verify(mockJavaScriptChannelHostApi.create(any, 'a')); + verify(mockJavaScriptChannelHostApi.create(any, 'b')); + verify(mockWebViewHostApi.addJavaScriptChannel(0, any)).called(2); + }); + + group('WebSettings', () { + testWidgets('javascriptMode', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: WebSetting.absent(), + javascriptMode: JavascriptMode.unrestricted, + ), + ), + ); + + verify(mockWebSettingsHostApi.setJavaScriptEnabled(any, true)); + }); + + testWidgets('hasNavigationDelegate', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: WebSetting.absent(), + hasNavigationDelegate: true, + ), + ), + ); + + verify(mockWebViewClientHostApi.create(any, true)); + }); + + testWidgets('debuggingEnabled', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: WebSetting.absent(), + debuggingEnabled: true, + ), + ), + ); + + verify(mockWebViewHostApi.setWebContentsDebuggingEnabled(true)); + }); + + testWidgets('userAgent', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: WebSetting.of('myUserAgent'), + ), + ), + ); + + verify(mockWebSettingsHostApi.setUserAgentString(any, 'myUserAgent')); + }); + + testWidgets('zoomEnabled', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: WebSetting.absent(), + zoomEnabled: false, + ), + ), + ); + + verify(mockWebSettingsHostApi.setSupportZoom(any, false)); + }); + }); + }); + + group('$AndroidWebViewPlatformController', () { + testWidgets('loadUrl', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + await controller.loadUrl( + 'https://www.google.com', + {'a': 'header'}, + ); + + verify(mockWebViewHostApi.loadUrl( + any, + 'https://www.google.com', + {'a': 'header'}, + )); + }); + + testWidgets('currentUrl', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + when(mockWebViewHostApi.getUrl(any)) + .thenReturn('https://www.google.com'); + expect(controller.currentUrl(), completion('https://www.google.com')); + }); + + testWidgets('canGoBack', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + when(mockWebViewHostApi.canGoBack(any)).thenReturn(false); + expect(controller.canGoBack(), completion(false)); + }); + + testWidgets('canGoForward', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + when(mockWebViewHostApi.canGoForward(any)).thenReturn(true); + expect(controller.canGoForward(), completion(true)); + }); + + testWidgets('goBack', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + await controller.goBack(); + verify(mockWebViewHostApi.goBack(any)); + }); + + testWidgets('goForward', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + await controller.goForward(); + verify(mockWebViewHostApi.goForward(any)); + }); + + testWidgets('reload', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + await controller.reload(); + verify(mockWebViewHostApi.reload(any)); + }); + + testWidgets('clearCache', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + await controller.clearCache(); + verify(mockWebViewHostApi.clearCache(any, true)); + }); + + testWidgets('evaluateJavascript', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + when(mockWebViewHostApi.evaluateJavascript(any, 'runJavaScript')) + .thenAnswer( + (_) => Future.value('returnString'), + ); + expect( + controller.evaluateJavascript('runJavaScript'), + completion('returnString'), + ); + }); + + testWidgets('addJavascriptChannels', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + await controller.addJavascriptChannels({'c', 'd'}); + verify(mockJavaScriptChannelHostApi.create(any, 'c')); + verify(mockJavaScriptChannelHostApi.create(any, 'd')); + verify(mockWebViewHostApi.addJavaScriptChannel(0, any)).called(2); + }); + + testWidgets('removeJavascriptChannels', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + await controller.addJavascriptChannels({'c', 'd'}); + await controller.removeJavascriptChannels({'c', 'd'}); + verify(mockWebViewHostApi.removeJavaScriptChannel(0, any)).called(2); + }); + + testWidgets('getTitle', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + when(mockWebViewHostApi.getTitle(any)).thenReturn('Web Title'); + expect(controller.getTitle(), completion('Web Title')); + }); + + testWidgets('scrollTo', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + await controller.scrollTo(1, 2); + verify(mockWebViewHostApi.scrollTo(any, 1, 2)); + }); + + testWidgets('scrollBy', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + await controller.scrollBy(3, 4); + verify(mockWebViewHostApi.scrollBy(any, 3, 4)); + }); + + testWidgets('getScrollX', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + when(mockWebViewHostApi.getScrollX(any)).thenReturn(23); + expect(controller.getScrollX(), completion(23)); + }); + + testWidgets('getScrollY', (WidgetTester tester) async { + final AndroidWebViewPlatformController controller = + await buildWidget(tester); + + when(mockWebViewHostApi.getScrollY(any)).thenReturn(25); + expect(controller.getScrollY(), completion(25)); + }); + }); + }); +} diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.mocks.dart new file mode 100644 index 000000000000..5f4381a580d4 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.mocks.dart @@ -0,0 +1,353 @@ +// 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. + +// Mocks generated by Mockito 5.0.16 from annotations +// in webview_flutter_android/test/webview_widget_test.dart. +// Do not manually edit this file. + +import 'dart:async' as _i3; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_platform_interface/src/platform_interface/javascript_channel_registry.dart' + as _i6; +import 'package:webview_flutter_platform_interface/src/platform_interface/webview_platform_callbacks_handler.dart' + as _i4; +import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i5; + +import 'android_webview.pigeon.dart' as _i2; + +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types + +/// A class which mocks [TestWebViewHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestWebViewHostApi extends _i1.Mock + implements _i2.TestWebViewHostApi { + MockTestWebViewHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create(int? instanceId, bool? useHybridComposition) => + super.noSuchMethod( + Invocation.method(#create, [instanceId, useHybridComposition]), + returnValueForMissingStub: null); + @override + void dispose(int? instanceId) => + super.noSuchMethod(Invocation.method(#dispose, [instanceId]), + returnValueForMissingStub: null); + @override + void loadUrl(int? instanceId, String? url, Map? headers) => + super.noSuchMethod( + Invocation.method(#loadUrl, [instanceId, url, headers]), + returnValueForMissingStub: null); + @override + String getUrl(int? instanceId) => + (super.noSuchMethod(Invocation.method(#getUrl, [instanceId]), + returnValue: '') as String); + @override + bool canGoBack(int? instanceId) => + (super.noSuchMethod(Invocation.method(#canGoBack, [instanceId]), + returnValue: false) as bool); + @override + bool canGoForward(int? instanceId) => + (super.noSuchMethod(Invocation.method(#canGoForward, [instanceId]), + returnValue: false) as bool); + @override + void goBack(int? instanceId) => + super.noSuchMethod(Invocation.method(#goBack, [instanceId]), + returnValueForMissingStub: null); + @override + void goForward(int? instanceId) => + super.noSuchMethod(Invocation.method(#goForward, [instanceId]), + returnValueForMissingStub: null); + @override + void reload(int? instanceId) => + super.noSuchMethod(Invocation.method(#reload, [instanceId]), + returnValueForMissingStub: null); + @override + void clearCache(int? instanceId, bool? includeDiskFiles) => + super.noSuchMethod( + Invocation.method(#clearCache, [instanceId, includeDiskFiles]), + returnValueForMissingStub: null); + @override + _i3.Future evaluateJavascript( + int? instanceId, String? javascriptString) => + (super.noSuchMethod( + Invocation.method( + #evaluateJavascript, [instanceId, javascriptString]), + returnValue: Future.value('')) as _i3.Future); + @override + String getTitle(int? instanceId) => + (super.noSuchMethod(Invocation.method(#getTitle, [instanceId]), + returnValue: '') as String); + @override + void scrollTo(int? instanceId, int? x, int? y) => + super.noSuchMethod(Invocation.method(#scrollTo, [instanceId, x, y]), + returnValueForMissingStub: null); + @override + void scrollBy(int? instanceId, int? x, int? y) => + super.noSuchMethod(Invocation.method(#scrollBy, [instanceId, x, y]), + returnValueForMissingStub: null); + @override + int getScrollX(int? instanceId) => + (super.noSuchMethod(Invocation.method(#getScrollX, [instanceId]), + returnValue: 0) as int); + @override + int getScrollY(int? instanceId) => + (super.noSuchMethod(Invocation.method(#getScrollY, [instanceId]), + returnValue: 0) as int); + @override + void setWebContentsDebuggingEnabled(bool? enabled) => super.noSuchMethod( + Invocation.method(#setWebContentsDebuggingEnabled, [enabled]), + returnValueForMissingStub: null); + @override + void setWebViewClient(int? instanceId, int? webViewClientInstanceId) => + super.noSuchMethod( + Invocation.method( + #setWebViewClient, [instanceId, webViewClientInstanceId]), + returnValueForMissingStub: null); + @override + void addJavaScriptChannel( + int? instanceId, int? javaScriptChannelInstanceId) => + super.noSuchMethod( + Invocation.method( + #addJavaScriptChannel, [instanceId, javaScriptChannelInstanceId]), + returnValueForMissingStub: null); + @override + void removeJavaScriptChannel( + int? instanceId, int? javaScriptChannelInstanceId) => + super.noSuchMethod( + Invocation.method(#removeJavaScriptChannel, + [instanceId, javaScriptChannelInstanceId]), + returnValueForMissingStub: null); + @override + void setDownloadListener(int? instanceId, int? listenerInstanceId) => + super.noSuchMethod( + Invocation.method( + #setDownloadListener, [instanceId, listenerInstanceId]), + returnValueForMissingStub: null); + @override + void setWebChromeClient(int? instanceId, int? clientInstanceId) => + super.noSuchMethod( + Invocation.method( + #setWebChromeClient, [instanceId, clientInstanceId]), + returnValueForMissingStub: null); + @override + String toString() => super.toString(); +} + +/// A class which mocks [TestWebSettingsHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestWebSettingsHostApi extends _i1.Mock + implements _i2.TestWebSettingsHostApi { + MockTestWebSettingsHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create(int? instanceId, int? webViewInstanceId) => super.noSuchMethod( + Invocation.method(#create, [instanceId, webViewInstanceId]), + returnValueForMissingStub: null); + @override + void dispose(int? instanceId) => + super.noSuchMethod(Invocation.method(#dispose, [instanceId]), + returnValueForMissingStub: null); + @override + void setDomStorageEnabled(int? instanceId, bool? flag) => super.noSuchMethod( + Invocation.method(#setDomStorageEnabled, [instanceId, flag]), + returnValueForMissingStub: null); + @override + void setJavaScriptCanOpenWindowsAutomatically(int? instanceId, bool? flag) => + super.noSuchMethod( + Invocation.method( + #setJavaScriptCanOpenWindowsAutomatically, [instanceId, flag]), + returnValueForMissingStub: null); + @override + void setSupportMultipleWindows(int? instanceId, bool? support) => + super.noSuchMethod( + Invocation.method(#setSupportMultipleWindows, [instanceId, support]), + returnValueForMissingStub: null); + @override + void setJavaScriptEnabled(int? instanceId, bool? flag) => super.noSuchMethod( + Invocation.method(#setJavaScriptEnabled, [instanceId, flag]), + returnValueForMissingStub: null); + @override + void setUserAgentString(int? instanceId, String? userAgentString) => + super.noSuchMethod( + Invocation.method(#setUserAgentString, [instanceId, userAgentString]), + returnValueForMissingStub: null); + @override + void setMediaPlaybackRequiresUserGesture(int? instanceId, bool? require) => + super.noSuchMethod( + Invocation.method( + #setMediaPlaybackRequiresUserGesture, [instanceId, require]), + returnValueForMissingStub: null); + @override + void setSupportZoom(int? instanceId, bool? support) => super.noSuchMethod( + Invocation.method(#setSupportZoom, [instanceId, support]), + returnValueForMissingStub: null); + @override + void setLoadWithOverviewMode(int? instanceId, bool? overview) => + super.noSuchMethod( + Invocation.method(#setLoadWithOverviewMode, [instanceId, overview]), + returnValueForMissingStub: null); + @override + void setUseWideViewPort(int? instanceId, bool? use) => super.noSuchMethod( + Invocation.method(#setUseWideViewPort, [instanceId, use]), + returnValueForMissingStub: null); + @override + void setDisplayZoomControls(int? instanceId, bool? enabled) => + super.noSuchMethod( + Invocation.method(#setDisplayZoomControls, [instanceId, enabled]), + returnValueForMissingStub: null); + @override + void setBuiltInZoomControls(int? instanceId, bool? enabled) => + super.noSuchMethod( + Invocation.method(#setBuiltInZoomControls, [instanceId, enabled]), + returnValueForMissingStub: null); + @override + String toString() => super.toString(); +} + +/// A class which mocks [TestWebViewClientHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestWebViewClientHostApi extends _i1.Mock + implements _i2.TestWebViewClientHostApi { + MockTestWebViewClientHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create(int? instanceId, bool? shouldOverrideUrlLoading) => + super.noSuchMethod( + Invocation.method(#create, [instanceId, shouldOverrideUrlLoading]), + returnValueForMissingStub: null); + @override + String toString() => super.toString(); +} + +/// A class which mocks [TestWebChromeClientHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestWebChromeClientHostApi extends _i1.Mock + implements _i2.TestWebChromeClientHostApi { + MockTestWebChromeClientHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create(int? instanceId, int? webViewClientInstanceId) => + super.noSuchMethod( + Invocation.method(#create, [instanceId, webViewClientInstanceId]), + returnValueForMissingStub: null); + @override + String toString() => super.toString(); +} + +/// A class which mocks [TestJavaScriptChannelHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestJavaScriptChannelHostApi extends _i1.Mock + implements _i2.TestJavaScriptChannelHostApi { + MockTestJavaScriptChannelHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create(int? instanceId, String? channelName) => + super.noSuchMethod(Invocation.method(#create, [instanceId, channelName]), + returnValueForMissingStub: null); + @override + String toString() => super.toString(); +} + +/// A class which mocks [TestDownloadListenerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestDownloadListenerHostApi extends _i1.Mock + implements _i2.TestDownloadListenerHostApi { + MockTestDownloadListenerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create(int? instanceId) => + super.noSuchMethod(Invocation.method(#create, [instanceId]), + returnValueForMissingStub: null); + @override + String toString() => super.toString(); +} + +/// A class which mocks [WebViewPlatformCallbacksHandler]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewPlatformCallbacksHandler extends _i1.Mock + implements _i4.WebViewPlatformCallbacksHandler { + MockWebViewPlatformCallbacksHandler() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.FutureOr onNavigationRequest({String? url, bool? isForMainFrame}) => + (super.noSuchMethod( + Invocation.method(#onNavigationRequest, [], + {#url: url, #isForMainFrame: isForMainFrame}), + returnValue: Future.value(false)) as _i3.FutureOr); + @override + void onPageStarted(String? url) => + super.noSuchMethod(Invocation.method(#onPageStarted, [url]), + returnValueForMissingStub: null); + @override + void onPageFinished(String? url) => + super.noSuchMethod(Invocation.method(#onPageFinished, [url]), + returnValueForMissingStub: null); + @override + void onProgress(int? progress) => + super.noSuchMethod(Invocation.method(#onProgress, [progress]), + returnValueForMissingStub: null); + @override + void onWebResourceError(_i5.WebResourceError? error) => + super.noSuchMethod(Invocation.method(#onWebResourceError, [error]), + returnValueForMissingStub: null); + @override + String toString() => super.toString(); +} + +/// A class which mocks [JavascriptChannelRegistry]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockJavascriptChannelRegistry extends _i1.Mock + implements _i6.JavascriptChannelRegistry { + MockJavascriptChannelRegistry() { + _i1.throwOnMissingStub(this); + } + + @override + Map get channels => + (super.noSuchMethod(Invocation.getter(#channels), + returnValue: {}) + as Map); + @override + void onJavascriptChannelMessage(String? channel, String? message) => + super.noSuchMethod( + Invocation.method(#onJavascriptChannelMessage, [channel, message]), + returnValueForMissingStub: null); + @override + void updateJavascriptChannelsFromSet(Set<_i5.JavascriptChannel>? channels) => + super.noSuchMethod( + Invocation.method(#updateJavascriptChannelsFromSet, [channels]), + returnValueForMissingStub: null); + @override + String toString() => super.toString(); +} From 04079cbec27c1a6544562a913b90e1d188aff214 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Wed, 10 Nov 2021 18:33:13 -0800 Subject: [PATCH 02/33] remove unused files, add navigation delegate to example, add support for runjavascript --- .../FlutterDownloadListener.java | 33 -- .../webviewflutter/FlutterWebView.java | 495 ------------------ .../webviewflutter/FlutterWebViewClient.java | 323 ------------ .../webviewflutter/JavaScriptChannel.java | 45 +- .../webviewflutter/WebViewBuilder.java | 172 ------ .../FlutterDownloadListenerTest.java | 42 -- .../FlutterWebViewClientTest.java | 60 --- .../webviewflutter/FlutterWebViewTest.java | 173 ------ .../webviewflutter/WebViewBuilderTest.java | 104 ---- .../example/lib/main.dart | 19 + .../lib/webview_widget.dart | 14 +- 11 files changed, 39 insertions(+), 1441 deletions(-) delete mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterDownloadListener.java delete mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java delete mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java delete mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewBuilder.java delete mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterDownloadListenerTest.java delete mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewClientTest.java delete mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java delete mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewBuilderTest.java 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/JavaScriptChannel.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java index 96ae3ce1fd4d..a7ae135e93ac 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 = () -> flutterApi.postMessage(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/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/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/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/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart index a22d165a6ff3..1a5a0cc91720 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -11,6 +11,8 @@ import 'package:flutter/material.dart'; import 'package:webview_flutter_android/webview_surface_android.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'navigation_decision.dart'; +import 'navigation_request.dart'; import 'web_view.dart'; void main() { @@ -66,6 +68,23 @@ class _WebViewExampleState extends State<_WebViewExample> { onWebViewCreated: (WebViewController controller) { _controller.complete(controller); }, + onProgress: (int progress) { + print("WebView is loading (progress : $progress%)"); + }, + navigationDelegate: (NavigationRequest request) { + if (request.url.startsWith('https://www.youtube.com/')) { + print('blocking navigation to $request}'); + return NavigationDecision.prevent; + } + print('allowing navigation to $request'); + return NavigationDecision.navigate; + }, + onPageStarted: (String url) { + print('Page started loading: $url'); + }, + onPageFinished: (String url) { + print('Page finished loading: $url'); + }, javascriptChannels: _createJavascriptChannels(context), javascriptMode: JavascriptMode.unrestricted, userAgent: 'Custom_User_Agent', diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart index ba2bb1a0204c..5133b98965d6 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart @@ -194,8 +194,18 @@ class AndroidWebViewPlatformController extends WebViewPlatformController { } @override - Future evaluateJavascript(String javascriptString) async { - return await webView.evaluateJavascript(javascriptString) ?? ''; + Future evaluateJavascript(String javascript) async { + return runJavascriptReturningResult(javascript); + } + + @override + Future runJavascript(String javascript) async { + await webView.evaluateJavascript(javascript); + } + + @override + Future runJavascriptReturningResult(String javascript) async { + return await webView.evaluateJavascript(javascript) ?? ''; } @override From bbbbd76748170fd8aa88b379f23885a0def49772 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Thu, 11 Nov 2021 11:11:02 -0800 Subject: [PATCH 03/33] working example app --- .../lib/webview_android.dart | 20 +- .../lib/webview_surface_android.dart | 20 +- .../lib/webview_widget.dart | 220 +++-- .../test/webview_widget_test.dart | 771 +++++++++--------- .../test/webview_widget_test.mocks.dart | 528 +++++++----- 5 files changed, 825 insertions(+), 734 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart index ff2bcce2ffb9..62166e3e13ad 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart @@ -12,6 +12,7 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_inte import 'webview_widget.dart'; import 'src/instance_manager.dart'; +import 'src/android_webview.dart'; /// Builds an Android webview. /// @@ -28,12 +29,17 @@ class AndroidWebView implements WebViewPlatform { WebViewPlatformCreatedCallback? onWebViewPlatformCreated, Set>? gestureRecognizers, }) { - return AndroidWebViewWidget( + final WebViewAndroidPlatformController controller = + WebViewAndroidPlatformController( + webView: WebView(useHybridComposition: false), creationParams: creationParams, - webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, + callbacksHandler: webViewPlatformCallbacksHandler, javascriptChannelRegistry: javascriptChannelRegistry, - useHybridComposition: false, - onBuildWidget: (AndroidWebViewPlatformController platformController) { + ); + + return AndroidWebViewWidget( + controller: controller, + onBuildWidget: () { return GestureDetector( // We prevent text selection by intercepting the long press event. // This is a temporary stop gap due to issues with text selection on Android: @@ -48,14 +54,14 @@ class AndroidWebView implements WebViewPlatform { viewType: 'plugins.flutter.io/webview', onPlatformViewCreated: (int id) { if (onWebViewPlatformCreated != null) { - onWebViewPlatformCreated(platformController); + onWebViewPlatformCreated(controller); } }, gestureRecognizers: gestureRecognizers, layoutDirection: Directionality.maybeOf(context) ?? TextDirection.rtl, - creationParams: InstanceManager.instance - .getInstanceId(platformController.webView), + creationParams: + InstanceManager.instance.getInstanceId(controller.webView), creationParamsCodec: const StandardMessageCodec(), ), ); diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart index 90e6fb1e3ac8..0f3255594011 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart @@ -9,6 +9,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'src/android_webview.dart'; import 'src/instance_manager.dart'; import 'webview_widget.dart'; import 'webview_android.dart'; @@ -32,12 +33,17 @@ class SurfaceAndroidWebView extends AndroidWebView { Set>? gestureRecognizers, required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, }) { - return AndroidWebViewWidget( + final WebViewAndroidPlatformController controller = + WebViewAndroidPlatformController( + webView: WebView(useHybridComposition: true), creationParams: creationParams, - webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, + callbacksHandler: webViewPlatformCallbacksHandler, javascriptChannelRegistry: javascriptChannelRegistry, - useHybridComposition: true, - onBuildWidget: (AndroidWebViewPlatformController platformController) { + ); + + return AndroidWebViewWidget( + controller: controller, + onBuildWidget: () { return PlatformViewLink( viewType: 'plugins.flutter.io/webview', surfaceFactory: ( @@ -59,14 +65,14 @@ class SurfaceAndroidWebView extends AndroidWebView { // we explicitly set it here so that the widget doesn't require an ambient // directionality. layoutDirection: TextDirection.rtl, - creationParams: InstanceManager.instance - .getInstanceId(platformController.webView), + creationParams: + InstanceManager.instance.getInstanceId(controller.webView), creationParamsCodec: const StandardMessageCodec(), ) ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) ..addOnPlatformViewCreatedListener((int id) { if (onWebViewPlatformCreated != null) { - onWebViewPlatformCreated(platformController); + onWebViewPlatformCreated(controller); } }) ..create(); diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart index 5133b98965d6..4fcb7ce1fac4 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart @@ -13,130 +13,77 @@ import 'src/android_webview.dart' as android_webview; /// Creates a [Widget] with a [android_webview.WebView]. class AndroidWebViewWidget extends StatefulWidget { /// Constructs a [AndroidWebViewWidget]. - AndroidWebViewWidget({ - required this.creationParams, - required this.webViewPlatformCallbacksHandler, - required this.javascriptChannelRegistry, - required this.useHybridComposition, - required this.onBuildWidget, - }); + AndroidWebViewWidget({required this.controller, required this.onBuildWidget}); - /// Initial parameters used to setup the WebView. - final CreationParams creationParams; - - /// Handles callbacks that are made by the created [AndroidWebViewPlatformController]. - final WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler; - - /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. - final JavascriptChannelRegistry javascriptChannelRegistry; - - /// Whether the Widget will be rendered with Hybrid Composition. - final bool useHybridComposition; + final WebViewAndroidPlatformController controller; /// Callback to build a widget once [android_webview.WebView] has been initialized. - final Widget Function(AndroidWebViewPlatformController platformController) - onBuildWidget; + final Widget Function() onBuildWidget; @override State createState() => _AndroidWebViewWidgetState(); } class _AndroidWebViewWidgetState extends State { - late android_webview.WebView webView; - late AndroidWebViewPlatformController platformController; - - @override - void initState() { - super.initState(); - webView = android_webview.WebView( - useHybridComposition: widget.useHybridComposition, - ); - webView.settings.setDomStorageEnabled(true); - webView.settings.setJavaScriptCanOpenWindowsAutomatically(true); - webView.settings.setSupportMultipleWindows(true); - webView.settings.setLoadWithOverviewMode(true); - webView.settings.setUseWideViewPort(true); - webView.settings.setDisplayZoomControls(false); - webView.settings.setBuiltInZoomControls(true); - - platformController = AndroidWebViewPlatformController( - webView: webView, - callbacksHandler: widget.webViewPlatformCallbacksHandler, - javascriptChannelRegistry: widget.javascriptChannelRegistry, - hasNavigationDelegate: - widget.creationParams.webSettings?.hasNavigationDelegate ?? false, - ); - - setCreationParams(widget.creationParams); - } - - void setCreationParams(CreationParams creationParams) { - final WebSettings? webSettings = creationParams.webSettings; - if (webSettings != null) { - platformController.updateSettings(webSettings); - } - - final String? userAgent = creationParams.userAgent; - if (userAgent != null) { - webView.settings.setUserAgentString(userAgent); - } - - final AutoMediaPlaybackPolicy autoMediaPlaybackPolicy = - creationParams.autoMediaPlaybackPolicy; - switch (autoMediaPlaybackPolicy) { - case AutoMediaPlaybackPolicy.always_allow: - webView.settings.setMediaPlaybackRequiresUserGesture(false); - break; - default: - webView.settings.setMediaPlaybackRequiresUserGesture(true); - } - - platformController.addJavascriptChannels( - creationParams.javascriptChannelNames, - ); - - final String? initialUrl = creationParams.initialUrl; - if (initialUrl != null) { - platformController.loadUrl(initialUrl, {}); - } - } - @override void dispose() { super.dispose(); - platformController.webView.release(); + widget.controller.release(); } @override Widget build(BuildContext context) { - return widget.onBuildWidget(platformController); + return widget.onBuildWidget(); } } /// Implementation of [WebViewPlatformController] with the Android WebView api. -class AndroidWebViewPlatformController extends WebViewPlatformController { - /// Construct a [AndroidWebViewPlatformController]. - AndroidWebViewPlatformController({ +class WebViewAndroidPlatformController extends WebViewPlatformController { + /// Construct a [WebViewAndroidPlatformController]. + WebViewAndroidPlatformController({ required this.webView, + WebViewAndroidWebViewClient? webViewClient, + WebViewAndroidDownloadListener? downloadListener, + WebViewAndroidWebChromeClient? webChromeClient, + required this.creationParams, required this.callbacksHandler, required this.javascriptChannelRegistry, - required bool hasNavigationDelegate, }) : super(callbacksHandler) { - _webViewClient = _WebViewClientImpl( - callbacksHandler: callbacksHandler, - loadUrl: loadUrl, - hasNavigationDelegate: hasNavigationDelegate, - ); - _downloadListener = _DownloadListenerImpl( - callbacksHandler: callbacksHandler, - loadUrl: loadUrl, - ); - _webChromeClient = _WebChromeClientImpl(callbacksHandler: callbacksHandler); - webView.setWebViewClient(_webViewClient); - webView.setDownloadListener(_downloadListener); - webView.setWebChromeClient(_webChromeClient); + webView.settings.setDomStorageEnabled(true); + webView.settings.setJavaScriptCanOpenWindowsAutomatically(true); + webView.settings.setSupportMultipleWindows(true); + webView.settings.setLoadWithOverviewMode(true); + webView.settings.setUseWideViewPort(true); + webView.settings.setDisplayZoomControls(false); + webView.settings.setBuiltInZoomControls(true); + + _webViewClient = webViewClient ?? + WebViewAndroidWebViewClient( + callbacksHandler: callbacksHandler, + loadUrl: loadUrl, + hasNavigationDelegate: + creationParams.webSettings?.hasNavigationDelegate ?? false, + ); + + this.downloadListener = downloadListener ?? + WebViewAndroidDownloadListener( + callbacksHandler: callbacksHandler, + loadUrl: loadUrl, + ); + + this.webChromeClient = + WebViewAndroidWebChromeClient(callbacksHandler: callbacksHandler); + + webView.setWebViewClient(this.webViewClient); + webView.setDownloadListener(this.downloadListener); + webView.setWebChromeClient(this.webChromeClient); + + _setCreationParams(creationParams); } + /// Initial parameters used to setup the WebView. + final CreationParams creationParams; + /// Represents the WebView maintained by platform code. final android_webview.WebView webView; @@ -146,11 +93,16 @@ class AndroidWebViewPlatformController extends WebViewPlatformController { /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. final JavascriptChannelRegistry javascriptChannelRegistry; - final Map _javaScriptChannels = - {}; - late final _DownloadListenerImpl _downloadListener; - late final _WebChromeClientImpl _webChromeClient; - late _WebViewClientImpl _webViewClient; + final Map _javaScriptChannels = + {}; + + late final WebViewAndroidDownloadListener downloadListener; + + late final WebViewAndroidWebChromeClient webChromeClient; + + late WebViewAndroidWebViewClient _webViewClient; + + WebViewAndroidWebViewClient get webViewClient => _webViewClient; @override Future loadUrl( @@ -217,8 +169,9 @@ class AndroidWebViewPlatformController extends WebViewPlatformController { }, ).map>( (String channelName) { - final _JavaScriptChannelImpl javaScriptChannel = - _JavaScriptChannelImpl(channelName, javascriptChannelRegistry); + final WebViewAndroidJavaScriptChannel javaScriptChannel = + WebViewAndroidJavaScriptChannel( + channelName, javascriptChannelRegistry); _javaScriptChannels[channelName] = javaScriptChannel; return webView.addJavaScriptChannel(javaScriptChannel); }, @@ -237,7 +190,7 @@ class AndroidWebViewPlatformController extends WebViewPlatformController { }, ).map>( (String channelName) { - final _JavaScriptChannelImpl javaScriptChannel = + final WebViewAndroidJavaScriptChannel javaScriptChannel = _javaScriptChannels[channelName]!; _javaScriptChannels.remove(channelName); return webView.removeJavaScriptChannel(javaScriptChannel); @@ -261,9 +214,40 @@ class AndroidWebViewPlatformController extends WebViewPlatformController { @override Future getScrollY() => webView.getScrollY(); + Future release() => webView.release(); + + void _setCreationParams(CreationParams creationParams) { + final WebSettings? webSettings = creationParams.webSettings; + if (webSettings != null) { + updateSettings(webSettings); + } + + final String? userAgent = creationParams.userAgent; + if (userAgent != null) { + webView.settings.setUserAgentString(userAgent); + } + + final AutoMediaPlaybackPolicy autoMediaPlaybackPolicy = + creationParams.autoMediaPlaybackPolicy; + switch (autoMediaPlaybackPolicy) { + case AutoMediaPlaybackPolicy.always_allow: + webView.settings.setMediaPlaybackRequiresUserGesture(false); + break; + default: + webView.settings.setMediaPlaybackRequiresUserGesture(true); + } + + addJavascriptChannels(creationParams.javascriptChannelNames); + + final String? initialUrl = creationParams.initialUrl; + if (initialUrl != null) { + loadUrl(initialUrl, {}); + } + } + Future _trySetHasProgressTracking(bool? hasProgressTracking) { if (hasProgressTracking != null) { - _webChromeClient.hasProgressTracking = hasProgressTracking; + webChromeClient.hasProgressTracking = hasProgressTracking; } return Future.sync(() => null); @@ -272,9 +256,9 @@ class AndroidWebViewPlatformController extends WebViewPlatformController { Future _trySetHasNavigationDelegate(bool? hasNavigationDelegate) { if (hasNavigationDelegate == null) return Future.sync(() => null); - _downloadListener.hasNavigationDelegate = hasNavigationDelegate; + downloadListener.hasNavigationDelegate = hasNavigationDelegate; if (_webViewClient.hasNavigationDelegate != hasNavigationDelegate) { - _webViewClient = _WebViewClientImpl( + _webViewClient = WebViewAndroidWebViewClient( callbacksHandler: callbacksHandler, loadUrl: loadUrl, hasNavigationDelegate: hasNavigationDelegate, @@ -317,8 +301,10 @@ class AndroidWebViewPlatformController extends WebViewPlatformController { } } -class _JavaScriptChannelImpl extends android_webview.JavaScriptChannel { - _JavaScriptChannelImpl(String channelName, this.javascriptChannelRegistry) +class WebViewAndroidJavaScriptChannel + extends android_webview.JavaScriptChannel { + WebViewAndroidJavaScriptChannel( + String channelName, this.javascriptChannelRegistry) : super(channelName); final JavascriptChannelRegistry javascriptChannelRegistry; @@ -329,8 +315,8 @@ class _JavaScriptChannelImpl extends android_webview.JavaScriptChannel { } } -class _DownloadListenerImpl extends android_webview.DownloadListener { - _DownloadListenerImpl({ +class WebViewAndroidDownloadListener extends android_webview.DownloadListener { + WebViewAndroidDownloadListener({ required this.callbacksHandler, required this.loadUrl, }); @@ -366,8 +352,8 @@ class _DownloadListenerImpl extends android_webview.DownloadListener { } } -class _WebViewClientImpl extends android_webview.WebViewClient { - _WebViewClientImpl({ +class WebViewAndroidWebViewClient extends android_webview.WebViewClient { + WebViewAndroidWebViewClient({ required this.callbacksHandler, required this.loadUrl, required this.hasNavigationDelegate, @@ -503,8 +489,8 @@ class _WebViewClientImpl extends android_webview.WebViewClient { } } -class _WebChromeClientImpl extends android_webview.WebChromeClient { - _WebChromeClientImpl({required this.callbacksHandler}); +class WebViewAndroidWebChromeClient extends android_webview.WebChromeClient { + WebViewAndroidWebChromeClient({required this.callbacksHandler}); final WebViewPlatformCallbacksHandler callbacksHandler; bool hasProgressTracking = false; diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart index e82915a8bc54..5ebe3e24bd3e 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart @@ -19,96 +19,105 @@ import 'android_webview.pigeon.dart'; import 'webview_widget_test.mocks.dart'; @GenerateMocks([ - TestWebViewHostApi, - TestWebSettingsHostApi, - TestWebViewClientHostApi, - TestWebChromeClientHostApi, - TestJavaScriptChannelHostApi, - TestDownloadListenerHostApi, - WebViewPlatformCallbacksHandler, + android_webview.WebSettings, + android_webview.WebView, + WebViewAndroidDownloadListener, + WebViewAndroidJavaScriptChannel, + WebViewAndroidWebChromeClient, + WebViewAndroidWebViewClient, JavascriptChannelRegistry, + WebViewPlatformCallbacksHandler, ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$AndroidWebViewWidget', () { - late MockTestWebViewHostApi mockWebViewHostApi; - late MockTestWebSettingsHostApi mockWebSettingsHostApi; - late MockTestWebViewClientHostApi mockWebViewClientHostApi; - late MockTestWebChromeClientHostApi mockWebChromeClientHostApi; - late MockTestJavaScriptChannelHostApi mockJavaScriptChannelHostApi; - late MockTestDownloadListenerHostApi mockDownloadListenerHostApi; - - late WebViewPlatformCallbacksHandler mockCallbacksHandler; - late JavascriptChannelRegistry mockJavascriptChannelRegistry; - - setUp(() { - mockWebViewHostApi = MockTestWebViewHostApi(); - mockWebSettingsHostApi = MockTestWebSettingsHostApi(); - mockWebViewClientHostApi = MockTestWebViewClientHostApi(); - mockWebChromeClientHostApi = MockTestWebChromeClientHostApi(); - mockJavaScriptChannelHostApi = MockTestJavaScriptChannelHostApi(); - mockDownloadListenerHostApi = MockTestDownloadListenerHostApi(); - - TestWebViewHostApi.setup(mockWebViewHostApi); - TestWebSettingsHostApi.setup(mockWebSettingsHostApi); - TestWebViewClientHostApi.setup(mockWebViewClientHostApi); - TestWebChromeClientHostApi.setup(mockWebChromeClientHostApi); - TestJavaScriptChannelHostApi.setup(mockJavaScriptChannelHostApi); - TestDownloadListenerHostApi.setup(mockDownloadListenerHostApi); + // late MockTestWebViewHostApi mockWebViewHostApi; + // late MockTestWebSettingsHostApi mockWebSettingsHostApi; + // late MockTestWebViewClientHostApi mockWebViewClientHostApi; + // late MockTestWebChromeClientHostApi mockWebChromeClientHostApi; + // late MockTestJavaScriptChannelHostApi mockJavaScriptChannelHostApi; + // late MockTestDownloadListenerHostApi mockDownloadListenerHostApi; + // + + late MockWebView mockWebView; + late MockWebSettings mockWebSettings; + + late MockWebViewAndroidDownloadListener mockDownloadListener; + late MockWebViewAndroidJavaScriptChannel mockJavaScriptChannel; + late MockWebViewAndroidWebChromeClient mockWebChromeClient; + late MockWebViewAndroidWebViewClient mockWebViewClient; + late MockWebViewPlatformCallbacksHandler mockCallbacksHandler; + late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; + // + // setUp(() { + // mockWebViewHostApi = MockTestWebViewHostApi(); + // mockWebSettingsHostApi = MockTestWebSettingsHostApi(); + // mockWebViewClientHostApi = MockTestWebViewClientHostApi(); + // mockWebChromeClientHostApi = MockTestWebChromeClientHostApi(); + // mockJavaScriptChannelHostApi = MockTestJavaScriptChannelHostApi(); + // mockDownloadListenerHostApi = MockTestDownloadListenerHostApi(); + // + // TestWebViewHostApi.setup(mockWebViewHostApi); + // TestWebSettingsHostApi.setup(mockWebSettingsHostApi); + // TestWebViewClientHostApi.setup(mockWebViewClientHostApi); + // TestWebChromeClientHostApi.setup(mockWebChromeClientHostApi); + // TestJavaScriptChannelHostApi.setup(mockJavaScriptChannelHostApi); + // TestDownloadListenerHostApi.setup(mockDownloadListenerHostApi); + // mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); - - final InstanceManager instanceManager = InstanceManager(); - android_webview.WebView.api = WebViewHostApiImpl( - instanceManager: instanceManager, - ); - android_webview.WebSettings.api = WebSettingsHostApiImpl( - instanceManager: instanceManager, - ); - android_webview.JavaScriptChannel.api = JavaScriptChannelHostApiImpl( - instanceManager: instanceManager, - ); - android_webview.WebViewClient.api = WebViewClientHostApiImpl( - instanceManager: instanceManager, - ); - android_webview.DownloadListener.api = DownloadListenerHostApiImpl( - instanceManager: instanceManager, - ); - android_webview.WebChromeClient.api = WebChromeClientHostApiImpl( - instanceManager: instanceManager, - ); - }); + // + // final InstanceManager instanceManager = InstanceManager(); + // android_webview.WebView.api = WebViewHostApiImpl( + // instanceManager: instanceManager, + // ); + // android_webview.WebSettings.api = WebSettingsHostApiImpl( + // instanceManager: instanceManager, + // ); + // android_webview.JavaScriptChannel.api = JavaScriptChannelHostApiImpl( + // instanceManager: instanceManager, + // ); + // android_webview.WebViewClient.api = WebViewClientHostApiImpl( + // instanceManager: instanceManager, + // ); + // android_webview.DownloadListener.api = DownloadListenerHostApiImpl( + // instanceManager: instanceManager, + // ); + // android_webview.WebChromeClient.api = WebChromeClientHostApiImpl( + // instanceManager: instanceManager, + // ); + // }); // Builds a AndroidWebViewWidget with default parameters. - Future buildWidget( + Future buildWidget( WidgetTester tester, { - Widget Function(AndroidWebViewPlatformController platformController)? - onBuildWidget, CreationParams? creationParams, WebViewPlatformCallbacksHandler? webViewPlatformCallbacksHandler, JavascriptChannelRegistry? javascriptChannelRegistry, bool? useHybridComposition, + Widget Function(WebViewAndroidPlatformController platformController)? + onBuildWidget, }) async { - final Completer controllerCompleter = - Completer(); - - await tester.pumpWidget( - AndroidWebViewWidget( - onBuildWidget: onBuildWidget ?? - (AndroidWebViewPlatformController controller) { - controllerCompleter.complete(controller); - return Container(); - }, - creationParams: creationParams ?? CreationParams(), - webViewPlatformCallbacksHandler: - webViewPlatformCallbacksHandler ?? mockCallbacksHandler, - javascriptChannelRegistry: - javascriptChannelRegistry ?? mockJavascriptChannelRegistry, - useHybridComposition: useHybridComposition ?? false, - ), - ); + final Completer controllerCompleter = + Completer(); + + // await tester.pumpWidget( + // AndroidWebViewWidget( + // onBuildWidget: onBuildWidget ?? + // (WebViewAndroidPlatformController controller) { + // controllerCompleter.complete(controller); + // return Container(); + // }, + // creationParams: creationParams ?? CreationParams(), + // webViewPlatformCallbacksHandler: + // webViewPlatformCallbacksHandler ?? mockCallbacksHandler, + // javascriptChannelRegistry: + // javascriptChannelRegistry ?? mockJavascriptChannelRegistry, + // useHybridComposition: useHybridComposition ?? false, + // ), + // ); return controllerCompleter.future; } @@ -116,314 +125,314 @@ void main() { testWidgets('Create Widget', (WidgetTester tester) async { await buildWidget(tester); - verify(mockWebSettingsHostApi.setDomStorageEnabled(1, true)); - verify(mockWebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically( - 1, - true, - )); - verify(mockWebSettingsHostApi.setSupportMultipleWindows(1, true)); - verify(mockWebSettingsHostApi.setLoadWithOverviewMode(1, true)); - verify(mockWebSettingsHostApi.setUseWideViewPort(1, true)); - verify(mockWebSettingsHostApi.setDisplayZoomControls(1, false)); - verify(mockWebSettingsHostApi.setBuiltInZoomControls(1, true)); - - verifyInOrder([ - mockWebViewHostApi.create(0, false), - mockWebViewHostApi.setWebViewClient(0, any), - mockWebViewHostApi.setDownloadListener(0, any), - mockWebViewHostApi.setWebChromeClient(0, any), - ]); - }); - - testWidgets( - 'Create Widget with Hybrid Composition', - (WidgetTester tester) async { - await buildWidget(tester, useHybridComposition: true); - verify(mockWebViewHostApi.create(0, true)); - }, - ); - - group('CreationParams', () { - testWidgets('initialUrl', (WidgetTester tester) async { - await buildWidget( - tester, - creationParams: CreationParams(initialUrl: 'https://www.google.com'), - ); - verify(mockWebViewHostApi.loadUrl( - 0, - 'https://www.google.com', - {}, - )); - }); - - testWidgets('userAgent', (WidgetTester tester) async { - await buildWidget( - tester, - creationParams: CreationParams(userAgent: 'MyUserAgent'), - ); - - verify(mockWebSettingsHostApi.setUserAgentString(1, 'MyUserAgent')); - }); - - testWidgets('autoMediaPlaybackPolicy true', (WidgetTester tester) async { - await buildWidget( - tester, - creationParams: CreationParams( - autoMediaPlaybackPolicy: - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - ), - ); - - verify( - mockWebSettingsHostApi.setMediaPlaybackRequiresUserGesture(any, true), - ); - }); - - testWidgets('autoMediaPlaybackPolicy false', (WidgetTester tester) async { - await buildWidget( - tester, - creationParams: CreationParams( - autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ); - - verify(mockWebSettingsHostApi.setMediaPlaybackRequiresUserGesture( - any, - false, - )); - }); - - testWidgets('javascriptChannelNames', (WidgetTester tester) async { - await buildWidget( - tester, - creationParams: CreationParams( - javascriptChannelNames: {'a', 'b'}, - ), - ); - - verify(mockJavaScriptChannelHostApi.create(any, 'a')); - verify(mockJavaScriptChannelHostApi.create(any, 'b')); - verify(mockWebViewHostApi.addJavaScriptChannel(0, any)).called(2); - }); - - group('WebSettings', () { - testWidgets('javascriptMode', (WidgetTester tester) async { - await buildWidget( - tester, - creationParams: CreationParams( - webSettings: WebSettings( - userAgent: WebSetting.absent(), - javascriptMode: JavascriptMode.unrestricted, - ), - ), - ); - - verify(mockWebSettingsHostApi.setJavaScriptEnabled(any, true)); - }); - - testWidgets('hasNavigationDelegate', (WidgetTester tester) async { - await buildWidget( - tester, - creationParams: CreationParams( - webSettings: WebSettings( - userAgent: WebSetting.absent(), - hasNavigationDelegate: true, - ), - ), - ); - - verify(mockWebViewClientHostApi.create(any, true)); - }); - - testWidgets('debuggingEnabled', (WidgetTester tester) async { - await buildWidget( - tester, - creationParams: CreationParams( - webSettings: WebSettings( - userAgent: WebSetting.absent(), - debuggingEnabled: true, - ), - ), - ); - - verify(mockWebViewHostApi.setWebContentsDebuggingEnabled(true)); - }); - - testWidgets('userAgent', (WidgetTester tester) async { - await buildWidget( - tester, - creationParams: CreationParams( - webSettings: WebSettings( - userAgent: WebSetting.of('myUserAgent'), - ), - ), - ); - - verify(mockWebSettingsHostApi.setUserAgentString(any, 'myUserAgent')); - }); - - testWidgets('zoomEnabled', (WidgetTester tester) async { - await buildWidget( - tester, - creationParams: CreationParams( - webSettings: WebSettings( - userAgent: WebSetting.absent(), - zoomEnabled: false, - ), - ), - ); - - verify(mockWebSettingsHostApi.setSupportZoom(any, false)); - }); - }); - }); - - group('$AndroidWebViewPlatformController', () { - testWidgets('loadUrl', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - await controller.loadUrl( - 'https://www.google.com', - {'a': 'header'}, - ); - - verify(mockWebViewHostApi.loadUrl( - any, - 'https://www.google.com', - {'a': 'header'}, - )); - }); - - testWidgets('currentUrl', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - when(mockWebViewHostApi.getUrl(any)) - .thenReturn('https://www.google.com'); - expect(controller.currentUrl(), completion('https://www.google.com')); - }); - - testWidgets('canGoBack', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - when(mockWebViewHostApi.canGoBack(any)).thenReturn(false); - expect(controller.canGoBack(), completion(false)); - }); - - testWidgets('canGoForward', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - when(mockWebViewHostApi.canGoForward(any)).thenReturn(true); - expect(controller.canGoForward(), completion(true)); - }); - - testWidgets('goBack', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - await controller.goBack(); - verify(mockWebViewHostApi.goBack(any)); - }); - - testWidgets('goForward', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - await controller.goForward(); - verify(mockWebViewHostApi.goForward(any)); - }); - - testWidgets('reload', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - await controller.reload(); - verify(mockWebViewHostApi.reload(any)); - }); - - testWidgets('clearCache', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - await controller.clearCache(); - verify(mockWebViewHostApi.clearCache(any, true)); - }); - - testWidgets('evaluateJavascript', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - when(mockWebViewHostApi.evaluateJavascript(any, 'runJavaScript')) - .thenAnswer( - (_) => Future.value('returnString'), - ); - expect( - controller.evaluateJavascript('runJavaScript'), - completion('returnString'), - ); - }); - - testWidgets('addJavascriptChannels', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - await controller.addJavascriptChannels({'c', 'd'}); - verify(mockJavaScriptChannelHostApi.create(any, 'c')); - verify(mockJavaScriptChannelHostApi.create(any, 'd')); - verify(mockWebViewHostApi.addJavaScriptChannel(0, any)).called(2); - }); - - testWidgets('removeJavascriptChannels', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - await controller.addJavascriptChannels({'c', 'd'}); - await controller.removeJavascriptChannels({'c', 'd'}); - verify(mockWebViewHostApi.removeJavaScriptChannel(0, any)).called(2); - }); - - testWidgets('getTitle', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - when(mockWebViewHostApi.getTitle(any)).thenReturn('Web Title'); - expect(controller.getTitle(), completion('Web Title')); - }); - - testWidgets('scrollTo', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - await controller.scrollTo(1, 2); - verify(mockWebViewHostApi.scrollTo(any, 1, 2)); - }); - - testWidgets('scrollBy', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - await controller.scrollBy(3, 4); - verify(mockWebViewHostApi.scrollBy(any, 3, 4)); - }); - - testWidgets('getScrollX', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - when(mockWebViewHostApi.getScrollX(any)).thenReturn(23); - expect(controller.getScrollX(), completion(23)); - }); - - testWidgets('getScrollY', (WidgetTester tester) async { - final AndroidWebViewPlatformController controller = - await buildWidget(tester); - - when(mockWebViewHostApi.getScrollY(any)).thenReturn(25); - expect(controller.getScrollY(), completion(25)); - }); + // verify(mockWebSettingsHostApi.setDomStorageEnabled(1, true)); + // verify(mockWebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically( + // 1, + // true, + // )); + // verify(mockWebSettingsHostApi.setSupportMultipleWindows(1, true)); + // verify(mockWebSettingsHostApi.setLoadWithOverviewMode(1, true)); + // verify(mockWebSettingsHostApi.setUseWideViewPort(1, true)); + // verify(mockWebSettingsHostApi.setDisplayZoomControls(1, false)); + // verify(mockWebSettingsHostApi.setBuiltInZoomControls(1, true)); + // + // verifyInOrder([ + // mockWebViewHostApi.create(0, false), + // mockWebViewHostApi.setWebViewClient(0, any), + // mockWebViewHostApi.setDownloadListener(0, any), + // mockWebViewHostApi.setWebChromeClient(0, any), + // ]); }); + // + // testWidgets( + // 'Create Widget with Hybrid Composition', + // (WidgetTester tester) async { + // await buildWidget(tester, useHybridComposition: true); + // verify(mockWebViewHostApi.create(0, true)); + // }, + // ); + // + // group('CreationParams', () { + // testWidgets('initialUrl', (WidgetTester tester) async { + // await buildWidget( + // tester, + // creationParams: CreationParams(initialUrl: 'https://www.google.com'), + // ); + // verify(mockWebViewHostApi.loadUrl( + // 0, + // 'https://www.google.com', + // {}, + // )); + // }); + // + // testWidgets('userAgent', (WidgetTester tester) async { + // await buildWidget( + // tester, + // creationParams: CreationParams(userAgent: 'MyUserAgent'), + // ); + // + // verify(mockWebSettingsHostApi.setUserAgentString(1, 'MyUserAgent')); + // }); + // + // testWidgets('autoMediaPlaybackPolicy true', (WidgetTester tester) async { + // await buildWidget( + // tester, + // creationParams: CreationParams( + // autoMediaPlaybackPolicy: + // AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, + // ), + // ); + // + // verify( + // mockWebSettingsHostApi.setMediaPlaybackRequiresUserGesture(any, true), + // ); + // }); + // + // testWidgets('autoMediaPlaybackPolicy false', (WidgetTester tester) async { + // await buildWidget( + // tester, + // creationParams: CreationParams( + // autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + // ), + // ); + // + // verify(mockWebSettingsHostApi.setMediaPlaybackRequiresUserGesture( + // any, + // false, + // )); + // }); + // + // testWidgets('javascriptChannelNames', (WidgetTester tester) async { + // await buildWidget( + // tester, + // creationParams: CreationParams( + // javascriptChannelNames: {'a', 'b'}, + // ), + // ); + // + // verify(mockJavaScriptChannelHostApi.create(any, 'a')); + // verify(mockJavaScriptChannelHostApi.create(any, 'b')); + // verify(mockWebViewHostApi.addJavaScriptChannel(0, any)).called(2); + // }); + // + // group('WebSettings', () { + // testWidgets('javascriptMode', (WidgetTester tester) async { + // await buildWidget( + // tester, + // creationParams: CreationParams( + // webSettings: WebSettings( + // userAgent: WebSetting.absent(), + // javascriptMode: JavascriptMode.unrestricted, + // ), + // ), + // ); + // + // verify(mockWebSettingsHostApi.setJavaScriptEnabled(any, true)); + // }); + // + // testWidgets('hasNavigationDelegate', (WidgetTester tester) async { + // await buildWidget( + // tester, + // creationParams: CreationParams( + // webSettings: WebSettings( + // userAgent: WebSetting.absent(), + // hasNavigationDelegate: true, + // ), + // ), + // ); + // + // verify(mockWebViewClientHostApi.create(any, true)); + // }); + // + // testWidgets('debuggingEnabled', (WidgetTester tester) async { + // await buildWidget( + // tester, + // creationParams: CreationParams( + // webSettings: WebSettings( + // userAgent: WebSetting.absent(), + // debuggingEnabled: true, + // ), + // ), + // ); + // + // verify(mockWebViewHostApi.setWebContentsDebuggingEnabled(true)); + // }); + // + // testWidgets('userAgent', (WidgetTester tester) async { + // await buildWidget( + // tester, + // creationParams: CreationParams( + // webSettings: WebSettings( + // userAgent: WebSetting.of('myUserAgent'), + // ), + // ), + // ); + // + // verify(mockWebSettingsHostApi.setUserAgentString(any, 'myUserAgent')); + // }); + // + // testWidgets('zoomEnabled', (WidgetTester tester) async { + // await buildWidget( + // tester, + // creationParams: CreationParams( + // webSettings: WebSettings( + // userAgent: WebSetting.absent(), + // zoomEnabled: false, + // ), + // ), + // ); + // + // verify(mockWebSettingsHostApi.setSupportZoom(any, false)); + // }); + // }); + // }); + // + // group('$AndroidWebViewPlatformController', () { + // testWidgets('loadUrl', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // await controller.loadUrl( + // 'https://www.google.com', + // {'a': 'header'}, + // ); + // + // verify(mockWebViewHostApi.loadUrl( + // any, + // 'https://www.google.com', + // {'a': 'header'}, + // )); + // }); + // + // testWidgets('currentUrl', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // when(mockWebViewHostApi.getUrl(any)) + // .thenReturn('https://www.google.com'); + // expect(controller.currentUrl(), completion('https://www.google.com')); + // }); + // + // testWidgets('canGoBack', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // when(mockWebViewHostApi.canGoBack(any)).thenReturn(false); + // expect(controller.canGoBack(), completion(false)); + // }); + // + // testWidgets('canGoForward', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // when(mockWebViewHostApi.canGoForward(any)).thenReturn(true); + // expect(controller.canGoForward(), completion(true)); + // }); + // + // testWidgets('goBack', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // await controller.goBack(); + // verify(mockWebViewHostApi.goBack(any)); + // }); + // + // testWidgets('goForward', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // await controller.goForward(); + // verify(mockWebViewHostApi.goForward(any)); + // }); + // + // testWidgets('reload', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // await controller.reload(); + // verify(mockWebViewHostApi.reload(any)); + // }); + // + // testWidgets('clearCache', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // await controller.clearCache(); + // verify(mockWebViewHostApi.clearCache(any, true)); + // }); + // + // testWidgets('evaluateJavascript', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // when(mockWebViewHostApi.evaluateJavascript(any, 'runJavaScript')) + // .thenAnswer( + // (_) => Future.value('returnString'), + // ); + // expect( + // controller.evaluateJavascript('runJavaScript'), + // completion('returnString'), + // ); + // }); + // + // testWidgets('addJavascriptChannels', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // await controller.addJavascriptChannels({'c', 'd'}); + // verify(mockJavaScriptChannelHostApi.create(any, 'c')); + // verify(mockJavaScriptChannelHostApi.create(any, 'd')); + // verify(mockWebViewHostApi.addJavaScriptChannel(0, any)).called(2); + // }); + // + // testWidgets('removeJavascriptChannels', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // await controller.addJavascriptChannels({'c', 'd'}); + // await controller.removeJavascriptChannels({'c', 'd'}); + // verify(mockWebViewHostApi.removeJavaScriptChannel(0, any)).called(2); + // }); + // + // testWidgets('getTitle', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // when(mockWebViewHostApi.getTitle(any)).thenReturn('Web Title'); + // expect(controller.getTitle(), completion('Web Title')); + // }); + // + // testWidgets('scrollTo', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // await controller.scrollTo(1, 2); + // verify(mockWebViewHostApi.scrollTo(any, 1, 2)); + // }); + // + // testWidgets('scrollBy', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // await controller.scrollBy(3, 4); + // verify(mockWebViewHostApi.scrollBy(any, 3, 4)); + // }); + // + // testWidgets('getScrollX', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // when(mockWebViewHostApi.getScrollX(any)).thenReturn(23); + // expect(controller.getScrollX(), completion(23)); + // }); + // + // testWidgets('getScrollY', (WidgetTester tester) async { + // final AndroidWebViewPlatformController controller = + // await buildWidget(tester); + // + // when(mockWebViewHostApi.getScrollY(any)).thenReturn(25); + // expect(controller.getScrollY(), completion(25)); + // }); + // }); }); } diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.mocks.dart index 5f4381a580d4..a01da11aa375 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.mocks.dart @@ -6,16 +6,13 @@ // in webview_flutter_android/test/webview_widget_test.dart. // Do not manually edit this file. -import 'dart:async' as _i3; +import 'dart:async' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_platform_interface/src/platform_interface/javascript_channel_registry.dart' - as _i6; -import 'package:webview_flutter_platform_interface/src/platform_interface/webview_platform_callbacks_handler.dart' - as _i4; -import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i5; - -import 'android_webview.pigeon.dart' as _i2; +import 'package:webview_flutter_android/src/android_webview.dart' as _i2; +import 'package:webview_flutter_android/webview_widget.dart' as _i5; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' + as _i3; // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters @@ -26,264 +23,379 @@ import 'android_webview.pigeon.dart' as _i2; // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types -/// A class which mocks [TestWebViewHostApi]. +class _FakeWebSettings_0 extends _i1.Fake implements _i2.WebSettings {} + +class _FakeWebViewPlatformCallbacksHandler_1 extends _i1.Fake + implements _i3.WebViewPlatformCallbacksHandler {} + +class _FakeJavascriptChannelRegistry_2 extends _i1.Fake + implements _i3.JavascriptChannelRegistry {} + +/// A class which mocks [WebSettings]. /// /// See the documentation for Mockito's code generation for more information. -class MockTestWebViewHostApi extends _i1.Mock - implements _i2.TestWebViewHostApi { - MockTestWebViewHostApi() { +class MockWebSettings extends _i1.Mock implements _i2.WebSettings { + MockWebSettings() { _i1.throwOnMissingStub(this); } @override - void create(int? instanceId, bool? useHybridComposition) => - super.noSuchMethod( - Invocation.method(#create, [instanceId, useHybridComposition]), - returnValueForMissingStub: null); - @override - void dispose(int? instanceId) => - super.noSuchMethod(Invocation.method(#dispose, [instanceId]), - returnValueForMissingStub: null); + _i4.Future setDomStorageEnabled(bool? flag) => + (super.noSuchMethod(Invocation.method(#setDomStorageEnabled, [flag]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); @override - void loadUrl(int? instanceId, String? url, Map? headers) => - super.noSuchMethod( - Invocation.method(#loadUrl, [instanceId, url, headers]), - returnValueForMissingStub: null); - @override - String getUrl(int? instanceId) => - (super.noSuchMethod(Invocation.method(#getUrl, [instanceId]), - returnValue: '') as String); + _i4.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => + (super.noSuchMethod( + Invocation.method(#setJavaScriptCanOpenWindowsAutomatically, [flag]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setSupportMultipleWindows(bool? support) => (super + .noSuchMethod(Invocation.method(#setSupportMultipleWindows, [support]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setJavaScriptEnabled(bool? flag) => + (super.noSuchMethod(Invocation.method(#setJavaScriptEnabled, [flag]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setUserAgentString(String? userAgentString) => (super + .noSuchMethod(Invocation.method(#setUserAgentString, [userAgentString]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setMediaPlaybackRequiresUserGesture(bool? require) => + (super.noSuchMethod( + Invocation.method(#setMediaPlaybackRequiresUserGesture, [require]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setSupportZoom(bool? support) => + (super.noSuchMethod(Invocation.method(#setSupportZoom, [support]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setLoadWithOverviewMode(bool? overview) => (super + .noSuchMethod(Invocation.method(#setLoadWithOverviewMode, [overview]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setUseWideViewPort(bool? use) => + (super.noSuchMethod(Invocation.method(#setUseWideViewPort, [use]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setDisplayZoomControls(bool? enabled) => + (super.noSuchMethod(Invocation.method(#setDisplayZoomControls, [enabled]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setBuiltInZoomControls(bool? enabled) => + (super.noSuchMethod(Invocation.method(#setBuiltInZoomControls, [enabled]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); @override - bool canGoBack(int? instanceId) => - (super.noSuchMethod(Invocation.method(#canGoBack, [instanceId]), - returnValue: false) as bool); + String toString() => super.toString(); +} + +/// A class which mocks [WebView]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebView extends _i1.Mock implements _i2.WebView { + MockWebView() { + _i1.throwOnMissingStub(this); + } + @override - bool canGoForward(int? instanceId) => - (super.noSuchMethod(Invocation.method(#canGoForward, [instanceId]), + bool get useHybridComposition => + (super.noSuchMethod(Invocation.getter(#useHybridComposition), returnValue: false) as bool); @override - void goBack(int? instanceId) => - super.noSuchMethod(Invocation.method(#goBack, [instanceId]), - returnValueForMissingStub: null); - @override - void goForward(int? instanceId) => - super.noSuchMethod(Invocation.method(#goForward, [instanceId]), - returnValueForMissingStub: null); - @override - void reload(int? instanceId) => - super.noSuchMethod(Invocation.method(#reload, [instanceId]), - returnValueForMissingStub: null); - @override - void clearCache(int? instanceId, bool? includeDiskFiles) => - super.noSuchMethod( - Invocation.method(#clearCache, [instanceId, includeDiskFiles]), - returnValueForMissingStub: null); - @override - _i3.Future evaluateJavascript( - int? instanceId, String? javascriptString) => + _i2.WebSettings get settings => + (super.noSuchMethod(Invocation.getter(#settings), + returnValue: _FakeWebSettings_0()) as _i2.WebSettings); + @override + _i4.Future loadUrl(String? url, Map? headers) => + (super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future getUrl() => + (super.noSuchMethod(Invocation.method(#getUrl, []), + returnValue: Future.value()) as _i4.Future); + @override + _i4.Future canGoBack() => + (super.noSuchMethod(Invocation.method(#canGoBack, []), + returnValue: Future.value(false)) as _i4.Future); + @override + _i4.Future canGoForward() => + (super.noSuchMethod(Invocation.method(#canGoForward, []), + returnValue: Future.value(false)) as _i4.Future); + @override + _i4.Future goBack() => + (super.noSuchMethod(Invocation.method(#goBack, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future goForward() => + (super.noSuchMethod(Invocation.method(#goForward, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future reload() => + (super.noSuchMethod(Invocation.method(#reload, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future clearCache(bool? includeDiskFiles) => + (super.noSuchMethod(Invocation.method(#clearCache, [includeDiskFiles]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future evaluateJavascript(String? javascriptString) => (super + .noSuchMethod(Invocation.method(#evaluateJavascript, [javascriptString]), + returnValue: Future.value()) as _i4.Future); + @override + _i4.Future getTitle() => + (super.noSuchMethod(Invocation.method(#getTitle, []), + returnValue: Future.value()) as _i4.Future); + @override + _i4.Future scrollTo(int? x, int? y) => + (super.noSuchMethod(Invocation.method(#scrollTo, [x, y]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future scrollBy(int? x, int? y) => + (super.noSuchMethod(Invocation.method(#scrollBy, [x, y]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future getScrollX() => + (super.noSuchMethod(Invocation.method(#getScrollX, []), + returnValue: Future.value(0)) as _i4.Future); + @override + _i4.Future getScrollY() => + (super.noSuchMethod(Invocation.method(#getScrollY, []), + returnValue: Future.value(0)) as _i4.Future); + @override + _i4.Future setWebViewClient(_i2.WebViewClient? webViewClient) => + (super.noSuchMethod(Invocation.method(#setWebViewClient, [webViewClient]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future addJavaScriptChannel( + _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( - Invocation.method( - #evaluateJavascript, [instanceId, javascriptString]), - returnValue: Future.value('')) as _i3.Future); - @override - String getTitle(int? instanceId) => - (super.noSuchMethod(Invocation.method(#getTitle, [instanceId]), - returnValue: '') as String); - @override - void scrollTo(int? instanceId, int? x, int? y) => - super.noSuchMethod(Invocation.method(#scrollTo, [instanceId, x, y]), - returnValueForMissingStub: null); - @override - void scrollBy(int? instanceId, int? x, int? y) => - super.noSuchMethod(Invocation.method(#scrollBy, [instanceId, x, y]), - returnValueForMissingStub: null); + Invocation.method(#addJavaScriptChannel, [javaScriptChannel]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); @override - int getScrollX(int? instanceId) => - (super.noSuchMethod(Invocation.method(#getScrollX, [instanceId]), - returnValue: 0) as int); - @override - int getScrollY(int? instanceId) => - (super.noSuchMethod(Invocation.method(#getScrollY, [instanceId]), - returnValue: 0) as int); + _i4.Future removeJavaScriptChannel( + _i2.JavaScriptChannel? javaScriptChannel) => + (super.noSuchMethod( + Invocation.method(#removeJavaScriptChannel, [javaScriptChannel]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); @override - void setWebContentsDebuggingEnabled(bool? enabled) => super.noSuchMethod( - Invocation.method(#setWebContentsDebuggingEnabled, [enabled]), - returnValueForMissingStub: null); + _i4.Future setDownloadListener(_i2.DownloadListener? listener) => + (super.noSuchMethod(Invocation.method(#setDownloadListener, [listener]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); @override - void setWebViewClient(int? instanceId, int? webViewClientInstanceId) => - super.noSuchMethod( - Invocation.method( - #setWebViewClient, [instanceId, webViewClientInstanceId]), - returnValueForMissingStub: null); + _i4.Future setWebChromeClient(_i2.WebChromeClient? client) => + (super.noSuchMethod(Invocation.method(#setWebChromeClient, [client]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); @override - void addJavaScriptChannel( - int? instanceId, int? javaScriptChannelInstanceId) => - super.noSuchMethod( - Invocation.method( - #addJavaScriptChannel, [instanceId, javaScriptChannelInstanceId]), - returnValueForMissingStub: null); - @override - void removeJavaScriptChannel( - int? instanceId, int? javaScriptChannelInstanceId) => - super.noSuchMethod( - Invocation.method(#removeJavaScriptChannel, - [instanceId, javaScriptChannelInstanceId]), - returnValueForMissingStub: null); - @override - void setDownloadListener(int? instanceId, int? listenerInstanceId) => - super.noSuchMethod( - Invocation.method( - #setDownloadListener, [instanceId, listenerInstanceId]), - returnValueForMissingStub: null); - @override - void setWebChromeClient(int? instanceId, int? clientInstanceId) => - super.noSuchMethod( - Invocation.method( - #setWebChromeClient, [instanceId, clientInstanceId]), - returnValueForMissingStub: null); + _i4.Future release() => + (super.noSuchMethod(Invocation.method(#release, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); @override String toString() => super.toString(); } -/// A class which mocks [TestWebSettingsHostApi]. +/// A class which mocks [WebViewAndroidDownloadListener]. /// /// See the documentation for Mockito's code generation for more information. -class MockTestWebSettingsHostApi extends _i1.Mock - implements _i2.TestWebSettingsHostApi { - MockTestWebSettingsHostApi() { +class MockWebViewAndroidDownloadListener extends _i1.Mock + implements _i5.WebViewAndroidDownloadListener { + MockWebViewAndroidDownloadListener() { _i1.throwOnMissingStub(this); } @override - void create(int? instanceId, int? webViewInstanceId) => super.noSuchMethod( - Invocation.method(#create, [instanceId, webViewInstanceId]), - returnValueForMissingStub: null); - @override - void dispose(int? instanceId) => - super.noSuchMethod(Invocation.method(#dispose, [instanceId]), - returnValueForMissingStub: null); - @override - void setDomStorageEnabled(int? instanceId, bool? flag) => super.noSuchMethod( - Invocation.method(#setDomStorageEnabled, [instanceId, flag]), - returnValueForMissingStub: null); - @override - void setJavaScriptCanOpenWindowsAutomatically(int? instanceId, bool? flag) => - super.noSuchMethod( - Invocation.method( - #setJavaScriptCanOpenWindowsAutomatically, [instanceId, flag]), - returnValueForMissingStub: null); - @override - void setSupportMultipleWindows(int? instanceId, bool? support) => - super.noSuchMethod( - Invocation.method(#setSupportMultipleWindows, [instanceId, support]), - returnValueForMissingStub: null); - @override - void setJavaScriptEnabled(int? instanceId, bool? flag) => super.noSuchMethod( - Invocation.method(#setJavaScriptEnabled, [instanceId, flag]), - returnValueForMissingStub: null); - @override - void setUserAgentString(int? instanceId, String? userAgentString) => - super.noSuchMethod( - Invocation.method(#setUserAgentString, [instanceId, userAgentString]), - returnValueForMissingStub: null); - @override - void setMediaPlaybackRequiresUserGesture(int? instanceId, bool? require) => - super.noSuchMethod( - Invocation.method( - #setMediaPlaybackRequiresUserGesture, [instanceId, require]), - returnValueForMissingStub: null); + _i3.WebViewPlatformCallbacksHandler get callbacksHandler => + (super.noSuchMethod(Invocation.getter(#callbacksHandler), + returnValue: _FakeWebViewPlatformCallbacksHandler_1()) + as _i3.WebViewPlatformCallbacksHandler); @override - void setSupportZoom(int? instanceId, bool? support) => super.noSuchMethod( - Invocation.method(#setSupportZoom, [instanceId, support]), - returnValueForMissingStub: null); + _i4.Future Function(String, Map?) get loadUrl => + (super.noSuchMethod(Invocation.getter(#loadUrl), + returnValue: (String url, Map? headers) => + Future.value()) as _i4.Future Function( + String, Map?)); @override - void setLoadWithOverviewMode(int? instanceId, bool? overview) => - super.noSuchMethod( - Invocation.method(#setLoadWithOverviewMode, [instanceId, overview]), - returnValueForMissingStub: null); + bool get hasNavigationDelegate => + (super.noSuchMethod(Invocation.getter(#hasNavigationDelegate), + returnValue: false) as bool); @override - void setUseWideViewPort(int? instanceId, bool? use) => super.noSuchMethod( - Invocation.method(#setUseWideViewPort, [instanceId, use]), + set hasNavigationDelegate(bool? _hasNavigationDelegate) => super.noSuchMethod( + Invocation.setter(#hasNavigationDelegate, _hasNavigationDelegate), returnValueForMissingStub: null); @override - void setDisplayZoomControls(int? instanceId, bool? enabled) => + void onDownloadStart(String? url, String? userAgent, + String? contentDisposition, String? mimetype, int? contentLength) => super.noSuchMethod( - Invocation.method(#setDisplayZoomControls, [instanceId, enabled]), - returnValueForMissingStub: null); - @override - void setBuiltInZoomControls(int? instanceId, bool? enabled) => - super.noSuchMethod( - Invocation.method(#setBuiltInZoomControls, [instanceId, enabled]), + Invocation.method(#onDownloadStart, + [url, userAgent, contentDisposition, mimetype, contentLength]), returnValueForMissingStub: null); @override String toString() => super.toString(); } -/// A class which mocks [TestWebViewClientHostApi]. +/// A class which mocks [WebViewAndroidJavaScriptChannel]. /// /// See the documentation for Mockito's code generation for more information. -class MockTestWebViewClientHostApi extends _i1.Mock - implements _i2.TestWebViewClientHostApi { - MockTestWebViewClientHostApi() { +class MockWebViewAndroidJavaScriptChannel extends _i1.Mock + implements _i5.WebViewAndroidJavaScriptChannel { + MockWebViewAndroidJavaScriptChannel() { _i1.throwOnMissingStub(this); } @override - void create(int? instanceId, bool? shouldOverrideUrlLoading) => - super.noSuchMethod( - Invocation.method(#create, [instanceId, shouldOverrideUrlLoading]), + _i3.JavascriptChannelRegistry get javascriptChannelRegistry => + (super.noSuchMethod(Invocation.getter(#javascriptChannelRegistry), + returnValue: _FakeJavascriptChannelRegistry_2()) + as _i3.JavascriptChannelRegistry); + @override + String get channelName => + (super.noSuchMethod(Invocation.getter(#channelName), returnValue: '') + as String); + @override + void postMessage(String? message) => + super.noSuchMethod(Invocation.method(#postMessage, [message]), returnValueForMissingStub: null); @override String toString() => super.toString(); } -/// A class which mocks [TestWebChromeClientHostApi]. +/// A class which mocks [WebViewAndroidWebChromeClient]. /// /// See the documentation for Mockito's code generation for more information. -class MockTestWebChromeClientHostApi extends _i1.Mock - implements _i2.TestWebChromeClientHostApi { - MockTestWebChromeClientHostApi() { +class MockWebViewAndroidWebChromeClient extends _i1.Mock + implements _i5.WebViewAndroidWebChromeClient { + MockWebViewAndroidWebChromeClient() { _i1.throwOnMissingStub(this); } @override - void create(int? instanceId, int? webViewClientInstanceId) => - super.noSuchMethod( - Invocation.method(#create, [instanceId, webViewClientInstanceId]), + _i3.WebViewPlatformCallbacksHandler get callbacksHandler => + (super.noSuchMethod(Invocation.getter(#callbacksHandler), + returnValue: _FakeWebViewPlatformCallbacksHandler_1()) + as _i3.WebViewPlatformCallbacksHandler); + @override + bool get hasProgressTracking => + (super.noSuchMethod(Invocation.getter(#hasProgressTracking), + returnValue: false) as bool); + @override + set hasProgressTracking(bool? _hasProgressTracking) => super.noSuchMethod( + Invocation.setter(#hasProgressTracking, _hasProgressTracking), + returnValueForMissingStub: null); + @override + void onProgressChanged(_i2.WebView? webView, int? progress) => super + .noSuchMethod(Invocation.method(#onProgressChanged, [webView, progress]), returnValueForMissingStub: null); @override String toString() => super.toString(); } -/// A class which mocks [TestJavaScriptChannelHostApi]. +/// A class which mocks [WebViewAndroidWebViewClient]. /// /// See the documentation for Mockito's code generation for more information. -class MockTestJavaScriptChannelHostApi extends _i1.Mock - implements _i2.TestJavaScriptChannelHostApi { - MockTestJavaScriptChannelHostApi() { +class MockWebViewAndroidWebViewClient extends _i1.Mock + implements _i5.WebViewAndroidWebViewClient { + MockWebViewAndroidWebViewClient() { _i1.throwOnMissingStub(this); } @override - void create(int? instanceId, String? channelName) => - super.noSuchMethod(Invocation.method(#create, [instanceId, channelName]), + _i3.WebViewPlatformCallbacksHandler get callbacksHandler => + (super.noSuchMethod(Invocation.getter(#callbacksHandler), + returnValue: _FakeWebViewPlatformCallbacksHandler_1()) + as _i3.WebViewPlatformCallbacksHandler); + @override + _i4.Future Function(String, Map?) get loadUrl => + (super.noSuchMethod(Invocation.getter(#loadUrl), + returnValue: (String url, Map? headers) => + Future.value()) as _i4.Future Function( + String, Map?)); + @override + bool get hasNavigationDelegate => + (super.noSuchMethod(Invocation.getter(#hasNavigationDelegate), + returnValue: false) as bool); + @override + bool get shouldOverrideUrlLoading => + (super.noSuchMethod(Invocation.getter(#shouldOverrideUrlLoading), + returnValue: false) as bool); + @override + void onPageStarted(_i2.WebView? webView, String? url) => + super.noSuchMethod(Invocation.method(#onPageStarted, [webView, url]), + returnValueForMissingStub: null); + @override + void onPageFinished(_i2.WebView? webView, String? url) => + super.noSuchMethod(Invocation.method(#onPageFinished, [webView, url]), + returnValueForMissingStub: null); + @override + void onReceivedError(_i2.WebView? webView, int? errorCode, + String? description, String? failingUrl) => + super.noSuchMethod( + Invocation.method( + #onReceivedError, [webView, errorCode, description, failingUrl]), + returnValueForMissingStub: null); + @override + void onReceivedRequestError(_i2.WebView? webView, + _i2.WebResourceRequest? request, _i2.WebResourceError? error) => + super.noSuchMethod( + Invocation.method(#onReceivedRequestError, [webView, request, error]), + returnValueForMissingStub: null); + @override + void urlLoading(_i2.WebView? webView, String? url) => + super.noSuchMethod(Invocation.method(#urlLoading, [webView, url]), + returnValueForMissingStub: null); + @override + void requestLoading(_i2.WebView? webView, _i2.WebResourceRequest? request) => + super.noSuchMethod(Invocation.method(#requestLoading, [webView, request]), returnValueForMissingStub: null); @override String toString() => super.toString(); } -/// A class which mocks [TestDownloadListenerHostApi]. +/// A class which mocks [JavascriptChannelRegistry]. /// /// See the documentation for Mockito's code generation for more information. -class MockTestDownloadListenerHostApi extends _i1.Mock - implements _i2.TestDownloadListenerHostApi { - MockTestDownloadListenerHostApi() { +class MockJavascriptChannelRegistry extends _i1.Mock + implements _i3.JavascriptChannelRegistry { + MockJavascriptChannelRegistry() { _i1.throwOnMissingStub(this); } @override - void create(int? instanceId) => - super.noSuchMethod(Invocation.method(#create, [instanceId]), + Map get channels => + (super.noSuchMethod(Invocation.getter(#channels), + returnValue: {}) + as Map); + @override + void onJavascriptChannelMessage(String? channel, String? message) => + super.noSuchMethod( + Invocation.method(#onJavascriptChannelMessage, [channel, message]), + returnValueForMissingStub: null); + @override + void updateJavascriptChannelsFromSet(Set<_i3.JavascriptChannel>? channels) => + super.noSuchMethod( + Invocation.method(#updateJavascriptChannelsFromSet, [channels]), returnValueForMissingStub: null); @override String toString() => super.toString(); @@ -293,17 +405,17 @@ class MockTestDownloadListenerHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockWebViewPlatformCallbacksHandler extends _i1.Mock - implements _i4.WebViewPlatformCallbacksHandler { + implements _i3.WebViewPlatformCallbacksHandler { MockWebViewPlatformCallbacksHandler() { _i1.throwOnMissingStub(this); } @override - _i3.FutureOr onNavigationRequest({String? url, bool? isForMainFrame}) => + _i4.FutureOr onNavigationRequest({String? url, bool? isForMainFrame}) => (super.noSuchMethod( Invocation.method(#onNavigationRequest, [], {#url: url, #isForMainFrame: isForMainFrame}), - returnValue: Future.value(false)) as _i3.FutureOr); + returnValue: Future.value(false)) as _i4.FutureOr); @override void onPageStarted(String? url) => super.noSuchMethod(Invocation.method(#onPageStarted, [url]), @@ -317,37 +429,9 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock super.noSuchMethod(Invocation.method(#onProgress, [progress]), returnValueForMissingStub: null); @override - void onWebResourceError(_i5.WebResourceError? error) => + void onWebResourceError(_i3.WebResourceError? error) => super.noSuchMethod(Invocation.method(#onWebResourceError, [error]), returnValueForMissingStub: null); @override String toString() => super.toString(); } - -/// A class which mocks [JavascriptChannelRegistry]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockJavascriptChannelRegistry extends _i1.Mock - implements _i6.JavascriptChannelRegistry { - MockJavascriptChannelRegistry() { - _i1.throwOnMissingStub(this); - } - - @override - Map get channels => - (super.noSuchMethod(Invocation.getter(#channels), - returnValue: {}) - as Map); - @override - void onJavascriptChannelMessage(String? channel, String? message) => - super.noSuchMethod( - Invocation.method(#onJavascriptChannelMessage, [channel, message]), - returnValueForMissingStub: null); - @override - void updateJavascriptChannelsFromSet(Set<_i5.JavascriptChannel>? channels) => - super.noSuchMethod( - Invocation.method(#updateJavascriptChannelsFromSet, [channels]), - returnValueForMissingStub: null); - @override - String toString() => super.toString(); -} From e87b43ede8aff87895515244f594fda9161d81be Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Thu, 11 Nov 2021 11:56:31 -0800 Subject: [PATCH 04/33] most tests passing --- .../lib/webview_widget.dart | 4 +- .../test/webview_widget_test.dart | 751 ++++++++++-------- 2 files changed, 402 insertions(+), 353 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart index 4fcb7ce1fac4..f26180ccdc49 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart @@ -71,7 +71,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { loadUrl: loadUrl, ); - this.webChromeClient = + this.webChromeClient = webChromeClient ?? WebViewAndroidWebChromeClient(callbacksHandler: callbacksHandler); webView.setWebViewClient(this.webViewClient); @@ -356,7 +356,7 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { WebViewAndroidWebViewClient({ required this.callbacksHandler, required this.loadUrl, - required this.hasNavigationDelegate, + this.hasNavigationDelegate = false, }) : super(shouldOverrideUrlLoading: hasNavigationDelegate); final WebViewPlatformCallbacksHandler callbacksHandler; diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart index 5ebe3e24bd3e..dd203af2a637 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart @@ -10,8 +10,6 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_android/src/android_webview.dart' as android_webview; -import 'package:webview_flutter_android/src/android_webview_api_impls.dart'; -import 'package:webview_flutter_android/src/instance_manager.dart'; import 'package:webview_flutter_android/webview_widget.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; @@ -43,65 +41,92 @@ void main() { late MockWebView mockWebView; late MockWebSettings mockWebSettings; - late MockWebViewAndroidDownloadListener mockDownloadListener; - late MockWebViewAndroidJavaScriptChannel mockJavaScriptChannel; - late MockWebViewAndroidWebChromeClient mockWebChromeClient; - late MockWebViewAndroidWebViewClient mockWebViewClient; late MockWebViewPlatformCallbacksHandler mockCallbacksHandler; + late WebViewAndroidWebViewClient webViewClient; + late WebViewAndroidDownloadListener downloadListener; + late WebViewAndroidWebChromeClient webChromeClient; + + //late WebViewAndroidJavaScriptChannel javaScriptChannel; late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; // - // setUp(() { - // mockWebViewHostApi = MockTestWebViewHostApi(); - // mockWebSettingsHostApi = MockTestWebSettingsHostApi(); - // mockWebViewClientHostApi = MockTestWebViewClientHostApi(); - // mockWebChromeClientHostApi = MockTestWebChromeClientHostApi(); - // mockJavaScriptChannelHostApi = MockTestJavaScriptChannelHostApi(); - // mockDownloadListenerHostApi = MockTestDownloadListenerHostApi(); - // - // TestWebViewHostApi.setup(mockWebViewHostApi); - // TestWebSettingsHostApi.setup(mockWebSettingsHostApi); - // TestWebViewClientHostApi.setup(mockWebViewClientHostApi); - // TestWebChromeClientHostApi.setup(mockWebChromeClientHostApi); - // TestJavaScriptChannelHostApi.setup(mockJavaScriptChannelHostApi); - // TestDownloadListenerHostApi.setup(mockDownloadListenerHostApi); - // + setUp(() { + // mockWebViewHostApi = MockTestWebViewHostApi(); + // mockWebSettingsHostApi = MockTestWebSettingsHostApi(); + // mockWebViewClientHostApi = MockTestWebViewClientHostApi(); + // mockWebChromeClientHostApi = MockTestWebChromeClientHostApi(); + // mockJavaScriptChannelHostApi = MockTestJavaScriptChannelHostApi(); + // mockDownloadListenerHostApi = MockTestDownloadListenerHostApi(); + // + // TestWebViewHostApi.setup(mockWebViewHostApi); + // TestWebSettingsHostApi.setup(mockWebSettingsHostApi); + // TestWebViewClientHostApi.setup(mockWebViewClientHostApi); + // TestWebChromeClientHostApi.setup(mockWebChromeClientHostApi); + // TestJavaScriptChannelHostApi.setup(mockJavaScriptChannelHostApi); + // TestDownloadListenerHostApi.setup(mockDownloadListenerHostApi); + // + + mockWebView = MockWebView(); + mockWebSettings = MockWebSettings(); + when(mockWebView.settings).thenReturn(mockWebSettings); mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); + mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); - // - // final InstanceManager instanceManager = InstanceManager(); - // android_webview.WebView.api = WebViewHostApiImpl( - // instanceManager: instanceManager, - // ); - // android_webview.WebSettings.api = WebSettingsHostApiImpl( - // instanceManager: instanceManager, - // ); - // android_webview.JavaScriptChannel.api = JavaScriptChannelHostApiImpl( - // instanceManager: instanceManager, - // ); - // android_webview.WebViewClient.api = WebViewClientHostApiImpl( - // instanceManager: instanceManager, - // ); - // android_webview.DownloadListener.api = DownloadListenerHostApiImpl( - // instanceManager: instanceManager, - // ); - // android_webview.WebChromeClient.api = WebChromeClientHostApiImpl( - // instanceManager: instanceManager, - // ); - // }); + + // + // final InstanceManager instanceManager = InstanceManager(); + // android_webview.WebView.api = WebViewHostApiImpl( + // instanceManager: instanceManager, + // ); + // android_webview.WebSettings.api = WebSettingsHostApiImpl( + // instanceManager: instanceManager, + // ); + // android_webview.JavaScriptChannel.api = JavaScriptChannelHostApiImpl( + // instanceManager: instanceManager, + // ); + // android_webview.WebViewClient.api = WebViewClientHostApiImpl( + // instanceManager: instanceManager, + // ); + // android_webview.DownloadListener.api = DownloadListenerHostApiImpl( + // instanceManager: instanceManager, + // ); + // android_webview.WebChromeClient.api = WebChromeClientHostApiImpl( + // instanceManager: instanceManager, + // ); + }); // Builds a AndroidWebViewWidget with default parameters. Future buildWidget( WidgetTester tester, { CreationParams? creationParams, - WebViewPlatformCallbacksHandler? webViewPlatformCallbacksHandler, - JavascriptChannelRegistry? javascriptChannelRegistry, - bool? useHybridComposition, - Widget Function(WebViewAndroidPlatformController platformController)? - onBuildWidget, }) async { - final Completer controllerCompleter = - Completer(); + webViewClient = WebViewAndroidWebViewClient( + callbacksHandler: mockCallbacksHandler, + loadUrl: mockWebView.loadUrl, + ); + downloadListener = WebViewAndroidDownloadListener( + callbacksHandler: mockCallbacksHandler, + loadUrl: mockWebView.loadUrl, + ); + webChromeClient = WebViewAndroidWebChromeClient( + callbacksHandler: mockCallbacksHandler, + ); + + final WebViewAndroidPlatformController controller = + WebViewAndroidPlatformController( + webView: mockWebView, + webViewClient: webViewClient, + downloadListener: downloadListener, + webChromeClient: webChromeClient, + creationParams: creationParams ?? CreationParams(), + callbacksHandler: mockCallbacksHandler, + javascriptChannelRegistry: mockJavascriptChannelRegistry, + ); + + await tester.pumpWidget(AndroidWebViewWidget( + controller: controller, + onBuildWidget: () => Container(), + )); // await tester.pumpWidget( // AndroidWebViewWidget( @@ -112,38 +137,34 @@ void main() { // }, // creationParams: creationParams ?? CreationParams(), // webViewPlatformCallbacksHandler: - // webViewPlatformCallbacksHandler ?? mockCallbacksHandler, + // callbacksHandler ?? mockCallbacksHandler, // javascriptChannelRegistry: // javascriptChannelRegistry ?? mockJavascriptChannelRegistry, // useHybridComposition: useHybridComposition ?? false, // ), // ); - return controllerCompleter.future; + return controller; } testWidgets('Create Widget', (WidgetTester tester) async { await buildWidget(tester); - // verify(mockWebSettingsHostApi.setDomStorageEnabled(1, true)); - // verify(mockWebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically( - // 1, - // true, - // )); - // verify(mockWebSettingsHostApi.setSupportMultipleWindows(1, true)); - // verify(mockWebSettingsHostApi.setLoadWithOverviewMode(1, true)); - // verify(mockWebSettingsHostApi.setUseWideViewPort(1, true)); - // verify(mockWebSettingsHostApi.setDisplayZoomControls(1, false)); - // verify(mockWebSettingsHostApi.setBuiltInZoomControls(1, true)); - // - // verifyInOrder([ - // mockWebViewHostApi.create(0, false), - // mockWebViewHostApi.setWebViewClient(0, any), - // mockWebViewHostApi.setDownloadListener(0, any), - // mockWebViewHostApi.setWebChromeClient(0, any), - // ]); + verify(mockWebSettings.setDomStorageEnabled(true)); + verify(mockWebSettings.setJavaScriptCanOpenWindowsAutomatically(true)); + verify(mockWebSettings.setSupportMultipleWindows(true)); + verify(mockWebSettings.setLoadWithOverviewMode(true)); + verify(mockWebSettings.setUseWideViewPort(true)); + verify(mockWebSettings.setDisplayZoomControls(false)); + verify(mockWebSettings.setBuiltInZoomControls(true)); + + verifyInOrder([ + mockWebView.setWebViewClient(webViewClient), + mockWebView.setDownloadListener(downloadListener), + mockWebView.setWebChromeClient(webChromeClient), + ]); }); - // + // testWidgets( // 'Create Widget with Hybrid Composition', // (WidgetTester tester) async { @@ -152,287 +173,315 @@ void main() { // }, // ); // - // group('CreationParams', () { - // testWidgets('initialUrl', (WidgetTester tester) async { - // await buildWidget( - // tester, - // creationParams: CreationParams(initialUrl: 'https://www.google.com'), - // ); - // verify(mockWebViewHostApi.loadUrl( - // 0, - // 'https://www.google.com', - // {}, - // )); - // }); - // - // testWidgets('userAgent', (WidgetTester tester) async { - // await buildWidget( - // tester, - // creationParams: CreationParams(userAgent: 'MyUserAgent'), - // ); - // - // verify(mockWebSettingsHostApi.setUserAgentString(1, 'MyUserAgent')); - // }); - // - // testWidgets('autoMediaPlaybackPolicy true', (WidgetTester tester) async { - // await buildWidget( - // tester, - // creationParams: CreationParams( - // autoMediaPlaybackPolicy: - // AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - // ), - // ); - // - // verify( - // mockWebSettingsHostApi.setMediaPlaybackRequiresUserGesture(any, true), - // ); - // }); - // - // testWidgets('autoMediaPlaybackPolicy false', (WidgetTester tester) async { - // await buildWidget( - // tester, - // creationParams: CreationParams( - // autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - // ), - // ); - // - // verify(mockWebSettingsHostApi.setMediaPlaybackRequiresUserGesture( - // any, - // false, - // )); - // }); - // - // testWidgets('javascriptChannelNames', (WidgetTester tester) async { - // await buildWidget( - // tester, - // creationParams: CreationParams( - // javascriptChannelNames: {'a', 'b'}, - // ), - // ); - // - // verify(mockJavaScriptChannelHostApi.create(any, 'a')); - // verify(mockJavaScriptChannelHostApi.create(any, 'b')); - // verify(mockWebViewHostApi.addJavaScriptChannel(0, any)).called(2); - // }); - // - // group('WebSettings', () { - // testWidgets('javascriptMode', (WidgetTester tester) async { - // await buildWidget( - // tester, - // creationParams: CreationParams( - // webSettings: WebSettings( - // userAgent: WebSetting.absent(), - // javascriptMode: JavascriptMode.unrestricted, - // ), - // ), - // ); - // - // verify(mockWebSettingsHostApi.setJavaScriptEnabled(any, true)); - // }); - // - // testWidgets('hasNavigationDelegate', (WidgetTester tester) async { - // await buildWidget( - // tester, - // creationParams: CreationParams( - // webSettings: WebSettings( - // userAgent: WebSetting.absent(), - // hasNavigationDelegate: true, - // ), - // ), - // ); - // - // verify(mockWebViewClientHostApi.create(any, true)); - // }); - // - // testWidgets('debuggingEnabled', (WidgetTester tester) async { - // await buildWidget( - // tester, - // creationParams: CreationParams( - // webSettings: WebSettings( - // userAgent: WebSetting.absent(), - // debuggingEnabled: true, - // ), - // ), - // ); - // - // verify(mockWebViewHostApi.setWebContentsDebuggingEnabled(true)); - // }); - // - // testWidgets('userAgent', (WidgetTester tester) async { - // await buildWidget( - // tester, - // creationParams: CreationParams( - // webSettings: WebSettings( - // userAgent: WebSetting.of('myUserAgent'), - // ), - // ), - // ); - // - // verify(mockWebSettingsHostApi.setUserAgentString(any, 'myUserAgent')); - // }); - // - // testWidgets('zoomEnabled', (WidgetTester tester) async { - // await buildWidget( - // tester, - // creationParams: CreationParams( - // webSettings: WebSettings( - // userAgent: WebSetting.absent(), - // zoomEnabled: false, - // ), - // ), - // ); - // - // verify(mockWebSettingsHostApi.setSupportZoom(any, false)); - // }); - // }); - // }); - // - // group('$AndroidWebViewPlatformController', () { - // testWidgets('loadUrl', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // await controller.loadUrl( - // 'https://www.google.com', - // {'a': 'header'}, - // ); - // - // verify(mockWebViewHostApi.loadUrl( - // any, - // 'https://www.google.com', - // {'a': 'header'}, - // )); - // }); - // - // testWidgets('currentUrl', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // when(mockWebViewHostApi.getUrl(any)) - // .thenReturn('https://www.google.com'); - // expect(controller.currentUrl(), completion('https://www.google.com')); - // }); - // - // testWidgets('canGoBack', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // when(mockWebViewHostApi.canGoBack(any)).thenReturn(false); - // expect(controller.canGoBack(), completion(false)); - // }); - // - // testWidgets('canGoForward', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // when(mockWebViewHostApi.canGoForward(any)).thenReturn(true); - // expect(controller.canGoForward(), completion(true)); - // }); - // - // testWidgets('goBack', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // await controller.goBack(); - // verify(mockWebViewHostApi.goBack(any)); - // }); - // - // testWidgets('goForward', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // await controller.goForward(); - // verify(mockWebViewHostApi.goForward(any)); - // }); - // - // testWidgets('reload', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // await controller.reload(); - // verify(mockWebViewHostApi.reload(any)); - // }); - // - // testWidgets('clearCache', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // await controller.clearCache(); - // verify(mockWebViewHostApi.clearCache(any, true)); - // }); - // - // testWidgets('evaluateJavascript', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // when(mockWebViewHostApi.evaluateJavascript(any, 'runJavaScript')) - // .thenAnswer( - // (_) => Future.value('returnString'), - // ); - // expect( - // controller.evaluateJavascript('runJavaScript'), - // completion('returnString'), - // ); - // }); - // - // testWidgets('addJavascriptChannels', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // await controller.addJavascriptChannels({'c', 'd'}); - // verify(mockJavaScriptChannelHostApi.create(any, 'c')); - // verify(mockJavaScriptChannelHostApi.create(any, 'd')); - // verify(mockWebViewHostApi.addJavaScriptChannel(0, any)).called(2); - // }); - // - // testWidgets('removeJavascriptChannels', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // await controller.addJavascriptChannels({'c', 'd'}); - // await controller.removeJavascriptChannels({'c', 'd'}); - // verify(mockWebViewHostApi.removeJavaScriptChannel(0, any)).called(2); - // }); - // - // testWidgets('getTitle', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // when(mockWebViewHostApi.getTitle(any)).thenReturn('Web Title'); - // expect(controller.getTitle(), completion('Web Title')); - // }); - // - // testWidgets('scrollTo', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // await controller.scrollTo(1, 2); - // verify(mockWebViewHostApi.scrollTo(any, 1, 2)); - // }); - // - // testWidgets('scrollBy', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // await controller.scrollBy(3, 4); - // verify(mockWebViewHostApi.scrollBy(any, 3, 4)); - // }); - // - // testWidgets('getScrollX', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // when(mockWebViewHostApi.getScrollX(any)).thenReturn(23); - // expect(controller.getScrollX(), completion(23)); - // }); - // - // testWidgets('getScrollY', (WidgetTester tester) async { - // final AndroidWebViewPlatformController controller = - // await buildWidget(tester); - // - // when(mockWebViewHostApi.getScrollY(any)).thenReturn(25); - // expect(controller.getScrollY(), completion(25)); - // }); - // }); + group('CreationParams', () { + testWidgets('initialUrl', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams(initialUrl: 'https://www.google.com'), + ); + verify(mockWebView.loadUrl( + 'https://www.google.com', + {}, + )); + }); + + testWidgets('userAgent', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams(userAgent: 'MyUserAgent'), + ); + + verify(mockWebSettings.setUserAgentString('MyUserAgent')); + }); + + testWidgets('autoMediaPlaybackPolicy true', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + autoMediaPlaybackPolicy: + AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, + ), + ); + + verify(mockWebSettings.setMediaPlaybackRequiresUserGesture(any)); + }); + + testWidgets('autoMediaPlaybackPolicy false', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ); + + verify(mockWebSettings.setMediaPlaybackRequiresUserGesture(false)); + }); + + testWidgets('javascriptChannelNames', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + javascriptChannelNames: {'a', 'b'}, + ), + ); + + final List javaScriptChannels = + verify(mockWebView.addJavaScriptChannel(captureAny)).captured; + expect(javaScriptChannels[0].channelName, 'a'); + expect(javaScriptChannels[1].channelName, 'b'); + }); + + group('WebSettings', () { + testWidgets('javascriptMode', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: WebSetting.absent(), + javascriptMode: JavascriptMode.unrestricted, + ), + ), + ); + + verify(mockWebSettings.setJavaScriptEnabled(true)); + }); + + // testWidgets('hasNavigationDelegate', (WidgetTester tester) async { + // await buildWidget( + // tester, + // creationParams: CreationParams( + // webSettings: WebSettings( + // userAgent: WebSetting.absent(), + // hasNavigationDelegate: true, + // ), + // ), + // ); + // + // verify(mockWebViewClientHostApi.create(any, true)); + // }); + + // testWidgets('debuggingEnabled', (WidgetTester tester) async { + // await buildWidget( + // tester, + // creationParams: CreationParams( + // webSettings: WebSettings( + // userAgent: WebSetting.absent(), + // debuggingEnabled: true, + // ), + // ), + // ); + // + // verify(mockWebSettings.setWebContentsDebuggingEnabled(true)); + // }); + + testWidgets('userAgent', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: WebSetting.of('myUserAgent'), + ), + ), + ); + + verify(mockWebSettings.setUserAgentString('myUserAgent')); + }); + + testWidgets('zoomEnabled', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: WebSetting.absent(), + zoomEnabled: false, + ), + ), + ); + + verify(mockWebSettings.setSupportZoom(false)); + }); + }); + }); + + group('$WebViewAndroidPlatformController', () { + testWidgets('loadUrl', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + await controller.loadUrl( + 'https://www.google.com', + {'a': 'header'}, + ); + + verify(mockWebView.loadUrl( + 'https://www.google.com', + {'a': 'header'}, + )); + }); + + testWidgets('currentUrl', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + when(mockWebView.getUrl()) + .thenAnswer((_) => Future.value('https://www.google.com')); + expect(controller.currentUrl(), completion('https://www.google.com')); + }); + + testWidgets('canGoBack', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + when(mockWebView.canGoBack()).thenAnswer( + (_) => Future.value(false), + ); + expect(controller.canGoBack(), completion(false)); + }); + + testWidgets('canGoForward', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + when(mockWebView.canGoForward()).thenAnswer( + (_) => Future.value(true), + ); + expect(controller.canGoForward(), completion(true)); + }); + + testWidgets('goBack', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + await controller.goBack(); + verify(mockWebView.goBack()); + }); + + testWidgets('goForward', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + await controller.goForward(); + verify(mockWebView.goForward()); + }); + + testWidgets('reload', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + await controller.reload(); + verify(mockWebView.reload()); + }); + + testWidgets('clearCache', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + await controller.clearCache(); + verify(mockWebView.clearCache(true)); + }); + + testWidgets('evaluateJavascript', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + when(mockWebView.evaluateJavascript('runJavaScript')).thenAnswer( + (_) => Future.value('returnString'), + ); + expect( + controller.evaluateJavascript('runJavaScript'), + completion('returnString'), + ); + }); + + testWidgets('runJavascriptReturningResult', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + when(mockWebView.evaluateJavascript('runJavaScript')).thenAnswer( + (_) => Future.value('returnString'), + ); + expect( + controller.runJavascriptReturningResult('runJavaScript'), + completion('returnString'), + ); + }); + + testWidgets('runJavascript', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + when(mockWebView.evaluateJavascript('runJavaScript')).thenAnswer( + (_) => Future.value('returnString'), + ); + expect( + controller.runJavascript('runJavaScript'), + completes, + ); + }); + + testWidgets('addJavascriptChannels', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + await controller.addJavascriptChannels({'c', 'd'}); + final List javaScriptChannels = + verify(mockWebView.addJavaScriptChannel(captureAny)).captured; + expect(javaScriptChannels[0].channelName, 'c'); + expect(javaScriptChannels[1].channelName, 'd'); + }); + + testWidgets('removeJavascriptChannels', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + await controller.addJavascriptChannels({'c', 'd'}); + await controller.removeJavascriptChannels({'c', 'd'}); + final List javaScriptChannels = + verify(mockWebView.removeJavaScriptChannel(captureAny)).captured; + expect(javaScriptChannels[0].channelName, 'c'); + expect(javaScriptChannels[1].channelName, 'd'); + }); + + testWidgets('getTitle', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + when(mockWebView.getTitle()) + .thenAnswer((_) => Future.value('Web Title')); + expect(controller.getTitle(), completion('Web Title')); + }); + + testWidgets('scrollTo', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + await controller.scrollTo(1, 2); + verify(mockWebView.scrollTo(1, 2)); + }); + + testWidgets('scrollBy', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + await controller.scrollBy(3, 4); + verify(mockWebView.scrollBy(3, 4)); + }); + + testWidgets('getScrollX', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + when(mockWebView.getScrollX()).thenAnswer((_) => Future.value(23)); + expect(controller.getScrollX(), completion(23)); + }); + + testWidgets('getScrollY', (WidgetTester tester) async { + final WebViewAndroidPlatformController controller = + await buildWidget(tester); + + when(mockWebView.getScrollY()).thenAnswer((_) => Future.value(25)); + expect(controller.getScrollY(), completion(25)); + }); + }); }); } From 2ac9967d32124aa54552af7487f09a953d6435ac Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Thu, 11 Nov 2021 13:10:15 -0800 Subject: [PATCH 05/33] rearrangement --- .../lib/webview_widget.dart | 157 ++++++++++-------- .../test/webview_widget_test.dart | 156 +++++++++-------- 2 files changed, 164 insertions(+), 149 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart index f26180ccdc49..1d5fbcc1249e 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart @@ -42,13 +42,13 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { /// Construct a [WebViewAndroidPlatformController]. WebViewAndroidPlatformController({ required this.webView, - WebViewAndroidWebViewClient? webViewClient, WebViewAndroidDownloadListener? downloadListener, WebViewAndroidWebChromeClient? webChromeClient, required this.creationParams, required this.callbacksHandler, required this.javascriptChannelRegistry, - }) : super(callbacksHandler) { + }) : assert(creationParams.webSettings?.hasNavigationDelegate != null), + super(callbacksHandler) { webView.settings.setDomStorageEnabled(true); webView.settings.setJavaScriptCanOpenWindowsAutomatically(true); webView.settings.setSupportMultipleWindows(true); @@ -57,28 +57,19 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { webView.settings.setDisplayZoomControls(false); webView.settings.setBuiltInZoomControls(true); - _webViewClient = webViewClient ?? - WebViewAndroidWebViewClient( - callbacksHandler: callbacksHandler, - loadUrl: loadUrl, - hasNavigationDelegate: - creationParams.webSettings?.hasNavigationDelegate ?? false, - ); + this.downloadListener = + downloadListener ?? WebViewAndroidDownloadListener(loadUrl: loadUrl); + this.webChromeClient = webChromeClient ?? WebViewAndroidWebChromeClient(); - this.downloadListener = downloadListener ?? - WebViewAndroidDownloadListener( - callbacksHandler: callbacksHandler, - loadUrl: loadUrl, - ); - - this.webChromeClient = webChromeClient ?? - WebViewAndroidWebChromeClient(callbacksHandler: callbacksHandler); + _setCreationParams(creationParams); - webView.setWebViewClient(this.webViewClient); webView.setDownloadListener(this.downloadListener); webView.setWebChromeClient(this.webChromeClient); - _setCreationParams(creationParams); + final String? initialUrl = creationParams.initialUrl; + if (initialUrl != null) { + loadUrl(initialUrl, {}); + } } /// Initial parameters used to setup the WebView. @@ -238,16 +229,13 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { } addJavascriptChannels(creationParams.javascriptChannelNames); - - final String? initialUrl = creationParams.initialUrl; - if (initialUrl != null) { - loadUrl(initialUrl, {}); - } } Future _trySetHasProgressTracking(bool? hasProgressTracking) { - if (hasProgressTracking != null) { - webChromeClient.hasProgressTracking = hasProgressTracking; + if (hasProgressTracking == true) { + webChromeClient._onProgress = callbacksHandler.onProgress; + } else if (hasProgressTracking == false) { + webChromeClient._onProgress = null; } return Future.sync(() => null); @@ -256,17 +244,24 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { Future _trySetHasNavigationDelegate(bool? hasNavigationDelegate) { if (hasNavigationDelegate == null) return Future.sync(() => null); - downloadListener.hasNavigationDelegate = hasNavigationDelegate; - if (_webViewClient.hasNavigationDelegate != hasNavigationDelegate) { - _webViewClient = WebViewAndroidWebViewClient( - callbacksHandler: callbacksHandler, + downloadListener._onNavigationRequest = + callbacksHandler.onNavigationRequest; + if (hasNavigationDelegate) { + _webViewClient = WebViewAndroidWebViewClient.handlesNavigation( + onPageStartedCallback: callbacksHandler.onPageStarted, + onPageFinishedCallback: callbacksHandler.onPageFinished, + onWebResourceErrorCallback: callbacksHandler.onWebResourceError, loadUrl: loadUrl, - hasNavigationDelegate: hasNavigationDelegate, + onNavigationRequestCallback: callbacksHandler.onNavigationRequest, + ); + } else { + _webViewClient = WebViewAndroidWebViewClient( + onPageStartedCallback: callbacksHandler.onPageStarted, + onPageFinishedCallback: callbacksHandler.onPageFinished, + onWebResourceErrorCallback: callbacksHandler.onWebResourceError, ); - return webView.setWebViewClient(_webViewClient); } - - return Future.sync(() => null); + return webView.setWebViewClient(_webViewClient); } Future _trySetJavaScriptMode(JavascriptMode? mode) async { @@ -316,14 +311,13 @@ class WebViewAndroidJavaScriptChannel } class WebViewAndroidDownloadListener extends android_webview.DownloadListener { - WebViewAndroidDownloadListener({ - required this.callbacksHandler, - required this.loadUrl, - }); + WebViewAndroidDownloadListener({required this.loadUrl}); - final WebViewPlatformCallbacksHandler callbacksHandler; final Future Function(String url, Map? headers) loadUrl; - bool hasNavigationDelegate = false; + FutureOr Function({ + required String url, + required bool isForMainFrame, + })? _onNavigationRequest; @override void onDownloadStart( @@ -333,9 +327,9 @@ class WebViewAndroidDownloadListener extends android_webview.DownloadListener { String mimetype, int contentLength, ) { - if (!hasNavigationDelegate) return; + if (_onNavigationRequest == null) return; - final FutureOr returnValue = callbacksHandler.onNavigationRequest( + final FutureOr returnValue = _onNavigationRequest!( url: url, isForMainFrame: true, ); @@ -354,14 +348,41 @@ class WebViewAndroidDownloadListener extends android_webview.DownloadListener { class WebViewAndroidWebViewClient extends android_webview.WebViewClient { WebViewAndroidWebViewClient({ - required this.callbacksHandler, - required this.loadUrl, - this.hasNavigationDelegate = false, - }) : super(shouldOverrideUrlLoading: hasNavigationDelegate); - - final WebViewPlatformCallbacksHandler callbacksHandler; - final Future Function(String url, Map? headers) loadUrl; - final bool hasNavigationDelegate; + required this.onPageStartedCallback, + required this.onPageFinishedCallback, + required this.onWebResourceErrorCallback, + }) : loadUrl = null, + onNavigationRequestCallback = null, + super(shouldOverrideUrlLoading: false); + + WebViewAndroidWebViewClient.handlesNavigation({ + required this.onPageStartedCallback, + required this.onPageFinishedCallback, + required this.onWebResourceErrorCallback, + required FutureOr Function({ + required String url, + required bool isForMainFrame, + }) + onNavigationRequestCallback, + required Future Function(String url, Map? headers) + loadUrl, + }) : onNavigationRequestCallback = onNavigationRequestCallback, + loadUrl = loadUrl, + super(shouldOverrideUrlLoading: true); + + final void Function(String url) onPageStartedCallback; + + final void Function(String url) onPageFinishedCallback; + + void Function(WebResourceError error) onWebResourceErrorCallback; + + final FutureOr Function({ + required String url, + required bool isForMainFrame, + })? onNavigationRequestCallback; + + final Future Function(String url, Map? headers)? + loadUrl; static WebResourceErrorType _errorCodeToErrorType(int errorCode) { switch (errorCode) { @@ -404,14 +425,17 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { ); } + bool get handlesNavigation => + loadUrl != null && onNavigationRequestCallback != null; + @override void onPageStarted(android_webview.WebView webView, String url) { - callbacksHandler.onPageStarted(url); + onPageStartedCallback(url); } @override void onPageFinished(android_webview.WebView webView, String url) { - callbacksHandler.onPageFinished(url); + onPageFinishedCallback(url); } @override @@ -421,7 +445,7 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { String description, String failingUrl, ) { - callbacksHandler.onWebResourceError(WebResourceError( + onWebResourceErrorCallback(WebResourceError( errorCode: errorCode, description: description, failingUrl: failingUrl, @@ -436,7 +460,7 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { android_webview.WebResourceError error, ) { if (request.isForMainFrame) { - callbacksHandler.onWebResourceError(WebResourceError( + onWebResourceErrorCallback(WebResourceError( errorCode: error.errorCode, description: error.description, failingUrl: request.url, @@ -447,19 +471,19 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { @override void urlLoading(android_webview.WebView webView, String url) { - if (!hasNavigationDelegate) return; + if (!handlesNavigation) return; - final FutureOr returnValue = callbacksHandler.onNavigationRequest( + final FutureOr returnValue = onNavigationRequestCallback!( url: url, isForMainFrame: true, ); if (returnValue is bool && returnValue) { - loadUrl(url, {}); + loadUrl!(url, {}); } else { (returnValue as Future).then((bool shouldLoadUrl) { if (shouldLoadUrl) { - loadUrl(url, {}); + loadUrl!(url, {}); } }); } @@ -470,19 +494,19 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { android_webview.WebView webView, android_webview.WebResourceRequest request, ) { - if (!hasNavigationDelegate) return; + if (!handlesNavigation) return; - final FutureOr returnValue = callbacksHandler.onNavigationRequest( + final FutureOr returnValue = onNavigationRequestCallback!( url: request.url, isForMainFrame: request.isForMainFrame, ); if (returnValue is bool && returnValue) { - loadUrl(request.url, {}); + loadUrl!(request.url, {}); } else { (returnValue as Future).then((bool shouldLoadUrl) { if (shouldLoadUrl) { - loadUrl(request.url, {}); + loadUrl!(request.url, {}); } }); } @@ -490,13 +514,12 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { } class WebViewAndroidWebChromeClient extends android_webview.WebChromeClient { - WebViewAndroidWebChromeClient({required this.callbacksHandler}); - - final WebViewPlatformCallbacksHandler callbacksHandler; - bool hasProgressTracking = false; + void Function(int progress)? _onProgress; @override void onProgressChanged(android_webview.WebView webView, int progress) { - if (hasProgressTracking) callbacksHandler.onProgress(progress); + if (_onProgress != null) { + _onProgress!(progress); + } } } diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart index dd203af2a637..32ed80547c34 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart @@ -13,7 +13,6 @@ import 'package:webview_flutter_android/src/android_webview.dart' import 'package:webview_flutter_android/webview_widget.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'android_webview.pigeon.dart'; import 'webview_widget_test.mocks.dart'; @GenerateMocks([ @@ -30,14 +29,6 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$AndroidWebViewWidget', () { - // late MockTestWebViewHostApi mockWebViewHostApi; - // late MockTestWebSettingsHostApi mockWebSettingsHostApi; - // late MockTestWebViewClientHostApi mockWebViewClientHostApi; - // late MockTestWebChromeClientHostApi mockWebChromeClientHostApi; - // late MockTestJavaScriptChannelHostApi mockJavaScriptChannelHostApi; - // late MockTestDownloadListenerHostApi mockDownloadListenerHostApi; - // - late MockWebView mockWebView; late MockWebSettings mockWebSettings; @@ -46,108 +37,57 @@ void main() { late WebViewAndroidDownloadListener downloadListener; late WebViewAndroidWebChromeClient webChromeClient; - //late WebViewAndroidJavaScriptChannel javaScriptChannel; late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; - // - setUp(() { - // mockWebViewHostApi = MockTestWebViewHostApi(); - // mockWebSettingsHostApi = MockTestWebSettingsHostApi(); - // mockWebViewClientHostApi = MockTestWebViewClientHostApi(); - // mockWebChromeClientHostApi = MockTestWebChromeClientHostApi(); - // mockJavaScriptChannelHostApi = MockTestJavaScriptChannelHostApi(); - // mockDownloadListenerHostApi = MockTestDownloadListenerHostApi(); - // - // TestWebViewHostApi.setup(mockWebViewHostApi); - // TestWebSettingsHostApi.setup(mockWebSettingsHostApi); - // TestWebViewClientHostApi.setup(mockWebViewClientHostApi); - // TestWebChromeClientHostApi.setup(mockWebChromeClientHostApi); - // TestJavaScriptChannelHostApi.setup(mockJavaScriptChannelHostApi); - // TestDownloadListenerHostApi.setup(mockDownloadListenerHostApi); - // + late WebViewAndroidPlatformController controller; + + setUp(() { mockWebView = MockWebView(); mockWebSettings = MockWebSettings(); when(mockWebView.settings).thenReturn(mockWebSettings); mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); - mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); - - // - // final InstanceManager instanceManager = InstanceManager(); - // android_webview.WebView.api = WebViewHostApiImpl( - // instanceManager: instanceManager, - // ); - // android_webview.WebSettings.api = WebSettingsHostApiImpl( - // instanceManager: instanceManager, - // ); - // android_webview.JavaScriptChannel.api = JavaScriptChannelHostApiImpl( - // instanceManager: instanceManager, - // ); - // android_webview.WebViewClient.api = WebViewClientHostApiImpl( - // instanceManager: instanceManager, - // ); - // android_webview.DownloadListener.api = DownloadListenerHostApiImpl( - // instanceManager: instanceManager, - // ); - // android_webview.WebChromeClient.api = WebChromeClientHostApiImpl( - // instanceManager: instanceManager, - // ); }); // Builds a AndroidWebViewWidget with default parameters. Future buildWidget( WidgetTester tester, { CreationParams? creationParams, + bool hasNavigationDelegate = false, + bool onProgress = false, }) async { - webViewClient = WebViewAndroidWebViewClient( - callbacksHandler: mockCallbacksHandler, - loadUrl: mockWebView.loadUrl, - ); downloadListener = WebViewAndroidDownloadListener( - callbacksHandler: mockCallbacksHandler, loadUrl: mockWebView.loadUrl, ); - webChromeClient = WebViewAndroidWebChromeClient( - callbacksHandler: mockCallbacksHandler, - ); - final WebViewAndroidPlatformController controller = - WebViewAndroidPlatformController( + webChromeClient = WebViewAndroidWebChromeClient(); + + controller = WebViewAndroidPlatformController( webView: mockWebView, - webViewClient: webViewClient, downloadListener: downloadListener, webChromeClient: webChromeClient, - creationParams: creationParams ?? CreationParams(), + creationParams: creationParams ?? + CreationParams( + webSettings: WebSettings( + userAgent: WebSetting.absent(), + hasNavigationDelegate: false, + )), callbacksHandler: mockCallbacksHandler, javascriptChannelRegistry: mockJavascriptChannelRegistry, ); + webViewClient = controller.webViewClient; + await tester.pumpWidget(AndroidWebViewWidget( controller: controller, onBuildWidget: () => Container(), )); - // await tester.pumpWidget( - // AndroidWebViewWidget( - // onBuildWidget: onBuildWidget ?? - // (WebViewAndroidPlatformController controller) { - // controllerCompleter.complete(controller); - // return Container(); - // }, - // creationParams: creationParams ?? CreationParams(), - // webViewPlatformCallbacksHandler: - // callbacksHandler ?? mockCallbacksHandler, - // javascriptChannelRegistry: - // javascriptChannelRegistry ?? mockJavascriptChannelRegistry, - // useHybridComposition: useHybridComposition ?? false, - // ), - // ); - return controller; } - testWidgets('Create Widget', (WidgetTester tester) async { + testWidgets('$AndroidWebViewWidget', (WidgetTester tester) async { await buildWidget(tester); verify(mockWebSettings.setDomStorageEnabled(true)); @@ -173,11 +113,17 @@ void main() { // }, // ); // - group('CreationParams', () { + group('$CreationParams', () { testWidgets('initialUrl', (WidgetTester tester) async { await buildWidget( tester, - creationParams: CreationParams(initialUrl: 'https://www.google.com'), + creationParams: CreationParams( + initialUrl: 'https://www.google.com', + webSettings: WebSettings( + userAgent: WebSetting.absent(), + hasNavigationDelegate: false, + ), + ), ); verify(mockWebView.loadUrl( 'https://www.google.com', @@ -188,7 +134,13 @@ void main() { testWidgets('userAgent', (WidgetTester tester) async { await buildWidget( tester, - creationParams: CreationParams(userAgent: 'MyUserAgent'), + creationParams: CreationParams( + userAgent: 'MyUserAgent', + webSettings: WebSettings( + userAgent: WebSetting.absent(), + hasNavigationDelegate: false, + ), + ), ); verify(mockWebSettings.setUserAgentString('MyUserAgent')); @@ -200,6 +152,10 @@ void main() { creationParams: CreationParams( autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, + webSettings: WebSettings( + userAgent: WebSetting.absent(), + hasNavigationDelegate: false, + ), ), ); @@ -211,6 +167,10 @@ void main() { tester, creationParams: CreationParams( autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + webSettings: WebSettings( + userAgent: WebSetting.absent(), + hasNavigationDelegate: false, + ), ), ); @@ -222,6 +182,10 @@ void main() { tester, creationParams: CreationParams( javascriptChannelNames: {'a', 'b'}, + webSettings: WebSettings( + userAgent: WebSetting.absent(), + hasNavigationDelegate: false, + ), ), ); @@ -231,7 +195,7 @@ void main() { expect(javaScriptChannels[1].channelName, 'b'); }); - group('WebSettings', () { + group('$WebSettings', () { testWidgets('javascriptMode', (WidgetTester tester) async { await buildWidget( tester, @@ -239,6 +203,7 @@ void main() { webSettings: WebSettings( userAgent: WebSetting.absent(), javascriptMode: JavascriptMode.unrestricted, + hasNavigationDelegate: false, ), ), ); @@ -280,6 +245,7 @@ void main() { creationParams: CreationParams( webSettings: WebSettings( userAgent: WebSetting.of('myUserAgent'), + hasNavigationDelegate: false, ), ), ); @@ -294,6 +260,7 @@ void main() { webSettings: WebSettings( userAgent: WebSetting.absent(), zoomEnabled: false, + hasNavigationDelegate: false, ), ), ); @@ -303,7 +270,7 @@ void main() { }); }); - group('$WebViewAndroidPlatformController', () { + group('$WebViewPlatformController', () { testWidgets('loadUrl', (WidgetTester tester) async { final WebViewAndroidPlatformController controller = await buildWidget(tester); @@ -483,5 +450,30 @@ void main() { expect(controller.getScrollY(), completion(25)); }); }); + + group('$WebViewPlatformCallbacksHandler', () { + testWidgets('onPageStarted', (WidgetTester tester) async { + await buildWidget( + tester, + ); + //webViewClient.onPageStarted(webView, url) + }); + }); }); } + +// FutureOr onNavigationRequest( +// {required String url, required bool isForMainFrame}); +// +// /// Invoked by [WebViewPlatformController] when a page has started loading. +// void onPageStarted(String url); +// +// /// Invoked by [WebViewPlatformController] when a page has finished loading. +// void onPageFinished(String url); +// +// /// Invoked by [WebViewPlatformController] when a page is loading. +// /// /// Only works when [WebSettings.hasProgressTracking] is set to `true`. +// void onProgress(int progress); +// +// /// Report web resource loading error to the host application. +// void onWebResourceError(WebResourceError error); From 928b5e794d1dfa257ed94376ec7bd6381ba568a2 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Thu, 11 Nov 2021 13:53:29 -0800 Subject: [PATCH 06/33] renames --- .../lib/webview_android.dart | 4 +- ...idget.dart => webview_android_widget.dart} | 51 ++-- .../lib/webview_surface_android.dart | 4 +- ....dart => webview_android_widget_test.dart} | 248 ++++++++++++------ ...=> webview_android_widget_test.mocks.dart} | 68 ++--- 5 files changed, 222 insertions(+), 153 deletions(-) rename packages/webview_flutter/webview_flutter_android/lib/{webview_widget.dart => webview_android_widget.dart} (90%) rename packages/webview_flutter/webview_flutter_android/test/{webview_widget_test.dart => webview_android_widget_test.dart} (67%) rename packages/webview_flutter/webview_flutter_android/test/{webview_widget_test.mocks.dart => webview_android_widget_test.mocks.dart} (88%) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart index 62166e3e13ad..9f5a044e373a 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart @@ -10,7 +10,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'webview_widget.dart'; +import 'webview_android_widget.dart'; import 'src/instance_manager.dart'; import 'src/android_webview.dart'; @@ -37,7 +37,7 @@ class AndroidWebView implements WebViewPlatform { javascriptChannelRegistry: javascriptChannelRegistry, ); - return AndroidWebViewWidget( + return WebViewAndroidWidget( controller: controller, onBuildWidget: () { return GestureDetector( diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart similarity index 90% rename from packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart rename to packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index 1d5fbcc1249e..ecf1459fed94 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -11,24 +11,25 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_inte import 'src/android_webview.dart' as android_webview; /// Creates a [Widget] with a [android_webview.WebView]. -class AndroidWebViewWidget extends StatefulWidget { - /// Constructs a [AndroidWebViewWidget]. - AndroidWebViewWidget({required this.controller, required this.onBuildWidget}); +class WebViewAndroidWidget extends StatefulWidget { + /// Constructs a [WebViewAndroidWidget]. + WebViewAndroidWidget({required this.controller, required this.onBuildWidget}); + /// Controls the Android WebView platform API. final WebViewAndroidPlatformController controller; /// Callback to build a widget once [android_webview.WebView] has been initialized. final Widget Function() onBuildWidget; @override - State createState() => _AndroidWebViewWidgetState(); + State createState() => _WebViewAndroidWidgetState(); } -class _AndroidWebViewWidgetState extends State { +class _WebViewAndroidWidgetState extends State { @override void dispose() { super.dispose(); - widget.controller.release(); + widget.controller._release(); } @override @@ -42,8 +43,6 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { /// Construct a [WebViewAndroidPlatformController]. WebViewAndroidPlatformController({ required this.webView, - WebViewAndroidDownloadListener? downloadListener, - WebViewAndroidWebChromeClient? webChromeClient, required this.creationParams, required this.callbacksHandler, required this.javascriptChannelRegistry, @@ -57,10 +56,10 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { webView.settings.setDisplayZoomControls(false); webView.settings.setBuiltInZoomControls(true); - this.downloadListener = - downloadListener ?? WebViewAndroidDownloadListener(loadUrl: loadUrl); - this.webChromeClient = webChromeClient ?? WebViewAndroidWebChromeClient(); + this.downloadListener =WebViewAndroidDownloadListener(loadUrl: loadUrl); + this.webChromeClient = WebViewAndroidWebChromeClient(); + // Also sets WebViewClient depending on WebSettings.hasNavigationDelegate. _setCreationParams(creationParams); webView.setDownloadListener(this.downloadListener); @@ -72,6 +71,11 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { } } + final Map _javaScriptChannels = + {}; + + late WebViewAndroidWebViewClient _webViewClient; + /// Initial parameters used to setup the WebView. final CreationParams creationParams; @@ -84,15 +88,13 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. final JavascriptChannelRegistry javascriptChannelRegistry; - final Map _javaScriptChannels = - {}; - + /// Receives callbacks when content can not be handled by the rendering engine for [android_webview.WebView], and should be downloaded instead. late final WebViewAndroidDownloadListener downloadListener; + /// Handles JavaScript dialogs, favicons, titles, new windows, and the progress for [android_webview.WebView]. late final WebViewAndroidWebChromeClient webChromeClient; - late WebViewAndroidWebViewClient _webViewClient; - + /// Receive various notifications and requests for [android_webview.WebView]. WebViewAndroidWebViewClient get webViewClient => _webViewClient; @override @@ -205,7 +207,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { @override Future getScrollY() => webView.getScrollY(); - Future release() => webView.release(); + Future _release() => webView.release(); void _setCreationParams(CreationParams creationParams) { final WebSettings? webSettings = creationParams.webSettings; @@ -296,12 +298,15 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { } } +/// Exposes a channel to receive calls from javaScript. class WebViewAndroidJavaScriptChannel extends android_webview.JavaScriptChannel { + /// Creates a [WebViewAndroidJavaScriptChannel]. WebViewAndroidJavaScriptChannel( String channelName, this.javascriptChannelRegistry) : super(channelName); + /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. final JavascriptChannelRegistry javascriptChannelRegistry; @override @@ -310,15 +315,19 @@ class WebViewAndroidJavaScriptChannel } } +/// Receives callbacks when content can not be handled by the rendering engine for [WebViewAndroidPlatformController], and should be downloaded instead. class WebViewAndroidDownloadListener extends android_webview.DownloadListener { + /// Creates a [WebViewAndroidDownloadListener]. WebViewAndroidDownloadListener({required this.loadUrl}); - final Future Function(String url, Map? headers) loadUrl; FutureOr Function({ - required String url, - required bool isForMainFrame, + required String url, + required bool isForMainFrame, })? _onNavigationRequest; + /// Callback to load a URL when a navigation request is approved. + final Future Function(String url, Map? headers) loadUrl; + @override void onDownloadStart( String url, @@ -425,6 +434,7 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { ); } + /// Whether this [android_webview.WebViewClient] handles navigation requests. bool get handlesNavigation => loadUrl != null && onNavigationRequestCallback != null; @@ -513,6 +523,7 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { } } +/// Handles JavaScript dialogs, favicons, titles, and the progress for [WebViewAndroidPlatformController]. class WebViewAndroidWebChromeClient extends android_webview.WebChromeClient { void Function(int progress)? _onProgress; diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart index 0f3255594011..e3eb8a2cb752 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart @@ -11,7 +11,7 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_inte import 'src/android_webview.dart'; import 'src/instance_manager.dart'; -import 'webview_widget.dart'; +import 'webview_android_widget.dart'; import 'webview_android.dart'; /// Android [WebViewPlatform] that uses [AndroidViewSurface] to build the [WebView] widget. @@ -41,7 +41,7 @@ class SurfaceAndroidWebView extends AndroidWebView { javascriptChannelRegistry: javascriptChannelRegistry, ); - return AndroidWebViewWidget( + return WebViewAndroidWidget( controller: controller, onBuildWidget: () { return PlatformViewLink( diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart similarity index 67% rename from packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart rename to packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart index 32ed80547c34..d876290708af 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart @@ -10,10 +10,10 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_android/src/android_webview.dart' as android_webview; -import 'package:webview_flutter_android/webview_widget.dart'; +import 'package:webview_flutter_android/webview_android_widget.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'webview_widget_test.mocks.dart'; +import 'webview_android_widget_test.mocks.dart'; @GenerateMocks([ android_webview.WebSettings, @@ -28,7 +28,7 @@ import 'webview_widget_test.mocks.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('$AndroidWebViewWidget', () { + group('$WebViewAndroidWidget', () { late MockWebView mockWebView; late MockWebSettings mockWebSettings; @@ -51,43 +51,38 @@ void main() { }); // Builds a AndroidWebViewWidget with default parameters. - Future buildWidget( + Future buildWidget( WidgetTester tester, { CreationParams? creationParams, bool hasNavigationDelegate = false, - bool onProgress = false, + bool hasProgressTracking = false, }) async { - downloadListener = WebViewAndroidDownloadListener( - loadUrl: mockWebView.loadUrl, - ); - webChromeClient = WebViewAndroidWebChromeClient(); controller = WebViewAndroidPlatformController( webView: mockWebView, - downloadListener: downloadListener, - webChromeClient: webChromeClient, creationParams: creationParams ?? CreationParams( webSettings: WebSettings( userAgent: WebSetting.absent(), - hasNavigationDelegate: false, + hasNavigationDelegate: hasNavigationDelegate, + hasProgressTracking: hasProgressTracking, )), callbacksHandler: mockCallbacksHandler, javascriptChannelRegistry: mockJavascriptChannelRegistry, ); webViewClient = controller.webViewClient; + downloadListener = controller.downloadListener; + webChromeClient = controller.webChromeClient; - await tester.pumpWidget(AndroidWebViewWidget( + await tester.pumpWidget(WebViewAndroidWidget( controller: controller, onBuildWidget: () => Container(), )); - - return controller; } - testWidgets('$AndroidWebViewWidget', (WidgetTester tester) async { + testWidgets('$WebViewAndroidWidget', (WidgetTester tester) async { await buildWidget(tester); verify(mockWebSettings.setDomStorageEnabled(true)); @@ -112,7 +107,7 @@ void main() { // verify(mockWebViewHostApi.create(0, true)); // }, // ); - // + group('$CreationParams', () { testWidgets('initialUrl', (WidgetTester tester) async { await buildWidget( @@ -211,19 +206,20 @@ void main() { verify(mockWebSettings.setJavaScriptEnabled(true)); }); - // testWidgets('hasNavigationDelegate', (WidgetTester tester) async { - // await buildWidget( - // tester, - // creationParams: CreationParams( - // webSettings: WebSettings( - // userAgent: WebSetting.absent(), - // hasNavigationDelegate: true, - // ), - // ), - // ); - // - // verify(mockWebViewClientHostApi.create(any, true)); - // }); + testWidgets('hasNavigationDelegate', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: WebSetting.absent(), + hasNavigationDelegate: true, + ), + ), + ); + + expect(controller.webViewClient.handlesNavigation, isTrue); + expect(controller.webViewClient.shouldOverrideUrlLoading, isTrue); + }); // testWidgets('debuggingEnabled', (WidgetTester tester) async { // await buildWidget( @@ -272,8 +268,7 @@ void main() { group('$WebViewPlatformController', () { testWidgets('loadUrl', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); await controller.loadUrl( 'https://www.google.com', @@ -287,8 +282,7 @@ void main() { }); testWidgets('currentUrl', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); when(mockWebView.getUrl()) .thenAnswer((_) => Future.value('https://www.google.com')); @@ -296,8 +290,7 @@ void main() { }); testWidgets('canGoBack', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); when(mockWebView.canGoBack()).thenAnswer( (_) => Future.value(false), @@ -306,8 +299,7 @@ void main() { }); testWidgets('canGoForward', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); when(mockWebView.canGoForward()).thenAnswer( (_) => Future.value(true), @@ -316,40 +308,35 @@ void main() { }); testWidgets('goBack', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); await controller.goBack(); verify(mockWebView.goBack()); }); testWidgets('goForward', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); await controller.goForward(); verify(mockWebView.goForward()); }); testWidgets('reload', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); await controller.reload(); verify(mockWebView.reload()); }); testWidgets('clearCache', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); await controller.clearCache(); verify(mockWebView.clearCache(true)); }); testWidgets('evaluateJavascript', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); when(mockWebView.evaluateJavascript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), @@ -361,8 +348,7 @@ void main() { }); testWidgets('runJavascriptReturningResult', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); when(mockWebView.evaluateJavascript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), @@ -374,8 +360,7 @@ void main() { }); testWidgets('runJavascript', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); when(mockWebView.evaluateJavascript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), @@ -387,8 +372,7 @@ void main() { }); testWidgets('addJavascriptChannels', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); await controller.addJavascriptChannels({'c', 'd'}); final List javaScriptChannels = @@ -398,8 +382,7 @@ void main() { }); testWidgets('removeJavascriptChannels', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); await controller.addJavascriptChannels({'c', 'd'}); await controller.removeJavascriptChannels({'c', 'd'}); @@ -410,8 +393,7 @@ void main() { }); testWidgets('getTitle', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); when(mockWebView.getTitle()) .thenAnswer((_) => Future.value('Web Title')); @@ -419,32 +401,28 @@ void main() { }); testWidgets('scrollTo', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); await controller.scrollTo(1, 2); verify(mockWebView.scrollTo(1, 2)); }); testWidgets('scrollBy', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); await controller.scrollBy(3, 4); verify(mockWebView.scrollBy(3, 4)); }); testWidgets('getScrollX', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); when(mockWebView.getScrollX()).thenAnswer((_) => Future.value(23)); expect(controller.getScrollX(), completion(23)); }); testWidgets('getScrollY', (WidgetTester tester) async { - final WebViewAndroidPlatformController controller = - await buildWidget(tester); + await buildWidget(tester); when(mockWebView.getScrollY()).thenAnswer((_) => Future.value(25)); expect(controller.getScrollY(), completion(25)); @@ -453,27 +431,127 @@ void main() { group('$WebViewPlatformCallbacksHandler', () { testWidgets('onPageStarted', (WidgetTester tester) async { - await buildWidget( - tester, + await buildWidget(tester); + webViewClient.onPageStarted(mockWebView, 'https://google.com'); + verify(mockCallbacksHandler.onPageStarted('https://google.com')); + }); + + testWidgets('onPageFinished', (WidgetTester tester) async { + await buildWidget(tester); + webViewClient.onPageFinished(mockWebView, 'https://google.com'); + verify(mockCallbacksHandler.onPageFinished('https://google.com')); + }); + + testWidgets('onWebResourceError from onReceivedError', + (WidgetTester tester) async { + await buildWidget(tester); + webViewClient.onReceivedError( + mockWebView, + android_webview.WebViewClient.errorAuthentication, + 'description', + 'https://google.com', ); - //webViewClient.onPageStarted(webView, url) + + final WebResourceError error = + verify(mockCallbacksHandler.onWebResourceError(captureAny)) + .captured + .single; + expect(error.description, 'description'); + expect(error.errorCode, -4); + expect(error.failingUrl, 'https://google.com'); + expect(error.domain, isNull); + expect(error.errorType, WebResourceErrorType.authentication); + }); + + testWidgets('onWebResourceError from onReceivedRequestError', + (WidgetTester tester) async { + await buildWidget(tester); + webViewClient.onReceivedRequestError( + mockWebView, + android_webview.WebResourceRequest( + url: 'https://google.com', + isForMainFrame: true, + isRedirect: false, + hasGesture: false, + method: 'POST', + requestHeaders: {}, + ), + android_webview.WebResourceError( + errorCode: android_webview.WebViewClient.errorUnsafeResource, + description: 'description', + ), + ); + + final WebResourceError error = + verify(mockCallbacksHandler.onWebResourceError(captureAny)) + .captured + .single; + expect(error.description, 'description'); + expect(error.errorCode, -16); + expect(error.failingUrl, 'https://google.com'); + expect(error.domain, isNull); + expect(error.errorType, WebResourceErrorType.unsafeResource); + }); + + testWidgets('onNavigationRequest from urlLoading', + (WidgetTester tester) async { + await buildWidget(tester, hasNavigationDelegate: true); + when(mockCallbacksHandler.onNavigationRequest( + isForMainFrame: argThat(isTrue, named: 'isForMainFrame'), + url: 'https://google.com', + )).thenReturn(true); + + webViewClient.urlLoading(mockWebView, 'https://google.com'); + verify(mockCallbacksHandler.onNavigationRequest( + url: 'https://google.com', + isForMainFrame: true, + )); + verify(mockWebView.loadUrl('https://google.com', {})); + }); + + testWidgets('onNavigationRequest from requestLoading', + (WidgetTester tester) async { + await buildWidget(tester, hasNavigationDelegate: true); + when(mockCallbacksHandler.onNavigationRequest( + isForMainFrame: argThat(isTrue, named: 'isForMainFrame'), + url: 'https://google.com', + )).thenReturn(true); + + webViewClient.requestLoading( + mockWebView, + android_webview.WebResourceRequest( + url: 'https://google.com', + isForMainFrame: true, + isRedirect: false, + hasGesture: false, + method: 'POST', + requestHeaders: {}, + ), + ); + verify(mockCallbacksHandler.onNavigationRequest( + url: 'https://google.com', + isForMainFrame: true, + )); + verify(mockWebView.loadUrl('https://google.com', {})); + }); + + group('$JavascriptChannelRegistry', () { + testWidgets('onJavascriptChannelMessage', (WidgetTester tester) async { + await buildWidget(tester); + + await controller.addJavascriptChannels({'hello'}); + + final WebViewAndroidJavaScriptChannel javaScriptChannel = + verify(mockWebView.addJavaScriptChannel(captureAny)) + .captured + .single; + javaScriptChannel.postMessage('goodbye'); + verify(mockJavascriptChannelRegistry.onJavascriptChannelMessage( + 'hello', + 'goodbye', + )); + }); }); }); }); } - -// FutureOr onNavigationRequest( -// {required String url, required bool isForMainFrame}); -// -// /// Invoked by [WebViewPlatformController] when a page has started loading. -// void onPageStarted(String url); -// -// /// Invoked by [WebViewPlatformController] when a page has finished loading. -// void onPageFinished(String url); -// -// /// Invoked by [WebViewPlatformController] when a page is loading. -// /// /// Only works when [WebSettings.hasProgressTracking] is set to `true`. -// void onProgress(int progress); -// -// /// Report web resource loading error to the host application. -// void onWebResourceError(WebResourceError error); diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart similarity index 88% rename from packages/webview_flutter/webview_flutter_android/test/webview_widget_test.mocks.dart rename to packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart index a01da11aa375..1b5dd47fc8f3 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart @@ -3,14 +3,14 @@ // found in the LICENSE file. // Mocks generated by Mockito 5.0.16 from annotations -// in webview_flutter_android/test/webview_widget_test.dart. +// in webview_flutter_android/test/webview_android_widget_test.dart. // Do not manually edit this file. import 'dart:async' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; -import 'package:webview_flutter_android/webview_widget.dart' as _i5; +import 'package:webview_flutter_android/webview_android_widget.dart' as _i5; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' as _i3; @@ -25,10 +25,7 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_inte class _FakeWebSettings_0 extends _i1.Fake implements _i2.WebSettings {} -class _FakeWebViewPlatformCallbacksHandler_1 extends _i1.Fake - implements _i3.WebViewPlatformCallbacksHandler {} - -class _FakeJavascriptChannelRegistry_2 extends _i1.Fake +class _FakeJavascriptChannelRegistry_1 extends _i1.Fake implements _i3.JavascriptChannelRegistry {} /// A class which mocks [WebSettings]. @@ -226,11 +223,6 @@ class MockWebViewAndroidDownloadListener extends _i1.Mock _i1.throwOnMissingStub(this); } - @override - _i3.WebViewPlatformCallbacksHandler get callbacksHandler => - (super.noSuchMethod(Invocation.getter(#callbacksHandler), - returnValue: _FakeWebViewPlatformCallbacksHandler_1()) - as _i3.WebViewPlatformCallbacksHandler); @override _i4.Future Function(String, Map?) get loadUrl => (super.noSuchMethod(Invocation.getter(#loadUrl), @@ -238,14 +230,6 @@ class MockWebViewAndroidDownloadListener extends _i1.Mock Future.value()) as _i4.Future Function( String, Map?)); @override - bool get hasNavigationDelegate => - (super.noSuchMethod(Invocation.getter(#hasNavigationDelegate), - returnValue: false) as bool); - @override - set hasNavigationDelegate(bool? _hasNavigationDelegate) => super.noSuchMethod( - Invocation.setter(#hasNavigationDelegate, _hasNavigationDelegate), - returnValueForMissingStub: null); - @override void onDownloadStart(String? url, String? userAgent, String? contentDisposition, String? mimetype, int? contentLength) => super.noSuchMethod( @@ -268,7 +252,7 @@ class MockWebViewAndroidJavaScriptChannel extends _i1.Mock @override _i3.JavascriptChannelRegistry get javascriptChannelRegistry => (super.noSuchMethod(Invocation.getter(#javascriptChannelRegistry), - returnValue: _FakeJavascriptChannelRegistry_2()) + returnValue: _FakeJavascriptChannelRegistry_1()) as _i3.JavascriptChannelRegistry); @override String get channelName => @@ -291,19 +275,6 @@ class MockWebViewAndroidWebChromeClient extends _i1.Mock _i1.throwOnMissingStub(this); } - @override - _i3.WebViewPlatformCallbacksHandler get callbacksHandler => - (super.noSuchMethod(Invocation.getter(#callbacksHandler), - returnValue: _FakeWebViewPlatformCallbacksHandler_1()) - as _i3.WebViewPlatformCallbacksHandler); - @override - bool get hasProgressTracking => - (super.noSuchMethod(Invocation.getter(#hasProgressTracking), - returnValue: false) as bool); - @override - set hasProgressTracking(bool? _hasProgressTracking) => super.noSuchMethod( - Invocation.setter(#hasProgressTracking, _hasProgressTracking), - returnValueForMissingStub: null); @override void onProgressChanged(_i2.WebView? webView, int? progress) => super .noSuchMethod(Invocation.method(#onProgressChanged, [webView, progress]), @@ -322,19 +293,28 @@ class MockWebViewAndroidWebViewClient extends _i1.Mock } @override - _i3.WebViewPlatformCallbacksHandler get callbacksHandler => - (super.noSuchMethod(Invocation.getter(#callbacksHandler), - returnValue: _FakeWebViewPlatformCallbacksHandler_1()) - as _i3.WebViewPlatformCallbacksHandler); + void Function(String) get onPageStartedCallback => + (super.noSuchMethod(Invocation.getter(#onPageStartedCallback), + returnValue: (String url) {}) as void Function(String)); @override - _i4.Future Function(String, Map?) get loadUrl => - (super.noSuchMethod(Invocation.getter(#loadUrl), - returnValue: (String url, Map? headers) => - Future.value()) as _i4.Future Function( - String, Map?)); + void Function(String) get onPageFinishedCallback => + (super.noSuchMethod(Invocation.getter(#onPageFinishedCallback), + returnValue: (String url) {}) as void Function(String)); + @override + void Function(_i3.WebResourceError) get onWebResourceErrorCallback => + (super.noSuchMethod(Invocation.getter(#onWebResourceErrorCallback), + returnValue: (_i3.WebResourceError error) {}) + as void Function(_i3.WebResourceError)); + @override + set onWebResourceErrorCallback( + void Function(_i3.WebResourceError)? _onWebResourceErrorCallback) => + super.noSuchMethod( + Invocation.setter( + #onWebResourceErrorCallback, _onWebResourceErrorCallback), + returnValueForMissingStub: null); @override - bool get hasNavigationDelegate => - (super.noSuchMethod(Invocation.getter(#hasNavigationDelegate), + bool get handlesNavigation => + (super.noSuchMethod(Invocation.getter(#handlesNavigation), returnValue: false) as bool); @override bool get shouldOverrideUrlLoading => From 556b23d919b98e18eb5dd7fd009f44a0c47cabb2 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Thu, 11 Nov 2021 14:04:55 -0800 Subject: [PATCH 07/33] documentation --- .../lib/webview_android_widget.dart | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index ecf1459fed94..b01c6c62ddbe 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -56,7 +56,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { webView.settings.setDisplayZoomControls(false); webView.settings.setBuiltInZoomControls(true); - this.downloadListener =WebViewAndroidDownloadListener(loadUrl: loadUrl); + this.downloadListener = WebViewAndroidDownloadListener(loadUrl: loadUrl); this.webChromeClient = WebViewAndroidWebChromeClient(); // Also sets WebViewClient depending on WebSettings.hasNavigationDelegate. @@ -72,7 +72,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { } final Map _javaScriptChannels = - {}; + {}; late WebViewAndroidWebViewClient _webViewClient; @@ -316,13 +316,18 @@ class WebViewAndroidJavaScriptChannel } /// Receives callbacks when content can not be handled by the rendering engine for [WebViewAndroidPlatformController], and should be downloaded instead. +/// +/// When handling navigation requests, this calls [onNavigationRequestCallback] +/// when a [android_webview.WebView] attempts to navigate to a new page. If +/// this callback return true, this calls [loadUrl]. class WebViewAndroidDownloadListener extends android_webview.DownloadListener { /// Creates a [WebViewAndroidDownloadListener]. WebViewAndroidDownloadListener({required this.loadUrl}); + // Changed by WebViewAndroidPlatformController. FutureOr Function({ - required String url, - required bool isForMainFrame, + required String url, + required bool isForMainFrame, })? _onNavigationRequest; /// Callback to load a URL when a navigation request is approved. @@ -355,7 +360,13 @@ class WebViewAndroidDownloadListener extends android_webview.DownloadListener { } } +/// Receives various navigation requests and errors for [WebViewAndroidPlatformController]. +/// +/// When handling navigation requests, this calls [onNavigationRequestCallback] +/// when a [android_webview.WebView] attempts to navigate to a new page. If +/// this callback return true, this calls [loadUrl]. class WebViewAndroidWebViewClient extends android_webview.WebViewClient { + /// Creates a [WebViewAndroidWebViewClient] that doesn't handle navigation requests. WebViewAndroidWebViewClient({ required this.onPageStartedCallback, required this.onPageFinishedCallback, @@ -364,6 +375,7 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { onNavigationRequestCallback = null, super(shouldOverrideUrlLoading: false); + /// Creates a [WebViewAndroidWebViewClient] that handles navigation requests. WebViewAndroidWebViewClient.handlesNavigation({ required this.onPageStartedCallback, required this.onPageFinishedCallback, @@ -379,17 +391,22 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { loadUrl = loadUrl, super(shouldOverrideUrlLoading: true); + /// Callback when [android_webview.WebViewClient] receives a callback from [android_webview.WebViewClient].onPageStarted. final void Function(String url) onPageStartedCallback; + /// Callback when [android_webview.WebViewClient] receives a callback from [android_webview.WebViewClient].onPageFinished. final void Function(String url) onPageFinishedCallback; + /// Callback when [android_webview.WebViewClient] receives an error callback. void Function(WebResourceError error) onWebResourceErrorCallback; + /// Checks whether a navigation request should be approved or disaproved. final FutureOr Function({ required String url, required bool isForMainFrame, })? onNavigationRequestCallback; + /// Callback when a navigation request is approved. final Future Function(String url, Map? headers)? loadUrl; @@ -525,6 +542,7 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { /// Handles JavaScript dialogs, favicons, titles, and the progress for [WebViewAndroidPlatformController]. class WebViewAndroidWebChromeClient extends android_webview.WebChromeClient { + // Changed by WebViewAndroidPlatformController. void Function(int progress)? _onProgress; @override From 66a7167cd27ae14c43e341d6d1dcb377cff4d54d Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Thu, 11 Nov 2021 14:33:09 -0800 Subject: [PATCH 08/33] formatting --- .../webviewflutter/FlutterWebViewFactory.java | 2 +- .../webviewflutter/JavaScriptChannel.java | 12 ++--- .../webviewflutter/WebViewFlutterPlugin.java | 26 +++++---- .../lib/webview_android.dart | 2 +- .../lib/webview_android_widget.dart | 53 +++++++++++++++++-- .../lib/webview_surface_android.dart | 2 +- .../test/webview_android_widget_test.dart | 52 ++++++++++-------- .../webview_android_widget_test.mocks.dart | 26 +++++++++ 8 files changed, 132 insertions(+), 43 deletions(-) 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 b5b3c2616485..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 @@ -25,4 +25,4 @@ public PlatformView create(Context context, int id, Object args) { } return view; } -} \ No newline at end of file +} 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 a7ae135e93ac..57320acaf548 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 @@ -48,13 +48,11 @@ public void postMessage(final String message) { if (flutterApi != null) { final Runnable postMessageRunnable = () -> flutterApi.postMessage(this, message, reply -> {}); - - 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/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java index 7c5f779ee7af..5c82b019b76c 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 @@ -7,7 +7,6 @@ import android.app.Activity; 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; @@ -26,8 +25,7 @@ * *

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

Call {@link #registerWith} 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, ActivityAware { private FlutterPluginBinding pluginBinding; @@ -56,18 +54,26 @@ public WebViewFlutterPlugin() {} */ @SuppressWarnings({"unused", "deprecation"}) public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - setUp(registrar.messenger(), registrar.platformViewRegistry(), registrar.activity(), registrar.view()); + setUp( + registrar.messenger(), + registrar.platformViewRegistry(), + registrar.activity(), + registrar.view()); new FlutterCookieManager(registrar.messenger()); } private static void setUp( - BinaryMessenger binaryMessenger, PlatformViewRegistry viewRegistry, Activity activity, View containerView) { + BinaryMessenger binaryMessenger, + PlatformViewRegistry viewRegistry, + Activity activity, + View containerView) { InstanceManager instanceManager = new InstanceManager(); viewRegistry.registerViewFactory( "plugins.flutter.io/webview", new FlutterWebViewFactory(instanceManager)); WebViewHostApi.setup( binaryMessenger, - new WebViewHostApiImpl(instanceManager, new WebViewHostApiImpl.WebViewProxy(), activity, containerView)); + new WebViewHostApiImpl( + instanceManager, new WebViewHostApiImpl.WebViewProxy(), activity, containerView)); WebViewClientHostApi.setup( binaryMessenger, new WebViewClientHostApiImpl( @@ -121,7 +127,8 @@ public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBi setUp( pluginBinding.getBinaryMessenger(), pluginBinding.getPlatformViewRegistry(), - activityPluginBinding.getActivity(), null); + activityPluginBinding.getActivity(), + null); } @Override @@ -133,9 +140,10 @@ public void onReattachedToActivityForConfigChanges( setUp( pluginBinding.getBinaryMessenger(), pluginBinding.getPlatformViewRegistry(), - activityPluginBinding.getActivity(), null); + activityPluginBinding.getActivity(), + null); } @Override public void onDetachedFromActivity() {} -} \ No newline at end of file +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart index 9f5a044e373a..9eaf804c9b3f 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart @@ -31,7 +31,7 @@ class AndroidWebView implements WebViewPlatform { }) { final WebViewAndroidPlatformController controller = WebViewAndroidPlatformController( - webView: WebView(useHybridComposition: false), + useHybridComposition: false, creationParams: creationParams, callbacksHandler: webViewPlatformCallbacksHandler, javascriptChannelRegistry: javascriptChannelRegistry, diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index b01c6c62ddbe..b2f93d94a675 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -42,12 +42,17 @@ class _WebViewAndroidWidgetState extends State { class WebViewAndroidPlatformController extends WebViewPlatformController { /// Construct a [WebViewAndroidPlatformController]. WebViewAndroidPlatformController({ - required this.webView, + required this.useHybridComposition, required this.creationParams, required this.callbacksHandler, required this.javascriptChannelRegistry, + @visibleForTesting this.webViewProxy = const WebViewProxy(), }) : assert(creationParams.webSettings?.hasNavigationDelegate != null), super(callbacksHandler) { + webView = webViewProxy.createWebView( + useHybridComposition: useHybridComposition, + ); + webView.settings.setDomStorageEnabled(true); webView.settings.setJavaScriptCanOpenWindowsAutomatically(true); webView.settings.setSupportMultipleWindows(true); @@ -79,8 +84,24 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { /// Initial parameters used to setup the WebView. final CreationParams creationParams; + /// Handles constructing [android_webview.WebView]s and calling static methods. + /// + /// This should only be changed for testing purposes. + final WebViewProxy webViewProxy; + + /// Whether the [android_webview.WebView] will be rendered with an [AndroidViewSurface]. + /// + /// This implementation uses hybrid composition to render the + /// [WebViewAndroidWidget]. This comes at the cost of some performance on + /// Android versions below 10. See + /// https://flutter.dev/docs/development/platform-integration/platform-views#performance + /// for more information. + /// + /// Defaults to false. + final bool useHybridComposition; + /// Represents the WebView maintained by platform code. - final android_webview.WebView webView; + late final android_webview.WebView webView; /// Handles callbacks that are made by [android_webview.WebViewClient], [android_webview.DownloadListener], and [android_webview.WebChromeClient]. final WebViewPlatformCallbacksHandler callbacksHandler; @@ -279,7 +300,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { Future _trySetDebuggingEnabled(bool? debuggingEnabled) async { if (debuggingEnabled == null) return Future.sync(() => null); - return android_webview.WebView.setWebContentsDebuggingEnabled( + return webViewProxy.setWebContentsDebuggingEnabled( debuggingEnabled, ); } @@ -552,3 +573,29 @@ class WebViewAndroidWebChromeClient extends android_webview.WebChromeClient { } } } + +/// Handles constructing [android_webview.WebView]s and calling static methods. +/// +/// This should only be used for testing purposes. +@visibleForTesting +class WebViewProxy { + /// Creates a [WebViewProxy]. + const WebViewProxy(); + + /// Constructs a [android_webview.WebView]. + android_webview.WebView createWebView({required bool useHybridComposition}) { + return android_webview.WebView(useHybridComposition: useHybridComposition); + } + + /// Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this application. + /// + /// This flag can be enabled in order to facilitate debugging of web layouts + /// and JavaScript code running inside WebViews. Please refer to + /// [android_webview.WebView] documentation for the debugging guide. The + /// default is false. + /// + /// See [android_webview.WebView].setWebContentsDebuggingEnabled. + Future setWebContentsDebuggingEnabled(bool enabled) { + return android_webview.WebView.setWebContentsDebuggingEnabled(true); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart index e3eb8a2cb752..d16e214cc772 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart @@ -35,7 +35,7 @@ class SurfaceAndroidWebView extends AndroidWebView { }) { final WebViewAndroidPlatformController controller = WebViewAndroidPlatformController( - webView: WebView(useHybridComposition: true), + useHybridComposition: true, creationParams: creationParams, callbacksHandler: webViewPlatformCallbacksHandler, javascriptChannelRegistry: javascriptChannelRegistry, diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart index d876290708af..8a98456d623f 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart @@ -24,6 +24,7 @@ import 'webview_android_widget_test.mocks.dart'; WebViewAndroidWebViewClient, JavascriptChannelRegistry, WebViewPlatformCallbacksHandler, + WebViewProxy, ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -31,6 +32,7 @@ void main() { group('$WebViewAndroidWidget', () { late MockWebView mockWebView; late MockWebSettings mockWebSettings; + late MockWebViewProxy mockWebViewProxy; late MockWebViewPlatformCallbacksHandler mockCallbacksHandler; late WebViewAndroidWebViewClient webViewClient; @@ -46,6 +48,11 @@ void main() { mockWebSettings = MockWebSettings(); when(mockWebView.settings).thenReturn(mockWebSettings); + mockWebViewProxy = MockWebViewProxy(); + when(mockWebViewProxy.createWebView( + useHybridComposition: anyNamed('useHybridComposition'), + )).thenReturn(mockWebView); + mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); }); @@ -56,11 +63,12 @@ void main() { CreationParams? creationParams, bool hasNavigationDelegate = false, bool hasProgressTracking = false, + bool useHybridComposition = false, }) async { webChromeClient = WebViewAndroidWebChromeClient(); controller = WebViewAndroidPlatformController( - webView: mockWebView, + useHybridComposition: useHybridComposition, creationParams: creationParams ?? CreationParams( webSettings: WebSettings( @@ -70,6 +78,7 @@ void main() { )), callbacksHandler: mockCallbacksHandler, javascriptChannelRegistry: mockJavascriptChannelRegistry, + webViewProxy: mockWebViewProxy, ); webViewClient = controller.webViewClient; @@ -100,13 +109,13 @@ void main() { ]); }); - // testWidgets( - // 'Create Widget with Hybrid Composition', - // (WidgetTester tester) async { - // await buildWidget(tester, useHybridComposition: true); - // verify(mockWebViewHostApi.create(0, true)); - // }, - // ); + testWidgets( + 'Create Widget with Hybrid Composition', + (WidgetTester tester) async { + await buildWidget(tester, useHybridComposition: true); + verify(mockWebViewProxy.createWebView(useHybridComposition: true)); + }, + ); group('$CreationParams', () { testWidgets('initialUrl', (WidgetTester tester) async { @@ -221,19 +230,20 @@ void main() { expect(controller.webViewClient.shouldOverrideUrlLoading, isTrue); }); - // testWidgets('debuggingEnabled', (WidgetTester tester) async { - // await buildWidget( - // tester, - // creationParams: CreationParams( - // webSettings: WebSettings( - // userAgent: WebSetting.absent(), - // debuggingEnabled: true, - // ), - // ), - // ); - // - // verify(mockWebSettings.setWebContentsDebuggingEnabled(true)); - // }); + testWidgets('debuggingEnabled', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: WebSetting.absent(), + debuggingEnabled: true, + hasNavigationDelegate: false, + ), + ), + ); + + verify(mockWebViewProxy.setWebContentsDebuggingEnabled(true)); + }); testWidgets('userAgent', (WidgetTester tester) async { await buildWidget( diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart index 1b5dd47fc8f3..c9d3324ecfc3 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart @@ -28,6 +28,8 @@ class _FakeWebSettings_0 extends _i1.Fake implements _i2.WebSettings {} class _FakeJavascriptChannelRegistry_1 extends _i1.Fake implements _i3.JavascriptChannelRegistry {} +class _FakeWebView_2 extends _i1.Fake implements _i2.WebView {} + /// A class which mocks [WebSettings]. /// /// See the documentation for Mockito's code generation for more information. @@ -415,3 +417,27 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock @override String toString() => super.toString(); } + +/// A class which mocks [WebViewProxy]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewProxy extends _i1.Mock implements _i5.WebViewProxy { + MockWebViewProxy() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.WebView createWebView({bool? useHybridComposition}) => + (super.noSuchMethod( + Invocation.method(#createWebView, [], + {#useHybridComposition: useHybridComposition}), + returnValue: _FakeWebView_2()) as _i2.WebView); + @override + _i4.Future setWebContentsDebuggingEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setWebContentsDebuggingEnabled, [enabled]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + String toString() => super.toString(); +} From 7aad502f196801fd5040dc7320363d08bd099068 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Fri, 12 Nov 2021 10:14:06 -0800 Subject: [PATCH 09/33] add initialize meethod --- .../lib/webview_android_widget.dart | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index b2f93d94a675..9dd2b27cee21 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -26,6 +26,12 @@ class WebViewAndroidWidget extends StatefulWidget { } class _WebViewAndroidWidgetState extends State { + @override + void initState() { + super.initState(); + widget.controller._initialize(); + } + @override void dispose() { super.dispose(); @@ -48,33 +54,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { required this.javascriptChannelRegistry, @visibleForTesting this.webViewProxy = const WebViewProxy(), }) : assert(creationParams.webSettings?.hasNavigationDelegate != null), - super(callbacksHandler) { - webView = webViewProxy.createWebView( - useHybridComposition: useHybridComposition, - ); - - webView.settings.setDomStorageEnabled(true); - webView.settings.setJavaScriptCanOpenWindowsAutomatically(true); - webView.settings.setSupportMultipleWindows(true); - webView.settings.setLoadWithOverviewMode(true); - webView.settings.setUseWideViewPort(true); - webView.settings.setDisplayZoomControls(false); - webView.settings.setBuiltInZoomControls(true); - - this.downloadListener = WebViewAndroidDownloadListener(loadUrl: loadUrl); - this.webChromeClient = WebViewAndroidWebChromeClient(); - - // Also sets WebViewClient depending on WebSettings.hasNavigationDelegate. - _setCreationParams(creationParams); - - webView.setDownloadListener(this.downloadListener); - webView.setWebChromeClient(this.webChromeClient); - - final String? initialUrl = creationParams.initialUrl; - if (initialUrl != null) { - loadUrl(initialUrl, {}); - } - } + super(callbacksHandler); final Map _javaScriptChannels = {}; @@ -118,6 +98,32 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { /// Receive various notifications and requests for [android_webview.WebView]. WebViewAndroidWebViewClient get webViewClient => _webViewClient; + void _initialize() { + webView = webViewProxy.createWebView( + useHybridComposition: useHybridComposition, + ); + + webView.settings.setDomStorageEnabled(true); + webView.settings.setJavaScriptCanOpenWindowsAutomatically(true); + webView.settings.setSupportMultipleWindows(true); + webView.settings.setLoadWithOverviewMode(true); + webView.settings.setUseWideViewPort(true); + webView.settings.setDisplayZoomControls(false); + webView.settings.setBuiltInZoomControls(true); + + this.downloadListener = WebViewAndroidDownloadListener(loadUrl: loadUrl); + this.webChromeClient = WebViewAndroidWebChromeClient(); + + _setCreationParams(creationParams); + webView.setDownloadListener(this.downloadListener); + webView.setWebChromeClient(this.webChromeClient); + + final String? initialUrl = creationParams.initialUrl; + if (initialUrl != null) { + loadUrl(initialUrl, {}); + } + } + @override Future loadUrl( String url, From 8724ad517fa506f6c1269df9444cb46d870094ed Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Fri, 12 Nov 2021 10:34:04 -0800 Subject: [PATCH 10/33] fix tests --- .../lib/webview_android.dart | 9 +- .../lib/webview_android_widget.dart | 133 +++++++++++------- .../lib/webview_surface_android.dart | 9 +- .../test/webview_android_widget_test.dart | 68 +++++---- 4 files changed, 116 insertions(+), 103 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart index 9eaf804c9b3f..cc4162275d03 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart @@ -29,17 +29,12 @@ class AndroidWebView implements WebViewPlatform { WebViewPlatformCreatedCallback? onWebViewPlatformCreated, Set>? gestureRecognizers, }) { - final WebViewAndroidPlatformController controller = - WebViewAndroidPlatformController( + return WebViewAndroidWidget( useHybridComposition: false, creationParams: creationParams, callbacksHandler: webViewPlatformCallbacksHandler, javascriptChannelRegistry: javascriptChannelRegistry, - ); - - return WebViewAndroidWidget( - controller: controller, - onBuildWidget: () { + onBuildWidget: (WebViewAndroidPlatformController controller) { return GestureDetector( // We prevent text selection by intercepting the long press event. // This is a temporary stop gap due to issues with text selection on Android: diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index 9dd2b27cee21..dd6cc1c950f8 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -13,34 +13,72 @@ import 'src/android_webview.dart' as android_webview; /// Creates a [Widget] with a [android_webview.WebView]. class WebViewAndroidWidget extends StatefulWidget { /// Constructs a [WebViewAndroidWidget]. - WebViewAndroidWidget({required this.controller, required this.onBuildWidget}); + WebViewAndroidWidget({ + required this.creationParams, + required this.useHybridComposition, + required this.callbacksHandler, + required this.javascriptChannelRegistry, + required this.onBuildWidget, + @visibleForTesting this.webViewProxy = const WebViewProxy(), + }); + + /// Initial parameters used to setup the WebView. + final CreationParams creationParams; + + /// Whether the [android_webview.WebView] will be rendered with an [AndroidViewSurface]. + /// + /// This implementation uses hybrid composition to render the + /// [WebViewAndroidWidget]. This comes at the cost of some performance on + /// Android versions below 10. See + /// https://flutter.dev/docs/development/platform-integration/platform-views#performance + /// for more information. + /// + /// Defaults to false. + final bool useHybridComposition; - /// Controls the Android WebView platform API. - final WebViewAndroidPlatformController controller; + /// Handles callbacks that are made by [android_webview.WebViewClient], [android_webview.DownloadListener], and [android_webview.WebChromeClient]. + final WebViewPlatformCallbacksHandler callbacksHandler; + + /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. + final JavascriptChannelRegistry javascriptChannelRegistry; + + /// Handles constructing [android_webview.WebView]s and calling static methods. + /// + /// This should only be changed for testing purposes. + final WebViewProxy webViewProxy; /// Callback to build a widget once [android_webview.WebView] has been initialized. - final Widget Function() onBuildWidget; + final Widget Function(WebViewAndroidPlatformController controller) + onBuildWidget; @override State createState() => _WebViewAndroidWidgetState(); } class _WebViewAndroidWidgetState extends State { + late final WebViewAndroidPlatformController controller; + @override void initState() { super.initState(); - widget.controller._initialize(); + controller = WebViewAndroidPlatformController( + useHybridComposition: widget.useHybridComposition, + creationParams: widget.creationParams, + callbacksHandler: widget.callbacksHandler, + javascriptChannelRegistry: widget.javascriptChannelRegistry, + webViewProxy: widget.webViewProxy, + ); } @override void dispose() { super.dispose(); - widget.controller._release(); + controller._release(); } @override Widget build(BuildContext context) { - return widget.onBuildWidget(); + return widget.onBuildWidget(controller); } } @@ -48,57 +86,13 @@ class _WebViewAndroidWidgetState extends State { class WebViewAndroidPlatformController extends WebViewPlatformController { /// Construct a [WebViewAndroidPlatformController]. WebViewAndroidPlatformController({ - required this.useHybridComposition, - required this.creationParams, + required bool useHybridComposition, + required CreationParams creationParams, required this.callbacksHandler, required this.javascriptChannelRegistry, @visibleForTesting this.webViewProxy = const WebViewProxy(), }) : assert(creationParams.webSettings?.hasNavigationDelegate != null), - super(callbacksHandler); - - final Map _javaScriptChannels = - {}; - - late WebViewAndroidWebViewClient _webViewClient; - - /// Initial parameters used to setup the WebView. - final CreationParams creationParams; - - /// Handles constructing [android_webview.WebView]s and calling static methods. - /// - /// This should only be changed for testing purposes. - final WebViewProxy webViewProxy; - - /// Whether the [android_webview.WebView] will be rendered with an [AndroidViewSurface]. - /// - /// This implementation uses hybrid composition to render the - /// [WebViewAndroidWidget]. This comes at the cost of some performance on - /// Android versions below 10. See - /// https://flutter.dev/docs/development/platform-integration/platform-views#performance - /// for more information. - /// - /// Defaults to false. - final bool useHybridComposition; - - /// Represents the WebView maintained by platform code. - late final android_webview.WebView webView; - - /// Handles callbacks that are made by [android_webview.WebViewClient], [android_webview.DownloadListener], and [android_webview.WebChromeClient]. - final WebViewPlatformCallbacksHandler callbacksHandler; - - /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. - final JavascriptChannelRegistry javascriptChannelRegistry; - - /// Receives callbacks when content can not be handled by the rendering engine for [android_webview.WebView], and should be downloaded instead. - late final WebViewAndroidDownloadListener downloadListener; - - /// Handles JavaScript dialogs, favicons, titles, new windows, and the progress for [android_webview.WebView]. - late final WebViewAndroidWebChromeClient webChromeClient; - - /// Receive various notifications and requests for [android_webview.WebView]. - WebViewAndroidWebViewClient get webViewClient => _webViewClient; - - void _initialize() { + super(callbacksHandler) { webView = webViewProxy.createWebView( useHybridComposition: useHybridComposition, ); @@ -124,6 +118,37 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { } } + final Map _javaScriptChannels = + {}; + + late WebViewAndroidWebViewClient _webViewClient; + + /// Represents the WebView maintained by platform code. + late final android_webview.WebView webView; + + /// Handles callbacks that are made by [android_webview.WebViewClient], [android_webview.DownloadListener], and [android_webview.WebChromeClient]. + final WebViewPlatformCallbacksHandler callbacksHandler; + + /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. + final JavascriptChannelRegistry javascriptChannelRegistry; + + /// Handles constructing [android_webview.WebView]s and calling static methods. + /// + /// This should only be changed for testing purposes. + final WebViewProxy webViewProxy; + + /// Receives callbacks when content can not be handled by the rendering engine for [android_webview.WebView], and should be downloaded instead. + @visibleForTesting + late final WebViewAndroidDownloadListener downloadListener; + + /// Handles JavaScript dialogs, favicons, titles, new windows, and the progress for [android_webview.WebView]. + @visibleForTesting + late final WebViewAndroidWebChromeClient webChromeClient; + + /// Receive various notifications and requests for [android_webview.WebView]. + @visibleForTesting + WebViewAndroidWebViewClient get webViewClient => _webViewClient; + @override Future loadUrl( String url, diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart index d16e214cc772..3fbe9825bf67 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart @@ -33,17 +33,12 @@ class SurfaceAndroidWebView extends AndroidWebView { Set>? gestureRecognizers, required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, }) { - final WebViewAndroidPlatformController controller = - WebViewAndroidPlatformController( + return WebViewAndroidWidget( useHybridComposition: true, creationParams: creationParams, callbacksHandler: webViewPlatformCallbacksHandler, javascriptChannelRegistry: javascriptChannelRegistry, - ); - - return WebViewAndroidWidget( - controller: controller, - onBuildWidget: () { + onBuildWidget: (WebViewAndroidPlatformController controller) { return PlatformViewLink( viewType: 'plugins.flutter.io/webview', surfaceFactory: ( diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart index 8a98456d623f..1a41976555ad 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart @@ -41,7 +41,7 @@ void main() { late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; - late WebViewAndroidPlatformController controller; + late WebViewAndroidPlatformController testController; setUp(() { mockWebView = MockWebView(); @@ -65,9 +65,7 @@ void main() { bool hasProgressTracking = false, bool useHybridComposition = false, }) async { - webChromeClient = WebViewAndroidWebChromeClient(); - - controller = WebViewAndroidPlatformController( + await tester.pumpWidget(WebViewAndroidWidget( useHybridComposition: useHybridComposition, creationParams: creationParams ?? CreationParams( @@ -79,16 +77,15 @@ void main() { callbacksHandler: mockCallbacksHandler, javascriptChannelRegistry: mockJavascriptChannelRegistry, webViewProxy: mockWebViewProxy, - ); - - webViewClient = controller.webViewClient; - downloadListener = controller.downloadListener; - webChromeClient = controller.webChromeClient; - - await tester.pumpWidget(WebViewAndroidWidget( - controller: controller, - onBuildWidget: () => Container(), + onBuildWidget: (WebViewAndroidPlatformController controller) { + testController = controller; + return Container(); + }, )); + + webViewClient = testController.webViewClient; + downloadListener = testController.downloadListener; + webChromeClient = testController.webChromeClient; } testWidgets('$WebViewAndroidWidget', (WidgetTester tester) async { @@ -226,8 +223,8 @@ void main() { ), ); - expect(controller.webViewClient.handlesNavigation, isTrue); - expect(controller.webViewClient.shouldOverrideUrlLoading, isTrue); + expect(testController.webViewClient.handlesNavigation, isTrue); + expect(testController.webViewClient.shouldOverrideUrlLoading, isTrue); }); testWidgets('debuggingEnabled', (WidgetTester tester) async { @@ -280,7 +277,7 @@ void main() { testWidgets('loadUrl', (WidgetTester tester) async { await buildWidget(tester); - await controller.loadUrl( + await testController.loadUrl( 'https://www.google.com', {'a': 'header'}, ); @@ -296,7 +293,8 @@ void main() { when(mockWebView.getUrl()) .thenAnswer((_) => Future.value('https://www.google.com')); - expect(controller.currentUrl(), completion('https://www.google.com')); + expect( + testController.currentUrl(), completion('https://www.google.com')); }); testWidgets('canGoBack', (WidgetTester tester) async { @@ -305,7 +303,7 @@ void main() { when(mockWebView.canGoBack()).thenAnswer( (_) => Future.value(false), ); - expect(controller.canGoBack(), completion(false)); + expect(testController.canGoBack(), completion(false)); }); testWidgets('canGoForward', (WidgetTester tester) async { @@ -314,34 +312,34 @@ void main() { when(mockWebView.canGoForward()).thenAnswer( (_) => Future.value(true), ); - expect(controller.canGoForward(), completion(true)); + expect(testController.canGoForward(), completion(true)); }); testWidgets('goBack', (WidgetTester tester) async { await buildWidget(tester); - await controller.goBack(); + await testController.goBack(); verify(mockWebView.goBack()); }); testWidgets('goForward', (WidgetTester tester) async { await buildWidget(tester); - await controller.goForward(); + await testController.goForward(); verify(mockWebView.goForward()); }); testWidgets('reload', (WidgetTester tester) async { await buildWidget(tester); - await controller.reload(); + await testController.reload(); verify(mockWebView.reload()); }); testWidgets('clearCache', (WidgetTester tester) async { await buildWidget(tester); - await controller.clearCache(); + await testController.clearCache(); verify(mockWebView.clearCache(true)); }); @@ -352,7 +350,7 @@ void main() { (_) => Future.value('returnString'), ); expect( - controller.evaluateJavascript('runJavaScript'), + testController.evaluateJavascript('runJavaScript'), completion('returnString'), ); }); @@ -364,7 +362,7 @@ void main() { (_) => Future.value('returnString'), ); expect( - controller.runJavascriptReturningResult('runJavaScript'), + testController.runJavascriptReturningResult('runJavaScript'), completion('returnString'), ); }); @@ -376,7 +374,7 @@ void main() { (_) => Future.value('returnString'), ); expect( - controller.runJavascript('runJavaScript'), + testController.runJavascript('runJavaScript'), completes, ); }); @@ -384,7 +382,7 @@ void main() { testWidgets('addJavascriptChannels', (WidgetTester tester) async { await buildWidget(tester); - await controller.addJavascriptChannels({'c', 'd'}); + await testController.addJavascriptChannels({'c', 'd'}); final List javaScriptChannels = verify(mockWebView.addJavaScriptChannel(captureAny)).captured; expect(javaScriptChannels[0].channelName, 'c'); @@ -394,8 +392,8 @@ void main() { testWidgets('removeJavascriptChannels', (WidgetTester tester) async { await buildWidget(tester); - await controller.addJavascriptChannels({'c', 'd'}); - await controller.removeJavascriptChannels({'c', 'd'}); + await testController.addJavascriptChannels({'c', 'd'}); + await testController.removeJavascriptChannels({'c', 'd'}); final List javaScriptChannels = verify(mockWebView.removeJavaScriptChannel(captureAny)).captured; expect(javaScriptChannels[0].channelName, 'c'); @@ -407,20 +405,20 @@ void main() { when(mockWebView.getTitle()) .thenAnswer((_) => Future.value('Web Title')); - expect(controller.getTitle(), completion('Web Title')); + expect(testController.getTitle(), completion('Web Title')); }); testWidgets('scrollTo', (WidgetTester tester) async { await buildWidget(tester); - await controller.scrollTo(1, 2); + await testController.scrollTo(1, 2); verify(mockWebView.scrollTo(1, 2)); }); testWidgets('scrollBy', (WidgetTester tester) async { await buildWidget(tester); - await controller.scrollBy(3, 4); + await testController.scrollBy(3, 4); verify(mockWebView.scrollBy(3, 4)); }); @@ -428,14 +426,14 @@ void main() { await buildWidget(tester); when(mockWebView.getScrollX()).thenAnswer((_) => Future.value(23)); - expect(controller.getScrollX(), completion(23)); + expect(testController.getScrollX(), completion(23)); }); testWidgets('getScrollY', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.getScrollY()).thenAnswer((_) => Future.value(25)); - expect(controller.getScrollY(), completion(25)); + expect(testController.getScrollY(), completion(25)); }); }); @@ -549,7 +547,7 @@ void main() { testWidgets('onJavascriptChannelMessage', (WidgetTester tester) async { await buildWidget(tester); - await controller.addJavascriptChannels({'hello'}); + await testController.addJavascriptChannels({'hello'}); final WebViewAndroidJavaScriptChannel javaScriptChannel = verify(mockWebView.addJavaScriptChannel(captureAny)) From 19c46f7d5bdd76dddf3d50788caab8c7c24948e5 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Fri, 12 Nov 2021 10:53:35 -0800 Subject: [PATCH 11/33] fix potential race condition --- .../webviewflutter/JavaScriptChannel.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) 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 57320acaf548..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 @@ -44,15 +44,17 @@ public JavaScriptChannel( @SuppressWarnings("unused") @JavascriptInterface public void postMessage(final String message) { + final Runnable postMessageRunnable = + () -> { + if (flutterApi != null) { + flutterApi.postMessage(JavaScriptChannel.this, message, reply -> {}); + } + }; - if (flutterApi != null) { - final Runnable postMessageRunnable = () -> flutterApi.postMessage(this, message, reply -> {}); - - if (platformThreadHandler.getLooper() == Looper.myLooper()) { - postMessageRunnable.run(); - } else { - platformThreadHandler.post(postMessageRunnable); - } + if (platformThreadHandler.getLooper() == Looper.myLooper()) { + postMessageRunnable.run(); + } else { + platformThreadHandler.post(postMessageRunnable); } } From ed1b199cced34533b1d7716f15cb9ac013d04a21 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Fri, 12 Nov 2021 10:55:14 -0800 Subject: [PATCH 12/33] version bump --- packages/webview_flutter/webview_flutter_android/CHANGELOG.md | 4 ++++ packages/webview_flutter/webview_flutter_android/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index b241be63248c..28fcac72460c 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 + +* Replace 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/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 2fe03c888d6b..e698341b030e 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.2.1 +version: 2.3.0 environment: sdk: ">=2.14.0 <3.0.0" From 588ef0b70a96db3154321611478b1e598b8d1e3c Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Fri, 12 Nov 2021 14:23:53 -0800 Subject: [PATCH 13/33] comments, improve readability --- .../lib/webview_android_widget.dart | 75 +++++++++---------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index dd6cc1c950f8..41ff51de0ea1 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -105,9 +105,6 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { webView.settings.setDisplayZoomControls(false); webView.settings.setBuiltInZoomControls(true); - this.downloadListener = WebViewAndroidDownloadListener(loadUrl: loadUrl); - this.webChromeClient = WebViewAndroidWebChromeClient(); - _setCreationParams(creationParams); webView.setDownloadListener(this.downloadListener); webView.setWebChromeClient(this.webChromeClient); @@ -137,13 +134,15 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { /// This should only be changed for testing purposes. final WebViewProxy webViewProxy; - /// Receives callbacks when content can not be handled by the rendering engine for [android_webview.WebView], and should be downloaded instead. + /// Receives callbacks when content should be downloaded instead. @visibleForTesting - late final WebViewAndroidDownloadListener downloadListener; + late final WebViewAndroidDownloadListener downloadListener = + WebViewAndroidDownloadListener(loadUrl: loadUrl); /// Handles JavaScript dialogs, favicons, titles, new windows, and the progress for [android_webview.WebView]. @visibleForTesting - late final WebViewAndroidWebChromeClient webChromeClient; + late final WebViewAndroidWebChromeClient webChromeClient = + WebViewAndroidWebChromeClient(); /// Receive various notifications and requests for [android_webview.WebView]. @visibleForTesting @@ -181,12 +180,16 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { @override Future updateSettings(WebSettings settings) { return Future.wait(>[ - _trySetHasProgressTracking(settings.hasProgressTracking), - _trySetHasNavigationDelegate(settings.hasNavigationDelegate), - _trySetJavaScriptMode(settings.javascriptMode), - _trySetDebuggingEnabled(settings.debuggingEnabled), - _trySetUserAgent(settings.userAgent), - _trySetZoomEnabled(settings.zoomEnabled), + _setUserAgent(settings.userAgent), + if (settings.hasProgressTracking != null) + _setHasProgressTracking(settings.hasProgressTracking!), + if (settings.hasProgressTracking != null) + _setHasNavigationDelegate(settings.hasNavigationDelegate!), + if (settings.javascriptMode != null) + _setJavaScriptMode(settings.javascriptMode!), + if (settings.debuggingEnabled != null) + _setDebuggingEnabled(settings.debuggingEnabled!), + if (settings.zoomEnabled != null) _setZoomEnabled(settings.zoomEnabled!), ]); } @@ -285,22 +288,19 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { addJavascriptChannels(creationParams.javascriptChannelNames); } - Future _trySetHasProgressTracking(bool? hasProgressTracking) { - if (hasProgressTracking == true) { + Future _setHasProgressTracking( + bool hasProgressTracking, + ) async { + if (hasProgressTracking) { webChromeClient._onProgress = callbacksHandler.onProgress; - } else if (hasProgressTracking == false) { - webChromeClient._onProgress = null; } - - return Future.sync(() => null); + webChromeClient._onProgress = null; } - Future _trySetHasNavigationDelegate(bool? hasNavigationDelegate) { - if (hasNavigationDelegate == null) return Future.sync(() => null); - - downloadListener._onNavigationRequest = - callbacksHandler.onNavigationRequest; + Future _setHasNavigationDelegate(bool hasNavigationDelegate) { if (hasNavigationDelegate) { + downloadListener._onNavigationRequest = + callbacksHandler.onNavigationRequest; _webViewClient = WebViewAndroidWebViewClient.handlesNavigation( onPageStartedCallback: callbacksHandler.onPageStarted, onPageFinishedCallback: callbacksHandler.onPageFinished, @@ -308,19 +308,18 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { loadUrl: loadUrl, onNavigationRequestCallback: callbacksHandler.onNavigationRequest, ); - } else { - _webViewClient = WebViewAndroidWebViewClient( - onPageStartedCallback: callbacksHandler.onPageStarted, - onPageFinishedCallback: callbacksHandler.onPageFinished, - onWebResourceErrorCallback: callbacksHandler.onWebResourceError, - ); } + + downloadListener._onNavigationRequest = null; + _webViewClient = WebViewAndroidWebViewClient( + onPageStartedCallback: callbacksHandler.onPageStarted, + onPageFinishedCallback: callbacksHandler.onPageFinished, + onWebResourceErrorCallback: callbacksHandler.onWebResourceError, + ); return webView.setWebViewClient(_webViewClient); } - Future _trySetJavaScriptMode(JavascriptMode? mode) async { - if (mode == null) return Future.sync(() => null); - + Future _setJavaScriptMode(JavascriptMode mode) { switch (mode) { case JavascriptMode.disabled: return webView.settings.setJavaScriptEnabled(false); @@ -329,14 +328,11 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { } } - Future _trySetDebuggingEnabled(bool? debuggingEnabled) async { - if (debuggingEnabled == null) return Future.sync(() => null); - return webViewProxy.setWebContentsDebuggingEnabled( - debuggingEnabled, - ); + Future _setDebuggingEnabled(bool debuggingEnabled) { + return webViewProxy.setWebContentsDebuggingEnabled(debuggingEnabled); } - Future _trySetUserAgent(WebSetting userAgent) { + Future _setUserAgent(WebSetting userAgent) { if (userAgent.isPresent && userAgent.value != null) { return webView.settings.setUserAgentString(userAgent.value!); } @@ -344,8 +340,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { return webView.settings.setUserAgentString(''); } - Future _trySetZoomEnabled(bool? zoomEnabled) async { - if (zoomEnabled == null) return Future.sync(() => null); + Future _setZoomEnabled(bool zoomEnabled) { return webView.settings.setSupportZoom(zoomEnabled); } } From c8da74f46a8542d27d812f20e0f2d7214116d387 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Fri, 12 Nov 2021 14:54:19 -0800 Subject: [PATCH 14/33] set the activity for actvity dependent apis --- .../JavaScriptChannelHostApiImpl.java | 12 ++- .../webviewflutter/WebViewFlutterPlugin.java | 87 ++++++++++++------- .../webviewflutter/WebViewHostApiImpl.java | 12 ++- 3 files changed, 78 insertions(+), 33 deletions(-) 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/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java index 5c82b019b76c..0e3a466802ea 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 @@ -31,6 +31,9 @@ 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 * register it. @@ -54,26 +57,39 @@ public WebViewFlutterPlugin() {} */ @SuppressWarnings({"unused", "deprecation"}) public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - setUp( - registrar.messenger(), - registrar.platformViewRegistry(), - registrar.activity(), - registrar.view()); + new WebViewFlutterPlugin() + .setUp( + registrar.messenger(), + registrar.platformViewRegistry(), + registrar.activity(), + registrar.view()); new FlutterCookieManager(registrar.messenger()); } - private static void setUp( + private void setUp( BinaryMessenger binaryMessenger, PlatformViewRegistry viewRegistry, Activity activity, View containerView) { + new FlutterCookieManager(binaryMessenger); + InstanceManager instanceManager = new InstanceManager(); + viewRegistry.registerViewFactory( "plugins.flutter.io/webview", new FlutterWebViewFactory(instanceManager)); - WebViewHostApi.setup( - binaryMessenger, + + webViewHostApi = new WebViewHostApiImpl( - instanceManager, new WebViewHostApiImpl.WebViewProxy(), activity, containerView)); + instanceManager, new WebViewHostApiImpl.WebViewProxy(), activity, containerView); + javaScriptChannelHostApi = + new JavaScriptChannelHostApiImpl( + instanceManager, + new JavaScriptChannelHostApiImpl.JavaScriptChannelCreator(), + new JavaScriptChannelFlutterApiImpl(binaryMessenger, instanceManager), + new Handler(activity.getMainLooper())); + + WebViewHostApi.setup(binaryMessenger, webViewHostApi); + JavaScriptChannelHostApi.setup(binaryMessenger, javaScriptChannelHostApi); WebViewClientHostApi.setup( binaryMessenger, new WebViewClientHostApiImpl( @@ -92,24 +108,15 @@ private static void setUp( instanceManager, new DownloadListenerHostApiImpl.DownloadListenerCreator(), new DownloadListenerFlutterApiImpl(binaryMessenger, instanceManager))); - JavaScriptChannelHostApi.setup( - binaryMessenger, - new JavaScriptChannelHostApiImpl( - instanceManager, - new JavaScriptChannelHostApiImpl.JavaScriptChannelCreator(), - new JavaScriptChannelFlutterApiImpl(binaryMessenger, instanceManager), - new Handler(activity.getMainLooper()))); WebSettingsHostApi.setup( binaryMessenger, new WebSettingsHostApiImpl( instanceManager, new WebSettingsHostApiImpl.WebSettingsCreator())); - new FlutterCookieManager(binaryMessenger); } @Override - public void onAttachedToEngine(FlutterPluginBinding binding) { + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { this.pluginBinding = binding; - flutterCookieManager = new FlutterCookieManager(binding.getBinaryMessenger()); } @Override @@ -124,26 +131,44 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { - setUp( - pluginBinding.getBinaryMessenger(), - pluginBinding.getPlatformViewRegistry(), - activityPluginBinding.getActivity(), - null); + if (webViewHostApi != null) { + final Activity activity = activityPluginBinding.getActivity(); + webViewHostApi.setContext(activityPluginBinding.getActivity()); + javaScriptChannelHostApi.setPlatformThreadHandler(new Handler(activity.getMainLooper())); + } else { + setUp( + pluginBinding.getBinaryMessenger(), + pluginBinding.getPlatformViewRegistry(), + activityPluginBinding.getActivity(), + null); + } } @Override - public void onDetachedFromActivityForConfigChanges() {} + public void onDetachedFromActivityForConfigChanges() { + webViewHostApi.setContext(null); + javaScriptChannelHostApi.setPlatformThreadHandler(null); + } @Override public void onReattachedToActivityForConfigChanges( @NonNull ActivityPluginBinding activityPluginBinding) { - setUp( - pluginBinding.getBinaryMessenger(), - pluginBinding.getPlatformViewRegistry(), - activityPluginBinding.getActivity(), - null); + if (webViewHostApi != null) { + final Activity activity = activityPluginBinding.getActivity(); + webViewHostApi.setContext(activity); + javaScriptChannelHostApi.setPlatformThreadHandler(new Handler(activity.getMainLooper())); + } else { + setUp( + pluginBinding.getBinaryMessenger(), + pluginBinding.getPlatformViewRegistry(), + activityPluginBinding.getActivity(), + null); + } } @Override - public void onDetachedFromActivity() {} + public void onDetachedFromActivity() { + webViewHostApi.setContext(null); + javaScriptChannelHostApi.setPlatformThreadHandler(null); + } } 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..42245d49cb44 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(); From 79cdc87c564f605b665dbc0cebbae0c014653900 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Fri, 12 Nov 2021 15:01:20 -0800 Subject: [PATCH 15/33] use application context --- .../webviewflutter/WebViewFlutterPlugin.java | 44 +++++++------------ 1 file changed, 16 insertions(+), 28 deletions(-) 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 0e3a466802ea..41458ee7dd25 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 @@ -5,6 +5,7 @@ package io.flutter.plugins.webviewflutter; import android.app.Activity; +import android.content.Context; import android.os.Handler; import android.view.View; import androidx.annotation.NonNull; @@ -28,9 +29,7 @@ *

Call {@link #registerWith} to use the stable {@code io.flutter.plugin.common} package instead. */ public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware { - private FlutterPluginBinding pluginBinding; private FlutterCookieManager flutterCookieManager; - private WebViewHostApiImpl webViewHostApi; private JavaScriptChannelHostApiImpl javaScriptChannelHostApi; @@ -69,7 +68,7 @@ public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registra private void setUp( BinaryMessenger binaryMessenger, PlatformViewRegistry viewRegistry, - Activity activity, + Context context, View containerView) { new FlutterCookieManager(binaryMessenger); @@ -80,13 +79,13 @@ private void setUp( webViewHostApi = new WebViewHostApiImpl( - instanceManager, new WebViewHostApiImpl.WebViewProxy(), activity, containerView); + instanceManager, new WebViewHostApiImpl.WebViewProxy(), context, containerView); javaScriptChannelHostApi = new JavaScriptChannelHostApiImpl( instanceManager, new JavaScriptChannelHostApiImpl.JavaScriptChannelCreator(), new JavaScriptChannelFlutterApiImpl(binaryMessenger, instanceManager), - new Handler(activity.getMainLooper())); + new Handler(context.getMainLooper())); WebViewHostApi.setup(binaryMessenger, webViewHostApi); JavaScriptChannelHostApi.setup(binaryMessenger, javaScriptChannelHostApi); @@ -116,7 +115,11 @@ private void setUp( @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { - this.pluginBinding = binding; + setUp( + binding.getBinaryMessenger(), + binding.getPlatformViewRegistry(), + binding.getApplicationContext(), + null); } @Override @@ -131,17 +134,10 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { - if (webViewHostApi != null) { - final Activity activity = activityPluginBinding.getActivity(); - webViewHostApi.setContext(activityPluginBinding.getActivity()); - javaScriptChannelHostApi.setPlatformThreadHandler(new Handler(activity.getMainLooper())); - } else { - setUp( - pluginBinding.getBinaryMessenger(), - pluginBinding.getPlatformViewRegistry(), - activityPluginBinding.getActivity(), - null); - } + + final Activity activity = activityPluginBinding.getActivity(); + webViewHostApi.setContext(activityPluginBinding.getActivity()); + javaScriptChannelHostApi.setPlatformThreadHandler(new Handler(activity.getMainLooper())); } @Override @@ -153,17 +149,9 @@ public void onDetachedFromActivityForConfigChanges() { @Override public void onReattachedToActivityForConfigChanges( @NonNull ActivityPluginBinding activityPluginBinding) { - if (webViewHostApi != null) { - final Activity activity = activityPluginBinding.getActivity(); - webViewHostApi.setContext(activity); - javaScriptChannelHostApi.setPlatformThreadHandler(new Handler(activity.getMainLooper())); - } else { - setUp( - pluginBinding.getBinaryMessenger(), - pluginBinding.getPlatformViewRegistry(), - activityPluginBinding.getActivity(), - null); - } + final Activity activity = activityPluginBinding.getActivity(); + webViewHostApi.setContext(activity); + javaScriptChannelHostApi.setPlatformThreadHandler(new Handler(activity.getMainLooper())); } @Override From 049afceec33070a3329fa9fb15d15886cb4fbd64 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Fri, 12 Nov 2021 15:06:20 -0800 Subject: [PATCH 16/33] dont set context to null --- .../webviewflutter/WebViewFlutterPlugin.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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 41458ee7dd25..933d22236290 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 @@ -29,6 +29,7 @@ *

Call {@link #registerWith} to use the stable {@code io.flutter.plugin.common} package instead. */ public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware { + private FlutterPluginBinding pluginBinding; private FlutterCookieManager flutterCookieManager; private WebViewHostApiImpl webViewHostApi; private JavaScriptChannelHostApiImpl javaScriptChannelHostApi; @@ -115,6 +116,7 @@ private void setUp( @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + pluginBinding = binding; setUp( binding.getBinaryMessenger(), binding.getPlatformViewRegistry(), @@ -134,7 +136,6 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { - final Activity activity = activityPluginBinding.getActivity(); webViewHostApi.setContext(activityPluginBinding.getActivity()); javaScriptChannelHostApi.setPlatformThreadHandler(new Handler(activity.getMainLooper())); @@ -142,8 +143,9 @@ public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBi @Override public void onDetachedFromActivityForConfigChanges() { - webViewHostApi.setContext(null); - javaScriptChannelHostApi.setPlatformThreadHandler(null); + final Context context = pluginBinding.getApplicationContext(); + webViewHostApi.setContext(context); + javaScriptChannelHostApi.setPlatformThreadHandler(new Handler(context.getMainLooper())); } @Override @@ -156,7 +158,8 @@ public void onReattachedToActivityForConfigChanges( @Override public void onDetachedFromActivity() { - webViewHostApi.setContext(null); - javaScriptChannelHostApi.setPlatformThreadHandler(null); + final Context context = pluginBinding.getApplicationContext(); + webViewHostApi.setContext(context); + javaScriptChannelHostApi.setPlatformThreadHandler(new Handler(context.getMainLooper())); } } From 3b49d41e6ac6f44c1b49755f7500923144980506 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Sat, 13 Nov 2021 11:53:57 -0800 Subject: [PATCH 17/33] fix unit tests --- .../lib/webview_android_widget.dart | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index 41ff51de0ea1..14140014b130 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -73,7 +73,7 @@ class _WebViewAndroidWidgetState extends State { @override void dispose() { super.dispose(); - controller._release(); + controller._dispose(); } @override @@ -178,12 +178,12 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { Future clearCache() => webView.clearCache(true); @override - Future updateSettings(WebSettings settings) { - return Future.wait(>[ + Future updateSettings(WebSettings settings) async { + await Future.wait(>[ _setUserAgent(settings.userAgent), if (settings.hasProgressTracking != null) _setHasProgressTracking(settings.hasProgressTracking!), - if (settings.hasProgressTracking != null) + if (settings.hasNavigationDelegate != null) _setHasNavigationDelegate(settings.hasNavigationDelegate!), if (settings.javascriptMode != null) _setJavaScriptMode(settings.javascriptMode!), @@ -262,7 +262,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { @override Future getScrollY() => webView.getScrollY(); - Future _release() => webView.release(); + Future _dispose() => webView.release(); void _setCreationParams(CreationParams creationParams) { final WebSettings? webSettings = creationParams.webSettings; @@ -288,13 +288,12 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { addJavascriptChannels(creationParams.javascriptChannelNames); } - Future _setHasProgressTracking( - bool hasProgressTracking, - ) async { + Future _setHasProgressTracking(bool hasProgressTracking) async { if (hasProgressTracking) { webChromeClient._onProgress = callbacksHandler.onProgress; + } else { + webChromeClient._onProgress = null; } - webChromeClient._onProgress = null; } Future _setHasNavigationDelegate(bool hasNavigationDelegate) { @@ -308,14 +307,14 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { loadUrl: loadUrl, onNavigationRequestCallback: callbacksHandler.onNavigationRequest, ); + } else { + downloadListener._onNavigationRequest = null; + _webViewClient = WebViewAndroidWebViewClient( + onPageStartedCallback: callbacksHandler.onPageStarted, + onPageFinishedCallback: callbacksHandler.onPageFinished, + onWebResourceErrorCallback: callbacksHandler.onWebResourceError, + ); } - - downloadListener._onNavigationRequest = null; - _webViewClient = WebViewAndroidWebViewClient( - onPageStartedCallback: callbacksHandler.onPageStarted, - onPageFinishedCallback: callbacksHandler.onPageFinished, - onWebResourceErrorCallback: callbacksHandler.onWebResourceError, - ); return webView.setWebViewClient(_webViewClient); } From 514d16eae15fa47bedecb382691ab78edffdc979 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Sat, 13 Nov 2021 12:01:39 -0800 Subject: [PATCH 18/33] change analysis options --- packages/webview_flutter/analysis_options.yaml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 packages/webview_flutter/analysis_options.yaml 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 From f472535317c682ee212c3c3ec79b9f1eed6445fb Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Sat, 13 Nov 2021 12:11:58 -0800 Subject: [PATCH 19/33] dart analysis errors --- .../webview_flutter/analysis_options.yaml | 1 + .../webview_flutter_test.dart | 47 +++--- .../example/lib/main.dart | 16 +-- .../example/lib/web_view.dart | 15 +- .../lib/src/instance_manager.dart | 4 +- .../lib/webview_android.dart | 4 +- .../lib/webview_android_widget.dart | 53 ++++--- .../lib/webview_surface_android.dart | 2 +- .../test/android_webview.pigeon.dart | 134 +++++++++--------- .../test/webview_android_widget_test.dart | 22 +-- .../analysis_options.yaml | 1 + .../analysis_options.yaml | 1 + 12 files changed, 153 insertions(+), 147 deletions(-) create mode 100644 packages/webview_flutter/webview_flutter/analysis_options.yaml create mode 100644 packages/webview_flutter/webview_flutter_platform_interface/analysis_options.yaml create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/analysis_options.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..cda4f6e153e6 --- /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_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index 8e3dcb458ee8..3e08c2a3c5e2 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 @@ -185,7 +185,7 @@ void main() { }, skip: _skipDueToIssue86757); testWidgets('resize webview', (WidgetTester tester) async { - final String resizeTest = ''' + const String resizeTest = ''' Resize test