diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index f278cf9f9d5b..329ce485d129 100644 --- a/packages/webview_flutter/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md @@ -1,11 +1,11 @@ -## NEXT +## 4.0.0 -* Updates code for `no_leading_underscores_for_local_identifiers` lint. -* Updates minimum Flutter version to 2.10. -* Fixes avoid_redundant_argument_values lint warnings and minor typos. -* Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/104231). +* **BREAKING CHANGE** Updates implementation to use the `2.0.0` release of + `webview_flutter_platform_interface`. See `Usage` section in the README for updated usage. See + `Migrating from 3.0 to 4.0` section in the README for details on migrating to this version. +* Updates minimum Flutter version to 3.0.0. +* Updates code for new analysis options. * Updates references to the obsolete master branch. -* Fixes typo from lowercase to uppercase. ## 3.0.4 diff --git a/packages/webview_flutter/webview_flutter/README.md b/packages/webview_flutter/webview_flutter/README.md index ffe91441326d..98f7b667025b 100644 --- a/packages/webview_flutter/webview_flutter/README.md +++ b/packages/webview_flutter/webview_flutter/README.md @@ -1,10 +1,12 @@ # WebView for Flutter + + [![pub package](https://img.shields.io/pub/v/webview_flutter.svg)](https://pub.dev/packages/webview_flutter) A Flutter plugin that provides a WebView widget. -On iOS the WebView widget is backed by a [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview); +On iOS the WebView widget is backed by a [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview). On Android the WebView widget is backed by a [WebView](https://developer.android.com/reference/android/webkit/WebView). | | Android | iOS | @@ -12,30 +14,60 @@ On Android the WebView widget is backed by a [WebView](https://developer.android | **Support** | SDK 19+ or 20+ | 9.0+ | ## Usage -Add `webview_flutter` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). If you are targeting Android, make sure to read the *Android Platform Views* section below to choose the platform view mode that best suits your needs. - -You can now include a WebView widget in your widget tree. See the -[WebView](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebView-class.html) -widget's Dartdoc for more details on how to use the widget. +Add `webview_flutter` as a [dependency in your pubspec.yaml file](https://pub.dev/packages/webview_flutter/install). + +You can now display a WebView by: + +1. Instantiating a [WebViewController](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html). + + +```dart +controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x00000000)) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + // Update loading bar. + }, + onPageStarted: (String url) {}, + onPageFinished: (String url) {}, + onWebResourceError: (WebResourceError error) {}, + onNavigationRequest: (NavigationRequest request) { + if (request.url.startsWith('https://www.youtube.com/')) { + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; + }, + ), + ) + ..loadRequest(Uri.parse('https://flutter.dev')); +``` -## Android Platform Views -This plugin uses -[Platform Views](https://flutter.dev/docs/development/platform-integration/platform-views) to embed -the Android’s webview within the Flutter app. It supports two modes: -*hybrid composition* (the current default) and *virtual display*. +2. Passing the controller to a [WebViewWidget](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html). -Here are some points to consider when choosing between the two: + +```dart +@override +Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Flutter Simple Example')), + body: WebViewWidget(controller: controller), + ); +} +``` -* *Hybrid composition* has built-in keyboard support while *virtual display* has multiple -[keyboard issues](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Avd-only+label%3A%22p%3A+webview-keyboard%22). -* *Hybrid composition* requires Android SDK 19+ while *virtual display* requires Android SDK 20+. -* *Hybrid composition* and *virtual display* have different - [performance tradeoffs](https://flutter.dev/docs/development/platform-integration/platform-views#performance). +See the Dartdocs for [WebViewController](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html) +and [WebViewWidget](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html) +for more details. +### Android Platform Views -### Using Hybrid Composition +This plugin uses +[Platform Views](https://flutter.dev/docs/development/platform-integration/platform-views) to embed +the Android’s WebView within the Flutter app. -The mode is currently enabled by default. You should however make sure to set the correct `minSdkVersion` in `android/app/build.gradle` if it was previously lower than 19: +You should however make sure to set the correct `minSdkVersion` in `android/app/build.gradle` if it was previously lower than 19: ```groovy android { @@ -45,47 +77,67 @@ android { } ``` -### Using Virtual displays +### Platform-Specific Features -1. Set the correct `minSdkVersion` in `android/app/build.gradle` (if it was previously lower than 20): +Many classes have a subclass or an underlying implementation that provides access to platform-specific +features. - ```groovy - android { - defaultConfig { - minSdkVersion 20 - } - } - ``` +To access platform-specific features, start by adding the platform implementation packages to your +app or package: -2. Set `WebView.platform = AndroidWebView();` in `initState()`. - For example: +* **Android**: [webview_flutter_android](https://pub.dev/packages/webview_flutter_android/install) +* **iOS**: [webview_flutter_wkwebview](https://pub.dev/packages/webview_flutter_wkwebview/install) - ```dart - import 'dart:io'; +Next, add the imports of the implementation packages to your app or package: - import 'package:webview_flutter/webview_flutter.dart'; + +```dart +// Import for Android features. +import 'package:webview_flutter_android/webview_flutter_android.dart'; +// Import for iOS features. +import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; +``` - class WebViewExample extends StatefulWidget { - @override - WebViewExampleState createState() => WebViewExampleState(); - } +Now, additional features can be accessed through the platform implementations. Classes +`WebViewController`, `WebViewWidget`, `NavigationDelegate`, and `WebViewCookieManager` pass their +functionality to a class provided by the current platform. Below are a couple of ways to access +additional functionality provided by the platform and is followed by an example. + +1. Pass a creation params class provided by a platform implementation to a `fromPlatformCreationParams` + constructor (e.g. `WebViewController.fromPlatformCreationParams`, + `WebViewWidget.fromPlatformCreationParams`, etc.). +2. Call methods on a platform implementation of a class by using the `platform` field (e.g. + `WebViewController.platform`, `WebViewWidget.platform`, etc.). + +Below is an example of setting additional iOS and Android parameters on the `WebViewController`. + + +```dart +late final PlatformWebViewControllerCreationParams params; +if (WebViewPlatform.instance is WebKitWebViewPlatform) { + params = WebKitWebViewControllerCreationParams( + allowsInlineMediaPlayback: true, + mediaTypesRequiringUserAction: const {}, + ); +} else { + params = const PlatformWebViewControllerCreationParams(); +} - class WebViewExampleState extends State { - @override - void initState() { - super.initState(); - // Enable virtual display. - if (Platform.isAndroid) WebView.platform = AndroidWebView(); - } - - @override - Widget build(BuildContext context) { - return WebView( - initialUrl: 'https://flutter.dev', - ); - } - } - ``` +final WebViewController controller = + WebViewController.fromPlatformCreationParams(params); +// ··· +if (controller.platform is AndroidWebViewController) { + AndroidWebViewController.enableDebugging(true); + (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); +} +``` + +See https://pub.dev/documentation/webview_flutter_android/latest/webview_flutter_android/webview_flutter_android-library.html +for more details on Android features. + +See https://pub.dev/documentation/webview_flutter_wkwebview/latest/webview_flutter_wkwebview/webview_flutter_wkwebview-library.html +for more details on iOS features. ### Enable Material Components for Android @@ -95,4 +147,76 @@ follow the steps described in the [Enabling Material Components instructions](ht ### Setting custom headers on POST requests Currently, setting custom headers when making a post request with the WebViewController's `loadRequest` method is not supported on Android. -If you require this functionality, a workaround is to make the request manually, and then load the response data using `loadHTMLString` instead. +If you require this functionality, a workaround is to make the request manually, and then load the response data using `loadHtmlString` instead. + +## Migrating from 3.0 to 4.0 + +### Instantiating WebViewController + +In version 3.0 and below, `WebViewController` could only be retrieved in a callback after the +`WebView` was added to the widget tree. Now, `WebViewController` must be instantiated and can be +used before it is added to the widget tree. See `Usage` section above for an example. + +### Replacing WebView Functionality + +The `WebView` class has been removed and its functionality has been split into `WebViewController` +and `WebViewWidget`. + +`WebViewController` handles all functionality that is associated with the underlying web view +provided by each platform. (e.g., loading a url, setting the background color of the underlying +platform view, or clearing the cache). + +`WebViewWidget` takes a `WebViewController` and handles all Flutter widget related functionality +(e.g., layout direction, gesture recognizers). + +See the Dartdocs for [WebViewController](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html) +and [WebViewWidget](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html) +for more details. + +### PlatformView Implementation on Android + +The PlatformView implementation for Android is currently no longer configurable. It uses Texture +Layer Hybrid Composition on versions 23+ and automatically fallbacks to Hybrid Composition for +version 19-23. See https://github.com/flutter/flutter/issues/108106 for progress on manually +switching to Hybrid Composition on versions 23+. + +### API Changes + +Below is a non-exhaustive list of changes to the API: + +* `WebViewController.clearCache` no longer clears local storage. Please use + `WebViewController.clearLocalStorage`. +* `WebViewController.clearCache` no longer reloads the page. +* `WebViewController.loadUrl` has been removed. Please use `WebViewController.loadRequest`. +* `WebViewController.evaluateJavascript` has been removed. Please use + `WebViewController.runJavaScript` or `WebViewController.runJavaScriptReturningResult`. +* `WebViewController.getScrollX` and `WebViewController.getScrollY` have been removed and have + been replaced by `WebViewController.getScrollPosition`. +* `WebViewController.runJavaScriptReturningResult` now returns an `Object` and not a `String`. This + will attempt to return a `bool` or `num` if the return value can be parsed. +* `CookieManager` is replaced by `WebViewCookieManager`. +* `NavigationDelegate.onWebResourceError` callback includes errors that are not from the main frame. + Use the `WebResourceError.isForMainFrame` field to filter errors. +* The following fields from `WebView` have been moved to `NavigationDelegate`. They can be added to + a WebView with `WebViewController.setNavigationDelegate`. + * `WebView.navigationDelegate` -> `NavigationDelegate.onNavigationRequest` + * `WebView.onPageStarted` -> `NavigationDelegate.onPageStarted` + * `WebView.onPageFinished` -> `NavigationDelegate.onPageFinished` + * `WebView.onProgress` -> `NavigationDelegate.onProgress` + * `WebView.onWebResourceError` -> `NavigationDelegate.onWebResourceError` +* The following fields from `WebView` have been moved to `WebViewController`: + * `WebView.javascriptMode` -> `WebViewController.setJavaScriptMode` + * `WebView.javascriptChannels` -> + `WebViewController.addJavaScriptChannel`/`WebViewController.removeJavaScriptChannel` + * `WebView.zoomEnabled` -> `WebViewController.enableZoom` + * `WebView.userAgent` -> `WebViewController.setUserAgent` + * `WebView.backgroundColor` -> `WebViewController.setBackgroundColor` +* The following features have been moved to an Android implementation class. See section + `Platform-Specific Features` for details on accessing Android platform specific features. + * `WebView.debuggingEnabled` -> `static AndroidWebViewController.enableDebugging` + * `WebView.initialMediaPlaybackPolicy` -> `AndroidWebViewController.setMediaPlaybackRequiresUserGesture` +* The following features have been moved to an iOS implementation class. See section + `Platform-Specific Features` for details on accessing iOS platform specific features. + * `WebView.gestureNavigationEnabled` -> `WebKitWebViewController.setAllowsBackForwardNavigationGestures` + * `WebView.initialMediaPlaybackPolicy` -> `WebKitWebViewControllerCreationParams.mediaTypesRequiringUserAction` + * `WebView.allowsInlineMediaPlayback` -> `WebKitWebViewControllerCreationParams.allowsInlineMediaPlayback` diff --git a/packages/webview_flutter/webview_flutter/example/build.excerpt.yaml b/packages/webview_flutter/webview_flutter/example/build.excerpt.yaml new file mode 100644 index 000000000000..46c1e754361f --- /dev/null +++ b/packages/webview_flutter/webview_flutter/example/build.excerpt.yaml @@ -0,0 +1,15 @@ +targets: + $default: + sources: + include: + - lib/** + # Some default includes that aren't really used here but will prevent + # false-negative warnings: + - $package$ + - lib/$lib$ + exclude: + - '**/.*/**' + - '**/build/**' + builders: + code_excerpter|code_excerpter: + enabled: true \ No newline at end of file diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/legacy/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/legacy/webview_flutter_test.dart new file mode 100644 index 000000000000..14539105d5d3 --- /dev/null +++ b/packages/webview_flutter/webview_flutter/example/integration_test/legacy/webview_flutter_test.dart @@ -0,0 +1,1382 @@ +// 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. + +// This test is run using `flutter drive` by the CI (see /script/tool/README.md +// in this repository for details on driving that tooling manually), but can +// also be run using `flutter test` directly during development. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) +// ignore: unnecessary_import +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:webview_flutter/src/webview_flutter_legacy.dart'; + +Future main() async { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0); + server.forEach((HttpRequest request) { + if (request.uri.path == '/hello.txt') { + request.response.writeln('Hello, world.'); + } else if (request.uri.path == '/secondary.txt') { + request.response.writeln('How are you today?'); + } else if (request.uri.path == '/headers') { + request.response.writeln('${request.headers}'); + } else if (request.uri.path == '/favicon.ico') { + request.response.statusCode = HttpStatus.notFound; + } else { + fail('unexpected request: ${request.method} ${request.uri}'); + } + request.response.close(); + }); + final String prefixUrl = 'http://${server.address.address}:${server.port}'; + final String primaryUrl = '$prefixUrl/hello.txt'; + final String secondaryUrl = '$prefixUrl/secondary.txt'; + final String headersUrl = '$prefixUrl/headers'; + + testWidgets('initialUrl', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageFinishedCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: pageFinishedCompleter.complete, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageFinishedCompleter.future; + + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets('loadUrl', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = StreamController(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: (String url) { + pageLoads.add(url); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + + await controller.loadUrl(secondaryUrl); + await expectLater( + pageLoads.stream.firstWhere((String url) => url == secondaryUrl), + completion(secondaryUrl), + ); + }); + + testWidgets('evaluateJavascript', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + // ignore: deprecated_member_use + final String result = await controller.evaluateJavascript('1 + 1'); + expect(result, equals('2')); + }); + + testWidgets('loadUrl with headers', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageStarts = StreamController(); + final StreamController pageLoads = StreamController(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarts.add(url); + }, + onPageFinished: (String url) { + pageLoads.add(url); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + final Map headers = { + 'test_header': 'flutter_test_header' + }; + await controller.loadUrl(headersUrl, headers: headers); + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, headersUrl); + + await pageStarts.stream.firstWhere((String url) => url == currentUrl); + await pageLoads.stream.firstWhere((String url) => url == currentUrl); + + final String content = await controller + .runJavascriptReturningResult('document.documentElement.innerText'); + expect(content.contains('flutter_test_header'), isTrue); + }); + + testWidgets('JavascriptChannel', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageStarted = Completer(); + final Completer pageLoaded = Completer(); + final Completer channelCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + // This is the data URL for: '' + initialUrl: + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + javascriptChannels: { + JavascriptChannel( + name: 'Echo', + onMessageReceived: (JavascriptMessage message) { + channelCompleter.complete(message.message); + }, + ), + }, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + expect(channelCompleter.isCompleted, isFalse); + await controller.runJavascript('Echo.postMessage("hello");'); + + await expectLater(channelCompleter.future, completion('hello')); + }); + + testWidgets('resize webview', (WidgetTester tester) async { + final Completer initialResizeCompleter = Completer(); + final Completer buttonTapResizeCompleter = Completer(); + final Completer onPageFinished = Completer(); + + bool resizeButtonTapped = false; + await tester.pumpWidget(ResizableWebView( + onResize: (_) { + if (resizeButtonTapped) { + buttonTapResizeCompleter.complete(); + } else { + initialResizeCompleter.complete(); + } + }, + onPageFinished: () => onPageFinished.complete(), + )); + await onPageFinished.future; + // Wait for a potential call to resize after page is loaded. + await initialResizeCompleter.future.timeout( + const Duration(seconds: 3), + onTimeout: () => null, + ); + + resizeButtonTapped = true; + await tester.tap(find.byKey(const ValueKey('resizeButton'))); + await tester.pumpAndSettle(); + expect(buttonTapResizeCompleter.future, completes); + }); + + testWidgets('set custom userAgent', (WidgetTester tester) async { + final Completer controllerCompleter1 = + Completer(); + final GlobalKey globalKey = GlobalKey(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: 'about:blank', + javascriptMode: JavascriptMode.unrestricted, + userAgent: 'Custom_User_Agent1', + onWebViewCreated: (WebViewController controller) { + controllerCompleter1.complete(controller); + }, + ), + ), + ); + final WebViewController controller1 = await controllerCompleter1.future; + final String customUserAgent1 = await _getUserAgent(controller1); + expect(customUserAgent1, 'Custom_User_Agent1'); + // rebuild the WebView with a different user agent. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: 'about:blank', + javascriptMode: JavascriptMode.unrestricted, + userAgent: 'Custom_User_Agent2', + ), + ), + ); + + final String customUserAgent2 = await _getUserAgent(controller1); + expect(customUserAgent2, 'Custom_User_Agent2'); + }); + + testWidgets('use default platform userAgent after webView is rebuilt', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final GlobalKey globalKey = GlobalKey(); + // Build the webView with no user agent to get the default platform user agent. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: primaryUrl, + javascriptMode: JavascriptMode.unrestricted, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + final String defaultPlatformUserAgent = await _getUserAgent(controller); + // rebuild the WebView with a custom user agent. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: 'about:blank', + javascriptMode: JavascriptMode.unrestricted, + userAgent: 'Custom_User_Agent', + ), + ), + ); + final String customUserAgent = await _getUserAgent(controller); + expect(customUserAgent, 'Custom_User_Agent'); + // rebuilds the WebView with no user agent. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: 'about:blank', + javascriptMode: JavascriptMode.unrestricted, + ), + ), + ); + + final String customUserAgent2 = await _getUserAgent(controller); + expect(customUserAgent2, defaultPlatformUserAgent); + }); + + group('Video playback policy', () { + late String videoTestBase64; + setUpAll(() async { + final ByteData videoData = + await rootBundle.load('assets/sample_video.mp4'); + final String base64VideoData = + base64Encode(Uint8List.view(videoData.buffer)); + final String videoTest = ''' + + Video auto play + + + + + + + '''; + videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); + }); + + testWidgets('Auto media playback', (WidgetTester tester) async { + Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + String isPaused = + await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + + controllerCompleter = Completer(); + pageLoaded = Completer(); + + // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + controller = await controllerCompleter.future; + await pageLoaded.future; + + isPaused = await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(true)); + }); + + testWidgets('Changes to initialMediaPlaybackPolicy are ignored', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + + final GlobalKey key = GlobalKey(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: key, + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + String isPaused = + await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + + pageLoaded = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: key, + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + await controller.reload(); + + await pageLoaded.future; + + isPaused = await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + }); + + testWidgets('Video plays inline when allowsInlineMediaPlayback is true', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageLoaded = Completer(); + final Completer videoPlaying = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + javascriptChannels: { + JavascriptChannel( + name: 'VideoTestTime', + onMessageReceived: (JavascriptMessage message) { + final double currentTime = double.parse(message.message); + // Let it play for at least 1 second to make sure the related video's properties are set. + if (currentTime > 1 && !videoPlaying.isCompleted) { + videoPlaying.complete(null); + } + }, + ), + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + allowsInlineMediaPlayback: true, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + // Pump once to trigger the video play. + await tester.pump(); + + // Makes sure we get the correct event that indicates the video is actually playing. + await videoPlaying.future; + + final String fullScreen = + await controller.runJavascriptReturningResult('isFullScreen();'); + expect(fullScreen, _webviewBool(false)); + }); + + // allowsInlineMediaPlayback is a noop on Android, so it is skipped. + testWidgets( + 'Video plays full screen when allowsInlineMediaPlayback is false', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageLoaded = Completer(); + final Completer videoPlaying = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + javascriptChannels: { + JavascriptChannel( + name: 'VideoTestTime', + onMessageReceived: (JavascriptMessage message) { + final double currentTime = double.parse(message.message); + // Let it play for at least 1 second to make sure the related video's properties are set. + if (currentTime > 1 && !videoPlaying.isCompleted) { + videoPlaying.complete(null); + } + }, + ), + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + // Pump once to trigger the video play. + await tester.pump(); + + // Makes sure we get the correct event that indicates the video is actually playing. + await videoPlaying.future; + + final String fullScreen = + await controller.runJavascriptReturningResult('isFullScreen();'); + expect(fullScreen, _webviewBool(true)); + }, skip: Platform.isAndroid); + }); + + group('Audio playback policy', () { + late String audioTestBase64; + setUpAll(() async { + final ByteData audioData = + await rootBundle.load('assets/sample_audio.ogg'); + final String base64AudioData = + base64Encode(Uint8List.view(audioData.buffer)); + final String audioTest = ''' + + Audio auto play + + + + + + + '''; + audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); + }); + + testWidgets('Auto media playback', (WidgetTester tester) async { + Completer controllerCompleter = + Completer(); + Completer pageStarted = Completer(); + Completer pageLoaded = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + WebViewController controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + String isPaused = + await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + + controllerCompleter = Completer(); + pageStarted = Completer(); + pageLoaded = Completer(); + + // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + isPaused = await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(true)); + }); + + testWidgets('Changes to initialMediaPlaybackPolicy are ignored', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + Completer pageStarted = Completer(); + Completer pageLoaded = Completer(); + + final GlobalKey key = GlobalKey(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: key, + initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + String isPaused = + await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + + pageStarted = Completer(); + pageLoaded = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: key, + initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + await controller.reload(); + + await pageStarted.future; + await pageLoaded.future; + + isPaused = await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + }); + }); + + testWidgets('getTitle', (WidgetTester tester) async { + const String getTitleTest = ''' + + Some title + + + + + '''; + final String getTitleTestBase64 = + base64Encode(const Utf8Encoder().convert(getTitleTest)); + final Completer pageStarted = Completer(); + final Completer pageLoaded = Completer(); + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + initialUrl: 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', + javascriptMode: JavascriptMode.unrestricted, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + // On at least iOS, it does not appear to be guaranteed that the native + // code has the title when the page load completes. Execute some JavaScript + // before checking the title to ensure that the page has been fully parsed + // and processed. + await controller.runJavascript('1;'); + + final String? title = await controller.getTitle(); + expect(title, 'Some title'); + }); + + group('Programmatic Scroll', () { + testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { + const String scrollTestPage = ''' + + + + + + +
+ + + '''; + + final String scrollTestPageBase64 = + base64Encode(const Utf8Encoder().convert(scrollTestPage)); + + final Completer pageLoaded = Completer(); + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + initialUrl: + 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + await tester.pumpAndSettle(const Duration(seconds: 3)); + + int scrollPosX = await controller.getScrollX(); + int scrollPosY = await controller.getScrollY(); + + // Check scrollTo() + const int X_SCROLL = 123; + const int Y_SCROLL = 321; + // Get the initial position; this ensures that scrollTo is actually + // changing something, but also gives the native view's scroll position + // time to settle. + expect(scrollPosX, isNot(X_SCROLL)); + expect(scrollPosX, isNot(Y_SCROLL)); + + await controller.scrollTo(X_SCROLL, Y_SCROLL); + scrollPosX = await controller.getScrollX(); + scrollPosY = await controller.getScrollY(); + expect(scrollPosX, X_SCROLL); + expect(scrollPosY, Y_SCROLL); + + // Check scrollBy() (on top of scrollTo()) + await controller.scrollBy(X_SCROLL, Y_SCROLL); + scrollPosX = await controller.getScrollX(); + scrollPosY = await controller.getScrollY(); + expect(scrollPosX, X_SCROLL * 2); + expect(scrollPosY, Y_SCROLL * 2); + }); + }); + + // Minimal end-to-end testing of the legacy Android implementation. + group('AndroidWebView (virtual display)', () { + setUpAll(() { + WebView.platform = AndroidWebView(); + }); + + tearDownAll(() { + WebView.platform = null; + }); + + testWidgets('initialUrl', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageFinishedCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: pageFinishedCompleter.complete, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageFinishedCompleter.future; + + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + }, skip: !Platform.isAndroid); + + group('NavigationDelegate', () { + const String blankPage = ''; + final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + '${base64Encode(const Utf8Encoder().convert(blankPage))}'; + + testWidgets('can allow requests', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) { + return (request.url.contains('youtube.com')) + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript('location.href = "$secondaryUrl"'); + + await pageLoads.stream.first; // Wait for the next page load. + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, secondaryUrl); + }); + + testWidgets('onWebResourceError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'https://www.notawebsite..com', + onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + }, + ), + ), + ); + + final WebResourceError error = await errorCompleter.future; + expect(error, isNotNull); + + if (Platform.isIOS) { + expect(error.domain, isNotNull); + expect(error.failingUrl, isNull); + } else if (Platform.isAndroid) { + expect(error.errorType, isNotNull); + expect(error.failingUrl?.startsWith('https://www.notawebsite..com'), + isTrue); + } + }); + + testWidgets('onWebResourceError is not called with valid url', + (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + }, + onPageFinished: (_) => pageFinishCompleter.complete(), + ), + ), + ); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + + testWidgets( + 'onWebResourceError only called for main frame', + (WidgetTester tester) async { + const String iframeTest = ''' + + + + WebResourceError test + + + + + + '''; + final String iframeTestBase64 = + base64Encode(const Utf8Encoder().convert(iframeTest)); + + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: + 'data:text/html;charset=utf-8;base64,$iframeTestBase64', + onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + }, + onPageFinished: (_) => pageFinishCompleter.complete(), + ), + ), + ); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }, + ); + + testWidgets('can block requests', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) { + return (request.url.contains('youtube.com')) + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller + .runJavascript('location.href = "https://www.youtube.com/"'); + + // There should never be any second page load, since our new URL is + // blocked. Still wait for a potential page change for some time in order + // to give the test a chance to fail. + await pageLoads.stream.first + .timeout(const Duration(milliseconds: 500), onTimeout: () => ''); + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, isNot(contains('youtube.com'))); + }); + + testWidgets('supports asynchronous decisions', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) async { + NavigationDecision decision = NavigationDecision.prevent; + decision = await Future.delayed( + const Duration(milliseconds: 10), + () => NavigationDecision.navigate); + return decision; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript('location.href = "$secondaryUrl"'); + + await pageLoads.stream.first; // Wait for second page to load. + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, secondaryUrl); + }); + }); + + testWidgets('launches with gestureNavigationEnabled on iOS', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: SizedBox( + width: 400, + height: 300, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + gestureNavigationEnabled: true, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets('target _blank opens in same window', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageLoaded = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript('window.open("$primaryUrl", "_blank")'); + await pageLoaded.future; + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets( + 'can open new window and go back', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(); + }, + initialUrl: primaryUrl, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + expect(controller.currentUrl(), completion(primaryUrl)); + await pageLoaded.future; + pageLoaded = Completer(); + + await controller.runJavascript('window.open("$secondaryUrl")'); + await pageLoaded.future; + pageLoaded = Completer(); + expect(controller.currentUrl(), completion(secondaryUrl)); + + expect(controller.canGoBack(), completion(true)); + await controller.goBack(); + await pageLoaded.future; + await expectLater(controller.currentUrl(), completion(primaryUrl)); + }, + ); + + testWidgets( + 'clearCache should clear local storage', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + + Completer pageLoadCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (_) => pageLoadCompleter.complete(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ); + + await pageLoadCompleter.future; + pageLoadCompleter = Completer(); + + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript('localStorage.setItem("myCat", "Tom");'); + final String myCatItem = await controller.runJavascriptReturningResult( + 'localStorage.getItem("myCat");', + ); + expect(myCatItem, _webviewString('Tom')); + + await controller.clearCache(); + await pageLoadCompleter.future; + + late final String? nullItem; + try { + nullItem = await controller.runJavascriptReturningResult( + 'localStorage.getItem("myCat");', + ); + } catch (exception) { + if (defaultTargetPlatform == TargetPlatform.iOS && + exception is ArgumentError && + (exception.message as String).contains( + 'Result of JavaScript execution returned a `null` value.')) { + nullItem = ''; + } + } + expect(nullItem, _webviewNull()); + }, + ); +} + +// JavaScript booleans evaluate to different string values on Android and iOS. +// This utility method returns the string boolean value of the current platform. +String _webviewBool(bool value) { + if (defaultTargetPlatform == TargetPlatform.iOS) { + return value ? '1' : '0'; + } + return value ? 'true' : 'false'; +} + +// JavaScript `null` evaluate to different string values on Android and iOS. +// This utility method returns the string boolean value of the current platform. +String _webviewNull() { + if (defaultTargetPlatform == TargetPlatform.iOS) { + return ''; + } + return 'null'; +} + +// JavaScript String evaluate to different string values on Android and iOS. +// This utility method returns the string boolean value of the current platform. +String _webviewString(String value) { + if (defaultTargetPlatform == TargetPlatform.iOS) { + return value; + } + return '"$value"'; +} + +/// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. +Future _getUserAgent(WebViewController controller) async { + return _runJavascriptReturningResult(controller, 'navigator.userAgent;'); +} + +Future _runJavascriptReturningResult( + WebViewController controller, String js) async { + if (defaultTargetPlatform == TargetPlatform.iOS) { + return controller.runJavascriptReturningResult(js); + } + return jsonDecode(await controller.runJavascriptReturningResult(js)) + as String; +} + +class ResizableWebView extends StatefulWidget { + const ResizableWebView({ + super.key, + required this.onResize, + required this.onPageFinished, + }); + + final JavascriptMessageHandler onResize; + final VoidCallback onPageFinished; + + @override + State createState() => ResizableWebViewState(); +} + +class ResizableWebViewState extends State { + double webViewWidth = 200; + double webViewHeight = 200; + + static const String resizePage = ''' + + Resize test + + + + + + '''; + + @override + Widget build(BuildContext context) { + final String resizeTestBase64 = + base64Encode(const Utf8Encoder().convert(resizePage)); + return Directionality( + textDirection: TextDirection.ltr, + child: Column( + children: [ + SizedBox( + width: webViewWidth, + height: webViewHeight, + child: WebView( + initialUrl: + 'data:text/html;charset=utf-8;base64,$resizeTestBase64', + javascriptChannels: { + JavascriptChannel( + name: 'Resize', + onMessageReceived: widget.onResize, + ), + }, + onPageFinished: (_) => widget.onPageFinished(), + javascriptMode: JavascriptMode.unrestricted, + ), + ), + TextButton( + key: const Key('resizeButton'), + onPressed: () { + setState(() { + webViewWidth += 100.0; + webViewHeight += 100.0; + }); + }, + child: const Text('ResizeButton'), + ), + ], + ), + ); + } +} diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart index 8dd832169024..7763327df582 100644 --- a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart @@ -19,6 +19,8 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; Future main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -43,163 +45,93 @@ Future main() async { final String secondaryUrl = '$prefixUrl/secondary.txt'; final String headersUrl = '$prefixUrl/headers'; - testWidgets('initialUrl', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer pageFinishedCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageFinished: pageFinishedCompleter.complete, - ), - ), - ); + testWidgets('loadRequest', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final WebViewController controller = WebViewController() + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), + ) + ..loadRequest(Uri.parse(primaryUrl)); - final WebViewController controller = await controllerCompleter.future; - await pageFinishedCompleter.future; + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageFinished.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); - testWidgets('loadUrl', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = StreamController(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageFinished: (String url) { - pageLoads.add(url); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; + testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { + final Completer pageFinished = Completer(); - await controller.loadUrl(secondaryUrl); - await expectLater( - pageLoads.stream.firstWhere((String url) => url == secondaryUrl), - completion(secondaryUrl), - ); - }); + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), + ) + ..loadRequest(Uri.parse(primaryUrl)); - testWidgets('evaluateJavascript', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - ), - ), + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageFinished.future; + + await expectLater( + controller.runJavaScriptReturningResult('1 + 1'), + completion(2), ); - final WebViewController controller = await controllerCompleter.future; - // ignore: deprecated_member_use - final String result = await controller.evaluateJavascript('1 + 1'); - expect(result, equals('2')); }); - testWidgets('loadUrl with headers', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageStarts = StreamController(); - final StreamController pageLoads = StreamController(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarts.add(url); - }, - onPageFinished: (String url) { - pageLoads.add(url); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; + testWidgets('loadRequest with headers', (WidgetTester tester) async { final Map headers = { 'test_header': 'flutter_test_header' }; - await controller.loadUrl(headersUrl, headers: headers); - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, headersUrl); - await pageStarts.stream.firstWhere((String url) => url == currentUrl); - await pageLoads.stream.firstWhere((String url) => url == currentUrl); + final StreamController pageLoads = StreamController(); - final String content = await controller - .runJavascriptReturningResult('document.documentElement.innerText'); + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (String url) => pageLoads.add(url)), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + controller.loadRequest(Uri.parse(headersUrl), headers: headers); + + await pageLoads.stream.firstWhere((String url) => url == headersUrl); + + final String content = await controller.runJavaScriptReturningResult( + 'document.documentElement.innerText', + ) as String; expect(content.contains('flutter_test_header'), isTrue); }); testWidgets('JavascriptChannel', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer pageStarted = Completer(); - final Completer pageLoaded = Completer(); + final Completer pageFinished = Completer(); + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), + ); + final Completer channelCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - // This is the data URL for: '' - initialUrl: - 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - javascriptChannels: { - JavascriptChannel( - name: 'Echo', - onMessageReceived: (JavascriptMessage message) { - channelCompleter.complete(message.message); - }, - ), - }, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), + await controller.addJavaScriptChannel( + 'Echo', + onMessageReceived: (JavaScriptMessage message) { + channelCompleter.complete(message.message); + }, + ); + + await controller.loadHtmlString( + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', ); - final WebViewController controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - expect(channelCompleter.isCompleted, isFalse); - await controller.runJavascript('Echo.postMessage("hello");'); + await tester.pumpWidget(WebViewWidget(controller: controller)); + await pageFinished.future; + + await controller.runJavaScript('Echo.postMessage("hello");'); await expectLater(channelCompleter.future, completion('hello')); }); @@ -210,7 +142,7 @@ Future main() async { bool resizeButtonTapped = false; await tester.pumpWidget(ResizableWebView( - onResize: (_) { + onResize: () { if (resizeButtonTapped) { buttonTapResizeCompleter.complete(); } else { @@ -219,6 +151,7 @@ Future main() async { }, onPageFinished: () => onPageFinished.complete(), )); + await onPageFinished.future; // Wait for a potential call to resize after page is loaded. await initialResizeCompleter.future.timeout( @@ -227,98 +160,30 @@ Future main() async { ); resizeButtonTapped = true; + await tester.tap(find.byKey(const ValueKey('resizeButton'))); await tester.pumpAndSettle(); - expect(buttonTapResizeCompleter.future, completes); + + await expectLater(buttonTapResizeCompleter.future, completes); }); testWidgets('set custom userAgent', (WidgetTester tester) async { - final Completer controllerCompleter1 = - Completer(); - final GlobalKey globalKey = GlobalKey(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent1', - onWebViewCreated: (WebViewController controller) { - controllerCompleter1.complete(controller); - }, - ), - ), - ); - final WebViewController controller1 = await controllerCompleter1.future; - final String customUserAgent1 = await _getUserAgent(controller1); - expect(customUserAgent1, 'Custom_User_Agent1'); - // rebuild the WebView with a different user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent2', - ), - ), - ); + final Completer pageFinished = Completer(); - final String customUserAgent2 = await _getUserAgent(controller1); - expect(customUserAgent2, 'Custom_User_Agent2'); - }); + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageFinished.complete(), + )) + ..setUserAgent('Custom_User_Agent1') + ..loadRequest(Uri.parse('about:blank')); - testWidgets('use default platform userAgent after webView is rebuilt', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final GlobalKey globalKey = GlobalKey(); - // Build the webView with no user agent to get the default platform user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: globalKey, - initialUrl: primaryUrl, - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final String defaultPlatformUserAgent = await _getUserAgent(controller); - // rebuild the WebView with a custom user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent', - ), - ), - ); - final String customUserAgent = await _getUserAgent(controller); - expect(customUserAgent, 'Custom_User_Agent'); - // rebuilds the WebView with no user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - ), - ), - ); + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageFinished.future; - final String customUserAgent2 = await _getUserAgent(controller); - expect(customUserAgent2, defaultPlatformUserAgent); + final String customUserAgent = await _getUserAgent(controller); + expect(customUserAgent, 'Custom_User_Agent1'); }); group('Video playback policy', () { @@ -362,219 +227,156 @@ Future main() async { }); testWidgets('Auto media playback', (WidgetTester tester) async { - Completer controllerCompleter = - Completer(); Completer pageLoaded = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ), - ); - WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; + late PlatformWebViewControllerCreationParams params; + if (defaultTargetPlatform == TargetPlatform.iOS) { + params = WebKitWebViewControllerCreationParams( + mediaTypesRequiringUserAction: const {}, + ); + } else { + params = const PlatformWebViewControllerCreationParams(); + } - String isPaused = - await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); + WebViewController controller = + WebViewController.fromPlatformCreationParams(params) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ); - controllerCompleter = Completer(); - pageLoaded = Completer(); + if (controller.platform is AndroidWebViewController) { + (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); + } - // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), ); - controller = await controllerCompleter.future; - await pageLoaded.future; - - isPaused = await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(true)); - }); - - testWidgets('Changes to initialMediaPlaybackPolicy are ignored', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - Completer pageLoaded = Completer(); + await tester.pumpWidget(WebViewWidget(controller: controller)); - final GlobalKey key = GlobalKey(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; - String isPaused = - await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); + bool isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, false); pageLoaded = Completer(); + controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ) + ..loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), + ); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - - await controller.reload(); + await tester.pumpWidget(WebViewWidget(controller: controller)); await pageLoaded.future; - isPaused = await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); + isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, true); }); - testWidgets('Video plays inline when allowsInlineMediaPlayback is true', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); + testWidgets('Video plays inline', (WidgetTester tester) async { final Completer pageLoaded = Completer(); final Completer videoPlaying = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - javascriptChannels: { - JavascriptChannel( - name: 'VideoTestTime', - onMessageReceived: (JavascriptMessage message) { - final double currentTime = double.parse(message.message); - // Let it play for at least 1 second to make sure the related video's properties are set. - if (currentTime > 1 && !videoPlaying.isCompleted) { - videoPlaying.complete(null); - } - }, - ), - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - allowsInlineMediaPlayback: true, - ), - ), + late PlatformWebViewControllerCreationParams params; + if (defaultTargetPlatform == TargetPlatform.iOS) { + params = WebKitWebViewControllerCreationParams( + mediaTypesRequiringUserAction: const {}, + allowsInlineMediaPlayback: true, + ); + } else { + params = const PlatformWebViewControllerCreationParams(); + } + final WebViewController controller = + WebViewController.fromPlatformCreationParams(params) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ) + ..addJavaScriptChannel( + 'VideoTestTime', + onMessageReceived: (JavaScriptMessage message) { + final double currentTime = double.parse(message.message); + // Let it play for at least 1 second to make sure the related video's properties are set. + if (currentTime > 1 && !videoPlaying.isCompleted) { + videoPlaying.complete(null); + } + }, + ); + + if (controller.platform is AndroidWebViewController) { + (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); + } + + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), ); - final WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - // Pump once to trigger the video play. - await tester.pump(); + await tester.pumpWidget(WebViewWidget(controller: controller)); + await tester.pumpAndSettle(); + + await pageLoaded.future; // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; - final String fullScreen = - await controller.runJavascriptReturningResult('isFullScreen();'); - expect(fullScreen, _webviewBool(false)); + final bool fullScreen = await controller + .runJavaScriptReturningResult('isFullScreen();') as bool; + expect(fullScreen, false); }); // allowsInlineMediaPlayback is a noop on Android, so it is skipped. testWidgets( 'Video plays full screen when allowsInlineMediaPlayback is false', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); final Completer pageLoaded = Completer(); final Completer videoPlaying = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - javascriptChannels: { - JavascriptChannel( - name: 'VideoTestTime', - onMessageReceived: (JavascriptMessage message) { - final double currentTime = double.parse(message.message); - // Let it play for at least 1 second to make sure the related video's properties are set. - if (currentTime > 1 && !videoPlaying.isCompleted) { - videoPlaying.complete(null); - } - }, + final WebViewController controller = + WebViewController.fromPlatformCreationParams( + WebKitWebViewControllerCreationParams( + mediaTypesRequiringUserAction: const {}, + ), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ) + ..addJavaScriptChannel( + 'VideoTestTime', + onMessageReceived: (JavaScriptMessage message) { + final double currentTime = double.parse(message.message); + // Let it play for at least 1 second to make sure the related video's properties are set. + if (currentTime > 1 && !videoPlaying.isCompleted) { + videoPlaying.complete(null); + } + }, + ) + ..loadRequest( + Uri.parse( + 'data:text/html;charset=utf-8;base64,$videoTestBase64', ), - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; + ); - // Pump once to trigger the video play. - await tester.pump(); + await tester.pumpWidget(WebViewWidget(controller: controller)); + await tester.pumpAndSettle(); + + await pageLoaded.future; // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; - final String fullScreen = - await controller.runJavascriptReturningResult('isFullScreen();'); - expect(fullScreen, _webviewBool(true)); + final bool fullScreen = await controller + .runJavaScriptReturningResult('isFullScreen();') as bool; + expect(fullScreen, true); }, skip: Platform.isAndroid); }); @@ -610,138 +412,60 @@ Future main() async { }); testWidgets('Auto media playback', (WidgetTester tester) async { - Completer controllerCompleter = - Completer(); - Completer pageStarted = Completer(); Completer pageLoaded = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ), - ); - WebViewController controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; + late PlatformWebViewControllerCreationParams params; + if (defaultTargetPlatform == TargetPlatform.iOS) { + params = WebKitWebViewControllerCreationParams( + mediaTypesRequiringUserAction: const {}, + ); + } else { + params = const PlatformWebViewControllerCreationParams(); + } - String isPaused = - await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); + WebViewController controller = + WebViewController.fromPlatformCreationParams(params) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ); - controllerCompleter = Completer(); - pageStarted = Completer(); - pageLoaded = Completer(); + if (controller.platform is AndroidWebViewController) { + (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); + } - // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$audioTestBase64'), ); - controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - - isPaused = await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(true)); - }); - - testWidgets('Changes to initialMediaPlaybackPolicy are ignored', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - Completer pageStarted = Completer(); - Completer pageLoaded = Completer(); + await tester.pumpWidget(WebViewWidget(controller: controller)); + await tester.pumpAndSettle(); - final GlobalKey key = GlobalKey(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageStarted.future; await pageLoaded.future; - String isPaused = - await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); + bool isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, false); - pageStarted = Completer(); pageLoaded = Completer(); + controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ) + ..loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$audioTestBase64'), + ); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - - await controller.reload(); + await tester.pumpWidget(WebViewWidget(controller: controller)); + await tester.pumpAndSettle(); - await pageStarted.future; await pageLoaded.future; - isPaused = await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); + isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, true); }); }); @@ -756,39 +480,26 @@ Future main() async { '''; final String getTitleTestBase64 = base64Encode(const Utf8Encoder().convert(getTitleTest)); - final Completer pageStarted = Completer(); final Completer pageLoaded = Completer(); - final Completer controllerCompleter = - Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - initialUrl: 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageStarted.future; + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )) + ..loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$getTitleTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + await pageLoaded.future; // On at least iOS, it does not appear to be guaranteed that the native // code has the title when the page load completes. Execute some JavaScript // before checking the title to ensure that the page has been fully parsed // and processed. - await controller.runJavascript('1;'); + await controller.runJavaScript('1;'); final String? title = await controller.getTitle(); expect(title, 'Some title'); @@ -821,32 +532,22 @@ Future main() async { base64Encode(const Utf8Encoder().convert(scrollTestPage)); final Completer pageLoaded = Completer(); - final Completer controllerCompleter = - Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - initialUrl: - 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )) + ..loadRequest(Uri.parse( + 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); - final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; await tester.pumpAndSettle(const Duration(seconds: 3)); - int scrollPosX = await controller.getScrollX(); - int scrollPosY = await controller.getScrollY(); + Offset scrollPos = await controller.getScrollPosition(); // Check scrollTo() const int X_SCROLL = 123; @@ -854,95 +555,51 @@ Future main() async { // Get the initial position; this ensures that scrollTo is actually // changing something, but also gives the native view's scroll position // time to settle. - expect(scrollPosX, isNot(X_SCROLL)); - expect(scrollPosX, isNot(Y_SCROLL)); + expect(scrollPos.dx, isNot(X_SCROLL)); + expect(scrollPos.dy, isNot(Y_SCROLL)); await controller.scrollTo(X_SCROLL, Y_SCROLL); - scrollPosX = await controller.getScrollX(); - scrollPosY = await controller.getScrollY(); - expect(scrollPosX, X_SCROLL); - expect(scrollPosY, Y_SCROLL); + scrollPos = await controller.getScrollPosition(); + expect(scrollPos.dx, X_SCROLL); + expect(scrollPos.dy, Y_SCROLL); // Check scrollBy() (on top of scrollTo()) await controller.scrollBy(X_SCROLL, Y_SCROLL); - scrollPosX = await controller.getScrollX(); - scrollPosY = await controller.getScrollY(); - expect(scrollPosX, X_SCROLL * 2); - expect(scrollPosY, Y_SCROLL * 2); + scrollPos = await controller.getScrollPosition(); + expect(scrollPos.dx, X_SCROLL * 2); + expect(scrollPos.dy, Y_SCROLL * 2); }); }); - // Minimal end-to-end testing of the legacy Android implementation. - group('AndroidWebView (virtual display)', () { - setUpAll(() { - WebView.platform = AndroidWebView(); - }); - - tearDownAll(() { - WebView.platform = null; - }); - - testWidgets('initialUrl', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer pageFinishedCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageFinished: pageFinishedCompleter.complete, - ), - ), - ); - - final WebViewController controller = await controllerCompleter.future; - await pageFinishedCompleter.future; - - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, primaryUrl); - }); - }, skip: !Platform.isAndroid); - group('NavigationDelegate', () { const String blankPage = ''; final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' '${base64Encode(const Utf8Encoder().convert(blankPage))}'; testWidgets('can allow requests', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: blankPageEncoded, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - navigationDelegate: (NavigationRequest request) { - return (request.url.contains('youtube.com')) - ? NavigationDecision.prevent - : NavigationDecision.navigate; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); + Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + onNavigationRequest: (NavigationRequest navigationRequest) { + return (navigationRequest.url.contains('youtube.com')) + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); - await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('location.href = "$secondaryUrl"'); + controller.loadRequest(Uri.parse(blankPageEncoded)); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); + await controller.runJavaScript('location.href = "$secondaryUrl"'); + await pageLoaded.future; // Wait for the next page load. - await pageLoads.stream.first; // Wait for the next page load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); @@ -951,30 +608,18 @@ Future main() async { final Completer errorCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'https://www.notawebsite..com', - onWebResourceError: (WebResourceError error) { - errorCompleter.complete(error); - }, - ), - ), - ); + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate(onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + })) + ..loadRequest(Uri.parse('https://www.notawebsite..com')); + + await tester.pumpWidget(WebViewWidget(controller: controller)); final WebResourceError error = await errorCompleter.future; expect(error, isNotNull); - - if (Platform.isIOS) { - expect(error.domain, isNotNull); - expect(error.failingUrl, isNull); - } else if (Platform.isAndroid) { - expect(error.errorType, isNotNull); - expect(error.failingUrl?.startsWith('https://www.notawebsite..com'), - isTrue); - } }); testWidgets('onWebResourceError is not called with valid url', @@ -983,190 +628,99 @@ Future main() async { Completer(); final Completer pageFinishCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: - 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', - onWebResourceError: (WebResourceError error) { - errorCompleter.complete(error); - }, - onPageFinished: (_) => pageFinishCompleter.complete(), - ), - ), - ); + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageFinishCompleter.complete(), + onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + }, + )) + ..loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }); - testWidgets( - 'onWebResourceError only called for main frame', - (WidgetTester tester) async { - const String iframeTest = ''' - - - - WebResourceError test - - - - - - '''; - final String iframeTestBase64 = - base64Encode(const Utf8Encoder().convert(iframeTest)); - - final Completer errorCompleter = - Completer(); - final Completer pageFinishCompleter = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: - 'data:text/html;charset=utf-8;base64,$iframeTestBase64', - onWebResourceError: (WebResourceError error) { - errorCompleter.complete(error); - }, - onPageFinished: (_) => pageFinishCompleter.complete(), - ), - ), - ); - - expect(errorCompleter.future, doesNotComplete); - await pageFinishCompleter.future; - }, - ); - testWidgets('can block requests', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: blankPageEncoded, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - navigationDelegate: (NavigationRequest request) { - return (request.url.contains('youtube.com')) + Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + onNavigationRequest: (NavigationRequest navigationRequest) { + return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); + })); - await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; + await tester.pumpWidget(WebViewWidget(controller: controller)); + + controller.loadRequest(Uri.parse(blankPageEncoded)); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); await controller - .runJavascript('location.href = "https://www.youtube.com/"'); + .runJavaScript('location.href = "https://www.youtube.com/"'); // There should never be any second page load, since our new URL is // blocked. Still wait for a potential page change for some time in order // to give the test a chance to fail. - await pageLoads.stream.first + await pageLoaded.future .timeout(const Duration(milliseconds: 500), onTimeout: () => ''); final String? currentUrl = await controller.currentUrl(); expect(currentUrl, isNot(contains('youtube.com'))); }); testWidgets('supports asynchronous decisions', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: blankPageEncoded, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - navigationDelegate: (NavigationRequest request) async { + Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + onNavigationRequest: (NavigationRequest navigationRequest) async { NavigationDecision decision = NavigationDecision.prevent; decision = await Future.delayed( const Duration(milliseconds: 10), () => NavigationDecision.navigate); return decision; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); + })); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + controller.loadRequest(Uri.parse(blankPageEncoded)); - await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('location.href = "$secondaryUrl"'); + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); + await controller.runJavaScript('location.href = "$secondaryUrl"'); + await pageLoaded.future; // Wait for second page to load. - await pageLoads.stream.first; // Wait for second page to load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); }); - testWidgets('launches with gestureNavigationEnabled on iOS', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: SizedBox( - width: 400, - height: 300, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - gestureNavigationEnabled: true, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, primaryUrl); - }); - testWidgets('target _blank opens in same window', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); final Completer pageLoaded = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('window.open("$primaryUrl", "_blank")'); + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await controller.runJavaScript('window.open("$primaryUrl", "_blank")'); await pageLoaded.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); @@ -1175,31 +729,22 @@ Future main() async { testWidgets( 'can open new window and go back', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); Completer pageLoaded = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(); - }, - initialUrl: primaryUrl, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; + + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )) + ..loadRequest(Uri.parse(primaryUrl)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + expect(controller.currentUrl(), completion(primaryUrl)); await pageLoaded.future; pageLoaded = Completer(); - await controller.runJavascript('window.open("$secondaryUrl")'); + await controller.runJavaScript('window.open("$secondaryUrl")'); await pageLoaded.future; pageLoaded = Completer(); expect(controller.currentUrl(), completion(secondaryUrl)); @@ -1212,46 +757,39 @@ Future main() async { ); testWidgets( - 'clearCache should clear local storage', + 'clearLocalStorage', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - Completer pageLoadCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (_) => pageLoadCompleter.complete(), - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ); + final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoadCompleter.complete(), + )) + ..loadRequest(Uri.parse(primaryUrl)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); await pageLoadCompleter.future; pageLoadCompleter = Completer(); - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('localStorage.setItem("myCat", "Tom");'); - final String myCatItem = await controller.runJavascriptReturningResult( + await controller.runJavaScript('localStorage.setItem("myCat", "Tom");'); + final String myCatItem = await controller.runJavaScriptReturningResult( 'localStorage.getItem("myCat");', - ); - expect(myCatItem, _webviewString('Tom')); + ) as String; + expect(myCatItem, _webViewString('Tom')); + + await controller.clearLocalStorage(); - await controller.clearCache(); + // Reload page to have changes take effect. + await controller.reload(); await pageLoadCompleter.future; late final String? nullItem; try { - nullItem = await controller.runJavascriptReturningResult( + nullItem = await controller.runJavaScriptReturningResult( 'localStorage.getItem("myCat");', - ); + ) as String; } catch (exception) { if (defaultTargetPlatform == TargetPlatform.iOS && exception is ArgumentError && @@ -1260,23 +798,14 @@ Future main() async { nullItem = ''; } } - expect(nullItem, _webviewNull()); + expect(nullItem, _webViewNull()); }, ); } -// JavaScript booleans evaluate to different string values on Android and iOS. -// This utility method returns the string boolean value of the current platform. -String _webviewBool(bool value) { - if (defaultTargetPlatform == TargetPlatform.iOS) { - return value ? '1' : '0'; - } - return value ? 'true' : 'false'; -} - // JavaScript `null` evaluate to different string values on Android and iOS. // This utility method returns the string boolean value of the current platform. -String _webviewNull() { +String _webViewNull() { if (defaultTargetPlatform == TargetPlatform.iOS) { return ''; } @@ -1285,7 +814,7 @@ String _webviewNull() { // JavaScript String evaluate to different string values on Android and iOS. // This utility method returns the string boolean value of the current platform. -String _webviewString(String value) { +String _webViewString(String value) { if (defaultTargetPlatform == TargetPlatform.iOS) { return value; } @@ -1298,20 +827,24 @@ Future _getUserAgent(WebViewController controller) async { } Future _runJavascriptReturningResult( - WebViewController controller, String js) async { + WebViewController controller, + String js, +) async { if (defaultTargetPlatform == TargetPlatform.iOS) { - return controller.runJavascriptReturningResult(js); + return await controller.runJavaScriptReturningResult(js) as String; } - return jsonDecode(await controller.runJavascriptReturningResult(js)) + return jsonDecode(await controller.runJavaScriptReturningResult(js) as String) as String; } class ResizableWebView extends StatefulWidget { - const ResizableWebView( - {Key? key, required this.onResize, required this.onPageFinished}) - : super(key: key); + const ResizableWebView({ + super.key, + required this.onResize, + required this.onPageFinished, + }); - final JavascriptMessageHandler onResize; + final VoidCallback onResize; final VoidCallback onPageFinished; @override @@ -1319,6 +852,23 @@ class ResizableWebView extends StatefulWidget { } class ResizableWebViewState extends State { + late final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => widget.onPageFinished(), + )) + ..addJavaScriptChannel( + 'Resize', + onMessageReceived: (_) { + widget.onResize(); + }, + ) + ..loadRequest( + Uri.parse( + 'data:text/html;charset=utf-8;base64,${base64Encode(const Utf8Encoder().convert(resizePage))}', + ), + ); + double webViewWidth = 200; double webViewHeight = 200; @@ -1341,28 +891,14 @@ class ResizableWebViewState extends State { @override Widget build(BuildContext context) { - final String resizeTestBase64 = - base64Encode(const Utf8Encoder().convert(resizePage)); return Directionality( textDirection: TextDirection.ltr, child: Column( children: [ SizedBox( - width: webViewWidth, - height: webViewHeight, - child: WebView( - initialUrl: - 'data:text/html;charset=utf-8;base64,$resizeTestBase64', - javascriptChannels: { - JavascriptChannel( - name: 'Resize', - onMessageReceived: widget.onResize, - ), - }, - onPageFinished: (_) => widget.onPageFinished(), - javascriptMode: JavascriptMode.unrestricted, - ), - ), + width: webViewWidth, + height: webViewHeight, + child: WebViewWidget(controller: controller)), TextButton( key: const Key('resizeButton'), onPressed: () { diff --git a/packages/webview_flutter/webview_flutter/example/lib/main.dart b/packages/webview_flutter/webview_flutter/example/lib/main.dart index 0e712901fe3e..239b417c4e04 100644 --- a/packages/webview_flutter/webview_flutter/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter/example/lib/main.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: public_member_api_docs, avoid_print +// ignore_for_file: public_member_api_docs import 'dart:async'; import 'dart:convert'; @@ -12,6 +12,12 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:webview_flutter/webview_flutter.dart'; +// #docregion platform_imports +// Import for Android features. +import 'package:webview_flutter_android/webview_flutter_android.dart'; +// Import for iOS features. +import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; +// #enddocregion platform_imports void main() => runApp(const MaterialApp(home: WebViewExample())); @@ -71,24 +77,86 @@ const String kTransparentBackgroundPage = ''' '''; class WebViewExample extends StatefulWidget { - const WebViewExample({Key? key, this.cookieManager}) : super(key: key); - - final CookieManager? cookieManager; + const WebViewExample({super.key}); @override State createState() => _WebViewExampleState(); } class _WebViewExampleState extends State { - final Completer _controller = - Completer(); + late final WebViewController _controller; @override void initState() { super.initState(); - if (Platform.isAndroid) { - WebView.platform = SurfaceAndroidWebView(); + + // #docregion platform_features + late final PlatformWebViewControllerCreationParams params; + if (WebViewPlatform.instance is WebKitWebViewPlatform) { + params = WebKitWebViewControllerCreationParams( + allowsInlineMediaPlayback: true, + mediaTypesRequiringUserAction: const {}, + ); + } else { + params = const PlatformWebViewControllerCreationParams(); + } + + final WebViewController controller = + WebViewController.fromPlatformCreationParams(params); + // #enddocregion platform_features + + controller + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x00000000)) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + debugPrint('WebView is loading (progress : $progress%)'); + }, + onPageStarted: (String url) { + debugPrint('Page started loading: $url'); + }, + onPageFinished: (String url) { + debugPrint('Page finished loading: $url'); + }, + onWebResourceError: (WebResourceError error) { + debugPrint(''' +Page resource error: + code: ${error.errorCode} + description: ${error.description} + errorType: ${error.errorType} + isForMainFrame: ${error.isForMainFrame} + '''); + }, + onNavigationRequest: (NavigationRequest request) { + if (request.url.startsWith('https://www.youtube.com/')) { + debugPrint('blocking navigation to ${request.url}'); + return NavigationDecision.prevent; + } + debugPrint('allowing navigation to ${request.url}'); + return NavigationDecision.navigate; + }, + ), + ) + ..addJavaScriptChannel( + 'Toaster', + onMessageReceived: (JavaScriptMessage message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message.message)), + ); + }, + ) + ..loadRequest(Uri.parse('https://flutter.dev')); + + // #docregion platform_features + if (controller.platform is AndroidWebViewController) { + AndroidWebViewController.enableDebugging(true); + (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); } + // #enddocregion platform_features + + _controller = controller; } @override @@ -99,77 +167,25 @@ class _WebViewExampleState extends State { title: const Text('Flutter WebView example'), // This drop down menu demonstrates that Flutter widgets can be shown over the web view. actions: [ - NavigationControls(_controller.future), - SampleMenu(_controller.future, widget.cookieManager), + NavigationControls(webViewController: _controller), + SampleMenu(webViewController: _controller), ], ), - body: WebView( - initialUrl: 'https://flutter.dev', - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (WebViewController webViewController) { - _controller.complete(webViewController); - }, - onProgress: (int progress) { - print('WebView is loading (progress : $progress%)'); - }, - javascriptChannels: { - _toasterJavascriptChannel(context), - }, - navigationDelegate: (NavigationRequest request) { - if (request.url.startsWith('https://www.youtube.com/')) { - print('blocking navigation to $request}'); - return NavigationDecision.prevent; - } - print('allowing navigation to $request'); - return NavigationDecision.navigate; - }, - onPageStarted: (String url) { - print('Page started loading: $url'); - }, - onPageFinished: (String url) { - print('Page finished loading: $url'); - }, - gestureNavigationEnabled: true, - backgroundColor: const Color(0x00000000), - ), + body: WebViewWidget(controller: _controller), floatingActionButton: favoriteButton(), ); } - JavascriptChannel _toasterJavascriptChannel(BuildContext context) { - return JavascriptChannel( - name: 'Toaster', - onMessageReceived: (JavascriptMessage message) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(message.message)), - ); - }); - } - Widget favoriteButton() { - return FutureBuilder( - future: _controller.future, - builder: (BuildContext context, - AsyncSnapshot controller) { - return FloatingActionButton( - onPressed: () async { - String? url; - if (controller.hasData) { - url = await controller.data!.currentUrl(); - } - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - controller.hasData - ? 'Favorited $url' - : 'Unable to favorite', - ), - ), - ); - }, - child: const Icon(Icons.favorite), - ); - }); + return FloatingActionButton( + onPressed: () async { + final String? url = await _controller.currentUrl(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Favorited $url')), + ); + }, + child: const Icon(Icons.favorite), + ); } } @@ -190,137 +206,130 @@ enum MenuOptions { } class SampleMenu extends StatelessWidget { - SampleMenu(this.controller, CookieManager? cookieManager, {Key? key}) - : cookieManager = cookieManager ?? CookieManager(), - super(key: key); + SampleMenu({ + super.key, + required this.webViewController, + }); - final Future controller; - late final CookieManager cookieManager; + final WebViewController webViewController; + late final WebViewCookieManager cookieManager = WebViewCookieManager(); @override Widget build(BuildContext context) { - return FutureBuilder( - future: controller, - builder: - (BuildContext context, AsyncSnapshot controller) { - return PopupMenuButton( - key: const ValueKey('ShowPopupMenu'), - onSelected: (MenuOptions value) { - switch (value) { - case MenuOptions.showUserAgent: - _onShowUserAgent(controller.data!, context); - break; - case MenuOptions.listCookies: - _onListCookies(controller.data!, context); - break; - case MenuOptions.clearCookies: - _onClearCookies(context); - break; - case MenuOptions.addToCache: - _onAddToCache(controller.data!, context); - break; - case MenuOptions.listCache: - _onListCache(controller.data!, context); - break; - case MenuOptions.clearCache: - _onClearCache(controller.data!, context); - break; - case MenuOptions.navigationDelegate: - _onNavigationDelegateExample(controller.data!, context); - break; - case MenuOptions.doPostRequest: - _onDoPostRequest(controller.data!, context); - break; - case MenuOptions.loadLocalFile: - _onLoadLocalFileExample(controller.data!, context); - break; - case MenuOptions.loadFlutterAsset: - _onLoadFlutterAssetExample(controller.data!, context); - break; - case MenuOptions.loadHtmlString: - _onLoadHtmlStringExample(controller.data!, context); - break; - case MenuOptions.transparentBackground: - _onTransparentBackground(controller.data!, context); - break; - case MenuOptions.setCookie: - _onSetCookie(controller.data!, context); - break; - } - }, - itemBuilder: (BuildContext context) => >[ - PopupMenuItem( - value: MenuOptions.showUserAgent, - enabled: controller.hasData, - child: const Text('Show user agent'), - ), - const PopupMenuItem( - value: MenuOptions.listCookies, - child: Text('List cookies'), - ), - const PopupMenuItem( - value: MenuOptions.clearCookies, - child: Text('Clear cookies'), - ), - const PopupMenuItem( - value: MenuOptions.addToCache, - child: Text('Add to cache'), - ), - const PopupMenuItem( - value: MenuOptions.listCache, - child: Text('List cache'), - ), - const PopupMenuItem( - value: MenuOptions.clearCache, - child: Text('Clear cache'), - ), - const PopupMenuItem( - value: MenuOptions.navigationDelegate, - child: Text('Navigation Delegate example'), - ), - const PopupMenuItem( - value: MenuOptions.doPostRequest, - child: Text('Post Request'), - ), - const PopupMenuItem( - value: MenuOptions.loadHtmlString, - child: Text('Load HTML string'), - ), - const PopupMenuItem( - value: MenuOptions.loadLocalFile, - child: Text('Load local file'), - ), - const PopupMenuItem( - value: MenuOptions.loadFlutterAsset, - child: Text('Load Flutter Asset'), - ), - const PopupMenuItem( - key: ValueKey('ShowTransparentBackgroundExample'), - value: MenuOptions.transparentBackground, - child: Text('Transparent background example'), - ), - const PopupMenuItem( - value: MenuOptions.setCookie, - child: Text('Set cookie'), - ), - ], - ); + return PopupMenuButton( + key: const ValueKey('ShowPopupMenu'), + onSelected: (MenuOptions value) { + switch (value) { + case MenuOptions.showUserAgent: + _onShowUserAgent(); + break; + case MenuOptions.listCookies: + _onListCookies(context); + break; + case MenuOptions.clearCookies: + _onClearCookies(context); + break; + case MenuOptions.addToCache: + _onAddToCache(context); + break; + case MenuOptions.listCache: + _onListCache(); + break; + case MenuOptions.clearCache: + _onClearCache(context); + break; + case MenuOptions.navigationDelegate: + _onNavigationDelegateExample(); + break; + case MenuOptions.doPostRequest: + _onDoPostRequest(); + break; + case MenuOptions.loadLocalFile: + _onLoadLocalFileExample(); + break; + case MenuOptions.loadFlutterAsset: + _onLoadFlutterAssetExample(); + break; + case MenuOptions.loadHtmlString: + _onLoadHtmlStringExample(); + break; + case MenuOptions.transparentBackground: + _onTransparentBackground(); + break; + case MenuOptions.setCookie: + _onSetCookie(); + break; + } }, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: MenuOptions.showUserAgent, + child: Text('Show user agent'), + ), + const PopupMenuItem( + value: MenuOptions.listCookies, + child: Text('List cookies'), + ), + const PopupMenuItem( + value: MenuOptions.clearCookies, + child: Text('Clear cookies'), + ), + const PopupMenuItem( + value: MenuOptions.addToCache, + child: Text('Add to cache'), + ), + const PopupMenuItem( + value: MenuOptions.listCache, + child: Text('List cache'), + ), + const PopupMenuItem( + value: MenuOptions.clearCache, + child: Text('Clear cache'), + ), + const PopupMenuItem( + value: MenuOptions.navigationDelegate, + child: Text('Navigation Delegate example'), + ), + const PopupMenuItem( + value: MenuOptions.doPostRequest, + child: Text('Post Request'), + ), + const PopupMenuItem( + value: MenuOptions.loadHtmlString, + child: Text('Load HTML string'), + ), + const PopupMenuItem( + value: MenuOptions.loadLocalFile, + child: Text('Load local file'), + ), + const PopupMenuItem( + value: MenuOptions.loadFlutterAsset, + child: Text('Load Flutter Asset'), + ), + const PopupMenuItem( + key: ValueKey('ShowTransparentBackgroundExample'), + value: MenuOptions.transparentBackground, + child: Text('Transparent background example'), + ), + const PopupMenuItem( + value: MenuOptions.setCookie, + child: Text('Set cookie'), + ), + ], ); } - Future _onShowUserAgent( - WebViewController controller, BuildContext context) async { + Future _onShowUserAgent() { // Send a message with the user agent string to the Toaster JavaScript channel we registered // with the WebView. - await controller.runJavascript( - 'Toaster.postMessage("User Agent: " + navigator.userAgent);'); + return webViewController.runJavaScript( + 'Toaster.postMessage("User Agent: " + navigator.userAgent);', + ); } - Future _onListCookies( - WebViewController controller, BuildContext context) async { - final String cookies = - await controller.runJavascriptReturningResult('document.cookie'); + Future _onListCookies(BuildContext context) async { + final String cookies = await webViewController + .runJavaScriptReturningResult('document.cookie') as String; ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Column( mainAxisAlignment: MainAxisAlignment.end, @@ -333,26 +342,25 @@ class SampleMenu extends StatelessWidget { )); } - Future _onAddToCache( - WebViewController controller, BuildContext context) async { - await controller.runJavascript( - 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";'); + Future _onAddToCache(BuildContext context) async { + await webViewController.runJavaScript( + 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";', + ); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Added a test entry to cache.'), )); } - Future _onListCache( - WebViewController controller, BuildContext context) async { - await controller.runJavascript('caches.keys()' + Future _onListCache() { + return webViewController.runJavaScript('caches.keys()' // ignore: missing_whitespace_between_adjacent_strings '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' '.then((caches) => Toaster.postMessage(caches))'); } - Future _onClearCache( - WebViewController controller, BuildContext context) async { - await controller.clearCache(); + Future _onClearCache(BuildContext context) async { + await webViewController.clearCache(); + await webViewController.clearLocalStorage(); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Cache cleared.'), )); @@ -369,53 +377,53 @@ class SampleMenu extends StatelessWidget { )); } - Future _onNavigationDelegateExample( - WebViewController controller, BuildContext context) async { - final String contentBase64 = - base64Encode(const Utf8Encoder().convert(kNavigationExamplePage)); - await controller.loadUrl('data:text/html;base64,$contentBase64'); + Future _onNavigationDelegateExample() { + final String contentBase64 = base64Encode( + const Utf8Encoder().convert(kNavigationExamplePage), + ); + return webViewController.loadRequest( + Uri.parse('data:text/html;base64,$contentBase64'), + ); } - Future _onSetCookie( - WebViewController controller, BuildContext context) async { + Future _onSetCookie() async { await cookieManager.setCookie( const WebViewCookie( - name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'), + name: 'foo', + value: 'bar', + domain: 'httpbin.org', + path: '/anything', + ), ); - await controller.loadUrl('https://httpbin.org/anything'); + await webViewController.loadRequest(Uri.parse( + 'https://httpbin.org/anything', + )); } - Future _onDoPostRequest( - WebViewController controller, BuildContext context) async { - final WebViewRequest request = WebViewRequest( - uri: Uri.parse('https://httpbin.org/post'), - method: WebViewRequestMethod.post, + Future _onDoPostRequest() { + return webViewController.loadRequest( + Uri.parse('https://httpbin.org/post'), + method: LoadRequestMethod.post, headers: {'foo': 'bar', 'Content-Type': 'text/plain'}, body: Uint8List.fromList('Test Body'.codeUnits), ); - await controller.loadRequest(request); } - Future _onLoadLocalFileExample( - WebViewController controller, BuildContext context) async { + Future _onLoadLocalFileExample() async { final String pathToIndex = await _prepareLocalFile(); - - await controller.loadFile(pathToIndex); + await webViewController.loadFile(pathToIndex); } - Future _onLoadFlutterAssetExample( - WebViewController controller, BuildContext context) async { - await controller.loadFlutterAsset('assets/www/index.html'); + Future _onLoadFlutterAssetExample() { + return webViewController.loadFlutterAsset('assets/www/index.html'); } - Future _onLoadHtmlStringExample( - WebViewController controller, BuildContext context) async { - await controller.loadHtmlString(kLocalExamplePage); + Future _onLoadHtmlStringExample() { + return webViewController.loadHtmlString(kLocalExamplePage); } - Future _onTransparentBackground( - WebViewController controller, BuildContext context) async { - await controller.loadHtmlString(kTransparentBackgroundPage); + Future _onTransparentBackground() { + return webViewController.loadHtmlString(kTransparentBackgroundPage); } Widget _getCookieList(String cookies) { @@ -445,65 +453,45 @@ class SampleMenu extends StatelessWidget { } class NavigationControls extends StatelessWidget { - const NavigationControls(this._webViewControllerFuture, {Key? key}) - : assert(_webViewControllerFuture != null), - super(key: key); + const NavigationControls({super.key, required this.webViewController}); - final Future _webViewControllerFuture; + final WebViewController webViewController; @override Widget build(BuildContext context) { - return FutureBuilder( - future: _webViewControllerFuture, - builder: - (BuildContext context, AsyncSnapshot snapshot) { - final bool webViewReady = - snapshot.connectionState == ConnectionState.done; - final WebViewController? controller = snapshot.data; - return Row( - children: [ - IconButton( - icon: const Icon(Icons.arrow_back_ios), - onPressed: !webViewReady - ? null - : () async { - if (await controller!.canGoBack()) { - await controller.goBack(); - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No back history item')), - ); - return; - } - }, - ), - IconButton( - icon: const Icon(Icons.arrow_forward_ios), - onPressed: !webViewReady - ? null - : () async { - if (await controller!.canGoForward()) { - await controller.goForward(); - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('No forward history item')), - ); - return; - } - }, - ), - IconButton( - icon: const Icon(Icons.replay), - onPressed: !webViewReady - ? null - : () { - controller!.reload(); - }, - ), - ], - ); - }, + return Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () async { + if (await webViewController.canGoBack()) { + await webViewController.goBack(); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No back history item')), + ); + return; + } + }, + ), + IconButton( + icon: const Icon(Icons.arrow_forward_ios), + onPressed: () async { + if (await webViewController.canGoForward()) { + await webViewController.goForward(); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No forward history item')), + ); + return; + } + }, + ), + IconButton( + icon: const Icon(Icons.replay), + onPressed: () => webViewController.reload(), + ), + ], ); } } diff --git a/packages/webview_flutter/webview_flutter/example/lib/simple_example.dart b/packages/webview_flutter/webview_flutter/example/lib/simple_example.dart new file mode 100644 index 000000000000..dfee9e6bd23a --- /dev/null +++ b/packages/webview_flutter/webview_flutter/example/lib/simple_example.dart @@ -0,0 +1,59 @@ +// 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. + +// ignore_for_file: public_member_api_docs + +import 'package:flutter/material.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +void main() => runApp(const MaterialApp(home: WebViewExample())); + +class WebViewExample extends StatefulWidget { + const WebViewExample({super.key}); + + @override + State createState() => _WebViewExampleState(); +} + +class _WebViewExampleState extends State { + late final WebViewController controller; + + @override + void initState() { + super.initState(); + + // #docregion webview_controller + controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x00000000)) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + // Update loading bar. + }, + onPageStarted: (String url) {}, + onPageFinished: (String url) {}, + onWebResourceError: (WebResourceError error) {}, + onNavigationRequest: (NavigationRequest request) { + if (request.url.startsWith('https://www.youtube.com/')) { + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; + }, + ), + ) + ..loadRequest(Uri.parse('https://flutter.dev')); + // #enddocregion webview_controller + } + + // #docregion webview_widget + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Flutter Simple Example')), + body: WebViewWidget(controller: controller), + ); + } + // #enddocregion webview_widget +} diff --git a/packages/webview_flutter/webview_flutter/example/pubspec.yaml b/packages/webview_flutter/webview_flutter/example/pubspec.yaml index 6b01b53ee4a3..4d8d7889d733 100644 --- a/packages/webview_flutter/webview_flutter/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the webview_flutter plugin. publish_to: none environment: - sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + sdk: ">=2.17.0 <3.0.0" + flutter: ">=3.0.0" dependencies: flutter: @@ -17,8 +17,11 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ + webview_flutter_android: ^3.0.0 + webview_flutter_wkwebview: ^3.0.0 dev_dependencies: + build_runner: ^2.1.5 espresso: ^0.2.0 flutter_driver: sdk: flutter @@ -26,6 +29,7 @@ dev_dependencies: sdk: flutter integration_test: sdk: flutter + webview_flutter_platform_interface: ^2.0.0 flutter: uses-material-design: true diff --git a/packages/webview_flutter/webview_flutter/example/test/main_test.dart b/packages/webview_flutter/webview_flutter/example/test/main_test.dart index 867633366e1a..7857022c14a0 100644 --- a/packages/webview_flutter/webview_flutter/example/test/main_test.dart +++ b/packages/webview_flutter/webview_flutter/example/test/main_test.dart @@ -4,17 +4,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_example/main.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; void main() { + setUp(() { + WebViewPlatform.instance = FakeWebViewPlatform(); + }); + testWidgets('Test snackbar from ScaffoldMessenger', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: WebViewExample(cookieManager: FakeCookieManager()), - ), - ); + await tester.pumpWidget(const MaterialApp(home: WebViewExample())); expect(find.byIcon(Icons.favorite), findsOneWidget); await tester.tap(find.byIcon(Icons.favorite)); await tester.pump(); @@ -22,18 +22,95 @@ void main() { }); } -class FakeCookieManager implements CookieManager { - factory FakeCookieManager() { - return _instance ??= FakeCookieManager._(); +class FakeWebViewPlatform extends WebViewPlatform { + @override + PlatformWebViewController createPlatformWebViewController( + PlatformWebViewControllerCreationParams params, + ) { + return FakeWebViewController(params); + } + + @override + PlatformWebViewWidget createPlatformWebViewWidget( + PlatformWebViewWidgetCreationParams params, + ) { + return FakeWebViewWidget(params); + } + + @override + PlatformWebViewCookieManager createPlatformCookieManager( + PlatformWebViewCookieManagerCreationParams params, + ) { + return FakeCookieManager(params); + } + + @override + PlatformNavigationDelegate createPlatformNavigationDelegate( + PlatformNavigationDelegateCreationParams params, + ) { + return FakeNavigationDelegate(params); + } +} + +class FakeWebViewController extends PlatformWebViewController { + FakeWebViewController(super.params) : super.implementation(); + + @override + Future setJavaScriptMode(JavaScriptMode javaScriptMode) async {} + + @override + Future setBackgroundColor(Color color) async {} + + @override + Future setPlatformNavigationDelegate( + PlatformNavigationDelegate handler, + ) async {} + + @override + Future addJavaScriptChannel( + JavaScriptChannelParams javaScriptChannelParams) async {} + + @override + Future loadRequest(LoadRequestParams params) async {} + + @override + Future currentUrl() async { + return 'https://www.google.com'; + } +} + +class FakeCookieManager extends PlatformWebViewCookieManager { + FakeCookieManager(super.params) : super.implementation(); +} + +class FakeWebViewWidget extends PlatformWebViewWidget { + FakeWebViewWidget(super.params) : super.implementation(); + + @override + Widget build(BuildContext context) { + return Container(); } +} - FakeCookieManager._(); +class FakeNavigationDelegate extends PlatformNavigationDelegate { + FakeNavigationDelegate(super.params) : super.implementation(); - static FakeCookieManager? _instance; + @override + Future setOnNavigationRequest( + NavigationRequestCallback onNavigationRequest, + ) async {} + + @override + Future setOnPageFinished(PageEventCallback onPageFinished) async {} + + @override + Future setOnPageStarted(PageEventCallback onPageStarted) async {} @override - Future clearCookies() => throw UnimplementedError(); + Future setOnProgress(ProgressCallback onProgress) async {} @override - Future setCookie(WebViewCookie cookie) => throw UnimplementedError(); + Future setOnWebResourceError( + WebResourceErrorCallback onWebResourceError, + ) async {} } diff --git a/packages/webview_flutter/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/webview_flutter/lib/src/legacy/platform_interface.dart similarity index 89% rename from packages/webview_flutter/webview_flutter/lib/platform_interface.dart rename to packages/webview_flutter/webview_flutter/lib/src/legacy/platform_interface.dart index 48f74346fe61..e036d2ef88a5 100644 --- a/packages/webview_flutter/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/legacy/platform_interface.dart @@ -5,7 +5,7 @@ /// Re-export the classes from the webview_flutter_platform_interface through /// the `platform_interface.dart` file so we don't accidentally break any /// non-endorsed existing implementations of the interface. -export 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' +export 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart' show AutoMediaPlaybackPolicy, CreationParams, diff --git a/packages/webview_flutter/webview_flutter/lib/src/webview.dart b/packages/webview_flutter/webview_flutter/lib/src/legacy/webview.dart similarity index 98% rename from packages/webview_flutter/webview_flutter/lib/src/webview.dart rename to packages/webview_flutter/webview_flutter/lib/src/legacy/webview.dart index 7de8e281711b..8d7baa9fa5af 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/webview.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/legacy/webview.dart @@ -8,10 +8,12 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; -import 'package:webview_flutter_android/webview_android_cookie_manager.dart'; -import 'package:webview_flutter_android/webview_surface_android.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_android/src/webview_flutter_android_legacy.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_wkwebview/src/webview_flutter_wkwebview_legacy.dart'; /// Optional callback invoked when a web view is first created. [controller] is /// the [WebViewController] for the created web view. @@ -76,7 +78,7 @@ class WebView extends StatefulWidget { /// /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null. const WebView({ - Key? key, + super.key, this.onWebViewCreated, this.initialUrl, this.initialCookies = const [], @@ -98,8 +100,7 @@ class WebView extends StatefulWidget { this.backgroundColor, }) : assert(javascriptMode != null), assert(initialMediaPlaybackPolicy != null), - assert(allowsInlineMediaPlayback != null), - super(key: key); + assert(allowsInlineMediaPlayback != null); static WebViewPlatform? _platform; diff --git a/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart b/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart new file mode 100644 index 000000000000..0651ad45f229 --- /dev/null +++ b/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart @@ -0,0 +1,104 @@ +// 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:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'webview_controller.dart'; + +/// Callbacks for accepting or rejecting navigation changes, and for tracking +/// the progress of navigation requests. +/// +/// See [WebViewController.setNavigationDelegate]. +class NavigationDelegate { + /// Constructs a [NavigationDelegate]. + NavigationDelegate({ + FutureOr Function(NavigationRequest request)? + onNavigationRequest, + void Function(String url)? onPageStarted, + void Function(String url)? onPageFinished, + void Function(int progress)? onProgress, + void Function(WebResourceError error)? onWebResourceError, + }) : this.fromPlatformCreationParams( + const PlatformNavigationDelegateCreationParams(), + onNavigationRequest: onNavigationRequest, + onPageStarted: onPageStarted, + onPageFinished: onPageFinished, + onProgress: onProgress, + onWebResourceError: onWebResourceError, + ); + + /// Constructs a [NavigationDelegate] from creation params for a specific + /// platform. + NavigationDelegate.fromPlatformCreationParams( + PlatformNavigationDelegateCreationParams params, { + FutureOr Function(NavigationRequest request)? + onNavigationRequest, + void Function(String url)? onPageStarted, + void Function(String url)? onPageFinished, + void Function(int progress)? onProgress, + void Function(WebResourceError error)? onWebResourceError, + }) : this.fromPlatform( + PlatformNavigationDelegate(params), + onNavigationRequest: onNavigationRequest, + onPageStarted: onPageStarted, + onPageFinished: onPageFinished, + onProgress: onProgress, + onWebResourceError: onWebResourceError, + ); + + /// Constructs a [NavigationDelegate] from a specific platform implementation. + NavigationDelegate.fromPlatform( + this.platform, { + this.onNavigationRequest, + this.onPageStarted, + this.onPageFinished, + this.onProgress, + this.onWebResourceError, + }) { + if (onNavigationRequest != null) { + platform.setOnNavigationRequest(onNavigationRequest!); + } + if (onPageStarted != null) { + platform.setOnPageStarted(onPageStarted!); + } + if (onPageFinished != null) { + platform.setOnPageFinished(onPageFinished!); + } + if (onProgress != null) { + platform.setOnProgress(onProgress!); + } + if (onWebResourceError != null) { + platform.setOnWebResourceError(onWebResourceError!); + } + } + + /// Implementation of [PlatformNavigationDelegate] for the current platform. + final PlatformNavigationDelegate platform; + + /// Invoked when a decision for a navigation request is pending. + /// + /// When a navigation is initiated by the WebView (e.g when a user clicks a + /// link) this delegate is called and has to decide how to proceed with the + /// navigation. + /// + /// *Important*: Some platforms may also trigger this callback from calls to + /// [WebViewController.loadRequest]. + /// + /// See [NavigationDecision]. + final NavigationRequestCallback? onNavigationRequest; + + /// Invoked when a page has started loading. + final PageEventCallback? onPageStarted; + + /// Invoked when a page has finished loading. + final PageEventCallback? onPageFinished; + + /// Invoked when a page is loading to report the progress. + final ProgressCallback? onProgress; + + /// Invoked when a resource loading error occurred. + final WebResourceErrorCallback? onWebResourceError; +} diff --git a/packages/webview_flutter/webview_flutter/lib/src/v4/webview_flutter.dart b/packages/webview_flutter/webview_flutter/lib/src/v4/webview_flutter.dart deleted file mode 100644 index f4a0b207e27a..000000000000 --- a/packages/webview_flutter/webview_flutter/lib/src/v4/webview_flutter.dart +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -library webview_flutter; - -export 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart' - show JavaScriptMessage, LoadRequestMethod, WebViewCookie; - -export 'src/webview_controller.dart'; -export 'src/webview_cookie_manager.dart'; -export 'src/webview_widget.dart'; diff --git a/packages/webview_flutter/webview_flutter/lib/src/v4/src/webview_controller.dart b/packages/webview_flutter/webview_flutter/lib/src/webview_controller.dart similarity index 94% rename from packages/webview_flutter/webview_flutter/lib/src/v4/src/webview_controller.dart rename to packages/webview_flutter/webview_flutter/lib/src/webview_controller.dart index bd03b247027e..d632d1e95231 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/v4/src/webview_controller.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/webview_controller.dart @@ -2,14 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:math'; - // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'navigation_delegate.dart'; /// Controls a WebView provided by the host platform. /// @@ -125,6 +125,12 @@ class WebViewController { return platform.reload(); } + /// Sets the [NavigationDelegate] containing the callback methods that are + /// called during navigation events. + Future setNavigationDelegate(NavigationDelegate delegate) { + return platform.setPlatformNavigationDelegate(delegate.platform); + } + /// Clears all caches used by the WebView. /// /// The following caches are cleared: @@ -233,9 +239,8 @@ class WebViewController { /// Returns the current scroll position of this view. /// /// Scroll position is measured from the top left. - Future getScrollPosition() async { - final Point position = await platform.getScrollPosition(); - return Offset(position.x.toDouble(), position.y.toDouble()); + Future getScrollPosition() { + return platform.getScrollPosition(); } /// Whether to support zooming using the on-screen zoom controls and gestures. diff --git a/packages/webview_flutter/webview_flutter/lib/src/v4/src/webview_cookie_manager.dart b/packages/webview_flutter/webview_flutter/lib/src/webview_cookie_manager.dart similarity index 93% rename from packages/webview_flutter/webview_flutter/lib/src/v4/src/webview_cookie_manager.dart rename to packages/webview_flutter/webview_flutter/lib/src/webview_cookie_manager.dart index a1091fa3c7b1..bffa1b5a71d2 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/v4/src/webview_cookie_manager.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/webview_cookie_manager.dart @@ -2,7 +2,7 @@ // 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 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; /// Manages cookies pertaining to all WebViews. class WebViewCookieManager { diff --git a/packages/webview_flutter/webview_flutter/lib/src/webview_flutter_legacy.dart b/packages/webview_flutter/webview_flutter/lib/src/webview_flutter_legacy.dart new file mode 100644 index 000000000000..d040fc2e71d8 --- /dev/null +++ b/packages/webview_flutter/webview_flutter/lib/src/webview_flutter_legacy.dart @@ -0,0 +1,9 @@ +// 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. + +export 'package:webview_flutter_android/src/webview_flutter_android_legacy.dart'; +export 'package:webview_flutter_wkwebview/src/webview_flutter_wkwebview_legacy.dart'; + +export 'legacy/platform_interface.dart'; +export 'legacy/webview.dart'; diff --git a/packages/webview_flutter/webview_flutter/lib/src/v4/src/webview_widget.dart b/packages/webview_flutter/webview_flutter/lib/src/webview_widget.dart similarity index 92% rename from packages/webview_flutter/webview_flutter/lib/src/v4/src/webview_widget.dart rename to packages/webview_flutter/webview_flutter/lib/src/webview_widget.dart index 06e4f78028df..b3180115c801 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/v4/src/webview_widget.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/webview_widget.dart @@ -5,7 +5,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; -import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webview_controller.dart'; @@ -35,8 +35,7 @@ class WebViewWidget extends StatelessWidget { }) : this.fromPlatform(key: key, platform: PlatformWebViewWidget(params)); /// Constructs a [WebViewWidget] from a specific platform implementation. - WebViewWidget.fromPlatform({Key? key, required this.platform}) - : super(key: key); + WebViewWidget.fromPlatform({super.key, required this.platform}); /// Implementation of [PlatformWebViewWidget] for the current platform. final PlatformWebViewWidget platform; diff --git a/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart index ba38771e5107..7b8301db2d4a 100644 --- a/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart @@ -2,9 +2,28 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -export 'package:webview_flutter_android/webview_android.dart'; -export 'package:webview_flutter_android/webview_surface_android.dart'; -export 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; +library webview_flutter; -export 'platform_interface.dart'; -export 'src/webview.dart'; +export 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' + show + JavaScriptMessage, + JavaScriptMode, + LoadRequestMethod, + NavigationDecision, + NavigationRequest, + NavigationRequestCallback, + PageEventCallback, + PlatformNavigationDelegateCreationParams, + PlatformWebViewControllerCreationParams, + PlatformWebViewCookieManagerCreationParams, + PlatformWebViewWidgetCreationParams, + ProgressCallback, + WebResourceError, + WebResourceErrorCallback, + WebViewCookie, + WebViewPlatform; + +export 'src/navigation_delegate.dart'; +export 'src/webview_controller.dart'; +export 'src/webview_cookie_manager.dart'; +export 'src/webview_widget.dart'; diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml index a02b0323e7ab..58c0411ad7dd 100644 --- a/packages/webview_flutter/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/pubspec.yaml @@ -2,11 +2,11 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.0.4 +version: 4.0.0 environment: - sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + sdk: ">=2.17.0 <3.0.0" + flutter: ">=3.0.0" flutter: plugin: @@ -19,9 +19,9 @@ flutter: dependencies: flutter: sdk: flutter - webview_flutter_android: ^2.8.0 - webview_flutter_platform_interface: ^1.9.3 - webview_flutter_wkwebview: ^2.7.0 + webview_flutter_android: ^3.0.0 + webview_flutter_platform_interface: ^2.0.0 + webview_flutter_wkwebview: ^3.0.0 dev_dependencies: build_runner: ^2.1.5 @@ -29,4 +29,5 @@ dev_dependencies: sdk: flutter flutter_test: sdk: flutter - mockito: ^5.0.16 + mockito: ^5.3.2 + plugin_platform_interface: ^2.1.3 diff --git a/packages/webview_flutter/webview_flutter/test/legacy/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/test/legacy/webview_flutter_test.dart new file mode 100644 index 000000000000..4db70113dfb2 --- /dev/null +++ b/packages/webview_flutter/webview_flutter/test/legacy/webview_flutter_test.dart @@ -0,0 +1,1367 @@ +// 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:typed_data'; + +import 'package:flutter/src/foundation/basic_types.dart'; +import 'package:flutter/src/gestures/recognizer.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter/src/webview_flutter_legacy.dart'; +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; + +import 'webview_flutter_test.mocks.dart'; + +typedef VoidCallback = void Function(); + +@GenerateMocks([WebViewPlatform, WebViewPlatformController]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late MockWebViewPlatform mockWebViewPlatform; + late MockWebViewPlatformController mockWebViewPlatformController; + late MockWebViewCookieManagerPlatform mockWebViewCookieManagerPlatform; + + setUp(() { + mockWebViewPlatformController = MockWebViewPlatformController(); + mockWebViewPlatform = MockWebViewPlatform(); + mockWebViewCookieManagerPlatform = MockWebViewCookieManagerPlatform(); + when(mockWebViewPlatform.build( + context: anyNamed('context'), + creationParams: anyNamed('creationParams'), + webViewPlatformCallbacksHandler: + anyNamed('webViewPlatformCallbacksHandler'), + javascriptChannelRegistry: anyNamed('javascriptChannelRegistry'), + onWebViewPlatformCreated: anyNamed('onWebViewPlatformCreated'), + gestureRecognizers: anyNamed('gestureRecognizers'), + )).thenAnswer((Invocation invocation) { + final WebViewPlatformCreatedCallback onWebViewPlatformCreated = + invocation.namedArguments[const Symbol('onWebViewPlatformCreated')] + as WebViewPlatformCreatedCallback; + return TestPlatformWebView( + mockWebViewPlatformController: mockWebViewPlatformController, + onWebViewPlatformCreated: onWebViewPlatformCreated, + ); + }); + + WebView.platform = mockWebViewPlatform; + WebViewCookieManagerPlatform.instance = mockWebViewCookieManagerPlatform; + }); + + tearDown(() { + mockWebViewCookieManagerPlatform.reset(); + }); + + testWidgets('Create WebView', (WidgetTester tester) async { + await tester.pumpWidget(const WebView()); + }); + + testWidgets('Initial url', (WidgetTester tester) async { + await tester.pumpWidget(const WebView(initialUrl: 'https://youtube.com')); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.initialUrl, 'https://youtube.com'); + }); + + testWidgets('Javascript mode', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + javascriptMode: JavascriptMode.unrestricted, + )); + + final CreationParams unrestrictedparams = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect( + unrestrictedparams.webSettings!.javascriptMode, + JavascriptMode.unrestricted, + ); + + await tester.pumpWidget(const WebView()); + + final CreationParams disabledparams = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(disabledparams.webSettings!.javascriptMode, JavascriptMode.disabled); + }); + + testWidgets('Load file', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + await controller!.loadFile('/test/path/index.html'); + + verify(mockWebViewPlatformController.loadFile( + '/test/path/index.html', + )); + }); + + testWidgets('Load file with empty path', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + expect(() => controller!.loadFile(''), throwsAssertionError); + }); + + testWidgets('Load Flutter asset', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + await controller!.loadFlutterAsset('assets/index.html'); + + verify(mockWebViewPlatformController.loadFlutterAsset( + 'assets/index.html', + )); + }); + + testWidgets('Load Flutter asset with empty key', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + expect(() => controller!.loadFlutterAsset(''), throwsAssertionError); + }); + + testWidgets('Load HTML string without base URL', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + await controller!.loadHtmlString('

