[webview_flutter_wkwebview] Implementation of WebKitWebViewController for WebKit (#6105)
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_proxy.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_proxy.dart new file mode 100644 index 0000000..48e6faf --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_proxy.dart
@@ -0,0 +1,47 @@ +// 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 '../../foundation/foundation.dart'; +import '../../web_kit/web_kit.dart'; + +/// Handles constructing objects and calling static methods for the WebKit +/// native library. +/// +/// This class provides dependency injection for the implementations of the +/// platform interface classes. Improving the ease of unit testing and/or +/// overriding the underlying WebKit classes. +/// +/// By default each function calls the default constructor of the WebKit class +/// it intends to return. +class WebKitProxy { + /// Constructs a [WebKitProxy]. + const WebKitProxy({ + this.createWebView = WKWebView.new, + this.createWebViewConfiguration = WKWebViewConfiguration.new, + this.createScriptMessageHandler = WKScriptMessageHandler.new, + }); + + /// Constructs a [WKWebView]. + final WKWebView Function( + WKWebViewConfiguration configuration, { + void Function( + String keyPath, + NSObject object, + Map<NSKeyValueChangeKey, Object?> change, + ) + observeValue, + }) createWebView; + + /// Constructs a [WKWebViewConfiguration]. + final WKWebViewConfiguration Function() createWebViewConfiguration; + + /// Constructs a [WKScriptMessageHandler]. + final WKScriptMessageHandler Function({ + required void Function( + WKUserContentController userContentController, + WKScriptMessage message, + ) + didReceiveScriptMessage, + }) createScriptMessageHandler; +}
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_webview_controller.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_webview_controller.dart new file mode 100644 index 0000000..9d76b5c --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_webview_controller.dart
@@ -0,0 +1,354 @@ +// 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/services.dart'; +import 'package:path/path.dart' as path; +import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; + +import '../../common/weak_reference_utils.dart'; +import '../../foundation/foundation.dart'; +import '../../web_kit/web_kit.dart'; +import 'webkit_proxy.dart'; + +/// Object specifying creation parameters for a [WebKitWebViewController]. +@immutable +class WebKitWebViewControllerCreationParams + extends PlatformWebViewControllerCreationParams { + /// Constructs a [WebKitWebViewControllerCreationParams]. + WebKitWebViewControllerCreationParams({ + @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), + }) : _configuration = webKitProxy.createWebViewConfiguration(); + + /// Constructs a [WebKitWebViewControllerCreationParams] using a + /// [PlatformWebViewControllerCreationParams]. + WebKitWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebViewControllerCreationParams params, { + @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), + }) : this(webKitProxy: webKitProxy); + + final WKWebViewConfiguration _configuration; +} + +/// An implementation of [PlatformWebViewController] with the WebKit api. +class WebKitWebViewController extends PlatformWebViewController { + /// Constructs a [WebKitWebViewController]. + WebKitWebViewController( + PlatformWebViewControllerCreationParams params, { + @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), + }) : super.implementation(params is WebKitWebViewControllerCreationParams + ? params + : WebKitWebViewControllerCreationParams + .fromPlatformWebViewControllerCreationParams(params)) { + _webView = webKitProxy.createWebView( + (params as WebKitWebViewControllerCreationParams)._configuration); + } + + late final WKWebView _webView; + + final Map<String, WebKitJavaScriptChannelParams> _javaScriptChannelParams = + <String, WebKitJavaScriptChannelParams>{}; + + bool _zoomEnabled = true; + + @override + Future<void> loadFile(String absoluteFilePath) { + return _webView.loadFileUrl( + absoluteFilePath, + readAccessUrl: path.dirname(absoluteFilePath), + ); + } + + @override + Future<void> loadFlutterAsset(String key) { + assert(key.isNotEmpty); + return _webView.loadFlutterAsset(key); + } + + @override + Future<void> loadHtmlString(String html, {String? baseUrl}) { + return _webView.loadHtmlString(html, baseUrl: baseUrl); + } + + @override + Future<void> loadRequest(LoadRequestParams params) { + if (!params.uri.hasScheme) { + throw ArgumentError( + 'LoadRequestParams#uri is required to have a scheme.', + ); + } + + return _webView.loadRequest(NSUrlRequest( + url: params.uri.toString(), + allHttpHeaderFields: params.headers, + httpMethod: describeEnum(params.method), + httpBody: params.body, + )); + } + + @override + Future<void> addJavaScriptChannel( + JavaScriptChannelParams javaScriptChannelParams, + ) { + final WebKitJavaScriptChannelParams webKitParams = + javaScriptChannelParams is WebKitJavaScriptChannelParams + ? javaScriptChannelParams + : WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( + javaScriptChannelParams); + + _javaScriptChannelParams[webKitParams.name] = webKitParams; + + final String wrapperSource = + 'window.${webKitParams.name} = webkit.messageHandlers.${webKitParams.name};'; + final WKUserScript wrapperScript = WKUserScript( + wrapperSource, + WKUserScriptInjectionTime.atDocumentStart, + isMainFrameOnly: false, + ); + _webView.configuration.userContentController.addUserScript(wrapperScript); + return _webView.configuration.userContentController.addScriptMessageHandler( + webKitParams._messageHandler, + webKitParams.name, + ); + } + + @override + Future<void> removeJavaScriptChannel(String javaScriptChannelName) async { + assert(javaScriptChannelName.isNotEmpty); + if (!_javaScriptChannelParams.containsKey(javaScriptChannelName)) { + return; + } + await _resetUserScripts(removedJavaScriptChannel: javaScriptChannelName); + } + + @override + Future<String?> currentUrl() => _webView.getUrl(); + + @override + Future<bool> canGoBack() => _webView.canGoBack(); + + @override + Future<bool> canGoForward() => _webView.canGoForward(); + + @override + Future<void> goBack() => _webView.goBack(); + + @override + Future<void> goForward() => _webView.goForward(); + + @override + Future<void> reload() => _webView.reload(); + + @override + Future<void> clearCache() { + return _webView.configuration.websiteDataStore.removeDataOfTypes( + <WKWebsiteDataType>{ + WKWebsiteDataType.memoryCache, + WKWebsiteDataType.diskCache, + WKWebsiteDataType.offlineWebApplicationCache, + }, + DateTime.fromMillisecondsSinceEpoch(0), + ); + } + + @override + Future<void> clearLocalStorage() { + return _webView.configuration.websiteDataStore.removeDataOfTypes( + <WKWebsiteDataType>{WKWebsiteDataType.localStorage}, + DateTime.fromMillisecondsSinceEpoch(0), + ); + } + + @override + Future<void> runJavaScript(String javaScript) async { + try { + await _webView.evaluateJavaScript(javaScript); + } on PlatformException catch (exception) { + // WebKit will throw an error when the type of the evaluated value is + // unsupported. This also goes for `null` and `undefined` on iOS 14+. For + // example, when running a void function. For ease of use, this specific + // error is ignored when no return value is expected. + if (exception.details is! NSError || + exception.details.code != + WKErrorCode.javaScriptResultTypeIsUnsupported) { + rethrow; + } + } + } + + @override + Future<String> runJavaScriptReturningResult(String javaScript) async { + final Object? result = await _webView.evaluateJavaScript(javaScript); + if (result == null) { + throw ArgumentError( + 'Result of JavaScript execution returned a `null` value. ' + 'Use `runJavascript` when expecting a null return value.', + ); + } + return result.toString(); + } + + /// Controls whether inline playback of HTML5 videos is allowed. + Future<void> setAllowsInlineMediaPlayback(bool allow) { + return _webView.configuration.setAllowsInlineMediaPlayback(allow); + } + + @override + Future<String?> getTitle() => _webView.getTitle(); + + @override + Future<void> scrollTo(int x, int y) { + return _webView.scrollView.setContentOffset(Point<double>( + x.toDouble(), + y.toDouble(), + )); + } + + @override + Future<void> scrollBy(int x, int y) { + return _webView.scrollView.scrollBy(Point<double>( + x.toDouble(), + y.toDouble(), + )); + } + + @override + Future<Point<int>> getScrollPosition() async { + final Point<double> offset = await _webView.scrollView.getContentOffset(); + return Point<int>(offset.x.round(), offset.y.round()); + } + + // TODO(bparrishMines): This is unique to iOS. Override should be removed if + // this is removed from the platform interface before webview_flutter version + // 4.0.0. + @override + Future<void> enableGestureNavigation(bool enabled) { + return _webView.setAllowsBackForwardNavigationGestures(enabled); + } + + @override + Future<void> setBackgroundColor(Color color) { + return Future.wait(<Future<void>>[ + _webView.scrollView.setBackgroundColor(color), + _webView.setOpaque(false), + _webView.setBackgroundColor(Colors.transparent), + ]); + } + + @override + Future<void> setJavaScriptMode(JavaScriptMode javaScriptMode) { + switch (javaScriptMode) { + case JavaScriptMode.disabled: + return _webView.configuration.preferences.setJavaScriptEnabled(false); + case JavaScriptMode.unrestricted: + return _webView.configuration.preferences.setJavaScriptEnabled(true); + } + } + + @override + Future<void> setUserAgent(String? userAgent) { + return _webView.setCustomUserAgent(userAgent); + } + + @override + Future<void> enableZoom(bool enabled) async { + if (_zoomEnabled == enabled) { + return; + } + + _zoomEnabled = enabled; + if (enabled) { + await _resetUserScripts(); + } else { + await _disableZoom(); + } + } + + Future<void> _disableZoom() { + const WKUserScript userScript = WKUserScript( + "var meta = document.createElement('meta');\n" + "meta.name = 'viewport';\n" + "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, " + "user-scalable=no';\n" + "var head = document.getElementsByTagName('head')[0];head.appendChild(meta);", + WKUserScriptInjectionTime.atDocumentEnd, + isMainFrameOnly: true, + ); + return _webView.configuration.userContentController + .addUserScript(userScript); + } + + // WKWebView does not support removing a single user script, so all user + // scripts and all message handlers are removed instead. And the JavaScript + // channels that shouldn't be removed are re-registered. Note that this + // workaround could interfere with exposing support for custom scripts from + // applications. + Future<void> _resetUserScripts({String? removedJavaScriptChannel}) async { + _webView.configuration.userContentController.removeAllUserScripts(); + // TODO(bparrishMines): This can be replaced with + // `removeAllScriptMessageHandlers` once Dart supports runtime version + // checking. (e.g. The equivalent to @availability in Objective-C.) + _javaScriptChannelParams.keys.forEach( + _webView.configuration.userContentController.removeScriptMessageHandler, + ); + + _javaScriptChannelParams.remove(removedJavaScriptChannel); + + await Future.wait(<Future<void>>[ + for (JavaScriptChannelParams params in _javaScriptChannelParams.values) + addJavaScriptChannel(params), + // Zoom is disabled with a WKUserScript, so this adds it back if it was + // removed above. + if (!_zoomEnabled) _disableZoom(), + ]); + } +} + +/// An implementation of [JavaScriptChannelParams] with the WebKit api. +/// +/// See [WebKitWebViewController.addJavaScriptChannel]. +@immutable +class WebKitJavaScriptChannelParams extends JavaScriptChannelParams { + /// Constructs a [WebKitJavaScriptChannelParams]. + WebKitJavaScriptChannelParams({ + required super.name, + required super.onMessageReceived, + @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), + }) : assert(name.isNotEmpty), + _messageHandler = webKitProxy.createScriptMessageHandler( + didReceiveScriptMessage: withWeakRefenceTo( + onMessageReceived, + (WeakReference<void Function(JavaScriptMessage)> weakReference) { + return ( + WKUserContentController controller, + WKScriptMessage message, + ) { + if (weakReference.target != null) { + weakReference.target!( + JavaScriptMessage(message: message.body!.toString()), + ); + } + }; + }, + ), + ); + + /// Constructs a [WebKitJavaScriptChannelParams] using a + /// [JavaScriptChannelParams]. + WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( + JavaScriptChannelParams params, { + @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), + }) : this( + name: params.name, + onMessageReceived: params.onMessageReceived, + webKitProxy: webKitProxy, + ); + + final WKScriptMessageHandler _messageHandler; +}
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_webview_platform.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_webview_platform.dart new file mode 100644 index 0000000..b808086 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_webview_platform.dart
@@ -0,0 +1,17 @@ +// 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 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; + +import 'webkit_webview_controller.dart'; + +/// Implementation of [WebViewPlatform] using the WebKit Api. +class WebKitWebViewPlatform extends WebViewPlatform { + @override + WebKitWebViewController createPlatformWebViewController( + PlatformWebViewControllerCreationParams params, + ) { + return WebKitWebViewController(params); + } +}
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/webview_flutter_wkwebview.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/webview_flutter_wkwebview.dart new file mode 100644 index 0000000..2a593fb --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/webview_flutter_wkwebview.dart
@@ -0,0 +1,7 @@ +// 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. + +library webview_flutter_wkwebview; + +export 'src/webkit_webview_controller.dart';
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart new file mode 100644 index 0000000..cf5da17 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart
@@ -0,0 +1,746 @@ +// 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'; +// 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/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart'; +import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart'; +import 'package:webview_flutter_wkwebview/src/v4/src/webkit_proxy.dart'; +import 'package:webview_flutter_wkwebview/src/v4/webview_flutter_wkwebview.dart'; +import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; + +import 'webkit_webview_controller_test.mocks.dart'; + +@GenerateMocks(<Type>[ + UIScrollView, + WKPreferences, + WKUserContentController, + WKWebsiteDataStore, + WKWebView, + WKWebViewConfiguration, +]) +void main() { + WidgetsFlutterBinding.ensureInitialized(); + + group('WebKitWebViewController', () { + WebKitWebViewController createControllerWithMocks({ + MockUIScrollView? mockScrollView, + MockWKPreferences? mockPreferences, + MockWKUserContentController? mockUserContentController, + MockWKWebsiteDataStore? mockWebsiteDataStore, + MockWKWebView Function( + WKWebViewConfiguration configuration, { + void Function( + String keyPath, + NSObject object, + Map<NSKeyValueChangeKey, Object?> change, + )? + observeValue, + })? + createMockWebView, + MockWKWebViewConfiguration? mockWebViewConfiguration, + }) { + final MockWKWebViewConfiguration nonNullMockWebViewConfiguration = + mockWebViewConfiguration ?? MockWKWebViewConfiguration(); + late final MockWKWebView nonNullMockWebView; + + final PlatformWebViewControllerCreationParams controllerCreationParams = + WebKitWebViewControllerCreationParams( + webKitProxy: WebKitProxy( + createWebViewConfiguration: () => nonNullMockWebViewConfiguration, + ), + ); + + final WebKitWebViewController controller = WebKitWebViewController( + controllerCreationParams, + webKitProxy: WebKitProxy( + createWebView: ( + _, { + void Function( + String keyPath, + NSObject object, + Map<NSKeyValueChangeKey, Object?> change, + )? + observeValue, + }) { + nonNullMockWebView = createMockWebView == null + ? MockWKWebView() + : createMockWebView( + nonNullMockWebViewConfiguration, + observeValue: observeValue, + ); + return nonNullMockWebView; + }, + ), + ); + + when(nonNullMockWebView.scrollView) + .thenReturn(mockScrollView ?? MockUIScrollView()); + when(nonNullMockWebView.configuration) + .thenReturn(nonNullMockWebViewConfiguration); + + when(nonNullMockWebViewConfiguration.preferences) + .thenReturn(mockPreferences ?? MockWKPreferences()); + when(nonNullMockWebViewConfiguration.userContentController).thenReturn( + mockUserContentController ?? MockWKUserContentController()); + when(nonNullMockWebViewConfiguration.websiteDataStore) + .thenReturn(mockWebsiteDataStore ?? MockWKWebsiteDataStore()); + + return controller; + } + + test('loadFile', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + await controller.loadFile('/path/to/file.html'); + verify(mockWebView.loadFileUrl( + '/path/to/file.html', + readAccessUrl: '/path/to', + )); + }); + + test('loadFlutterAsset', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + await controller.loadFlutterAsset('test_assets/index.html'); + verify(mockWebView.loadFlutterAsset('test_assets/index.html')); + }); + + test('loadHtmlString', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + const String htmlString = '<html><body>Test data.</body></html>'; + await controller.loadHtmlString(htmlString, baseUrl: 'baseUrl'); + + verify(mockWebView.loadHtmlString( + '<html><body>Test data.</body></html>', + baseUrl: 'baseUrl', + )); + }); + + group('loadRequest', () { + test('Throws ArgumentError for empty scheme', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + expect( + () async => await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse('www.google.com'), + method: LoadRequestMethod.get, + headers: const <String, String>{}, + ), + ), + throwsA(isA<ArgumentError>()), + ); + }); + + test('GET without headers', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse('https://www.google.com'), + method: LoadRequestMethod.get, + headers: const <String, String>{}, + ), + ); + + final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) + .captured + .single as NSUrlRequest; + expect(request.url, 'https://www.google.com'); + expect(request.allHttpHeaderFields, <String, String>{}); + expect(request.httpMethod, 'get'); + }); + + test('GET with headers', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse('https://www.google.com'), + method: LoadRequestMethod.get, + headers: const <String, String>{'a': 'header'}, + ), + ); + + final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) + .captured + .single as NSUrlRequest; + expect(request.url, 'https://www.google.com'); + expect(request.allHttpHeaderFields, <String, String>{'a': 'header'}); + expect(request.httpMethod, 'get'); + }); + + test('POST without body', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + await controller.loadRequest(LoadRequestParams( + uri: Uri.parse('https://www.google.com'), + method: LoadRequestMethod.post, + headers: const <String, String>{}, + )); + + final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) + .captured + .single as NSUrlRequest; + expect(request.url, 'https://www.google.com'); + expect(request.httpMethod, 'post'); + }); + + test('POST with body', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + await controller.loadRequest(LoadRequestParams( + uri: Uri.parse('https://www.google.com'), + method: LoadRequestMethod.post, + body: Uint8List.fromList('Test Body'.codeUnits), + headers: const <String, String>{}, + )); + + final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) + .captured + .single as NSUrlRequest; + expect(request.url, 'https://www.google.com'); + expect(request.httpMethod, 'post'); + expect( + request.httpBody, + Uint8List.fromList('Test Body'.codeUnits), + ); + }); + }); + + test('canGoBack', () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + when(mockWebView.canGoBack()).thenAnswer( + (_) => Future<bool>.value(false), + ); + expect(controller.canGoBack(), completion(false)); + }); + + test('canGoForward', () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + when(mockWebView.canGoForward()).thenAnswer( + (_) => Future<bool>.value(true), + ); + expect(controller.canGoForward(), completion(true)); + }); + + test('goBack', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + await controller.goBack(); + verify(mockWebView.goBack()); + }); + + test('goForward', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + await controller.goForward(); + verify(mockWebView.goForward()); + }); + + test('reload', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + await controller.reload(); + verify(mockWebView.reload()); + }); + + test('enableGestureNavigation', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + await controller.enableGestureNavigation(true); + verify(mockWebView.setAllowsBackForwardNavigationGestures(true)); + }); + + test('runJavaScriptReturningResult', () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + (_) => Future<String>.value('returnString'), + ); + expect( + controller.runJavaScriptReturningResult('runJavaScript'), + completion('returnString'), + ); + }); + + test('runJavaScriptReturningResult throws error on null return value', () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + (_) => Future<String?>.value(null), + ); + expect( + () => controller.runJavaScriptReturningResult('runJavaScript'), + throwsArgumentError, + ); + }); + + test('runJavaScript', () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + (_) => Future<String>.value('returnString'), + ); + expect( + controller.runJavaScript('runJavaScript'), + completes, + ); + }); + + test('runJavaScript ignores exception with unsupported javaScript type', + () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + when(mockWebView.evaluateJavaScript('runJavaScript')) + .thenThrow(PlatformException( + code: '', + details: const NSError( + code: WKErrorCode.javaScriptResultTypeIsUnsupported, + domain: '', + localizedDescription: '', + ), + )); + expect( + controller.runJavaScript('runJavaScript'), + completes, + ); + }); + + test('getTitle', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + when(mockWebView.getTitle()) + .thenAnswer((_) => Future<String>.value('Web Title')); + expect(controller.getTitle(), completion('Web Title')); + }); + + test('currentUrl', () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + when(mockWebView.getUrl()) + .thenAnswer((_) => Future<String>.value('myUrl.com')); + expect(controller.currentUrl(), completion('myUrl.com')); + }); + + test('scrollTo', () async { + final MockUIScrollView mockScrollView = MockUIScrollView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockScrollView: mockScrollView, + ); + + await controller.scrollTo(2, 4); + verify(mockScrollView.setContentOffset(const Point<double>(2.0, 4.0))); + }); + + test('scrollBy', () async { + final MockUIScrollView mockScrollView = MockUIScrollView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockScrollView: mockScrollView, + ); + + await controller.scrollBy(2, 4); + verify(mockScrollView.scrollBy(const Point<double>(2.0, 4.0))); + }); + + test('getScrollPosition', () { + final MockUIScrollView mockScrollView = MockUIScrollView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockScrollView: mockScrollView, + ); + + when(mockScrollView.getContentOffset()).thenAnswer( + (_) => Future<Point<double>>.value(const Point<double>(8.0, 16.0)), + ); + expect( + controller.getScrollPosition(), + completion(const Point<double>(8.0, 16.0)), + ); + }); + + test('disable zoom', () async { + final MockWKUserContentController mockUserContentController = + MockWKUserContentController(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockUserContentController: mockUserContentController, + ); + + await controller.enableZoom(false); + + final WKUserScript zoomScript = + verify(mockUserContentController.addUserScript(captureAny)) + .captured + .first as WKUserScript; + expect(zoomScript.isMainFrameOnly, isTrue); + expect(zoomScript.injectionTime, WKUserScriptInjectionTime.atDocumentEnd); + expect( + zoomScript.source, + "var meta = document.createElement('meta');\n" + "meta.name = 'viewport';\n" + "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, " + "user-scalable=no';\n" + "var head = document.getElementsByTagName('head')[0];head.appendChild(meta);", + ); + }); + + test('setBackgroundColor', () async { + final MockWKWebView mockWebView = MockWKWebView(); + final MockUIScrollView mockScrollView = MockUIScrollView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + mockScrollView: mockScrollView, + ); + + controller.setBackgroundColor(Colors.red); + + verify(mockWebView.setOpaque(false)); + verify(mockWebView.setBackgroundColor(Colors.transparent)); + verify(mockScrollView.setBackgroundColor(Colors.red)); + }); + + test('userAgent', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + ); + + await controller.setUserAgent('MyUserAgent'); + verify(mockWebView.setCustomUserAgent('MyUserAgent')); + }); + + test('enable JavaScript', () async { + final MockWKPreferences mockPreferences = MockWKPreferences(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockPreferences: mockPreferences, + ); + + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + + verify(mockPreferences.setJavaScriptEnabled(true)); + }); + + test('disable JavaScript', () async { + final MockWKPreferences mockPreferences = MockWKPreferences(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockPreferences: mockPreferences, + ); + + await controller.setJavaScriptMode(JavaScriptMode.disabled); + + verify(mockPreferences.setJavaScriptEnabled(false)); + }); + + test('clearCache', () { + final MockWKWebsiteDataStore mockWebsiteDataStore = + MockWKWebsiteDataStore(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebsiteDataStore: mockWebsiteDataStore, + ); + when( + mockWebsiteDataStore.removeDataOfTypes( + <WKWebsiteDataType>{ + WKWebsiteDataType.memoryCache, + WKWebsiteDataType.diskCache, + WKWebsiteDataType.offlineWebApplicationCache, + }, + DateTime.fromMillisecondsSinceEpoch(0), + ), + ).thenAnswer((_) => Future<bool>.value(false)); + + expect(controller.clearCache(), completes); + }); + + test('clearLocalStorage', () { + final MockWKWebsiteDataStore mockWebsiteDataStore = + MockWKWebsiteDataStore(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebsiteDataStore: mockWebsiteDataStore, + ); + when( + mockWebsiteDataStore.removeDataOfTypes( + <WKWebsiteDataType>{WKWebsiteDataType.localStorage}, + DateTime.fromMillisecondsSinceEpoch(0), + ), + ).thenAnswer((_) => Future<bool>.value(false)); + + expect(controller.clearLocalStorage(), completes); + }); + + test('addJavaScriptChannel', () async { + final WebKitProxy webKitProxy = WebKitProxy( + createScriptMessageHandler: ({ + required void Function( + WKUserContentController userContentController, + WKScriptMessage message, + ) + didReceiveScriptMessage, + }) { + return WKScriptMessageHandler.detached( + didReceiveScriptMessage: didReceiveScriptMessage, + ); + }, + ); + + final WebKitJavaScriptChannelParams javaScriptChannelParams = + WebKitJavaScriptChannelParams( + name: 'name', + onMessageReceived: (JavaScriptMessage message) {}, + webKitProxy: webKitProxy, + ); + + final MockWKUserContentController mockUserContentController = + MockWKUserContentController(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockUserContentController: mockUserContentController, + ); + + await controller.addJavaScriptChannel(javaScriptChannelParams); + verify(mockUserContentController.addScriptMessageHandler( + argThat(isA<WKScriptMessageHandler>()), + 'name', + )); + + final WKUserScript userScript = + verify(mockUserContentController.addUserScript(captureAny)) + .captured + .single as WKUserScript; + expect(userScript.source, 'window.name = webkit.messageHandlers.name;'); + expect( + userScript.injectionTime, + WKUserScriptInjectionTime.atDocumentStart, + ); + }); + + test('removeJavaScriptChannel', () async { + final WebKitProxy webKitProxy = WebKitProxy( + createScriptMessageHandler: ({ + required void Function( + WKUserContentController userContentController, + WKScriptMessage message, + ) + didReceiveScriptMessage, + }) { + return WKScriptMessageHandler.detached( + didReceiveScriptMessage: didReceiveScriptMessage, + ); + }, + ); + + final WebKitJavaScriptChannelParams javaScriptChannelParams = + WebKitJavaScriptChannelParams( + name: 'name', + onMessageReceived: (JavaScriptMessage message) {}, + webKitProxy: webKitProxy, + ); + + final MockWKUserContentController mockUserContentController = + MockWKUserContentController(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockUserContentController: mockUserContentController, + ); + + await controller.addJavaScriptChannel(javaScriptChannelParams); + reset(mockUserContentController); + + await controller.removeJavaScriptChannel('name'); + + verify(mockUserContentController.removeAllUserScripts()); + verify(mockUserContentController.removeScriptMessageHandler('name')); + + verifyNoMoreInteractions(mockUserContentController); + }); + + test('removeJavaScriptChannel with zoom disabled', () async { + final WebKitProxy webKitProxy = WebKitProxy( + createScriptMessageHandler: ({ + required void Function( + WKUserContentController userContentController, + WKScriptMessage message, + ) + didReceiveScriptMessage, + }) { + return WKScriptMessageHandler.detached( + didReceiveScriptMessage: didReceiveScriptMessage, + ); + }, + ); + + final WebKitJavaScriptChannelParams javaScriptChannelParams = + WebKitJavaScriptChannelParams( + name: 'name', + onMessageReceived: (JavaScriptMessage message) {}, + webKitProxy: webKitProxy, + ); + + final MockWKUserContentController mockUserContentController = + MockWKUserContentController(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockUserContentController: mockUserContentController, + ); + + await controller.enableZoom(false); + await controller.addJavaScriptChannel(javaScriptChannelParams); + clearInteractions(mockUserContentController); + await controller.removeJavaScriptChannel('name'); + + final WKUserScript zoomScript = + verify(mockUserContentController.addUserScript(captureAny)) + .captured + .first as WKUserScript; + expect(zoomScript.isMainFrameOnly, isTrue); + expect(zoomScript.injectionTime, WKUserScriptInjectionTime.atDocumentEnd); + expect( + zoomScript.source, + "var meta = document.createElement('meta');\n" + "meta.name = 'viewport';\n" + "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, " + "user-scalable=no';\n" + "var head = document.getElementsByTagName('head')[0];head.appendChild(meta);", + ); + }); + }); + + group('WebKitJavaScriptChannelParams', () { + test('onMessageReceived', () async { + late final WKScriptMessageHandler messageHandler; + + final WebKitProxy webKitProxy = WebKitProxy( + createScriptMessageHandler: ({ + required void Function( + WKUserContentController userContentController, + WKScriptMessage message, + ) + didReceiveScriptMessage, + }) { + messageHandler = WKScriptMessageHandler.detached( + didReceiveScriptMessage: didReceiveScriptMessage, + ); + return messageHandler; + }, + ); + + late final String callbackMessage; + WebKitJavaScriptChannelParams( + name: 'name', + onMessageReceived: (JavaScriptMessage message) { + callbackMessage = message.message; + }, + webKitProxy: webKitProxy, + ); + + messageHandler.didReceiveScriptMessage( + MockWKUserContentController(), + const WKScriptMessage(name: 'name', body: 'myMessage'), + ); + + expect(callbackMessage, 'myMessage'); + }); + }); +}
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.mocks.dart new file mode 100644 index 0000000..6138fdd --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.mocks.dart
@@ -0,0 +1,414 @@ +// Mocks generated by Mockito 5.2.0 from annotations +// in webview_flutter_wkwebview/example/ios/.symlinks/plugins/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart. +// Do not manually edit this file. + +import 'dart:async' as _i5; +import 'dart:math' as _i2; +import 'dart:ui' as _i6; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart' + as _i7; +import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart' as _i3; +import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart' as _i4; + +// 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 + +class _FakePoint_0<T extends num> extends _i1.Fake implements _i2.Point<T> {} + +class _FakeUIScrollView_1 extends _i1.Fake implements _i3.UIScrollView {} + +class _FakeWKPreferences_2 extends _i1.Fake implements _i4.WKPreferences {} + +class _FakeWKUserContentController_3 extends _i1.Fake + implements _i4.WKUserContentController {} + +class _FakeWKHttpCookieStore_4 extends _i1.Fake + implements _i4.WKHttpCookieStore {} + +class _FakeWKWebsiteDataStore_5 extends _i1.Fake + implements _i4.WKWebsiteDataStore {} + +class _FakeWKWebViewConfiguration_6 extends _i1.Fake + implements _i4.WKWebViewConfiguration {} + +class _FakeWKWebView_7 extends _i1.Fake implements _i4.WKWebView {} + +/// A class which mocks [UIScrollView]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockUIScrollView extends _i1.Mock implements _i3.UIScrollView { + MockUIScrollView() { + _i1.throwOnMissingStub(this); + } + + @override + _i5.Future<_i2.Point<double>> getContentOffset() => (super.noSuchMethod( + Invocation.method(#getContentOffset, []), + returnValue: Future<_i2.Point<double>>.value(_FakePoint_0<double>())) + as _i5.Future<_i2.Point<double>>); + @override + _i5.Future<void> scrollBy(_i2.Point<double>? offset) => + (super.noSuchMethod(Invocation.method(#scrollBy, [offset]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> setContentOffset(_i2.Point<double>? offset) => + (super.noSuchMethod(Invocation.method(#setContentOffset, [offset]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i3.UIScrollView copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeUIScrollView_1()) as _i3.UIScrollView); + @override + _i5.Future<void> setBackgroundColor(_i6.Color? color) => + (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> setOpaque(bool? opaque) => + (super.noSuchMethod(Invocation.method(#setOpaque, [opaque]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> addObserver(_i7.NSObject? observer, + {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> removeObserver(_i7.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); +} + +/// A class which mocks [WKPreferences]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockWKPreferences extends _i1.Mock implements _i4.WKPreferences { + MockWKPreferences() { + _i1.throwOnMissingStub(this); + } + + @override + _i5.Future<void> setJavaScriptEnabled(bool? enabled) => + (super.noSuchMethod(Invocation.method(#setJavaScriptEnabled, [enabled]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i4.WKPreferences copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeWKPreferences_2()) as _i4.WKPreferences); + @override + _i5.Future<void> addObserver(_i7.NSObject? observer, + {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> removeObserver(_i7.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); +} + +/// A class which mocks [WKUserContentController]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockWKUserContentController extends _i1.Mock + implements _i4.WKUserContentController { + MockWKUserContentController() { + _i1.throwOnMissingStub(this); + } + + @override + _i5.Future<void> addScriptMessageHandler( + _i4.WKScriptMessageHandler? handler, String? name) => + (super.noSuchMethod( + Invocation.method(#addScriptMessageHandler, [handler, name]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> removeScriptMessageHandler(String? name) => (super + .noSuchMethod(Invocation.method(#removeScriptMessageHandler, [name]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> removeAllScriptMessageHandlers() => (super.noSuchMethod( + Invocation.method(#removeAllScriptMessageHandlers, []), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> addUserScript(_i4.WKUserScript? userScript) => + (super.noSuchMethod(Invocation.method(#addUserScript, [userScript]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> removeAllUserScripts() => + (super.noSuchMethod(Invocation.method(#removeAllUserScripts, []), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i4.WKUserContentController copy() => + (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeWKUserContentController_3()) + as _i4.WKUserContentController); + @override + _i5.Future<void> addObserver(_i7.NSObject? observer, + {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> removeObserver(_i7.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); +} + +/// A class which mocks [WKWebsiteDataStore]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockWKWebsiteDataStore extends _i1.Mock + implements _i4.WKWebsiteDataStore { + MockWKWebsiteDataStore() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.WKHttpCookieStore get httpCookieStore => + (super.noSuchMethod(Invocation.getter(#httpCookieStore), + returnValue: _FakeWKHttpCookieStore_4()) as _i4.WKHttpCookieStore); + @override + _i5.Future<bool> removeDataOfTypes( + Set<_i4.WKWebsiteDataType>? dataTypes, DateTime? since) => + (super.noSuchMethod( + Invocation.method(#removeDataOfTypes, [dataTypes, since]), + returnValue: Future<bool>.value(false)) as _i5.Future<bool>); + @override + _i4.WKWebsiteDataStore copy() => + (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeWKWebsiteDataStore_5()) as _i4.WKWebsiteDataStore); + @override + _i5.Future<void> addObserver(_i7.NSObject? observer, + {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> removeObserver(_i7.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); +} + +/// A class which mocks [WKWebView]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockWKWebView extends _i1.Mock implements _i4.WKWebView { + MockWKWebView() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.WKWebViewConfiguration get configuration => + (super.noSuchMethod(Invocation.getter(#configuration), + returnValue: _FakeWKWebViewConfiguration_6()) + as _i4.WKWebViewConfiguration); + @override + _i3.UIScrollView get scrollView => + (super.noSuchMethod(Invocation.getter(#scrollView), + returnValue: _FakeUIScrollView_1()) as _i3.UIScrollView); + @override + _i5.Future<void> setUIDelegate(_i4.WKUIDelegate? delegate) => + (super.noSuchMethod(Invocation.method(#setUIDelegate, [delegate]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> setNavigationDelegate(_i4.WKNavigationDelegate? delegate) => + (super.noSuchMethod(Invocation.method(#setNavigationDelegate, [delegate]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<String?> getUrl() => + (super.noSuchMethod(Invocation.method(#getUrl, []), + returnValue: Future<String?>.value()) as _i5.Future<String?>); + @override + _i5.Future<double> getEstimatedProgress() => + (super.noSuchMethod(Invocation.method(#getEstimatedProgress, []), + returnValue: Future<double>.value(0.0)) as _i5.Future<double>); + @override + _i5.Future<void> loadRequest(_i7.NSUrlRequest? request) => + (super.noSuchMethod(Invocation.method(#loadRequest, [request]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> loadHtmlString(String? string, {String? baseUrl}) => + (super.noSuchMethod( + Invocation.method(#loadHtmlString, [string], {#baseUrl: baseUrl}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> loadFileUrl(String? url, {String? readAccessUrl}) => + (super.noSuchMethod( + Invocation.method( + #loadFileUrl, [url], {#readAccessUrl: readAccessUrl}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> loadFlutterAsset(String? key) => + (super.noSuchMethod(Invocation.method(#loadFlutterAsset, [key]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<bool> canGoBack() => + (super.noSuchMethod(Invocation.method(#canGoBack, []), + returnValue: Future<bool>.value(false)) as _i5.Future<bool>); + @override + _i5.Future<bool> canGoForward() => + (super.noSuchMethod(Invocation.method(#canGoForward, []), + returnValue: Future<bool>.value(false)) as _i5.Future<bool>); + @override + _i5.Future<void> goBack() => + (super.noSuchMethod(Invocation.method(#goBack, []), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> goForward() => + (super.noSuchMethod(Invocation.method(#goForward, []), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> reload() => + (super.noSuchMethod(Invocation.method(#reload, []), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<String?> getTitle() => + (super.noSuchMethod(Invocation.method(#getTitle, []), + returnValue: Future<String?>.value()) as _i5.Future<String?>); + @override + _i5.Future<void> setAllowsBackForwardNavigationGestures(bool? allow) => + (super.noSuchMethod( + Invocation.method(#setAllowsBackForwardNavigationGestures, [allow]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> setCustomUserAgent(String? userAgent) => + (super.noSuchMethod(Invocation.method(#setCustomUserAgent, [userAgent]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<Object?> evaluateJavaScript(String? javaScriptString) => (super + .noSuchMethod(Invocation.method(#evaluateJavaScript, [javaScriptString]), + returnValue: Future<Object?>.value()) as _i5.Future<Object?>); + @override + _i4.WKWebView copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeWKWebView_7()) as _i4.WKWebView); + @override + _i5.Future<void> setBackgroundColor(_i6.Color? color) => + (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> setOpaque(bool? opaque) => + (super.noSuchMethod(Invocation.method(#setOpaque, [opaque]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> addObserver(_i7.NSObject? observer, + {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> removeObserver(_i7.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); +} + +/// A class which mocks [WKWebViewConfiguration]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockWKWebViewConfiguration extends _i1.Mock + implements _i4.WKWebViewConfiguration { + MockWKWebViewConfiguration() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.WKUserContentController get userContentController => + (super.noSuchMethod(Invocation.getter(#userContentController), + returnValue: _FakeWKUserContentController_3()) + as _i4.WKUserContentController); + @override + _i4.WKPreferences get preferences => + (super.noSuchMethod(Invocation.getter(#preferences), + returnValue: _FakeWKPreferences_2()) as _i4.WKPreferences); + @override + _i4.WKWebsiteDataStore get websiteDataStore => + (super.noSuchMethod(Invocation.getter(#websiteDataStore), + returnValue: _FakeWKWebsiteDataStore_5()) as _i4.WKWebsiteDataStore); + @override + _i5.Future<void> setAllowsInlineMediaPlayback(bool? allow) => (super + .noSuchMethod(Invocation.method(#setAllowsInlineMediaPlayback, [allow]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> setMediaTypesRequiringUserActionForPlayback( + Set<_i4.WKAudiovisualMediaType>? types) => + (super.noSuchMethod( + Invocation.method( + #setMediaTypesRequiringUserActionForPlayback, [types]), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i4.WKWebViewConfiguration copy() => + (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeWKWebViewConfiguration_6()) + as _i4.WKWebViewConfiguration); + @override + _i5.Future<void> addObserver(_i7.NSObject? observer, + {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); + @override + _i5.Future<void> removeObserver(_i7.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future<void>.value(), + returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>); +}