From 7957040cb4dadf259146fe052915b03d1617a09e Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 13 Jul 2022 11:39:28 -0700 Subject: [PATCH 01/19] implement controller --- .../src/v4/src/webkit_webview_controller.dart | 349 ++++++++++++++++++ .../src/v4/src/webkit_webview_platform.dart | 17 + .../lib/src/v4/webview_flutter_wkwebview.dart | 3 + 3 files changed, 369 insertions(+) create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_webview_controller.dart create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_webview_platform.dart create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/webview_flutter_wkwebview.dart 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 000000000000..d06cd8e5a602 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_webview_controller.dart @@ -0,0 +1,349 @@ +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 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; + +import '../../foundation/foundation.dart'; + +/// Object specifying creation parameters for a [WebKitWebViewController]. +@immutable +class WebKitWebViewControllerCreationParams + extends PlatformWebViewControllerCreationParams { + /// Constructs a [WebKitWebViewControllerCreationParams] using a + /// [PlatformWebViewControllerCreationParams]. + factory WebKitWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( + PlatformWebViewControllerCreationParams params, + ) { + return WebKitWebViewControllerCreationParams.fromNativeApi( + params, + configuration: WKWebViewConfiguration(), + ); + } + + /// Constructs a [WebKitWebViewControllerCreationParams] with classes from the + /// native api. + @visibleForTesting + const WebKitWebViewControllerCreationParams.fromNativeApi( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebViewControllerCreationParams params, { + required WKWebViewConfiguration configuration, + }) : _configuration = configuration; + + final WKWebViewConfiguration _configuration; +} + +/// An implementation of [PlatformWebViewController] with the WebKit api. +class WebKitWebViewController extends PlatformWebViewController { + /// Constructs a [WebKitWebViewController]. + WebKitWebViewController(WebKitWebViewControllerCreationParams params) + : this.fromNativeApi(params, webView: WKWebView(params._configuration)); + + /// Constructs a [WebKitWebViewController] with classes from the native api. + @visibleForTesting + WebKitWebViewController.fromNativeApi( + super.params, { + required WKWebView webView, + }) : _webView = webView, + super.implementation(); + + final WKWebView _webView; + + final Map _javaScriptChannelParams = + {}; + + bool _zoomEnabled = true; + + @override + Future loadFile(String absoluteFilePath) { + return _webView.loadFileUrl( + absoluteFilePath, + readAccessUrl: path.dirname(absoluteFilePath), + ); + } + + @override + Future loadFlutterAsset(String key) { + assert(key.isNotEmpty); + return _webView.loadFlutterAsset(key); + } + + @override + Future loadHtmlString(String html, {String? baseUrl}) { + return _webView.loadHtmlString(html, baseUrl: baseUrl); + } + + @override + Future 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 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 removeJavaScriptChannel(String javaScriptChannelName) { + assert(javaScriptChannelName.isNotEmpty); + return _resetUserScripts(removedJavaScriptChannel: javaScriptChannelName); + } + + @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() { + return _webView.configuration.websiteDataStore.removeDataOfTypes( + { + WKWebsiteDataType.memoryCache, + WKWebsiteDataType.diskCache, + WKWebsiteDataType.offlineWebApplicationCache, + }, + DateTime.fromMillisecondsSinceEpoch(0), + ); + } + + @override + Future clearLocalStorage() { + return _webView.configuration.websiteDataStore.removeDataOfTypes( + {WKWebsiteDataType.localStorage}, + DateTime.fromMillisecondsSinceEpoch(0), + ); + } + + @override + Future 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 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 setAllowsInlineMediaPlayback(bool allow) { + return _webView.configuration.setAllowsInlineMediaPlayback(allow); + } + + @override + Future getTitle() => _webView.getTitle(); + + @override + Future scrollTo(int x, int y) { + return _webView.scrollView.setContentOffset(Point( + x.toDouble(), + y.toDouble(), + )); + } + + @override + Future scrollBy(int x, int y) { + return _webView.scrollView.scrollBy(Point( + x.toDouble(), + y.toDouble(), + )); + } + + @override + Future> getScrollPosition() async { + final Point offset = await _webView.scrollView.getContentOffset(); + return Point(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 + // 2.0.0. + @override + Future enableGestureNavigation(bool enabled) { + return _webView.setAllowsBackForwardNavigationGestures(enabled); + } + + @override + Future setBackgroundColor(Color color) { + return Future.wait(>[ + _webView.setOpaque(false), + _webView.setBackgroundColor(Colors.transparent), + _webView.scrollView.setBackgroundColor(color), + ]); + } + + @override + Future 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 setUserAgent(String? userAgent) { + return _webView.setCustomUserAgent(userAgent); + } + + @override + Future enableZoom(bool enabled) async { + if (_zoomEnabled == enabled) { + return; + } + + _zoomEnabled = enabled; + if (enabled) { + await _resetUserScripts(); + } else { + await _disableZoom(); + } + } + + Future _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 _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(>[ + 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] using a + /// [JavaScriptChannelParams]. + factory WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( + JavaScriptChannelParams params, + ) { + final WeakReference + weakOnMessageReceived = WeakReference( + params.onMessageReceived, + ); + return WebKitJavaScriptChannelParams.fromNativeApi( + params, + messageHandler: WKScriptMessageHandler( + didReceiveScriptMessage: (_, WKScriptMessage message) { + if (weakOnMessageReceived.target != null) { + weakOnMessageReceived.target!( + JavaScriptMessage(message: message.body!.toString()), + ); + } + }, + ), + ); + } + + /// Constructs a [WebKitJavaScriptChannelParams] with classes from the native + /// api. + @visibleForTesting + WebKitJavaScriptChannelParams.fromNativeApi( + JavaScriptChannelParams params, { + required WKScriptMessageHandler messageHandler, + }) : _messageHandler = messageHandler, + super(name: params.name, onMessageReceived: params.onMessageReceived); + + 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 000000000000..696f7111ab0d --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_webview_platform.dart @@ -0,0 +1,17 @@ +import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; + +import 'webkit_webview_controller.dart'; + +class WebKitWebViewPlatform extends WebViewPlatform { + @override + WebKitWebViewController createPlatformWebViewController( + PlatformWebViewControllerCreationParams params, + ) { + return WebKitWebViewController( + params is WebKitWebViewControllerCreationParams + ? params + : WebKitWebViewControllerCreationParams + .fromPlatformWebViewControllerCreationParams(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 000000000000..d295d026c945 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/webview_flutter_wkwebview.dart @@ -0,0 +1,3 @@ +library webview_flutter_wkwebview; + +export 'src/webkit_webview_controller.dart'; From f3c3486f6f3ae6044edb372890e81be7226ea1b8 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 13 Jul 2022 12:39:35 -0700 Subject: [PATCH 02/19] use a proxy --- .../lib/src/v4/src/webkit_proxy.dart | 40 +++++++++ .../src/v4/src/webkit_webview_controller.dart | 81 +++++++------------ .../src/v4/src/webkit_webview_platform.dart | 1 + .../v4/webkit_webview_controller_test.dart | 6 ++ 4 files changed, 76 insertions(+), 52 deletions(-) create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_proxy.dart create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart 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 000000000000..7edde233d278 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_proxy.dart @@ -0,0 +1,40 @@ +import '../../foundation/foundation.dart'; +import '../../web_kit/web_kit.dart'; + +/// Handles constructing objects and calling static methods for the WebKit +/// native library. +class WebKitProxy { + /// Constructs a [WebKitProxy]. + const WebKitProxy(); + + /// Constructs a [WKWebView]. + WKWebView createWebView( + WKWebViewConfiguration configuration, { + void Function( + String keyPath, + NSObject object, + Map change, + )? + observeValue, + }) { + return WKWebView(configuration, observeValue: observeValue); + } + + /// Constructs a [WKWebViewConfiguration]. + WKWebViewConfiguration createWebViewConfiguration() { + return WKWebViewConfiguration(); + } + + /// Constructs a [WKScriptMessageHandler]. + WKScriptMessageHandler createScriptMessageHandler({ + required void Function( + WKUserContentController userContentController, + WKScriptMessage message, + ) + didReceiveScriptMessage, + }) { + return WKScriptMessageHandler( + didReceiveScriptMessage: didReceiveScriptMessage, + ); + } +} 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 index d06cd8e5a602..43b362836916 100644 --- 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 @@ -5,9 +5,11 @@ 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 'package:webview_flutter_wkwebview/src/common/weak_reference_utils.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; import '../../foundation/foundation.dart'; +import 'webkit_proxy.dart'; /// Object specifying creation parameters for a [WebKitWebViewController]. @immutable @@ -15,24 +17,12 @@ class WebKitWebViewControllerCreationParams extends PlatformWebViewControllerCreationParams { /// Constructs a [WebKitWebViewControllerCreationParams] using a /// [PlatformWebViewControllerCreationParams]. - factory WebKitWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( - PlatformWebViewControllerCreationParams params, - ) { - return WebKitWebViewControllerCreationParams.fromNativeApi( - params, - configuration: WKWebViewConfiguration(), - ); - } - - /// Constructs a [WebKitWebViewControllerCreationParams] with classes from the - /// native api. - @visibleForTesting - const WebKitWebViewControllerCreationParams.fromNativeApi( + WebKitWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( // Recommended placeholder to prevent being broken by platform interface. // ignore: avoid_unused_constructor_parameters PlatformWebViewControllerCreationParams params, { - required WKWebViewConfiguration configuration, - }) : _configuration = configuration; + @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), + }) : _configuration = webKitProxy.createWebViewConfiguration(); final WKWebViewConfiguration _configuration; } @@ -40,15 +30,10 @@ class WebKitWebViewControllerCreationParams /// An implementation of [PlatformWebViewController] with the WebKit api. class WebKitWebViewController extends PlatformWebViewController { /// Constructs a [WebKitWebViewController]. - WebKitWebViewController(WebKitWebViewControllerCreationParams params) - : this.fromNativeApi(params, webView: WKWebView(params._configuration)); - - /// Constructs a [WebKitWebViewController] with classes from the native api. - @visibleForTesting - WebKitWebViewController.fromNativeApi( - super.params, { - required WKWebView webView, - }) : _webView = webView, + WebKitWebViewController( + WebKitWebViewControllerCreationParams super.params, { + @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), + }) : _webView = webKitProxy.createWebView(params._configuration), super.implementation(); final WKWebView _webView; @@ -224,7 +209,7 @@ class WebKitWebViewController extends PlatformWebViewController { // TODO(bparrishMines): This is unique to iOS. Override should be removed if // this is removed from the platform interface before webview_flutter version - // 2.0.0. + // 4.0.0. @override Future enableGestureNavigation(bool enabled) { return _webView.setAllowsBackForwardNavigationGestures(enabled); @@ -315,34 +300,26 @@ class WebKitWebViewController extends PlatformWebViewController { class WebKitJavaScriptChannelParams extends JavaScriptChannelParams { /// Constructs a [WebKitJavaScriptChannelParams] using a /// [JavaScriptChannelParams]. - factory WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( - JavaScriptChannelParams params, - ) { - final WeakReference - weakOnMessageReceived = WeakReference( - params.onMessageReceived, - ); - return WebKitJavaScriptChannelParams.fromNativeApi( - params, - messageHandler: WKScriptMessageHandler( - didReceiveScriptMessage: (_, WKScriptMessage message) { - if (weakOnMessageReceived.target != null) { - weakOnMessageReceived.target!( - JavaScriptMessage(message: message.body!.toString()), - ); - } - }, - ), - ); - } - - /// Constructs a [WebKitJavaScriptChannelParams] with classes from the native - /// api. - @visibleForTesting - WebKitJavaScriptChannelParams.fromNativeApi( + WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( JavaScriptChannelParams params, { - required WKScriptMessageHandler messageHandler, - }) : _messageHandler = messageHandler, + @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), + }) : _messageHandler = webKitProxy.createScriptMessageHandler( + didReceiveScriptMessage: withWeakRefenceTo( + params.onMessageReceived, + (WeakReference weakReference) { + return ( + WKUserContentController controller, + WKScriptMessage message, + ) { + if (weakReference.target != null) { + weakReference.target!( + JavaScriptMessage(message: message.body!.toString()), + ); + } + }; + }, + ), + ), super(name: params.name, onMessageReceived: params.onMessageReceived); 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 index 696f7111ab0d..51485f4507b3 100644 --- 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 @@ -2,6 +2,7 @@ import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_i import 'webkit_webview_controller.dart'; +/// Implementation of [WebViewPlatform] using the WebKit Api. class WebKitWebViewPlatform extends WebViewPlatform { @override WebKitWebViewController createPlatformWebViewController( 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 000000000000..1ad2ab206cc0 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart @@ -0,0 +1,6 @@ +import 'package:mockito/annotations.dart'; +import 'package:webview_flutter_wkwebview/src/v4/src/webkit_proxy.dart'; +import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; + +@GenerateMocks([WKWebView, WKWebViewConfiguration, WebKitProxy]) +void main() {} From 29340b36243b8879ab1222c9b9b461290222d4a2 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 13 Jul 2022 14:19:55 -0700 Subject: [PATCH 03/19] first solid test --- .../lib/src/v4/src/webkit_proxy.dart | 26 +- .../src/v4/src/webkit_webview_controller.dart | 17 +- .../src/v4/src/webkit_webview_platform.dart | 7 +- .../v4/webkit_webview_controller_test.dart | 59 ++++- .../webkit_webview_controller_test.mocks.dart | 224 ++++++++++++++++++ 5 files changed, 304 insertions(+), 29 deletions(-) create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.mocks.dart 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 index 7edde233d278..6ff2546512f6 100644 --- 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 @@ -5,36 +5,32 @@ import '../../web_kit/web_kit.dart'; /// native library. class WebKitProxy { /// Constructs a [WebKitProxy]. - const WebKitProxy(); + const WebKitProxy({ + this.onCreateWebView = WKWebView.new, + this.onCreateWebViewConfiguration = WKWebViewConfiguration.new, + this.onCreateScriptMessageHandler = WKScriptMessageHandler.new, + }); /// Constructs a [WKWebView]. - WKWebView createWebView( + final WKWebView Function( WKWebViewConfiguration configuration, { void Function( String keyPath, NSObject object, Map change, - )? + ) observeValue, - }) { - return WKWebView(configuration, observeValue: observeValue); - } + }) onCreateWebView; /// Constructs a [WKWebViewConfiguration]. - WKWebViewConfiguration createWebViewConfiguration() { - return WKWebViewConfiguration(); - } + final WKWebViewConfiguration Function() onCreateWebViewConfiguration; /// Constructs a [WKScriptMessageHandler]. - WKScriptMessageHandler createScriptMessageHandler({ + final WKScriptMessageHandler Function({ required void Function( WKUserContentController userContentController, WKScriptMessage message, ) didReceiveScriptMessage, - }) { - return WKScriptMessageHandler( - didReceiveScriptMessage: didReceiveScriptMessage, - ); - } + }) onCreateScriptMessageHandler; } 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 index 43b362836916..6df810909761 100644 --- 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 @@ -22,7 +22,7 @@ class WebKitWebViewControllerCreationParams // ignore: avoid_unused_constructor_parameters PlatformWebViewControllerCreationParams params, { @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), - }) : _configuration = webKitProxy.createWebViewConfiguration(); + }) : _configuration = webKitProxy.onCreateWebViewConfiguration(); final WKWebViewConfiguration _configuration; } @@ -31,12 +31,17 @@ class WebKitWebViewControllerCreationParams class WebKitWebViewController extends PlatformWebViewController { /// Constructs a [WebKitWebViewController]. WebKitWebViewController( - WebKitWebViewControllerCreationParams super.params, { + PlatformWebViewControllerCreationParams params, { @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), - }) : _webView = webKitProxy.createWebView(params._configuration), - super.implementation(); + }) : super.implementation(params is WebKitWebViewControllerCreationParams + ? params + : WebKitWebViewControllerCreationParams + .fromPlatformWebViewControllerCreationParams(params)) { + _webView = webKitProxy.onCreateWebView( + (params as WebKitWebViewControllerCreationParams)._configuration); + } - final WKWebView _webView; + late final WKWebView _webView; final Map _javaScriptChannelParams = {}; @@ -303,7 +308,7 @@ class WebKitJavaScriptChannelParams extends JavaScriptChannelParams { WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( JavaScriptChannelParams params, { @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), - }) : _messageHandler = webKitProxy.createScriptMessageHandler( + }) : _messageHandler = webKitProxy.onCreateScriptMessageHandler( didReceiveScriptMessage: withWeakRefenceTo( params.onMessageReceived, (WeakReference weakReference) { 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 index 51485f4507b3..a38525d96a25 100644 --- 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 @@ -8,11 +8,6 @@ class WebKitWebViewPlatform extends WebViewPlatform { WebKitWebViewController createPlatformWebViewController( PlatformWebViewControllerCreationParams params, ) { - return WebKitWebViewController( - params is WebKitWebViewControllerCreationParams - ? params - : WebKitWebViewControllerCreationParams - .fromPlatformWebViewControllerCreationParams(params), - ); + return WebKitWebViewController(params); } } 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 index 1ad2ab206cc0..92a00d6cdc56 100644 --- 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 @@ -1,6 +1,61 @@ +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/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'; -@GenerateMocks([WKWebView, WKWebViewConfiguration, WebKitProxy]) -void main() {} +import 'webkit_webview_controller_test.mocks.dart'; + +@GenerateMocks([WKWebView, WKWebViewConfiguration]) +void main() { + group('WebKitWebViewController', () { + WebKitWebViewController createControllerWithMocks({ + MockWKWebView? mockWebView, + MockWKWebViewConfiguration? mockWebViewConfiguration, + }) { + final PlatformWebViewControllerCreationParams controllerCreationParams = + WebKitWebViewControllerCreationParams + .fromPlatformWebViewControllerCreationParams( + const PlatformWebViewControllerCreationParams(), + webKitProxy: WebKitProxy( + onCreateWebViewConfiguration: () => + mockWebViewConfiguration ?? MockWKWebViewConfiguration(), + ), + ); + + return WebKitWebViewController( + controllerCreationParams, + webKitProxy: WebKitProxy( + onCreateWebView: ( + _, { + void Function( + String keyPath, + NSObject object, + Map change, + )? + observeValue, + }) { + return mockWebView ?? MockWKWebView(); + }, + ), + ); + } + + test('loadFile', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.loadFile('/path/to/file.html'); + verify(mockWebView.loadFileUrl( + '/path/to/file.html', + readAccessUrl: '/path/to', + )); + }); + }); +} 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 000000000000..9732eefa2d4f --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.mocks.dart @@ -0,0 +1,224 @@ +// Mocks generated by Mockito 5.2.0 from annotations +// in webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart. +// Do not manually edit this file. + +import 'dart:async' as _i4; +import 'dart:ui' as _i6; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart' + as _i5; +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 _i2; + +// 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 _FakeWKWebViewConfiguration_0 extends _i1.Fake + implements _i2.WKWebViewConfiguration {} + +class _FakeUIScrollView_1 extends _i1.Fake implements _i3.UIScrollView {} + +class _FakeWKWebView_2 extends _i1.Fake implements _i2.WKWebView {} + +class _FakeWKUserContentController_3 extends _i1.Fake + implements _i2.WKUserContentController {} + +class _FakeWKPreferences_4 extends _i1.Fake implements _i2.WKPreferences {} + +class _FakeWKWebsiteDataStore_5 extends _i1.Fake + implements _i2.WKWebsiteDataStore {} + +/// 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 _i2.WKWebView { + MockWKWebView() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.WKWebViewConfiguration get configuration => + (super.noSuchMethod(Invocation.getter(#configuration), + returnValue: _FakeWKWebViewConfiguration_0()) + as _i2.WKWebViewConfiguration); + @override + _i3.UIScrollView get scrollView => + (super.noSuchMethod(Invocation.getter(#scrollView), + returnValue: _FakeUIScrollView_1()) as _i3.UIScrollView); + @override + _i4.Future setUIDelegate(_i2.WKUIDelegate? delegate) => + (super.noSuchMethod(Invocation.method(#setUIDelegate, [delegate]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setNavigationDelegate(_i2.WKNavigationDelegate? delegate) => + (super.noSuchMethod(Invocation.method(#setNavigationDelegate, [delegate]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future getUrl() => + (super.noSuchMethod(Invocation.method(#getUrl, []), + returnValue: Future.value()) as _i4.Future); + @override + _i4.Future getEstimatedProgress() => + (super.noSuchMethod(Invocation.method(#getEstimatedProgress, []), + returnValue: Future.value(0.0)) as _i4.Future); + @override + _i4.Future loadRequest(_i5.NSUrlRequest? request) => + (super.noSuchMethod(Invocation.method(#loadRequest, [request]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future loadHtmlString(String? string, {String? baseUrl}) => + (super.noSuchMethod( + Invocation.method(#loadHtmlString, [string], {#baseUrl: baseUrl}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future loadFileUrl(String? url, {String? readAccessUrl}) => + (super.noSuchMethod( + Invocation.method( + #loadFileUrl, [url], {#readAccessUrl: readAccessUrl}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future loadFlutterAsset(String? key) => + (super.noSuchMethod(Invocation.method(#loadFlutterAsset, [key]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future canGoBack() => + (super.noSuchMethod(Invocation.method(#canGoBack, []), + returnValue: Future.value(false)) as _i4.Future); + @override + _i4.Future canGoForward() => + (super.noSuchMethod(Invocation.method(#canGoForward, []), + returnValue: Future.value(false)) as _i4.Future); + @override + _i4.Future goBack() => + (super.noSuchMethod(Invocation.method(#goBack, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future goForward() => + (super.noSuchMethod(Invocation.method(#goForward, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future reload() => + (super.noSuchMethod(Invocation.method(#reload, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future getTitle() => + (super.noSuchMethod(Invocation.method(#getTitle, []), + returnValue: Future.value()) as _i4.Future); + @override + _i4.Future setAllowsBackForwardNavigationGestures(bool? allow) => + (super.noSuchMethod( + Invocation.method(#setAllowsBackForwardNavigationGestures, [allow]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setCustomUserAgent(String? userAgent) => + (super.noSuchMethod(Invocation.method(#setCustomUserAgent, [userAgent]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future evaluateJavaScript(String? javaScriptString) => (super + .noSuchMethod(Invocation.method(#evaluateJavaScript, [javaScriptString]), + returnValue: Future.value()) as _i4.Future); + @override + _i2.WKWebView copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeWKWebView_2()) as _i2.WKWebView); + @override + _i4.Future setBackgroundColor(_i6.Color? color) => + (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setOpaque(bool? opaque) => + (super.noSuchMethod(Invocation.method(#setOpaque, [opaque]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future addObserver(_i5.NSObject? observer, + {String? keyPath, Set<_i5.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future removeObserver(_i5.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); +} + +/// 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 _i2.WKWebViewConfiguration { + MockWKWebViewConfiguration() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.WKUserContentController get userContentController => + (super.noSuchMethod(Invocation.getter(#userContentController), + returnValue: _FakeWKUserContentController_3()) + as _i2.WKUserContentController); + @override + _i2.WKPreferences get preferences => + (super.noSuchMethod(Invocation.getter(#preferences), + returnValue: _FakeWKPreferences_4()) as _i2.WKPreferences); + @override + _i2.WKWebsiteDataStore get websiteDataStore => + (super.noSuchMethod(Invocation.getter(#websiteDataStore), + returnValue: _FakeWKWebsiteDataStore_5()) as _i2.WKWebsiteDataStore); + @override + _i4.Future setAllowsInlineMediaPlayback(bool? allow) => (super + .noSuchMethod(Invocation.method(#setAllowsInlineMediaPlayback, [allow]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future setMediaTypesRequiringUserActionForPlayback( + Set<_i2.WKAudiovisualMediaType>? types) => + (super.noSuchMethod( + Invocation.method( + #setMediaTypesRequiringUserActionForPlayback, [types]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i2.WKWebViewConfiguration copy() => + (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeWKWebViewConfiguration_0()) + as _i2.WKWebViewConfiguration); + @override + _i4.Future addObserver(_i5.NSObject? observer, + {String? keyPath, Set<_i5.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override + _i4.Future removeObserver(_i5.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); +} From ffeda105cf90e8aae6782609187821c67eb45d8f Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 13 Jul 2022 16:13:11 -0700 Subject: [PATCH 04/19] some api improvements and tests --- .../src/v4/src/webkit_webview_controller.dart | 43 +- .../v4/webkit_webview_controller_test.dart | 533 +++++++++++++++++- .../webkit_webview_controller_test.mocks.dart | 348 +++++++++--- 3 files changed, 829 insertions(+), 95 deletions(-) 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 index 6df810909761..25ea19718872 100644 --- 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 @@ -15,6 +15,11 @@ import 'webkit_proxy.dart'; @immutable class WebKitWebViewControllerCreationParams extends PlatformWebViewControllerCreationParams { + /// Constructs a [WebKitWebViewControllerCreationParams]. + WebKitWebViewControllerCreationParams({ + @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), + }) : _configuration = webKitProxy.onCreateWebViewConfiguration(); + /// Constructs a [WebKitWebViewControllerCreationParams] using a /// [PlatformWebViewControllerCreationParams]. WebKitWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( @@ -22,7 +27,7 @@ class WebKitWebViewControllerCreationParams // ignore: avoid_unused_constructor_parameters PlatformWebViewControllerCreationParams params, { @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), - }) : _configuration = webKitProxy.onCreateWebViewConfiguration(); + }) : this(webKitProxy: webKitProxy); final WKWebViewConfiguration _configuration; } @@ -110,9 +115,12 @@ class WebKitWebViewController extends PlatformWebViewController { } @override - Future removeJavaScriptChannel(String javaScriptChannelName) { + Future removeJavaScriptChannel(String javaScriptChannelName) async { assert(javaScriptChannelName.isNotEmpty); - return _resetUserScripts(removedJavaScriptChannel: javaScriptChannelName); + if (!_javaScriptChannelParams.containsKey(javaScriptChannelName)) { + return; + } + await _resetUserScripts(removedJavaScriptChannel: javaScriptChannelName); } @override @@ -272,7 +280,7 @@ class WebKitWebViewController extends PlatformWebViewController { .addUserScript(userScript); } - // WkWebView does not support removing a single user script, so all user + // 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 @@ -303,14 +311,15 @@ class WebKitWebViewController extends PlatformWebViewController { /// See [WebKitWebViewController.addJavaScriptChannel]. @immutable class WebKitJavaScriptChannelParams extends JavaScriptChannelParams { - /// Constructs a [WebKitJavaScriptChannelParams] using a - /// [JavaScriptChannelParams]. - WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( - JavaScriptChannelParams params, { + /// Constructs a [WebKitJavaScriptChannelParams]. + WebKitJavaScriptChannelParams({ + required super.name, + required super.onMessageReceived, @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), - }) : _messageHandler = webKitProxy.onCreateScriptMessageHandler( + }) : assert(name.isNotEmpty), + _messageHandler = webKitProxy.onCreateScriptMessageHandler( didReceiveScriptMessage: withWeakRefenceTo( - params.onMessageReceived, + onMessageReceived, (WeakReference weakReference) { return ( WKUserContentController controller, @@ -324,8 +333,18 @@ class WebKitJavaScriptChannelParams extends JavaScriptChannelParams { }; }, ), - ), - super(name: params.name, onMessageReceived: params.onMessageReceived); + ); + + /// 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/test/v4/webkit_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart index 92a00d6cdc56..f32e05644dcf 100644 --- 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 @@ -1,28 +1,61 @@ +import 'dart:math'; + +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([WKWebView, WKWebViewConfiguration]) +@GenerateMocks([ + UIScrollView, + WKPreferences, + WKUserContentController, + WKWebsiteDataStore, + WKWebView, + WKWebViewConfiguration, +]) void main() { + WidgetsFlutterBinding.ensureInitialized(); + group('WebKitWebViewController', () { WebKitWebViewController createControllerWithMocks({ + MockUIScrollView? mockScrollView, + MockWKPreferences? mockPreferences, + MockWKUserContentController? mockUserContentController, + MockWKWebsiteDataStore? mockWebsiteDataStore, MockWKWebView? mockWebView, MockWKWebViewConfiguration? mockWebViewConfiguration, }) { + final MockWKWebViewConfiguration nonNullMockWebViewConfiguration = + mockWebViewConfiguration ?? MockWKWebViewConfiguration(); + final MockWKWebView nonNullMockWebView = mockWebView ?? MockWKWebView(); + + 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()); + final PlatformWebViewControllerCreationParams controllerCreationParams = WebKitWebViewControllerCreationParams .fromPlatformWebViewControllerCreationParams( const PlatformWebViewControllerCreationParams(), webKitProxy: WebKitProxy( - onCreateWebViewConfiguration: () => - mockWebViewConfiguration ?? MockWKWebViewConfiguration(), + onCreateWebViewConfiguration: () => nonNullMockWebViewConfiguration, ), ); @@ -38,7 +71,7 @@ void main() { )? observeValue, }) { - return mockWebView ?? MockWKWebView(); + return nonNullMockWebView; }, ), ); @@ -57,5 +90,497 @@ void main() { readAccessUrl: '/path/to', )); }); + + test('loadFlutterAsset', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: 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( + mockWebView: mockWebView, + ); + + const String htmlString = 'Test data.'; + await controller.loadHtmlString(htmlString, baseUrl: 'baseUrl'); + + verify(mockWebView.loadHtmlString( + 'Test data.', + baseUrl: 'baseUrl', + )); + }); + + group('loadRequest', () { + test('Throws ArgumentError for empty scheme', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + expect( + () async => await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse('www.google.com'), + method: LoadRequestMethod.get, + headers: const {}, + ), + ), + throwsA(isA()), + ); + }); + + test('GET without headers', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse('https://www.google.com'), + method: LoadRequestMethod.get, + headers: const {}, + ), + ); + + final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) + .captured + .single as NSUrlRequest; + expect(request.url, 'https://www.google.com'); + expect(request.allHttpHeaderFields, {}); + expect(request.httpMethod, 'get'); + }); + + test('GET with headers', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse('https://www.google.com'), + method: LoadRequestMethod.get, + headers: const {'a': 'header'}, + ), + ); + + final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) + .captured + .single as NSUrlRequest; + expect(request.url, 'https://www.google.com'); + expect(request.allHttpHeaderFields, {'a': 'header'}); + expect(request.httpMethod, 'get'); + }); + + test('POST without body', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.loadRequest(LoadRequestParams( + uri: Uri.parse('https://www.google.com'), + method: LoadRequestMethod.post, + headers: const {}, + )); + + 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( + mockWebView: mockWebView, + ); + + await controller.loadRequest(LoadRequestParams( + uri: Uri.parse('https://www.google.com'), + method: LoadRequestMethod.post, + body: Uint8List.fromList('Test Body'.codeUnits), + headers: const {}, + )); + + 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( + mockWebView: mockWebView, + ); + + when(mockWebView.canGoBack()).thenAnswer( + (_) => Future.value(false), + ); + expect(controller.canGoBack(), completion(false)); + }); + + test('canGoForward', () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.canGoForward()).thenAnswer( + (_) => Future.value(true), + ); + expect(controller.canGoForward(), completion(true)); + }); + + test('goBack', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.goBack(); + verify(mockWebView.goBack()); + }); + + test('goForward', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.goForward(); + verify(mockWebView.goForward()); + }); + + test('reload', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.reload(); + verify(mockWebView.reload()); + }); + + test('runJavaScriptReturningResult', () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + (_) => Future.value('returnString'), + ); + expect( + controller.runJavaScriptReturningResult('runJavaScript'), + completion('returnString'), + ); + }); + + test('runJavaScriptReturningResult throws error on null return value', () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + (_) => Future.value(null), + ); + expect( + () => controller.runJavaScriptReturningResult('runJavaScript'), + throwsArgumentError, + ); + }); + + test('runJavaScript', () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( + (_) => Future.value('returnString'), + ); + expect( + controller.runJavaScript('runJavaScript'), + completes, + ); + }); + + test('runJavaScript ignores exception with unsupported javaScript type', + () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: 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( + mockWebView: mockWebView, + ); + + when(mockWebView.getTitle()) + .thenAnswer((_) => Future.value('Web Title')); + expect(controller.getTitle(), completion('Web Title')); + }); + + test('currentUrl', () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.getUrl()) + .thenAnswer((_) => Future.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(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(2.0, 4.0))); + }); + + test('getScrollPosition', () { + final MockUIScrollView mockScrollView = MockUIScrollView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockScrollView: mockScrollView, + ); + + when(mockScrollView.getContentOffset()).thenAnswer( + (_) => Future>.value(const Point(8.0, 16.0)), + ); + expect( + controller.getScrollPosition(), + completion(const Point(8.0, 16.0)), + ); + }); + + test('clearCache', () { + final MockWKWebsiteDataStore mockWebsiteDataStore = + MockWKWebsiteDataStore(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebsiteDataStore: mockWebsiteDataStore, + ); + when( + mockWebsiteDataStore.removeDataOfTypes( + { + WKWebsiteDataType.memoryCache, + WKWebsiteDataType.diskCache, + WKWebsiteDataType.offlineWebApplicationCache, + }, + DateTime.fromMillisecondsSinceEpoch(0), + ), + ).thenAnswer((_) => Future.value(false)); + + expect(controller.clearCache(), completes); + }); + + test('addJavaScriptChannel', () async { + final WebKitProxy webKitProxy = WebKitProxy( + onCreateScriptMessageHandler: ({ + required void Function( + WKUserContentController userContentController, + WKScriptMessage message, + ) + didReceiveScriptMessage, + }) { + return WKScriptMessageHandler.detached( + didReceiveScriptMessage: didReceiveScriptMessage, + ); + }, + ); + + final WebKitJavaScriptChannelParams javaScriptChannelParams = + WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( + JavaScriptChannelParams( + 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()), + '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( + onCreateScriptMessageHandler: ({ + required void Function( + WKUserContentController userContentController, + WKScriptMessage message, + ) + didReceiveScriptMessage, + }) { + return WKScriptMessageHandler.detached( + didReceiveScriptMessage: didReceiveScriptMessage, + ); + }, + ); + + final WebKitJavaScriptChannelParams javaScriptChannelParams = + WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( + JavaScriptChannelParams( + 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( + onCreateScriptMessageHandler: ({ + 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);", + ); + }); }); } 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 index 9732eefa2d4f..6138fdd2f4a4 100644 --- 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 @@ -1,15 +1,16 @@ // Mocks generated by Mockito 5.2.0 from annotations -// in webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart. +// 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 _i4; +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 _i5; + 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 _i2; +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 @@ -21,149 +22,338 @@ import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart' as _i2; // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types -class _FakeWKWebViewConfiguration_0 extends _i1.Fake - implements _i2.WKWebViewConfiguration {} +class _FakePoint_0 extends _i1.Fake implements _i2.Point {} class _FakeUIScrollView_1 extends _i1.Fake implements _i3.UIScrollView {} -class _FakeWKWebView_2 extends _i1.Fake implements _i2.WKWebView {} +class _FakeWKPreferences_2 extends _i1.Fake implements _i4.WKPreferences {} class _FakeWKUserContentController_3 extends _i1.Fake - implements _i2.WKUserContentController {} + implements _i4.WKUserContentController {} -class _FakeWKPreferences_4 extends _i1.Fake implements _i2.WKPreferences {} +class _FakeWKHttpCookieStore_4 extends _i1.Fake + implements _i4.WKHttpCookieStore {} class _FakeWKWebsiteDataStore_5 extends _i1.Fake - implements _i2.WKWebsiteDataStore {} + 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> getContentOffset() => (super.noSuchMethod( + Invocation.method(#getContentOffset, []), + returnValue: Future<_i2.Point>.value(_FakePoint_0())) + as _i5.Future<_i2.Point>); + @override + _i5.Future scrollBy(_i2.Point? offset) => + (super.noSuchMethod(Invocation.method(#scrollBy, [offset]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future setContentOffset(_i2.Point? offset) => + (super.noSuchMethod(Invocation.method(#setContentOffset, [offset]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i3.UIScrollView copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeUIScrollView_1()) as _i3.UIScrollView); + @override + _i5.Future setBackgroundColor(_i6.Color? color) => + (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future setOpaque(bool? opaque) => + (super.noSuchMethod(Invocation.method(#setOpaque, [opaque]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future addObserver(_i7.NSObject? observer, + {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future removeObserver(_i7.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); +} + +/// 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 setJavaScriptEnabled(bool? enabled) => + (super.noSuchMethod(Invocation.method(#setJavaScriptEnabled, [enabled]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i4.WKPreferences copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeWKPreferences_2()) as _i4.WKPreferences); + @override + _i5.Future addObserver(_i7.NSObject? observer, + {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future removeObserver(_i7.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); +} + +/// 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 addScriptMessageHandler( + _i4.WKScriptMessageHandler? handler, String? name) => + (super.noSuchMethod( + Invocation.method(#addScriptMessageHandler, [handler, name]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future removeScriptMessageHandler(String? name) => (super + .noSuchMethod(Invocation.method(#removeScriptMessageHandler, [name]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future removeAllScriptMessageHandlers() => (super.noSuchMethod( + Invocation.method(#removeAllScriptMessageHandlers, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future addUserScript(_i4.WKUserScript? userScript) => + (super.noSuchMethod(Invocation.method(#addUserScript, [userScript]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future removeAllUserScripts() => + (super.noSuchMethod(Invocation.method(#removeAllUserScripts, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i4.WKUserContentController copy() => + (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeWKUserContentController_3()) + as _i4.WKUserContentController); + @override + _i5.Future addObserver(_i7.NSObject? observer, + {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future removeObserver(_i7.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); +} + +/// 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 removeDataOfTypes( + Set<_i4.WKWebsiteDataType>? dataTypes, DateTime? since) => + (super.noSuchMethod( + Invocation.method(#removeDataOfTypes, [dataTypes, since]), + returnValue: Future.value(false)) as _i5.Future); + @override + _i4.WKWebsiteDataStore copy() => + (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeWKWebsiteDataStore_5()) as _i4.WKWebsiteDataStore); + @override + _i5.Future addObserver(_i7.NSObject? observer, + {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => + (super.noSuchMethod( + Invocation.method( + #addObserver, [observer], {#keyPath: keyPath, #options: options}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future removeObserver(_i7.NSObject? observer, {String? keyPath}) => + (super.noSuchMethod( + Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); +} /// 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 _i2.WKWebView { +class MockWKWebView extends _i1.Mock implements _i4.WKWebView { MockWKWebView() { _i1.throwOnMissingStub(this); } @override - _i2.WKWebViewConfiguration get configuration => + _i4.WKWebViewConfiguration get configuration => (super.noSuchMethod(Invocation.getter(#configuration), - returnValue: _FakeWKWebViewConfiguration_0()) - as _i2.WKWebViewConfiguration); + returnValue: _FakeWKWebViewConfiguration_6()) + as _i4.WKWebViewConfiguration); @override _i3.UIScrollView get scrollView => (super.noSuchMethod(Invocation.getter(#scrollView), returnValue: _FakeUIScrollView_1()) as _i3.UIScrollView); @override - _i4.Future setUIDelegate(_i2.WKUIDelegate? delegate) => + _i5.Future setUIDelegate(_i4.WKUIDelegate? delegate) => (super.noSuchMethod(Invocation.method(#setUIDelegate, [delegate]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setNavigationDelegate(_i2.WKNavigationDelegate? delegate) => + _i5.Future setNavigationDelegate(_i4.WKNavigationDelegate? delegate) => (super.noSuchMethod(Invocation.method(#setNavigationDelegate, [delegate]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future getUrl() => + _i5.Future getUrl() => (super.noSuchMethod(Invocation.method(#getUrl, []), - returnValue: Future.value()) as _i4.Future); + returnValue: Future.value()) as _i5.Future); @override - _i4.Future getEstimatedProgress() => + _i5.Future getEstimatedProgress() => (super.noSuchMethod(Invocation.method(#getEstimatedProgress, []), - returnValue: Future.value(0.0)) as _i4.Future); + returnValue: Future.value(0.0)) as _i5.Future); @override - _i4.Future loadRequest(_i5.NSUrlRequest? request) => + _i5.Future loadRequest(_i7.NSUrlRequest? request) => (super.noSuchMethod(Invocation.method(#loadRequest, [request]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future loadHtmlString(String? string, {String? baseUrl}) => + _i5.Future loadHtmlString(String? string, {String? baseUrl}) => (super.noSuchMethod( Invocation.method(#loadHtmlString, [string], {#baseUrl: baseUrl}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future loadFileUrl(String? url, {String? readAccessUrl}) => + _i5.Future loadFileUrl(String? url, {String? readAccessUrl}) => (super.noSuchMethod( Invocation.method( #loadFileUrl, [url], {#readAccessUrl: readAccessUrl}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future loadFlutterAsset(String? key) => + _i5.Future loadFlutterAsset(String? key) => (super.noSuchMethod(Invocation.method(#loadFlutterAsset, [key]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future canGoBack() => + _i5.Future canGoBack() => (super.noSuchMethod(Invocation.method(#canGoBack, []), - returnValue: Future.value(false)) as _i4.Future); + returnValue: Future.value(false)) as _i5.Future); @override - _i4.Future canGoForward() => + _i5.Future canGoForward() => (super.noSuchMethod(Invocation.method(#canGoForward, []), - returnValue: Future.value(false)) as _i4.Future); + returnValue: Future.value(false)) as _i5.Future); @override - _i4.Future goBack() => + _i5.Future goBack() => (super.noSuchMethod(Invocation.method(#goBack, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future goForward() => + _i5.Future goForward() => (super.noSuchMethod(Invocation.method(#goForward, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future reload() => + _i5.Future reload() => (super.noSuchMethod(Invocation.method(#reload, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future getTitle() => + _i5.Future getTitle() => (super.noSuchMethod(Invocation.method(#getTitle, []), - returnValue: Future.value()) as _i4.Future); + returnValue: Future.value()) as _i5.Future); @override - _i4.Future setAllowsBackForwardNavigationGestures(bool? allow) => + _i5.Future setAllowsBackForwardNavigationGestures(bool? allow) => (super.noSuchMethod( Invocation.method(#setAllowsBackForwardNavigationGestures, [allow]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setCustomUserAgent(String? userAgent) => + _i5.Future setCustomUserAgent(String? userAgent) => (super.noSuchMethod(Invocation.method(#setCustomUserAgent, [userAgent]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future evaluateJavaScript(String? javaScriptString) => (super + _i5.Future evaluateJavaScript(String? javaScriptString) => (super .noSuchMethod(Invocation.method(#evaluateJavaScript, [javaScriptString]), - returnValue: Future.value()) as _i4.Future); + returnValue: Future.value()) as _i5.Future); @override - _i2.WKWebView copy() => (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeWKWebView_2()) as _i2.WKWebView); + _i4.WKWebView copy() => (super.noSuchMethod(Invocation.method(#copy, []), + returnValue: _FakeWKWebView_7()) as _i4.WKWebView); @override - _i4.Future setBackgroundColor(_i6.Color? color) => + _i5.Future setBackgroundColor(_i6.Color? color) => (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setOpaque(bool? opaque) => + _i5.Future setOpaque(bool? opaque) => (super.noSuchMethod(Invocation.method(#setOpaque, [opaque]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future addObserver(_i5.NSObject? observer, - {String? keyPath, Set<_i5.NSKeyValueObservingOptions>? options}) => + _i5.Future addObserver(_i7.NSObject? observer, + {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], {#keyPath: keyPath, #options: options}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future removeObserver(_i5.NSObject? observer, {String? keyPath}) => + _i5.Future removeObserver(_i7.NSObject? observer, {String? keyPath}) => (super.noSuchMethod( Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); } /// A class which mocks [WKWebViewConfiguration]. @@ -171,54 +361,54 @@ class MockWKWebView extends _i1.Mock implements _i2.WKWebView { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKWebViewConfiguration extends _i1.Mock - implements _i2.WKWebViewConfiguration { + implements _i4.WKWebViewConfiguration { MockWKWebViewConfiguration() { _i1.throwOnMissingStub(this); } @override - _i2.WKUserContentController get userContentController => + _i4.WKUserContentController get userContentController => (super.noSuchMethod(Invocation.getter(#userContentController), returnValue: _FakeWKUserContentController_3()) - as _i2.WKUserContentController); + as _i4.WKUserContentController); @override - _i2.WKPreferences get preferences => + _i4.WKPreferences get preferences => (super.noSuchMethod(Invocation.getter(#preferences), - returnValue: _FakeWKPreferences_4()) as _i2.WKPreferences); + returnValue: _FakeWKPreferences_2()) as _i4.WKPreferences); @override - _i2.WKWebsiteDataStore get websiteDataStore => + _i4.WKWebsiteDataStore get websiteDataStore => (super.noSuchMethod(Invocation.getter(#websiteDataStore), - returnValue: _FakeWKWebsiteDataStore_5()) as _i2.WKWebsiteDataStore); + returnValue: _FakeWKWebsiteDataStore_5()) as _i4.WKWebsiteDataStore); @override - _i4.Future setAllowsInlineMediaPlayback(bool? allow) => (super + _i5.Future setAllowsInlineMediaPlayback(bool? allow) => (super .noSuchMethod(Invocation.method(#setAllowsInlineMediaPlayback, [allow]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setMediaTypesRequiringUserActionForPlayback( - Set<_i2.WKAudiovisualMediaType>? types) => + _i5.Future setMediaTypesRequiringUserActionForPlayback( + Set<_i4.WKAudiovisualMediaType>? types) => (super.noSuchMethod( Invocation.method( #setMediaTypesRequiringUserActionForPlayback, [types]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i2.WKWebViewConfiguration copy() => + _i4.WKWebViewConfiguration copy() => (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeWKWebViewConfiguration_0()) - as _i2.WKWebViewConfiguration); + returnValue: _FakeWKWebViewConfiguration_6()) + as _i4.WKWebViewConfiguration); @override - _i4.Future addObserver(_i5.NSObject? observer, - {String? keyPath, Set<_i5.NSKeyValueObservingOptions>? options}) => + _i5.Future addObserver(_i7.NSObject? observer, + {String? keyPath, Set<_i7.NSKeyValueObservingOptions>? options}) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], {#keyPath: keyPath, #options: options}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future removeObserver(_i5.NSObject? observer, {String? keyPath}) => + _i5.Future removeObserver(_i7.NSObject? observer, {String? keyPath}) => (super.noSuchMethod( Invocation.method(#removeObserver, [observer], {#keyPath: keyPath}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); } From 9fa6d0b6a2bbb0762ffe2154a883caa3af593a3d Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 13 Jul 2022 16:14:36 -0700 Subject: [PATCH 05/19] convenience constructors --- .../v4/webkit_webview_controller_test.dart | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) 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 index f32e05644dcf..bad462221826 100644 --- 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 @@ -51,9 +51,7 @@ void main() { .thenReturn(mockWebsiteDataStore ?? MockWKWebsiteDataStore()); final PlatformWebViewControllerCreationParams controllerCreationParams = - WebKitWebViewControllerCreationParams - .fromPlatformWebViewControllerCreationParams( - const PlatformWebViewControllerCreationParams(), + WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( onCreateWebViewConfiguration: () => nonNullMockWebViewConfiguration, ), @@ -459,11 +457,9 @@ void main() { ); final WebKitJavaScriptChannelParams javaScriptChannelParams = - WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( - JavaScriptChannelParams( - name: 'name', - onMessageReceived: (JavaScriptMessage message) {}, - ), + WebKitJavaScriptChannelParams( + name: 'name', + onMessageReceived: (JavaScriptMessage message) {}, webKitProxy: webKitProxy, ); @@ -507,11 +503,9 @@ void main() { ); final WebKitJavaScriptChannelParams javaScriptChannelParams = - WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( - JavaScriptChannelParams( - name: 'name', - onMessageReceived: (JavaScriptMessage message) {}, - ), + WebKitJavaScriptChannelParams( + name: 'name', + onMessageReceived: (JavaScriptMessage message) {}, webKitProxy: webKitProxy, ); From 2a150de7988a4a33a2260e21cb6d3423de44e347 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 13 Jul 2022 16:26:54 -0700 Subject: [PATCH 06/19] a few tests left --- .../src/v4/src/webkit_webview_controller.dart | 4 +-- .../v4/webkit_webview_controller_test.dart | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) 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 index 25ea19718872..519ab9777140 100644 --- 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 @@ -5,10 +5,10 @@ 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 'package:webview_flutter_wkwebview/src/common/weak_reference_utils.dart'; -import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.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]. 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 index bad462221826..f80220c75ab3 100644 --- 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 @@ -287,6 +287,17 @@ void main() { verify(mockWebView.reload()); }); + test('enableGestureNavigation', () async { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.enableGestureNavigation(true); + verify(mockWebView.setAllowsBackForwardNavigationGestures(true)); + }); + test('runJavaScriptReturningResult', () { final MockWKWebView mockWebView = MockWKWebView(); @@ -441,6 +452,23 @@ void main() { expect(controller.clearCache(), completes); }); + test('clearLocalStorage', () { + final MockWKWebsiteDataStore mockWebsiteDataStore = + MockWKWebsiteDataStore(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebsiteDataStore: mockWebsiteDataStore, + ); + when( + mockWebsiteDataStore.removeDataOfTypes( + {WKWebsiteDataType.localStorage}, + DateTime.fromMillisecondsSinceEpoch(0), + ), + ).thenAnswer((_) => Future.value(false)); + + expect(controller.clearLocalStorage(), completes); + }); + test('addJavaScriptChannel', () async { final WebKitProxy webKitProxy = WebKitProxy( onCreateScriptMessageHandler: ({ From b6431cf8a734a6fbb22fd95e22de677ef09d19ef Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 13 Jul 2022 16:35:59 -0700 Subject: [PATCH 07/19] rest of tests and licenses --- .../lib/src/v4/src/webkit_proxy.dart | 4 + .../src/v4/src/webkit_webview_controller.dart | 4 + .../src/v4/src/webkit_webview_platform.dart | 4 + .../lib/src/v4/webview_flutter_wkwebview.dart | 4 + .../v4/webkit_webview_controller_test.dart | 81 +++++++++++++++++++ 5 files changed, 97 insertions(+) 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 index 6ff2546512f6..1bc194c7e14c 100644 --- 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 @@ -1,3 +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. + import '../../foundation/foundation.dart'; import '../../web_kit/web_kit.dart'; 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 index 519ab9777140..feb018abcdc2 100644 --- 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 @@ -1,3 +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. + import 'dart:math'; import 'package:flutter/foundation.dart'; 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 index a38525d96a25..b808086d56f8 100644 --- 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 @@ -1,3 +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. + import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; import 'webkit_webview_controller.dart'; 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 index d295d026c945..2a593fb5a088 100644 --- 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 @@ -1,3 +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 index f80220c75ab3..3ca4d43b3827 100644 --- 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 @@ -1,3 +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. + import 'dart:math'; import 'package:flutter/material.dart'; @@ -431,6 +435,83 @@ void main() { ); }); + 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( + mockWebView: 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( + mockWebView: 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(); From dcdb8dcb0b5858cbf4aa8992a7240b3457696c07 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 14 Jul 2022 14:06:51 -0700 Subject: [PATCH 08/19] add test for callback method --- .../v4/webkit_webview_controller_test.dart | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) 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 index 3ca4d43b3827..e8bc8905d8ed 100644 --- 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 @@ -3,6 +3,9 @@ // 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'; @@ -686,4 +689,41 @@ void main() { ); }); }); + + group('WebKitJavaScriptChannelParams', () { + test('onMessageReceived', () async { + late final WKScriptMessageHandler messageHandler; + + final WebKitProxy webKitProxy = WebKitProxy( + onCreateScriptMessageHandler: ({ + 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'); + }); + }); } From e5f6f6a4d985e6d546ab595f9bfb7f3cb4acadc0 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 20 Jul 2022 12:52:50 -0700 Subject: [PATCH 09/19] webkit implementation of navigation delegate --- .../v4/src/webkit_navigation_delegate.dart | 160 ++++++++++++++++++ .../lib/src/v4/src/webkit_proxy.dart | 31 ++++ .../src/v4/src/webkit_webview_platform.dart | 17 ++ .../lib/src/v4/webview_flutter_wkwebview.dart | 7 + 4 files changed, 215 insertions(+) create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_proxy.dart create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_webview_platform.dart create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/webview_flutter_wkwebview.dart diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart new file mode 100644 index 000000000000..7a6a1a5c1fba --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart @@ -0,0 +1,160 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; + +import '../../foundation/foundation.dart'; +import '../../web_kit/web_kit.dart'; +import 'webkit_proxy.dart'; + +/// An implementation of [WebResourceError] with the WebKit API. +class WebKitWebResourceError extends WebResourceError { + WebKitWebResourceError._(this._nsError) + : super( + errorCode: _nsError.code, + description: _nsError.localizedDescription, + errorType: _toWebResourceErrorType(_nsError.code), + ); + + static WebResourceErrorType? _toWebResourceErrorType(int code) { + switch (code) { + case WKErrorCode.unknown: + return WebResourceErrorType.unknown; + case WKErrorCode.webContentProcessTerminated: + return WebResourceErrorType.webContentProcessTerminated; + case WKErrorCode.webViewInvalidated: + return WebResourceErrorType.webViewInvalidated; + case WKErrorCode.javaScriptExceptionOccurred: + return WebResourceErrorType.javaScriptExceptionOccurred; + case WKErrorCode.javaScriptResultTypeIsUnsupported: + return WebResourceErrorType.javaScriptResultTypeIsUnsupported; + } + + return null; + } + + /// A string representing the domain of the error. + String? get domain => _nsError.domain; + + final NSError _nsError; +} + +/// An implementation of [PlatformNavigationDelegate] with the WebKit API. +class WebKitNavigationDelegate extends PlatformNavigationDelegate { + /// Constructs a [WebKitNavigationDelegate]. + WebKitNavigationDelegate( + super.params, { + @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), + }) : super.implementation() { + final WeakReference weakThis = + WeakReference(this); + navigationDelegate = webKitProxy.onCreateNavigationDelegate( + didFinishNavigation: (WKWebView webView, String? url) { + if (weakThis.target?._onPageFinished != null) { + weakThis.target!._onPageFinished!(url ?? ''); + } + }, + didStartProvisionalNavigation: (WKWebView webView, String? url) { + if (weakThis.target?._onPageStarted != null) { + weakThis.target!._onPageStarted!(url ?? ''); + } + }, + decidePolicyForNavigationAction: ( + WKWebView webView, + WKNavigationAction action, + ) async { + if (weakThis.target?._onNavigationRequest != null) { + final bool allow = await weakThis.target!._onNavigationRequest!( + url: action.request.url, + isForMainFrame: action.targetFrame.isMainFrame, + ); + return allow + ? WKNavigationActionPolicy.allow + : WKNavigationActionPolicy.cancel; + } + return WKNavigationActionPolicy.allow; + }, + didFailNavigation: (WKWebView webView, NSError error) { + if (weakThis.target?._onWebResourceError != null) { + weakThis.target!._onWebResourceError!( + WebKitWebResourceError._(error), + ); + } + }, + didFailProvisionalNavigation: (WKWebView webView, NSError error) { + if (weakThis.target?._onWebResourceError != null) { + weakThis.target!._onWebResourceError!( + WebKitWebResourceError._(error), + ); + } + }, + webViewWebContentProcessDidTerminate: (WKWebView webView) { + if (weakThis.target?._onWebResourceError != null) { + weakThis.target!._onWebResourceError!( + WebKitWebResourceError._( + const NSError( + code: WKErrorCode.webContentProcessTerminated, + // Value from https://developer.apple.com/documentation/webkit/wkerrordomain?language=objc. + domain: 'WKErrorDomain', + localizedDescription: '', + ), + ), + ); + } + }, + ); + } + + /// WebKit class that handles navigation changes and tracking navigation + /// requests. + @protected + late final WKNavigationDelegate navigationDelegate; + + void Function(String url)? _onPageFinished; + void Function(String url)? _onPageStarted; + void Function(int progress)? _onProgress; + void Function(WebResourceError error)? _onWebResourceError; + FutureOr Function({required String url, required bool isForMainFrame})? + _onNavigationRequest; + + /// Callback method that receives progress of a loading page. + @protected + void Function(int progress)? get onProgress => _onProgress; + + @override + Future setOnPageFinished( + void Function(String url) onPageFinished, + ) async { + _onPageFinished = onPageFinished; + } + + @override + Future setOnPageStarted(void Function(String url) onPageStarted) async { + _onPageStarted = onPageStarted; + } + + @override + Future setOnProgress(void Function(int progress) onProgress) async { + onProgress = onProgress; + } + + @override + Future setOnWebResourceError( + void Function(WebResourceError error) onWebResourceError, + ) async { + _onWebResourceError = onWebResourceError; + } + + @override + Future setOnNavigationRequest( + FutureOr Function({required String url, required bool isForMainFrame}) + onNavigationRequest, + ) async { + _onNavigationRequest = onNavigationRequest; + } +} 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 000000000000..e3478ebf34a2 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_proxy.dart @@ -0,0 +1,31 @@ +// 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. +class WebKitProxy { + /// Constructs a [WebKitProxy]. + const WebKitProxy({ + this.onCreateNavigationDelegate = WKNavigationDelegate.new, + }); + + /// Constructs a [WKNavigationDelegate]. + final WKNavigationDelegate Function({ + void Function(WKWebView webView, String? url)? didFinishNavigation, + void Function(WKWebView webView, String? url)? + didStartProvisionalNavigation, + Future Function( + WKWebView webView, + WKNavigationAction navigationAction, + )? + decidePolicyForNavigationAction, + void Function(WKWebView webView, NSError error)? didFailNavigation, + void Function(WKWebView webView, NSError error)? + didFailProvisionalNavigation, + void Function(WKWebView webView)? webViewWebContentProcessDidTerminate, + }) onCreateNavigationDelegate; +} 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 000000000000..b206fd476879 --- /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_navigation_delegate.dart'; + +/// Implementation of [WebViewPlatform] using the WebKit API. +class WebKitWebViewPlatform extends WebViewPlatform { + @override + WebKitNavigationDelegate createPlatformNavigationDelegate( + PlatformNavigationDelegateCreationParams params, + ) { + return WebKitNavigationDelegate(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 000000000000..afb25fa5138b --- /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_navigation_delegate.dart'; From 656ef820300bcef856444d7b8125099472ee9347 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 20 Jul 2022 14:56:13 -0700 Subject: [PATCH 10/19] tests --- .../v4/webkit_navigation_delegate_test.dart | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_navigation_delegate_test.dart diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_navigation_delegate_test.dart new file mode 100644 index 000000000000..03d5e41ab2f0 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_navigation_delegate_test.dart @@ -0,0 +1,255 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.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/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'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + + group('WebKitNavigationDelegate', () { + WKNavigationDelegate Function({ + void Function(WKWebView webView, String? url)? didFinishNavigation, + void Function(WKWebView webView, String? url)? + didStartProvisionalNavigation, + Future Function( + WKWebView webView, + WKNavigationAction navigationAction, + )? + decidePolicyForNavigationAction, + void Function(WKWebView webView, NSError error)? didFailNavigation, + void Function(WKWebView webView, NSError error)? + didFailProvisionalNavigation, + void Function(WKWebView webView)? webViewWebContentProcessDidTerminate, + }) createDetachedDelegate( + void Function(WKNavigationDelegate delegate) onCreateDelegate, + ) { + return ({ + void Function(WKWebView webView, String? url)? didFinishNavigation, + void Function(WKWebView webView, String? url)? + didStartProvisionalNavigation, + Future Function( + WKWebView webView, + WKNavigationAction navigationAction, + )? + decidePolicyForNavigationAction, + void Function(WKWebView webView, NSError error)? didFailNavigation, + void Function(WKWebView webView, NSError error)? + didFailProvisionalNavigation, + void Function(WKWebView webView)? webViewWebContentProcessDidTerminate, + }) { + final WKNavigationDelegate delegate = WKNavigationDelegate.detached( + didFinishNavigation: didFinishNavigation, + didStartProvisionalNavigation: didStartProvisionalNavigation, + decidePolicyForNavigationAction: decidePolicyForNavigationAction, + didFailNavigation: didFailNavigation, + didFailProvisionalNavigation: didFailProvisionalNavigation, + webViewWebContentProcessDidTerminate: + webViewWebContentProcessDidTerminate, + ); + onCreateDelegate(delegate); + return delegate; + }; + } + + test('setOnPageFinished', () async { + late final WKNavigationDelegate navigationDelegate; + + final WebKitProxy webKitProxy = WebKitProxy( + onCreateNavigationDelegate: createDetachedDelegate( + (WKNavigationDelegate delegate) => navigationDelegate = delegate, + ), + ); + + late final String callbackUrl; + void onPageFinished(String url) => callbackUrl = url; + + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: webKitProxy, + ); + webKitDelgate.setOnPageFinished(onPageFinished); + navigationDelegate.didFinishNavigation!( + WKWebView.detached(), + 'https://www.google.com', + ); + + expect(callbackUrl, 'https://www.google.com'); + }); + + test('setOnPageStarted', () async { + late final WKNavigationDelegate navigationDelegate; + + final WebKitProxy webKitProxy = WebKitProxy( + onCreateNavigationDelegate: createDetachedDelegate( + (WKNavigationDelegate delegate) => navigationDelegate = delegate, + ), + ); + + late final String callbackUrl; + void onPageStarted(String url) => callbackUrl = url; + + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: webKitProxy, + ); + webKitDelgate.setOnPageStarted(onPageStarted); + navigationDelegate.didStartProvisionalNavigation!( + WKWebView.detached(), + 'https://www.google.com', + ); + + expect(callbackUrl, 'https://www.google.com'); + }); + + test('onWebResourceError from didFailNavigation', () async { + late final WKNavigationDelegate navigationDelegate; + + final WebKitProxy webKitProxy = WebKitProxy( + onCreateNavigationDelegate: createDetachedDelegate( + (WKNavigationDelegate delegate) => navigationDelegate = delegate, + ), + ); + + late final WebKitWebResourceError callbackError; + void onWebResourceError(WebResourceError error) { + callbackError = error as WebKitWebResourceError; + } + + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: webKitProxy, + ); + webKitDelgate.setOnWebResourceError(onWebResourceError); + navigationDelegate.didFailNavigation!( + WKWebView.detached(), + const NSError( + code: WKErrorCode.webViewInvalidated, + domain: 'domain', + localizedDescription: 'my desc', + ), + ); + + expect(callbackError.description, 'my desc'); + expect(callbackError.errorCode, WKErrorCode.webViewInvalidated); + expect(callbackError.domain, 'domain'); + expect(callbackError.errorType, WebResourceErrorType.webViewInvalidated); + }); + + test('onWebResourceError from didFailProvisionalNavigation', () async { + late final WKNavigationDelegate navigationDelegate; + + final WebKitProxy webKitProxy = WebKitProxy( + onCreateNavigationDelegate: createDetachedDelegate( + (WKNavigationDelegate delegate) => navigationDelegate = delegate, + ), + ); + + late final WebKitWebResourceError callbackError; + void onWebResourceError(WebResourceError error) { + callbackError = error as WebKitWebResourceError; + } + + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: webKitProxy, + ); + webKitDelgate.setOnWebResourceError(onWebResourceError); + navigationDelegate.didFailProvisionalNavigation!( + WKWebView.detached(), + const NSError( + code: WKErrorCode.webViewInvalidated, + domain: 'domain', + localizedDescription: 'my desc', + ), + ); + + expect(callbackError.description, 'my desc'); + expect(callbackError.errorCode, WKErrorCode.webViewInvalidated); + expect(callbackError.domain, 'domain'); + expect(callbackError.errorType, WebResourceErrorType.webViewInvalidated); + }); + + test('onWebResourceError from webViewWebContentProcessDidTerminate', + () async { + late final WKNavigationDelegate navigationDelegate; + + final WebKitProxy webKitProxy = WebKitProxy( + onCreateNavigationDelegate: createDetachedDelegate( + (WKNavigationDelegate delegate) => navigationDelegate = delegate, + ), + ); + + late final WebKitWebResourceError callbackError; + void onWebResourceError(WebResourceError error) { + callbackError = error as WebKitWebResourceError; + } + + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: webKitProxy, + ); + webKitDelgate.setOnWebResourceError(onWebResourceError); + navigationDelegate.webViewWebContentProcessDidTerminate!( + WKWebView.detached(), + ); + + expect(callbackError.description, ''); + expect(callbackError.errorCode, WKErrorCode.webContentProcessTerminated); + expect(callbackError.domain, 'WKErrorDomain'); + expect( + callbackError.errorType, + WebResourceErrorType.webContentProcessTerminated, + ); + }); + + test('onNavigationRequest from decidePolicyForNavigationAction', () async { + late final WKNavigationDelegate navigationDelegate; + + final WebKitProxy webKitProxy = WebKitProxy( + onCreateNavigationDelegate: createDetachedDelegate( + (WKNavigationDelegate delegate) => navigationDelegate = delegate, + ), + ); + + late final String callbackUrl; + late final bool callbackIsMainFrame; + FutureOr onNavigationRequest({ + required String url, + required bool isForMainFrame, + }) { + callbackUrl = url; + callbackIsMainFrame = isForMainFrame; + return true; + } + + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: webKitProxy, + ); + webKitDelgate.setOnNavigationRequest(onNavigationRequest); + + expect( + navigationDelegate.decidePolicyForNavigationAction!( + WKWebView.detached(), + const WKNavigationAction( + request: NSUrlRequest(url: 'https://www.google.com'), + targetFrame: WKFrameInfo(isMainFrame: false), + ), + ), + completion(WKNavigationActionPolicy.allow), + ); + + expect(callbackUrl, 'https://www.google.com'); + expect(callbackIsMainFrame, isFalse); + }); + }); +} From c3f30290967d32588232a0e768fd2bacfa03e85f Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:50:26 -0700 Subject: [PATCH 11/19] improve proxy and change background color call order --- .../lib/src/v4/src/webkit_proxy.dart | 19 +++++++++++++------ .../src/v4/src/webkit_webview_controller.dart | 8 ++++---- .../v4/webkit_webview_controller_test.dart | 12 ++++++------ 3 files changed, 23 insertions(+), 16 deletions(-) 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 index 1bc194c7e14c..48e6faf9abd7 100644 --- 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 @@ -7,12 +7,19 @@ 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.onCreateWebView = WKWebView.new, - this.onCreateWebViewConfiguration = WKWebViewConfiguration.new, - this.onCreateScriptMessageHandler = WKScriptMessageHandler.new, + this.createWebView = WKWebView.new, + this.createWebViewConfiguration = WKWebViewConfiguration.new, + this.createScriptMessageHandler = WKScriptMessageHandler.new, }); /// Constructs a [WKWebView]. @@ -24,10 +31,10 @@ class WebKitProxy { Map change, ) observeValue, - }) onCreateWebView; + }) createWebView; /// Constructs a [WKWebViewConfiguration]. - final WKWebViewConfiguration Function() onCreateWebViewConfiguration; + final WKWebViewConfiguration Function() createWebViewConfiguration; /// Constructs a [WKScriptMessageHandler]. final WKScriptMessageHandler Function({ @@ -36,5 +43,5 @@ class WebKitProxy { WKScriptMessage message, ) didReceiveScriptMessage, - }) onCreateScriptMessageHandler; + }) 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 index feb018abcdc2..9d76b5cda04d 100644 --- 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 @@ -22,7 +22,7 @@ class WebKitWebViewControllerCreationParams /// Constructs a [WebKitWebViewControllerCreationParams]. WebKitWebViewControllerCreationParams({ @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), - }) : _configuration = webKitProxy.onCreateWebViewConfiguration(); + }) : _configuration = webKitProxy.createWebViewConfiguration(); /// Constructs a [WebKitWebViewControllerCreationParams] using a /// [PlatformWebViewControllerCreationParams]. @@ -46,7 +46,7 @@ class WebKitWebViewController extends PlatformWebViewController { ? params : WebKitWebViewControllerCreationParams .fromPlatformWebViewControllerCreationParams(params)) { - _webView = webKitProxy.onCreateWebView( + _webView = webKitProxy.createWebView( (params as WebKitWebViewControllerCreationParams)._configuration); } @@ -235,9 +235,9 @@ class WebKitWebViewController extends PlatformWebViewController { @override Future setBackgroundColor(Color color) { return Future.wait(>[ + _webView.scrollView.setBackgroundColor(color), _webView.setOpaque(false), _webView.setBackgroundColor(Colors.transparent), - _webView.scrollView.setBackgroundColor(color), ]); } @@ -321,7 +321,7 @@ class WebKitJavaScriptChannelParams extends JavaScriptChannelParams { required super.onMessageReceived, @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), }) : assert(name.isNotEmpty), - _messageHandler = webKitProxy.onCreateScriptMessageHandler( + _messageHandler = webKitProxy.createScriptMessageHandler( didReceiveScriptMessage: withWeakRefenceTo( onMessageReceived, (WeakReference weakReference) { 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 index e8bc8905d8ed..dcaada514b94 100644 --- 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 @@ -60,14 +60,14 @@ void main() { final PlatformWebViewControllerCreationParams controllerCreationParams = WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( - onCreateWebViewConfiguration: () => nonNullMockWebViewConfiguration, + createWebViewConfiguration: () => nonNullMockWebViewConfiguration, ), ); return WebKitWebViewController( controllerCreationParams, webKitProxy: WebKitProxy( - onCreateWebView: ( + createWebView: ( _, { void Function( String keyPath, @@ -555,7 +555,7 @@ void main() { test('addJavaScriptChannel', () async { final WebKitProxy webKitProxy = WebKitProxy( - onCreateScriptMessageHandler: ({ + createScriptMessageHandler: ({ required void Function( WKUserContentController userContentController, WKScriptMessage message, @@ -601,7 +601,7 @@ void main() { test('removeJavaScriptChannel', () async { final WebKitProxy webKitProxy = WebKitProxy( - onCreateScriptMessageHandler: ({ + createScriptMessageHandler: ({ required void Function( WKUserContentController userContentController, WKScriptMessage message, @@ -641,7 +641,7 @@ void main() { test('removeJavaScriptChannel with zoom disabled', () async { final WebKitProxy webKitProxy = WebKitProxy( - onCreateScriptMessageHandler: ({ + createScriptMessageHandler: ({ required void Function( WKUserContentController userContentController, WKScriptMessage message, @@ -695,7 +695,7 @@ void main() { late final WKScriptMessageHandler messageHandler; final WebKitProxy webKitProxy = WebKitProxy( - onCreateScriptMessageHandler: ({ + createScriptMessageHandler: ({ required void Function( WKUserContentController userContentController, WKScriptMessage message, From c3f6cfaca04cd7098c0aff29505eb67120c3b522 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Tue, 26 Jul 2022 14:46:13 -0700 Subject: [PATCH 12/19] set navigation delegate --- .../v4/src/webkit_navigation_delegate.dart | 4 +-- .../src/v4/src/webkit_webview_controller.dart | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart index 6cf2d6b516bf..6cc31e358126 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart @@ -112,7 +112,6 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate { /// WebKit class that handles navigation changes and tracking navigation /// requests. - @protected late final WKNavigationDelegate navigationDelegate; void Function(String url)? _onPageFinished; @@ -123,7 +122,6 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate { _onNavigationRequest; /// Callback method that receives progress of a loading page. - @protected void Function(int progress)? get onProgress => _onProgress; @override @@ -140,7 +138,7 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate { @override Future setOnProgress(void Function(int progress) onProgress) async { - onProgress = onProgress; + _onProgress = onProgress; } @override 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 index 9d76b5cda04d..df4371c6f64d 100644 --- 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 @@ -13,6 +13,7 @@ import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_i import '../../common/weak_reference_utils.dart'; import '../../foundation/foundation.dart'; import '../../web_kit/web_kit.dart'; +import 'webkit_navigation_delegate.dart'; import 'webkit_proxy.dart'; /// Object specifying creation parameters for a [WebKitWebViewController]. @@ -46,8 +47,22 @@ class WebKitWebViewController extends PlatformWebViewController { ? params : WebKitWebViewControllerCreationParams .fromPlatformWebViewControllerCreationParams(params)) { + final WeakReference weakThis = + WeakReference(this); _webView = webKitProxy.createWebView( - (params as WebKitWebViewControllerCreationParams)._configuration); + (params as WebKitWebViewControllerCreationParams)._configuration, + observeValue: ( + String keyPath, + NSObject object, + Map change, + ) { + if (weakThis.target != null && _onProgress != null) { + final double progress = + change[NSKeyValueChangeKey.newValue]! as double; + weakThis.target!._onProgress!((progress * 100).round()); + } + }, + ); } late final WKWebView _webView; @@ -56,6 +71,7 @@ class WebKitWebViewController extends PlatformWebViewController { {}; bool _zoomEnabled = true; + void Function(int progress)? _onProgress; @override Future loadFile(String absoluteFilePath) { @@ -270,6 +286,14 @@ class WebKitWebViewController extends PlatformWebViewController { } } + @override + Future setPlatformNavigationDelegate( + covariant WebKitNavigationDelegate handler, + ) { + _onProgress = handler.onProgress; + return _webView.setNavigationDelegate(handler.navigationDelegate); + } + Future _disableZoom() { const WKUserScript userScript = WKUserScript( "var meta = document.createElement('meta');\n" From 26a329b22f97f4e08c3d835cdcb62ec0422ba129 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Tue, 26 Jul 2022 16:33:47 -0700 Subject: [PATCH 13/19] setnavigationdelegatetest --- .../v4/webkit_navigation_delegate_test.dart | 160 ++++++------------ .../v4/webkit_webview_controller_test.dart | 39 +++++ 2 files changed, 93 insertions(+), 106 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_navigation_delegate_test.dart index ad8d21626191..074a546068d2 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_navigation_delegate_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_navigation_delegate_test.dart @@ -16,68 +16,19 @@ void main() { WidgetsFlutterBinding.ensureInitialized(); group('WebKitNavigationDelegate', () { - WKNavigationDelegate Function({ - void Function(WKWebView webView, String? url)? didFinishNavigation, - void Function(WKWebView webView, String? url)? - didStartProvisionalNavigation, - Future Function( - WKWebView webView, - WKNavigationAction navigationAction, - )? - decidePolicyForNavigationAction, - void Function(WKWebView webView, NSError error)? didFailNavigation, - void Function(WKWebView webView, NSError error)? - didFailProvisionalNavigation, - void Function(WKWebView webView)? webViewWebContentProcessDidTerminate, - }) createDetachedDelegate( - void Function(WKNavigationDelegate delegate) onCreateDelegate, - ) { - return ({ - void Function(WKWebView webView, String? url)? didFinishNavigation, - void Function(WKWebView webView, String? url)? - didStartProvisionalNavigation, - Future Function( - WKWebView webView, - WKNavigationAction navigationAction, - )? - decidePolicyForNavigationAction, - void Function(WKWebView webView, NSError error)? didFailNavigation, - void Function(WKWebView webView, NSError error)? - didFailProvisionalNavigation, - void Function(WKWebView webView)? webViewWebContentProcessDidTerminate, - }) { - final WKNavigationDelegate delegate = WKNavigationDelegate.detached( - didFinishNavigation: didFinishNavigation, - didStartProvisionalNavigation: didStartProvisionalNavigation, - decidePolicyForNavigationAction: decidePolicyForNavigationAction, - didFailNavigation: didFailNavigation, - didFailProvisionalNavigation: didFailProvisionalNavigation, - webViewWebContentProcessDidTerminate: - webViewWebContentProcessDidTerminate, - ); - onCreateDelegate(delegate); - return delegate; - }; - } - test('setOnPageFinished', () async { - late final WKNavigationDelegate navigationDelegate; - - final WebKitProxy webKitProxy = WebKitProxy( - createNavigationDelegate: createDetachedDelegate( - (WKNavigationDelegate delegate) => navigationDelegate = delegate, + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: const WebKitProxy( + createNavigationDelegate: CapturingNavigationDelegate.new, ), ); late final String callbackUrl; void onPageFinished(String url) => callbackUrl = url; - - final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( - const PlatformNavigationDelegateCreationParams(), - webKitProxy: webKitProxy, - ); webKitDelgate.setOnPageFinished(onPageFinished); - navigationDelegate.didFinishNavigation!( + + CapturingNavigationDelegate.lastCreatedDelegate.didFinishNavigation!( WKWebView.detached(), 'https://www.google.com', ); @@ -86,23 +37,19 @@ void main() { }); test('setOnPageStarted', () async { - late final WKNavigationDelegate navigationDelegate; - - final WebKitProxy webKitProxy = WebKitProxy( - createNavigationDelegate: createDetachedDelegate( - (WKNavigationDelegate delegate) => navigationDelegate = delegate, + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: const WebKitProxy( + createNavigationDelegate: CapturingNavigationDelegate.new, ), ); late final String callbackUrl; void onPageStarted(String url) => callbackUrl = url; - - final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( - const PlatformNavigationDelegateCreationParams(), - webKitProxy: webKitProxy, - ); webKitDelgate.setOnPageStarted(onPageStarted); - navigationDelegate.didStartProvisionalNavigation!( + + CapturingNavigationDelegate + .lastCreatedDelegate.didStartProvisionalNavigation!( WKWebView.detached(), 'https://www.google.com', ); @@ -111,11 +58,10 @@ void main() { }); test('onWebResourceError from didFailNavigation', () async { - late final WKNavigationDelegate navigationDelegate; - - final WebKitProxy webKitProxy = WebKitProxy( - createNavigationDelegate: createDetachedDelegate( - (WKNavigationDelegate delegate) => navigationDelegate = delegate, + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: const WebKitProxy( + createNavigationDelegate: CapturingNavigationDelegate.new, ), ); @@ -124,12 +70,9 @@ void main() { callbackError = error as WebKitWebResourceError; } - final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( - const PlatformNavigationDelegateCreationParams(), - webKitProxy: webKitProxy, - ); webKitDelgate.setOnWebResourceError(onWebResourceError); - navigationDelegate.didFailNavigation!( + + CapturingNavigationDelegate.lastCreatedDelegate.didFailNavigation!( WKWebView.detached(), const NSError( code: WKErrorCode.webViewInvalidated, @@ -145,11 +88,10 @@ void main() { }); test('onWebResourceError from didFailProvisionalNavigation', () async { - late final WKNavigationDelegate navigationDelegate; - - final WebKitProxy webKitProxy = WebKitProxy( - createNavigationDelegate: createDetachedDelegate( - (WKNavigationDelegate delegate) => navigationDelegate = delegate, + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: const WebKitProxy( + createNavigationDelegate: CapturingNavigationDelegate.new, ), ); @@ -158,12 +100,10 @@ void main() { callbackError = error as WebKitWebResourceError; } - final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( - const PlatformNavigationDelegateCreationParams(), - webKitProxy: webKitProxy, - ); webKitDelgate.setOnWebResourceError(onWebResourceError); - navigationDelegate.didFailProvisionalNavigation!( + + CapturingNavigationDelegate + .lastCreatedDelegate.didFailProvisionalNavigation!( WKWebView.detached(), const NSError( code: WKErrorCode.webViewInvalidated, @@ -180,11 +120,10 @@ void main() { test('onWebResourceError from webViewWebContentProcessDidTerminate', () async { - late final WKNavigationDelegate navigationDelegate; - - final WebKitProxy webKitProxy = WebKitProxy( - createNavigationDelegate: createDetachedDelegate( - (WKNavigationDelegate delegate) => navigationDelegate = delegate, + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: const WebKitProxy( + createNavigationDelegate: CapturingNavigationDelegate.new, ), ); @@ -193,12 +132,10 @@ void main() { callbackError = error as WebKitWebResourceError; } - final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( - const PlatformNavigationDelegateCreationParams(), - webKitProxy: webKitProxy, - ); webKitDelgate.setOnWebResourceError(onWebResourceError); - navigationDelegate.webViewWebContentProcessDidTerminate!( + + CapturingNavigationDelegate + .lastCreatedDelegate.webViewWebContentProcessDidTerminate!( WKWebView.detached(), ); @@ -212,11 +149,10 @@ void main() { }); test('onNavigationRequest from decidePolicyForNavigationAction', () async { - late final WKNavigationDelegate navigationDelegate; - - final WebKitProxy webKitProxy = WebKitProxy( - createNavigationDelegate: createDetachedDelegate( - (WKNavigationDelegate delegate) => navigationDelegate = delegate, + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: const WebKitProxy( + createNavigationDelegate: CapturingNavigationDelegate.new, ), ); @@ -231,14 +167,11 @@ void main() { return true; } - final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( - const PlatformNavigationDelegateCreationParams(), - webKitProxy: webKitProxy, - ); webKitDelgate.setOnNavigationRequest(onNavigationRequest); expect( - navigationDelegate.decidePolicyForNavigationAction!( + CapturingNavigationDelegate + .lastCreatedDelegate.decidePolicyForNavigationAction!( WKWebView.detached(), const WKNavigationAction( request: NSUrlRequest(url: 'https://www.google.com'), @@ -253,3 +186,18 @@ void main() { }); }); } + +class CapturingNavigationDelegate extends WKNavigationDelegate { + CapturingNavigationDelegate({ + super.didFinishNavigation, + super.didStartProvisionalNavigation, + super.didFailNavigation, + super.didFailProvisionalNavigation, + super.decidePolicyForNavigationAction, + super.webViewWebContentProcessDidTerminate, + }) : super.detached() { + lastCreatedDelegate = this; + } + static CapturingNavigationDelegate lastCreatedDelegate = + CapturingNavigationDelegate(); +} 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 index dcaada514b94..31054088e3a7 100644 --- 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 @@ -688,6 +688,30 @@ void main() { "var head = document.getElementsByTagName('head')[0];head.appendChild(meta);", ); }); + + test('setPlatformNavigationDelegate', () { + final MockWKWebView mockWebView = MockWKWebView(); + + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + final WebKitNavigationDelegate navigationDelegate = + WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: const WebKitProxy( + createNavigationDelegate: CapturingNavigationDelegate.new, + ), + ); + + controller.setPlatformNavigationDelegate(navigationDelegate); + + verify( + mockWebView.setNavigationDelegate( + CapturingNavigationDelegate.lastCreatedDelegate, + ), + ); + }); }); group('WebKitJavaScriptChannelParams', () { @@ -727,3 +751,18 @@ void main() { }); }); } + +class CapturingNavigationDelegate extends WKNavigationDelegate { + CapturingNavigationDelegate({ + super.didFinishNavigation, + super.didStartProvisionalNavigation, + super.didFailNavigation, + super.didFailProvisionalNavigation, + super.decidePolicyForNavigationAction, + super.webViewWebContentProcessDidTerminate, + }) : super.detached() { + lastCreatedDelegate = this; + } + static CapturingNavigationDelegate lastCreatedDelegate = + CapturingNavigationDelegate(); +} From d4639629f8c461c812b0b7e10eb15ec29832b55e Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Tue, 26 Jul 2022 16:53:16 -0700 Subject: [PATCH 14/19] test improvements and test attempt --- .../v4/webkit_navigation_delegate_test.dart | 19 ++++----- .../v4/webkit_webview_controller_test.dart | 40 +++++++++++++++++++ 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_navigation_delegate_test.dart index 074a546068d2..32d82f56b604 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_navigation_delegate_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_navigation_delegate_test.dart @@ -16,7 +16,7 @@ void main() { WidgetsFlutterBinding.ensureInitialized(); group('WebKitNavigationDelegate', () { - test('setOnPageFinished', () async { + test('setOnPageFinished', () { final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( const PlatformNavigationDelegateCreationParams(), webKitProxy: const WebKitProxy( @@ -25,8 +25,7 @@ void main() { ); late final String callbackUrl; - void onPageFinished(String url) => callbackUrl = url; - webKitDelgate.setOnPageFinished(onPageFinished); + webKitDelgate.setOnPageFinished((String url) => callbackUrl = url); CapturingNavigationDelegate.lastCreatedDelegate.didFinishNavigation!( WKWebView.detached(), @@ -36,7 +35,7 @@ void main() { expect(callbackUrl, 'https://www.google.com'); }); - test('setOnPageStarted', () async { + test('setOnPageStarted', () { final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( const PlatformNavigationDelegateCreationParams(), webKitProxy: const WebKitProxy( @@ -45,8 +44,7 @@ void main() { ); late final String callbackUrl; - void onPageStarted(String url) => callbackUrl = url; - webKitDelgate.setOnPageStarted(onPageStarted); + webKitDelgate.setOnPageStarted((String url) => callbackUrl = url); CapturingNavigationDelegate .lastCreatedDelegate.didStartProvisionalNavigation!( @@ -57,7 +55,7 @@ void main() { expect(callbackUrl, 'https://www.google.com'); }); - test('onWebResourceError from didFailNavigation', () async { + test('onWebResourceError from didFailNavigation', () { final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( const PlatformNavigationDelegateCreationParams(), webKitProxy: const WebKitProxy( @@ -87,7 +85,7 @@ void main() { expect(callbackError.errorType, WebResourceErrorType.webViewInvalidated); }); - test('onWebResourceError from didFailProvisionalNavigation', () async { + test('onWebResourceError from didFailProvisionalNavigation', () { final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( const PlatformNavigationDelegateCreationParams(), webKitProxy: const WebKitProxy( @@ -118,8 +116,7 @@ void main() { expect(callbackError.errorType, WebResourceErrorType.webViewInvalidated); }); - test('onWebResourceError from webViewWebContentProcessDidTerminate', - () async { + test('onWebResourceError from webViewWebContentProcessDidTerminate', () { final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( const PlatformNavigationDelegateCreationParams(), webKitProxy: const WebKitProxy( @@ -148,7 +145,7 @@ void main() { ); }); - test('onNavigationRequest from decidePolicyForNavigationAction', () async { + test('onNavigationRequest from decidePolicyForNavigationAction', () { final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( const PlatformNavigationDelegateCreationParams(), webKitProxy: const WebKitProxy( 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 index 31054088e3a7..2edd1bd3042e 100644 --- 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 @@ -712,6 +712,46 @@ void main() { ), ); }); + + test('setPlatformNavigationDelegate onProgress', () { + late final WKWebView webView; + + final WebKitProxy webKitProxy = WebKitProxy( + createWebView: ( + _, { + void Function( + String keyPath, + NSObject object, + Map change, + )? + observeValue, + }) { + webView = WKWebView.detached(observeValue: observeValue); + return webView; + }, + createNavigationDelegate: CapturingNavigationDelegate.new, + ); + + final WebKitNavigationDelegate navigationDelegate = + WebKitNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + webKitProxy: webKitProxy, + ); + + late final int callbackProgress; + navigationDelegate.setOnProgress( + (int progress) => callbackProgress = progress, + ); + + final WebKitWebViewController controller = WebKitWebViewController( + const PlatformWebViewControllerCreationParams(), + webKitProxy: webKitProxy, + ); + controller.setPlatformNavigationDelegate(navigationDelegate); + + navigationDelegate.onProgress!(0); + expect(callbackProgress, 0); + }); }); group('WebKitJavaScriptChannelParams', () { From 2c66ce546e06e0cba356e627e1fd49a71dc628a3 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Tue, 26 Jul 2022 17:32:37 -0700 Subject: [PATCH 15/19] call observe value --- .../src/v4/src/webkit_webview_controller.dart | 7 +++++ .../v4/webkit_webview_controller_test.dart | 26 +++++-------------- 2 files changed, 13 insertions(+), 20 deletions(-) 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 index df4371c6f64d..a81bff446b19 100644 --- 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 @@ -63,6 +63,13 @@ class WebKitWebViewController extends PlatformWebViewController { } }, ); + _webView.addObserver( + _webView, + keyPath: 'estimatedProgress', + options: { + NSKeyValueObservingOptions.newValue, + }, + ); } late final WKWebView _webView; 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 index 2edd1bd3042e..3743fa13d7f4 100644 --- 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 @@ -714,28 +714,18 @@ void main() { }); test('setPlatformNavigationDelegate onProgress', () { - late final WKWebView webView; + final MockWKWebView mockWebView = MockWKWebView(); - final WebKitProxy webKitProxy = WebKitProxy( - createWebView: ( - _, { - void Function( - String keyPath, - NSObject object, - Map change, - )? - observeValue, - }) { - webView = WKWebView.detached(observeValue: observeValue); - return webView; - }, - createNavigationDelegate: CapturingNavigationDelegate.new, + final WebKitWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, ); final WebKitNavigationDelegate navigationDelegate = WebKitNavigationDelegate( const PlatformNavigationDelegateCreationParams(), - webKitProxy: webKitProxy, + webKitProxy: const WebKitProxy( + createNavigationDelegate: CapturingNavigationDelegate.new, + ), ); late final int callbackProgress; @@ -743,10 +733,6 @@ void main() { (int progress) => callbackProgress = progress, ); - final WebKitWebViewController controller = WebKitWebViewController( - const PlatformWebViewControllerCreationParams(), - webKitProxy: webKitProxy, - ); controller.setPlatformNavigationDelegate(navigationDelegate); navigationDelegate.onProgress!(0); From ee96f37f660485784d87da145896b6ef8e379e96 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Tue, 26 Jul 2022 17:52:56 -0700 Subject: [PATCH 16/19] use creator in method --- .../v4/webkit_webview_controller_test.dart | 95 +++++++++++-------- 1 file changed, 56 insertions(+), 39 deletions(-) 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 index 3743fa13d7f4..4f620ad75978 100644 --- 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 @@ -38,24 +38,21 @@ void main() { MockWKPreferences? mockPreferences, MockWKUserContentController? mockUserContentController, MockWKWebsiteDataStore? mockWebsiteDataStore, - MockWKWebView? mockWebView, + MockWKWebView Function( + WKWebViewConfiguration configuration, { + void Function( + String keyPath, + NSObject object, + Map change, + )? + observeValue, + })? + createMockWebView, MockWKWebViewConfiguration? mockWebViewConfiguration, }) { final MockWKWebViewConfiguration nonNullMockWebViewConfiguration = mockWebViewConfiguration ?? MockWKWebViewConfiguration(); - final MockWKWebView nonNullMockWebView = mockWebView ?? MockWKWebView(); - - 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()); + late final MockWKWebView nonNullMockWebView; final PlatformWebViewControllerCreationParams controllerCreationParams = WebKitWebViewControllerCreationParams( @@ -64,7 +61,7 @@ void main() { ), ); - return WebKitWebViewController( + final WebKitWebViewController controller = WebKitWebViewController( controllerCreationParams, webKitProxy: WebKitProxy( createWebView: ( @@ -76,17 +73,37 @@ void main() { )? 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( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.loadFile('/path/to/file.html'); @@ -100,7 +117,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.loadFlutterAsset('test_assets/index.html'); @@ -111,7 +128,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); const String htmlString = 'Test data.'; @@ -128,7 +145,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); expect( @@ -147,7 +164,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.loadRequest( @@ -170,7 +187,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.loadRequest( @@ -193,7 +210,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.loadRequest(LoadRequestParams( @@ -213,7 +230,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.loadRequest(LoadRequestParams( @@ -239,7 +256,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.canGoBack()).thenAnswer( @@ -252,7 +269,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.canGoForward()).thenAnswer( @@ -265,7 +282,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.goBack(); @@ -276,7 +293,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.goForward(); @@ -287,7 +304,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.reload(); @@ -298,7 +315,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.enableGestureNavigation(true); @@ -309,7 +326,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( @@ -325,7 +342,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( @@ -341,7 +358,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( @@ -358,7 +375,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.evaluateJavaScript('runJavaScript')) @@ -380,7 +397,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.getTitle()) @@ -392,7 +409,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.getUrl()) @@ -469,7 +486,7 @@ void main() { final MockUIScrollView mockScrollView = MockUIScrollView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, mockScrollView: mockScrollView, ); @@ -484,7 +501,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.setUserAgent('MyUserAgent'); @@ -693,7 +710,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); final WebKitNavigationDelegate navigationDelegate = @@ -717,7 +734,7 @@ void main() { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( - mockWebView: mockWebView, + createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); final WebKitNavigationDelegate navigationDelegate = From f134aa3d6f72850bcfe88bcccfa6c017007cad22 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Tue, 26 Jul 2022 17:59:49 -0700 Subject: [PATCH 17/19] finish tests --- .../v4/webkit_webview_controller_test.dart | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) 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 index 4f620ad75978..2ebc8619526c 100644 --- 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 @@ -730,11 +730,38 @@ void main() { ); }); - test('setPlatformNavigationDelegate onProgress', () { + test('setPlatformNavigationDelegate onProgress', () async { final MockWKWebView mockWebView = MockWKWebView(); + late final void Function( + String keyPath, + NSObject object, + Map change, + ) webViewObserveValue; + final WebKitWebViewController controller = createControllerWithMocks( - createMockWebView: (_, {dynamic observeValue}) => mockWebView, + createMockWebView: ( + _, { + void Function( + String keyPath, + NSObject object, + Map change, + )? + observeValue, + }) { + webViewObserveValue = observeValue!; + return mockWebView; + }, + ); + + verify( + mockWebView.addObserver( + mockWebView, + keyPath: 'estimatedProgress', + options: { + NSKeyValueObservingOptions.newValue, + }, + ), ); final WebKitNavigationDelegate navigationDelegate = @@ -750,9 +777,14 @@ void main() { (int progress) => callbackProgress = progress, ); - controller.setPlatformNavigationDelegate(navigationDelegate); + await controller.setPlatformNavigationDelegate(navigationDelegate); + + webViewObserveValue( + 'estimatedProgress', + mockWebView, + {NSKeyValueChangeKey.newValue: 0.0}, + ); - navigationDelegate.onProgress!(0); expect(callbackProgress, 0); }); }); From 41af992830d863e919fd860f74a73af0c32fe146 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 27 Jul 2022 16:45:25 -0700 Subject: [PATCH 18/19] more documentation --- .../lib/src/v4/src/webkit_navigation_delegate.dart | 3 +++ .../lib/src/v4/webview_flutter_wkwebview.dart | 1 + 2 files changed, 4 insertions(+) diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart index 6cc31e358126..10b1fab52e03 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/v4/src/webkit_navigation_delegate.dart @@ -110,6 +110,7 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate { ); } + // Used to set `WKWebView.setNavigationDelegate` in `WebKitWebViewController`. /// WebKit class that handles navigation changes and tracking navigation /// requests. late final WKNavigationDelegate navigationDelegate; @@ -121,6 +122,8 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate { FutureOr Function({required String url, required bool isForMainFrame})? _onNavigationRequest; + // `WKWebView` in `WebKitWebViewController` uses this to track loading + // progress. This can't be done with `WKNavigationDelegate`. /// Callback method that receives progress of a loading page. void Function(int progress)? get onProgress => _onProgress; 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 index 990f61c72bb0..6c5cd93f11d7 100644 --- 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 @@ -6,3 +6,4 @@ library webview_flutter_wkwebview; export 'src/webkit_navigation_delegate.dart'; export 'src/webkit_webview_controller.dart'; +export 'src/webkit_webview_controller.dart'; From 45704c2763911ddad9dd8436312c8c557e1d708f Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 11 Aug 2022 15:15:26 -0700 Subject: [PATCH 19/19] shorten bool line --- .../lib/src/v4/src/webkit_webview_controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index a81bff446b19..dc0693e5f9a5 100644 --- 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 @@ -56,7 +56,7 @@ class WebKitWebViewController extends PlatformWebViewController { NSObject object, Map change, ) { - if (weakThis.target != null && _onProgress != null) { + if (weakThis.target?._onProgress != null) { final double progress = change[NSKeyValueChangeKey.newValue]! as double; weakThis.target!._onProgress!((progress * 100).round());