This is a test paragraph.

'); + + verify(mockWebViewPlatformController.loadHtmlString( + '

This is a test paragraph.

', + )); + }); + + testWidgets('Load HTML string with base URL', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + await controller!.loadHtmlString( + '

This is a test paragraph.

', + baseUrl: 'https://flutter.dev', + ); + + verify(mockWebViewPlatformController.loadHtmlString( + '

This is a test paragraph.

', + baseUrl: 'https://flutter.dev', + )); + }); + + testWidgets('Load HTML string with empty string', + (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + expect(() => controller!.loadHtmlString(''), throwsAssertionError); + }); + + testWidgets('Load url', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + await controller!.loadUrl('https://flutter.io'); + + verify(mockWebViewPlatformController.loadUrl( + 'https://flutter.io', + argThat(isNull), + )); + }); + + testWidgets('Invalid urls', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.initialUrl, isNull); + + expect(() => controller!.loadUrl(''), throwsA(anything)); + expect(() => controller!.loadUrl('flutter.io'), throwsA(anything)); + }); + + testWidgets('Headers in loadUrl', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + final Map headers = { + 'CACHE-CONTROL': 'ABC' + }; + await controller!.loadUrl('https://flutter.io', headers: headers); + + verify(mockWebViewPlatformController.loadUrl( + 'https://flutter.io', + {'CACHE-CONTROL': 'ABC'}, + )); + }); + + testWidgets('loadRequest', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + expect(controller, isNotNull); + + final WebViewRequest req = WebViewRequest( + uri: Uri.parse('https://flutter.dev'), + method: WebViewRequestMethod.post, + headers: {'foo': 'bar'}, + body: Uint8List.fromList('Test Body'.codeUnits), + ); + + await controller!.loadRequest(req); + + verify(mockWebViewPlatformController.loadRequest(req)); + }); + + testWidgets('Clear Cache', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + await controller!.clearCache(); + + verify(mockWebViewPlatformController.clearCache()); + }); + + testWidgets('Can go back', (WidgetTester tester) async { + when(mockWebViewPlatformController.canGoBack()) + .thenAnswer((_) => Future.value(true)); + + WebViewController? controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + expect(controller!.canGoBack(), completion(true)); + }); + + testWidgets("Can't go forward", (WidgetTester tester) async { + when(mockWebViewPlatformController.canGoForward()) + .thenAnswer((_) => Future.value(false)); + + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + expect(controller!.canGoForward(), completion(false)); + }); + + testWidgets('Go back', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + await controller!.goBack(); + verify(mockWebViewPlatformController.goBack()); + }); + + testWidgets('Go forward', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + await controller!.goForward(); + verify(mockWebViewPlatformController.goForward()); + }); + + testWidgets('Current URL', (WidgetTester tester) async { + when(mockWebViewPlatformController.currentUrl()) + .thenAnswer((_) => Future.value('https://youtube.com')); + + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + expect(await controller!.currentUrl(), 'https://youtube.com'); + }); + + testWidgets('Reload url', (WidgetTester tester) async { + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + await controller.reload(); + verify(mockWebViewPlatformController.reload()); + }); + + testWidgets('evaluate Javascript', (WidgetTester tester) async { + when(mockWebViewPlatformController.evaluateJavascript('fake js string')) + .thenAnswer((_) => Future.value('fake js string')); + + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + javascriptMode: JavascriptMode.unrestricted, + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect( + // ignore: deprecated_member_use_from_same_package + await controller.evaluateJavascript('fake js string'), + 'fake js string', + reason: 'should get the argument'); + }); + + testWidgets('evaluate Javascript with JavascriptMode disabled', + (WidgetTester tester) async { + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + expect( + // ignore: deprecated_member_use_from_same_package + () => controller.evaluateJavascript('fake js string'), + throwsA(anything), + ); + }); + + testWidgets('runJavaScript', (WidgetTester tester) async { + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + javascriptMode: JavascriptMode.unrestricted, + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + await controller.runJavascript('fake js string'); + verify(mockWebViewPlatformController.runJavascript('fake js string')); + }); + + testWidgets('runJavaScript with JavascriptMode disabled', + (WidgetTester tester) async { + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + expect( + () => controller.runJavascript('fake js string'), + throwsA(anything), + ); + }); + + testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { + when(mockWebViewPlatformController + .runJavascriptReturningResult('fake js string')) + .thenAnswer((_) => Future.value('fake js string')); + + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + javascriptMode: JavascriptMode.unrestricted, + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + expect(await controller.runJavascriptReturningResult('fake js string'), + 'fake js string', + reason: 'should get the argument'); + }); + + testWidgets('runJavaScriptReturningResult with JavascriptMode disabled', + (WidgetTester tester) async { + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + expect( + () => controller.runJavascriptReturningResult('fake js string'), + throwsA(anything), + ); + }); + + testWidgets('Cookies can be cleared once', (WidgetTester tester) async { + await tester.pumpWidget( + const WebView( + initialUrl: 'https://flutter.io', + ), + ); + final CookieManager cookieManager = CookieManager(); + final bool hasCookies = await cookieManager.clearCookies(); + expect(hasCookies, true); + }); + + testWidgets('Cookies can be set', (WidgetTester tester) async { + const WebViewCookie cookie = + WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev'); + + await tester.pumpWidget( + const WebView( + initialUrl: 'https://flutter.io', + ), + ); + final CookieManager cookieManager = CookieManager(); + await cookieManager.setCookie(cookie); + expect(mockWebViewCookieManagerPlatform.setCookieCalls, + [cookie]); + }); + + testWidgets('Initial JavaScript channels', (WidgetTester tester) async { + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), + JavascriptChannel( + name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), + }, + ), + ); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.javascriptChannelNames, + unorderedEquals(['Tts', 'Alarm'])); + }); + + test('Only valid JavaScript channel names are allowed', () { + void noOp(JavascriptMessage msg) {} + JavascriptChannel(name: 'Tts1', onMessageReceived: noOp); + JavascriptChannel(name: '_Alarm', onMessageReceived: noOp); + JavascriptChannel(name: 'foo_bar_', onMessageReceived: noOp); + + VoidCallback createChannel(String name) { + return () { + JavascriptChannel(name: name, onMessageReceived: noOp); + }; + } + + expect(createChannel('1Alarm'), throwsAssertionError); + expect(createChannel('foo.bar'), throwsAssertionError); + expect(createChannel(''), throwsAssertionError); + }); + + testWidgets('Unique JavaScript channel names are required', + (WidgetTester tester) async { + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), + JavascriptChannel( + name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), + }, + ), + ); + expect(tester.takeException(), isNot(null)); + }); + + testWidgets('JavaScript channels update', (WidgetTester tester) async { + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), + JavascriptChannel( + name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), + }, + ), + ); + + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), + JavascriptChannel( + name: 'Alarm2', onMessageReceived: (JavascriptMessage msg) {}), + JavascriptChannel( + name: 'Alarm3', onMessageReceived: (JavascriptMessage msg) {}), + }, + ), + ); + + final JavascriptChannelRegistry channelRegistry = captureBuildArgs( + mockWebViewPlatform, + javascriptChannelRegistry: true, + ).first as JavascriptChannelRegistry; + + expect( + channelRegistry.channels.keys, + unorderedEquals(['Tts', 'Alarm2', 'Alarm3']), + ); + }); + + testWidgets('Remove all JavaScript channels and then add', + (WidgetTester tester) async { + // This covers a specific bug we had where after updating javascriptChannels to null, + // updating it again with a subset of the previously registered channels fails as the + // widget's cache of current channel wasn't properly updated when updating javascriptChannels to + // null. + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), + }, + ), + ); + + await tester.pumpWidget( + const WebView( + initialUrl: 'https://youtube.com', + ), + ); + + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), + }, + ), + ); + + final JavascriptChannelRegistry channelRegistry = captureBuildArgs( + mockWebViewPlatform, + javascriptChannelRegistry: true, + ).last as JavascriptChannelRegistry; + + expect(channelRegistry.channels.keys, unorderedEquals(['Tts'])); + }); + + testWidgets('JavaScript channel messages', (WidgetTester tester) async { + final List ttsMessagesReceived = []; + final List alarmMessagesReceived = []; + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'Tts', + onMessageReceived: (JavascriptMessage msg) { + ttsMessagesReceived.add(msg.message); + }), + JavascriptChannel( + name: 'Alarm', + onMessageReceived: (JavascriptMessage msg) { + alarmMessagesReceived.add(msg.message); + }), + }, + ), + ); + + final JavascriptChannelRegistry channelRegistry = captureBuildArgs( + mockWebViewPlatform, + javascriptChannelRegistry: true, + ).single as JavascriptChannelRegistry; + + expect(ttsMessagesReceived, isEmpty); + expect(alarmMessagesReceived, isEmpty); + + channelRegistry.onJavascriptChannelMessage('Tts', 'Hello'); + channelRegistry.onJavascriptChannelMessage('Tts', 'World'); + + expect(ttsMessagesReceived, ['Hello', 'World']); + }); + + group('$PageStartedCallback', () { + testWidgets('onPageStarted is not null', (WidgetTester tester) async { + String? returnedUrl; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onPageStarted: (String url) { + returnedUrl = url; + }, + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).single as WebViewPlatformCallbacksHandler; + + handler.onPageStarted('https://youtube.com'); + + expect(returnedUrl, 'https://youtube.com'); + }); + + testWidgets('onPageStarted is null', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + initialUrl: 'https://youtube.com', + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).single as WebViewPlatformCallbacksHandler; + + // The platform side will always invoke a call for onPageStarted. This is + // to test that it does not crash on a null callback. + handler.onPageStarted('https://youtube.com'); + }); + + testWidgets('onPageStarted changed', (WidgetTester tester) async { + String? returnedUrl; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onPageStarted: (String url) {}, + )); + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onPageStarted: (String url) { + returnedUrl = url; + }, + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).last as WebViewPlatformCallbacksHandler; + handler.onPageStarted('https://youtube.com'); + + expect(returnedUrl, 'https://youtube.com'); + }); + }); + + group('$PageFinishedCallback', () { + testWidgets('onPageFinished is not null', (WidgetTester tester) async { + String? returnedUrl; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onPageFinished: (String url) { + returnedUrl = url; + }, + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).single as WebViewPlatformCallbacksHandler; + handler.onPageFinished('https://youtube.com'); + + expect(returnedUrl, 'https://youtube.com'); + }); + + testWidgets('onPageFinished is null', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + initialUrl: 'https://youtube.com', + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).single as WebViewPlatformCallbacksHandler; + // The platform side will always invoke a call for onPageFinished. This is + // to test that it does not crash on a null callback. + handler.onPageFinished('https://youtube.com'); + }); + + testWidgets('onPageFinished changed', (WidgetTester tester) async { + String? returnedUrl; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onPageFinished: (String url) {}, + )); + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onPageFinished: (String url) { + returnedUrl = url; + }, + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).last as WebViewPlatformCallbacksHandler; + handler.onPageFinished('https://youtube.com'); + + expect(returnedUrl, 'https://youtube.com'); + }); + }); + + group('$PageLoadingCallback', () { + testWidgets('onLoadingProgress is not null', (WidgetTester tester) async { + int? loadingProgress; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onProgress: (int progress) { + loadingProgress = progress; + }, + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).single as WebViewPlatformCallbacksHandler; + handler.onProgress(50); + + expect(loadingProgress, 50); + }); + + testWidgets('onLoadingProgress is null', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + initialUrl: 'https://youtube.com', + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).single as WebViewPlatformCallbacksHandler; + + // This is to test that it does not crash on a null callback. + handler.onProgress(50); + }); + + testWidgets('onLoadingProgress changed', (WidgetTester tester) async { + int? loadingProgress; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onProgress: (int progress) {}, + )); + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onProgress: (int progress) { + loadingProgress = progress; + }, + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).last as WebViewPlatformCallbacksHandler; + handler.onProgress(50); + + expect(loadingProgress, 50); + }); + }); + + group('navigationDelegate', () { + testWidgets('hasNavigationDelegate', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + initialUrl: 'https://youtube.com', + )); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.webSettings!.hasNavigationDelegate, false); + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + navigationDelegate: (NavigationRequest r) => + NavigationDecision.navigate, + )); + + final WebSettings updateSettings = + verify(mockWebViewPlatformController.updateSettings(captureAny)) + .captured + .single as WebSettings; + + expect(updateSettings.hasNavigationDelegate, true); + }); + + testWidgets('Block navigation', (WidgetTester tester) async { + final List navigationRequests = []; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + navigationDelegate: (NavigationRequest request) { + navigationRequests.add(request); + // Only allow navigating to https://flutter.dev + return request.url == 'https://flutter.dev' + ? NavigationDecision.navigate + : NavigationDecision.prevent; + })); + + final List args = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + webViewPlatformCallbacksHandler: true, + ); + + final CreationParams params = args[0] as CreationParams; + expect(params.webSettings!.hasNavigationDelegate, true); + + final WebViewPlatformCallbacksHandler handler = + args[1] as WebViewPlatformCallbacksHandler; + + // The navigation delegate only allows navigation to https://flutter.dev + // so we should still be in https://youtube.com. + expect( + handler.onNavigationRequest( + url: 'https://www.google.com', + isForMainFrame: true, + ), + completion(false), + ); + + expect(navigationRequests.length, 1); + expect(navigationRequests[0].url, 'https://www.google.com'); + expect(navigationRequests[0].isForMainFrame, true); + + expect( + handler.onNavigationRequest( + url: 'https://flutter.dev', + isForMainFrame: true, + ), + completion(true), + ); + }); + }); + + group('debuggingEnabled', () { + testWidgets('enable debugging', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + debuggingEnabled: true, + )); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.webSettings!.debuggingEnabled, true); + }); + + testWidgets('defaults to false', (WidgetTester tester) async { + await tester.pumpWidget(const WebView()); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.webSettings!.debuggingEnabled, false); + }); + + testWidgets('can be changed', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + await tester.pumpWidget(WebView(key: key)); + + await tester.pumpWidget(WebView( + key: key, + debuggingEnabled: true, + )); + + final WebSettings enabledSettings = + verify(mockWebViewPlatformController.updateSettings(captureAny)) + .captured + .last as WebSettings; + expect(enabledSettings.debuggingEnabled, true); + + await tester.pumpWidget(WebView( + key: key, + )); + + final WebSettings disabledSettings = + verify(mockWebViewPlatformController.updateSettings(captureAny)) + .captured + .last as WebSettings; + expect(disabledSettings.debuggingEnabled, false); + }); + }); + + group('zoomEnabled', () { + testWidgets('Enable zoom', (WidgetTester tester) async { + await tester.pumpWidget(const WebView()); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.webSettings!.zoomEnabled, isTrue); + }); + + testWidgets('defaults to true', (WidgetTester tester) async { + await tester.pumpWidget(const WebView()); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.webSettings!.zoomEnabled, isTrue); + }); + + testWidgets('can be changed', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + await tester.pumpWidget(WebView(key: key)); + + await tester.pumpWidget(WebView( + key: key, + )); + + final WebSettings enabledSettings = + verify(mockWebViewPlatformController.updateSettings(captureAny)) + .captured + .last as WebSettings; + // Zoom defaults to true, so no changes are made to settings. + expect(enabledSettings.zoomEnabled, isNull); + + await tester.pumpWidget(WebView( + key: key, + zoomEnabled: false, + )); + + final WebSettings disabledSettings = + verify(mockWebViewPlatformController.updateSettings(captureAny)) + .captured + .last as WebSettings; + expect(disabledSettings.zoomEnabled, isFalse); + }); + }); + + group('Background color', () { + testWidgets('Defaults to null', (WidgetTester tester) async { + await tester.pumpWidget(const WebView()); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.backgroundColor, null); + }); + + testWidgets('Can be transparent', (WidgetTester tester) async { + const Color transparentColor = Color(0x00000000); + + await tester.pumpWidget(const WebView( + backgroundColor: transparentColor, + )); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.backgroundColor, transparentColor); + }); + }); + + group('Custom platform implementation', () { + setUp(() { + WebView.platform = MyWebViewPlatform(); + }); + tearDownAll(() { + WebView.platform = null; + }); + + testWidgets('creation', (WidgetTester tester) async { + await tester.pumpWidget( + const WebView( + initialUrl: 'https://youtube.com', + gestureNavigationEnabled: true, + ), + ); + + final MyWebViewPlatform builder = WebView.platform as MyWebViewPlatform; + final MyWebViewPlatformController platform = builder.lastPlatformBuilt!; + + expect( + platform.creationParams, + MatchesCreationParams(CreationParams( + initialUrl: 'https://youtube.com', + webSettings: WebSettings( + javascriptMode: JavascriptMode.disabled, + hasNavigationDelegate: false, + debuggingEnabled: false, + userAgent: const WebSetting.of(null), + gestureNavigationEnabled: true, + zoomEnabled: true, + ), + ))); + }); + + testWidgets('loadUrl', (WidgetTester tester) async { + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + final MyWebViewPlatform builder = WebView.platform as MyWebViewPlatform; + final MyWebViewPlatformController platform = builder.lastPlatformBuilt!; + + final Map headers = { + 'header': 'value', + }; + + await controller.loadUrl('https://google.com', headers: headers); + + expect(platform.lastUrlLoaded, 'https://google.com'); + expect(platform.lastRequestHeaders, headers); + }); + }); + + testWidgets('Set UserAgent', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + initialUrl: 'https://youtube.com', + javascriptMode: JavascriptMode.unrestricted, + )); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.webSettings!.userAgent.value, isNull); + + await tester.pumpWidget(const WebView( + initialUrl: 'https://youtube.com', + javascriptMode: JavascriptMode.unrestricted, + userAgent: 'UA', + )); + + final WebSettings settings = + verify(mockWebViewPlatformController.updateSettings(captureAny)) + .captured + .last as WebSettings; + expect(settings.userAgent.value, 'UA'); + }); +} + +List captureBuildArgs( + MockWebViewPlatform mockWebViewPlatform, { + bool context = false, + bool creationParams = false, + bool webViewPlatformCallbacksHandler = false, + bool javascriptChannelRegistry = false, + bool onWebViewPlatformCreated = false, + bool gestureRecognizers = false, +}) { + return verify(mockWebViewPlatform.build( + context: context ? captureAnyNamed('context') : anyNamed('context'), + creationParams: creationParams + ? captureAnyNamed('creationParams') + : anyNamed('creationParams'), + webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler + ? captureAnyNamed('webViewPlatformCallbacksHandler') + : anyNamed('webViewPlatformCallbacksHandler'), + javascriptChannelRegistry: javascriptChannelRegistry + ? captureAnyNamed('javascriptChannelRegistry') + : anyNamed('javascriptChannelRegistry'), + onWebViewPlatformCreated: onWebViewPlatformCreated + ? captureAnyNamed('onWebViewPlatformCreated') + : anyNamed('onWebViewPlatformCreated'), + gestureRecognizers: gestureRecognizers + ? captureAnyNamed('gestureRecognizers') + : anyNamed('gestureRecognizers'), + )).captured; +} + +// This Widget ensures that onWebViewPlatformCreated is only called once when +// making multiple calls to `WidgetTester.pumpWidget` with different parameters +// for the WebView. +class TestPlatformWebView extends StatefulWidget { + const TestPlatformWebView({ + super.key, + required this.mockWebViewPlatformController, + this.onWebViewPlatformCreated, + }); + + final MockWebViewPlatformController mockWebViewPlatformController; + final WebViewPlatformCreatedCallback? onWebViewPlatformCreated; + + @override + State createState() => TestPlatformWebViewState(); +} + +class TestPlatformWebViewState extends State { + @override + void initState() { + super.initState(); + final WebViewPlatformCreatedCallback? onWebViewPlatformCreated = + widget.onWebViewPlatformCreated; + if (onWebViewPlatformCreated != null) { + onWebViewPlatformCreated(widget.mockWebViewPlatformController); + } + } + + @override + Widget build(BuildContext context) { + return Container(); + } +} + +class MyWebViewPlatform implements WebViewPlatform { + MyWebViewPlatformController? lastPlatformBuilt; + + @override + Widget build({ + BuildContext? context, + CreationParams? creationParams, + required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, + required JavascriptChannelRegistry javascriptChannelRegistry, + WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set>? gestureRecognizers, + }) { + assert(onWebViewPlatformCreated != null); + lastPlatformBuilt = MyWebViewPlatformController( + creationParams, gestureRecognizers, webViewPlatformCallbacksHandler); + onWebViewPlatformCreated!(lastPlatformBuilt); + return Container(); + } + + @override + Future clearCookies() { + return Future.sync(() => true); + } +} + +class MyWebViewPlatformController extends WebViewPlatformController { + MyWebViewPlatformController(this.creationParams, this.gestureRecognizers, + WebViewPlatformCallbacksHandler platformHandler) + : super(platformHandler); + + CreationParams? creationParams; + Set>? gestureRecognizers; + + String? lastUrlLoaded; + Map? lastRequestHeaders; + + @override + Future loadUrl(String url, Map? headers) async { + equals(1, 1); + lastUrlLoaded = url; + lastRequestHeaders = headers; + } +} + +class MatchesWebSettings extends Matcher { + MatchesWebSettings(this._webSettings); + + final WebSettings? _webSettings; + + @override + Description describe(Description description) => + description.add('$_webSettings'); + + @override + bool matches( + covariant WebSettings webSettings, Map matchState) { + return _webSettings!.javascriptMode == webSettings.javascriptMode && + _webSettings!.hasNavigationDelegate == + webSettings.hasNavigationDelegate && + _webSettings!.debuggingEnabled == webSettings.debuggingEnabled && + _webSettings!.gestureNavigationEnabled == + webSettings.gestureNavigationEnabled && + _webSettings!.userAgent == webSettings.userAgent && + _webSettings!.zoomEnabled == webSettings.zoomEnabled; + } +} + +class MatchesCreationParams extends Matcher { + MatchesCreationParams(this._creationParams); + + final CreationParams _creationParams; + + @override + Description describe(Description description) => + description.add('$_creationParams'); + + @override + bool matches(covariant CreationParams creationParams, + Map matchState) { + return _creationParams.initialUrl == creationParams.initialUrl && + MatchesWebSettings(_creationParams.webSettings) + .matches(creationParams.webSettings!, matchState) && + orderedEquals(_creationParams.javascriptChannelNames) + .matches(creationParams.javascriptChannelNames, matchState); + } +} + +class MockWebViewCookieManagerPlatform extends WebViewCookieManagerPlatform { + List setCookieCalls = []; + + @override + Future clearCookies() async => true; + + @override + Future setCookie(WebViewCookie cookie) async { + setCookieCalls.add(cookie); + } + + void reset() { + setCookieCalls = []; + } +} diff --git a/packages/webview_flutter/webview_flutter/test/legacy/webview_flutter_test.mocks.dart b/packages/webview_flutter/webview_flutter/test/legacy/webview_flutter_test.mocks.dart new file mode 100644 index 000000000000..a40cf34828ae --- /dev/null +++ b/packages/webview_flutter/webview_flutter/test/legacy/webview_flutter_test.mocks.dart @@ -0,0 +1,346 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in webview_flutter/test/legacy/webview_flutter_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i9; + +import 'package:flutter/foundation.dart' as _i3; +import 'package:flutter/gestures.dart' as _i8; +import 'package:flutter/widgets.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/javascript_channel_registry.dart' + as _i7; +import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/webview_platform.dart' + as _i4; +import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/webview_platform_callbacks_handler.dart' + as _i6; +import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/webview_platform_controller.dart' + as _i10; +import 'package:webview_flutter_platform_interface/src/legacy/types/types.dart' + as _i5; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeWidget_0 extends _i1.SmartFake implements _i2.Widget { + _FakeWidget_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); +} + +/// A class which mocks [WebViewPlatform]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewPlatform extends _i1.Mock implements _i4.WebViewPlatform { + MockWebViewPlatform() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.Widget build({ + required _i2.BuildContext? context, + required _i5.CreationParams? creationParams, + required _i6.WebViewPlatformCallbacksHandler? + webViewPlatformCallbacksHandler, + required _i7.JavascriptChannelRegistry? javascriptChannelRegistry, + _i4.WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set<_i3.Factory<_i8.OneSequenceGestureRecognizer>>? gestureRecognizers, + }) => + (super.noSuchMethod( + Invocation.method( + #build, + [], + { + #context: context, + #creationParams: creationParams, + #webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, + #javascriptChannelRegistry: javascriptChannelRegistry, + #onWebViewPlatformCreated: onWebViewPlatformCreated, + #gestureRecognizers: gestureRecognizers, + }, + ), + returnValue: _FakeWidget_0( + this, + Invocation.method( + #build, + [], + { + #context: context, + #creationParams: creationParams, + #webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, + #javascriptChannelRegistry: javascriptChannelRegistry, + #onWebViewPlatformCreated: onWebViewPlatformCreated, + #gestureRecognizers: gestureRecognizers, + }, + ), + ), + ) as _i2.Widget); + @override + _i9.Future clearCookies() => (super.noSuchMethod( + Invocation.method( + #clearCookies, + [], + ), + returnValue: _i9.Future.value(false), + ) as _i9.Future); +} + +/// A class which mocks [WebViewPlatformController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewPlatformController extends _i1.Mock + implements _i10.WebViewPlatformController { + MockWebViewPlatformController() { + _i1.throwOnMissingStub(this); + } + + @override + _i9.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( + Invocation.method( + #loadFile, + [absoluteFilePath], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future loadFlutterAsset(String? key) => (super.noSuchMethod( + Invocation.method( + #loadFlutterAsset, + [key], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future loadHtmlString( + String? html, { + String? baseUrl, + }) => + (super.noSuchMethod( + Invocation.method( + #loadHtmlString, + [html], + {#baseUrl: baseUrl}, + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future loadUrl( + String? url, + Map? headers, + ) => + (super.noSuchMethod( + Invocation.method( + #loadUrl, + [ + url, + headers, + ], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future loadRequest(_i5.WebViewRequest? request) => + (super.noSuchMethod( + Invocation.method( + #loadRequest, + [request], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future updateSettings(_i5.WebSettings? setting) => + (super.noSuchMethod( + Invocation.method( + #updateSettings, + [setting], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future currentUrl() => (super.noSuchMethod( + Invocation.method( + #currentUrl, + [], + ), + returnValue: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future canGoBack() => (super.noSuchMethod( + Invocation.method( + #canGoBack, + [], + ), + returnValue: _i9.Future.value(false), + ) as _i9.Future); + @override + _i9.Future canGoForward() => (super.noSuchMethod( + Invocation.method( + #canGoForward, + [], + ), + returnValue: _i9.Future.value(false), + ) as _i9.Future); + @override + _i9.Future goBack() => (super.noSuchMethod( + Invocation.method( + #goBack, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future goForward() => (super.noSuchMethod( + Invocation.method( + #goForward, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future reload() => (super.noSuchMethod( + Invocation.method( + #reload, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future clearCache() => (super.noSuchMethod( + Invocation.method( + #clearCache, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future evaluateJavascript(String? javascript) => + (super.noSuchMethod( + Invocation.method( + #evaluateJavascript, + [javascript], + ), + returnValue: _i9.Future.value(''), + ) as _i9.Future); + @override + _i9.Future runJavascript(String? javascript) => (super.noSuchMethod( + Invocation.method( + #runJavascript, + [javascript], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future runJavascriptReturningResult(String? javascript) => + (super.noSuchMethod( + Invocation.method( + #runJavascriptReturningResult, + [javascript], + ), + returnValue: _i9.Future.value(''), + ) as _i9.Future); + @override + _i9.Future addJavascriptChannels(Set? javascriptChannelNames) => + (super.noSuchMethod( + Invocation.method( + #addJavascriptChannels, + [javascriptChannelNames], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future removeJavascriptChannels( + Set? javascriptChannelNames) => + (super.noSuchMethod( + Invocation.method( + #removeJavascriptChannels, + [javascriptChannelNames], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future getTitle() => (super.noSuchMethod( + Invocation.method( + #getTitle, + [], + ), + returnValue: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future scrollTo( + int? x, + int? y, + ) => + (super.noSuchMethod( + Invocation.method( + #scrollTo, + [ + x, + y, + ], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future scrollBy( + int? x, + int? y, + ) => + (super.noSuchMethod( + Invocation.method( + #scrollBy, + [ + x, + y, + ], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future getScrollX() => (super.noSuchMethod( + Invocation.method( + #getScrollX, + [], + ), + returnValue: _i9.Future.value(0), + ) as _i9.Future); + @override + _i9.Future getScrollY() => (super.noSuchMethod( + Invocation.method( + #getScrollY, + [], + ), + returnValue: _i9.Future.value(0), + ) as _i9.Future); +} diff --git a/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.dart new file mode 100644 index 000000000000..839454eaa605 --- /dev/null +++ b/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.dart @@ -0,0 +1,91 @@ +// 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:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'navigation_delegate_test.mocks.dart'; + +@GenerateMocks([WebViewPlatform, PlatformNavigationDelegate]) +void main() { + group('NavigationDelegate', () { + test('onNavigationRequest', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + NavigationDecision onNavigationRequest(NavigationRequest request) { + return NavigationDecision.navigate; + } + + final NavigationDelegate delegate = NavigationDelegate( + onNavigationRequest: onNavigationRequest, + ); + + verify(delegate.platform.setOnNavigationRequest(onNavigationRequest)); + }); + + test('onPageStarted', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onPageStarted(String url) {} + + final NavigationDelegate delegate = NavigationDelegate( + onPageStarted: onPageStarted, + ); + + verify(delegate.platform.setOnPageStarted(onPageStarted)); + }); + + test('onPageFinished', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onPageFinished(String url) {} + + final NavigationDelegate delegate = NavigationDelegate( + onPageFinished: onPageFinished, + ); + + verify(delegate.platform.setOnPageFinished(onPageFinished)); + }); + + test('onProgress', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onProgress(int progress) {} + + final NavigationDelegate delegate = NavigationDelegate( + onProgress: onProgress, + ); + + verify(delegate.platform.setOnProgress(onProgress)); + }); + + test('onWebResourceError', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onWebResourceError(WebResourceError error) {} + + final NavigationDelegate delegate = NavigationDelegate( + onWebResourceError: onWebResourceError, + ); + + verify(delegate.platform.setOnWebResourceError(onWebResourceError)); + }); + }); +} + +class TestWebViewPlatform extends WebViewPlatform { + @override + PlatformNavigationDelegate createPlatformNavigationDelegate( + PlatformNavigationDelegateCreationParams params, + ) { + return TestMockPlatformNavigationDelegate(); + } +} + +class TestMockPlatformNavigationDelegate extends MockPlatformNavigationDelegate + with MockPlatformInterfaceMixin {} diff --git a/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.mocks.dart b/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.mocks.dart new file mode 100644 index 000000000000..a7ac41e558c3 --- /dev/null +++ b/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.mocks.dart @@ -0,0 +1,231 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in webview_flutter/test/navigation_delegate_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i8; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_platform_interface/src/platform_navigation_delegate.dart' + as _i3; +import 'package:webview_flutter_platform_interface/src/platform_webview_controller.dart' + as _i4; +import 'package:webview_flutter_platform_interface/src/platform_webview_cookie_manager.dart' + as _i2; +import 'package:webview_flutter_platform_interface/src/platform_webview_widget.dart' + as _i5; +import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i6; +import 'package:webview_flutter_platform_interface/src/webview_platform.dart' + as _i7; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePlatformWebViewCookieManager_0 extends _i1.SmartFake + implements _i2.PlatformWebViewCookieManager { + _FakePlatformWebViewCookieManager_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformNavigationDelegate_1 extends _i1.SmartFake + implements _i3.PlatformNavigationDelegate { + _FakePlatformNavigationDelegate_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformWebViewController_2 extends _i1.SmartFake + implements _i4.PlatformWebViewController { + _FakePlatformWebViewController_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformWebViewWidget_3 extends _i1.SmartFake + implements _i5.PlatformWebViewWidget { + _FakePlatformWebViewWidget_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformNavigationDelegateCreationParams_4 extends _i1.SmartFake + implements _i6.PlatformNavigationDelegateCreationParams { + _FakePlatformNavigationDelegateCreationParams_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [WebViewPlatform]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewPlatform extends _i1.Mock implements _i7.WebViewPlatform { + MockWebViewPlatform() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PlatformWebViewCookieManager createPlatformCookieManager( + _i6.PlatformWebViewCookieManagerCreationParams? params) => + (super.noSuchMethod( + Invocation.method( + #createPlatformCookieManager, + [params], + ), + returnValue: _FakePlatformWebViewCookieManager_0( + this, + Invocation.method( + #createPlatformCookieManager, + [params], + ), + ), + ) as _i2.PlatformWebViewCookieManager); + @override + _i3.PlatformNavigationDelegate createPlatformNavigationDelegate( + _i6.PlatformNavigationDelegateCreationParams? params) => + (super.noSuchMethod( + Invocation.method( + #createPlatformNavigationDelegate, + [params], + ), + returnValue: _FakePlatformNavigationDelegate_1( + this, + Invocation.method( + #createPlatformNavigationDelegate, + [params], + ), + ), + ) as _i3.PlatformNavigationDelegate); + @override + _i4.PlatformWebViewController createPlatformWebViewController( + _i6.PlatformWebViewControllerCreationParams? params) => + (super.noSuchMethod( + Invocation.method( + #createPlatformWebViewController, + [params], + ), + returnValue: _FakePlatformWebViewController_2( + this, + Invocation.method( + #createPlatformWebViewController, + [params], + ), + ), + ) as _i4.PlatformWebViewController); + @override + _i5.PlatformWebViewWidget createPlatformWebViewWidget( + _i6.PlatformWebViewWidgetCreationParams? params) => + (super.noSuchMethod( + Invocation.method( + #createPlatformWebViewWidget, + [params], + ), + returnValue: _FakePlatformWebViewWidget_3( + this, + Invocation.method( + #createPlatformWebViewWidget, + [params], + ), + ), + ) as _i5.PlatformWebViewWidget); +} + +/// A class which mocks [PlatformNavigationDelegate]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlatformNavigationDelegate extends _i1.Mock + implements _i3.PlatformNavigationDelegate { + MockPlatformNavigationDelegate() { + _i1.throwOnMissingStub(this); + } + + @override + _i6.PlatformNavigationDelegateCreationParams get params => + (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformNavigationDelegateCreationParams_4( + this, + Invocation.getter(#params), + ), + ) as _i6.PlatformNavigationDelegateCreationParams); + @override + _i8.Future setOnNavigationRequest( + _i3.NavigationRequestCallback? onNavigationRequest) => + (super.noSuchMethod( + Invocation.method( + #setOnNavigationRequest, + [onNavigationRequest], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future setOnPageStarted(_i3.PageEventCallback? onPageStarted) => + (super.noSuchMethod( + Invocation.method( + #setOnPageStarted, + [onPageStarted], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future setOnPageFinished(_i3.PageEventCallback? onPageFinished) => + (super.noSuchMethod( + Invocation.method( + #setOnPageFinished, + [onPageFinished], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future setOnProgress(_i3.ProgressCallback? onProgress) => + (super.noSuchMethod( + Invocation.method( + #setOnProgress, + [onProgress], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + _i8.Future setOnWebResourceError( + _i3.WebResourceErrorCallback? onWebResourceError) => + (super.noSuchMethod( + Invocation.method( + #setOnWebResourceError, + [onWebResourceError], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); +} diff --git a/packages/webview_flutter/webview_flutter/test/v4/webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter/test/v4/webview_controller_test.mocks.dart deleted file mode 100644 index f0fb4b47fc6e..000000000000 --- a/packages/webview_flutter/webview_flutter/test/v4/webview_controller_test.mocks.dart +++ /dev/null @@ -1,203 +0,0 @@ -// Mocks generated by Mockito 5.3.0 from annotations -// in webview_flutter/test/v4/webview_controller_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i5; -import 'dart:math' as _i3; -import 'dart:ui' as _i7; - -import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_platform_interface/v4/src/platform_navigation_delegate.dart' - as _i6; -import 'package:webview_flutter_platform_interface/v4/src/platform_webview_controller.dart' - as _i4; -import 'package:webview_flutter_platform_interface/v4/src/webview_platform.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 -// ignore_for_file: subtype_of_sealed_class - -class _FakePlatformWebViewControllerCreationParams_0 extends _i1.SmartFake - implements _i2.PlatformWebViewControllerCreationParams { - _FakePlatformWebViewControllerCreationParams_0( - Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); -} - -class _FakePoint_1 extends _i1.SmartFake - implements _i3.Point { - _FakePoint_1(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); -} - -/// A class which mocks [PlatformWebViewController]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPlatformWebViewController extends _i1.Mock - implements _i4.PlatformWebViewController { - MockPlatformWebViewController() { - _i1.throwOnMissingStub(this); - } - - @override - _i2.PlatformWebViewControllerCreationParams get params => - (super.noSuchMethod(Invocation.getter(#params), - returnValue: _FakePlatformWebViewControllerCreationParams_0( - this, Invocation.getter(#params))) - as _i2.PlatformWebViewControllerCreationParams); - @override - _i5.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( - Invocation.method(#loadFile, [absoluteFilePath]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future loadFlutterAsset(String? key) => (super.noSuchMethod( - Invocation.method(#loadFlutterAsset, [key]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future loadHtmlString(String? html, {String? baseUrl}) => - (super.noSuchMethod( - Invocation.method(#loadHtmlString, [html], {#baseUrl: baseUrl}), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) - as _i5.Future); - @override - _i5.Future loadRequest(_i2.LoadRequestParams? params) => - (super.noSuchMethod(Invocation.method(#loadRequest, [params]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) - as _i5.Future); - @override - _i5.Future currentUrl() => - (super.noSuchMethod(Invocation.method(#currentUrl, []), - returnValue: _i5.Future.value()) as _i5.Future); - @override - _i5.Future canGoBack() => - (super.noSuchMethod(Invocation.method(#canGoBack, []), - returnValue: _i5.Future.value(false)) as _i5.Future); - @override - _i5.Future canGoForward() => - (super.noSuchMethod(Invocation.method(#canGoForward, []), - returnValue: _i5.Future.value(false)) as _i5.Future); - @override - _i5.Future goBack() => (super.noSuchMethod( - Invocation.method(#goBack, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future goForward() => (super.noSuchMethod( - Invocation.method(#goForward, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future reload() => (super.noSuchMethod( - Invocation.method(#reload, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future clearCache() => (super.noSuchMethod( - Invocation.method(#clearCache, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future clearLocalStorage() => (super.noSuchMethod( - Invocation.method(#clearLocalStorage, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future setPlatformNavigationDelegate( - _i6.PlatformNavigationDelegate? handler) => - (super.noSuchMethod( - Invocation.method(#setPlatformNavigationDelegate, [handler]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) - as _i5.Future); - @override - _i5.Future runJavaScript(String? javaScript) => (super.noSuchMethod( - Invocation.method(#runJavaScript, [javaScript]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future runJavaScriptReturningResult(String? javaScript) => - (super.noSuchMethod( - Invocation.method(#runJavaScriptReturningResult, [javaScript]), - returnValue: _i5.Future.value('')) as _i5.Future); - @override - _i5.Future addJavaScriptChannel( - _i4.JavaScriptChannelParams? javaScriptChannelParams) => - (super.noSuchMethod( - Invocation.method(#addJavaScriptChannel, [javaScriptChannelParams]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: - _i5.Future.value()) as _i5.Future); - @override - _i5.Future removeJavaScriptChannel(String? javaScriptChannelName) => - (super.noSuchMethod( - Invocation.method(#removeJavaScriptChannel, [javaScriptChannelName]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: - _i5.Future.value()) as _i5.Future); - @override - _i5.Future getTitle() => - (super.noSuchMethod(Invocation.method(#getTitle, []), - returnValue: _i5.Future.value()) as _i5.Future); - @override - _i5.Future scrollTo(int? x, int? y) => (super.noSuchMethod( - Invocation.method(#scrollTo, [x, y]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future scrollBy(int? x, int? y) => (super.noSuchMethod( - Invocation.method(#scrollBy, [x, y]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future<_i3.Point> getScrollPosition() => - (super.noSuchMethod(Invocation.method(#getScrollPosition, []), - returnValue: _i5.Future<_i3.Point>.value(_FakePoint_1( - this, Invocation.method(#getScrollPosition, [])))) - as _i5.Future<_i3.Point>); - @override - _i5.Future enableDebugging(bool? enabled) => (super.noSuchMethod( - Invocation.method(#enableDebugging, [enabled]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future enableGestureNavigation(bool? enabled) => (super - .noSuchMethod(Invocation.method(#enableGestureNavigation, [enabled]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) - as _i5.Future); - @override - _i5.Future enableZoom(bool? enabled) => (super.noSuchMethod( - Invocation.method(#enableZoom, [enabled]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future setBackgroundColor(_i7.Color? color) => (super.noSuchMethod( - Invocation.method(#setBackgroundColor, [color]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); - @override - _i5.Future setJavaScriptMode(_i2.JavaScriptMode? javaScriptMode) => - (super.noSuchMethod( - Invocation.method(#setJavaScriptMode, [javaScriptMode]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) - as _i5.Future); - @override - _i5.Future setUserAgent(String? userAgent) => (super.noSuchMethod( - Invocation.method(#setUserAgent, [userAgent]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value()) as _i5.Future); -} diff --git a/packages/webview_flutter/webview_flutter/test/v4/webview_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter/test/v4/webview_widget_test.mocks.dart deleted file mode 100644 index e481d752be5d..000000000000 --- a/packages/webview_flutter/webview_flutter/test/v4/webview_widget_test.mocks.dart +++ /dev/null @@ -1,246 +0,0 @@ -// Mocks generated by Mockito 5.3.0 from annotations -// in webview_flutter/test/v4/webview_widget_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:math' as _i3; -import 'dart:ui' as _i9; - -import 'package:flutter/foundation.dart' as _i5; -import 'package:flutter/widgets.dart' as _i4; -import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_platform_interface/v4/src/platform_navigation_delegate.dart' - as _i8; -import 'package:webview_flutter_platform_interface/v4/src/platform_webview_controller.dart' - as _i6; -import 'package:webview_flutter_platform_interface/v4/src/platform_webview_widget.dart' - as _i10; -import 'package:webview_flutter_platform_interface/v4/src/webview_platform.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 -// ignore_for_file: subtype_of_sealed_class - -class _FakePlatformWebViewControllerCreationParams_0 extends _i1.SmartFake - implements _i2.PlatformWebViewControllerCreationParams { - _FakePlatformWebViewControllerCreationParams_0( - Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); -} - -class _FakePoint_1 extends _i1.SmartFake - implements _i3.Point { - _FakePoint_1(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); -} - -class _FakePlatformWebViewWidgetCreationParams_2 extends _i1.SmartFake - implements _i2.PlatformWebViewWidgetCreationParams { - _FakePlatformWebViewWidgetCreationParams_2( - Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); -} - -class _FakeWidget_3 extends _i1.SmartFake implements _i4.Widget { - _FakeWidget_3(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); - - @override - String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => - super.toString(); -} - -/// A class which mocks [PlatformWebViewController]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPlatformWebViewController extends _i1.Mock - implements _i6.PlatformWebViewController { - MockPlatformWebViewController() { - _i1.throwOnMissingStub(this); - } - - @override - _i2.PlatformWebViewControllerCreationParams get params => - (super.noSuchMethod(Invocation.getter(#params), - returnValue: _FakePlatformWebViewControllerCreationParams_0( - this, Invocation.getter(#params))) - as _i2.PlatformWebViewControllerCreationParams); - @override - _i7.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( - Invocation.method(#loadFile, [absoluteFilePath]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future loadFlutterAsset(String? key) => (super.noSuchMethod( - Invocation.method(#loadFlutterAsset, [key]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future loadHtmlString(String? html, {String? baseUrl}) => - (super.noSuchMethod( - Invocation.method(#loadHtmlString, [html], {#baseUrl: baseUrl}), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) - as _i7.Future); - @override - _i7.Future loadRequest(_i2.LoadRequestParams? params) => - (super.noSuchMethod(Invocation.method(#loadRequest, [params]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) - as _i7.Future); - @override - _i7.Future currentUrl() => - (super.noSuchMethod(Invocation.method(#currentUrl, []), - returnValue: _i7.Future.value()) as _i7.Future); - @override - _i7.Future canGoBack() => - (super.noSuchMethod(Invocation.method(#canGoBack, []), - returnValue: _i7.Future.value(false)) as _i7.Future); - @override - _i7.Future canGoForward() => - (super.noSuchMethod(Invocation.method(#canGoForward, []), - returnValue: _i7.Future.value(false)) as _i7.Future); - @override - _i7.Future goBack() => (super.noSuchMethod( - Invocation.method(#goBack, []), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future goForward() => (super.noSuchMethod( - Invocation.method(#goForward, []), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future reload() => (super.noSuchMethod( - Invocation.method(#reload, []), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future clearCache() => (super.noSuchMethod( - Invocation.method(#clearCache, []), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future clearLocalStorage() => (super.noSuchMethod( - Invocation.method(#clearLocalStorage, []), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future setPlatformNavigationDelegate( - _i8.PlatformNavigationDelegate? handler) => - (super.noSuchMethod( - Invocation.method(#setPlatformNavigationDelegate, [handler]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) - as _i7.Future); - @override - _i7.Future runJavaScript(String? javaScript) => (super.noSuchMethod( - Invocation.method(#runJavaScript, [javaScript]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future runJavaScriptReturningResult(String? javaScript) => - (super.noSuchMethod( - Invocation.method(#runJavaScriptReturningResult, [javaScript]), - returnValue: _i7.Future.value('')) as _i7.Future); - @override - _i7.Future addJavaScriptChannel( - _i6.JavaScriptChannelParams? javaScriptChannelParams) => - (super.noSuchMethod( - Invocation.method(#addJavaScriptChannel, [javaScriptChannelParams]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: - _i7.Future.value()) as _i7.Future); - @override - _i7.Future removeJavaScriptChannel(String? javaScriptChannelName) => - (super.noSuchMethod( - Invocation.method(#removeJavaScriptChannel, [javaScriptChannelName]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: - _i7.Future.value()) as _i7.Future); - @override - _i7.Future getTitle() => - (super.noSuchMethod(Invocation.method(#getTitle, []), - returnValue: _i7.Future.value()) as _i7.Future); - @override - _i7.Future scrollTo(int? x, int? y) => (super.noSuchMethod( - Invocation.method(#scrollTo, [x, y]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future scrollBy(int? x, int? y) => (super.noSuchMethod( - Invocation.method(#scrollBy, [x, y]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future<_i3.Point> getScrollPosition() => - (super.noSuchMethod(Invocation.method(#getScrollPosition, []), - returnValue: _i7.Future<_i3.Point>.value(_FakePoint_1( - this, Invocation.method(#getScrollPosition, [])))) - as _i7.Future<_i3.Point>); - @override - _i7.Future enableDebugging(bool? enabled) => (super.noSuchMethod( - Invocation.method(#enableDebugging, [enabled]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future enableGestureNavigation(bool? enabled) => (super - .noSuchMethod(Invocation.method(#enableGestureNavigation, [enabled]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) - as _i7.Future); - @override - _i7.Future enableZoom(bool? enabled) => (super.noSuchMethod( - Invocation.method(#enableZoom, [enabled]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future setBackgroundColor(_i9.Color? color) => (super.noSuchMethod( - Invocation.method(#setBackgroundColor, [color]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); - @override - _i7.Future setJavaScriptMode(_i2.JavaScriptMode? javaScriptMode) => - (super.noSuchMethod( - Invocation.method(#setJavaScriptMode, [javaScriptMode]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) - as _i7.Future); - @override - _i7.Future setUserAgent(String? userAgent) => (super.noSuchMethod( - Invocation.method(#setUserAgent, [userAgent]), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value()) as _i7.Future); -} - -/// A class which mocks [PlatformWebViewWidget]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPlatformWebViewWidget extends _i1.Mock - implements _i10.PlatformWebViewWidget { - MockPlatformWebViewWidget() { - _i1.throwOnMissingStub(this); - } - - @override - _i2.PlatformWebViewWidgetCreationParams get params => - (super.noSuchMethod(Invocation.getter(#params), - returnValue: _FakePlatformWebViewWidgetCreationParams_2( - this, Invocation.getter(#params))) - as _i2.PlatformWebViewWidgetCreationParams); - @override - _i4.Widget build(_i4.BuildContext? context) => - (super.noSuchMethod(Invocation.method(#build, [context]), - returnValue: - _FakeWidget_3(this, Invocation.method(#build, [context]))) - as _i4.Widget); -} diff --git a/packages/webview_flutter/webview_flutter/test/v4/webview_controller_test.dart b/packages/webview_flutter/webview_flutter/test/webview_controller_test.dart similarity index 91% rename from packages/webview_flutter/webview_flutter/test/v4/webview_controller_test.dart rename to packages/webview_flutter/webview_flutter/test/webview_controller_test.dart index f767a2e48d5e..f11884bb2acf 100644 --- a/packages/webview_flutter/webview_flutter/test/v4/webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter/test/webview_controller_test.dart @@ -2,19 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:webview_flutter/src/v4/src/webview_controller.dart'; -import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webview_controller_test.mocks.dart'; -@GenerateMocks([PlatformWebViewController]) +@GenerateMocks([PlatformWebViewController, PlatformNavigationDelegate]) void main() { test('loadFile', () async { final MockPlatformWebViewController mockPlatformWebViewController = @@ -286,9 +285,7 @@ void main() { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); when(mockPlatformWebViewController.getScrollPosition()).thenAnswer( - (_) => Future>.value( - const Point(2, 3), - ), + (_) => Future.value(const Offset(2, 3)), ); final WebViewController webViewController = WebViewController.fromPlatform( @@ -350,4 +347,22 @@ void main() { await webViewController.setUserAgent('userAgent'); verify(mockPlatformWebViewController.setUserAgent('userAgent')); }); + + test('setNavigationDelegate', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + final MockPlatformNavigationDelegate mockPlatformNavigationDelegate = + MockPlatformNavigationDelegate(); + final NavigationDelegate navigationDelegate = + NavigationDelegate.fromPlatform(mockPlatformNavigationDelegate); + + await webViewController.setNavigationDelegate(navigationDelegate); + verify(mockPlatformWebViewController.setPlatformNavigationDelegate( + mockPlatformNavigationDelegate, + )); + }); } diff --git a/packages/webview_flutter/webview_flutter/test/webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter/test/webview_controller_test.mocks.dart new file mode 100644 index 000000000000..2bb1ef691321 --- /dev/null +++ b/packages/webview_flutter/webview_flutter/test/webview_controller_test.mocks.dart @@ -0,0 +1,417 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in webview_flutter/test/webview_controller_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; +import 'dart:ui' as _i3; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_platform_interface/src/platform_navigation_delegate.dart' + as _i6; +import 'package:webview_flutter_platform_interface/src/platform_webview_controller.dart' + as _i4; +import 'package:webview_flutter_platform_interface/src/webview_platform.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 +// ignore_for_file: subtype_of_sealed_class + +class _FakePlatformWebViewControllerCreationParams_0 extends _i1.SmartFake + implements _i2.PlatformWebViewControllerCreationParams { + _FakePlatformWebViewControllerCreationParams_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeObject_1 extends _i1.SmartFake implements Object { + _FakeObject_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeOffset_2 extends _i1.SmartFake implements _i3.Offset { + _FakeOffset_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformNavigationDelegateCreationParams_3 extends _i1.SmartFake + implements _i2.PlatformNavigationDelegateCreationParams { + _FakePlatformNavigationDelegateCreationParams_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [PlatformWebViewController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlatformWebViewController extends _i1.Mock + implements _i4.PlatformWebViewController { + MockPlatformWebViewController() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PlatformWebViewControllerCreationParams get params => (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformWebViewControllerCreationParams_0( + this, + Invocation.getter(#params), + ), + ) as _i2.PlatformWebViewControllerCreationParams); + @override + _i5.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( + Invocation.method( + #loadFile, + [absoluteFilePath], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future loadFlutterAsset(String? key) => (super.noSuchMethod( + Invocation.method( + #loadFlutterAsset, + [key], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future loadHtmlString( + String? html, { + String? baseUrl, + }) => + (super.noSuchMethod( + Invocation.method( + #loadHtmlString, + [html], + {#baseUrl: baseUrl}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future loadRequest(_i2.LoadRequestParams? params) => + (super.noSuchMethod( + Invocation.method( + #loadRequest, + [params], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future currentUrl() => (super.noSuchMethod( + Invocation.method( + #currentUrl, + [], + ), + returnValue: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future canGoBack() => (super.noSuchMethod( + Invocation.method( + #canGoBack, + [], + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + _i5.Future canGoForward() => (super.noSuchMethod( + Invocation.method( + #canGoForward, + [], + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + _i5.Future goBack() => (super.noSuchMethod( + Invocation.method( + #goBack, + [], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future goForward() => (super.noSuchMethod( + Invocation.method( + #goForward, + [], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future reload() => (super.noSuchMethod( + Invocation.method( + #reload, + [], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future clearCache() => (super.noSuchMethod( + Invocation.method( + #clearCache, + [], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future clearLocalStorage() => (super.noSuchMethod( + Invocation.method( + #clearLocalStorage, + [], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setPlatformNavigationDelegate( + _i6.PlatformNavigationDelegate? handler) => + (super.noSuchMethod( + Invocation.method( + #setPlatformNavigationDelegate, + [handler], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future runJavaScript(String? javaScript) => (super.noSuchMethod( + Invocation.method( + #runJavaScript, + [javaScript], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future runJavaScriptReturningResult(String? javaScript) => + (super.noSuchMethod( + Invocation.method( + #runJavaScriptReturningResult, + [javaScript], + ), + returnValue: _i5.Future.value(_FakeObject_1( + this, + Invocation.method( + #runJavaScriptReturningResult, + [javaScript], + ), + )), + ) as _i5.Future); + @override + _i5.Future addJavaScriptChannel( + _i4.JavaScriptChannelParams? javaScriptChannelParams) => + (super.noSuchMethod( + Invocation.method( + #addJavaScriptChannel, + [javaScriptChannelParams], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future removeJavaScriptChannel(String? javaScriptChannelName) => + (super.noSuchMethod( + Invocation.method( + #removeJavaScriptChannel, + [javaScriptChannelName], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future getTitle() => (super.noSuchMethod( + Invocation.method( + #getTitle, + [], + ), + returnValue: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future scrollTo( + int? x, + int? y, + ) => + (super.noSuchMethod( + Invocation.method( + #scrollTo, + [ + x, + y, + ], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future scrollBy( + int? x, + int? y, + ) => + (super.noSuchMethod( + Invocation.method( + #scrollBy, + [ + x, + y, + ], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future<_i3.Offset> getScrollPosition() => (super.noSuchMethod( + Invocation.method( + #getScrollPosition, + [], + ), + returnValue: _i5.Future<_i3.Offset>.value(_FakeOffset_2( + this, + Invocation.method( + #getScrollPosition, + [], + ), + )), + ) as _i5.Future<_i3.Offset>); + @override + _i5.Future enableZoom(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #enableZoom, + [enabled], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setBackgroundColor(_i3.Color? color) => (super.noSuchMethod( + Invocation.method( + #setBackgroundColor, + [color], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setJavaScriptMode(_i2.JavaScriptMode? javaScriptMode) => + (super.noSuchMethod( + Invocation.method( + #setJavaScriptMode, + [javaScriptMode], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setUserAgent(String? userAgent) => (super.noSuchMethod( + Invocation.method( + #setUserAgent, + [userAgent], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); +} + +/// A class which mocks [PlatformNavigationDelegate]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlatformNavigationDelegate extends _i1.Mock + implements _i6.PlatformNavigationDelegate { + MockPlatformNavigationDelegate() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PlatformNavigationDelegateCreationParams get params => + (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformNavigationDelegateCreationParams_3( + this, + Invocation.getter(#params), + ), + ) as _i2.PlatformNavigationDelegateCreationParams); + @override + _i5.Future setOnNavigationRequest( + _i6.NavigationRequestCallback? onNavigationRequest) => + (super.noSuchMethod( + Invocation.method( + #setOnNavigationRequest, + [onNavigationRequest], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setOnPageStarted(_i6.PageEventCallback? onPageStarted) => + (super.noSuchMethod( + Invocation.method( + #setOnPageStarted, + [onPageStarted], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setOnPageFinished(_i6.PageEventCallback? onPageFinished) => + (super.noSuchMethod( + Invocation.method( + #setOnPageFinished, + [onPageFinished], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setOnProgress(_i6.ProgressCallback? onProgress) => + (super.noSuchMethod( + Invocation.method( + #setOnProgress, + [onProgress], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setOnWebResourceError( + _i6.WebResourceErrorCallback? onWebResourceError) => + (super.noSuchMethod( + Invocation.method( + #setOnWebResourceError, + [onWebResourceError], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); +} diff --git a/packages/webview_flutter/webview_flutter/test/v4/webview_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter/test/webview_cookie_manager_test.dart similarity index 90% rename from packages/webview_flutter/webview_flutter/test/v4/webview_cookie_manager_test.dart rename to packages/webview_flutter/webview_flutter/test/webview_cookie_manager_test.dart index e8152407fb92..babf74b18922 100644 --- a/packages/webview_flutter/webview_flutter/test/v4/webview_cookie_manager_test.dart +++ b/packages/webview_flutter/webview_flutter/test/webview_cookie_manager_test.dart @@ -5,8 +5,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:webview_flutter/src/v4/src/webview_cookie_manager.dart'; -import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webview_cookie_manager_test.mocks.dart'; diff --git a/packages/webview_flutter/webview_flutter/test/v4/webview_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter/test/webview_cookie_manager_test.mocks.dart similarity index 55% rename from packages/webview_flutter/webview_flutter/test/v4/webview_cookie_manager_test.mocks.dart rename to packages/webview_flutter/webview_flutter/test/webview_cookie_manager_test.mocks.dart index 4bca8b6a1f12..7cae6632d157 100644 --- a/packages/webview_flutter/webview_flutter/test/v4/webview_cookie_manager_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter/test/webview_cookie_manager_test.mocks.dart @@ -1,14 +1,14 @@ -// Mocks generated by Mockito 5.3.0 from annotations -// in webview_flutter/test/v4/webview_cookie_manager_test.dart. +// Mocks generated by Mockito 5.3.2 from annotations +// in webview_flutter/test/webview_cookie_manager_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_platform_interface/v4/src/platform_webview_cookie_manager.dart' +import 'package:webview_flutter_platform_interface/src/platform_webview_cookie_manager.dart' as _i3; -import 'package:webview_flutter_platform_interface/v4/src/webview_platform.dart' +import 'package:webview_flutter_platform_interface/src/webview_platform.dart' as _i2; // ignore_for_file: type=lint @@ -25,8 +25,12 @@ import 'package:webview_flutter_platform_interface/v4/src/webview_platform.dart' class _FakePlatformWebViewCookieManagerCreationParams_0 extends _i1.SmartFake implements _i2.PlatformWebViewCookieManagerCreationParams { _FakePlatformWebViewCookieManagerCreationParams_0( - Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); } /// A class which mocks [PlatformWebViewCookieManager]. @@ -40,17 +44,28 @@ class MockPlatformWebViewCookieManager extends _i1.Mock @override _i2.PlatformWebViewCookieManagerCreationParams get params => - (super.noSuchMethod(Invocation.getter(#params), - returnValue: _FakePlatformWebViewCookieManagerCreationParams_0( - this, Invocation.getter(#params))) - as _i2.PlatformWebViewCookieManagerCreationParams); + (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformWebViewCookieManagerCreationParams_0( + this, + Invocation.getter(#params), + ), + ) as _i2.PlatformWebViewCookieManagerCreationParams); @override - _i4.Future clearCookies() => - (super.noSuchMethod(Invocation.method(#clearCookies, []), - returnValue: _i4.Future.value(false)) as _i4.Future); + _i4.Future clearCookies() => (super.noSuchMethod( + Invocation.method( + #clearCookies, + [], + ), + returnValue: _i4.Future.value(false), + ) as _i4.Future); @override _i4.Future setCookie(_i2.WebViewCookie? cookie) => (super.noSuchMethod( - Invocation.method(#setCookie, [cookie]), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value()) as _i4.Future); + Invocation.method( + #setCookie, + [cookie], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); } diff --git a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart index b10366c82024..d13b51c57955 100644 --- a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart @@ -2,1366 +2,43 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:typed_data'; - -import 'package:flutter/src/foundation/basic_types.dart'; -import 'package:flutter/src/gestures/recognizer.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:webview_flutter/webview_flutter.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; - -import 'webview_flutter_test.mocks.dart'; - -typedef VoidCallback = void Function(); +import 'package:webview_flutter/webview_flutter.dart' as main_file; -@GenerateMocks([WebViewPlatform, WebViewPlatformController]) void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - late MockWebViewPlatform mockWebViewPlatform; - late MockWebViewPlatformController mockWebViewPlatformController; - late MockWebViewCookieManagerPlatform mockWebViewCookieManagerPlatform; - - setUp(() { - mockWebViewPlatformController = MockWebViewPlatformController(); - mockWebViewPlatform = MockWebViewPlatform(); - mockWebViewCookieManagerPlatform = MockWebViewCookieManagerPlatform(); - when(mockWebViewPlatform.build( - context: anyNamed('context'), - creationParams: anyNamed('creationParams'), - webViewPlatformCallbacksHandler: - anyNamed('webViewPlatformCallbacksHandler'), - javascriptChannelRegistry: anyNamed('javascriptChannelRegistry'), - onWebViewPlatformCreated: anyNamed('onWebViewPlatformCreated'), - gestureRecognizers: anyNamed('gestureRecognizers'), - )).thenAnswer((Invocation invocation) { - final WebViewPlatformCreatedCallback onWebViewPlatformCreated = - invocation.namedArguments[const Symbol('onWebViewPlatformCreated')] - as WebViewPlatformCreatedCallback; - return TestPlatformWebView( - mockWebViewPlatformController: mockWebViewPlatformController, - onWebViewPlatformCreated: onWebViewPlatformCreated, - ); + group('webview_flutter', () { + test('ensure webview_flutter.dart exports classes from platform interface', + () { + // ignore: unnecessary_statements + main_file.JavaScriptMessage; + // ignore: unnecessary_statements + main_file.JavaScriptMode; + // ignore: unnecessary_statements + main_file.LoadRequestMethod; + // ignore: unnecessary_statements + main_file.NavigationDecision; + // ignore: unnecessary_statements + main_file.NavigationRequest; + // ignore: unnecessary_statements + main_file.NavigationRequestCallback; + // ignore: unnecessary_statements + main_file.PageEventCallback; + // ignore: unnecessary_statements + main_file.PlatformNavigationDelegateCreationParams; + // ignore: unnecessary_statements + main_file.PlatformWebViewControllerCreationParams; + // ignore: unnecessary_statements + main_file.PlatformWebViewCookieManagerCreationParams; + // ignore: unnecessary_statements + main_file.PlatformWebViewWidgetCreationParams; + // ignore: unnecessary_statements + main_file.ProgressCallback; + // ignore: unnecessary_statements + main_file.WebResourceError; + // ignore: unnecessary_statements + main_file.WebResourceErrorCallback; + // ignore: unnecessary_statements + main_file.WebViewCookie; }); - - WebView.platform = mockWebViewPlatform; - WebViewCookieManagerPlatform.instance = mockWebViewCookieManagerPlatform; - }); - - tearDown(() { - mockWebViewCookieManagerPlatform.reset(); - }); - - testWidgets('Create WebView', (WidgetTester tester) async { - await tester.pumpWidget(const WebView()); - }); - - testWidgets('Initial url', (WidgetTester tester) async { - await tester.pumpWidget(const WebView(initialUrl: 'https://youtube.com')); - - final CreationParams params = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect(params.initialUrl, 'https://youtube.com'); - }); - - testWidgets('Javascript mode', (WidgetTester tester) async { - await tester.pumpWidget(const WebView( - javascriptMode: JavascriptMode.unrestricted, - )); - - final CreationParams unrestrictedparams = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect( - unrestrictedparams.webSettings!.javascriptMode, - JavascriptMode.unrestricted, - ); - - await tester.pumpWidget(const WebView()); - - final CreationParams disabledparams = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect(disabledparams.webSettings!.javascriptMode, JavascriptMode.disabled); - }); - - testWidgets('Load file', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - - await controller!.loadFile('/test/path/index.html'); - - verify(mockWebViewPlatformController.loadFile( - '/test/path/index.html', - )); - }); - - testWidgets('Load file with empty path', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - - expect(() => controller!.loadFile(''), throwsAssertionError); - }); - - testWidgets('Load Flutter asset', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - - await controller!.loadFlutterAsset('assets/index.html'); - - verify(mockWebViewPlatformController.loadFlutterAsset( - 'assets/index.html', - )); - }); - - testWidgets('Load Flutter asset with empty key', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - - expect(() => controller!.loadFlutterAsset(''), throwsAssertionError); }); - - testWidgets('Load HTML string without base URL', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - - await controller!.loadHtmlString('

