From 4e7f4c29e48e971fc45764d95099916ce4432c00 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Sun, 6 Nov 2022 09:26:04 +0100 Subject: [PATCH 1/4] Added Android implementation of PlatformWebViewController --- .../lib/src/v4/src/android_proxy.dart | 30 + .../v4/src/android_webview_controller.dart | 320 +++++ .../v4/android_webview_controller_test.dart | 654 ++++++++++ ...android_webview_controller_test.mocks.dart | 1123 +++++++++++++++++ 4 files changed, 2127 insertions(+) create mode 100644 packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_controller.dart create mode 100644 packages/webview_flutter/webview_flutter_android/test/v4/android_webview_controller_test.dart create mode 100644 packages/webview_flutter/webview_flutter_android/test/v4/android_webview_controller_test.mocks.dart diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_proxy.dart b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_proxy.dart index 4a9ce192319d..ff86f5bc923a 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_proxy.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_proxy.dart @@ -16,10 +16,18 @@ import '../../android_webview.dart' as android_webview; class AndroidWebViewProxy { /// Constructs a [AndroidWebViewProxy]. const AndroidWebViewProxy({ + this.createAndroidWebView = android_webview.WebView.new, this.createAndroidWebChromeClient = android_webview.WebChromeClient.new, this.createAndroidWebViewClient = android_webview.WebViewClient.new, + this.createFlutterAssetManager = android_webview.FlutterAssetManager.new, + this.createJavaScriptChannel = android_webview.JavaScriptChannel.new, }); + /// Constructs a [android_webview.WebView]. + final android_webview.WebView Function({ + required bool useHybridComposition, + }) createAndroidWebView; + /// Constructs a [android_webview.WebChromeClient]. final android_webview.WebChromeClient Function({ void Function(android_webview.WebView webView, int progress)? @@ -51,4 +59,26 @@ class AndroidWebViewProxy { requestLoading, void Function(android_webview.WebView webView, String url)? urlLoading, }) createAndroidWebViewClient; + + /// Constructs a [android_webview.FlutterAssetManager]. + final android_webview.FlutterAssetManager Function() + createFlutterAssetManager; + + /// Constructs a [android_webview.JavaScriptChannel]. + final android_webview.JavaScriptChannel Function( + String channelName, { + required void Function(String) postMessage, + }) createJavaScriptChannel; + + /// 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(enabled); + } } diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_controller.dart new file mode 100644 index 000000000000..8f1a7580115b --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_controller.dart @@ -0,0 +1,320 @@ +// 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:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; + +import '../../android_webview.dart' as android_webview; +import '../../weak_reference_utils.dart'; +import 'android_navigation_delegate.dart'; +import 'android_proxy.dart'; + +/// Object specifying creation parameters for creating a [AndroidWebViewController]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebViewControllerCreationParams] for +/// more information. +@immutable +class AndroidWebViewControllerCreationParams + extends PlatformWebViewControllerCreationParams { + /// Creates a new [AndroidWebViewControllerCreationParams] instance. + AndroidWebViewControllerCreationParams({ + @visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(), + @visibleForTesting android_webview.WebStorage? androidWebStorage, + }) : androidWebStorage = + androidWebStorage ?? android_webview.WebStorage.instance, + super(); + + /// Creates a [AndroidWebViewControllerCreationParams] instance based on [PlatformWebViewControllerCreationParams]. + factory AndroidWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebViewControllerCreationParams params, { + @visibleForTesting + AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(), + @visibleForTesting android_webview.WebStorage? androidWebStorage, + }) { + return AndroidWebViewControllerCreationParams( + androidWebViewProxy: androidWebViewProxy, + androidWebStorage: + androidWebStorage ?? android_webview.WebStorage.instance, + ); + } + + /// Handles constructing objects and calling static methods for the Android WebView + /// native library. + @visibleForTesting + final AndroidWebViewProxy androidWebViewProxy; + + /// Manages the JavaScript storage APIs provided by the [android_webview.WebView]. + @visibleForTesting + final android_webview.WebStorage androidWebStorage; +} + +/// Implementation of the [PlatformWebViewController] with the Android WebView API. +class AndroidWebViewController extends PlatformWebViewController { + /// Creates a new [AndroidWebViewCookieManager]. + AndroidWebViewController(PlatformWebViewControllerCreationParams params) + : super.implementation(params is AndroidWebViewControllerCreationParams + ? params + : AndroidWebViewControllerCreationParams + .fromPlatformWebViewControllerCreationParams(params)); + + AndroidWebViewControllerCreationParams get _androidWebViewParams => + params as AndroidWebViewControllerCreationParams; + + /// The native [android_webview.WebView] being controlled. + late final android_webview.WebView _webView = withWeakRefenceTo(this, ( + WeakReference weakReference, + ) { + return _androidWebViewParams.androidWebViewProxy.createAndroidWebView( + useHybridComposition: true, + ); + }); + + /// The native [android_webview.FlutterAssetManager] allows managing assets. + late final android_webview.FlutterAssetManager _flutterAssetManager = + withWeakRefenceTo(this, ( + WeakReference weakReference, + ) { + return _androidWebViewParams.androidWebViewProxy + .createFlutterAssetManager(); + }); + + final Map _javaScriptChannelParams = + {}; + + @override + Future loadFile( + String absoluteFilePath, + ) { + final String url = absoluteFilePath.startsWith('file://') + ? absoluteFilePath + : 'file://$absoluteFilePath'; + + _webView.settings.setAllowFileAccess(true); + return _webView.loadUrl(url, {}); + } + + @override + Future loadFlutterAsset( + String key, + ) async { + final String assetFilePath = + await _flutterAssetManager.getAssetFilePathByName(key); + final List pathElements = assetFilePath.split('/'); + final String fileName = pathElements.removeLast(); + final List paths = + await _flutterAssetManager.list(pathElements.join('/')); + + if (!paths.contains(fileName)) { + throw ArgumentError( + 'Asset for key "$key" not found.', + 'key', + ); + } + + return _webView.loadUrl( + 'file:///android_asset/$assetFilePath', + {}, + ); + } + + @override + Future loadHtmlString( + String html, { + String? baseUrl, + }) { + return _webView.loadDataWithBaseUrl( + baseUrl: baseUrl, + data: html, + mimeType: 'text/html', + ); + } + + @override + Future loadRequest( + LoadRequestParams params, + ) { + if (!params.uri.hasScheme) { + throw ArgumentError('WebViewRequest#uri is required to have a scheme.'); + } + switch (params.method) { + case LoadRequestMethod.get: + return _webView.loadUrl(params.uri.toString(), params.headers); + case LoadRequestMethod.post: + return _webView.postUrl( + params.uri.toString(), params.body ?? Uint8List(0)); + default: + throw UnimplementedError( + 'This version of webview_android_widget currently has no implementation for HTTP method ${params.method.serialize()} in loadRequest.', + ); + } + } + + @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 clearLocalStorage() => + _androidWebViewParams.androidWebStorage.deleteAllData(); + + @override + Future setPlatformNavigationDelegate( + covariant AndroidNavigationDelegate handler) async { + _webView.setWebViewClient(handler.androidWebViewClient); + _webView.setWebChromeClient(handler.androidWebChromeClient); + } + + @override + Future runJavaScript(String javaScript) { + return _webView.evaluateJavascript(javaScript); + } + + @override + Future runJavaScriptReturningResult(String javaScript) async { + return await _webView.evaluateJavascript(javaScript) ?? ''; + } + + @override + Future addJavaScriptChannel( + JavaScriptChannelParams javaScriptChannelParams, + ) { + final AndroidJavaScriptChannelParams androidJavaScriptParams = + javaScriptChannelParams is AndroidJavaScriptChannelParams + ? javaScriptChannelParams + : AndroidJavaScriptChannelParams.fromJavaScriptChannelParams( + javaScriptChannelParams); + + // When JavaScript channel with the same name exists make sure toremove it + // before registering the new channel. + if (_javaScriptChannelParams.containsKey(androidJavaScriptParams.name)) { + _webView + .removeJavaScriptChannel(androidJavaScriptParams._javaScriptChannel); + } + + _javaScriptChannelParams[androidJavaScriptParams.name] = + androidJavaScriptParams; + + return _webView + .addJavaScriptChannel(androidJavaScriptParams._javaScriptChannel); + } + + @override + Future removeJavaScriptChannel(String javaScriptChannelName) async { + final AndroidJavaScriptChannelParams? javaScriptChannelParams = + _javaScriptChannelParams[javaScriptChannelName]; + + if (javaScriptChannelParams == null) { + return; + } + + _javaScriptChannelParams.remove(javaScriptChannelName); + return _webView + .removeJavaScriptChannel(javaScriptChannelParams._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> getScrollPosition() async { + final Offset position = await _webView.getScrollPosition(); + return Point(position.dx.round(), position.dy.round()); + } + + @override + Future enableDebugging(bool enabled) => + _androidWebViewParams.androidWebViewProxy + .setWebContentsDebuggingEnabled(enabled); + + @override + Future enableZoom(bool enabled) => + _webView.settings.setSupportZoom(enabled); + + @override + Future setBackgroundColor(Color color) => + _webView.setBackgroundColor(color); + + @override + Future setJavaScriptMode(JavaScriptMode javaScriptMode) => + _webView.settings + .setJavaScriptEnabled(javaScriptMode == JavaScriptMode.unrestricted); + + @override + Future setUserAgent(String? userAgent) => + _webView.settings.setUserAgentString(userAgent); +} + +/// An implementation of [JavaScriptChannelParams] with the Android WebView API. +/// +/// See [AndroidWebViewController.addJavaScriptChannel]. +@immutable +class AndroidJavaScriptChannelParams extends JavaScriptChannelParams { + /// Constructs a [AndroidJavaScriptChannelParams]. + AndroidJavaScriptChannelParams({ + required super.name, + required super.onMessageReceived, + @visibleForTesting + AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(), + }) : assert(name.isNotEmpty), + _javaScriptChannel = webViewProxy.createJavaScriptChannel( + name, + postMessage: withWeakRefenceTo( + onMessageReceived, + (WeakReference weakReference) { + return ( + String message, + ) { + if (weakReference.target != null) { + weakReference.target!( + JavaScriptMessage(message: message), + ); + } + }; + }, + ), + ); + + /// Constructs a [AndroidJavaScriptChannelParams] using a + /// [JavaScriptChannelParams]. + AndroidJavaScriptChannelParams.fromJavaScriptChannelParams( + JavaScriptChannelParams params, { + @visibleForTesting + AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(), + }) : this( + name: params.name, + onMessageReceived: params.onMessageReceived, + webViewProxy: webViewProxy, + ); + + final android_webview.JavaScriptChannel _javaScriptChannel; +} diff --git a/packages/webview_flutter/webview_flutter_android/test/v4/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/v4/android_webview_controller_test.dart new file mode 100644 index 000000000000..a85e0a03189f --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/v4/android_webview_controller_test.dart @@ -0,0 +1,654 @@ +// 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:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.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/v4/src/android_navigation_delegate.dart'; +import 'package:webview_flutter_android/src/v4/src/android_proxy.dart'; +import 'package:webview_flutter_android/src/v4/src/android_webview_controller.dart'; +import 'package:webview_flutter_platform_interface/v4/src/webview_platform.dart'; + +import 'android_webview_controller_test.mocks.dart'; + +@GenerateNiceMocks(>[ + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('AndroidWebViewController', () { + AndroidWebViewController createControllerWithMocks({ + android_webview.FlutterAssetManager? mockFlutterAssetManager, + android_webview.JavaScriptChannel? mockJavaScriptChannel, + android_webview.WebChromeClient? mockWebChromeClient, + android_webview.WebView? mockWebView, + android_webview.WebViewClient? mockWebViewClient, + android_webview.WebStorage? mockWebStorage, + }) { + final android_webview.WebView nonNullMockWebView = + mockWebView ?? MockWebView(); + + final AndroidWebViewControllerCreationParams creationParams = + AndroidWebViewControllerCreationParams( + androidWebStorage: mockWebStorage ?? MockWebStorage(), + androidWebViewProxy: AndroidWebViewProxy( + createAndroidWebChromeClient: ( + {void Function(android_webview.WebView, int)? + onProgressChanged}) => + mockWebChromeClient ?? MockWebChromeClient(), + createAndroidWebView: ({required bool useHybridComposition}) => + nonNullMockWebView, + createAndroidWebViewClient: ({ + void Function(android_webview.WebView webView, String url)? + onPageFinished, + void Function(android_webview.WebView webView, String url)? + onPageStarted, + @Deprecated('Only called on Android version < 23.') + void Function( + android_webview.WebView webView, + int errorCode, + String description, + String failingUrl, + )? + onReceivedError, + void Function( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + )? + onReceivedRequestError, + void Function( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + )? + requestLoading, + void Function(android_webview.WebView webView, String url)? + urlLoading, + }) => + mockWebViewClient ?? MockWebViewClient(), + createFlutterAssetManager: () => + mockFlutterAssetManager ?? MockFlutterAssetManager(), + createJavaScriptChannel: ( + String channelName, { + required void Function(String) postMessage, + }) => + mockJavaScriptChannel ?? MockJavaScriptChannel(), + )); + + return AndroidWebViewController(creationParams); + } + + AndroidJavaScriptChannelParams + createAndroidJavaScriptChannelParamsWithMocks({ + String? name, + MockJavaScriptChannel? mockJavaScriptChannel, + }) { + return AndroidJavaScriptChannelParams( + name: name ?? 'test', + onMessageReceived: (JavaScriptMessage message) {}, + webViewProxy: AndroidWebViewProxy( + createJavaScriptChannel: ( + String channelName, { + required void Function(String) postMessage, + }) => + mockJavaScriptChannel ?? MockJavaScriptChannel(), + )); + } + + test('loadFile without file prefix', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.settings).thenReturn(mockWebSettings); + + await controller.loadFile('/path/to/file.html'); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify(mockWebView.loadUrl( + 'file:///path/to/file.html', + {}, + )).called(1); + }); + + test('loadFile with file prefix', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.settings).thenReturn(mockWebSettings); + + await controller.loadFile('file:///path/to/file.html'); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify(mockWebView.loadUrl( + 'file:///path/to/file.html', + {}, + )).called(1); + }); + + test('loadFlutterAsset when asset does not exists', () async { + final MockWebView mockWebView = MockWebView(); + final MockFlutterAssetManager mockAssetManager = + MockFlutterAssetManager(); + final AndroidWebViewController controller = createControllerWithMocks( + mockFlutterAssetManager: mockAssetManager, + mockWebView: mockWebView, + ); + + when(mockAssetManager.getAssetFilePathByName('mock_key')) + .thenAnswer((_) => Future.value('')); + when(mockAssetManager.list('')) + .thenAnswer((_) => Future>.value([])); + + try { + await controller.loadFlutterAsset('mock_key'); + fail('Expected an `ArgumentError`.'); + } on ArgumentError catch (e) { + expect(e.message, 'Asset for key "mock_key" not found.'); + expect(e.name, 'key'); + } on Error { + fail('Expect an `ArgumentError`.'); + } + + verify(mockAssetManager.getAssetFilePathByName('mock_key')).called(1); + verify(mockAssetManager.list('')).called(1); + verifyNever(mockWebView.loadUrl(any, any)); + }); + + test('loadFlutterAsset when asset does exists', () async { + final MockWebView mockWebView = MockWebView(); + final MockFlutterAssetManager mockAssetManager = + MockFlutterAssetManager(); + final AndroidWebViewController controller = createControllerWithMocks( + mockFlutterAssetManager: mockAssetManager, + mockWebView: mockWebView, + ); + + when(mockAssetManager.getAssetFilePathByName('mock_key')) + .thenAnswer((_) => Future.value('www/mock_file.html')); + when(mockAssetManager.list('www')).thenAnswer( + (_) => Future>.value(['mock_file.html'])); + + await controller.loadFlutterAsset('mock_key'); + + verify(mockAssetManager.getAssetFilePathByName('mock_key')).called(1); + verify(mockAssetManager.list('www')).called(1); + verify(mockWebView.loadUrl( + 'file:///android_asset/www/mock_file.html', {})); + }); + + test('loadHtmlString without baseUrl', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.loadHtmlString('

Hello Test!

'); + + verify(mockWebView.loadDataWithBaseUrl( + data: '

Hello Test!

', + mimeType: 'text/html', + )).called(1); + }); + + test('loadHtmlString with baseUrl', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.loadHtmlString('

Hello Test!

', + baseUrl: 'https://flutter.dev'); + + verify(mockWebView.loadDataWithBaseUrl( + data: '

Hello Test!

', + baseUrl: 'https://flutter.dev', + mimeType: 'text/html', + )).called(1); + }); + + test('loadRequest without URI scheme', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final LoadRequestParams requestParams = LoadRequestParams( + uri: Uri.parse('flutter.dev'), + method: LoadRequestMethod.get, + headers: const {}, + ); + + try { + await controller.loadRequest(requestParams); + fail('Expect an `ArgumentError`.'); + } on ArgumentError catch (e) { + expect(e.message, 'WebViewRequest#uri is required to have a scheme.'); + } on Error { + fail('Expect a `ArgumentError`.'); + } + + verifyNever(mockWebView.loadUrl(any, any)); + verifyNever(mockWebView.postUrl(any, any)); + }); + + test('loadRequest using the GET method', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final LoadRequestParams requestParams = LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + method: LoadRequestMethod.get, + headers: const {'X-Test': 'Testing'}, + ); + + await controller.loadRequest(requestParams); + + verify(mockWebView.loadUrl( + 'https://flutter.dev', + {'X-Test': 'Testing'}, + )); + verifyNever(mockWebView.postUrl(any, any)); + }); + + test('loadRequest using the POST method without body', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final LoadRequestParams requestParams = LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + method: LoadRequestMethod.post, + headers: const {'X-Test': 'Testing'}, + ); + + await controller.loadRequest(requestParams); + + verify(mockWebView.postUrl( + 'https://flutter.dev', + Uint8List(0), + )); + verifyNever(mockWebView.loadUrl(any, any)); + }); + + test('loadRequest using the POST method with body', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final LoadRequestParams requestParams = LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + method: LoadRequestMethod.post, + headers: const {'X-Test': 'Testing'}, + body: Uint8List.fromList('{"message": "Hello World!"}'.codeUnits), + ); + + await controller.loadRequest(requestParams); + + verify(mockWebView.postUrl( + 'https://flutter.dev', + Uint8List.fromList('{"message": "Hello World!"}'.codeUnits), + )); + verifyNever(mockWebView.loadUrl(any, any)); + }); + + test('currentUrl', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.currentUrl(); + + verify(mockWebView.getUrl()).called(1); + }); + + test('canGoBack', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.canGoBack(); + + verify(mockWebView.canGoBack()).called(1); + }); + + test('canGoForward', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.canGoForward(); + + verify(mockWebView.canGoForward()).called(1); + }); + + test('goBack', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.goBack(); + + verify(mockWebView.goBack()).called(1); + }); + + test('goForward', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.goForward(); + + verify(mockWebView.goForward()).called(1); + }); + + test('reload', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.reload(); + + verify(mockWebView.reload()).called(1); + }); + + test('clearCache', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.clearCache(); + + verify(mockWebView.clearCache(true)).called(1); + }); + + test('clearLocalStorage', () async { + final MockWebStorage mockWebStorage = MockWebStorage(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebStorage: mockWebStorage, + ); + + await controller.clearLocalStorage(); + + verify(mockWebStorage.deleteAllData()).called(1); + }); + + test('setPlatformNavigationDelegate', () async { + final MockAndroidNavigationDelegate mockNavigationDelegate = + MockAndroidNavigationDelegate(); + final MockWebView mockWebView = MockWebView(); + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + final MockWebViewClient mockWebViewClient = MockWebViewClient(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockNavigationDelegate.androidWebChromeClient) + .thenReturn(mockWebChromeClient); + when(mockNavigationDelegate.androidWebViewClient) + .thenReturn(mockWebViewClient); + + await controller.setPlatformNavigationDelegate(mockNavigationDelegate); + + verifyInOrder([ + mockWebView.setWebViewClient(mockWebViewClient), + mockWebView.setWebChromeClient(mockWebChromeClient), + ]); + }); + + test('runJavaScript', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.runJavaScript('alert("This is a test.");'); + + verify(mockWebView.evaluateJavascript('alert("This is a test.");')) + .called(1); + }); + + test('runJavaScriptReturningResult with return value', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.evaluateJavascript('return "Hello" + " World!";')) + .thenAnswer((_) => Future.value('Hello World!')); + + final String message = await controller + .runJavaScriptReturningResult('return "Hello" + " World!";'); + + expect(message, 'Hello World!'); + }); + + test('runJavaScriptReturningResult returning null', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.evaluateJavascript('alert("This is a test.");')) + .thenAnswer((_) => Future.value()); + + final String message = await controller + .runJavaScriptReturningResult('alert("This is a test.");'); + + expect(message, ''); + }); + + test('addJavaScriptChannel', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final AndroidJavaScriptChannelParams paramsWithMock = + createAndroidJavaScriptChannelParamsWithMocks(name: 'test'); + await controller.addJavaScriptChannel(paramsWithMock); + verify(mockWebView.addJavaScriptChannel( + argThat(isA()))) + .called(1); + }); + + test( + 'addJavaScriptChannel add channel with same name should remove existing channel', + () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final AndroidJavaScriptChannelParams paramsWithMock = + createAndroidJavaScriptChannelParamsWithMocks(name: 'test'); + await controller.addJavaScriptChannel(paramsWithMock); + verify(mockWebView.addJavaScriptChannel( + argThat(isA()))) + .called(1); + + await controller.addJavaScriptChannel(paramsWithMock); + verifyInOrder([ + mockWebView.removeJavaScriptChannel( + argThat(isA())), + mockWebView.addJavaScriptChannel( + argThat(isA())), + ]); + }); + + test('removeJavaScriptChannel when channel is not registered', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.removeJavaScriptChannel('test'); + verifyNever(mockWebView.removeJavaScriptChannel(any)); + }); + + test('removeJavaScriptChannel when channel exists', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final AndroidJavaScriptChannelParams paramsWithMock = + createAndroidJavaScriptChannelParamsWithMocks(name: 'test'); + + // Make sure channel exists before removing it. + await controller.addJavaScriptChannel(paramsWithMock); + verify(mockWebView.addJavaScriptChannel( + argThat(isA()))) + .called(1); + + await controller.removeJavaScriptChannel('test'); + verify(mockWebView.removeJavaScriptChannel( + argThat(isA()))) + .called(1); + }); + + test('getTitle', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.getTitle(); + + verify(mockWebView.getTitle()).called(1); + }); + + test('scrollTo', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.scrollTo(4, 2); + + verify(mockWebView.scrollTo(4, 2)).called(1); + }); + + test('scrollBy', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.scrollBy(4, 2); + + verify(mockWebView.scrollBy(4, 2)).called(1); + }); + + test('getScrollPosition', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + when(mockWebView.getScrollPosition()) + .thenAnswer((_) => Future.value(const Offset(4, 2))); + + final Point position = await controller.getScrollPosition(); + + verify(mockWebView.getScrollPosition()).called(1); + expect(position.x, 4); + expect(position.y, 2); + }); + + test('enableDebugging', () async { + final MockAndroidWebViewProxy mockProxy = MockAndroidWebViewProxy(); + final AndroidWebViewControllerCreationParams creationParams = + AndroidWebViewControllerCreationParams( + androidWebViewProxy: mockProxy, + androidWebStorage: MockWebStorage(), + ); + final AndroidWebViewController controller = + AndroidWebViewController(creationParams); + + await controller.enableDebugging(true); + + verify(mockProxy.setWebContentsDebuggingEnabled(true)).called(1); + }); + + test('enableZoom', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.settings).thenReturn(mockSettings); + + await controller.enableZoom(true); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setSupportZoom(true)).called(1); + }); + + test('setBackgroundColor', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.setBackgroundColor(Colors.blue); + + verify(mockWebView.setBackgroundColor(Colors.blue)).called(1); + }); + + test('setJavaScriptMode', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.settings).thenReturn(mockSettings); + + await controller.setJavaScriptMode(JavaScriptMode.disabled); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setJavaScriptEnabled(false)).called(1); + }); + + test('setUserAgent', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.settings).thenReturn(mockSettings); + + await controller.setUserAgent('Test Framework'); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setUserAgentString('Test Framework')).called(1); + }); + }); +} diff --git a/packages/webview_flutter/webview_flutter_android/test/v4/android_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/v4/android_webview_controller_test.mocks.dart new file mode 100644 index 000000000000..d3b8083f3cfc --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/v4/android_webview_controller_test.mocks.dart @@ -0,0 +1,1123 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in webview_flutter_android/test/v4/android_webview_controller_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i6; +import 'dart:typed_data' as _i8; +import 'dart:ui' 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/src/v4/src/android_navigation_delegate.dart' + as _i5; +import 'package:webview_flutter_android/src/v4/src/android_proxy.dart' as _i7; +import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart' + as _i3; + +// ignore_for_file: type=lint +// 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 +// ignore_for_file: subtype_of_sealed_class + +class _FakeWebChromeClient_0 extends _i1.SmartFake + implements _i2.WebChromeClient { + _FakeWebChromeClient_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWebViewClient_1 extends _i1.SmartFake implements _i2.WebViewClient { + _FakeWebViewClient_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformNavigationDelegateCreationParams_2 extends _i1.SmartFake + implements _i3.PlatformNavigationDelegateCreationParams { + _FakePlatformNavigationDelegateCreationParams_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWebView_3 extends _i1.SmartFake implements _i2.WebView { + _FakeWebView_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeFlutterAssetManager_4 extends _i1.SmartFake + implements _i2.FlutterAssetManager { + _FakeFlutterAssetManager_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeJavaScriptChannel_5 extends _i1.SmartFake + implements _i2.JavaScriptChannel { + _FakeJavaScriptChannel_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWebSettings_6 extends _i1.SmartFake implements _i2.WebSettings { + _FakeWebSettings_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeOffset_7 extends _i1.SmartFake implements _i4.Offset { + _FakeOffset_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWebStorage_8 extends _i1.SmartFake implements _i2.WebStorage { + _FakeWebStorage_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [AndroidNavigationDelegate]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAndroidNavigationDelegate extends _i1.Mock + implements _i5.AndroidNavigationDelegate { + @override + _i2.WebChromeClient get androidWebChromeClient => (super.noSuchMethod( + Invocation.getter(#androidWebChromeClient), + returnValue: _FakeWebChromeClient_0( + this, + Invocation.getter(#androidWebChromeClient), + ), + returnValueForMissingStub: _FakeWebChromeClient_0( + this, + Invocation.getter(#androidWebChromeClient), + ), + ) as _i2.WebChromeClient); + @override + _i2.WebViewClient get androidWebViewClient => (super.noSuchMethod( + Invocation.getter(#androidWebViewClient), + returnValue: _FakeWebViewClient_1( + this, + Invocation.getter(#androidWebViewClient), + ), + returnValueForMissingStub: _FakeWebViewClient_1( + this, + Invocation.getter(#androidWebViewClient), + ), + ) as _i2.WebViewClient); + @override + _i3.PlatformNavigationDelegateCreationParams get params => + (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformNavigationDelegateCreationParams_2( + this, + Invocation.getter(#params), + ), + returnValueForMissingStub: + _FakePlatformNavigationDelegateCreationParams_2( + this, + Invocation.getter(#params), + ), + ) as _i3.PlatformNavigationDelegateCreationParams); + @override + _i6.Future setOnLoadUrl(_i5.LoadUrlCallback? onLoadUrl) => + (super.noSuchMethod( + Invocation.method( + #setOnLoadUrl, + [onLoadUrl], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setOnNavigationRequest( + _i3.NavigationRequestCallback? onNavigationRequest) => + (super.noSuchMethod( + Invocation.method( + #setOnNavigationRequest, + [onNavigationRequest], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setOnPageStarted(_i3.PageEventCallback? onPageStarted) => + (super.noSuchMethod( + Invocation.method( + #setOnPageStarted, + [onPageStarted], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setOnPageFinished(_i3.PageEventCallback? onPageFinished) => + (super.noSuchMethod( + Invocation.method( + #setOnPageFinished, + [onPageFinished], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setOnProgress(_i3.ProgressCallback? onProgress) => + (super.noSuchMethod( + Invocation.method( + #setOnProgress, + [onProgress], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setOnWebResourceError( + _i3.WebResourceErrorCallback? onWebResourceError) => + (super.noSuchMethod( + Invocation.method( + #setOnWebResourceError, + [onWebResourceError], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); +} + +/// A class which mocks [AndroidWebViewProxy]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAndroidWebViewProxy extends _i1.Mock + implements _i7.AndroidWebViewProxy { + @override + _i2.WebView Function({required bool useHybridComposition}) + get createAndroidWebView => (super.noSuchMethod( + Invocation.getter(#createAndroidWebView), + returnValue: ({required bool useHybridComposition}) => + _FakeWebView_3( + this, + Invocation.getter(#createAndroidWebView), + ), + returnValueForMissingStub: ({required bool useHybridComposition}) => + _FakeWebView_3( + this, + Invocation.getter(#createAndroidWebView), + ), + ) as _i2.WebView Function({required bool useHybridComposition})); + @override + _i2.WebChromeClient Function( + {void Function( + _i2.WebView, + int, + )? + onProgressChanged}) get createAndroidWebChromeClient => + (super.noSuchMethod( + Invocation.getter(#createAndroidWebChromeClient), + returnValue: ( + {void Function( + _i2.WebView, + int, + )? + onProgressChanged}) => + _FakeWebChromeClient_0( + this, + Invocation.getter(#createAndroidWebChromeClient), + ), + returnValueForMissingStub: ( + {void Function( + _i2.WebView, + int, + )? + onProgressChanged}) => + _FakeWebChromeClient_0( + this, + Invocation.getter(#createAndroidWebChromeClient), + ), + ) as _i2.WebChromeClient Function( + {void Function( + _i2.WebView, + int, + )? + onProgressChanged})); + @override + _i2.WebViewClient Function({ + void Function( + _i2.WebView, + String, + )? + onPageFinished, + void Function( + _i2.WebView, + String, + )? + onPageStarted, + void Function( + _i2.WebView, + int, + String, + String, + )? + onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + )? + requestLoading, + void Function( + _i2.WebView, + String, + )? + urlLoading, + }) get createAndroidWebViewClient => (super.noSuchMethod( + Invocation.getter(#createAndroidWebViewClient), + returnValue: ({ + void Function( + _i2.WebView, + String, + )? + onPageFinished, + void Function( + _i2.WebView, + String, + )? + onPageStarted, + void Function( + _i2.WebView, + int, + String, + String, + )? + onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + )? + requestLoading, + void Function( + _i2.WebView, + String, + )? + urlLoading, + }) => + _FakeWebViewClient_1( + this, + Invocation.getter(#createAndroidWebViewClient), + ), + returnValueForMissingStub: ({ + void Function( + _i2.WebView, + String, + )? + onPageFinished, + void Function( + _i2.WebView, + String, + )? + onPageStarted, + void Function( + _i2.WebView, + int, + String, + String, + )? + onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + )? + requestLoading, + void Function( + _i2.WebView, + String, + )? + urlLoading, + }) => + _FakeWebViewClient_1( + this, + Invocation.getter(#createAndroidWebViewClient), + ), + ) as _i2.WebViewClient Function({ + void Function( + _i2.WebView, + String, + )? + onPageFinished, + void Function( + _i2.WebView, + String, + )? + onPageStarted, + void Function( + _i2.WebView, + int, + String, + String, + )? + onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + )? + requestLoading, + void Function( + _i2.WebView, + String, + )? + urlLoading, + })); + @override + _i2.FlutterAssetManager Function() get createFlutterAssetManager => + (super.noSuchMethod( + Invocation.getter(#createFlutterAssetManager), + returnValue: () => _FakeFlutterAssetManager_4( + this, + Invocation.getter(#createFlutterAssetManager), + ), + returnValueForMissingStub: () => _FakeFlutterAssetManager_4( + this, + Invocation.getter(#createFlutterAssetManager), + ), + ) as _i2.FlutterAssetManager Function()); + @override + _i2.JavaScriptChannel Function( + String, { + required void Function(String) postMessage, + }) get createJavaScriptChannel => (super.noSuchMethod( + Invocation.getter(#createJavaScriptChannel), + returnValue: ( + String channelName, { + required void Function(String) postMessage, + }) => + _FakeJavaScriptChannel_5( + this, + Invocation.getter(#createJavaScriptChannel), + ), + returnValueForMissingStub: ( + String channelName, { + required void Function(String) postMessage, + }) => + _FakeJavaScriptChannel_5( + this, + Invocation.getter(#createJavaScriptChannel), + ), + ) as _i2.JavaScriptChannel Function( + String, { + required void Function(String) postMessage, + })); + @override + _i6.Future setWebContentsDebuggingEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method( + #setWebContentsDebuggingEnabled, + [enabled], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); +} + +/// A class which mocks [FlutterAssetManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFlutterAssetManager extends _i1.Mock + implements _i2.FlutterAssetManager { + @override + _i6.Future> list(String? path) => (super.noSuchMethod( + Invocation.method( + #list, + [path], + ), + returnValue: _i6.Future>.value([]), + returnValueForMissingStub: _i6.Future>.value([]), + ) as _i6.Future>); + @override + _i6.Future getAssetFilePathByName(String? name) => + (super.noSuchMethod( + Invocation.method( + #getAssetFilePathByName, + [name], + ), + returnValue: _i6.Future.value(''), + returnValueForMissingStub: _i6.Future.value(''), + ) as _i6.Future); +} + +/// A class which mocks [JavaScriptChannel]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockJavaScriptChannel extends _i1.Mock implements _i2.JavaScriptChannel { + @override + String get channelName => (super.noSuchMethod( + Invocation.getter(#channelName), + returnValue: '', + returnValueForMissingStub: '', + ) as String); + @override + void Function(String) get postMessage => (super.noSuchMethod( + Invocation.getter(#postMessage), + returnValue: (String message) {}, + returnValueForMissingStub: (String message) {}, + ) as void Function(String)); + @override + _i2.JavaScriptChannel copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeJavaScriptChannel_5( + this, + Invocation.method( + #copy, + [], + ), + ), + returnValueForMissingStub: _FakeJavaScriptChannel_5( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.JavaScriptChannel); +} + +/// A class which mocks [WebChromeClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { + @override + _i2.WebChromeClient copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebChromeClient_0( + this, + Invocation.method( + #copy, + [], + ), + ), + returnValueForMissingStub: _FakeWebChromeClient_0( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebChromeClient); +} + +/// A class which mocks [WebSettings]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebSettings extends _i1.Mock implements _i2.WebSettings { + @override + _i6.Future setDomStorageEnabled(bool? flag) => (super.noSuchMethod( + Invocation.method( + #setDomStorageEnabled, + [flag], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => + (super.noSuchMethod( + Invocation.method( + #setJavaScriptCanOpenWindowsAutomatically, + [flag], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setSupportMultipleWindows(bool? support) => + (super.noSuchMethod( + Invocation.method( + #setSupportMultipleWindows, + [support], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setJavaScriptEnabled(bool? flag) => (super.noSuchMethod( + Invocation.method( + #setJavaScriptEnabled, + [flag], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setUserAgentString(String? userAgentString) => + (super.noSuchMethod( + Invocation.method( + #setUserAgentString, + [userAgentString], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setMediaPlaybackRequiresUserGesture(bool? require) => + (super.noSuchMethod( + Invocation.method( + #setMediaPlaybackRequiresUserGesture, + [require], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setSupportZoom(bool? support) => (super.noSuchMethod( + Invocation.method( + #setSupportZoom, + [support], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setLoadWithOverviewMode(bool? overview) => + (super.noSuchMethod( + Invocation.method( + #setLoadWithOverviewMode, + [overview], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setUseWideViewPort(bool? use) => (super.noSuchMethod( + Invocation.method( + #setUseWideViewPort, + [use], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setDisplayZoomControls(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #setDisplayZoomControls, + [enabled], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setBuiltInZoomControls(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #setBuiltInZoomControls, + [enabled], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setAllowFileAccess(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #setAllowFileAccess, + [enabled], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i2.WebSettings copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebSettings_6( + this, + Invocation.method( + #copy, + [], + ), + ), + returnValueForMissingStub: _FakeWebSettings_6( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebSettings); +} + +/// A class which mocks [WebView]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebView extends _i1.Mock implements _i2.WebView { + @override + bool get useHybridComposition => (super.noSuchMethod( + Invocation.getter(#useHybridComposition), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + _i2.WebSettings get settings => (super.noSuchMethod( + Invocation.getter(#settings), + returnValue: _FakeWebSettings_6( + this, + Invocation.getter(#settings), + ), + returnValueForMissingStub: _FakeWebSettings_6( + this, + Invocation.getter(#settings), + ), + ) as _i2.WebSettings); + @override + _i6.Future loadData({ + required String? data, + String? mimeType, + String? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #loadData, + [], + { + #data: data, + #mimeType: mimeType, + #encoding: encoding, + }, + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future loadDataWithBaseUrl({ + String? baseUrl, + required String? data, + String? mimeType, + String? encoding, + String? historyUrl, + }) => + (super.noSuchMethod( + Invocation.method( + #loadDataWithBaseUrl, + [], + { + #baseUrl: baseUrl, + #data: data, + #mimeType: mimeType, + #encoding: encoding, + #historyUrl: historyUrl, + }, + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future loadUrl( + String? url, + Map? headers, + ) => + (super.noSuchMethod( + Invocation.method( + #loadUrl, + [ + url, + headers, + ], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future postUrl( + String? url, + _i8.Uint8List? data, + ) => + (super.noSuchMethod( + Invocation.method( + #postUrl, + [ + url, + data, + ], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future getUrl() => (super.noSuchMethod( + Invocation.method( + #getUrl, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future canGoBack() => (super.noSuchMethod( + Invocation.method( + #canGoBack, + [], + ), + returnValue: _i6.Future.value(false), + returnValueForMissingStub: _i6.Future.value(false), + ) as _i6.Future); + @override + _i6.Future canGoForward() => (super.noSuchMethod( + Invocation.method( + #canGoForward, + [], + ), + returnValue: _i6.Future.value(false), + returnValueForMissingStub: _i6.Future.value(false), + ) as _i6.Future); + @override + _i6.Future goBack() => (super.noSuchMethod( + Invocation.method( + #goBack, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future goForward() => (super.noSuchMethod( + Invocation.method( + #goForward, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future reload() => (super.noSuchMethod( + Invocation.method( + #reload, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future clearCache(bool? includeDiskFiles) => (super.noSuchMethod( + Invocation.method( + #clearCache, + [includeDiskFiles], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future evaluateJavascript(String? javascriptString) => + (super.noSuchMethod( + Invocation.method( + #evaluateJavascript, + [javascriptString], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future getTitle() => (super.noSuchMethod( + Invocation.method( + #getTitle, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future scrollTo( + int? x, + int? y, + ) => + (super.noSuchMethod( + Invocation.method( + #scrollTo, + [ + x, + y, + ], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future scrollBy( + int? x, + int? y, + ) => + (super.noSuchMethod( + Invocation.method( + #scrollBy, + [ + x, + y, + ], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future getScrollX() => (super.noSuchMethod( + Invocation.method( + #getScrollX, + [], + ), + returnValue: _i6.Future.value(0), + returnValueForMissingStub: _i6.Future.value(0), + ) as _i6.Future); + @override + _i6.Future getScrollY() => (super.noSuchMethod( + Invocation.method( + #getScrollY, + [], + ), + returnValue: _i6.Future.value(0), + returnValueForMissingStub: _i6.Future.value(0), + ) as _i6.Future); + @override + _i6.Future<_i4.Offset> getScrollPosition() => (super.noSuchMethod( + Invocation.method( + #getScrollPosition, + [], + ), + returnValue: _i6.Future<_i4.Offset>.value(_FakeOffset_7( + this, + Invocation.method( + #getScrollPosition, + [], + ), + )), + returnValueForMissingStub: _i6.Future<_i4.Offset>.value(_FakeOffset_7( + this, + Invocation.method( + #getScrollPosition, + [], + ), + )), + ) as _i6.Future<_i4.Offset>); + @override + _i6.Future setWebViewClient(_i2.WebViewClient? webViewClient) => + (super.noSuchMethod( + Invocation.method( + #setWebViewClient, + [webViewClient], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future addJavaScriptChannel( + _i2.JavaScriptChannel? javaScriptChannel) => + (super.noSuchMethod( + Invocation.method( + #addJavaScriptChannel, + [javaScriptChannel], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future removeJavaScriptChannel( + _i2.JavaScriptChannel? javaScriptChannel) => + (super.noSuchMethod( + Invocation.method( + #removeJavaScriptChannel, + [javaScriptChannel], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setDownloadListener(_i2.DownloadListener? listener) => + (super.noSuchMethod( + Invocation.method( + #setDownloadListener, + [listener], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setWebChromeClient(_i2.WebChromeClient? client) => + (super.noSuchMethod( + Invocation.method( + #setWebChromeClient, + [client], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future setBackgroundColor(_i4.Color? color) => (super.noSuchMethod( + Invocation.method( + #setBackgroundColor, + [color], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i2.WebView copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebView_3( + this, + Invocation.method( + #copy, + [], + ), + ), + returnValueForMissingStub: _FakeWebView_3( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebView); +} + +/// A class which mocks [WebViewClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewClient extends _i1.Mock implements _i2.WebViewClient { + @override + _i6.Future setSynchronousReturnValueForShouldOverrideUrlLoading( + bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForShouldOverrideUrlLoading, + [value], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i2.WebViewClient copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebViewClient_1( + this, + Invocation.method( + #copy, + [], + ), + ), + returnValueForMissingStub: _FakeWebViewClient_1( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebViewClient); +} + +/// A class which mocks [WebStorage]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebStorage extends _i1.Mock implements _i2.WebStorage { + @override + _i6.Future deleteAllData() => (super.noSuchMethod( + Invocation.method( + #deleteAllData, + [], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i2.WebStorage copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebStorage_8( + this, + Invocation.method( + #copy, + [], + ), + ), + returnValueForMissingStub: _FakeWebStorage_8( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebStorage); +} From 4594cc4f416debb43e760958ddc1b66bff0d82e6 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 16 Nov 2022 19:45:19 +0100 Subject: [PATCH 2/4] Apply feedback from PR --- .../lib/src/android_webview.dart | 4 + .../lib/src/v4/src/android_proxy.dart | 4 + .../v4/src/android_webview_controller.dart | 101 ++++++++++++++---- 3 files changed, 91 insertions(+), 18 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index d2f0333fdd32..66f93dde1679 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -78,6 +78,10 @@ class JavaObject with Copyable { /// When a [WebView] is no longer needed [release] must be called. class WebView extends JavaObject { /// Constructs a new WebView. + /// + /// Due to changes in Flutter 3.0 the [useHybridComposition] doesn't have + /// any effect and should not be exposed publicly. More info here: + /// https://github.com/flutter/flutter/issues/108106 WebView({this.useHybridComposition = false}) : super.detached() { api.createFromInstance(this); } diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_proxy.dart b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_proxy.dart index ff86f5bc923a..7f02d41d0168 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_proxy.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_proxy.dart @@ -24,6 +24,10 @@ class AndroidWebViewProxy { }); /// Constructs a [android_webview.WebView]. + /// + /// Due to changes in Flutter 3.0 the [useHybridComposition] doesn't have + /// any effect and should not be exposed publicly. More info here: + /// https://github.com/flutter/flutter/issues/108106 final android_webview.WebView Function({ required bool useHybridComposition, }) createAndroidWebView; diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_controller.dart index 8f1a7580115b..463afa5978a4 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_controller.dart @@ -4,11 +4,18 @@ import 'dart:math'; +// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) +// ignore: unnecessary_import +import 'dart:typed_data'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; import '../../android_webview.dart' as android_webview; +import '../../android_webview.dart'; +import '../../instance_manager.dart'; import '../../weak_reference_utils.dart'; import 'android_navigation_delegate.dart'; import 'android_proxy.dart'; @@ -68,22 +75,18 @@ class AndroidWebViewController extends PlatformWebViewController { params as AndroidWebViewControllerCreationParams; /// The native [android_webview.WebView] being controlled. - late final android_webview.WebView _webView = withWeakRefenceTo(this, ( - WeakReference weakReference, - ) { - return _androidWebViewParams.androidWebViewProxy.createAndroidWebView( - useHybridComposition: true, - ); - }); + late final android_webview.WebView _webView = + _androidWebViewParams.androidWebViewProxy.createAndroidWebView( + // Due to changes in Flutter 3.0 the `useHybridComposition` doesn't have + // any effect and is purposefully not exposed publicly by the + // [AndroidWebViewController]. More info here: + // https://github.com/flutter/flutter/issues/108106 + useHybridComposition: true, + ); /// The native [android_webview.FlutterAssetManager] allows managing assets. late final android_webview.FlutterAssetManager _flutterAssetManager = - withWeakRefenceTo(this, ( - WeakReference weakReference, - ) { - return _androidWebViewParams.androidWebViewProxy - .createFlutterAssetManager(); - }); + _androidWebViewParams.androidWebViewProxy.createFlutterAssetManager(); final Map _javaScriptChannelParams = {}; @@ -94,7 +97,7 @@ class AndroidWebViewController extends PlatformWebViewController { ) { final String url = absoluteFilePath.startsWith('file://') ? absoluteFilePath - : 'file://$absoluteFilePath'; + : Uri.file(absoluteFilePath).toString(); _webView.settings.setAllowFileAccess(true); return _webView.loadUrl(url, {}); @@ -119,7 +122,7 @@ class AndroidWebViewController extends PlatformWebViewController { } return _webView.loadUrl( - 'file:///android_asset/$assetFilePath', + Uri.file('/android_asset/$assetFilePath').toString(), {}, ); } @@ -151,7 +154,7 @@ class AndroidWebViewController extends PlatformWebViewController { params.uri.toString(), params.body ?? Uint8List(0)); default: throw UnimplementedError( - 'This version of webview_android_widget currently has no implementation for HTTP method ${params.method.serialize()} in loadRequest.', + 'This version of `AndroidWebViewController` currently has no implementation for HTTP method ${params.method.serialize()} in loadRequest.', ); } } @@ -208,7 +211,7 @@ class AndroidWebViewController extends PlatformWebViewController { : AndroidJavaScriptChannelParams.fromJavaScriptChannelParams( javaScriptChannelParams); - // When JavaScript channel with the same name exists make sure toremove it + // When JavaScript channel with the same name exists make sure to remove it // before registering the new channel. if (_javaScriptChannelParams.containsKey(androidJavaScriptParams.name)) { _webView @@ -226,7 +229,6 @@ class AndroidWebViewController extends PlatformWebViewController { Future removeJavaScriptChannel(String javaScriptChannelName) async { final AndroidJavaScriptChannelParams? javaScriptChannelParams = _javaScriptChannelParams[javaScriptChannelName]; - if (javaScriptChannelParams == null) { return; } @@ -318,3 +320,66 @@ class AndroidJavaScriptChannelParams extends JavaScriptChannelParams { final android_webview.JavaScriptChannel _javaScriptChannel; } + +/// Object specifying creation parameters for creating a [AndroidWebViewWidget]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebViewWidgetCreationParams] for +/// more information. +@immutable +class AndroidWebViewWidgetCreationParams + extends PlatformWebViewWidgetCreationParams { + /// Creates [AndroidWebWidgetCreationParams]. + AndroidWebViewWidgetCreationParams({ + super.key, + required super.controller, + super.layoutDirection, + super.gestureRecognizers, + @visibleForTesting InstanceManager? instanceManager, + }) : _instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Constructs a [WebKitWebViewWidgetCreationParams] using a + /// [PlatformWebViewWidgetCreationParams]. + AndroidWebViewWidgetCreationParams.fromPlatformWebViewWidgetCreationParams( + PlatformWebViewWidgetCreationParams params, { + InstanceManager? instanceManager, + }) : this( + key: params.key, + controller: params.controller, + layoutDirection: params.layoutDirection, + gestureRecognizers: params.gestureRecognizers, + instanceManager: instanceManager, + ); + + // Maintains instances used to communicate with the native objects they + // represent. + final InstanceManager _instanceManager; +} + +/// An implementation of [PlatformWebViewWidget] with the Android WebView API. +class AndroidWebViewWidget extends PlatformWebViewWidget { + /// Constructs a [WebKitWebViewWidget]. + AndroidWebViewWidget(PlatformWebViewWidgetCreationParams params) + : super.implementation( + params is AndroidWebViewWidgetCreationParams + ? params + : AndroidWebViewWidgetCreationParams + .fromPlatformWebViewWidgetCreationParams(params), + ); + + AndroidWebViewWidgetCreationParams get _androidParams => + params as AndroidWebViewWidgetCreationParams; + + @override + Widget build(BuildContext context) { + return AndroidView( + viewType: 'plugins.flutter.io/webview', + onPlatformViewCreated: (_) {}, + gestureRecognizers: _androidParams.gestureRecognizers, + layoutDirection: _androidParams.layoutDirection, + creationParams: _androidParams._instanceManager.getIdentifier( + (_androidParams.controller as AndroidWebViewController)._webView), + creationParamsCodec: const StandardMessageCodec(), + ); + } +} From 9917bbff8a606203cc462e11dd68b6ac3683b7a4 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 16 Nov 2022 19:46:22 +0100 Subject: [PATCH 3/4] Apply feedback from PR --- .../v4/android_webview_controller_test.dart | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/webview_flutter/webview_flutter_android/test/v4/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/v4/android_webview_controller_test.dart index a85e0a03189f..8ec4a4847d80 100644 --- a/packages/webview_flutter/webview_flutter_android/test/v4/android_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/v4/android_webview_controller_test.dart @@ -129,6 +129,24 @@ void main() { )).called(1); }); + test('loadFile without file prefix and characters to be escaped', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.settings).thenReturn(mockWebSettings); + + await controller.loadFile('/path/to/?_<_>_.html'); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify(mockWebView.loadUrl( + 'file:///path/to/%3F_%3C_%3E_.html', + {}, + )).called(1); + }); + test('loadFile with file prefix', () async { final MockWebView mockWebView = MockWebView(); final MockWebSettings mockWebSettings = MockWebSettings(); @@ -198,6 +216,30 @@ void main() { 'file:///android_asset/www/mock_file.html', {})); }); + test( + 'loadFlutterAsset when asset name contains characters that should be escaped', + () async { + final MockWebView mockWebView = MockWebView(); + final MockFlutterAssetManager mockAssetManager = + MockFlutterAssetManager(); + final AndroidWebViewController controller = createControllerWithMocks( + mockFlutterAssetManager: mockAssetManager, + mockWebView: mockWebView, + ); + + when(mockAssetManager.getAssetFilePathByName('mock_key')) + .thenAnswer((_) => Future.value('www/?_<_>_.html')); + when(mockAssetManager.list('www')).thenAnswer( + (_) => Future>.value(['?_<_>_.html'])); + + await controller.loadFlutterAsset('mock_key'); + + verify(mockAssetManager.getAssetFilePathByName('mock_key')).called(1); + verify(mockAssetManager.list('www')).called(1); + verify(mockWebView.loadUrl( + 'file:///android_asset/www/%3F_%3C_%3E_.html', {})); + }); + test('loadHtmlString without baseUrl', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( From 62d5149826dbe97d054730ad892b954747787ff0 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 17 Nov 2022 15:55:18 +0100 Subject: [PATCH 4/4] Remove AndroidWebViewWidget which was added to early --- .../v4/src/android_webview_controller.dart | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_controller.dart index 463afa5978a4..f1fbfff50ae0 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_controller.dart @@ -320,66 +320,3 @@ class AndroidJavaScriptChannelParams extends JavaScriptChannelParams { final android_webview.JavaScriptChannel _javaScriptChannel; } - -/// Object specifying creation parameters for creating a [AndroidWebViewWidget]. -/// -/// When adding additional fields make sure they can be null or have a default -/// value to avoid breaking changes. See [PlatformWebViewWidgetCreationParams] for -/// more information. -@immutable -class AndroidWebViewWidgetCreationParams - extends PlatformWebViewWidgetCreationParams { - /// Creates [AndroidWebWidgetCreationParams]. - AndroidWebViewWidgetCreationParams({ - super.key, - required super.controller, - super.layoutDirection, - super.gestureRecognizers, - @visibleForTesting InstanceManager? instanceManager, - }) : _instanceManager = instanceManager ?? JavaObject.globalInstanceManager; - - /// Constructs a [WebKitWebViewWidgetCreationParams] using a - /// [PlatformWebViewWidgetCreationParams]. - AndroidWebViewWidgetCreationParams.fromPlatformWebViewWidgetCreationParams( - PlatformWebViewWidgetCreationParams params, { - InstanceManager? instanceManager, - }) : this( - key: params.key, - controller: params.controller, - layoutDirection: params.layoutDirection, - gestureRecognizers: params.gestureRecognizers, - instanceManager: instanceManager, - ); - - // Maintains instances used to communicate with the native objects they - // represent. - final InstanceManager _instanceManager; -} - -/// An implementation of [PlatformWebViewWidget] with the Android WebView API. -class AndroidWebViewWidget extends PlatformWebViewWidget { - /// Constructs a [WebKitWebViewWidget]. - AndroidWebViewWidget(PlatformWebViewWidgetCreationParams params) - : super.implementation( - params is AndroidWebViewWidgetCreationParams - ? params - : AndroidWebViewWidgetCreationParams - .fromPlatformWebViewWidgetCreationParams(params), - ); - - AndroidWebViewWidgetCreationParams get _androidParams => - params as AndroidWebViewWidgetCreationParams; - - @override - Widget build(BuildContext context) { - return AndroidView( - viewType: 'plugins.flutter.io/webview', - onPlatformViewCreated: (_) {}, - gestureRecognizers: _androidParams.gestureRecognizers, - layoutDirection: _androidParams.layoutDirection, - creationParams: _androidParams._instanceManager.getIdentifier( - (_androidParams.controller as AndroidWebViewController)._webView), - creationParamsCodec: const StandardMessageCodec(), - ); - } -}