This is a test paragraph.

'); - - verify(mockWebViewPlatformController.loadHtmlString( - '

This is a test paragraph.

', - )); - }); - - testWidgets('Load HTML string with base URL', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - - await controller!.loadHtmlString( - '

This is a test paragraph.

', - baseUrl: 'https://flutter.dev', - ); - - verify(mockWebViewPlatformController.loadHtmlString( - '

This is a test paragraph.

', - baseUrl: 'https://flutter.dev', - )); - }); - - testWidgets('Load HTML string with empty string', - (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - - expect(() => controller!.loadHtmlString(''), throwsAssertionError); - }); - - testWidgets('Load url', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - - await controller!.loadUrl('https://flutter.io'); - - verify(mockWebViewPlatformController.loadUrl( - 'https://flutter.io', - argThat(isNull), - )); - }); - - testWidgets('Invalid urls', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - - final CreationParams params = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect(params.initialUrl, isNull); - - expect(() => controller!.loadUrl(''), throwsA(anything)); - expect(() => controller!.loadUrl('flutter.io'), throwsA(anything)); - }); - - testWidgets('Headers in loadUrl', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - - final Map headers = { - 'CACHE-CONTROL': 'ABC' - }; - await controller!.loadUrl('https://flutter.io', headers: headers); - - verify(mockWebViewPlatformController.loadUrl( - 'https://flutter.io', - {'CACHE-CONTROL': 'ABC'}, - )); - }); - - testWidgets('loadRequest', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - expect(controller, isNotNull); - - final WebViewRequest req = WebViewRequest( - uri: Uri.parse('https://flutter.dev'), - method: WebViewRequestMethod.post, - headers: {'foo': 'bar'}, - body: Uint8List.fromList('Test Body'.codeUnits), - ); - - await controller!.loadRequest(req); - - verify(mockWebViewPlatformController.loadRequest(req)); - }); - - testWidgets('Clear Cache', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - - await controller!.clearCache(); - - verify(mockWebViewPlatformController.clearCache()); - }); - - testWidgets('Can go back', (WidgetTester tester) async { - when(mockWebViewPlatformController.canGoBack()) - .thenAnswer((_) => Future.value(true)); - - WebViewController? controller; - await tester.pumpWidget( - WebView( - initialUrl: 'https://flutter.io', - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - expect(controller!.canGoBack(), completion(true)); - }); - - testWidgets("Can't go forward", (WidgetTester tester) async { - when(mockWebViewPlatformController.canGoForward()) - .thenAnswer((_) => Future.value(false)); - - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - expect(controller!.canGoForward(), completion(false)); - }); - - testWidgets('Go back', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - initialUrl: 'https://youtube.com', - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - await controller!.goBack(); - verify(mockWebViewPlatformController.goBack()); - }); - - testWidgets('Go forward', (WidgetTester tester) async { - WebViewController? controller; - await tester.pumpWidget( - WebView( - initialUrl: 'https://youtube.com', - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - await controller!.goForward(); - verify(mockWebViewPlatformController.goForward()); - }); - - testWidgets('Current URL', (WidgetTester tester) async { - when(mockWebViewPlatformController.currentUrl()) - .thenAnswer((_) => Future.value('https://youtube.com')); - - WebViewController? controller; - await tester.pumpWidget( - WebView( - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect(controller, isNotNull); - expect(await controller!.currentUrl(), 'https://youtube.com'); - }); - - testWidgets('Reload url', (WidgetTester tester) async { - late WebViewController controller; - await tester.pumpWidget( - WebView( - initialUrl: 'https://flutter.io', - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - await controller.reload(); - verify(mockWebViewPlatformController.reload()); - }); - - testWidgets('evaluate Javascript', (WidgetTester tester) async { - when(mockWebViewPlatformController.evaluateJavascript('fake js string')) - .thenAnswer((_) => Future.value('fake js string')); - - late WebViewController controller; - await tester.pumpWidget( - WebView( - initialUrl: 'https://flutter.io', - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - expect( - // ignore: deprecated_member_use_from_same_package - await controller.evaluateJavascript('fake js string'), - 'fake js string', - reason: 'should get the argument'); - }); - - testWidgets('evaluate Javascript with JavascriptMode disabled', - (WidgetTester tester) async { - late WebViewController controller; - await tester.pumpWidget( - WebView( - initialUrl: 'https://flutter.io', - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - expect( - // ignore: deprecated_member_use_from_same_package - () => controller.evaluateJavascript('fake js string'), - throwsA(anything), - ); - }); - - testWidgets('runJavaScript', (WidgetTester tester) async { - late WebViewController controller; - await tester.pumpWidget( - WebView( - initialUrl: 'https://flutter.io', - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - await controller.runJavascript('fake js string'); - verify(mockWebViewPlatformController.runJavascript('fake js string')); - }); - - testWidgets('runJavaScript with JavascriptMode disabled', - (WidgetTester tester) async { - late WebViewController controller; - await tester.pumpWidget( - WebView( - initialUrl: 'https://flutter.io', - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - expect( - () => controller.runJavascript('fake js string'), - throwsA(anything), - ); - }); - - testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { - when(mockWebViewPlatformController - .runJavascriptReturningResult('fake js string')) - .thenAnswer((_) => Future.value('fake js string')); - - late WebViewController controller; - await tester.pumpWidget( - WebView( - initialUrl: 'https://flutter.io', - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - expect(await controller.runJavascriptReturningResult('fake js string'), - 'fake js string', - reason: 'should get the argument'); - }); - - testWidgets('runJavaScriptReturningResult with JavascriptMode disabled', - (WidgetTester tester) async { - late WebViewController controller; - await tester.pumpWidget( - WebView( - initialUrl: 'https://flutter.io', - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - expect( - () => controller.runJavascriptReturningResult('fake js string'), - throwsA(anything), - ); - }); - - testWidgets('Cookies can be cleared once', (WidgetTester tester) async { - await tester.pumpWidget( - const WebView( - initialUrl: 'https://flutter.io', - ), - ); - final CookieManager cookieManager = CookieManager(); - final bool hasCookies = await cookieManager.clearCookies(); - expect(hasCookies, true); - }); - - testWidgets('Cookies can be set', (WidgetTester tester) async { - const WebViewCookie cookie = - WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev'); - - await tester.pumpWidget( - const WebView( - initialUrl: 'https://flutter.io', - ), - ); - final CookieManager cookieManager = CookieManager(); - await cookieManager.setCookie(cookie); - expect(mockWebViewCookieManagerPlatform.setCookieCalls, - [cookie]); - }); - - testWidgets('Initial JavaScript channels', (WidgetTester tester) async { - await tester.pumpWidget( - WebView( - initialUrl: 'https://youtube.com', - javascriptChannels: { - JavascriptChannel( - name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), - JavascriptChannel( - name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), - }, - ), - ); - - final CreationParams params = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect(params.javascriptChannelNames, - unorderedEquals(['Tts', 'Alarm'])); - }); - - test('Only valid JavaScript channel names are allowed', () { - void noOp(JavascriptMessage msg) {} - JavascriptChannel(name: 'Tts1', onMessageReceived: noOp); - JavascriptChannel(name: '_Alarm', onMessageReceived: noOp); - JavascriptChannel(name: 'foo_bar_', onMessageReceived: noOp); - - VoidCallback createChannel(String name) { - return () { - JavascriptChannel(name: name, onMessageReceived: noOp); - }; - } - - expect(createChannel('1Alarm'), throwsAssertionError); - expect(createChannel('foo.bar'), throwsAssertionError); - expect(createChannel(''), throwsAssertionError); - }); - - testWidgets('Unique JavaScript channel names are required', - (WidgetTester tester) async { - await tester.pumpWidget( - WebView( - initialUrl: 'https://youtube.com', - javascriptChannels: { - JavascriptChannel( - name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), - JavascriptChannel( - name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), - }, - ), - ); - expect(tester.takeException(), isNot(null)); - }); - - testWidgets('JavaScript channels update', (WidgetTester tester) async { - await tester.pumpWidget( - WebView( - initialUrl: 'https://youtube.com', - javascriptChannels: { - JavascriptChannel( - name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), - JavascriptChannel( - name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), - }, - ), - ); - - await tester.pumpWidget( - WebView( - initialUrl: 'https://youtube.com', - javascriptChannels: { - JavascriptChannel( - name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), - JavascriptChannel( - name: 'Alarm2', onMessageReceived: (JavascriptMessage msg) {}), - JavascriptChannel( - name: 'Alarm3', onMessageReceived: (JavascriptMessage msg) {}), - }, - ), - ); - - final JavascriptChannelRegistry channelRegistry = captureBuildArgs( - mockWebViewPlatform, - javascriptChannelRegistry: true, - ).first as JavascriptChannelRegistry; - - expect( - channelRegistry.channels.keys, - unorderedEquals(['Tts', 'Alarm2', 'Alarm3']), - ); - }); - - testWidgets('Remove all JavaScript channels and then add', - (WidgetTester tester) async { - // This covers a specific bug we had where after updating javascriptChannels to null, - // updating it again with a subset of the previously registered channels fails as the - // widget's cache of current channel wasn't properly updated when updating javascriptChannels to - // null. - await tester.pumpWidget( - WebView( - initialUrl: 'https://youtube.com', - javascriptChannels: { - JavascriptChannel( - name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), - }, - ), - ); - - await tester.pumpWidget( - const WebView( - initialUrl: 'https://youtube.com', - ), - ); - - await tester.pumpWidget( - WebView( - initialUrl: 'https://youtube.com', - javascriptChannels: { - JavascriptChannel( - name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), - }, - ), - ); - - final JavascriptChannelRegistry channelRegistry = captureBuildArgs( - mockWebViewPlatform, - javascriptChannelRegistry: true, - ).last as JavascriptChannelRegistry; - - expect(channelRegistry.channels.keys, unorderedEquals(['Tts'])); - }); - - testWidgets('JavaScript channel messages', (WidgetTester tester) async { - final List ttsMessagesReceived = []; - final List alarmMessagesReceived = []; - await tester.pumpWidget( - WebView( - initialUrl: 'https://youtube.com', - javascriptChannels: { - JavascriptChannel( - name: 'Tts', - onMessageReceived: (JavascriptMessage msg) { - ttsMessagesReceived.add(msg.message); - }), - JavascriptChannel( - name: 'Alarm', - onMessageReceived: (JavascriptMessage msg) { - alarmMessagesReceived.add(msg.message); - }), - }, - ), - ); - - final JavascriptChannelRegistry channelRegistry = captureBuildArgs( - mockWebViewPlatform, - javascriptChannelRegistry: true, - ).single as JavascriptChannelRegistry; - - expect(ttsMessagesReceived, isEmpty); - expect(alarmMessagesReceived, isEmpty); - - channelRegistry.onJavascriptChannelMessage('Tts', 'Hello'); - channelRegistry.onJavascriptChannelMessage('Tts', 'World'); - - expect(ttsMessagesReceived, ['Hello', 'World']); - }); - - group('$PageStartedCallback', () { - testWidgets('onPageStarted is not null', (WidgetTester tester) async { - String? returnedUrl; - - await tester.pumpWidget(WebView( - initialUrl: 'https://youtube.com', - onPageStarted: (String url) { - returnedUrl = url; - }, - )); - - final WebViewPlatformCallbacksHandler handler = captureBuildArgs( - mockWebViewPlatform, - webViewPlatformCallbacksHandler: true, - ).single as WebViewPlatformCallbacksHandler; - - handler.onPageStarted('https://youtube.com'); - - expect(returnedUrl, 'https://youtube.com'); - }); - - testWidgets('onPageStarted is null', (WidgetTester tester) async { - await tester.pumpWidget(const WebView( - initialUrl: 'https://youtube.com', - )); - - final WebViewPlatformCallbacksHandler handler = captureBuildArgs( - mockWebViewPlatform, - webViewPlatformCallbacksHandler: true, - ).single as WebViewPlatformCallbacksHandler; - - // The platform side will always invoke a call for onPageStarted. This is - // to test that it does not crash on a null callback. - handler.onPageStarted('https://youtube.com'); - }); - - testWidgets('onPageStarted changed', (WidgetTester tester) async { - String? returnedUrl; - - await tester.pumpWidget(WebView( - initialUrl: 'https://youtube.com', - onPageStarted: (String url) {}, - )); - - await tester.pumpWidget(WebView( - initialUrl: 'https://youtube.com', - onPageStarted: (String url) { - returnedUrl = url; - }, - )); - - final WebViewPlatformCallbacksHandler handler = captureBuildArgs( - mockWebViewPlatform, - webViewPlatformCallbacksHandler: true, - ).last as WebViewPlatformCallbacksHandler; - handler.onPageStarted('https://youtube.com'); - - expect(returnedUrl, 'https://youtube.com'); - }); - }); - - group('$PageFinishedCallback', () { - testWidgets('onPageFinished is not null', (WidgetTester tester) async { - String? returnedUrl; - - await tester.pumpWidget(WebView( - initialUrl: 'https://youtube.com', - onPageFinished: (String url) { - returnedUrl = url; - }, - )); - - final WebViewPlatformCallbacksHandler handler = captureBuildArgs( - mockWebViewPlatform, - webViewPlatformCallbacksHandler: true, - ).single as WebViewPlatformCallbacksHandler; - handler.onPageFinished('https://youtube.com'); - - expect(returnedUrl, 'https://youtube.com'); - }); - - testWidgets('onPageFinished is null', (WidgetTester tester) async { - await tester.pumpWidget(const WebView( - initialUrl: 'https://youtube.com', - )); - - final WebViewPlatformCallbacksHandler handler = captureBuildArgs( - mockWebViewPlatform, - webViewPlatformCallbacksHandler: true, - ).single as WebViewPlatformCallbacksHandler; - // The platform side will always invoke a call for onPageFinished. This is - // to test that it does not crash on a null callback. - handler.onPageFinished('https://youtube.com'); - }); - - testWidgets('onPageFinished changed', (WidgetTester tester) async { - String? returnedUrl; - - await tester.pumpWidget(WebView( - initialUrl: 'https://youtube.com', - onPageFinished: (String url) {}, - )); - - await tester.pumpWidget(WebView( - initialUrl: 'https://youtube.com', - onPageFinished: (String url) { - returnedUrl = url; - }, - )); - - final WebViewPlatformCallbacksHandler handler = captureBuildArgs( - mockWebViewPlatform, - webViewPlatformCallbacksHandler: true, - ).last as WebViewPlatformCallbacksHandler; - handler.onPageFinished('https://youtube.com'); - - expect(returnedUrl, 'https://youtube.com'); - }); - }); - - group('$PageLoadingCallback', () { - testWidgets('onLoadingProgress is not null', (WidgetTester tester) async { - int? loadingProgress; - - await tester.pumpWidget(WebView( - initialUrl: 'https://youtube.com', - onProgress: (int progress) { - loadingProgress = progress; - }, - )); - - final WebViewPlatformCallbacksHandler handler = captureBuildArgs( - mockWebViewPlatform, - webViewPlatformCallbacksHandler: true, - ).single as WebViewPlatformCallbacksHandler; - handler.onProgress(50); - - expect(loadingProgress, 50); - }); - - testWidgets('onLoadingProgress is null', (WidgetTester tester) async { - await tester.pumpWidget(const WebView( - initialUrl: 'https://youtube.com', - )); - - final WebViewPlatformCallbacksHandler handler = captureBuildArgs( - mockWebViewPlatform, - webViewPlatformCallbacksHandler: true, - ).single as WebViewPlatformCallbacksHandler; - - // This is to test that it does not crash on a null callback. - handler.onProgress(50); - }); - - testWidgets('onLoadingProgress changed', (WidgetTester tester) async { - int? loadingProgress; - - await tester.pumpWidget(WebView( - initialUrl: 'https://youtube.com', - onProgress: (int progress) {}, - )); - - await tester.pumpWidget(WebView( - initialUrl: 'https://youtube.com', - onProgress: (int progress) { - loadingProgress = progress; - }, - )); - - final WebViewPlatformCallbacksHandler handler = captureBuildArgs( - mockWebViewPlatform, - webViewPlatformCallbacksHandler: true, - ).last as WebViewPlatformCallbacksHandler; - handler.onProgress(50); - - expect(loadingProgress, 50); - }); - }); - - group('navigationDelegate', () { - testWidgets('hasNavigationDelegate', (WidgetTester tester) async { - await tester.pumpWidget(const WebView( - initialUrl: 'https://youtube.com', - )); - - final CreationParams params = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect(params.webSettings!.hasNavigationDelegate, false); - - await tester.pumpWidget(WebView( - initialUrl: 'https://youtube.com', - navigationDelegate: (NavigationRequest r) => - NavigationDecision.navigate, - )); - - final WebSettings updateSettings = - verify(mockWebViewPlatformController.updateSettings(captureAny)) - .captured - .single as WebSettings; - - expect(updateSettings.hasNavigationDelegate, true); - }); - - testWidgets('Block navigation', (WidgetTester tester) async { - final List navigationRequests = []; - - await tester.pumpWidget(WebView( - initialUrl: 'https://youtube.com', - navigationDelegate: (NavigationRequest request) { - navigationRequests.add(request); - // Only allow navigating to https://flutter.dev - return request.url == 'https://flutter.dev' - ? NavigationDecision.navigate - : NavigationDecision.prevent; - })); - - final List args = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - webViewPlatformCallbacksHandler: true, - ); - - final CreationParams params = args[0] as CreationParams; - expect(params.webSettings!.hasNavigationDelegate, true); - - final WebViewPlatformCallbacksHandler handler = - args[1] as WebViewPlatformCallbacksHandler; - - // The navigation delegate only allows navigation to https://flutter.dev - // so we should still be in https://youtube.com. - expect( - handler.onNavigationRequest( - url: 'https://www.google.com', - isForMainFrame: true, - ), - completion(false), - ); - - expect(navigationRequests.length, 1); - expect(navigationRequests[0].url, 'https://www.google.com'); - expect(navigationRequests[0].isForMainFrame, true); - - expect( - handler.onNavigationRequest( - url: 'https://flutter.dev', - isForMainFrame: true, - ), - completion(true), - ); - }); - }); - - group('debuggingEnabled', () { - testWidgets('enable debugging', (WidgetTester tester) async { - await tester.pumpWidget(const WebView( - debuggingEnabled: true, - )); - - final CreationParams params = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect(params.webSettings!.debuggingEnabled, true); - }); - - testWidgets('defaults to false', (WidgetTester tester) async { - await tester.pumpWidget(const WebView()); - - final CreationParams params = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect(params.webSettings!.debuggingEnabled, false); - }); - - testWidgets('can be changed', (WidgetTester tester) async { - final GlobalKey key = GlobalKey(); - await tester.pumpWidget(WebView(key: key)); - - await tester.pumpWidget(WebView( - key: key, - debuggingEnabled: true, - )); - - final WebSettings enabledSettings = - verify(mockWebViewPlatformController.updateSettings(captureAny)) - .captured - .last as WebSettings; - expect(enabledSettings.debuggingEnabled, true); - - await tester.pumpWidget(WebView( - key: key, - )); - - final WebSettings disabledSettings = - verify(mockWebViewPlatformController.updateSettings(captureAny)) - .captured - .last as WebSettings; - expect(disabledSettings.debuggingEnabled, false); - }); - }); - - group('zoomEnabled', () { - testWidgets('Enable zoom', (WidgetTester tester) async { - await tester.pumpWidget(const WebView()); - - final CreationParams params = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect(params.webSettings!.zoomEnabled, isTrue); - }); - - testWidgets('defaults to true', (WidgetTester tester) async { - await tester.pumpWidget(const WebView()); - - final CreationParams params = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect(params.webSettings!.zoomEnabled, isTrue); - }); - - testWidgets('can be changed', (WidgetTester tester) async { - final GlobalKey key = GlobalKey(); - await tester.pumpWidget(WebView(key: key)); - - await tester.pumpWidget(WebView( - key: key, - )); - - final WebSettings enabledSettings = - verify(mockWebViewPlatformController.updateSettings(captureAny)) - .captured - .last as WebSettings; - // Zoom defaults to true, so no changes are made to settings. - expect(enabledSettings.zoomEnabled, isNull); - - await tester.pumpWidget(WebView( - key: key, - zoomEnabled: false, - )); - - final WebSettings disabledSettings = - verify(mockWebViewPlatformController.updateSettings(captureAny)) - .captured - .last as WebSettings; - expect(disabledSettings.zoomEnabled, isFalse); - }); - }); - - group('Background color', () { - testWidgets('Defaults to null', (WidgetTester tester) async { - await tester.pumpWidget(const WebView()); - - final CreationParams params = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect(params.backgroundColor, null); - }); - - testWidgets('Can be transparent', (WidgetTester tester) async { - const Color transparentColor = Color(0x00000000); - - await tester.pumpWidget(const WebView( - backgroundColor: transparentColor, - )); - - final CreationParams params = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect(params.backgroundColor, transparentColor); - }); - }); - - group('Custom platform implementation', () { - setUp(() { - WebView.platform = MyWebViewPlatform(); - }); - tearDownAll(() { - WebView.platform = null; - }); - - testWidgets('creation', (WidgetTester tester) async { - await tester.pumpWidget( - const WebView( - initialUrl: 'https://youtube.com', - gestureNavigationEnabled: true, - ), - ); - - final MyWebViewPlatform builder = WebView.platform as MyWebViewPlatform; - final MyWebViewPlatformController platform = builder.lastPlatformBuilt!; - - expect( - platform.creationParams, - MatchesCreationParams(CreationParams( - initialUrl: 'https://youtube.com', - webSettings: WebSettings( - javascriptMode: JavascriptMode.disabled, - hasNavigationDelegate: false, - debuggingEnabled: false, - userAgent: const WebSetting.of(null), - gestureNavigationEnabled: true, - zoomEnabled: true, - ), - ))); - }); - - testWidgets('loadUrl', (WidgetTester tester) async { - late WebViewController controller; - await tester.pumpWidget( - WebView( - initialUrl: 'https://youtube.com', - onWebViewCreated: (WebViewController webViewController) { - controller = webViewController; - }, - ), - ); - - final MyWebViewPlatform builder = WebView.platform as MyWebViewPlatform; - final MyWebViewPlatformController platform = builder.lastPlatformBuilt!; - - final Map headers = { - 'header': 'value', - }; - - await controller.loadUrl('https://google.com', headers: headers); - - expect(platform.lastUrlLoaded, 'https://google.com'); - expect(platform.lastRequestHeaders, headers); - }); - }); - - testWidgets('Set UserAgent', (WidgetTester tester) async { - await tester.pumpWidget(const WebView( - initialUrl: 'https://youtube.com', - javascriptMode: JavascriptMode.unrestricted, - )); - - final CreationParams params = captureBuildArgs( - mockWebViewPlatform, - creationParams: true, - ).single as CreationParams; - - expect(params.webSettings!.userAgent.value, isNull); - - await tester.pumpWidget(const WebView( - initialUrl: 'https://youtube.com', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'UA', - )); - - final WebSettings settings = - verify(mockWebViewPlatformController.updateSettings(captureAny)) - .captured - .last as WebSettings; - expect(settings.userAgent.value, 'UA'); - }); -} - -List captureBuildArgs( - MockWebViewPlatform mockWebViewPlatform, { - bool context = false, - bool creationParams = false, - bool webViewPlatformCallbacksHandler = false, - bool javascriptChannelRegistry = false, - bool onWebViewPlatformCreated = false, - bool gestureRecognizers = false, -}) { - return verify(mockWebViewPlatform.build( - context: context ? captureAnyNamed('context') : anyNamed('context'), - creationParams: creationParams - ? captureAnyNamed('creationParams') - : anyNamed('creationParams'), - webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler - ? captureAnyNamed('webViewPlatformCallbacksHandler') - : anyNamed('webViewPlatformCallbacksHandler'), - javascriptChannelRegistry: javascriptChannelRegistry - ? captureAnyNamed('javascriptChannelRegistry') - : anyNamed('javascriptChannelRegistry'), - onWebViewPlatformCreated: onWebViewPlatformCreated - ? captureAnyNamed('onWebViewPlatformCreated') - : anyNamed('onWebViewPlatformCreated'), - gestureRecognizers: gestureRecognizers - ? captureAnyNamed('gestureRecognizers') - : anyNamed('gestureRecognizers'), - )).captured; -} - -// This Widget ensures that onWebViewPlatformCreated is only called once when -// making multiple calls to `WidgetTester.pumpWidget` with different parameters -// for the WebView. -class TestPlatformWebView extends StatefulWidget { - const TestPlatformWebView({ - Key? key, - required this.mockWebViewPlatformController, - this.onWebViewPlatformCreated, - }) : super(key: key); - - final MockWebViewPlatformController mockWebViewPlatformController; - final WebViewPlatformCreatedCallback? onWebViewPlatformCreated; - - @override - State createState() => TestPlatformWebViewState(); -} - -class TestPlatformWebViewState extends State { - @override - void initState() { - super.initState(); - final WebViewPlatformCreatedCallback? onWebViewPlatformCreated = - widget.onWebViewPlatformCreated; - if (onWebViewPlatformCreated != null) { - onWebViewPlatformCreated(widget.mockWebViewPlatformController); - } - } - - @override - Widget build(BuildContext context) { - return Container(); - } -} - -class MyWebViewPlatform implements WebViewPlatform { - MyWebViewPlatformController? lastPlatformBuilt; - - @override - Widget build({ - BuildContext? context, - CreationParams? creationParams, - required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, - required JavascriptChannelRegistry javascriptChannelRegistry, - WebViewPlatformCreatedCallback? onWebViewPlatformCreated, - Set>? gestureRecognizers, - }) { - assert(onWebViewPlatformCreated != null); - lastPlatformBuilt = MyWebViewPlatformController( - creationParams, gestureRecognizers, webViewPlatformCallbacksHandler); - onWebViewPlatformCreated!(lastPlatformBuilt); - return Container(); - } - - @override - Future clearCookies() { - return Future.sync(() => true); - } -} - -class MyWebViewPlatformController extends WebViewPlatformController { - MyWebViewPlatformController(this.creationParams, this.gestureRecognizers, - WebViewPlatformCallbacksHandler platformHandler) - : super(platformHandler); - - CreationParams? creationParams; - Set>? gestureRecognizers; - - String? lastUrlLoaded; - Map? lastRequestHeaders; - - @override - Future loadUrl(String url, Map? headers) async { - equals(1, 1); - lastUrlLoaded = url; - lastRequestHeaders = headers; - } -} - -class MatchesWebSettings extends Matcher { - MatchesWebSettings(this._webSettings); - - final WebSettings? _webSettings; - - @override - Description describe(Description description) => - description.add('$_webSettings'); - - @override - bool matches( - covariant WebSettings webSettings, Map matchState) { - return _webSettings!.javascriptMode == webSettings.javascriptMode && - _webSettings!.hasNavigationDelegate == - webSettings.hasNavigationDelegate && - _webSettings!.debuggingEnabled == webSettings.debuggingEnabled && - _webSettings!.gestureNavigationEnabled == - webSettings.gestureNavigationEnabled && - _webSettings!.userAgent == webSettings.userAgent && - _webSettings!.zoomEnabled == webSettings.zoomEnabled; - } -} - -class MatchesCreationParams extends Matcher { - MatchesCreationParams(this._creationParams); - - final CreationParams _creationParams; - - @override - Description describe(Description description) => - description.add('$_creationParams'); - - @override - bool matches(covariant CreationParams creationParams, - Map matchState) { - return _creationParams.initialUrl == creationParams.initialUrl && - MatchesWebSettings(_creationParams.webSettings) - .matches(creationParams.webSettings!, matchState) && - orderedEquals(_creationParams.javascriptChannelNames) - .matches(creationParams.javascriptChannelNames, matchState); - } -} - -class MockWebViewCookieManagerPlatform extends WebViewCookieManagerPlatform { - List setCookieCalls = []; - - @override - Future clearCookies() async => true; - - @override - Future setCookie(WebViewCookie cookie) async { - setCookieCalls.add(cookie); - } - - void reset() { - setCookieCalls = []; - } } diff --git a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.mocks.dart b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.mocks.dart deleted file mode 100644 index a7a21007d6be..000000000000 --- a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.mocks.dart +++ /dev/null @@ -1,213 +0,0 @@ -// Mocks generated by Mockito 5.3.0 from annotations -// in webview_flutter/test/webview_flutter_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i9; - -import 'package:flutter/foundation.dart' as _i3; -import 'package:flutter/gestures.dart' as _i8; -import 'package:flutter/widgets.dart' as _i2; -import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_platform_interface/src/platform_interface/javascript_channel_registry.dart' - as _i7; -import 'package:webview_flutter_platform_interface/src/platform_interface/webview_platform.dart' - as _i4; -import 'package:webview_flutter_platform_interface/src/platform_interface/webview_platform_callbacks_handler.dart' - as _i6; -import 'package:webview_flutter_platform_interface/src/platform_interface/webview_platform_controller.dart' - as _i10; -import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i5; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -class _FakeWidget_0 extends _i1.SmartFake implements _i2.Widget { - _FakeWidget_0(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); - - @override - String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => - super.toString(); -} - -/// A class which mocks [WebViewPlatform]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWebViewPlatform extends _i1.Mock implements _i4.WebViewPlatform { - MockWebViewPlatform() { - _i1.throwOnMissingStub(this); - } - - @override - _i2.Widget build( - {_i2.BuildContext? context, - _i5.CreationParams? creationParams, - _i6.WebViewPlatformCallbacksHandler? webViewPlatformCallbacksHandler, - _i7.JavascriptChannelRegistry? javascriptChannelRegistry, - _i4.WebViewPlatformCreatedCallback? onWebViewPlatformCreated, - Set<_i3.Factory<_i8.OneSequenceGestureRecognizer>>? - gestureRecognizers}) => - (super.noSuchMethod( - Invocation.method(#build, [], { - #context: context, - #creationParams: creationParams, - #webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, - #javascriptChannelRegistry: javascriptChannelRegistry, - #onWebViewPlatformCreated: onWebViewPlatformCreated, - #gestureRecognizers: gestureRecognizers - }), - returnValue: _FakeWidget_0( - this, - Invocation.method(#build, [], { - #context: context, - #creationParams: creationParams, - #webViewPlatformCallbacksHandler: - webViewPlatformCallbacksHandler, - #javascriptChannelRegistry: javascriptChannelRegistry, - #onWebViewPlatformCreated: onWebViewPlatformCreated, - #gestureRecognizers: gestureRecognizers - }))) as _i2.Widget); - @override - _i9.Future clearCookies() => - (super.noSuchMethod(Invocation.method(#clearCookies, []), - returnValue: _i9.Future.value(false)) as _i9.Future); -} - -/// A class which mocks [WebViewPlatformController]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWebViewPlatformController extends _i1.Mock - implements _i10.WebViewPlatformController { - MockWebViewPlatformController() { - _i1.throwOnMissingStub(this); - } - - @override - _i9.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( - Invocation.method(#loadFile, [absoluteFilePath]), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) as _i9.Future); - @override - _i9.Future loadFlutterAsset(String? key) => (super.noSuchMethod( - Invocation.method(#loadFlutterAsset, [key]), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) as _i9.Future); - @override - _i9.Future loadHtmlString(String? html, {String? baseUrl}) => - (super.noSuchMethod( - Invocation.method(#loadHtmlString, [html], {#baseUrl: baseUrl}), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) - as _i9.Future); - @override - _i9.Future loadUrl(String? url, Map? headers) => - (super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) - as _i9.Future); - @override - _i9.Future loadRequest(_i5.WebViewRequest? request) => - (super.noSuchMethod(Invocation.method(#loadRequest, [request]), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) - as _i9.Future); - @override - _i9.Future updateSettings(_i5.WebSettings? setting) => - (super.noSuchMethod(Invocation.method(#updateSettings, [setting]), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) - as _i9.Future); - @override - _i9.Future currentUrl() => - (super.noSuchMethod(Invocation.method(#currentUrl, []), - returnValue: _i9.Future.value()) as _i9.Future); - @override - _i9.Future canGoBack() => - (super.noSuchMethod(Invocation.method(#canGoBack, []), - returnValue: _i9.Future.value(false)) as _i9.Future); - @override - _i9.Future canGoForward() => - (super.noSuchMethod(Invocation.method(#canGoForward, []), - returnValue: _i9.Future.value(false)) as _i9.Future); - @override - _i9.Future goBack() => (super.noSuchMethod( - Invocation.method(#goBack, []), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) as _i9.Future); - @override - _i9.Future goForward() => (super.noSuchMethod( - Invocation.method(#goForward, []), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) as _i9.Future); - @override - _i9.Future reload() => (super.noSuchMethod( - Invocation.method(#reload, []), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) as _i9.Future); - @override - _i9.Future clearCache() => (super.noSuchMethod( - Invocation.method(#clearCache, []), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) as _i9.Future); - @override - _i9.Future evaluateJavascript(String? javascript) => - (super.noSuchMethod(Invocation.method(#evaluateJavascript, [javascript]), - returnValue: _i9.Future.value('')) as _i9.Future); - @override - _i9.Future runJavascript(String? javascript) => (super.noSuchMethod( - Invocation.method(#runJavascript, [javascript]), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) as _i9.Future); - @override - _i9.Future runJavascriptReturningResult(String? javascript) => - (super.noSuchMethod( - Invocation.method(#runJavascriptReturningResult, [javascript]), - returnValue: _i9.Future.value('')) as _i9.Future); - @override - _i9.Future addJavascriptChannels(Set? javascriptChannelNames) => - (super.noSuchMethod( - Invocation.method(#addJavascriptChannels, [javascriptChannelNames]), - returnValue: _i9.Future.value(), - returnValueForMissingStub: - _i9.Future.value()) as _i9.Future); - @override - _i9.Future removeJavascriptChannels( - Set? javascriptChannelNames) => - (super.noSuchMethod( - Invocation.method( - #removeJavascriptChannels, [javascriptChannelNames]), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) - as _i9.Future); - @override - _i9.Future getTitle() => - (super.noSuchMethod(Invocation.method(#getTitle, []), - returnValue: _i9.Future.value()) as _i9.Future); - @override - _i9.Future scrollTo(int? x, int? y) => (super.noSuchMethod( - Invocation.method(#scrollTo, [x, y]), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) as _i9.Future); - @override - _i9.Future scrollBy(int? x, int? y) => (super.noSuchMethod( - Invocation.method(#scrollBy, [x, y]), - returnValue: _i9.Future.value(), - returnValueForMissingStub: _i9.Future.value()) as _i9.Future); - @override - _i9.Future getScrollX() => - (super.noSuchMethod(Invocation.method(#getScrollX, []), - returnValue: _i9.Future.value(0)) as _i9.Future); - @override - _i9.Future getScrollY() => - (super.noSuchMethod(Invocation.method(#getScrollY, []), - returnValue: _i9.Future.value(0)) as _i9.Future); -} diff --git a/packages/webview_flutter/webview_flutter/test/v4/webview_widget_test.dart b/packages/webview_flutter/webview_flutter/test/webview_widget_test.dart similarity index 90% rename from packages/webview_flutter/webview_flutter/test/v4/webview_widget_test.dart rename to packages/webview_flutter/webview_flutter/test/webview_widget_test.dart index 455d8b371ec7..21e9f53a2260 100644 --- a/packages/webview_flutter/webview_flutter/test/v4/webview_widget_test.dart +++ b/packages/webview_flutter/webview_flutter/test/webview_widget_test.dart @@ -8,8 +8,8 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:webview_flutter/src/v4/webview_flutter.dart'; -import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webview_widget_test.mocks.dart'; @@ -77,8 +77,7 @@ class TestWebViewPlatform extends WebViewPlatform { } class TestPlatformWebViewWidget extends PlatformWebViewWidget { - TestPlatformWebViewWidget(PlatformWebViewWidgetCreationParams params) - : super.implementation(params); + TestPlatformWebViewWidget(super.params) : super.implementation(); @override Widget build(BuildContext context) { diff --git a/packages/webview_flutter/webview_flutter/test/webview_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter/test/webview_widget_test.mocks.dart new file mode 100644 index 000000000000..0e29ede0d561 --- /dev/null +++ b/packages/webview_flutter/webview_flutter/test/webview_widget_test.mocks.dart @@ -0,0 +1,396 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in webview_flutter/test/webview_widget_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i7; +import 'dart:ui' as _i3; + +import 'package:flutter/foundation.dart' as _i5; +import 'package:flutter/widgets.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_platform_interface/src/platform_navigation_delegate.dart' + as _i8; +import 'package:webview_flutter_platform_interface/src/platform_webview_controller.dart' + as _i6; +import 'package:webview_flutter_platform_interface/src/platform_webview_widget.dart' + as _i9; +import 'package:webview_flutter_platform_interface/src/webview_platform.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 +// ignore_for_file: subtype_of_sealed_class + +class _FakePlatformWebViewControllerCreationParams_0 extends _i1.SmartFake + implements _i2.PlatformWebViewControllerCreationParams { + _FakePlatformWebViewControllerCreationParams_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeObject_1 extends _i1.SmartFake implements Object { + _FakeObject_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeOffset_2 extends _i1.SmartFake implements _i3.Offset { + _FakeOffset_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformWebViewWidgetCreationParams_3 extends _i1.SmartFake + implements _i2.PlatformWebViewWidgetCreationParams { + _FakePlatformWebViewWidgetCreationParams_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWidget_4 extends _i1.SmartFake implements _i4.Widget { + _FakeWidget_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +/// A class which mocks [PlatformWebViewController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlatformWebViewController extends _i1.Mock + implements _i6.PlatformWebViewController { + MockPlatformWebViewController() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PlatformWebViewControllerCreationParams get params => (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformWebViewControllerCreationParams_0( + this, + Invocation.getter(#params), + ), + ) as _i2.PlatformWebViewControllerCreationParams); + @override + _i7.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( + Invocation.method( + #loadFile, + [absoluteFilePath], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future loadFlutterAsset(String? key) => (super.noSuchMethod( + Invocation.method( + #loadFlutterAsset, + [key], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future loadHtmlString( + String? html, { + String? baseUrl, + }) => + (super.noSuchMethod( + Invocation.method( + #loadHtmlString, + [html], + {#baseUrl: baseUrl}, + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future loadRequest(_i2.LoadRequestParams? params) => + (super.noSuchMethod( + Invocation.method( + #loadRequest, + [params], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future currentUrl() => (super.noSuchMethod( + Invocation.method( + #currentUrl, + [], + ), + returnValue: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future canGoBack() => (super.noSuchMethod( + Invocation.method( + #canGoBack, + [], + ), + returnValue: _i7.Future.value(false), + ) as _i7.Future); + @override + _i7.Future canGoForward() => (super.noSuchMethod( + Invocation.method( + #canGoForward, + [], + ), + returnValue: _i7.Future.value(false), + ) as _i7.Future); + @override + _i7.Future goBack() => (super.noSuchMethod( + Invocation.method( + #goBack, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future goForward() => (super.noSuchMethod( + Invocation.method( + #goForward, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future reload() => (super.noSuchMethod( + Invocation.method( + #reload, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future clearCache() => (super.noSuchMethod( + Invocation.method( + #clearCache, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future clearLocalStorage() => (super.noSuchMethod( + Invocation.method( + #clearLocalStorage, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setPlatformNavigationDelegate( + _i8.PlatformNavigationDelegate? handler) => + (super.noSuchMethod( + Invocation.method( + #setPlatformNavigationDelegate, + [handler], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future runJavaScript(String? javaScript) => (super.noSuchMethod( + Invocation.method( + #runJavaScript, + [javaScript], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future runJavaScriptReturningResult(String? javaScript) => + (super.noSuchMethod( + Invocation.method( + #runJavaScriptReturningResult, + [javaScript], + ), + returnValue: _i7.Future.value(_FakeObject_1( + this, + Invocation.method( + #runJavaScriptReturningResult, + [javaScript], + ), + )), + ) as _i7.Future); + @override + _i7.Future addJavaScriptChannel( + _i6.JavaScriptChannelParams? javaScriptChannelParams) => + (super.noSuchMethod( + Invocation.method( + #addJavaScriptChannel, + [javaScriptChannelParams], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future removeJavaScriptChannel(String? javaScriptChannelName) => + (super.noSuchMethod( + Invocation.method( + #removeJavaScriptChannel, + [javaScriptChannelName], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future getTitle() => (super.noSuchMethod( + Invocation.method( + #getTitle, + [], + ), + returnValue: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future scrollTo( + int? x, + int? y, + ) => + (super.noSuchMethod( + Invocation.method( + #scrollTo, + [ + x, + y, + ], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future scrollBy( + int? x, + int? y, + ) => + (super.noSuchMethod( + Invocation.method( + #scrollBy, + [ + x, + y, + ], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future<_i3.Offset> getScrollPosition() => (super.noSuchMethod( + Invocation.method( + #getScrollPosition, + [], + ), + returnValue: _i7.Future<_i3.Offset>.value(_FakeOffset_2( + this, + Invocation.method( + #getScrollPosition, + [], + ), + )), + ) as _i7.Future<_i3.Offset>); + @override + _i7.Future enableZoom(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #enableZoom, + [enabled], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setBackgroundColor(_i3.Color? color) => (super.noSuchMethod( + Invocation.method( + #setBackgroundColor, + [color], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setJavaScriptMode(_i2.JavaScriptMode? javaScriptMode) => + (super.noSuchMethod( + Invocation.method( + #setJavaScriptMode, + [javaScriptMode], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setUserAgent(String? userAgent) => (super.noSuchMethod( + Invocation.method( + #setUserAgent, + [userAgent], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); +} + +/// A class which mocks [PlatformWebViewWidget]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlatformWebViewWidget extends _i1.Mock + implements _i9.PlatformWebViewWidget { + MockPlatformWebViewWidget() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PlatformWebViewWidgetCreationParams get params => (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformWebViewWidgetCreationParams_3( + this, + Invocation.getter(#params), + ), + ) as _i2.PlatformWebViewWidgetCreationParams); + @override + _i4.Widget build(_i4.BuildContext? context) => (super.noSuchMethod( + Invocation.method( + #build, + [context], + ), + returnValue: _FakeWidget_4( + this, + Invocation.method( + #build, + [context], + ), + ), + ) as _i4.Widget); +} diff --git a/script/configs/exclude_all_plugins_app.yaml b/script/configs/exclude_all_plugins_app.yaml index a19f5fe0ca37..8dd0fde5ef5f 100644 --- a/script/configs/exclude_all_plugins_app.yaml +++ b/script/configs/exclude_all_plugins_app.yaml @@ -8,10 +8,3 @@ # This is a permament entry, as it should never be a direct app dependency. - plugin_platform_interface -# Packages below are temporarily added to push and release a new webview -# interface. Remove packages with release of `webview_flutter` 4.0.0. See -# https://github.com/flutter/flutter/issues/94051. -- webview_flutter_platform_interface -- webview_flutter_wkwebview -- webview_flutter_android -- webview_flutter_web diff --git a/script/configs/temp_exclude_excerpt.yaml b/script/configs/temp_exclude_excerpt.yaml index c59983efd058..40346297e999 100644 --- a/script/configs/temp_exclude_excerpt.yaml +++ b/script/configs/temp_exclude_excerpt.yaml @@ -18,6 +18,5 @@ - plugin_platform_interface - quick_actions/quick_actions - shared_preferences/shared_preferences -- webview_flutter/webview_flutter - webview_flutter_android - webview_flutter_web