From 9ab20a41930a165b175a1edda7726637d17d564d Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 15 Dec 2022 10:27:59 -0500 Subject: [PATCH 1/4] new android release --- .../DownloadListenerFlutterApiImpl.java | 14 - .../DownloadListenerHostApiImpl.java | 21 +- .../GeneratedAndroidWebView.java | 382 ++-- .../webviewflutter/JavaScriptChannel.java | 19 +- .../JavaScriptChannelFlutterApiImpl.java | 14 - .../plugins/webviewflutter/Releasable.java | 14 - .../WebChromeClientFlutterApiImpl.java | 14 - .../WebChromeClientHostApiImpl.java | 47 +- .../WebSettingsHostApiImpl.java | 5 - .../WebViewClientFlutterApiImpl.java | 14 - .../WebViewClientHostApiImpl.java | 135 +- .../webviewflutter/WebViewFlutterPlugin.java | 15 +- .../webviewflutter/WebViewHostApiImpl.java | 212 +-- .../webviewflutter/DownloadListenerTest.java | 9 - .../webviewflutter/JavaScriptChannelTest.java | 7 - .../webviewflutter/WebChromeClientTest.java | 15 +- .../webviewflutter/WebViewClientTest.java | 48 +- .../plugins/webviewflutter/WebViewTest.java | 119 +- .../legacy/webview_flutter_test.dart | 1567 +++++++++++++++ .../webview_flutter_test.dart | 1645 +++++++--------- .../lib/{ => legacy}/navigation_decision.dart | 0 .../lib/{ => legacy}/navigation_request.dart | 0 .../example/lib/{ => legacy}/web_view.dart | 9 +- .../example/lib/main.dart | 607 +++--- .../example/pubspec.yaml | 3 +- .../lib/src/android_navigation_delegate.dart | 318 ++++ .../lib/src/android_proxy.dart | 101 + .../lib/src/android_webview.dart | 210 +-- .../lib/src/android_webview.pigeon.dart | 267 +-- .../lib/src/android_webview_api_impls.dart | 145 +- .../lib/src/android_webview_controller.dart | 440 +++++ .../src/android_webview_cookie_manager.dart | 74 + .../lib/src/android_webview_platform.dart | 45 + .../lib/{ => src/legacy}/webview_android.dart | 5 +- .../webview_android_cookie_manager.dart | 5 +- .../legacy}/webview_android_widget.dart | 407 ++-- .../legacy}/webview_surface_android.dart | 5 +- .../lib/src/weak_reference_utils.dart | 34 + .../src/webview_flutter_android_legacy.dart | 7 + .../lib/webview_flutter_android.dart | 10 + .../pigeons/android_webview.dart | 21 +- .../webview_flutter_android/pubspec.yaml | 11 +- .../android_navigation_delegate_test.dart | 514 +++++ .../test/android_webview_controller_test.dart | 798 ++++++++ ...android_webview_controller_test.mocks.dart | 1679 +++++++++++++++++ .../android_webview_cookie_manager_test.dart | 79 + ...id_webview_cookie_manager_test.mocks.dart} | 35 +- .../test/android_webview_test.dart | 197 +- .../test/android_webview_test.mocks.dart | 1461 ++++++++++---- .../{ => legacy}/surface_android_test.dart | 4 +- .../webview_android_cookie_manager_test.dart | 4 +- ...iew_android_cookie_manager_test.mocks.dart | 54 + .../webview_android_widget_test.dart | 328 ++-- .../webview_android_widget_test.mocks.dart | 1016 ++++++++++ .../test/test_android_webview.pigeon.dart | 137 +- .../webview_android_widget_test.mocks.dart | 572 ------ 56 files changed, 9754 insertions(+), 4164 deletions(-) delete mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/Releasable.java create mode 100644 packages/webview_flutter/webview_flutter_android/example/integration_test/legacy/webview_flutter_test.dart rename packages/webview_flutter/webview_flutter_android/example/lib/{ => legacy}/navigation_decision.dart (100%) rename packages/webview_flutter/webview_flutter_android/example/lib/{ => legacy}/navigation_request.dart (100%) rename packages/webview_flutter/webview_flutter_android/example/lib/{ => legacy}/web_view.dart (98%) create mode 100644 packages/webview_flutter/webview_flutter_android/lib/src/android_navigation_delegate.dart create mode 100644 packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart create mode 100644 packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart create mode 100644 packages/webview_flutter/webview_flutter_android/lib/src/android_webview_cookie_manager.dart create mode 100644 packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart rename packages/webview_flutter/webview_flutter_android/lib/{ => src/legacy}/webview_android.dart (94%) rename packages/webview_flutter/webview_flutter_android/lib/{ => src/legacy}/webview_android_cookie_manager.dart (85%) rename packages/webview_flutter/webview_flutter_android/lib/{ => src/legacy}/webview_android_widget.dart (68%) rename packages/webview_flutter/webview_flutter_android/lib/{ => src/legacy}/webview_surface_android.dart (96%) create mode 100644 packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart create mode 100644 packages/webview_flutter/webview_flutter_android/lib/src/webview_flutter_android_legacy.dart create mode 100644 packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart create mode 100644 packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart create mode 100644 packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart create mode 100644 packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart create mode 100644 packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.dart rename packages/webview_flutter/webview_flutter_android/test/{webview_android_cookie_manager_test.mocks.dart => android_webview_cookie_manager_test.mocks.dart} (52%) rename packages/webview_flutter/webview_flutter_android/test/{ => legacy}/surface_android_test.dart (94%) rename packages/webview_flutter/webview_flutter_android/test/{ => legacy}/webview_android_cookie_manager_test.dart (89%) create mode 100644 packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.mocks.dart rename packages/webview_flutter/webview_flutter_android/test/{ => legacy}/webview_android_widget_test.dart (75%) create mode 100644 packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart delete mode 100644 packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerFlutterApiImpl.java index 1981d8eccf12..0d4797e9a1bb 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerFlutterApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerFlutterApiImpl.java @@ -47,20 +47,6 @@ public void onDownloadStart( callback); } - /** - * Communicates to Dart that the reference to a {@link DownloadListener} was removed. - * - * @param downloadListener the instance whose reference will be removed - * @param callback reply callback with return value from Dart - */ - public void dispose(DownloadListener downloadListener, Reply callback) { - if (instanceManager.containsInstance(downloadListener)) { - dispose(getIdentifierForListener(downloadListener), callback); - } else { - callback.reply(null); - } - } - private long getIdentifierForListener(DownloadListener listener) { final Long identifier = instanceManager.getIdentifierForStrongReference(listener); if (identifier == null) { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java index ed0c2aee4700..a9cbcbdd410a 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java @@ -6,7 +6,6 @@ import android.webkit.DownloadListener; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerHostApi; /** @@ -21,11 +20,9 @@ public class DownloadListenerHostApiImpl implements DownloadListenerHostApi { /** * Implementation of {@link DownloadListener} that passes arguments of callback methods to Dart. - * - *

No messages are sent to Dart after {@link DownloadListenerImpl#release} is called. */ - public static class DownloadListenerImpl implements DownloadListener, Releasable { - @Nullable private DownloadListenerFlutterApiImpl flutterApi; + public static class DownloadListenerImpl implements DownloadListener { + private final DownloadListenerFlutterApiImpl flutterApi; /** * Creates a {@link DownloadListenerImpl} that passes arguments of callbacks methods to Dart. @@ -43,18 +40,8 @@ public void onDownloadStart( String contentDisposition, String mimetype, long contentLength) { - if (flutterApi != null) { - flutterApi.onDownloadStart( - this, url, userAgent, contentDisposition, mimetype, contentLength, reply -> {}); - } - } - - @Override - public void release() { - if (flutterApi != null) { - flutterApi.dispose(this, reply -> {}); - } - flutterApi = null; + flutterApi.onDownloadStart( + this, url, userAgent, contentDisposition, mimetype, contentLength, reply -> {}); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java index 9a6b1c44d39d..15c80cc0a907 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v4.0.2), do not edit directly. +// Autogenerated from Pigeon (v4.2.3), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.webviewflutter; @@ -17,6 +17,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -342,22 +343,22 @@ public interface Result { void error(Throwable error); } - - private static class JavaObjectHostApiCodec extends StandardMessageCodec { - public static final JavaObjectHostApiCodec INSTANCE = new JavaObjectHostApiCodec(); - - private JavaObjectHostApiCodec() {} - } - - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + /** + * Handles methods calls to the native Java Object class. + * + *

Also handles calls to remove the reference to an instance with `dispose`. + * + *

See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. + * + *

Generated interface from Pigeon that represents a handler of messages from Flutter. + */ public interface JavaObjectHostApi { void dispose(@NonNull Long identifier); /** The codec used by JavaObjectHostApi. */ static MessageCodec getCodec() { - return JavaObjectHostApiCodec.INSTANCE; + return new StandardMessageCodec(); } - /** * Sets up an instance of `JavaObjectHostApi` to handle messages through the `binaryMessenger`. */ @@ -372,6 +373,7 @@ static void setup(BinaryMessenger binaryMessenger, JavaObjectHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number identifierArg = (Number) args.get(0); if (identifierArg == null) { throw new NullPointerException("identifierArg unexpectedly null."); @@ -389,14 +391,13 @@ static void setup(BinaryMessenger binaryMessenger, JavaObjectHostApi api) { } } } - - private static class JavaObjectFlutterApiCodec extends StandardMessageCodec { - public static final JavaObjectFlutterApiCodec INSTANCE = new JavaObjectFlutterApiCodec(); - - private JavaObjectFlutterApiCodec() {} - } - - /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + /** + * Handles callbacks methods for the native Java Object class. + * + *

See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. + * + *

Generated class from Pigeon that represents Flutter messages that can be called from Java. + */ public static class JavaObjectFlutterApi { private final BinaryMessenger binaryMessenger; @@ -407,9 +408,9 @@ public JavaObjectFlutterApi(BinaryMessenger argBinaryMessenger) { public interface Reply { void reply(T reply); } - + /** The codec used by JavaObjectFlutterApi. */ static MessageCodec getCodec() { - return JavaObjectFlutterApiCodec.INSTANCE; + return new StandardMessageCodec(); } public void dispose(@NonNull Long identifierArg, Reply callback) { @@ -417,19 +418,12 @@ public void dispose(@NonNull Long identifierArg, Reply callback) { new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.JavaObjectFlutterApi.dispose", getCodec()); channel.send( - new ArrayList(Arrays.asList(identifierArg)), + new ArrayList(Collections.singletonList(identifierArg)), channelReply -> { callback.reply(null); }); } } - - private static class CookieManagerHostApiCodec extends StandardMessageCodec { - public static final CookieManagerHostApiCodec INSTANCE = new CookieManagerHostApiCodec(); - - private CookieManagerHostApiCodec() {} - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface CookieManagerHostApi { void clearCookies(Result result); @@ -438,9 +432,8 @@ public interface CookieManagerHostApi { /** The codec used by CookieManagerHostApi. */ static MessageCodec getCodec() { - return CookieManagerHostApiCodec.INSTANCE; + return new StandardMessageCodec(); } - /** * Sets up an instance of `CookieManagerHostApi` to handle messages through the * `binaryMessenger`. @@ -490,6 +483,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; String urlArg = (String) args.get(0); if (urlArg == null) { throw new NullPointerException("urlArg unexpectedly null."); @@ -518,7 +512,7 @@ private static class WebViewHostApiCodec extends StandardMessageCodec { private WebViewHostApiCodec() {} @Override - protected Object readValueOfType(byte type, ByteBuffer buffer) { + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { case (byte) 128: return WebViewPoint.fromMap((Map) readValue(buffer)); @@ -529,7 +523,7 @@ protected Object readValueOfType(byte type, ByteBuffer buffer) { } @Override - protected void writeValue(ByteArrayOutputStream stream, Object value) { + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { if (value instanceof WebViewPoint) { stream.write(128); writeValue(stream, ((WebViewPoint) value).toMap()); @@ -543,8 +537,6 @@ protected void writeValue(ByteArrayOutputStream stream, Object value) { public interface WebViewHostApi { void create(@NonNull Long instanceId, @NonNull Boolean useHybridComposition); - void dispose(@NonNull Long instanceId); - void loadData( @NonNull Long instanceId, @NonNull String data, @@ -619,7 +611,6 @@ void removeJavaScriptChannel( static MessageCodec getCodec() { return WebViewHostApiCodec.INSTANCE; } - /** Sets up an instance of `WebViewHostApi` to handle messages through the `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { { @@ -632,6 +623,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -653,31 +645,6 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { channel.setMessageHandler(null); } } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.dispose", getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - Map wrapped = new HashMap<>(); - try { - ArrayList args = (ArrayList) message; - Number instanceIdArg = (Number) args.get(0); - if (instanceIdArg == null) { - throw new NullPointerException("instanceIdArg unexpectedly null."); - } - api.dispose((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); - } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -688,6 +655,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -725,6 +693,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -764,6 +733,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -800,6 +770,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -834,6 +805,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -860,6 +832,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -886,6 +859,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -912,6 +886,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -937,6 +912,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -962,6 +938,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -987,6 +964,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1020,6 +998,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1064,6 +1043,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1090,6 +1070,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1126,6 +1107,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1162,6 +1144,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1188,6 +1171,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1214,6 +1198,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1243,6 +1228,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Boolean enabledArg = (Boolean) args.get(0); if (enabledArg == null) { throw new NullPointerException("enabledArg unexpectedly null."); @@ -1268,6 +1254,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1303,6 +1290,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1339,6 +1327,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1375,6 +1364,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1405,6 +1395,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1435,6 +1426,7 @@ public void error(Throwable error) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1458,19 +1450,10 @@ public void error(Throwable error) { } } } - - private static class WebSettingsHostApiCodec extends StandardMessageCodec { - public static final WebSettingsHostApiCodec INSTANCE = new WebSettingsHostApiCodec(); - - private WebSettingsHostApiCodec() {} - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface WebSettingsHostApi { void create(@NonNull Long instanceId, @NonNull Long webViewInstanceId); - void dispose(@NonNull Long instanceId); - void setDomStorageEnabled(@NonNull Long instanceId, @NonNull Boolean flag); void setJavaScriptCanOpenWindowsAutomatically(@NonNull Long instanceId, @NonNull Boolean flag); @@ -1497,9 +1480,8 @@ public interface WebSettingsHostApi { /** The codec used by WebSettingsHostApi. */ static MessageCodec getCodec() { - return WebSettingsHostApiCodec.INSTANCE; + return new StandardMessageCodec(); } - /** * Sets up an instance of `WebSettingsHostApi` to handle messages through the `binaryMessenger`. */ @@ -1514,6 +1496,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1535,31 +1518,6 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { channel.setMessageHandler(null); } } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.WebSettingsHostApi.dispose", getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - Map wrapped = new HashMap<>(); - try { - ArrayList args = (ArrayList) message; - Number instanceIdArg = (Number) args.get(0); - if (instanceIdArg == null) { - throw new NullPointerException("instanceIdArg unexpectedly null."); - } - api.dispose((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); - } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } { BasicMessageChannel channel = new BasicMessageChannel<>( @@ -1572,6 +1530,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1604,6 +1563,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1636,6 +1596,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1668,6 +1629,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1700,6 +1662,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1730,6 +1693,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1762,6 +1726,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1794,6 +1759,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1826,6 +1792,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1858,6 +1825,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1890,6 +1858,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1922,6 +1891,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1944,23 +1914,14 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } } } - - private static class JavaScriptChannelHostApiCodec extends StandardMessageCodec { - public static final JavaScriptChannelHostApiCodec INSTANCE = - new JavaScriptChannelHostApiCodec(); - - private JavaScriptChannelHostApiCodec() {} - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface JavaScriptChannelHostApi { void create(@NonNull Long instanceId, @NonNull String channelName); /** The codec used by JavaScriptChannelHostApi. */ static MessageCodec getCodec() { - return JavaScriptChannelHostApiCodec.INSTANCE; + return new StandardMessageCodec(); } - /** * Sets up an instance of `JavaScriptChannelHostApi` to handle messages through the * `binaryMessenger`. @@ -1976,6 +1937,7 @@ static void setup(BinaryMessenger binaryMessenger, JavaScriptChannelHostApi api) Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -1998,14 +1960,6 @@ static void setup(BinaryMessenger binaryMessenger, JavaScriptChannelHostApi api) } } } - - private static class JavaScriptChannelFlutterApiCodec extends StandardMessageCodec { - public static final JavaScriptChannelFlutterApiCodec INSTANCE = - new JavaScriptChannelFlutterApiCodec(); - - private JavaScriptChannelFlutterApiCodec() {} - } - /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class JavaScriptChannelFlutterApi { private final BinaryMessenger binaryMessenger; @@ -2017,22 +1971,9 @@ public JavaScriptChannelFlutterApi(BinaryMessenger argBinaryMessenger) { public interface Reply { void reply(T reply); } - + /** The codec used by JavaScriptChannelFlutterApi. */ static MessageCodec getCodec() { - return JavaScriptChannelFlutterApiCodec.INSTANCE; - } - - public void dispose(@NonNull Long instanceIdArg, Reply callback) { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.JavaScriptChannelFlutterApi.dispose", - getCodec()); - channel.send( - new ArrayList(Arrays.asList(instanceIdArg)), - channelReply -> { - callback.reply(null); - }); + return new StandardMessageCodec(); } public void postMessage( @@ -2049,22 +1990,17 @@ public void postMessage( }); } } - - private static class WebViewClientHostApiCodec extends StandardMessageCodec { - public static final WebViewClientHostApiCodec INSTANCE = new WebViewClientHostApiCodec(); - - private WebViewClientHostApiCodec() {} - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface WebViewClientHostApi { - void create(@NonNull Long instanceId, @NonNull Boolean shouldOverrideUrlLoading); + void create(@NonNull Long instanceId); + + void setSynchronousReturnValueForShouldOverrideUrlLoading( + @NonNull Long instanceId, @NonNull Boolean value); /** The codec used by WebViewClientHostApi. */ static MessageCodec getCodec() { - return WebViewClientHostApiCodec.INSTANCE; + return new StandardMessageCodec(); } - /** * Sets up an instance of `WebViewClientHostApi` to handle messages through the * `binaryMessenger`. @@ -2080,18 +2016,45 @@ static void setup(BinaryMessenger binaryMessenger, WebViewClientHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); } - Boolean shouldOverrideUrlLoadingArg = (Boolean) args.get(1); - if (shouldOverrideUrlLoadingArg == null) { - throw new NullPointerException( - "shouldOverrideUrlLoadingArg unexpectedly null."); + api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + assert args != null; + Number instanceIdArg = (Number) args.get(0); + if (instanceIdArg == null) { + throw new NullPointerException("instanceIdArg unexpectedly null."); } - api.create( - (instanceIdArg == null) ? null : instanceIdArg.longValue(), - shouldOverrideUrlLoadingArg); + Boolean valueArg = (Boolean) args.get(1); + if (valueArg == null) { + throw new NullPointerException("valueArg unexpectedly null."); + } + api.setSynchronousReturnValueForShouldOverrideUrlLoading( + (instanceIdArg == null) ? null : instanceIdArg.longValue(), valueArg); wrapped.put("result", null); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); @@ -2111,7 +2074,7 @@ private static class WebViewClientFlutterApiCodec extends StandardMessageCodec { private WebViewClientFlutterApiCodec() {} @Override - protected Object readValueOfType(byte type, ByteBuffer buffer) { + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { case (byte) 128: return WebResourceErrorData.fromMap((Map) readValue(buffer)); @@ -2125,7 +2088,7 @@ protected Object readValueOfType(byte type, ByteBuffer buffer) { } @Override - protected void writeValue(ByteArrayOutputStream stream, Object value) { + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { if (value instanceof WebResourceErrorData) { stream.write(128); writeValue(stream, ((WebResourceErrorData) value).toMap()); @@ -2149,22 +2112,11 @@ public WebViewClientFlutterApi(BinaryMessenger argBinaryMessenger) { public interface Reply { void reply(T reply); } - + /** The codec used by WebViewClientFlutterApi. */ static MessageCodec getCodec() { return WebViewClientFlutterApiCodec.INSTANCE; } - public void dispose(@NonNull Long instanceIdArg, Reply callback) { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.WebViewClientFlutterApi.dispose", getCodec()); - channel.send( - new ArrayList(Arrays.asList(instanceIdArg)), - channelReply -> { - callback.reply(null); - }); - } - public void onPageStarted( @NonNull Long instanceIdArg, @NonNull Long webViewInstanceIdArg, @@ -2275,22 +2227,14 @@ public void urlLoading( }); } } - - private static class DownloadListenerHostApiCodec extends StandardMessageCodec { - public static final DownloadListenerHostApiCodec INSTANCE = new DownloadListenerHostApiCodec(); - - private DownloadListenerHostApiCodec() {} - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface DownloadListenerHostApi { void create(@NonNull Long instanceId); /** The codec used by DownloadListenerHostApi. */ static MessageCodec getCodec() { - return DownloadListenerHostApiCodec.INSTANCE; + return new StandardMessageCodec(); } - /** * Sets up an instance of `DownloadListenerHostApi` to handle messages through the * `binaryMessenger`. @@ -2306,6 +2250,7 @@ static void setup(BinaryMessenger binaryMessenger, DownloadListenerHostApi api) Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -2323,14 +2268,6 @@ static void setup(BinaryMessenger binaryMessenger, DownloadListenerHostApi api) } } } - - private static class DownloadListenerFlutterApiCodec extends StandardMessageCodec { - public static final DownloadListenerFlutterApiCodec INSTANCE = - new DownloadListenerFlutterApiCodec(); - - private DownloadListenerFlutterApiCodec() {} - } - /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class DownloadListenerFlutterApi { private final BinaryMessenger binaryMessenger; @@ -2342,20 +2279,9 @@ public DownloadListenerFlutterApi(BinaryMessenger argBinaryMessenger) { public interface Reply { void reply(T reply); } - + /** The codec used by DownloadListenerFlutterApi. */ static MessageCodec getCodec() { - return DownloadListenerFlutterApiCodec.INSTANCE; - } - - public void dispose(@NonNull Long instanceIdArg, Reply callback) { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.DownloadListenerFlutterApi.dispose", getCodec()); - channel.send( - new ArrayList(Arrays.asList(instanceIdArg)), - channelReply -> { - callback.reply(null); - }); + return new StandardMessageCodec(); } public void onDownloadStart( @@ -2385,22 +2311,14 @@ public void onDownloadStart( }); } } - - private static class WebChromeClientHostApiCodec extends StandardMessageCodec { - public static final WebChromeClientHostApiCodec INSTANCE = new WebChromeClientHostApiCodec(); - - private WebChromeClientHostApiCodec() {} - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface WebChromeClientHostApi { - void create(@NonNull Long instanceId, @NonNull Long webViewClientInstanceId); + void create(@NonNull Long instanceId); /** The codec used by WebChromeClientHostApi. */ static MessageCodec getCodec() { - return WebChromeClientHostApiCodec.INSTANCE; + return new StandardMessageCodec(); } - /** * Sets up an instance of `WebChromeClientHostApi` to handle messages through the * `binaryMessenger`. @@ -2416,19 +2334,12 @@ static void setup(BinaryMessenger binaryMessenger, WebChromeClientHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); } - Number webViewClientInstanceIdArg = (Number) args.get(1); - if (webViewClientInstanceIdArg == null) { - throw new NullPointerException("webViewClientInstanceIdArg unexpectedly null."); - } - api.create( - (instanceIdArg == null) ? null : instanceIdArg.longValue(), - (webViewClientInstanceIdArg == null) - ? null - : webViewClientInstanceIdArg.longValue()); + api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); wrapped.put("result", null); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); @@ -2441,14 +2352,6 @@ static void setup(BinaryMessenger binaryMessenger, WebChromeClientHostApi api) { } } } - - private static class FlutterAssetManagerHostApiCodec extends StandardMessageCodec { - public static final FlutterAssetManagerHostApiCodec INSTANCE = - new FlutterAssetManagerHostApiCodec(); - - private FlutterAssetManagerHostApiCodec() {} - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface FlutterAssetManagerHostApi { @NonNull @@ -2459,9 +2362,8 @@ public interface FlutterAssetManagerHostApi { /** The codec used by FlutterAssetManagerHostApi. */ static MessageCodec getCodec() { - return FlutterAssetManagerHostApiCodec.INSTANCE; + return new StandardMessageCodec(); } - /** * Sets up an instance of `FlutterAssetManagerHostApi` to handle messages through the * `binaryMessenger`. @@ -2477,6 +2379,7 @@ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi ap Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; String pathArg = (String) args.get(0); if (pathArg == null) { throw new NullPointerException("pathArg unexpectedly null."); @@ -2504,6 +2407,7 @@ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi ap Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; String nameArg = (String) args.get(0); if (nameArg == null) { throw new NullPointerException("nameArg unexpectedly null."); @@ -2521,14 +2425,6 @@ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi ap } } } - - private static class WebChromeClientFlutterApiCodec extends StandardMessageCodec { - public static final WebChromeClientFlutterApiCodec INSTANCE = - new WebChromeClientFlutterApiCodec(); - - private WebChromeClientFlutterApiCodec() {} - } - /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class WebChromeClientFlutterApi { private final BinaryMessenger binaryMessenger; @@ -2540,20 +2436,9 @@ public WebChromeClientFlutterApi(BinaryMessenger argBinaryMessenger) { public interface Reply { void reply(T reply); } - + /** The codec used by WebChromeClientFlutterApi. */ static MessageCodec getCodec() { - return WebChromeClientFlutterApiCodec.INSTANCE; - } - - public void dispose(@NonNull Long instanceIdArg, Reply callback) { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.WebChromeClientFlutterApi.dispose", getCodec()); - channel.send( - new ArrayList(Arrays.asList(instanceIdArg)), - channelReply -> { - callback.reply(null); - }); + return new StandardMessageCodec(); } public void onProgressChanged( @@ -2573,13 +2458,6 @@ public void onProgressChanged( }); } } - - private static class WebStorageHostApiCodec extends StandardMessageCodec { - public static final WebStorageHostApiCodec INSTANCE = new WebStorageHostApiCodec(); - - private WebStorageHostApiCodec() {} - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface WebStorageHostApi { void create(@NonNull Long instanceId); @@ -2588,9 +2466,8 @@ public interface WebStorageHostApi { /** The codec used by WebStorageHostApi. */ static MessageCodec getCodec() { - return WebStorageHostApiCodec.INSTANCE; + return new StandardMessageCodec(); } - /** * Sets up an instance of `WebStorageHostApi` to handle messages through the `binaryMessenger`. */ @@ -2605,6 +2482,7 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -2630,6 +2508,7 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; + assert args != null; Number instanceIdArg = (Number) args.get(0); if (instanceIdArg == null) { throw new NullPointerException("instanceIdArg unexpectedly null."); @@ -2648,7 +2527,8 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { } } - private static Map wrapError(Throwable exception) { + @NonNull + private static Map wrapError(@NonNull Throwable exception) { Map errorMap = new HashMap<>(); errorMap.put("message", exception.toString()); errorMap.put("code", exception.getClass().getSimpleName()); diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java index ce6f2b81ed1c..cf2c2629989a 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java @@ -8,7 +8,6 @@ import android.os.Looper; import android.webkit.JavascriptInterface; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; /** * Added as a JavaScript interface to the WebView for any JavaScript channel that the Dart code sets @@ -16,13 +15,11 @@ * *

Exposes a single method named `postMessage` to JavaScript, which sends a message to the Dart * code. - * - *

No messages are sent to Dart after {@link JavaScriptChannel#release} is called. */ -public class JavaScriptChannel implements Releasable { +public class JavaScriptChannel { private final Handler platformThreadHandler; final String javaScriptChannelName; - @Nullable private JavaScriptChannelFlutterApiImpl flutterApi; + private final JavaScriptChannelFlutterApiImpl flutterApi; /** * Creates a {@link JavaScriptChannel} that passes arguments of callback methods to Dart. @@ -46,9 +43,7 @@ public JavaScriptChannel( public void postMessage(final String message) { final Runnable postMessageRunnable = () -> { - if (flutterApi != null) { - flutterApi.postMessage(JavaScriptChannel.this, message, reply -> {}); - } + flutterApi.postMessage(JavaScriptChannel.this, message, reply -> {}); }; if (platformThreadHandler.getLooper() == Looper.myLooper()) { @@ -57,12 +52,4 @@ public void postMessage(final String message) { platformThreadHandler.post(postMessageRunnable); } } - - @Override - public void release() { - if (flutterApi != null) { - flutterApi.dispose(this, reply -> {}); - } - flutterApi = null; - } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java index dbac83382a29..ca0892699638 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java @@ -33,20 +33,6 @@ public void postMessage( super.postMessage(getIdentifierForJavaScriptChannel(javaScriptChannel), messageArg, callback); } - /** - * Communicates to Dart that the reference to a {@link JavaScriptChannel} was removed. - * - * @param javaScriptChannel The instance whose reference will be removed. - * @param callback Reply callback with return value from Dart. - */ - public void dispose(JavaScriptChannel javaScriptChannel, Reply callback) { - if (instanceManager.containsInstance(javaScriptChannel)) { - dispose(getIdentifierForJavaScriptChannel(javaScriptChannel), callback); - } else { - callback.reply(null); - } - } - private long getIdentifierForJavaScriptChannel(JavaScriptChannel javaScriptChannel) { final Long identifier = instanceManager.getIdentifierForStrongReference(javaScriptChannel); if (identifier == null) { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/Releasable.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/Releasable.java deleted file mode 100644 index 9c4ed7650640..000000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/Releasable.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.webviewflutter; - -/** - * Represents a resource, or a holder of resources, which may be released once they are no longer - * needed. - */ -interface Releasable { - /** Notify that that the reference to an object will be removed by a holder. */ - void release(); -} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java index 28d63ec82dec..cf263e2a6d34 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java @@ -40,20 +40,6 @@ public void onProgressChanged( getIdentifierForClient(webChromeClient), webViewIdentifier, progress, callback); } - /** - * Communicates to Dart that the reference to a {@link WebChromeClient}} was removed. - * - * @param webChromeClient the instance whose reference will be removed - * @param callback reply callback with return value from Dart - */ - public void dispose(WebChromeClient webChromeClient, Reply callback) { - if (instanceManager.containsInstance(webChromeClient)) { - dispose(getIdentifierForClient(webChromeClient), callback); - } else { - callback.reply(null); - } - } - private long getIdentifierForClient(WebChromeClient webChromeClient) { final Long identifier = instanceManager.getIdentifierForStrongReference(webChromeClient); if (identifier == null) { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java index 0f50c82fa7bb..7b5241ed4d33 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java @@ -29,20 +29,17 @@ public class WebChromeClientHostApiImpl implements WebChromeClientHostApi { /** * Implementation of {@link WebChromeClient} that passes arguments of callback methods to Dart. */ - public static class WebChromeClientImpl extends WebChromeClient implements Releasable { - @Nullable private WebChromeClientFlutterApiImpl flutterApi; - private WebViewClient webViewClient; + public static class WebChromeClientImpl extends WebChromeClient { + private final WebChromeClientFlutterApiImpl flutterApi; + @Nullable private WebViewClient webViewClient; /** * Creates a {@link WebChromeClient} that passes arguments of callbacks methods to Dart. * * @param flutterApi handles sending messages to Dart - * @param webViewClient receives forwarded calls from {@link WebChromeClient#onCreateWindow} */ - public WebChromeClientImpl( - @NonNull WebChromeClientFlutterApiImpl flutterApi, WebViewClient webViewClient) { + public WebChromeClientImpl(@NonNull WebChromeClientFlutterApiImpl flutterApi) { this.flutterApi = flutterApi; - this.webViewClient = webViewClient; } @Override @@ -67,6 +64,14 @@ public boolean onCreateWindow( @VisibleForTesting boolean onCreateWindow( final WebView view, Message resultMsg, @Nullable WebView onCreateWindowWebView) { + // WebChromeClient requires a WebViewClient because of a bug fix that makes + // calls to WebViewClient.requestLoading/WebViewClient.urlLoading when a new + // window is opened. This is to make sure a url opened by `Window.open` has + // a secure url. + if (webViewClient == null) { + return false; + } + final WebViewClient windowWebViewClient = new WebViewClient() { @RequiresApi(api = Build.VERSION_CODES.N) @@ -102,9 +107,7 @@ public boolean shouldOverrideUrlLoading(WebView windowWebView, String url) { @Override public void onProgressChanged(WebView view, int progress) { - if (flutterApi != null) { - flutterApi.onProgressChanged(this, view, (long) progress, reply -> {}); - } + flutterApi.onProgressChanged(this, view, (long) progress, reply -> {}); } /** @@ -113,17 +116,9 @@ public void onProgressChanged(WebView view, int progress) { * * @param webViewClient the forwarding {@link WebViewClient} */ - public void setWebViewClient(WebViewClient webViewClient) { + public void setWebViewClient(@NonNull WebViewClient webViewClient) { this.webViewClient = webViewClient; } - - @Override - public void release() { - if (flutterApi != null) { - flutterApi.dispose(this, reply -> {}); - } - flutterApi = null; - } } /** Handles creating {@link WebChromeClient}s for a {@link WebChromeClientHostApiImpl}. */ @@ -132,12 +127,10 @@ public static class WebChromeClientCreator { * Creates a {@link DownloadListenerHostApiImpl.DownloadListenerImpl}. * * @param flutterApi handles sending messages to Dart - * @param webViewClient receives forwarded calls from {@link WebChromeClient#onCreateWindow} - * @return the created {@link DownloadListenerHostApiImpl.DownloadListenerImpl} + * @return the created {@link WebChromeClientHostApiImpl.WebChromeClientImpl} */ - public WebChromeClientImpl createWebChromeClient( - WebChromeClientFlutterApiImpl flutterApi, WebViewClient webViewClient) { - return new WebChromeClientImpl(flutterApi, webViewClient); + public WebChromeClientImpl createWebChromeClient(WebChromeClientFlutterApiImpl flutterApi) { + return new WebChromeClientImpl(flutterApi); } } @@ -158,11 +151,9 @@ public WebChromeClientHostApiImpl( } @Override - public void create(Long instanceId, Long webViewClientInstanceId) { - final WebViewClient webViewClient = - (WebViewClient) instanceManager.getInstance(webViewClientInstanceId); + public void create(Long instanceId) { final WebChromeClient webChromeClient = - webChromeClientCreator.createWebChromeClient(flutterApi, webViewClient); + webChromeClientCreator.createWebChromeClient(flutterApi); instanceManager.addDartCreatedInstance(webChromeClient, instanceId); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java index 5b6f9e78a8d5..98fd4fcfb53e 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java @@ -49,11 +49,6 @@ public void create(Long instanceId, Long webViewInstanceId) { webSettingsCreator.createWebSettings(webView), instanceId); } - @Override - public void dispose(Long instanceId) { - instanceManager.remove(instanceId); - } - @Override public void setDomStorageEnabled(Long instanceId, Boolean flag) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java index c23e8e79ae37..0dc0bbb82b07 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java @@ -197,20 +197,6 @@ public void urlLoading( urlLoading(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback); } - /** - * Communicates to Dart that the reference to a {@link WebViewClient} was removed. - * - * @param webViewClient the instance whose reference will be removed - * @param callback reply callback with return value from Dart - */ - public void dispose(WebViewClient webViewClient, Reply callback) { - if (instanceManager.containsInstance(webViewClient)) { - dispose(getIdentifierForClient(webViewClient), callback); - } else { - callback.reply(null); - } - } - private long getIdentifierForClient(WebViewClient webViewClient) { final Long identifier = instanceManager.getIdentifierForStrongReference(webViewClient); if (identifier == null) { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java index 4833ee917d34..09a34f2d4a85 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java @@ -14,10 +14,10 @@ import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.webkit.WebResourceErrorCompat; import androidx.webkit.WebViewClientCompat; +import java.util.Objects; /** * Host api implementation for {@link WebViewClient}. @@ -29,73 +29,53 @@ public class WebViewClientHostApiImpl implements GeneratedAndroidWebView.WebView private final WebViewClientCreator webViewClientCreator; private final WebViewClientFlutterApiImpl flutterApi; - /** - * An interface implemented by a class that extends {@link WebViewClient} and {@link Releasable}. - */ - public interface ReleasableWebViewClient extends Releasable {} - /** Implementation of {@link WebViewClient} that passes arguments of callback methods to Dart. */ @RequiresApi(Build.VERSION_CODES.N) - public static class WebViewClientImpl extends WebViewClient implements ReleasableWebViewClient { - @Nullable private WebViewClientFlutterApiImpl flutterApi; - private final boolean shouldOverrideUrlLoading; + public static class WebViewClientImpl extends WebViewClient { + private final WebViewClientFlutterApiImpl flutterApi; + private boolean returnValueForShouldOverrideUrlLoading = false; /** * Creates a {@link WebViewClient} that passes arguments of callbacks methods to Dart. * * @param flutterApi handles sending messages to Dart - * @param shouldOverrideUrlLoading whether loading a url should be overridden */ - public WebViewClientImpl( - @NonNull WebViewClientFlutterApiImpl flutterApi, boolean shouldOverrideUrlLoading) { - this.shouldOverrideUrlLoading = shouldOverrideUrlLoading; + public WebViewClientImpl(@NonNull WebViewClientFlutterApiImpl flutterApi) { this.flutterApi = flutterApi; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { - if (flutterApi != null) { - flutterApi.onPageStarted(this, view, url, reply -> {}); - } + flutterApi.onPageStarted(this, view, url, reply -> {}); } @Override public void onPageFinished(WebView view, String url) { - if (flutterApi != null) { - flutterApi.onPageFinished(this, view, url, reply -> {}); - } + flutterApi.onPageFinished(this, view, url, reply -> {}); } @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { - if (flutterApi != null) { - flutterApi.onReceivedRequestError(this, view, request, error, reply -> {}); - } + flutterApi.onReceivedRequestError(this, view, request, error, reply -> {}); } @Override public void onReceivedError( WebView view, int errorCode, String description, String failingUrl) { - if (flutterApi != null) { - flutterApi.onReceivedError( - this, view, (long) errorCode, description, failingUrl, reply -> {}); - } + flutterApi.onReceivedError( + this, view, (long) errorCode, description, failingUrl, reply -> {}); } @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - if (flutterApi != null) { - flutterApi.requestLoading(this, view, request, reply -> {}); - } - return shouldOverrideUrlLoading; + flutterApi.requestLoading(this, view, request, reply -> {}); + return returnValueForShouldOverrideUrlLoading; } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (flutterApi != null) { - flutterApi.urlLoading(this, view, url, reply -> {}); - } - return shouldOverrideUrlLoading; + flutterApi.urlLoading(this, view, url, reply -> {}); + return returnValueForShouldOverrideUrlLoading; } @Override @@ -105,11 +85,9 @@ public void onUnhandledKeyEvent(WebView view, KeyEvent event) { // truly lost. } - public void release() { - if (flutterApi != null) { - flutterApi.dispose(this, reply -> {}); - } - flutterApi = null; + /** Sets return value for {@link #shouldOverrideUrlLoading}. */ + public void setReturnValueForShouldOverrideUrlLoading(boolean value) { + returnValueForShouldOverrideUrlLoading = value; } } @@ -117,29 +95,22 @@ public void release() { * Implementation of {@link WebViewClientCompat} that passes arguments of callback methods to * Dart. */ - public static class WebViewClientCompatImpl extends WebViewClientCompat - implements ReleasableWebViewClient { - private @Nullable WebViewClientFlutterApiImpl flutterApi; - private final boolean shouldOverrideUrlLoading; - - public WebViewClientCompatImpl( - @NonNull WebViewClientFlutterApiImpl flutterApi, boolean shouldOverrideUrlLoading) { - this.shouldOverrideUrlLoading = shouldOverrideUrlLoading; + public static class WebViewClientCompatImpl extends WebViewClientCompat { + private final WebViewClientFlutterApiImpl flutterApi; + private boolean returnValueForShouldOverrideUrlLoading = false; + + public WebViewClientCompatImpl(@NonNull WebViewClientFlutterApiImpl flutterApi) { this.flutterApi = flutterApi; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { - if (flutterApi != null) { - flutterApi.onPageStarted(this, view, url, reply -> {}); - } + flutterApi.onPageStarted(this, view, url, reply -> {}); } @Override public void onPageFinished(WebView view, String url) { - if (flutterApi != null) { - flutterApi.onPageFinished(this, view, url, reply -> {}); - } + flutterApi.onPageFinished(this, view, url, reply -> {}); } // This method is only called when the WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR feature is @@ -151,36 +122,28 @@ public void onReceivedError( @NonNull WebView view, @NonNull WebResourceRequest request, @NonNull WebResourceErrorCompat error) { - if (flutterApi != null) { - flutterApi.onReceivedRequestError(this, view, request, error, reply -> {}); - } + flutterApi.onReceivedRequestError(this, view, request, error, reply -> {}); } @Override public void onReceivedError( WebView view, int errorCode, String description, String failingUrl) { - if (flutterApi != null) { - flutterApi.onReceivedError( - this, view, (long) errorCode, description, failingUrl, reply -> {}); - } + flutterApi.onReceivedError( + this, view, (long) errorCode, description, failingUrl, reply -> {}); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public boolean shouldOverrideUrlLoading( @NonNull WebView view, @NonNull WebResourceRequest request) { - if (flutterApi != null) { - flutterApi.requestLoading(this, view, request, reply -> {}); - } - return shouldOverrideUrlLoading; + flutterApi.requestLoading(this, view, request, reply -> {}); + return returnValueForShouldOverrideUrlLoading; } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (flutterApi != null) { - flutterApi.urlLoading(this, view, url, reply -> {}); - } - return shouldOverrideUrlLoading; + flutterApi.urlLoading(this, view, url, reply -> {}); + return returnValueForShouldOverrideUrlLoading; } @Override @@ -190,11 +153,9 @@ public void onUnhandledKeyEvent(WebView view, KeyEvent event) { // truly lost. } - public void release() { - if (flutterApi != null) { - flutterApi.dispose(this, reply -> {}); - } - flutterApi = null; + /** Sets return value for {@link #shouldOverrideUrlLoading}. */ + public void setReturnValueForShouldOverrideUrlLoading(boolean value) { + returnValueForShouldOverrideUrlLoading = value; } } @@ -206,8 +167,7 @@ public static class WebViewClientCreator { * @param flutterApi handles sending messages to Dart * @return the created {@link WebViewClient} */ - public WebViewClient createWebViewClient( - WebViewClientFlutterApiImpl flutterApi, boolean shouldOverrideUrlLoading) { + public WebViewClient createWebViewClient(WebViewClientFlutterApiImpl flutterApi) { // WebViewClientCompat is used to get // shouldOverrideUrlLoading(WebView view, WebResourceRequest request) // invoked by the webview on older Android devices, without it pages that use iframes will @@ -217,9 +177,9 @@ public WebViewClient createWebViewClient( // to bug https://bugs.chromium.org/p/chromium/issues/detail?id=925887. Also, see // https://github.com/flutter/flutter/issues/29446. if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return new WebViewClientImpl(flutterApi, shouldOverrideUrlLoading); + return new WebViewClientImpl(flutterApi); } else { - return new WebViewClientCompatImpl(flutterApi, shouldOverrideUrlLoading); + return new WebViewClientCompatImpl(flutterApi); } } } @@ -241,9 +201,24 @@ public WebViewClientHostApiImpl( } @Override - public void create(Long instanceId, Boolean shouldOverrideUrlLoading) { - final WebViewClient webViewClient = - webViewClientCreator.createWebViewClient(flutterApi, shouldOverrideUrlLoading); + public void create(@NonNull Long instanceId) { + final WebViewClient webViewClient = webViewClientCreator.createWebViewClient(flutterApi); instanceManager.addDartCreatedInstance(webViewClient, instanceId); } + + @Override + public void setSynchronousReturnValueForShouldOverrideUrlLoading( + @NonNull Long instanceId, @NonNull Boolean value) { + final WebViewClient webViewClient = + Objects.requireNonNull(instanceManager.getInstance(instanceId)); + if (webViewClient instanceof WebViewClientCompatImpl) { + ((WebViewClientCompatImpl) webViewClient).setReturnValueForShouldOverrideUrlLoading(value); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + && webViewClient instanceof WebViewClientImpl) { + ((WebViewClientImpl) webViewClient).setReturnValueForShouldOverrideUrlLoading(value); + } else { + throw new IllegalStateException( + "This WebViewClient doesn't support setting the returnValueForShouldOverrideUrlLoading."); + } + } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java index fe7615c664a4..1c5a55057ca6 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java @@ -17,6 +17,7 @@ import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.CookieManagerHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.FlutterAssetManagerHostApi; +import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaObjectHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebSettingsHostApi; @@ -77,15 +78,22 @@ private void setUp( Context context, View containerView, FlutterAssetManager flutterAssetManager) { - - instanceManager = InstanceManager.open(identifier -> {}); + instanceManager = + InstanceManager.open( + identifier -> + new GeneratedAndroidWebView.JavaObjectFlutterApi(binaryMessenger) + .dispose(identifier, reply -> {})); viewRegistry.registerViewFactory( "plugins.flutter.io/webview", new FlutterWebViewFactory(instanceManager)); webViewHostApi = new WebViewHostApiImpl( - instanceManager, new WebViewHostApiImpl.WebViewProxy(), context, containerView); + instanceManager, + binaryMessenger, + new WebViewHostApiImpl.WebViewProxy(), + context, + containerView); javaScriptChannelHostApi = new JavaScriptChannelHostApiImpl( instanceManager, @@ -93,6 +101,7 @@ private void setUp( new JavaScriptChannelFlutterApiImpl(binaryMessenger, instanceManager), new Handler(context.getMainLooper())); + JavaObjectHostApi.setup(binaryMessenger, new JavaObjectHostApiImpl(instanceManager)); WebViewHostApi.setup(binaryMessenger, webViewHostApi); JavaScriptChannelHostApi.setup(binaryMessenger, javaScriptChannelHostApi); WebViewClientHostApi.setup( diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java index 778ad611d05f..2fd990535b29 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java @@ -14,12 +14,10 @@ import android.webkit.WebViewClient; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.platform.PlatformView; -import io.flutter.plugins.webviewflutter.DownloadListenerHostApiImpl.DownloadListenerImpl; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewHostApi; import io.flutter.plugins.webviewflutter.WebChromeClientHostApiImpl.WebChromeClientImpl; -import io.flutter.plugins.webviewflutter.WebViewClientHostApiImpl.ReleasableWebViewClient; -import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -33,6 +31,7 @@ public class WebViewHostApiImpl implements WebViewHostApi { private final WebViewProxy webViewProxy; // Only used with WebView using virtual displays. @Nullable private final View containerView; + private final BinaryMessenger binaryMessenger; private Context context; @@ -42,10 +41,14 @@ public static class WebViewProxy { * Creates a {@link WebViewPlatformView}. * * @param context an Activity Context to access application assets + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager mangages instances used to communicate with the corresponding objects + * in Dart * @return the created {@link WebViewPlatformView} */ - public WebViewPlatformView createWebView(Context context) { - return new WebViewPlatformView(context); + public WebViewPlatformView createWebView( + Context context, BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + return new WebViewPlatformView(context, binaryMessenger, instanceManager); } /** @@ -56,8 +59,12 @@ public WebViewPlatformView createWebView(Context context) { * @return the created {@link InputAwareWebViewPlatformView} */ public InputAwareWebViewPlatformView createInputAwareWebView( - Context context, @Nullable View containerView) { - return new InputAwareWebViewPlatformView(context, containerView); + Context context, + BinaryMessenger binaryMessenger, + InstanceManager instanceManager, + @Nullable View containerView) { + return new InputAwareWebViewPlatformView( + context, binaryMessenger, instanceManager, containerView); } /** @@ -70,51 +77,26 @@ public void setWebContentsDebuggingEnabled(boolean enabled) { } } - private static class ReleasableValue { - @Nullable private T value; - - ReleasableValue() {} - - ReleasableValue(@Nullable T value) { - this.value = value; - } - - void set(@Nullable T newValue) { - release(); - value = newValue; - } - - @Nullable - T get() { - return value; - } - - void release() { - if (value != null) { - value.release(); - } - value = null; - } - } - /** Implementation of {@link WebView} that can be used as a Flutter {@link PlatformView}s. */ - public static class WebViewPlatformView extends WebView implements PlatformView, Releasable { - private final ReleasableValue - currentWebViewClient = new ReleasableValue<>(); - private final ReleasableValue currentDownloadListener = - new ReleasableValue<>(); - private final ReleasableValue currentWebChromeClient = - new ReleasableValue<>(); - private final Map> javaScriptInterfaces = - new HashMap<>(); + public static class WebViewPlatformView extends WebView implements PlatformView { + private WebViewClient currentWebViewClient; + private WebChromeClientImpl currentWebChromeClient; /** * Creates a {@link WebViewPlatformView}. * * @param context an Activity Context to access application assets. This value cannot be null. */ - public WebViewPlatformView(Context context) { + public WebViewPlatformView( + Context context, BinaryMessenger binaryMessenger, InstanceManager instanceManager) { super(context); + currentWebViewClient = new WebViewClient(); + currentWebChromeClient = + new WebChromeClientImpl( + new WebChromeClientFlutterApiImpl(binaryMessenger, instanceManager)); + + setWebViewClient(currentWebViewClient); + setWebChromeClient(currentWebChromeClient); } @Override @@ -130,56 +112,18 @@ public void dispose() { @Override public void setWebViewClient(WebViewClient webViewClient) { super.setWebViewClient(webViewClient); - currentWebViewClient.set((ReleasableWebViewClient) webViewClient); - - final WebChromeClientImpl webChromeClient = currentWebChromeClient.get(); - if (webChromeClient != null) { - ((WebChromeClientImpl) webChromeClient).setWebViewClient(webViewClient); - } - } - - @Override - public void setDownloadListener(DownloadListener listener) { - super.setDownloadListener(listener); - currentDownloadListener.set((DownloadListenerImpl) listener); + currentWebViewClient = webViewClient; + currentWebChromeClient.setWebViewClient(webViewClient); } @Override public void setWebChromeClient(WebChromeClient client) { super.setWebChromeClient(client); - currentWebChromeClient.set((WebChromeClientImpl) client); - } - - @SuppressLint("JavascriptInterface") - @Override - public void addJavascriptInterface(Object object, String name) { - super.addJavascriptInterface(object, name); - if (object instanceof JavaScriptChannel) { - final ReleasableValue javaScriptChannel = javaScriptInterfaces.get(name); - if (javaScriptChannel != null && javaScriptChannel.get() != object) { - javaScriptChannel.release(); - } - javaScriptInterfaces.put(name, new ReleasableValue<>((JavaScriptChannel) object)); - } - } - - @Override - public void removeJavascriptInterface(@NonNull String name) { - super.removeJavascriptInterface(name); - final ReleasableValue javaScriptChannel = javaScriptInterfaces.get(name); - javaScriptChannel.release(); - javaScriptInterfaces.remove(name); - } - - @Override - public void release() { - currentWebViewClient.release(); - currentDownloadListener.release(); - currentWebChromeClient.release(); - for (ReleasableValue channel : javaScriptInterfaces.values()) { - channel.release(); + if (!(client instanceof WebChromeClientImpl)) { + throw new AssertionError("Client must be a WebChromeClientImpl."); } - javaScriptInterfaces.clear(); + currentWebChromeClient = (WebChromeClientImpl) client; + currentWebChromeClient.setWebViewClient(currentWebViewClient); } } @@ -189,23 +133,28 @@ public void release() { */ @SuppressLint("ViewConstructor") public static class InputAwareWebViewPlatformView extends InputAwareWebView - implements PlatformView, Releasable { - private final ReleasableValue - currentWebViewClient = new ReleasableValue<>(); - private final ReleasableValue currentDownloadListener = - new ReleasableValue<>(); - private final ReleasableValue currentWebChromeClient = - new ReleasableValue<>(); - private final Map> javaScriptInterfaces = - new HashMap<>(); + implements PlatformView { + private WebViewClient currentWebViewClient; + private WebChromeClientImpl currentWebChromeClient; /** * Creates a {@link InputAwareWebViewPlatformView}. * * @param context an Activity Context to access application assets. This value cannot be null. */ - public InputAwareWebViewPlatformView(Context context, View containerView) { + public InputAwareWebViewPlatformView( + Context context, + BinaryMessenger binaryMessenger, + InstanceManager instanceManager, + View containerView) { super(context, containerView); + currentWebViewClient = new WebViewClient(); + currentWebChromeClient = + new WebChromeClientImpl( + new WebChromeClientFlutterApiImpl(binaryMessenger, instanceManager)); + + setWebViewClient(currentWebViewClient); + setWebChromeClient(currentWebChromeClient); } @Override @@ -242,56 +191,18 @@ public void onInputConnectionUnlocked() { @Override public void setWebViewClient(WebViewClient webViewClient) { super.setWebViewClient(webViewClient); - currentWebViewClient.set((ReleasableWebViewClient) webViewClient); - - final WebChromeClientImpl webChromeClient = currentWebChromeClient.get(); - if (webChromeClient != null) { - webChromeClient.setWebViewClient(webViewClient); - } - } - - @Override - public void setDownloadListener(DownloadListener listener) { - super.setDownloadListener(listener); - currentDownloadListener.set((DownloadListenerImpl) listener); + currentWebViewClient = webViewClient; + currentWebChromeClient.setWebViewClient(webViewClient); } @Override public void setWebChromeClient(WebChromeClient client) { super.setWebChromeClient(client); - currentWebChromeClient.set((WebChromeClientImpl) client); - } - - @SuppressLint("JavascriptInterface") - @Override - public void addJavascriptInterface(Object object, String name) { - super.addJavascriptInterface(object, name); - if (object instanceof JavaScriptChannel) { - final ReleasableValue javaScriptChannel = javaScriptInterfaces.get(name); - if (javaScriptChannel != null && javaScriptChannel.get() != object) { - javaScriptChannel.release(); - } - javaScriptInterfaces.put(name, new ReleasableValue<>((JavaScriptChannel) object)); - } - } - - @Override - public void removeJavascriptInterface(@NonNull String name) { - super.removeJavascriptInterface(name); - final ReleasableValue javaScriptChannel = javaScriptInterfaces.get(name); - javaScriptChannel.release(); - javaScriptInterfaces.remove(name); - } - - @Override - public void release() { - currentWebViewClient.release(); - currentDownloadListener.release(); - currentWebChromeClient.release(); - for (ReleasableValue channel : javaScriptInterfaces.values()) { - channel.release(); + if (!(client instanceof WebChromeClientImpl)) { + throw new AssertionError("Client must be a WebChromeClientImpl."); } - javaScriptInterfaces.clear(); + currentWebChromeClient = (WebChromeClientImpl) client; + currentWebChromeClient.setWebViewClient(currentWebViewClient); } } @@ -299,16 +210,19 @@ public void release() { * Creates a host API that handles creating {@link WebView}s and invoking its methods. * * @param instanceManager maintains instances stored to communicate with Dart objects + * @param binaryMessenger used to communicate with Dart over asynchronous messages * @param webViewProxy handles creating {@link WebView}s and calling its static methods * @param context an Activity Context to access application assets. This value cannot be null. * @param containerView parent of the webView */ public WebViewHostApiImpl( InstanceManager instanceManager, + BinaryMessenger binaryMessenger, WebViewProxy webViewProxy, Context context, @Nullable View containerView) { this.instanceManager = instanceManager; + this.binaryMessenger = binaryMessenger; this.webViewProxy = webViewProxy; this.context = context; this.containerView = containerView; @@ -332,22 +246,14 @@ public void create(Long instanceId, Boolean useHybridComposition) { final WebView webView = useHybridComposition - ? webViewProxy.createWebView(context) - : webViewProxy.createInputAwareWebView(context, containerView); + ? webViewProxy.createWebView(context, binaryMessenger, instanceManager) + : webViewProxy.createInputAwareWebView( + context, binaryMessenger, instanceManager, containerView); displayListenerProxy.onPostWebViewInitialization(displayManager); instanceManager.addDartCreatedInstance(webView, instanceId); } - @Override - public void dispose(Long instanceId) { - final WebView instance = (WebView) instanceManager.getInstance(instanceId); - if (instance != null) { - ((Releasable) instance).release(); - instanceManager.remove(instanceId); - } - } - @Override public void loadData(Long instanceId, String data, String mimeType, String encoding) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java index da25dace4517..caffbb9a95ef 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java @@ -6,11 +6,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; -import android.webkit.DownloadListener; import io.flutter.plugins.webviewflutter.DownloadListenerHostApiImpl.DownloadListenerCreator; import io.flutter.plugins.webviewflutter.DownloadListenerHostApiImpl.DownloadListenerImpl; import org.junit.After; @@ -67,11 +64,5 @@ public void postMessage() { eq("mimetype"), eq(54L), any()); - - reset(mockFlutterApi); - downloadListener.release(); - downloadListener.onDownloadStart("", "", "", "", 23); - verify(mockFlutterApi, never()) - .onDownloadStart((DownloadListener) any(), any(), any(), any(), any(), eq(23), any()); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java index 4bde211c6a4d..c9a5e64c0a3a 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java @@ -6,8 +6,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import android.os.Handler; @@ -62,10 +60,5 @@ public void tearDown() { public void postMessage() { javaScriptChannel.postMessage("A message post."); verify(mockFlutterApi).postMessage(eq(javaScriptChannel), eq("A message post."), any()); - - reset(mockFlutterApi); - javaScriptChannel.release(); - javaScriptChannel.postMessage("a message"); - verify(mockFlutterApi, never()).postMessage((JavaScriptChannel) any(), any(), any()); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java index 03d48d17df91..e821537eda97 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java @@ -10,13 +10,11 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.net.Uri; import android.os.Message; -import android.webkit.WebChromeClient; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebView.WebViewTransport; @@ -50,21 +48,20 @@ public void setUp() { instanceManager = InstanceManager.open(identifier -> {}); instanceManager.addDartCreatedInstance(mockWebView, 0L); - instanceManager.addDartCreatedInstance(mockWebViewClient, 1L); final WebChromeClientCreator webChromeClientCreator = new WebChromeClientCreator() { @Override public WebChromeClientImpl createWebChromeClient( - WebChromeClientFlutterApiImpl flutterApi, WebViewClient webViewClient) { - webChromeClient = super.createWebChromeClient(flutterApi, webViewClient); + WebChromeClientFlutterApiImpl flutterApi) { + webChromeClient = super.createWebChromeClient(flutterApi); return webChromeClient; } }; hostApiImpl = new WebChromeClientHostApiImpl(instanceManager, webChromeClientCreator, mockFlutterApi); - hostApiImpl.create(2L, 1L); + hostApiImpl.create(2L); } @After @@ -76,11 +73,6 @@ public void tearDown() { public void onProgressChanged() { webChromeClient.onProgressChanged(mockWebView, 23); verify(mockFlutterApi).onProgressChanged(eq(webChromeClient), eq(mockWebView), eq(23L), any()); - - reset(mockFlutterApi); - webChromeClient.release(); - webChromeClient.onProgressChanged(mockWebView, 11); - verify(mockFlutterApi, never()).onProgressChanged((WebChromeClient) any(), any(), any(), any()); } @Test @@ -91,6 +83,7 @@ public void onCreateWindow() { final Message message = new Message(); message.obj = mock(WebViewTransport.class); + webChromeClient.setWebViewClient(mockWebViewClient); assertTrue(webChromeClient.onCreateWindow(mockWebView, message, mockOnCreateWindowWebView)); /// Capture the WebViewClient used with onCreateWindow WebView. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java index 5d0cb701010e..3267291b2e99 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java @@ -8,8 +8,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -35,6 +33,8 @@ public class WebViewClientTest { @Mock public WebView mockWebView; + @Mock public WebViewClientCompatImpl mockWebViewClient; + InstanceManager instanceManager; WebViewClientHostApiImpl hostApiImpl; WebViewClientCompatImpl webViewClient; @@ -48,18 +48,15 @@ public void setUp() { final WebViewClientCreator webViewClientCreator = new WebViewClientCreator() { @Override - public WebViewClient createWebViewClient( - WebViewClientFlutterApiImpl flutterApi, boolean shouldOverrideUrlLoading) { - webViewClient = - (WebViewClientCompatImpl) - super.createWebViewClient(flutterApi, shouldOverrideUrlLoading); + public WebViewClient createWebViewClient(WebViewClientFlutterApiImpl flutterApi) { + webViewClient = (WebViewClientCompatImpl) super.createWebViewClient(flutterApi); return webViewClient; } }; hostApiImpl = new WebViewClientHostApiImpl(instanceManager, webViewClientCreator, mockFlutterApi); - hostApiImpl.create(1L, true); + hostApiImpl.create(1L); } @After @@ -72,11 +69,6 @@ public void onPageStarted() { webViewClient.onPageStarted(mockWebView, "https://www.google.com", null); verify(mockFlutterApi) .onPageStarted(eq(webViewClient), eq(mockWebView), eq("https://www.google.com"), any()); - - reset(mockFlutterApi); - webViewClient.release(); - webViewClient.onPageStarted(mockWebView, "", null); - verify(mockFlutterApi, never()).onPageStarted((WebViewClient) any(), any(), any(), any()); } @Test @@ -90,12 +82,6 @@ public void onReceivedError() { eq("description"), eq("https://www.google.com"), any()); - - reset(mockFlutterApi); - webViewClient.release(); - webViewClient.onReceivedError(mockWebView, 33, "", ""); - verify(mockFlutterApi, never()) - .onReceivedError((WebViewClient) any(), any(), any(), any(), any(), any()); } @Test @@ -103,11 +89,6 @@ public void urlLoading() { webViewClient.shouldOverrideUrlLoading(mockWebView, "https://www.google.com"); verify(mockFlutterApi) .urlLoading(eq(webViewClient), eq(mockWebView), eq("https://www.google.com"), any()); - - reset(mockFlutterApi); - webViewClient.release(); - webViewClient.shouldOverrideUrlLoading(mockWebView, ""); - verify(mockFlutterApi, never()).urlLoading((WebViewClient) any(), any(), any(), any()); } @Test @@ -125,4 +106,23 @@ public void convertWebResourceRequestWithNullHeaders() { WebViewClientFlutterApiImpl.createWebResourceRequestData(mockRequest); assertEquals(data.getRequestHeaders(), new HashMap()); } + + @Test + public void setReturnValueForShouldOverrideUrlLoading() { + final WebViewClientHostApiImpl webViewClientHostApi = + new WebViewClientHostApiImpl( + instanceManager, + new WebViewClientCreator() { + @Override + public WebViewClient createWebViewClient(WebViewClientFlutterApiImpl flutterApi) { + return mockWebViewClient; + } + }, + mockFlutterApi); + + instanceManager.addDartCreatedInstance(mockWebViewClient, 0); + webViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading(0L, false); + + verify(mockWebViewClient).setReturnValueForShouldOverrideUrlLoading(false); + } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java index 30bc256cd985..ecaab779c16a 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java @@ -15,10 +15,7 @@ import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebViewClient; -import io.flutter.plugins.webviewflutter.DownloadListenerHostApiImpl.DownloadListenerImpl; -import io.flutter.plugins.webviewflutter.WebChromeClientHostApiImpl.WebChromeClientImpl; -import io.flutter.plugins.webviewflutter.WebViewClientHostApiImpl.WebViewClientImpl; -import io.flutter.plugins.webviewflutter.WebViewHostApiImpl.InputAwareWebViewPlatformView; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.webviewflutter.WebViewHostApiImpl.WebViewPlatformView; import java.util.HashMap; import org.junit.After; @@ -39,6 +36,8 @@ public class WebViewTest { @Mock Context mockContext; + @Mock BinaryMessenger mockBinaryMessenger; + InstanceManager testInstanceManager; WebViewHostApiImpl testHostApiImpl; @@ -46,9 +45,11 @@ public class WebViewTest { public void setUp() { testInstanceManager = InstanceManager.open(identifier -> {}); - when(mockWebViewProxy.createWebView(mockContext)).thenReturn(mockWebView); + when(mockWebViewProxy.createWebView(mockContext, mockBinaryMessenger, testInstanceManager)) + .thenReturn(mockWebView); testHostApiImpl = - new WebViewHostApiImpl(testInstanceManager, mockWebViewProxy, mockContext, null); + new WebViewHostApiImpl( + testInstanceManager, mockBinaryMessenger, mockWebViewProxy, mockContext, null); testHostApiImpl.create(0L, true); } @@ -57,112 +58,6 @@ public void tearDown() { testInstanceManager.close(); } - @Test - public void releaseWebView() { - final WebViewPlatformView webView = new WebViewPlatformView(mockContext); - - final WebViewClientImpl mockWebViewClient = mock(WebViewClientImpl.class); - final WebChromeClientImpl mockWebChromeClient = mock(WebChromeClientImpl.class); - final DownloadListenerImpl mockDownloadListener = mock(DownloadListenerImpl.class); - final JavaScriptChannel mockJavaScriptChannel = mock(JavaScriptChannel.class); - - webView.setWebViewClient(mockWebViewClient); - webView.setWebChromeClient(mockWebChromeClient); - webView.setDownloadListener(mockDownloadListener); - webView.addJavascriptInterface(mockJavaScriptChannel, "jchannel"); - - webView.release(); - - verify(mockWebViewClient).release(); - verify(mockWebChromeClient).release(); - verify(mockDownloadListener).release(); - verify(mockJavaScriptChannel).release(); - } - - @Test - public void releaseWebViewDependents() { - final WebViewPlatformView webView = new WebViewPlatformView(mockContext); - - final WebViewClientImpl mockWebViewClient = mock(WebViewClientImpl.class); - final WebChromeClientImpl mockWebChromeClient = mock(WebChromeClientImpl.class); - final DownloadListenerImpl mockDownloadListener = mock(DownloadListenerImpl.class); - final JavaScriptChannel mockJavaScriptChannel = mock(JavaScriptChannel.class); - final JavaScriptChannel mockJavaScriptChannel2 = mock(JavaScriptChannel.class); - - webView.setWebViewClient(mockWebViewClient); - webView.setWebChromeClient(mockWebChromeClient); - webView.setDownloadListener(mockDownloadListener); - webView.addJavascriptInterface(mockJavaScriptChannel, "jchannel"); - - // Release should be called on the object added above. - webView.addJavascriptInterface(mockJavaScriptChannel2, "jchannel"); - verify(mockJavaScriptChannel).release(); - - webView.setWebViewClient(null); - webView.setWebChromeClient(null); - webView.setDownloadListener(null); - webView.removeJavascriptInterface("jchannel"); - - verify(mockWebViewClient).release(); - verify(mockWebChromeClient).release(); - verify(mockDownloadListener).release(); - verify(mockJavaScriptChannel2).release(); - } - - @Test - public void releaseInputAwareWebView() { - final InputAwareWebViewPlatformView webView = - new InputAwareWebViewPlatformView(mockContext, null); - - final WebViewClientImpl mockWebViewClient = mock(WebViewClientImpl.class); - final WebChromeClientImpl mockWebChromeClient = mock(WebChromeClientImpl.class); - final DownloadListenerImpl mockDownloadListener = mock(DownloadListenerImpl.class); - final JavaScriptChannel mockJavaScriptChannel = mock(JavaScriptChannel.class); - - webView.setWebViewClient(mockWebViewClient); - webView.setWebChromeClient(mockWebChromeClient); - webView.setDownloadListener(mockDownloadListener); - webView.addJavascriptInterface(mockJavaScriptChannel, "jchannel"); - - webView.release(); - - verify(mockWebViewClient).release(); - verify(mockWebChromeClient).release(); - verify(mockDownloadListener).release(); - verify(mockJavaScriptChannel).release(); - } - - @Test - public void releaseInputAwareWebViewDependents() { - final InputAwareWebViewPlatformView webView = - new InputAwareWebViewPlatformView(mockContext, null); - - final WebViewClientImpl mockWebViewClient = mock(WebViewClientImpl.class); - final WebChromeClientImpl mockWebChromeClient = mock(WebChromeClientImpl.class); - final DownloadListenerImpl mockDownloadListener = mock(DownloadListenerImpl.class); - final JavaScriptChannel mockJavaScriptChannel = mock(JavaScriptChannel.class); - final JavaScriptChannel mockJavaScriptChannel2 = mock(JavaScriptChannel.class); - - webView.setWebViewClient(mockWebViewClient); - webView.setWebChromeClient(mockWebChromeClient); - webView.setDownloadListener(mockDownloadListener); - webView.addJavascriptInterface(mockJavaScriptChannel, "jchannel"); - - // Release should be called on the object added above. - webView.addJavascriptInterface(mockJavaScriptChannel2, "jchannel"); - verify(mockJavaScriptChannel).release(); - - webView.setWebViewClient(null); - webView.setWebChromeClient(null); - webView.setDownloadListener(null); - webView.removeJavascriptInterface("jchannel"); - - verify(mockWebViewClient).release(); - verify(mockWebChromeClient).release(); - verify(mockDownloadListener).release(); - verify(mockJavaScriptChannel2).release(); - } - @Test public void loadData() { testHostApiImpl.loadData( diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/legacy/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/legacy/webview_flutter_test.dart new file mode 100644 index 000000000000..57d33998a0d2 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/legacy/webview_flutter_test.dart @@ -0,0 +1,1567 @@ +// 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_android/src/instance_manager.dart'; +import 'package:webview_flutter_android/src/weak_reference_utils.dart'; +import 'package:webview_flutter_android/src/webview_flutter_android_legacy.dart'; +import 'package:webview_flutter_android_example/legacy/navigation_decision.dart'; +import 'package:webview_flutter_android_example/legacy/navigation_request.dart'; +import 'package:webview_flutter_android_example/legacy/web_view.dart'; +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_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( + MaterialApp( + home: 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( + 'withWeakRefenceTo allows encapsulating class to be garbage collected', + (WidgetTester tester) async { + final Completer gcCompleter = Completer(); + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: gcCompleter.complete, + ); + + ClassWithCallbackClass? instance = ClassWithCallbackClass(); + instanceManager.addHostCreatedInstance(instance.callbackClass, 0); + instance = null; + + // Force garbage collection. + await IntegrationTestWidgetsFlutterBinding.instance + .watchPerformance(() async { + await tester.pumpAndSettle(); + }); + + final int gcIdentifier = await gcCompleter.future; + expect(gcIdentifier, 0); + }, timeout: const Timeout(Duration(seconds: 10))); + + 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; + 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)); + }); + }); + + 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', + 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; + + 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); + }); + }); + + group('SurfaceAndroidWebView', () { + setUpAll(() { + WebView.platform = SurfaceAndroidWebView(); + }); + + tearDownAll(() { + WebView.platform = AndroidWebView(); + }); + + 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)); + + // Check scrollTo() + const int X_SCROLL = 123; + const int Y_SCROLL = 321; + + await controller.scrollTo(X_SCROLL, Y_SCROLL); + int scrollPosX = await controller.getScrollX(); + int scrollPosY = await controller.getScrollY(); + expect(X_SCROLL, scrollPosX); + expect(Y_SCROLL, scrollPosY); + + // Check scrollBy() (on top of scrollTo()) + await controller.scrollBy(X_SCROLL, Y_SCROLL); + scrollPosX = await controller.getScrollX(); + scrollPosY = await controller.getScrollY(); + expect(X_SCROLL * 2, scrollPosX); + expect(Y_SCROLL * 2, scrollPosY); + }); + + testWidgets('inputs are scrolled into view when focused', + (WidgetTester tester) async { + const String scrollTestPage = ''' + + + + + + +
+ + + + '''; + + final String scrollTestPageBase64 = + base64Encode(const Utf8Encoder().convert(scrollTestPage)); + + final Completer pageLoaded = Completer(); + final Completer controllerCompleter = + Completer(); + + await tester.runAsync(() async { + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: SizedBox( + width: 200, + height: 200, + child: WebView( + initialUrl: + 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + javascriptMode: JavascriptMode.unrestricted, + ), + ), + ), + ); + await Future.delayed(const Duration(milliseconds: 20)); + await tester.pump(); + }); + + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + final String viewportRectJSON = await _runJavaScriptReturningResult( + controller, 'JSON.stringify(viewport.getBoundingClientRect())'); + final Map viewportRectRelativeToViewport = + jsonDecode(viewportRectJSON) as Map; + + // Check that the input is originally outside of the viewport. + + final String initialInputClientRectJSON = + await _runJavaScriptReturningResult( + controller, 'JSON.stringify(inputEl.getBoundingClientRect())'); + final Map initialInputClientRectRelativeToViewport = + jsonDecode(initialInputClientRectJSON) as Map; + + expect( + initialInputClientRectRelativeToViewport['bottom'] <= + viewportRectRelativeToViewport['bottom'], + isFalse); + + await controller.runJavascript('inputEl.focus()'); + + // Check that focusing the input brought it into view. + + final String lastInputClientRectJSON = + await _runJavaScriptReturningResult( + controller, 'JSON.stringify(inputEl.getBoundingClientRect())'); + final Map lastInputClientRectRelativeToViewport = + jsonDecode(lastInputClientRectJSON) as Map; + + expect( + lastInputClientRectRelativeToViewport['top'] >= + viewportRectRelativeToViewport['top'], + isTrue); + expect( + lastInputClientRectRelativeToViewport['bottom'] <= + viewportRectRelativeToViewport['bottom'], + isTrue); + + expect( + lastInputClientRectRelativeToViewport['left'] >= + viewportRectRelativeToViewport['left'], + isTrue); + expect( + lastInputClientRectRelativeToViewport['right'] <= + viewportRectRelativeToViewport['right'], + isTrue); + }); + }); + + 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); + + 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( + 'JavaScript does not run in parent window', + (WidgetTester tester) async { + const String iframe = ''' + + + '''; + final String iframeTestBase64 = + base64Encode(const Utf8Encoder().convert(iframe)); + + final String openWindowTest = ''' + + + + XSS test + + + + + + '''; + final String openWindowTestBase64 = + base64Encode(const Utf8Encoder().convert(openWindowTest)); + final Completer controllerCompleter = + Completer(); + final Completer pageLoadCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + initialUrl: + 'data:text/html;charset=utf-8;base64,$openWindowTestBase64', + onPageFinished: (String url) { + pageLoadCompleter.complete(); + }, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageLoadCompleter.future; + + final String iframeLoaded = + await controller.runJavascriptReturningResult('iframeLoaded'); + expect(iframeLoaded, 'true'); + + final String elementText = await controller.runJavascriptReturningResult( + 'document.querySelector("p") && document.querySelector("p").textContent', + ); + expect(elementText, 'null'); + }, + ); + + 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, '"Tom"'); + + await controller.clearCache(); + await pageLoadCompleter.future; + + final String nullItem = await controller.runJavascriptReturningResult( + 'localStorage.getItem("myCat");', + ); + expect(nullItem, 'null'); + }, + ); +} + +// 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'; +} + +/// 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 { + return jsonDecode(await controller.runJavascriptReturningResult(js)) + as String; +} + +class ResizableWebView extends StatefulWidget { + const ResizableWebView({ + Key? key, + required this.onResize, + required this.onPageFinished, + }) : super(key: key); + + 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'), + ), + ], + ), + ); + } +} + +class CopyableObjectWithCallback with Copyable { + CopyableObjectWithCallback(this.callback); + + final VoidCallback callback; + + @override + CopyableObjectWithCallback copy() { + return CopyableObjectWithCallback(callback); + } +} + +class ClassWithCallbackClass { + ClassWithCallbackClass() { + callbackClass = CopyableObjectWithCallback( + withWeakRefenceTo( + this, + (WeakReference weakReference) { + return () { + // Weak reference to `this` in callback. + // ignore: unnecessary_statements + weakReference; + }; + }, + ), + ); + } + + late final CopyableObjectWithCallback callbackClass; +} diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index 69c1a46d750f..2265aeb4229c 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart @@ -18,11 +18,9 @@ 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_android/webview_android.dart'; -import 'package:webview_flutter_android/webview_surface_android.dart'; -import 'package:webview_flutter_android_example/navigation_decision.dart'; -import 'package:webview_flutter_android_example/navigation_request.dart'; -import 'package:webview_flutter_android_example/web_view.dart'; +import 'package:webview_flutter_android/src/instance_manager.dart'; +import 'package:webview_flutter_android/src/weak_reference_utils.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; Future main() async { @@ -48,164 +46,169 @@ 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(); + testWidgets('loadRequest', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageFinished.complete()), + ) + ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + await tester.pumpWidget( - MaterialApp( - home: Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageFinished: pageFinishedCompleter.complete, - ), - ), + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, ), ); - final WebViewController controller = await controllerCompleter.future; - await pageFinishedCompleter.future; + 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(); + testWidgets( + 'withWeakRefenceTo allows encapsulating class to be garbage collected', + (WidgetTester tester) async { + final Completer gcCompleter = Completer(); + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: gcCompleter.complete, + ); + + ClassWithCallbackClass? instance = ClassWithCallbackClass(); + instanceManager.addHostCreatedInstance(instance.callbackClass, 0); + instance = null; + + // Force garbage collection. + await IntegrationTestWidgetsFlutterBinding.instance + .watchPerformance(() async { + await tester.pumpAndSettle(); + }); + + final int gcIdentifier = await gcCompleter.future; + expect(gcIdentifier, 0); + }, timeout: const Timeout(Duration(seconds: 10))); + + testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageFinished.complete()), + ) + ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + 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); - }, - ), + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, ), ); - final WebViewController controller = await controllerCompleter.future; - await controller.loadUrl(secondaryUrl); + await pageFinished.future; + await expectLater( - pageLoads.stream.firstWhere((String url) => url == secondaryUrl), - completion(secondaryUrl), + controller.runJavaScriptReturningResult('1 + 1'), + completion(2), ); }); - 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; - final String result = await controller.evaluateJavascript('1 + 1'); - expect(result, equals('2')); - }); + testWidgets('loadRequest with headers', (WidgetTester tester) async { + final Map headers = { + 'test_header': 'flutter_test_header' + }; - 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 PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((String url) => pageLoads.add(url)), + ) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse(headersUrl), + headers: headers, ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, ), ); - 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); + await pageLoads.stream.firstWhere((String url) => url == headersUrl); - final String content = await controller - .runJavascriptReturningResult('document.documentElement.innerText'); + 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 PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageFinished.complete()), + ); + final Completer channelCompleter = Completer(); + await controller.addJavaScriptChannel( + JavaScriptChannelParams( + name: 'Echo', + onMessageReceived: (JavaScriptMessage message) { + channelCompleter.complete(message.message); + }, + ), + ); + + await controller.loadHtmlString( + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + ); + 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); - }, - ), + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, ), ); - final WebViewController controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - expect(channelCompleter.isCompleted, isFalse); - await controller.runJavascript('Echo.postMessage("hello");'); + await pageFinished.future; + await controller.runJavaScript('Echo.postMessage("hello");'); await expectLater(channelCompleter.future, completion('hello')); }); @@ -216,7 +219,7 @@ Future main() async { bool resizeButtonTapped = false; await tester.pumpWidget(ResizableWebView( - onResize: (_) { + onResize: () { if (resizeButtonTapped) { buttonTapResizeCompleter.complete(); } else { @@ -225,6 +228,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( @@ -233,98 +237,42 @@ 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. + final Completer pageFinished = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageFinished.complete()), + ) + ..setUserAgent('Custom_User_Agent1') + ..loadRequest(LoadRequestParams(uri: Uri.parse('about:blank'))); + await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent2', - ), + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, ), ); - final String customUserAgent2 = await _getUserAgent(controller1); - expect(customUserAgent2, 'Custom_User_Agent2'); - }); + await pageFinished.future; - 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); + expect(customUserAgent, 'Custom_User_Agent1'); }); group('Video playback policy', () { @@ -335,201 +283,160 @@ Future main() async { final String base64VideoData = base64Encode(Uint8List.view(videoData.buffer)); final String videoTest = ''' - - Video auto play - - - - - - - '''; + + 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, + PlatformWebViewController controller = AndroidWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + AndroidNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageLoaded.complete()), + ) + ..setMediaPlaybackRequiresUserGesture(false) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + ), ), - ), - ); - 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); - }, - ), + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, ), ); - 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)); + bool isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, false); pageLoaded = Completer(); + controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageLoaded.complete()), + ) + ..loadRequest( + LoadRequestParams( + uri: 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); - }, - ), + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, ), ); - await controller.reload(); - 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); + final PlatformWebViewController controller = AndroidWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + AndroidNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageLoaded.complete()), + ) + ..addJavaScriptChannel( + JavaScriptChannelParams( + 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); + } }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - allowsInlineMediaPlayback: true, ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; + ) + ..setMediaPlaybackRequiresUserGesture(false) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + ), + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); - // Pump once to trigger the video play. - await tester.pump(); + 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); }); }); @@ -565,138 +472,75 @@ 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, + PlatformWebViewController controller = AndroidWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + AndroidNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageLoaded.complete()), + ) + ..setMediaPlaybackRequiresUserGesture(false) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$audioTestBase64', + ), ), - ), - ); - 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); - }, - ), + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, ), ); - 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(); + bool isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, false); - 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(); + controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageLoaded.complete()), + ) + ..loadRequest( + LoadRequestParams( + uri: 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); - }, - ), + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, ), ); - await controller.reload(); - - 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); }); }); @@ -711,33 +555,43 @@ 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', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageLoaded.complete()), + ) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', + ), ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, ), ); - 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'); }); @@ -769,32 +623,36 @@ 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 PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageLoaded.complete()), + ) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', + ), ), - ), - ); + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); - 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; @@ -802,206 +660,19 @@ 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); - }); - }); - - group('SurfaceAndroidWebView', () { - setUpAll(() { - WebView.platform = SurfaceAndroidWebView(); - }); - - tearDownAll(() { - WebView.platform = AndroidWebView(); - }); - - 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)); - - // Check scrollTo() - const int X_SCROLL = 123; - const int Y_SCROLL = 321; - - await controller.scrollTo(X_SCROLL, Y_SCROLL); - int scrollPosX = await controller.getScrollX(); - int scrollPosY = await controller.getScrollY(); - expect(X_SCROLL, scrollPosX); - expect(Y_SCROLL, scrollPosY); - - // Check scrollBy() (on top of scrollTo()) - await controller.scrollBy(X_SCROLL, Y_SCROLL); - scrollPosX = await controller.getScrollX(); - scrollPosY = await controller.getScrollY(); - expect(X_SCROLL * 2, scrollPosX); - expect(Y_SCROLL * 2, scrollPosY); - }); - - testWidgets('inputs are scrolled into view when focused', - (WidgetTester tester) async { - const String scrollTestPage = ''' - - - - - - -
- - - - '''; - - final String scrollTestPageBase64 = - base64Encode(const Utf8Encoder().convert(scrollTestPage)); - - final Completer pageLoaded = Completer(); - final Completer controllerCompleter = - Completer(); - - await tester.runAsync(() async { - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: SizedBox( - width: 200, - height: 200, - child: WebView( - initialUrl: - 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - javascriptMode: JavascriptMode.unrestricted, - ), - ), - ), - ); - await Future.delayed(const Duration(milliseconds: 20)); - await tester.pump(); - }); - - final WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - final String viewportRectJSON = await _runJavaScriptReturningResult( - controller, 'JSON.stringify(viewport.getBoundingClientRect())'); - final Map viewportRectRelativeToViewport = - jsonDecode(viewportRectJSON) as Map; - - // Check that the input is originally outside of the viewport. - - final String initialInputClientRectJSON = - await _runJavaScriptReturningResult( - controller, 'JSON.stringify(inputEl.getBoundingClientRect())'); - final Map initialInputClientRectRelativeToViewport = - jsonDecode(initialInputClientRectJSON) as Map; - - expect( - initialInputClientRectRelativeToViewport['bottom'] <= - viewportRectRelativeToViewport['bottom'], - isFalse); - - await controller.runJavascript('inputEl.focus()'); - - // Check that focusing the input brought it into view. - - final String lastInputClientRectJSON = - await _runJavaScriptReturningResult( - controller, 'JSON.stringify(inputEl.getBoundingClientRect())'); - final Map lastInputClientRectRelativeToViewport = - jsonDecode(lastInputClientRectJSON) as Map; - - expect( - lastInputClientRectRelativeToViewport['top'] >= - viewportRectRelativeToViewport['top'], - isTrue); - expect( - lastInputClientRectRelativeToViewport['bottom'] <= - viewportRectRelativeToViewport['bottom'], - isTrue); - - expect( - lastInputClientRectRelativeToViewport['left'] >= - viewportRectRelativeToViewport['left'], - isTrue); - expect( - lastInputClientRectRelativeToViewport['right'] <= - viewportRectRelativeToViewport['right'], - isTrue); + scrollPos = await controller.getScrollPosition(); + expect(scrollPos.dx, X_SCROLL * 2); + expect(scrollPos.dy, Y_SCROLL * 2); }); }); @@ -1011,33 +682,38 @@ Future main() async { '${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')) + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ) + ..setOnPageFinished((String url) => pageLoads.add(url)) + ..setOnNavigationRequest((NavigationRequest navigationRequest) { + return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); + }), + ) + ..loadRequest( + LoadRequestParams(uri: Uri.parse(blankPageEncoded)), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('location.href = "$secondaryUrl"'); + await controller.runJavaScript('location.href = "$secondaryUrl"'); await pageLoads.stream.first; // Wait for the next page load. final String? currentUrl = await controller.currentUrl(); @@ -1048,25 +724,39 @@ 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) { + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnWebResourceError((WebResourceError error) { errorCompleter.complete(error); - }, - ), - ), - ); + }), + ) + ..loadRequest( + LoadRequestParams(uri: Uri.parse('https://www.notawebsite..com')), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); final WebResourceError error = await errorCompleter.future; expect(error, isNotNull); expect(error.errorType, isNotNull); expect( - error.failingUrl?.startsWith('https://www.notawebsite..com'), isTrue); + (error as AndroidWebResourceError) + .failingUrl + ?.startsWith('https://www.notawebsite..com'), + isTrue, + ); }); testWidgets('onWebResourceError is not called with valid url', @@ -1075,95 +765,71 @@ 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) { + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ) + ..setOnPageFinished((_) => pageFinishCompleter.complete()) + ..setOnWebResourceError((WebResourceError error) { errorCompleter.complete(error); - }, - onPageFinished: (_) => pageFinishCompleter.complete(), + }), + ) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + ), ), - ), - ); + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); 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')) + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ) + ..setOnPageFinished((String url) => pageLoads.add(url)) + ..setOnNavigationRequest((NavigationRequest navigationRequest) { + return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); + }), + ) + ..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; 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 @@ -1175,35 +841,39 @@ Future main() async { }); 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 { + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ) + ..setOnPageFinished((String url) => pageLoads.add(url)) + ..setOnNavigationRequest( + (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), - ), - ), - ); + }), + ) + ..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('location.href = "$secondaryUrl"'); + await controller.runJavaScript('location.href = "$secondaryUrl"'); await pageLoads.stream.first; // Wait for second page to load. final String? currentUrl = await controller.currentUrl(); @@ -1211,54 +881,27 @@ Future main() async { }); }); - 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 PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate(PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageLoaded.complete())); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await controller.runJavaScript('window.open("$primaryUrl", "_blank")'); await pageLoaded.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); @@ -1267,31 +910,30 @@ 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 PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate(PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageLoaded.complete())) + ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + 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)); @@ -1336,105 +978,56 @@ Future main() async { '''; final String openWindowTestBase64 = base64Encode(const Utf8Encoder().convert(openWindowTest)); - final Completer controllerCompleter = - Completer(); + final Completer pageLoadCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - initialUrl: - 'data:text/html;charset=utf-8;base64,$openWindowTestBase64', - onPageFinished: (String url) { - pageLoadCompleter.complete(); - }, + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate(PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageLoadCompleter.complete())) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$openWindowTestBase64', + ), ), - ), - ); + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); - final WebViewController controller = await controllerCompleter.future; await pageLoadCompleter.future; - final String iframeLoaded = - await controller.runJavascriptReturningResult('iframeLoaded'); - expect(iframeLoaded, 'true'); + final bool iframeLoaded = + await controller.runJavaScriptReturningResult('iframeLoaded') as bool; + expect(iframeLoaded, true); - final String elementText = await controller.runJavascriptReturningResult( + final String elementText = await controller.runJavaScriptReturningResult( 'document.querySelector("p") && document.querySelector("p").textContent', - ); + ) as String; expect(elementText, 'null'); }, ); - - 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, '"Tom"'); - - await controller.clearCache(); - await pageLoadCompleter.future; - - final String nullItem = await controller.runJavascriptReturningResult( - 'localStorage.getItem("myCat");', - ); - expect(nullItem, 'null'); - }, - ); -} - -// 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'; } /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. -Future _getUserAgent(WebViewController controller) async { +Future _getUserAgent(PlatformWebViewController controller) async { return _runJavaScriptReturningResult(controller, 'navigator.userAgent;'); } Future _runJavaScriptReturningResult( - WebViewController controller, + PlatformWebViewController controller, String js, ) async { - return jsonDecode(await controller.runJavascriptReturningResult(js)) + return jsonDecode(await controller.runJavaScriptReturningResult(js) as String) as String; } @@ -1445,7 +1038,7 @@ class ResizableWebView extends StatefulWidget { required this.onPageFinished, }) : super(key: key); - final JavascriptMessageHandler onResize; + final VoidCallback onResize; final VoidCallback onPageFinished; @override @@ -1453,6 +1046,31 @@ class ResizableWebView extends StatefulWidget { } class ResizableWebViewState extends State { + late final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => widget.onPageFinished()), + ) + ..addJavaScriptChannel( + JavaScriptChannelParams( + name: 'Resize', + onMessageReceived: (_) { + widget.onResize(); + }, + ), + ) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,${base64Encode(const Utf8Encoder().convert(resizePage))}', + ), + ), + ); + double webViewWidth = 200; double webViewHeight = 200; @@ -1475,8 +1093,6 @@ class ResizableWebViewState extends State { @override Widget build(BuildContext context) { - final String resizeTestBase64 = - base64Encode(const Utf8Encoder().convert(resizePage)); return Directionality( textDirection: TextDirection.ltr, child: Column( @@ -1484,18 +1100,9 @@ class ResizableWebViewState extends State { 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, - ), + child: PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context), ), TextButton( key: const Key('resizeButton'), @@ -1512,3 +1119,33 @@ class ResizableWebViewState extends State { ); } } + +class CopyableObjectWithCallback with Copyable { + CopyableObjectWithCallback(this.callback); + + final VoidCallback callback; + + @override + CopyableObjectWithCallback copy() { + return CopyableObjectWithCallback(callback); + } +} + +class ClassWithCallbackClass { + ClassWithCallbackClass() { + callbackClass = CopyableObjectWithCallback( + withWeakRefenceTo( + this, + (WeakReference weakReference) { + return () { + // Weak reference to `this` in callback. + // ignore: unnecessary_statements + weakReference; + }; + }, + ), + ); + } + + late final CopyableObjectWithCallback callbackClass; +} diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/navigation_decision.dart b/packages/webview_flutter/webview_flutter_android/example/lib/legacy/navigation_decision.dart similarity index 100% rename from packages/webview_flutter/webview_flutter_android/example/lib/navigation_decision.dart rename to packages/webview_flutter/webview_flutter_android/example/lib/legacy/navigation_decision.dart diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/navigation_request.dart b/packages/webview_flutter/webview_flutter_android/example/lib/legacy/navigation_request.dart similarity index 100% rename from packages/webview_flutter/webview_flutter_android/example/lib/navigation_request.dart rename to packages/webview_flutter/webview_flutter_android/example/lib/legacy/navigation_request.dart diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_android/example/lib/legacy/web_view.dart similarity index 98% rename from packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart rename to packages/webview_flutter/webview_flutter_android/example/lib/legacy/web_view.dart index b7d98f28684a..3129968a04c4 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/legacy/web_view.dart @@ -8,9 +8,10 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:webview_flutter_android/webview_android.dart'; -import 'package:webview_flutter_android/webview_android_cookie_manager.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.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'; import 'navigation_decision.dart'; import 'navigation_request.dart'; @@ -319,8 +320,10 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { required bool isForMainFrame, }) async { if (url.startsWith('https://www.youtube.com/')) { + print('blocking navigation to $url'); return false; } + print('allowing navigation to $url'); return true; } diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart index bd958ed8998f..56a9a00c7ad0 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -2,33 +2,20 @@ // 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'; import 'dart:io'; import 'dart:typed_data'; + import 'package:flutter/material.dart'; -import 'package:flutter_driver/driver_extension.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:webview_flutter_android/webview_surface_android.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'navigation_decision.dart'; -import 'navigation_request.dart'; -import 'web_view.dart'; - -void appMain() { - enableFlutterDriverExtension(); - main(); -} - void main() { - // Configure the [WebView] to use the [SurfaceAndroidWebView] - // implementation instead of the default [AndroidWebView]. - WebView.platform = SurfaceAndroidWebView(); - - runApp(const MaterialApp(home: _WebViewExample())); + runApp(const MaterialApp(home: WebViewExample())); } const String kNavigationExamplePage = ''' @@ -46,7 +33,7 @@ The navigation delegate is set to block navigation to the youtube website. '''; -const String kExamplePage = ''' +const String kLocalExamplePage = ''' @@ -86,16 +73,70 @@ const String kTransparentBackgroundPage = ''' '''; -class _WebViewExample extends StatefulWidget { - const _WebViewExample({Key? key}) : super(key: key); +class WebViewExample extends StatefulWidget { + const WebViewExample({Key? key, this.cookieManager}) : super(key: key); + + final PlatformWebViewCookieManager? cookieManager; @override - _WebViewExampleState createState() => _WebViewExampleState(); + State createState() => _WebViewExampleState(); } -class _WebViewExampleState extends State<_WebViewExample> { - final Completer _controller = - Completer(); +class _WebViewExampleState extends State { + late final PlatformWebViewController _controller; + + @override + void initState() { + super.initState(); + + _controller = PlatformWebViewController( + AndroidWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x80000000)) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ) + ..setOnProgress((int progress) { + print('WebView is loading (progress : $progress%)'); + }) + ..setOnPageStarted((String url) { + print('Page started loading: $url'); + }) + ..setOnPageFinished((String url) { + print('Page finished loading: $url'); + }) + ..setOnWebResourceError((WebResourceError error) { + print(''' +Page resource error: + code: ${error.errorCode} + description: ${error.description} + errorType: ${error.errorType} + isForMainFrame: ${error.isForMainFrame} + '''); + }) + ..setOnNavigationRequest((NavigationRequest request) { + if (request.url.startsWith('https://www.youtube.com/')) { + print('blocking navigation to ${request.url}'); + return NavigationDecision.prevent; + } + print('allowing navigation to ${request.url}'); + return NavigationDecision.navigate; + }), + ) + ..addJavaScriptChannel(JavaScriptChannelParams( + name: 'Toaster', + onMessageReceived: (JavaScriptMessage message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message.message)), + ); + }, + )) + ..loadRequest(LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + )); + } @override Widget build(BuildContext context) { @@ -105,74 +146,34 @@ class _WebViewExampleState extends State<_WebViewExample> { 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), + NavigationControls(webViewController: _controller), + SampleMenu( + webViewController: _controller, + cookieManager: widget.cookieManager, + ), ], ), - body: WebView( - initialUrl: 'https://flutter.dev', - onWebViewCreated: (WebViewController controller) { - _controller.complete(controller); - }, - onProgress: (int progress) { - print('WebView is loading (progress : $progress%)'); - }, - navigationDelegate: (NavigationRequest request) { - if (request.url.startsWith('https://www.youtube.com/')) { - print('blocking navigation to $request}'); - return NavigationDecision.prevent; - } - print('allowing navigation to $request'); - return NavigationDecision.navigate; - }, - onPageStarted: (String url) { - print('Page started loading: $url'); - }, - onPageFinished: (String url) { - print('Page finished loading: $url'); - }, - javascriptChannels: _createJavascriptChannels(context), - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent', - backgroundColor: const Color(0x80000000), - ), + body: PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: _controller), + ).build(context), floatingActionButton: favoriteButton(), ); } Widget favoriteButton() { - return FutureBuilder( - future: _controller.future, - builder: (BuildContext context, - AsyncSnapshot controller) { - if (controller.hasData) { - return FloatingActionButton( - onPressed: () async { - final String url = (await controller.data!.currentUrl())!; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Favorited $url')), - ); - }, - child: const Icon(Icons.favorite), - ); - } - return Container(); - }); + return FloatingActionButton( + onPressed: () async { + final String? url = await _controller.currentUrl(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Favorited $url')), + ); + }, + child: const Icon(Icons.favorite), + ); } } -Set _createJavascriptChannels(BuildContext context) { - return { - JavascriptChannel( - name: 'Snackbar', - onMessageReceived: (JavascriptMessage message) { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text(message.message))); - }), - }; -} - -enum _MenuOptions { +enum MenuOptions { showUserAgent, listCookies, clearCookies, @@ -180,143 +181,144 @@ enum _MenuOptions { listCache, clearCache, navigationDelegate, - loadFlutterAsset, + doPostRequest, loadLocalFile, + loadFlutterAsset, loadHtmlString, transparentBackground, - doPostRequest, setCookie, } -class _SampleMenu extends StatelessWidget { - const _SampleMenu(this.controller); +class SampleMenu extends StatelessWidget { + SampleMenu({ + Key? key, + required this.webViewController, + PlatformWebViewCookieManager? cookieManager, + }) : cookieManager = cookieManager ?? + PlatformWebViewCookieManager( + const PlatformWebViewCookieManagerCreationParams(), + ), + super(key: key); - final Future controller; + final PlatformWebViewController webViewController; + late final PlatformWebViewCookieManager cookieManager; @override Widget build(BuildContext context) { - return FutureBuilder( - future: controller, - builder: - (BuildContext context, AsyncSnapshot controller) { - return PopupMenuButton<_MenuOptions>( - 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(controller.data!, 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.loadFlutterAsset: - _onLoadFlutterAssetExample(controller.data!, context); - break; - case _MenuOptions.loadLocalFile: - _onLoadLocalFileExample(controller.data!, context); - break; - case _MenuOptions.loadHtmlString: - _onLoadHtmlStringExample(controller.data!, context); - break; - case _MenuOptions.transparentBackground: - _onTransparentBackground(controller.data!, context); - break; - case _MenuOptions.doPostRequest: - _onDoPostRequest(controller.data!, context); - break; - case _MenuOptions.setCookie: - _onSetCookie(controller.data!, context); - break; - } - }, - itemBuilder: (BuildContext context) => >[ - PopupMenuItem<_MenuOptions>( - value: _MenuOptions.showUserAgent, - enabled: controller.hasData, - child: const Text('Show user agent'), - ), - const PopupMenuItem<_MenuOptions>( - value: _MenuOptions.listCookies, - child: Text('List cookies'), - ), - const PopupMenuItem<_MenuOptions>( - value: _MenuOptions.clearCookies, - child: Text('Clear cookies'), - ), - const PopupMenuItem<_MenuOptions>( - value: _MenuOptions.addToCache, - child: Text('Add to cache'), - ), - const PopupMenuItem<_MenuOptions>( - value: _MenuOptions.listCache, - child: Text('List cache'), - ), - const PopupMenuItem<_MenuOptions>( - value: _MenuOptions.clearCache, - child: Text('Clear cache'), - ), - const PopupMenuItem<_MenuOptions>( - value: _MenuOptions.navigationDelegate, - child: Text('Navigation Delegate example'), - ), - const PopupMenuItem<_MenuOptions>( - value: _MenuOptions.loadFlutterAsset, - child: Text('Load Flutter Asset'), - ), - const PopupMenuItem<_MenuOptions>( - value: _MenuOptions.loadHtmlString, - child: Text('Load HTML string'), - ), - const PopupMenuItem<_MenuOptions>( - value: _MenuOptions.loadLocalFile, - child: Text('Load local file'), - ), - const PopupMenuItem<_MenuOptions>( - key: ValueKey('ShowTransparentBackgroundExample'), - value: _MenuOptions.transparentBackground, - child: Text('Transparent background example'), - ), - const PopupMenuItem<_MenuOptions>( - value: _MenuOptions.doPostRequest, - child: Text('Post Request'), - ), - const PopupMenuItem<_MenuOptions>( - 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( + value: MenuOptions.setCookie, + child: Text('Set cookie'), + ), + const PopupMenuItem( + key: ValueKey('ShowTransparentBackgroundExample'), + value: MenuOptions.transparentBackground, + child: Text('Transparent background example'), + ), + ], ); } - Future _onShowUserAgent( - WebViewController controller, BuildContext context) async { - // Send a message with the user agent string to the Snackbar JavaScript channel we registered + Future _onShowUserAgent() { + // Send a message with the user agent string to the Toaster JavaScript channel we registered // with the WebView. - await controller.runJavascript( - 'Snackbar.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, @@ -329,34 +331,32 @@ 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) => Snackbar.postMessage(caches))'); + '.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.'), )); } - Future _onClearCookies( - WebViewController controller, BuildContext context) async { - final bool hadCookies = await WebViewCookieManager.instance.clearCookies(); + Future _onClearCookies(BuildContext context) async { + final bool hadCookies = await cookieManager.clearCookies(); String message = 'There were cookies. Now, they are gone!'; if (!hadCookies) { message = 'There are no cookies.'; @@ -366,47 +366,58 @@ class _SampleMenu extends StatelessWidget { )); } - Future _onSetCookie( - WebViewController controller, BuildContext context) async { - await WebViewCookieManager.instance.setCookie( - const WebViewCookie( - name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'), + Future _onNavigationDelegateExample() { + final String contentBase64 = base64Encode( + const Utf8Encoder().convert(kNavigationExamplePage), + ); + return webViewController.loadRequest( + LoadRequestParams( + uri: Uri.parse('data:text/html;base64,$contentBase64'), + ), ); - await controller.loadUrl('https://httpbin.org/anything'); } - Future _onNavigationDelegateExample( - WebViewController controller, BuildContext context) async { - final String contentBase64 = - base64Encode(const Utf8Encoder().convert(kNavigationExamplePage)); - await controller.loadUrl('data:text/html;base64,$contentBase64'); + Future _onSetCookie() async { + await cookieManager.setCookie( + const WebViewCookie( + name: 'foo', + value: 'bar', + domain: 'httpbin.org', + path: '/anything', + ), + ); + await webViewController.loadRequest(LoadRequestParams( + uri: Uri.parse('https://httpbin.org/anything'), + )); } - Future _onLoadFlutterAssetExample( - WebViewController controller, BuildContext context) async { - await controller.loadFlutterAsset('assets/www/index.html'); + Future _onDoPostRequest() { + return webViewController.loadRequest(LoadRequestParams( + uri: Uri.parse('https://httpbin.org/post'), + method: LoadRequestMethod.post, + headers: const { + 'foo': 'bar', + 'Content-Type': 'text/plain', + }, + body: Uint8List.fromList('Test Body'.codeUnits), + )); } - Future _onLoadLocalFileExample( - WebViewController controller, BuildContext context) async { + Future _onLoadLocalFileExample() async { final String pathToIndex = await _prepareLocalFile(); + await webViewController.loadFile(pathToIndex); + } - await controller.loadFile(pathToIndex); + Future _onLoadFlutterAssetExample() { + return webViewController.loadFlutterAsset('assets/www/index.html'); } - Future _onLoadHtmlStringExample( - WebViewController controller, BuildContext context) async { - await controller.loadHtmlString(kExamplePage); + Future _onLoadHtmlStringExample() { + return webViewController.loadHtmlString(kLocalExamplePage); } - Future _onDoPostRequest( - WebViewController controller, BuildContext context) async { - final WebViewRequest request = WebViewRequest( - uri: Uri.parse('https://httpbin.org/post'), - method: WebViewRequestMethod.post, - body: Uint8List.fromList('Test Body'.codeUnits), - ); - await controller.loadRequest(request); + Future _onTransparentBackground() { + return webViewController.loadHtmlString(kTransparentBackgroundPage); } Widget _getCookieList(String cookies) { @@ -425,83 +436,57 @@ class _SampleMenu extends StatelessWidget { static Future _prepareLocalFile() async { final String tmpDir = (await getTemporaryDirectory()).path; - final File indexFile = File('$tmpDir/www/index.html'); + final File indexFile = File( + {tmpDir, 'www', 'index.html'}.join(Platform.pathSeparator)); - await Directory('$tmpDir/www').create(recursive: true); - await indexFile.writeAsString(kExamplePage); + await indexFile.create(recursive: true); + await indexFile.writeAsString(kLocalExamplePage); return indexFile.path; } - - Future _onTransparentBackground( - WebViewController controller, BuildContext context) async { - await controller.loadHtmlString(kTransparentBackgroundPage); - } } -class _NavigationControls extends StatelessWidget { - const _NavigationControls(this._webViewControllerFuture) - : assert(_webViewControllerFuture != null); +class NavigationControls extends StatelessWidget { + const NavigationControls({Key? key, required this.webViewController}) + : super(key: key); - final Future _webViewControllerFuture; + final PlatformWebViewController 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(), + ), + ], ); } } - -/// Callback type for handling messages sent from JavaScript running in a web view. -typedef JavascriptMessageHandler = void Function(JavascriptMessage message); diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml index de6980a736cc..29c320a45cc9 100644 --- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml @@ -18,7 +18,8 @@ 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_platform_interface: ^1.8.0 + webview_flutter_platform_interface: + path: ../../webview_flutter_platform_interface dev_dependencies: espresso: ^0.2.0 diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_navigation_delegate.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_navigation_delegate.dart new file mode 100644 index 000000000000..51c62764fde4 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_navigation_delegate.dart @@ -0,0 +1,318 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'android_proxy.dart'; +import 'android_webview.dart' as android_webview; + +/// Signature for the `loadRequest` callback responsible for loading the [url] +/// after a navigation request has been approved. +typedef LoadRequestCallback = Future Function(LoadRequestParams params); + +/// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred. +@immutable +class AndroidWebResourceError extends WebResourceError { + /// Creates a new [AndroidWebResourceError]. + AndroidWebResourceError._({ + required super.errorCode, + required super.description, + super.isForMainFrame, + this.failingUrl, + }) : super( + errorType: _errorCodeToErrorType(errorCode), + ); + + /// Gets the URL for which the failing resource request was made. + final String? failingUrl; + + static WebResourceErrorType? _errorCodeToErrorType(int errorCode) { + switch (errorCode) { + case android_webview.WebViewClient.errorAuthentication: + return WebResourceErrorType.authentication; + case android_webview.WebViewClient.errorBadUrl: + return WebResourceErrorType.badUrl; + case android_webview.WebViewClient.errorConnect: + return WebResourceErrorType.connect; + case android_webview.WebViewClient.errorFailedSslHandshake: + return WebResourceErrorType.failedSslHandshake; + case android_webview.WebViewClient.errorFile: + return WebResourceErrorType.file; + case android_webview.WebViewClient.errorFileNotFound: + return WebResourceErrorType.fileNotFound; + case android_webview.WebViewClient.errorHostLookup: + return WebResourceErrorType.hostLookup; + case android_webview.WebViewClient.errorIO: + return WebResourceErrorType.io; + case android_webview.WebViewClient.errorProxyAuthentication: + return WebResourceErrorType.proxyAuthentication; + case android_webview.WebViewClient.errorRedirectLoop: + return WebResourceErrorType.redirectLoop; + case android_webview.WebViewClient.errorTimeout: + return WebResourceErrorType.timeout; + case android_webview.WebViewClient.errorTooManyRequests: + return WebResourceErrorType.tooManyRequests; + case android_webview.WebViewClient.errorUnknown: + return WebResourceErrorType.unknown; + case android_webview.WebViewClient.errorUnsafeResource: + return WebResourceErrorType.unsafeResource; + case android_webview.WebViewClient.errorUnsupportedAuthScheme: + return WebResourceErrorType.unsupportedAuthScheme; + case android_webview.WebViewClient.errorUnsupportedScheme: + return WebResourceErrorType.unsupportedScheme; + } + + throw ArgumentError( + 'Could not find a WebResourceErrorType for errorCode: $errorCode', + ); + } +} + +/// Object specifying creation parameters for creating a [AndroidNavigationDelegate]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformNavigationDelegateCreationParams] for +/// more information. +@immutable +class AndroidNavigationDelegateCreationParams + extends PlatformNavigationDelegateCreationParams { + /// Creates a new [AndroidNavigationDelegateCreationParams] instance. + const AndroidNavigationDelegateCreationParams._({ + @visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(), + }) : super(); + + /// Creates a [AndroidNavigationDelegateCreationParams] instance based on [PlatformNavigationDelegateCreationParams]. + factory AndroidNavigationDelegateCreationParams.fromPlatformNavigationDelegateCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformNavigationDelegateCreationParams params, { + @visibleForTesting + AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(), + }) { + return AndroidNavigationDelegateCreationParams._( + androidWebViewProxy: androidWebViewProxy, + ); + } + + /// Handles constructing objects and calling static methods for the Android WebView + /// native library. + @visibleForTesting + final AndroidWebViewProxy androidWebViewProxy; +} + +/// A place to register callback methods responsible to handle navigation events +/// triggered by the [android_webview.WebView]. +class AndroidNavigationDelegate extends PlatformNavigationDelegate { + /// Creates a new [AndroidNavigationkDelegate]. + AndroidNavigationDelegate(PlatformNavigationDelegateCreationParams params) + : super.implementation(params is AndroidNavigationDelegateCreationParams + ? params + : AndroidNavigationDelegateCreationParams + .fromPlatformNavigationDelegateCreationParams(params)) { + final WeakReference weakThis = + WeakReference(this); + + _webChromeClient = (this.params as AndroidNavigationDelegateCreationParams) + .androidWebViewProxy + .createAndroidWebChromeClient( + onProgressChanged: (android_webview.WebView webView, int progress) { + if (weakThis.target?._onProgress != null) { + weakThis.target!._onProgress!(progress); + } + }); + + _webViewClient = (this.params as AndroidNavigationDelegateCreationParams) + .androidWebViewProxy + .createAndroidWebViewClient( + onPageFinished: (android_webview.WebView webView, String url) { + if (weakThis.target?._onPageFinished != null) { + weakThis.target!._onPageFinished!(url); + } + }, + onPageStarted: (android_webview.WebView webView, String url) { + if (weakThis.target?._onPageStarted != null) { + weakThis.target!._onPageStarted!(url); + } + }, + onReceivedRequestError: ( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + ) { + if (weakThis.target?._onWebResourceError != null) { + weakThis.target!._onWebResourceError!(AndroidWebResourceError._( + errorCode: error.errorCode, + description: error.description, + failingUrl: request.url, + isForMainFrame: request.isForMainFrame, + )); + } + }, + onReceivedError: ( + android_webview.WebView webView, + int errorCode, + String description, + String failingUrl, + ) { + if (weakThis.target?._onWebResourceError != null) { + weakThis.target!._onWebResourceError!(AndroidWebResourceError._( + errorCode: errorCode, + description: description, + failingUrl: failingUrl, + isForMainFrame: true, + )); + } + }, + requestLoading: ( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + ) { + if (weakThis.target != null) { + weakThis.target!._handleNavigation( + request.url, + headers: request.requestHeaders, + isForMainFrame: request.isForMainFrame, + ); + } + }, + urlLoading: ( + android_webview.WebView webView, + String url, + ) { + if (weakThis.target != null) { + weakThis.target!._handleNavigation(url, isForMainFrame: true); + } + }, + ); + + _downloadListener = (this.params as AndroidNavigationDelegateCreationParams) + .androidWebViewProxy + .createDownloadListener( + onDownloadStart: ( + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) { + if (weakThis.target != null) { + weakThis.target?._handleNavigation(url, isForMainFrame: true); + } + }, + ); + } + + late final android_webview.WebChromeClient _webChromeClient; + + /// Gets the native [android_webview.WebChromeClient] that is bridged by this [AndroidNavigationDelegate]. + /// + /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebChromeClient`. + android_webview.WebChromeClient get androidWebChromeClient => + _webChromeClient; + + late final android_webview.WebViewClient _webViewClient; + + /// Gets the native [android_webview.WebViewClient] that is bridged by this [AndroidNavigationDelegate]. + /// + /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebViewClient`. + android_webview.WebViewClient get androidWebViewClient => _webViewClient; + + late final android_webview.DownloadListener _downloadListener; + + /// Gets the native [android_webview.DownloadListener] that is bridged by this [AndroidNavigationDelegate]. + /// + /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setDownloadListener`. + android_webview.DownloadListener get androidDownloadListener => + _downloadListener; + + PageEventCallback? _onPageFinished; + PageEventCallback? _onPageStarted; + ProgressCallback? _onProgress; + WebResourceErrorCallback? _onWebResourceError; + NavigationRequestCallback? _onNavigationRequest; + LoadRequestCallback? _onLoadRequest; + + void _handleNavigation( + String url, { + required bool isForMainFrame, + Map headers = const {}, + }) { + final LoadRequestCallback? onLoadRequest = _onLoadRequest; + final NavigationRequestCallback? onNavigationRequest = _onNavigationRequest; + + if (onNavigationRequest == null || onLoadRequest == null) { + return; + } + + final FutureOr returnValue = onNavigationRequest( + NavigationRequest( + url: url, + isMainFrame: isForMainFrame, + ), + ); + + if (returnValue is NavigationDecision && + returnValue == NavigationDecision.navigate) { + onLoadRequest(LoadRequestParams( + uri: Uri.parse(url), + headers: headers, + )); + } else if (returnValue is Future) { + returnValue.then((NavigationDecision shouldLoadUrl) { + if (shouldLoadUrl == NavigationDecision.navigate) { + onLoadRequest(LoadRequestParams( + uri: Uri.parse(url), + headers: headers, + )); + } + }); + } + } + + /// Invoked when loading the url after a navigation request is approved. + Future setOnLoadRequest( + LoadRequestCallback onLoadRequest, + ) async { + _onLoadRequest = onLoadRequest; + } + + @override + Future setOnNavigationRequest( + NavigationRequestCallback onNavigationRequest, + ) async { + _onNavigationRequest = onNavigationRequest; + _webViewClient.setSynchronousReturnValueForShouldOverrideUrlLoading(true); + } + + @override + Future setOnPageStarted( + PageEventCallback onPageStarted, + ) async { + _onPageStarted = onPageStarted; + } + + @override + Future setOnPageFinished( + PageEventCallback onPageFinished, + ) async { + _onPageFinished = onPageFinished; + } + + @override + Future setOnProgress( + ProgressCallback onProgress, + ) async { + _onProgress = onProgress; + } + + @override + Future setOnWebResourceError( + WebResourceErrorCallback onWebResourceError, + ) async { + _onWebResourceError = onWebResourceError; + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart new file mode 100644 index 000000000000..db247ee41d1c --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart @@ -0,0 +1,101 @@ +// 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 'android_webview.dart' as android_webview; + +/// Handles constructing objects and calling static methods for the Android +/// WebView native library. +/// +/// This class provides dependency injection for the implementations of the +/// platform interface classes. Improving the ease of unit testing and/or +/// overriding the underlying Android WebView classes. +/// +/// By default each function calls the default constructor of the WebView class +/// it intends to return. +class AndroidWebViewProxy { + /// Constructs a [AndroidWebViewProxy]. + const AndroidWebViewProxy({ + this.createAndroidWebView = android_webview.WebView.new, + this.createAndroidWebChromeClient = android_webview.WebChromeClient.new, + this.createAndroidWebViewClient = android_webview.WebViewClient.new, + this.createFlutterAssetManager = android_webview.FlutterAssetManager.new, + this.createJavaScriptChannel = android_webview.JavaScriptChannel.new, + this.createDownloadListener = android_webview.DownloadListener.new, + }); + + /// Constructs a [android_webview.WebView]. + /// + /// Due to changes in Flutter 3.0 the [useHybridComposition] doesn't have + /// any effect and should not be exposed publicly. More info here: + /// https://github.com/flutter/flutter/issues/108106 + final android_webview.WebView Function({ + required bool useHybridComposition, + }) createAndroidWebView; + + /// Constructs a [android_webview.WebChromeClient]. + final android_webview.WebChromeClient Function({ + void Function(android_webview.WebView webView, int progress)? + onProgressChanged, + }) createAndroidWebChromeClient; + + /// Constructs a [android_webview.WebViewClient]. + final android_webview.WebViewClient Function({ + void Function(android_webview.WebView webView, String url)? onPageStarted, + void Function(android_webview.WebView webView, String url)? onPageFinished, + void Function( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + )? + onReceivedRequestError, + @Deprecated('Only called on Android version < 23.') + void Function( + android_webview.WebView webView, + int errorCode, + String description, + String failingUrl, + )? + onReceivedError, + void Function( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + )? + requestLoading, + void Function(android_webview.WebView webView, String url)? urlLoading, + }) createAndroidWebViewClient; + + /// Constructs a [android_webview.FlutterAssetManager]. + final android_webview.FlutterAssetManager Function() + createFlutterAssetManager; + + /// Constructs a [android_webview.JavaScriptChannel]. + final android_webview.JavaScriptChannel Function( + String channelName, { + required void Function(String) postMessage, + }) createJavaScriptChannel; + + /// Constructs a [android_webview.DownloadListener]. + final android_webview.DownloadListener Function({ + required void Function( + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) + onDownloadStart, + }) createDownloadListener; + + /// Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this application. + /// + /// This flag can be enabled in order to facilitate debugging of web layouts + /// and JavaScript code running inside WebViews. Please refer to + /// [android_webview.WebView] documentation for the debugging guide. The + /// default is false. + /// + /// See [android_webview.WebView].setWebContentsDebuggingEnabled. + Future setWebContentsDebuggingEnabled(bool enabled) { + return android_webview.WebView.setWebContentsDebuggingEnabled(enabled); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index e08703106990..66f93dde1679 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -38,7 +38,9 @@ class JavaObject with Copyable { /// Global instance of [InstanceManager]. static final InstanceManager globalInstanceManager = InstanceManager( - onWeakReferenceRemoved: (_) {}, + onWeakReferenceRemoved: (int identifier) { + JavaObjectHostApiImpl().dispose(identifier); + }, ); /// Pigeon Host Api implementation for [JavaObject]. @@ -76,6 +78,10 @@ class JavaObject with Copyable { /// When a [WebView] is no longer needed [release] must be called. class WebView extends JavaObject { /// Constructs a new WebView. + /// + /// Due to changes in Flutter 3.0 the [useHybridComposition] doesn't have + /// any effect and should not be exposed publicly. More info here: + /// https://github.com/flutter/flutter/issues/108106 WebView({this.useHybridComposition = false}) : super.detached() { api.createFromInstance(this); } @@ -90,8 +96,6 @@ class WebView extends JavaObject { @visibleForTesting static WebViewHostApiImpl api = WebViewHostApiImpl(); - WebViewClient? _currentWebViewClient; - /// Whether the [WebView] will be rendered with an [AndroidViewSurface]. /// /// This implementation uses hybrid composition to render the WebView Widget. @@ -330,8 +334,6 @@ class WebView extends JavaObject { /// /// This will replace the current handler. Future setWebViewClient(WebViewClient webViewClient) { - _currentWebViewClient = webViewClient; - WebViewClient.api.createFromInstance(webViewClient); return api.setWebViewClientFromInstance(this, webViewClient); } @@ -375,11 +377,8 @@ class WebView extends JavaObject { /// Registers the interface to be used when content can not be handled by the rendering engine, and should be downloaded instead. /// /// This will replace the current handler. - Future setDownloadListener(DownloadListener? listener) async { - await Future.wait(>[ - if (listener != null) DownloadListener.api.createFromInstance(listener), - api.setDownloadListenerFromInstance(this, listener) - ]); + Future setDownloadListener(DownloadListener? listener) { + return api.setDownloadListenerFromInstance(this, listener); } /// Sets the chrome handler. @@ -387,20 +386,8 @@ class WebView extends JavaObject { /// This is an implementation of [WebChromeClient] for use in handling /// JavaScript dialogs, favicons, titles, and the progress. This will replace /// the current handler. - Future setWebChromeClient(WebChromeClient? client) async { - // WebView requires a WebViewClient because of a bug fix that makes - // calls to WebViewClient.requestLoading/WebViewClient.urlLoading when a new - // window is opened. This is to make sure a url opened by `Window.open` has - // a secure url. - assert( - _currentWebViewClient != null, - "Can't set a WebChromeClient without setting a WebViewClient first.", - ); - await Future.wait(>[ - if (client != null) - WebChromeClient.api.createFromInstance(client, _currentWebViewClient!), - api.setWebChromeClientFromInstance(this, client), - ]); + Future setWebChromeClient(WebChromeClient? client) { + return api.setWebChromeClientFromInstance(this, client); } /// Sets the background color of this WebView. @@ -408,15 +395,6 @@ class WebView extends JavaObject { return api.setBackgroundColorFromInstance(this, color.value); } - /// Releases all resources used by the [WebView]. - /// - /// Any methods called after [release] will throw an exception. - Future release() { - _currentWebViewClient = null; - WebSettings.api.disposeFromInstance(settings); - return api.disposeFromInstance(this); - } - @override WebView copy() { return WebView.detached(useHybridComposition: useHybridComposition); @@ -624,9 +602,10 @@ class JavaScriptChannel extends JavaObject { /// Constructs a [JavaScriptChannel]. JavaScriptChannel( this.channelName, { - void Function(String message)? postMessage, + required this.postMessage, }) : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); + api.createFromInstance(this); } /// Constructs a [JavaScriptChannel] without creating the associated Java @@ -636,7 +615,7 @@ class JavaScriptChannel extends JavaObject { /// create copies. JavaScriptChannel.detached( this.channelName, { - void Function(String message)? postMessage, + required this.postMessage, }) : super.detached(); /// Pigeon Host Api implementation for [JavaScriptChannel]. @@ -647,7 +626,7 @@ class JavaScriptChannel extends JavaObject { final String channelName; /// Callback method when javaScript calls `postMessage` on the object instance passed. - void postMessage(String message) {} + final void Function(String message) postMessage; @override JavaScriptChannel copy() { @@ -659,26 +638,15 @@ class JavaScriptChannel extends JavaObject { class WebViewClient extends JavaObject { /// Constructs a [WebViewClient]. WebViewClient({ - this.shouldOverrideUrlLoading = true, - void Function(WebView webView, String url)? onPageStarted, - void Function(WebView webView, String url)? onPageFinished, - void Function( - WebView webView, - WebResourceRequest request, - WebResourceError error, - )? - onReceivedRequestError, - void Function( - WebView webView, - int errorCode, - String description, - String failingUrl, - )? - onReceivedError, - void Function(WebView webView, WebResourceRequest request)? requestLoading, - void Function(WebView webView, String url)? urlLoading, + this.onPageStarted, + this.onPageFinished, + this.onReceivedRequestError, + @Deprecated('Only called on Android version < 23.') this.onReceivedError, + this.requestLoading, + this.urlLoading, }) : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); + api.createFromInstance(this); } /// Constructs a [WebViewClient] without creating the associated Java object. @@ -686,24 +654,12 @@ class WebViewClient extends JavaObject { /// This should only be used by subclasses created by this library or to /// create copies. WebViewClient.detached({ - this.shouldOverrideUrlLoading = true, - void Function(WebView webView, String url)? onPageStarted, - void Function(WebView webView, String url)? onPageFinished, - void Function( - WebView webView, - WebResourceRequest request, - WebResourceError error, - )? - onReceivedRequestError, - void Function( - WebView webView, - int errorCode, - String description, - String failingUrl, - )? - onReceivedError, - void Function(WebView webView, WebResourceRequest request)? requestLoading, - void Function(WebView webView, String url)? urlLoading, + this.onPageStarted, + this.onPageFinished, + this.onReceivedRequestError, + @Deprecated('Only called on Android version < 23.') this.onReceivedError, + this.requestLoading, + this.urlLoading, }) : super.detached(); /// User authentication failed on server. @@ -790,20 +746,6 @@ class WebViewClient extends JavaObject { @visibleForTesting static WebViewClientHostApiImpl api = WebViewClientHostApiImpl(); - /// Whether loading a url should be overridden. - /// - /// In Java, `shouldOverrideUrlLoading()` and `shouldOverrideRequestLoading()` - /// callbacks must synchronously return a boolean. This sets the default - /// return value. - /// - /// Setting [shouldOverrideUrlLoading] to true causes the current [WebView] to - /// abort loading the URL, while returning false causes the [WebView] to - /// continue loading the URL as usual. [requestLoading] or [urlLoading] will - /// still be called either way. - /// - /// Defaults to true. - final bool shouldOverrideUrlLoading; - /// Notify the host application that a page has started loading. /// /// This method is called once for each main frame load so a page with iframes @@ -812,7 +754,7 @@ class WebViewClient extends JavaObject { /// embedded frame changes, i.e. clicking a link whose target is an iframe, it /// will also not be called for fragment navigations (navigations to /// #fragment_id). - void onPageStarted(WebView webView, String url) {} + final void Function(WebView webView, String url)? onPageStarted; // TODO(bparrishMines): Update documentation when WebView.postVisualStateCallback is added. /// Notify the host application that a page has finished loading. @@ -820,7 +762,7 @@ class WebViewClient extends JavaObject { /// This method is called only for main frame. Receiving an [onPageFinished] /// callback does not guarantee that the next frame drawn by WebView will /// reflect the state of the DOM at this point. - void onPageFinished(WebView webView, String url) {} + final void Function(WebView webView, String url)? onPageFinished; /// Report web resource loading error to the host application. /// @@ -829,48 +771,58 @@ class WebViewClient extends JavaObject { /// be called for any resource (iframe, image, etc.), not just for the main /// page. Thus, it is recommended to perform minimum required work in this /// callback. - void onReceivedRequestError( + final void Function( WebView webView, WebResourceRequest request, WebResourceError error, - ) {} + )? onReceivedRequestError; /// Report an error to the host application. /// /// These errors are unrecoverable (i.e. the main resource is unavailable). /// The errorCode parameter corresponds to one of the error* constants. @Deprecated('Only called on Android version < 23.') - void onReceivedError( + final void Function( WebView webView, int errorCode, String description, String failingUrl, - ) {} + )? onReceivedError; - // TODO(bparrishMines): Update documentation once synchronous url handling is supported. - /// When a URL is about to be loaded in the current [WebView]. + /// When the current [WebView] wants to load a URL. /// - /// If a [WebViewClient] is not provided, by default [WebView] will ask - /// Activity Manager to choose the proper handler for the URL. If a - /// [WebViewClient] is provided, setting [shouldOverrideUrlLoading] to true - /// causes the current [WebView] to abort loading the URL, while returning - /// false causes the [WebView] to continue loading the URL as usual. - void requestLoading(WebView webView, WebResourceRequest request) {} + /// The value set by [setSynchronousReturnValueForShouldOverrideUrlLoading] + /// indicates whether the [WebView] loaded the request. + final void Function(WebView webView, WebResourceRequest request)? + requestLoading; - // TODO(bparrishMines): Update documentation once synchronous url handling is supported. - /// When a URL is about to be loaded in the current [WebView]. + /// When the current [WebView] wants to load a URL. /// - /// If a [WebViewClient] is not provided, by default [WebView] will ask - /// Activity Manager to choose the proper handler for the URL. If a - /// [WebViewClient] is provided, setting [shouldOverrideUrlLoading] to true - /// causes the current [WebView] to abort loading the URL, while returning - /// false causes the [WebView] to continue loading the URL as usual. - void urlLoading(WebView webView, String url) {} + /// The value set by [setSynchronousReturnValueForShouldOverrideUrlLoading] + /// indicates whether the [WebView] loaded the URL. + final void Function(WebView webView, String url)? urlLoading; + + /// Sets the required synchronous return value for the Java method, + /// `WebViewClient.shouldOverrideUrlLoading(...)`. + /// + /// The Java method, `WebViewClient.shouldOverrideUrlLoading(...)`, requires + /// a boolean to be returned and this method sets the returned value for all + /// calls to the Java method. + /// + /// Setting this to true causes the current [WebView] to abort loading any URL + /// received by [requestLoading] or [urlLoading], while setting this to false + /// causes the [WebView] to continue loading a URL as usual. + /// + /// Defaults to false. + Future setSynchronousReturnValueForShouldOverrideUrlLoading( + bool value, + ) { + return api.setShouldOverrideUrlLoadingReturnValueFromInstance(this, value); + } @override WebViewClient copy() { return WebViewClient.detached( - shouldOverrideUrlLoading: shouldOverrideUrlLoading, onPageStarted: onPageStarted, onPageFinished: onPageFinished, onReceivedRequestError: onReceivedRequestError, @@ -885,17 +837,9 @@ class WebViewClient extends JavaObject { /// engine for [WebView], and should be downloaded instead. class DownloadListener extends JavaObject { /// Constructs a [DownloadListener]. - DownloadListener({ - void Function( - String url, - String userAgent, - String contentDisposition, - String mimetype, - int contentLength, - )? - onDownloadStart, - }) : super.detached() { + DownloadListener({required this.onDownloadStart}) : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); + api.createFromInstance(this); } /// Constructs a [DownloadListener] without creating the associated Java @@ -903,43 +847,33 @@ class DownloadListener extends JavaObject { /// /// This should only be used by subclasses created by this library or to /// create copies. - DownloadListener.detached({ - void Function( - String url, - String userAgent, - String contentDisposition, - String mimetype, - int contentLength, - )? - onDownloadStart, - }) : super.detached(); + DownloadListener.detached({required this.onDownloadStart}) : super.detached(); /// Pigeon Host Api implementation for [DownloadListener]. @visibleForTesting static DownloadListenerHostApiImpl api = DownloadListenerHostApiImpl(); /// Notify the host application that a file should be downloaded. - void onDownloadStart( + final void Function( String url, String userAgent, String contentDisposition, String mimetype, int contentLength, - ) {} + ) onDownloadStart; @override DownloadListener copy() { - return DownloadListener(onDownloadStart: onDownloadStart); + return DownloadListener.detached(onDownloadStart: onDownloadStart); } } /// Handles JavaScript dialogs, favicons, titles, and the progress for [WebView]. class WebChromeClient extends JavaObject { /// Constructs a [WebChromeClient]. - WebChromeClient({ - void Function(WebView webView, int progress)? onProgressChanged, - }) : super.detached() { + WebChromeClient({this.onProgressChanged}) : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); + api.createFromInstance(this); } /// Constructs a [WebChromeClient] without creating the associated Java @@ -947,16 +881,14 @@ class WebChromeClient extends JavaObject { /// /// This should only be used by subclasses created by this library or to /// create copies. - WebChromeClient.detached({ - void Function(WebView webView, int progress)? onProgressChanged, - }) : super.detached(); + WebChromeClient.detached({this.onProgressChanged}) : super.detached(); /// Pigeon Host Api implementation for [WebChromeClient]. @visibleForTesting static WebChromeClientHostApiImpl api = WebChromeClientHostApiImpl(); /// Notify the host application that a file should be downloaded. - void onProgressChanged(WebView webView, int progress) {} + final void Function(WebView webView, int progress)? onProgressChanged; @override WebChromeClient copy() { diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart index e793dbb950a8..5bdab16d6720 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v4.0.2), do not edit directly. +// Autogenerated from Pigeon (v4.2.3), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import import 'dart:async'; @@ -102,20 +102,20 @@ class WebViewPoint { } } -class _JavaObjectHostApiCodec extends StandardMessageCodec { - const _JavaObjectHostApiCodec(); -} - +/// Handles methods calls to the native Java Object class. +/// +/// Also handles calls to remove the reference to an instance with `dispose`. +/// +/// See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. class JavaObjectHostApi { /// Constructor for [JavaObjectHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. JavaObjectHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _JavaObjectHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); Future dispose(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( @@ -142,12 +142,11 @@ class JavaObjectHostApi { } } -class _JavaObjectFlutterApiCodec extends StandardMessageCodec { - const _JavaObjectFlutterApiCodec(); -} - +/// Handles callbacks methods for the native Java Object class. +/// +/// See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. abstract class JavaObjectFlutterApi { - static const MessageCodec codec = _JavaObjectFlutterApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); void dispose(int identifier); static void setup(JavaObjectFlutterApi? api, @@ -174,20 +173,15 @@ abstract class JavaObjectFlutterApi { } } -class _CookieManagerHostApiCodec extends StandardMessageCodec { - const _CookieManagerHostApiCodec(); -} - class CookieManagerHostApi { /// Constructor for [CookieManagerHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. CookieManagerHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _CookieManagerHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); Future clearCookies() async { final BasicMessageChannel channel = BasicMessageChannel( @@ -273,7 +267,6 @@ class WebViewHostApi { /// BinaryMessenger will be used which routes to the host platform. WebViewHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _WebViewHostApiCodec(); @@ -303,30 +296,6 @@ class WebViewHostApi { } } - Future dispose(int arg_instanceId) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.WebViewHostApi.dispose', codec, - binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; - throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], - ); - } else { - return; - } - } - Future loadData(int arg_instanceId, String arg_data, String? arg_mimeType, String? arg_encoding) async { final BasicMessageChannel channel = BasicMessageChannel( @@ -987,20 +956,15 @@ class WebViewHostApi { } } -class _WebSettingsHostApiCodec extends StandardMessageCodec { - const _WebSettingsHostApiCodec(); -} - class WebSettingsHostApi { /// Constructor for [WebSettingsHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. WebSettingsHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _WebSettingsHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_instanceId, int arg_webViewInstanceId) async { final BasicMessageChannel channel = BasicMessageChannel( @@ -1027,30 +991,6 @@ class WebSettingsHostApi { } } - Future dispose(int arg_instanceId) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.WebSettingsHostApi.dispose', codec, - binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; - throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], - ); - } else { - return; - } - } - Future setDomStorageEnabled(int arg_instanceId, bool arg_flag) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled', codec, @@ -1352,20 +1292,15 @@ class WebSettingsHostApi { } } -class _JavaScriptChannelHostApiCodec extends StandardMessageCodec { - const _JavaScriptChannelHostApiCodec(); -} - class JavaScriptChannelHostApi { /// Constructor for [JavaScriptChannelHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. JavaScriptChannelHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _JavaScriptChannelHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_instanceId, String arg_channelName) async { final BasicMessageChannel channel = BasicMessageChannel( @@ -1393,37 +1328,12 @@ class JavaScriptChannelHostApi { } } -class _JavaScriptChannelFlutterApiCodec extends StandardMessageCodec { - const _JavaScriptChannelFlutterApiCodec(); -} - abstract class JavaScriptChannelFlutterApi { - static const MessageCodec codec = - _JavaScriptChannelFlutterApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); - void dispose(int instanceId); void postMessage(int instanceId, String message); static void setup(JavaScriptChannelFlutterApi? api, {BinaryMessenger? binaryMessenger}) { - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.JavaScriptChannelFlutterApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMessageHandler(null); - } else { - channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.JavaScriptChannelFlutterApi.dispose was null.'); - final List args = (message as List?)!; - final int? arg_instanceId = (args[0] as int?); - assert(arg_instanceId != null, - 'Argument for dev.flutter.pigeon.JavaScriptChannelFlutterApi.dispose was null, expected non-null int.'); - api.dispose(arg_instanceId!); - return; - }); - } - } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaScriptChannelFlutterApi.postMessage', codec, @@ -1449,29 +1359,48 @@ abstract class JavaScriptChannelFlutterApi { } } -class _WebViewClientHostApiCodec extends StandardMessageCodec { - const _WebViewClientHostApiCodec(); -} - class WebViewClientHostApi { /// Constructor for [WebViewClientHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. WebViewClientHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _WebViewClientHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); - Future create( - int arg_instanceId, bool arg_shouldOverrideUrlLoading) async { + Future create(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientHostApi.create', codec, binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_instanceId]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } + + Future setSynchronousReturnValueForShouldOverrideUrlLoading( + int arg_instanceId, bool arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading', + codec, + binaryMessenger: _binaryMessenger); final Map? replyMap = await channel - .send([arg_instanceId, arg_shouldOverrideUrlLoading]) - as Map?; + .send([arg_instanceId, arg_value]) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -1524,7 +1453,6 @@ class _WebViewClientFlutterApiCodec extends StandardMessageCodec { abstract class WebViewClientFlutterApi { static const MessageCodec codec = _WebViewClientFlutterApiCodec(); - void dispose(int instanceId); void onPageStarted(int instanceId, int webViewInstanceId, String url); void onPageFinished(int instanceId, int webViewInstanceId, String url); void onReceivedRequestError(int instanceId, int webViewInstanceId, @@ -1536,25 +1464,6 @@ abstract class WebViewClientFlutterApi { void urlLoading(int instanceId, int webViewInstanceId, String url); static void setup(WebViewClientFlutterApi? api, {BinaryMessenger? binaryMessenger}) { - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.WebViewClientFlutterApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMessageHandler(null); - } else { - channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.dispose was null.'); - final List args = (message as List?)!; - final int? arg_instanceId = (args[0] as int?); - assert(arg_instanceId != null, - 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.dispose was null, expected non-null int.'); - api.dispose(arg_instanceId!); - return; - }); - } - } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientFlutterApi.onPageStarted', codec, @@ -1724,20 +1633,15 @@ abstract class WebViewClientFlutterApi { } } -class _DownloadListenerHostApiCodec extends StandardMessageCodec { - const _DownloadListenerHostApiCodec(); -} - class DownloadListenerHostApi { /// Constructor for [DownloadListenerHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. DownloadListenerHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _DownloadListenerHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( @@ -1764,37 +1668,13 @@ class DownloadListenerHostApi { } } -class _DownloadListenerFlutterApiCodec extends StandardMessageCodec { - const _DownloadListenerFlutterApiCodec(); -} - abstract class DownloadListenerFlutterApi { - static const MessageCodec codec = _DownloadListenerFlutterApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); - void dispose(int instanceId); void onDownloadStart(int instanceId, String url, String userAgent, String contentDisposition, String mimetype, int contentLength); static void setup(DownloadListenerFlutterApi? api, {BinaryMessenger? binaryMessenger}) { - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.DownloadListenerFlutterApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMessageHandler(null); - } else { - channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.DownloadListenerFlutterApi.dispose was null.'); - final List args = (message as List?)!; - final int? arg_instanceId = (args[0] as int?); - assert(arg_instanceId != null, - 'Argument for dev.flutter.pigeon.DownloadListenerFlutterApi.dispose was null, expected non-null int.'); - api.dispose(arg_instanceId!); - return; - }); - } - } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.DownloadListenerFlutterApi.onDownloadStart', @@ -1834,29 +1714,22 @@ abstract class DownloadListenerFlutterApi { } } -class _WebChromeClientHostApiCodec extends StandardMessageCodec { - const _WebChromeClientHostApiCodec(); -} - class WebChromeClientHostApi { /// Constructor for [WebChromeClientHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. WebChromeClientHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _WebChromeClientHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); - Future create( - int arg_instanceId, int arg_webViewClientInstanceId) async { + Future create(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebChromeClientHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_webViewClientInstanceId]) - as Map?; + final Map? replyMap = + await channel.send([arg_instanceId]) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -1876,20 +1749,15 @@ class WebChromeClientHostApi { } } -class _FlutterAssetManagerHostApiCodec extends StandardMessageCodec { - const _FlutterAssetManagerHostApiCodec(); -} - class FlutterAssetManagerHostApi { /// Constructor for [FlutterAssetManagerHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. FlutterAssetManagerHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _FlutterAssetManagerHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); Future> list(String arg_path) async { final BasicMessageChannel channel = BasicMessageChannel( @@ -1951,36 +1819,12 @@ class FlutterAssetManagerHostApi { } } -class _WebChromeClientFlutterApiCodec extends StandardMessageCodec { - const _WebChromeClientFlutterApiCodec(); -} - abstract class WebChromeClientFlutterApi { - static const MessageCodec codec = _WebChromeClientFlutterApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); - void dispose(int instanceId); void onProgressChanged(int instanceId, int webViewInstanceId, int progress); static void setup(WebChromeClientFlutterApi? api, {BinaryMessenger? binaryMessenger}) { - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.WebChromeClientFlutterApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMessageHandler(null); - } else { - channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.dispose was null.'); - final List args = (message as List?)!; - final int? arg_instanceId = (args[0] as int?); - assert(arg_instanceId != null, - 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.dispose was null, expected non-null int.'); - api.dispose(arg_instanceId!); - return; - }); - } - } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebChromeClientFlutterApi.onProgressChanged', @@ -2011,20 +1855,15 @@ abstract class WebChromeClientFlutterApi { } } -class _WebStorageHostApiCodec extends StandardMessageCodec { - const _WebStorageHostApiCodec(); -} - class WebStorageHostApi { /// Constructor for [WebStorageHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. WebStorageHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = _WebStorageHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart index 4f2c51c4a13a..8aa5f7d9dab4 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart @@ -128,10 +128,9 @@ class JavaObjectFlutterApiImpl implements JavaObjectFlutterApi { class WebViewHostApiImpl extends WebViewHostApi { /// Constructs a [WebViewHostApiImpl]. WebViewHostApiImpl({ - BinaryMessenger? binaryMessenger, + super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, - super(binaryMessenger: binaryMessenger); + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; @@ -144,15 +143,6 @@ class WebViewHostApiImpl extends WebViewHostApi { ); } - /// Helper method to convert instances ids to objects. - Future disposeFromInstance(WebView instance) async { - final int? instanceId = instanceManager.getIdentifier(instance); - if (instanceId != null) { - instanceManager.remove(instanceId); - await dispose(instanceId); - } - } - /// Helper method to convert the instances ids to objects. Future loadDataFromInstance( WebView instance, @@ -351,10 +341,9 @@ class WebViewHostApiImpl extends WebViewHostApi { class WebSettingsHostApiImpl extends WebSettingsHostApi { /// Constructs a [WebSettingsHostApiImpl]. WebSettingsHostApiImpl({ - BinaryMessenger? binaryMessenger, + super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, - super(binaryMessenger: binaryMessenger); + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; @@ -367,15 +356,6 @@ class WebSettingsHostApiImpl extends WebSettingsHostApi { ); } - /// Helper method to convert instances ids to objects. - Future disposeFromInstance(WebSettings instance) async { - final int? instanceId = instanceManager.getIdentifier(instance); - if (instanceId != null) { - instanceManager.remove(instanceId); - return dispose(instanceId); - } - } - /// Helper method to convert instances ids to objects. Future setDomStorageEnabledFromInstance( WebSettings instance, @@ -502,10 +482,9 @@ class WebSettingsHostApiImpl extends WebSettingsHostApi { class JavaScriptChannelHostApiImpl extends JavaScriptChannelHostApi { /// Constructs a [JavaScriptChannelHostApiImpl]. JavaScriptChannelHostApiImpl({ - BinaryMessenger? binaryMessenger, + super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, - super(binaryMessenger: binaryMessenger); + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; @@ -531,11 +510,6 @@ class JavaScriptChannelFlutterApiImpl extends JavaScriptChannelFlutterApi { /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; - @override - void dispose(int instanceId) { - instanceManager.remove(instanceId); - } - @override void postMessage(int instanceId, String message) { final JavaScriptChannel? instance = instanceManager @@ -552,10 +526,9 @@ class JavaScriptChannelFlutterApiImpl extends JavaScriptChannelFlutterApi { class WebViewClientHostApiImpl extends WebViewClientHostApi { /// Constructs a [WebViewClientHostApiImpl]. WebViewClientHostApiImpl({ - BinaryMessenger? binaryMessenger, + super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, - super(binaryMessenger: binaryMessenger); + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; @@ -564,9 +537,20 @@ class WebViewClientHostApiImpl extends WebViewClientHostApi { Future createFromInstance(WebViewClient instance) async { if (instanceManager.getIdentifier(instance) == null) { final int identifier = instanceManager.addDartCreatedInstance(instance); - return create(identifier, instance.shouldOverrideUrlLoading); + return create(identifier); } } + + /// Helper method to convert instances ids to objects. + Future setShouldOverrideUrlLoadingReturnValueFromInstance( + WebViewClient instance, + bool value, + ) { + return setSynchronousReturnValueForShouldOverrideUrlLoading( + instanceManager.getIdentifier(instance)!, + value, + ); + } } /// Flutter api implementation for [WebViewClient]. @@ -578,11 +562,6 @@ class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; - @override - void dispose(int instanceId) { - instanceManager.remove(instanceId); - } - @override void onPageFinished(int instanceId, int webViewInstanceId, String url) { final WebViewClient? instance = instanceManager @@ -597,7 +576,9 @@ class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); - instance!.onPageFinished(webViewInstance!, url); + if (instance!.onPageFinished != null) { + instance.onPageFinished!(webViewInstance!, url); + } } @override @@ -614,7 +595,9 @@ class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); - instance!.onPageStarted(webViewInstance!, url); + if (instance!.onPageStarted != null) { + instance.onPageStarted!(webViewInstance!, url); + } } @override @@ -638,12 +621,14 @@ class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); // ignore: deprecated_member_use_from_same_package - instance!.onReceivedError( - webViewInstance!, - errorCode, - description, - failingUrl, - ); + if (instance!.onReceivedError != null) { + instance.onReceivedError!( + webViewInstance!, + errorCode, + description, + failingUrl, + ); + } } @override @@ -665,11 +650,13 @@ class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); - instance!.onReceivedRequestError( - webViewInstance!, - _toWebResourceRequest(request), - _toWebResourceError(error), - ); + if (instance!.onReceivedRequestError != null) { + instance.onReceivedRequestError!( + webViewInstance!, + _toWebResourceRequest(request), + _toWebResourceError(error), + ); + } } @override @@ -690,7 +677,12 @@ class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); - instance!.requestLoading(webViewInstance!, _toWebResourceRequest(request)); + if (instance!.requestLoading != null) { + instance.requestLoading!( + webViewInstance!, + _toWebResourceRequest(request), + ); + } } @override @@ -711,7 +703,9 @@ class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); - instance!.urlLoading(webViewInstance!, url); + if (instance!.urlLoading != null) { + instance.urlLoading!(webViewInstance!, url); + } } } @@ -719,10 +713,9 @@ class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { class DownloadListenerHostApiImpl extends DownloadListenerHostApi { /// Constructs a [DownloadListenerHostApiImpl]. DownloadListenerHostApiImpl({ - BinaryMessenger? binaryMessenger, + super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, - super(binaryMessenger: binaryMessenger); + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; @@ -745,11 +738,6 @@ class DownloadListenerFlutterApiImpl extends DownloadListenerFlutterApi { /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; - @override - void dispose(int instanceId) { - instanceManager.remove(instanceId); - } - @override void onDownloadStart( int instanceId, @@ -779,25 +767,18 @@ class DownloadListenerFlutterApiImpl extends DownloadListenerFlutterApi { class WebChromeClientHostApiImpl extends WebChromeClientHostApi { /// Constructs a [WebChromeClientHostApiImpl]. WebChromeClientHostApiImpl({ - BinaryMessenger? binaryMessenger, + super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, - super(binaryMessenger: binaryMessenger); + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; /// Helper method to convert instances ids to objects. - Future createFromInstance( - WebChromeClient instance, - WebViewClient webViewClient, - ) async { + Future createFromInstance(WebChromeClient instance) async { if (instanceManager.getIdentifier(instance) == null) { final int identifier = instanceManager.addDartCreatedInstance(instance); - return create( - identifier, - instanceManager.getIdentifier(webViewClient)!, - ); + return create(identifier); } } } @@ -811,11 +792,6 @@ class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; - @override - void dispose(int instanceId) { - instanceManager.remove(instanceId); - } - @override void onProgressChanged(int instanceId, int webViewInstanceId, int progress) { final WebChromeClient? instance = instanceManager @@ -830,7 +806,9 @@ class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); - instance!.onProgressChanged(webViewInstance!, progress); + if (instance!.onProgressChanged != null) { + instance.onProgressChanged!(webViewInstance!, progress); + } } } @@ -838,10 +816,9 @@ class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { class WebStorageHostApiImpl extends WebStorageHostApi { /// Constructs a [WebStorageHostApiImpl]. WebStorageHostApiImpl({ - BinaryMessenger? binaryMessenger, + super.binaryMessenger, InstanceManager? instanceManager, - }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, - super(binaryMessenger: binaryMessenger); + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart new file mode 100644 index 000000000000..2cb01d41976e --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -0,0 +1,440 @@ +// 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. + +// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) +// ignore: unnecessary_import +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'android_navigation_delegate.dart'; +import 'android_proxy.dart'; +import 'android_webview.dart' as android_webview; +import 'android_webview.dart'; +import 'instance_manager.dart'; +import 'weak_reference_utils.dart'; + +/// Object specifying creation parameters for creating a [AndroidWebViewController]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebViewControllerCreationParams] for +/// more information. +@immutable +class AndroidWebViewControllerCreationParams + extends PlatformWebViewControllerCreationParams { + /// Creates a new [AndroidWebViewControllerCreationParams] instance. + AndroidWebViewControllerCreationParams({ + @visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(), + @visibleForTesting android_webview.WebStorage? androidWebStorage, + }) : androidWebStorage = + androidWebStorage ?? android_webview.WebStorage.instance, + super(); + + /// Creates a [AndroidWebViewControllerCreationParams] instance based on [PlatformWebViewControllerCreationParams]. + factory AndroidWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebViewControllerCreationParams params, { + @visibleForTesting + AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(), + @visibleForTesting android_webview.WebStorage? androidWebStorage, + }) { + return AndroidWebViewControllerCreationParams( + androidWebViewProxy: androidWebViewProxy, + androidWebStorage: + androidWebStorage ?? android_webview.WebStorage.instance, + ); + } + + /// Handles constructing objects and calling static methods for the Android WebView + /// native library. + @visibleForTesting + final AndroidWebViewProxy androidWebViewProxy; + + /// Manages the JavaScript storage APIs provided by the [android_webview.WebView]. + @visibleForTesting + final android_webview.WebStorage androidWebStorage; +} + +/// Implementation of the [PlatformWebViewController] with the Android WebView API. +class AndroidWebViewController extends PlatformWebViewController { + /// Creates a new [AndroidWebViewCookieManager]. + AndroidWebViewController(PlatformWebViewControllerCreationParams params) + : super.implementation(params is AndroidWebViewControllerCreationParams + ? params + : AndroidWebViewControllerCreationParams + .fromPlatformWebViewControllerCreationParams(params)) { + _webView.settings.setDomStorageEnabled(true); + _webView.settings.setJavaScriptCanOpenWindowsAutomatically(true); + _webView.settings.setSupportMultipleWindows(true); + _webView.settings.setLoadWithOverviewMode(true); + _webView.settings.setUseWideViewPort(true); + _webView.settings.setDisplayZoomControls(false); + _webView.settings.setBuiltInZoomControls(true); + } + + AndroidWebViewControllerCreationParams get _androidWebViewParams => + params as AndroidWebViewControllerCreationParams; + + /// The native [android_webview.WebView] being controlled. + late final android_webview.WebView _webView = + _androidWebViewParams.androidWebViewProxy.createAndroidWebView( + // Due to changes in Flutter 3.0 the `useHybridComposition` doesn't have + // any effect and is purposefully not exposed publicly by the + // [AndroidWebViewController]. More info here: + // https://github.com/flutter/flutter/issues/108106 + useHybridComposition: true, + ); + + /// The native [android_webview.FlutterAssetManager] allows managing assets. + late final android_webview.FlutterAssetManager _flutterAssetManager = + _androidWebViewParams.androidWebViewProxy.createFlutterAssetManager(); + + final Map _javaScriptChannelParams = + {}; + + // The keeps a reference to the current NavigationDelegate so that the + // callback methods remain reachable. + // ignore: unused_field + late AndroidNavigationDelegate _currentNavigationDelegate; + + /// Whether to enable the platform's webview content debugging tools. + /// + /// Defaults to false. + static Future enableDebugging( + bool enabled, { + @visibleForTesting + AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(), + }) { + return webViewProxy.setWebContentsDebuggingEnabled(enabled); + } + + @override + Future loadFile( + String absoluteFilePath, + ) { + final String url = absoluteFilePath.startsWith('file://') + ? absoluteFilePath + : Uri.file(absoluteFilePath).toString(); + + _webView.settings.setAllowFileAccess(true); + return _webView.loadUrl(url, {}); + } + + @override + Future loadFlutterAsset( + String key, + ) async { + final String assetFilePath = + await _flutterAssetManager.getAssetFilePathByName(key); + final List pathElements = assetFilePath.split('/'); + final String fileName = pathElements.removeLast(); + final List paths = + await _flutterAssetManager.list(pathElements.join('/')); + + if (!paths.contains(fileName)) { + throw ArgumentError( + 'Asset for key "$key" not found.', + 'key', + ); + } + + return _webView.loadUrl( + Uri.file('/android_asset/$assetFilePath').toString(), + {}, + ); + } + + @override + Future loadHtmlString( + String html, { + String? baseUrl, + }) { + return _webView.loadDataWithBaseUrl( + baseUrl: baseUrl, + data: html, + mimeType: 'text/html', + ); + } + + @override + Future loadRequest( + LoadRequestParams params, + ) { + if (!params.uri.hasScheme) { + throw ArgumentError('WebViewRequest#uri is required to have a scheme.'); + } + switch (params.method) { + case LoadRequestMethod.get: + return _webView.loadUrl(params.uri.toString(), params.headers); + case LoadRequestMethod.post: + return _webView.postUrl( + params.uri.toString(), params.body ?? Uint8List(0)); + default: + throw UnimplementedError( + 'This version of `AndroidWebViewController` currently has no implementation for HTTP method ${params.method.serialize()} in loadRequest.', + ); + } + } + + @override + Future currentUrl() => _webView.getUrl(); + + @override + Future canGoBack() => _webView.canGoBack(); + + @override + Future canGoForward() => _webView.canGoForward(); + + @override + Future goBack() => _webView.goBack(); + + @override + Future goForward() => _webView.goForward(); + + @override + Future reload() => _webView.reload(); + + @override + Future clearCache() => _webView.clearCache(true); + + @override + Future clearLocalStorage() => + _androidWebViewParams.androidWebStorage.deleteAllData(); + + @override + Future setPlatformNavigationDelegate( + covariant AndroidNavigationDelegate handler) async { + _currentNavigationDelegate = handler; + handler.setOnLoadRequest(loadRequest); + _webView.setWebViewClient(handler.androidWebViewClient); + _webView.setWebChromeClient(handler.androidWebChromeClient); + _webView.setDownloadListener(handler.androidDownloadListener); + } + + @override + Future runJavaScript(String javaScript) { + return _webView.evaluateJavascript(javaScript); + } + + @override + Future runJavaScriptReturningResult(String javaScript) async { + final String? result = await _webView.evaluateJavascript(javaScript); + + if (result == null) { + return ''; + } else if (result == 'true') { + return true; + } else if (result == 'false') { + return false; + } + + return num.tryParse(result) ?? result; + } + + @override + Future addJavaScriptChannel( + JavaScriptChannelParams javaScriptChannelParams, + ) { + final AndroidJavaScriptChannelParams androidJavaScriptParams = + javaScriptChannelParams is AndroidJavaScriptChannelParams + ? javaScriptChannelParams + : AndroidJavaScriptChannelParams.fromJavaScriptChannelParams( + javaScriptChannelParams); + + // When JavaScript channel with the same name exists make sure to remove it + // before registering the new channel. + if (_javaScriptChannelParams.containsKey(androidJavaScriptParams.name)) { + _webView + .removeJavaScriptChannel(androidJavaScriptParams._javaScriptChannel); + } + + _javaScriptChannelParams[androidJavaScriptParams.name] = + androidJavaScriptParams; + + return _webView + .addJavaScriptChannel(androidJavaScriptParams._javaScriptChannel); + } + + @override + Future removeJavaScriptChannel(String javaScriptChannelName) async { + final AndroidJavaScriptChannelParams? javaScriptChannelParams = + _javaScriptChannelParams[javaScriptChannelName]; + if (javaScriptChannelParams == null) { + return; + } + + _javaScriptChannelParams.remove(javaScriptChannelName); + return _webView + .removeJavaScriptChannel(javaScriptChannelParams._javaScriptChannel); + } + + @override + Future getTitle() => _webView.getTitle(); + + @override + Future scrollTo(int x, int y) => _webView.scrollTo(x, y); + + @override + Future scrollBy(int x, int y) => _webView.scrollBy(x, y); + + @override + Future getScrollPosition() { + return _webView.getScrollPosition(); + } + + @override + Future enableZoom(bool enabled) => + _webView.settings.setSupportZoom(enabled); + + @override + Future setBackgroundColor(Color color) => + _webView.setBackgroundColor(color); + + @override + Future setJavaScriptMode(JavaScriptMode javaScriptMode) => + _webView.settings + .setJavaScriptEnabled(javaScriptMode == JavaScriptMode.unrestricted); + + @override + Future setUserAgent(String? userAgent) => + _webView.settings.setUserAgentString(userAgent); + + /// Sets the restrictions that apply on automatic media playback. + Future setMediaPlaybackRequiresUserGesture(bool require) { + return _webView.settings.setMediaPlaybackRequiresUserGesture(require); + } +} + +/// An implementation of [JavaScriptChannelParams] with the Android WebView API. +/// +/// See [AndroidWebViewController.addJavaScriptChannel]. +@immutable +class AndroidJavaScriptChannelParams extends JavaScriptChannelParams { + /// Constructs a [AndroidJavaScriptChannelParams]. + AndroidJavaScriptChannelParams({ + required super.name, + required super.onMessageReceived, + @visibleForTesting + AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(), + }) : assert(name.isNotEmpty), + _javaScriptChannel = webViewProxy.createJavaScriptChannel( + name, + postMessage: withWeakRefenceTo( + onMessageReceived, + (WeakReference weakReference) { + return ( + String message, + ) { + if (weakReference.target != null) { + weakReference.target!( + JavaScriptMessage(message: message), + ); + } + }; + }, + ), + ); + + /// Constructs a [AndroidJavaScriptChannelParams] using a + /// [JavaScriptChannelParams]. + AndroidJavaScriptChannelParams.fromJavaScriptChannelParams( + JavaScriptChannelParams params, { + @visibleForTesting + AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(), + }) : this( + name: params.name, + onMessageReceived: params.onMessageReceived, + webViewProxy: webViewProxy, + ); + + final android_webview.JavaScriptChannel _javaScriptChannel; +} + +/// Object specifying creation parameters for creating a [AndroidWebViewWidget]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebViewWidgetCreationParams] for +/// more information. +@immutable +class AndroidWebViewWidgetCreationParams + extends PlatformWebViewWidgetCreationParams { + /// Creates [AndroidWebWidgetCreationParams]. + AndroidWebViewWidgetCreationParams({ + super.key, + required super.controller, + super.layoutDirection, + super.gestureRecognizers, + @visibleForTesting InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Constructs a [WebKitWebViewWidgetCreationParams] using a + /// [PlatformWebViewWidgetCreationParams]. + AndroidWebViewWidgetCreationParams.fromPlatformWebViewWidgetCreationParams( + PlatformWebViewWidgetCreationParams params, { + InstanceManager? instanceManager, + }) : this( + key: params.key, + controller: params.controller, + layoutDirection: params.layoutDirection, + gestureRecognizers: params.gestureRecognizers, + instanceManager: instanceManager, + ); + + /// Maintains instances used to communicate with the native objects they + /// represent. + /// + /// This field is exposed for testing purposes only and should not be used + /// outside of tests. + @visibleForTesting + final InstanceManager instanceManager; +} + +/// An implementation of [PlatformWebViewWidget] with the Android WebView API. +class AndroidWebViewWidget extends PlatformWebViewWidget { + /// Constructs a [WebKitWebViewWidget]. + AndroidWebViewWidget(PlatformWebViewWidgetCreationParams params) + : super.implementation( + params is AndroidWebViewWidgetCreationParams + ? params + : AndroidWebViewWidgetCreationParams + .fromPlatformWebViewWidgetCreationParams(params), + ); + + AndroidWebViewWidgetCreationParams get _androidParams => + params as AndroidWebViewWidgetCreationParams; + + @override + Widget build(BuildContext context) { + return PlatformViewLink( + key: _androidParams.key, + viewType: 'plugins.flutter.io/webview', + surfaceFactory: ( + BuildContext context, + PlatformViewController controller, + ) { + return AndroidViewSurface( + controller: controller as AndroidViewController, + gestureRecognizers: _androidParams.gestureRecognizers, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + ); + }, + onCreatePlatformView: (PlatformViewCreationParams params) { + return PlatformViewsService.initSurfaceAndroidView( + id: params.id, + viewType: 'plugins.flutter.io/webview', + layoutDirection: _androidParams.layoutDirection, + creationParams: _androidParams.instanceManager.getIdentifier( + (_androidParams.controller as AndroidWebViewController) + ._webView), + creationParamsCodec: const StandardMessageCodec(), + ) + ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) + ..create(); + }); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_cookie_manager.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_cookie_manager.dart new file mode 100644 index 000000000000..5174ca576088 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_cookie_manager.dart @@ -0,0 +1,74 @@ +// 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/foundation.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'android_webview.dart'; + +/// Object specifying creation parameters for creating a [AndroidWebViewCookieManager]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebViewCookieManagerCreationParams] for +/// more information. +@immutable +class AndroidWebViewCookieManagerCreationParams + extends PlatformWebViewCookieManagerCreationParams { + /// Creates a new [AndroidWebViewCookieManagerCreationParams] instance. + const AndroidWebViewCookieManagerCreationParams._( + // This parameter prevents breaking changes later. + // ignore: avoid_unused_constructor_parameters + PlatformWebViewCookieManagerCreationParams params, + ) : super(); + + /// Creates a [AndroidWebViewCookieManagerCreationParams] instance based on [PlatformWebViewCookieManagerCreationParams]. + factory AndroidWebViewCookieManagerCreationParams.fromPlatformWebViewCookieManagerCreationParams( + PlatformWebViewCookieManagerCreationParams params) { + return AndroidWebViewCookieManagerCreationParams._(params); + } +} + +/// Handles all cookie operations for the Android platform. +class AndroidWebViewCookieManager extends PlatformWebViewCookieManager { + /// Creates a new [AndroidWebViewCookieManager]. + AndroidWebViewCookieManager( + PlatformWebViewCookieManagerCreationParams params, { + CookieManager? cookieManager, + }) : _cookieManager = cookieManager ?? CookieManager.instance, + super.implementation( + params is AndroidWebViewCookieManagerCreationParams + ? params + : AndroidWebViewCookieManagerCreationParams + .fromPlatformWebViewCookieManagerCreationParams(params), + ); + + final CookieManager _cookieManager; + + @override + Future clearCookies() { + return _cookieManager.clearCookies(); + } + + @override + Future setCookie(WebViewCookie cookie) { + if (!_isValidPath(cookie.path)) { + throw ArgumentError( + 'The path property for the provided cookie was not given a legal value.'); + } + return _cookieManager.setCookie( + cookie.domain, + '${Uri.encodeComponent(cookie.name)}=${Uri.encodeComponent(cookie.value)}; path=${cookie.path}', + ); + } + + bool _isValidPath(String path) { + // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 + for (final int char in path.codeUnits) { + if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) { + return false; + } + } + return true; + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart new file mode 100644 index 000000000000..24581ebd24dc --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart @@ -0,0 +1,45 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'android_navigation_delegate.dart'; +import 'android_webview_controller.dart'; +import 'android_webview_cookie_manager.dart'; + +/// Implementation of [WebViewPlatform] using the WebKit API. +class AndroidWebViewPlatform extends WebViewPlatform { + /// Registers this class as the default instance of [WebViewPlatform]. + static void registerWith() { + WebViewPlatform.instance = AndroidWebViewPlatform(); + } + + @override + AndroidWebViewController createPlatformWebViewController( + PlatformWebViewControllerCreationParams params, + ) { + return AndroidWebViewController(params); + } + + @override + AndroidNavigationDelegate createPlatformNavigationDelegate( + PlatformNavigationDelegateCreationParams params, + ) { + return AndroidNavigationDelegate(params); + } + + @override + AndroidWebViewWidget createPlatformWebViewWidget( + PlatformWebViewWidgetCreationParams params, + ) { + return AndroidWebViewWidget(params); + } + + @override + AndroidWebViewCookieManager createPlatformCookieManager( + PlatformWebViewCookieManagerCreationParams params, + ) { + return AndroidWebViewCookieManager(params); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android.dart similarity index 94% rename from packages/webview_flutter/webview_flutter_android/lib/webview_android.dart rename to packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android.dart index 47740247db73..cfda749fa4ab 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android.dart @@ -8,9 +8,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; -import 'src/android_webview.dart'; +import '../android_webview.dart'; import 'webview_android_widget.dart'; /// Builds an Android webview. diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_cookie_manager.dart similarity index 85% rename from packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart rename to packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_cookie_manager.dart index 6e3f6f28d8ef..663a2076b412 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_cookie_manager.dart @@ -2,9 +2,10 @@ // 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/webview_flutter_platform_interface.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; -import 'src/android_webview.dart' as android_webview; +import '../android_webview.dart' as android_webview; /// Handles all cookie operations for the current platform. class WebViewAndroidCookieManager extends WebViewCookieManagerPlatform { diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart similarity index 68% rename from packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart rename to packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart index 140d0da7e7f7..fc1028e7af15 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart @@ -6,16 +6,18 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/widgets.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; -import 'src/android_webview.dart' as android_webview; +import '../android_webview.dart' as android_webview; +import '../weak_reference_utils.dart'; import 'webview_android_cookie_manager.dart'; /// Creates a [Widget] with a [android_webview.WebView]. class WebViewAndroidWidget extends StatefulWidget { /// Constructs a [WebViewAndroidWidget]. const WebViewAndroidWidget({ - Key? key, + super.key, required this.creationParams, required this.useHybridComposition, required this.callbacksHandler, @@ -25,7 +27,7 @@ class WebViewAndroidWidget extends StatefulWidget { @visibleForTesting this.flutterAssetManager = const android_webview.FlutterAssetManager(), @visibleForTesting this.webStorage, - }) : super(key: key); + }); /// Initial parameters used to setup the WebView. final CreationParams creationParams; @@ -85,12 +87,6 @@ class _WebViewAndroidWidgetState extends State { ); } - @override - void dispose() { - super.dispose(); - controller._dispose(); - } - @override Widget build(BuildContext context) { return widget.onBuildWidget(controller); @@ -127,6 +123,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { _setCreationParams(creationParams); webView.setDownloadListener(downloadListener); webView.setWebChromeClient(webChromeClient); + webView.setWebViewClient(webViewClient); final String? initialUrl = creationParams.initialUrl; if (initialUrl != null) { @@ -137,7 +134,61 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { final Map _javaScriptChannels = {}; - late WebViewAndroidWebViewClient _webViewClient; + late final android_webview.WebViewClient _webViewClient = withWeakRefenceTo( + this, (WeakReference weakReference) { + return webViewProxy.createWebViewClient( + onPageStarted: (_, String url) { + weakReference.target?.callbacksHandler.onPageStarted(url); + }, + onPageFinished: (_, String url) { + weakReference.target?.callbacksHandler.onPageFinished(url); + }, + onReceivedError: ( + _, + int errorCode, + String description, + String failingUrl, + ) { + weakReference.target?.callbacksHandler + .onWebResourceError(WebResourceError( + errorCode: errorCode, + description: description, + failingUrl: failingUrl, + errorType: _errorCodeToErrorType(errorCode), + )); + }, + onReceivedRequestError: ( + _, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + ) { + if (request.isForMainFrame) { + weakReference.target?.callbacksHandler + .onWebResourceError(WebResourceError( + errorCode: error.errorCode, + description: error.description, + failingUrl: request.url, + errorType: _errorCodeToErrorType(error.errorCode), + )); + } + }, + urlLoading: (_, String url) { + weakReference.target?._handleNavigationRequest( + url: url, + isForMainFrame: true, + ); + }, + requestLoading: (_, android_webview.WebResourceRequest request) { + weakReference.target?._handleNavigationRequest( + url: request.url, + isForMainFrame: request.isForMainFrame, + ); + }, + ); + }); + + bool _hasNavigationDelegate = false; + bool _hasProgressTracking = false; /// Represents the WebView maintained by platform code. late final android_webview.WebView webView; @@ -160,20 +211,50 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { /// Receives callbacks when content should be downloaded instead. @visibleForTesting - late final WebViewAndroidDownloadListener downloadListener = - WebViewAndroidDownloadListener(loadUrl: loadUrl); + late final android_webview.DownloadListener downloadListener = + android_webview.DownloadListener( + onDownloadStart: withWeakRefenceTo( + this, + (WeakReference weakReference) { + return ( + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) { + weakReference.target?._handleNavigationRequest( + url: url, + isForMainFrame: true, + ); + }; + }, + ), + ); /// Handles JavaScript dialogs, favicons, titles, new windows, and the progress for [android_webview.WebView]. @visibleForTesting - late final WebViewAndroidWebChromeClient webChromeClient = - WebViewAndroidWebChromeClient(); + late final android_webview.WebChromeClient webChromeClient = + android_webview.WebChromeClient( + onProgressChanged: withWeakRefenceTo( + this, + (WeakReference weakReference) { + return (_, int progress) { + final WebViewAndroidPlatformController? controller = + weakReference.target; + if (controller != null && controller._hasProgressTracking) { + controller.callbacksHandler.onProgress(progress); + } + }; + }, + )); /// Manages the JavaScript storage APIs. final android_webview.WebStorage webStorage; /// Receive various notifications and requests for [android_webview.WebView]. @visibleForTesting - WebViewAndroidWebViewClient get webViewClient => _webViewClient; + android_webview.WebViewClient get webViewClient => _webViewClient; @override Future loadHtmlString(String html, {String? baseUrl}) { @@ -272,10 +353,9 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { @override Future updateSettings(WebSettings setting) async { + _hasProgressTracking = setting.hasProgressTracking ?? _hasProgressTracking; await Future.wait(>[ _setUserAgent(setting.userAgent), - if (setting.hasProgressTracking != null) - _setHasProgressTracking(setting.hasProgressTracking!), if (setting.hasNavigationDelegate != null) _setHasNavigationDelegate(setting.hasNavigationDelegate!), if (setting.javascriptMode != null) @@ -355,8 +435,6 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { @override Future getScrollY() => webView.getScrollY(); - Future _dispose() => webView.release(); - void _setCreationParams(CreationParams creationParams) { final WebSettings? webSettings = creationParams.webSettings; if (webSettings != null) { @@ -389,34 +467,11 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { .forEach(WebViewCookieManagerPlatform.instance!.setCookie); } - Future _setHasProgressTracking(bool hasProgressTracking) async { - if (hasProgressTracking) { - webChromeClient._onProgress = callbacksHandler.onProgress; - } else { - webChromeClient._onProgress = null; - } - } - Future _setHasNavigationDelegate(bool hasNavigationDelegate) { - if (hasNavigationDelegate) { - downloadListener._onNavigationRequest = - callbacksHandler.onNavigationRequest; - _webViewClient = WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: callbacksHandler.onPageStarted, - onPageFinishedCallback: callbacksHandler.onPageFinished, - onWebResourceErrorCallback: callbacksHandler.onWebResourceError, - loadUrl: loadUrl, - onNavigationRequestCallback: callbacksHandler.onNavigationRequest, - ); - } else { - downloadListener._onNavigationRequest = null; - _webViewClient = WebViewAndroidWebViewClient( - onPageStartedCallback: callbacksHandler.onPageStarted, - onPageFinishedCallback: callbacksHandler.onPageFinished, - onWebResourceErrorCallback: callbacksHandler.onWebResourceError, - ); - } - return webView.setWebViewClient(_webViewClient); + _hasNavigationDelegate = hasNavigationDelegate; + return _webViewClient.setSynchronousReturnValueForShouldOverrideUrlLoading( + hasNavigationDelegate, + ); } Future _setJavaScriptMode(JavascriptMode mode) { @@ -444,114 +499,6 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { Future _setZoomEnabled(bool zoomEnabled) { return webView.settings.setSupportZoom(zoomEnabled); } -} - -/// Exposes a channel to receive calls from javaScript. -class WebViewAndroidJavaScriptChannel - extends android_webview.JavaScriptChannel { - /// Creates a [WebViewAndroidJavaScriptChannel]. - WebViewAndroidJavaScriptChannel( - String channelName, this.javascriptChannelRegistry) - : super(channelName); - - /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. - final JavascriptChannelRegistry javascriptChannelRegistry; - - @override - void postMessage(String message) { - javascriptChannelRegistry.onJavascriptChannelMessage(channelName, message); - } -} - -/// Receives callbacks when content can not be handled by the rendering engine for [WebViewAndroidPlatformController], and should be downloaded instead. -/// -/// When handling navigation requests, this calls [onNavigationRequestCallback] -/// when a [android_webview.WebView] attempts to navigate to a new page. If -/// this callback return true, this calls [loadUrl]. -class WebViewAndroidDownloadListener extends android_webview.DownloadListener { - /// Creates a [WebViewAndroidDownloadListener]. - WebViewAndroidDownloadListener({required this.loadUrl}); - - // Changed by WebViewAndroidPlatformController. - FutureOr Function({ - required String url, - required bool isForMainFrame, - })? _onNavigationRequest; - - /// Callback to load a URL when a navigation request is approved. - final Future Function(String url, Map? headers) loadUrl; - - @override - void onDownloadStart( - String url, - String userAgent, - String contentDisposition, - String mimetype, - int contentLength, - ) { - if (_onNavigationRequest == null) { - return; - } - - final FutureOr returnValue = _onNavigationRequest!( - url: url, - isForMainFrame: true, - ); - - if (returnValue is bool && returnValue) { - loadUrl(url, {}); - } else { - (returnValue as Future).then((bool shouldLoadUrl) { - if (shouldLoadUrl) { - loadUrl(url, {}); - } - }); - } - } -} - -/// Receives various navigation requests and errors for [WebViewAndroidPlatformController]. -/// -/// When handling navigation requests, this calls [onNavigationRequestCallback] -/// when a [android_webview.WebView] attempts to navigate to a new page. If -/// this callback return true, this calls [loadUrl]. -class WebViewAndroidWebViewClient extends android_webview.WebViewClient { - /// Creates a [WebViewAndroidWebViewClient] that doesn't handle navigation requests. - WebViewAndroidWebViewClient({ - required this.onPageStartedCallback, - required this.onPageFinishedCallback, - required this.onWebResourceErrorCallback, - }) : loadUrl = null, - onNavigationRequestCallback = null, - super(shouldOverrideUrlLoading: false); - - /// Creates a [WebViewAndroidWebViewClient] that handles navigation requests. - WebViewAndroidWebViewClient.handlesNavigation({ - required this.onPageStartedCallback, - required this.onPageFinishedCallback, - required this.onWebResourceErrorCallback, - required this.onNavigationRequestCallback, - required this.loadUrl, - }) : super(shouldOverrideUrlLoading: true); - - /// Callback when [android_webview.WebViewClient] receives a callback from [android_webview.WebViewClient].onPageStarted. - final void Function(String url) onPageStartedCallback; - - /// Callback when [android_webview.WebViewClient] receives a callback from [android_webview.WebViewClient].onPageFinished. - final void Function(String url) onPageFinishedCallback; - - /// Callback when [android_webview.WebViewClient] receives an error callback. - void Function(WebResourceError error) onWebResourceErrorCallback; - - /// Checks whether a navigation request should be approved or disaproved. - final FutureOr Function({ - required String url, - required bool isForMainFrame, - })? onNavigationRequestCallback; - - /// Callback when a navigation request is approved. - final Future Function(String url, Map? headers)? - loadUrl; static WebResourceErrorType _errorCodeToErrorType(int errorCode) { switch (errorCode) { @@ -594,110 +541,54 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { ); } - /// Whether this [android_webview.WebViewClient] handles navigation requests. - bool get handlesNavigation => - loadUrl != null && onNavigationRequestCallback != null; - - @override - void onPageStarted(android_webview.WebView webView, String url) { - onPageStartedCallback(url); - } - - @override - void onPageFinished(android_webview.WebView webView, String url) { - onPageFinishedCallback(url); - } - - @override - void onReceivedError( - android_webview.WebView webView, - int errorCode, - String description, - String failingUrl, - ) { - onWebResourceErrorCallback(WebResourceError( - errorCode: errorCode, - description: description, - failingUrl: failingUrl, - errorType: _errorCodeToErrorType(errorCode), - )); - } - - @override - void onReceivedRequestError( - android_webview.WebView webView, - android_webview.WebResourceRequest request, - android_webview.WebResourceError error, - ) { - if (request.isForMainFrame) { - onWebResourceErrorCallback(WebResourceError( - errorCode: error.errorCode, - description: error.description, - failingUrl: request.url, - errorType: _errorCodeToErrorType(error.errorCode), - )); - } - } - - @override - void urlLoading(android_webview.WebView webView, String url) { - if (!handlesNavigation) { + void _handleNavigationRequest({ + required String url, + required bool isForMainFrame, + }) { + if (!_hasNavigationDelegate) { return; } - final FutureOr returnValue = onNavigationRequestCallback!( + final FutureOr returnValue = callbacksHandler.onNavigationRequest( url: url, - isForMainFrame: true, + isForMainFrame: isForMainFrame, ); if (returnValue is bool && returnValue) { - loadUrl!(url, {}); - } else if (returnValue is Future) { - returnValue.then((bool shouldLoadUrl) { - if (shouldLoadUrl) { - loadUrl!(url, {}); - } - }); - } - } - - @override - void requestLoading( - android_webview.WebView webView, - android_webview.WebResourceRequest request, - ) { - if (!handlesNavigation) { - return; - } - - final FutureOr returnValue = onNavigationRequestCallback!( - url: request.url, - isForMainFrame: request.isForMainFrame, - ); - - if (returnValue is bool && returnValue) { - loadUrl!(request.url, {}); + loadUrl(url, {}); } else if (returnValue is Future) { returnValue.then((bool shouldLoadUrl) { if (shouldLoadUrl) { - loadUrl!(request.url, {}); + loadUrl(url, {}); } }); } } } -/// Handles JavaScript dialogs, favicons, titles, and the progress for [WebViewAndroidPlatformController]. -class WebViewAndroidWebChromeClient extends android_webview.WebChromeClient { - // Changed by WebViewAndroidPlatformController. - void Function(int progress)? _onProgress; +/// Exposes a channel to receive calls from javaScript. +class WebViewAndroidJavaScriptChannel + extends android_webview.JavaScriptChannel { + /// Creates a [WebViewAndroidJavaScriptChannel]. + WebViewAndroidJavaScriptChannel( + super.channelName, + this.javascriptChannelRegistry, + ) : super( + postMessage: withWeakRefenceTo( + javascriptChannelRegistry, + (WeakReference weakReference) { + return (String message) { + weakReference.target?.onJavascriptChannelMessage( + channelName, + message, + ); + }; + }, + ), + ); - @override - void onProgressChanged(android_webview.WebView webView, int progress) { - if (_onProgress != null) { - _onProgress!(progress); - } - } + /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. + final JavascriptChannelRegistry javascriptChannelRegistry; } /// Handles constructing [android_webview.WebView]s and calling static methods. @@ -713,6 +604,38 @@ class WebViewProxy { return android_webview.WebView(useHybridComposition: useHybridComposition); } + /// Constructs a [android_webview.WebViewClient]. + android_webview.WebViewClient createWebViewClient({ + void Function(android_webview.WebView webView, String url)? onPageStarted, + void Function(android_webview.WebView webView, String url)? onPageFinished, + void Function( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + )? + onReceivedRequestError, + void Function( + android_webview.WebView webView, + int errorCode, + String description, + String failingUrl, + )? + onReceivedError, + void Function(android_webview.WebView webView, + android_webview.WebResourceRequest request)? + requestLoading, + void Function(android_webview.WebView webView, String url)? urlLoading, + }) { + return android_webview.WebViewClient( + onPageStarted: onPageStarted, + onPageFinished: onPageFinished, + onReceivedRequestError: onReceivedRequestError, + onReceivedError: onReceivedError, + requestLoading: requestLoading, + urlLoading: urlLoading, + ); + } + /// Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this application. /// /// This flag can be enabled in order to facilitate debugging of web layouts diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_surface_android.dart similarity index 96% rename from packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart rename to packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_surface_android.dart index e89fb7d1512f..8db2fe08835f 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_surface_android.dart @@ -7,9 +7,10 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; -import 'src/android_webview.dart'; +import '../android_webview.dart'; import 'webview_android.dart'; import 'webview_android_widget.dart'; diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart b/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart new file mode 100644 index 000000000000..ad0c9ebf4f5c --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart @@ -0,0 +1,34 @@ +// 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. + +/// Helper method for creating callbacks methods with a weak reference. +/// +/// Example: +/// ``` +/// final JavascriptChannelRegistry javascriptChannelRegistry = ... +/// +/// final WKScriptMessageHandler handler = WKScriptMessageHandler( +/// didReceiveScriptMessage: withWeakRefenceTo( +/// javascriptChannelRegistry, +/// (WeakReference weakReference) { +/// return ( +/// WKUserContentController userContentController, +/// WKScriptMessage message, +/// ) { +/// weakReference.target?.onJavascriptChannelMessage( +/// message.name, +/// message.body!.toString(), +/// ); +/// }; +/// }, +/// ), +/// ); +/// ``` +S withWeakRefenceTo( + T reference, + S Function(WeakReference weakReference) onCreate, +) { + final WeakReference weakReference = WeakReference(reference); + return onCreate(weakReference); +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/webview_flutter_android_legacy.dart b/packages/webview_flutter/webview_flutter_android/lib/src/webview_flutter_android_legacy.dart new file mode 100644 index 000000000000..a4f9166a07dd --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/src/webview_flutter_android_legacy.dart @@ -0,0 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'legacy/webview_android.dart'; +export 'legacy/webview_android_cookie_manager.dart'; +export 'legacy/webview_surface_android.dart'; diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart new file mode 100644 index 000000000000..faab71549995 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart @@ -0,0 +1,10 @@ +// 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_android; + +export 'src/android_navigation_delegate.dart'; +export 'src/android_webview_controller.dart'; +export 'src/android_webview_cookie_manager.dart'; +export 'src/android_webview_platform.dart'; diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart index f3946ed4212a..d3adac8ee4c4 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -88,8 +88,6 @@ abstract class CookieManagerHostApi { abstract class WebViewHostApi { void create(int instanceId, bool useHybridComposition); - void dispose(int instanceId); - void loadData( int instanceId, String data, @@ -169,8 +167,6 @@ abstract class WebViewHostApi { abstract class WebSettingsHostApi { void create(int instanceId, int webViewInstanceId); - void dispose(int instanceId); - void setDomStorageEnabled(int instanceId, bool flag); void setJavaScriptCanOpenWindowsAutomatically(int instanceId, bool flag); @@ -203,20 +199,21 @@ abstract class JavaScriptChannelHostApi { @FlutterApi() abstract class JavaScriptChannelFlutterApi { - void dispose(int instanceId); - void postMessage(int instanceId, String message); } @HostApi(dartHostTestHandler: 'TestWebViewClientHostApi') abstract class WebViewClientHostApi { - void create(int instanceId, bool shouldOverrideUrlLoading); + void create(int instanceId); + + void setSynchronousReturnValueForShouldOverrideUrlLoading( + int instanceId, + bool value, + ); } @FlutterApi() abstract class WebViewClientFlutterApi { - void dispose(int instanceId); - void onPageStarted(int instanceId, int webViewInstanceId, String url); void onPageFinished(int instanceId, int webViewInstanceId, String url); @@ -252,8 +249,6 @@ abstract class DownloadListenerHostApi { @FlutterApi() abstract class DownloadListenerFlutterApi { - void dispose(int instanceId); - void onDownloadStart( int instanceId, String url, @@ -266,7 +261,7 @@ abstract class DownloadListenerFlutterApi { @HostApi(dartHostTestHandler: 'TestWebChromeClientHostApi') abstract class WebChromeClientHostApi { - void create(int instanceId, int webViewClientInstanceId); + void create(int instanceId); } @HostApi(dartHostTestHandler: 'TestAssetManagerHostApi') @@ -278,8 +273,6 @@ abstract class FlutterAssetManagerHostApi { @FlutterApi() abstract class WebChromeClientFlutterApi { - void dispose(int instanceId); - void onProgressChanged(int instanceId, int webViewInstanceId, int progress); } diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index e411b4e1326a..8155965f8771 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -3,9 +3,10 @@ description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 version: 2.10.4 +publish_to: none environment: - sdk: ">=2.14.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" flutter: @@ -15,11 +16,13 @@ flutter: android: package: io.flutter.plugins.webviewflutter pluginClass: WebViewFlutterPlugin + dartPluginClass: AndroidWebViewPlatform dependencies: flutter: sdk: flutter - webview_flutter_platform_interface: ^1.8.0 + webview_flutter_platform_interface: + path: ../webview_flutter_platform_interface dev_dependencies: build_runner: ^2.1.4 @@ -27,5 +30,5 @@ dev_dependencies: sdk: flutter flutter_test: sdk: flutter - mockito: ^5.2.0 - pigeon: ^4.0.2 + mockito: ^5.3.2 + pigeon: ^4.2.3 diff --git a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart new file mode 100644 index 000000000000..26d4e686d389 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart @@ -0,0 +1,514 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:webview_flutter_android/src/android_proxy.dart'; +import 'package:webview_flutter_android/src/android_webview.dart' + as android_webview; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +void main() { + group('AndroidNavigationDelegate', () { + test('onPageFinished', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + late final String callbackUrl; + androidNavigationDelegate + .setOnPageFinished((String url) => callbackUrl = url); + + CapturingWebViewClient.lastCreatedDelegate.onPageFinished!( + android_webview.WebView.detached(), + 'https://www.google.com', + ); + + expect(callbackUrl, 'https://www.google.com'); + }); + + test('onPageStarted', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + late final String callbackUrl; + androidNavigationDelegate + .setOnPageStarted((String url) => callbackUrl = url); + + CapturingWebViewClient.lastCreatedDelegate.onPageStarted!( + android_webview.WebView.detached(), + 'https://www.google.com', + ); + + expect(callbackUrl, 'https://www.google.com'); + }); + + test('onWebResourceError from onReceivedRequestError', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + late final WebResourceError callbackError; + androidNavigationDelegate.setOnWebResourceError( + (WebResourceError error) => callbackError = error); + + CapturingWebViewClient.lastCreatedDelegate.onReceivedRequestError!( + android_webview.WebView.detached(), + android_webview.WebResourceRequest( + url: 'https://www.google.com', + isForMainFrame: false, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: {'X-Mock': 'mocking'}, + ), + android_webview.WebResourceError( + errorCode: android_webview.WebViewClient.errorFileNotFound, + description: 'Page not found.', + ), + ); + + expect(callbackError.errorCode, + android_webview.WebViewClient.errorFileNotFound); + expect(callbackError.description, 'Page not found.'); + expect(callbackError.errorType, WebResourceErrorType.fileNotFound); + expect(callbackError.isForMainFrame, false); + }); + + test('onWebResourceError from onRequestError', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + late final WebResourceError callbackError; + androidNavigationDelegate.setOnWebResourceError( + (WebResourceError error) => callbackError = error); + + CapturingWebViewClient.lastCreatedDelegate.onReceivedError!( + android_webview.WebView.detached(), + android_webview.WebViewClient.errorFileNotFound, + 'Page not found.', + 'https://www.google.com', + ); + + expect(callbackError.errorCode, + android_webview.WebViewClient.errorFileNotFound); + expect(callbackError.description, 'Page not found.'); + expect(callbackError.errorType, WebResourceErrorType.fileNotFound); + expect(callbackError.isForMainFrame, true); + }); + + test( + 'onNavigationRequest from requestLoading should not be called when loadUrlCallback is not specified', + () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + NavigationRequest? callbackNavigationRequest; + androidNavigationDelegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + android_webview.WebView.detached(), + android_webview.WebResourceRequest( + url: 'https://www.google.com', + isForMainFrame: true, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: {'X-Mock': 'mocking'}, + ), + ); + + expect(callbackNavigationRequest, isNull); + }); + + test( + 'onLoadRequest from requestLoading should not be called when navigationRequestCallback is not specified', + () { + final Completer completer = Completer(); + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((_) { + completer.complete(); + return completer.future; + }); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + android_webview.WebView.detached(), + android_webview.WebResourceRequest( + url: 'https://www.google.com', + isForMainFrame: true, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: {'X-Mock': 'mocking'}, + ), + ); + + expect(completer.isCompleted, false); + }); + + test( + 'onLoadRequest from requestLoading should not be called when onNavigationRequestCallback returns NavigationDecision.prevent', + () { + final Completer completer = Completer(); + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((_) { + completer.complete(); + return completer.future; + }); + + late final NavigationRequest callbackNavigationRequest; + androidNavigationDelegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + android_webview.WebView.detached(), + android_webview.WebResourceRequest( + url: 'https://www.google.com', + isForMainFrame: true, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: {'X-Mock': 'mocking'}, + ), + ); + + expect(callbackNavigationRequest.isMainFrame, true); + expect(callbackNavigationRequest.url, 'https://www.google.com'); + expect(completer.isCompleted, false); + }); + + test( + 'onLoadRequest from requestLoading should complete when onNavigationRequestCallback returns NavigationDecision.navigate', + () { + final Completer completer = Completer(); + late final LoadRequestParams loadRequestParams; + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((LoadRequestParams params) { + loadRequestParams = params; + completer.complete(); + return completer.future; + }); + + late final NavigationRequest callbackNavigationRequest; + androidNavigationDelegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.navigate; + }); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + android_webview.WebView.detached(), + android_webview.WebResourceRequest( + url: 'https://www.google.com', + isForMainFrame: true, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: {'X-Mock': 'mocking'}, + ), + ); + + expect(loadRequestParams.uri.toString(), 'https://www.google.com'); + expect(loadRequestParams.headers, {'X-Mock': 'mocking'}); + expect(callbackNavigationRequest.isMainFrame, true); + expect(callbackNavigationRequest.url, 'https://www.google.com'); + expect(completer.isCompleted, true); + }); + + test( + 'onNavigationRequest from urlLoading should not be called when loadUrlCallback is not specified', + () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + NavigationRequest? callbackNavigationRequest; + androidNavigationDelegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + CapturingWebViewClient.lastCreatedDelegate.urlLoading!( + android_webview.WebView.detached(), + 'https://www.google.com', + ); + + expect(callbackNavigationRequest, isNull); + }); + + test( + 'onLoadRequest from urlLoading should not be called when navigationRequestCallback is not specified', + () { + final Completer completer = Completer(); + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((_) { + completer.complete(); + return completer.future; + }); + + CapturingWebViewClient.lastCreatedDelegate.urlLoading!( + android_webview.WebView.detached(), + 'https://www.google.com', + ); + + expect(completer.isCompleted, false); + }); + + test( + 'onLoadRequest from urlLoading should not be called when onNavigationRequestCallback returns NavigationDecision.prevent', + () { + final Completer completer = Completer(); + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((_) { + completer.complete(); + return completer.future; + }); + + late final NavigationRequest callbackNavigationRequest; + androidNavigationDelegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + CapturingWebViewClient.lastCreatedDelegate.urlLoading!( + android_webview.WebView.detached(), + 'https://www.google.com', + ); + + expect(callbackNavigationRequest.isMainFrame, true); + expect(callbackNavigationRequest.url, 'https://www.google.com'); + expect(completer.isCompleted, false); + }); + + test( + 'onLoadRequest from urlLoading should complete when onNavigationRequestCallback returns NavigationDecision.navigate', + () { + final Completer completer = Completer(); + late final LoadRequestParams loadRequestParams; + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((LoadRequestParams params) { + loadRequestParams = params; + completer.complete(); + return completer.future; + }); + + late final NavigationRequest callbackNavigationRequest; + androidNavigationDelegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.navigate; + }); + + CapturingWebViewClient.lastCreatedDelegate.urlLoading!( + android_webview.WebView.detached(), + 'https://www.google.com', + ); + + expect(loadRequestParams.uri.toString(), 'https://www.google.com'); + expect(loadRequestParams.headers, {}); + expect(callbackNavigationRequest.isMainFrame, true); + expect(callbackNavigationRequest.url, 'https://www.google.com'); + expect(completer.isCompleted, true); + }); + + test('setOnNavigationRequest should override URL loading', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnNavigationRequest( + (NavigationRequest request) => NavigationDecision.navigate, + ); + + expect( + CapturingWebViewClient.lastCreatedDelegate + .synchronousReturnValueForShouldOverrideUrlLoading, + isTrue); + }); + + test('onProgress', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + late final int callbackProgress; + androidNavigationDelegate + .setOnProgress((int progress) => callbackProgress = progress); + + CapturingWebChromeClient.lastCreatedDelegate.onProgressChanged!( + android_webview.WebView.detached(), + 42, + ); + + expect(callbackProgress, 42); + }); + + test( + 'onLoadRequest from onDownloadStart should not be called when navigationRequestCallback is not specified', + () { + final Completer completer = Completer(); + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((_) { + completer.complete(); + return completer.future; + }); + + CapturingDownloadListener.lastCreatedListener.onDownloadStart( + '', + '', + '', + '', + 0, + ); + + expect(completer.isCompleted, false); + }); + + test( + 'onLoadRequest from onDownloadStart should not be called when onNavigationRequestCallback returns NavigationDecision.prevent', + () { + final Completer completer = Completer(); + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((_) { + completer.complete(); + return completer.future; + }); + + late final NavigationRequest callbackNavigationRequest; + androidNavigationDelegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + CapturingDownloadListener.lastCreatedListener.onDownloadStart( + 'https://www.google.com', + '', + '', + '', + 0, + ); + + expect(callbackNavigationRequest.isMainFrame, true); + expect(callbackNavigationRequest.url, 'https://www.google.com'); + expect(completer.isCompleted, false); + }); + + test( + 'onLoadRequest from onDownloadStart should complete when onNavigationRequestCallback returns NavigationDecision.navigate', + () { + final Completer completer = Completer(); + late final LoadRequestParams loadRequestParams; + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((LoadRequestParams params) { + loadRequestParams = params; + completer.complete(); + return completer.future; + }); + + late final NavigationRequest callbackNavigationRequest; + androidNavigationDelegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.navigate; + }); + + CapturingDownloadListener.lastCreatedListener.onDownloadStart( + 'https://www.google.com', + '', + '', + '', + 0, + ); + + expect(loadRequestParams.uri.toString(), 'https://www.google.com'); + expect(loadRequestParams.headers, {}); + expect(callbackNavigationRequest.isMainFrame, true); + expect(callbackNavigationRequest.url, 'https://www.google.com'); + expect(completer.isCompleted, true); + }); + }); +} + +AndroidNavigationDelegateCreationParams _buildCreationParams() { + return AndroidNavigationDelegateCreationParams + .fromPlatformNavigationDelegateCreationParams( + const PlatformNavigationDelegateCreationParams(), + androidWebViewProxy: const AndroidWebViewProxy( + createAndroidWebChromeClient: CapturingWebChromeClient.new, + createAndroidWebViewClient: CapturingWebViewClient.new, + createDownloadListener: CapturingDownloadListener.new, + ), + ); +} + +// Records the last created instance of itself. +class CapturingWebViewClient extends android_webview.WebViewClient { + CapturingWebViewClient({ + super.onPageFinished, + super.onPageStarted, + super.onReceivedError, + super.onReceivedRequestError, + super.requestLoading, + super.urlLoading, + }) : super.detached() { + lastCreatedDelegate = this; + } + + static CapturingWebViewClient lastCreatedDelegate = CapturingWebViewClient(); + + bool synchronousReturnValueForShouldOverrideUrlLoading = false; + + @override + Future setSynchronousReturnValueForShouldOverrideUrlLoading( + bool value) async { + synchronousReturnValueForShouldOverrideUrlLoading = value; + } +} + +// Records the last created instance of itself. +class CapturingWebChromeClient extends android_webview.WebChromeClient { + CapturingWebChromeClient({ + super.onProgressChanged, + }) : super.detached() { + lastCreatedDelegate = this; + } + static CapturingWebChromeClient lastCreatedDelegate = + CapturingWebChromeClient(); +} + +// Records the last created instance of itself. +class CapturingDownloadListener extends android_webview.DownloadListener { + CapturingDownloadListener({ + required super.onDownloadStart, + }) : super.detached() { + lastCreatedListener = this; + } + static CapturingDownloadListener lastCreatedListener = + CapturingDownloadListener(onDownloadStart: (_, __, ___, ____, _____) {}); +} diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart new file mode 100644 index 000000000000..4b74f3af3390 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart @@ -0,0 +1,798 @@ +// 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/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter_android/src/android_proxy.dart'; +import 'package:webview_flutter_android/src/android_webview.dart' + as android_webview; +import 'package:webview_flutter_android/src/instance_manager.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_platform_interface/src/webview_platform.dart'; + +import 'android_webview_controller_test.mocks.dart'; + +@GenerateNiceMocks(>[ + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + AndroidWebViewController createControllerWithMocks({ + android_webview.FlutterAssetManager? mockFlutterAssetManager, + android_webview.JavaScriptChannel? mockJavaScriptChannel, + android_webview.WebChromeClient? mockWebChromeClient, + android_webview.WebView? mockWebView, + android_webview.WebViewClient? mockWebViewClient, + android_webview.WebStorage? mockWebStorage, + android_webview.WebSettings? mockSettings, + }) { + final android_webview.WebView nonNullMockWebView = + mockWebView ?? MockWebView(); + + final AndroidWebViewControllerCreationParams creationParams = + AndroidWebViewControllerCreationParams( + androidWebStorage: mockWebStorage ?? MockWebStorage(), + androidWebViewProxy: AndroidWebViewProxy( + createAndroidWebChromeClient: ( + {void Function(android_webview.WebView, int)? + onProgressChanged}) => + mockWebChromeClient ?? MockWebChromeClient(), + createAndroidWebView: ({required bool useHybridComposition}) => + nonNullMockWebView, + createAndroidWebViewClient: ({ + void Function(android_webview.WebView webView, String url)? + onPageFinished, + void Function(android_webview.WebView webView, String url)? + onPageStarted, + @Deprecated('Only called on Android version < 23.') + void Function( + android_webview.WebView webView, + int errorCode, + String description, + String failingUrl, + )? + onReceivedError, + void Function( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + )? + onReceivedRequestError, + void Function( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + )? + requestLoading, + void Function(android_webview.WebView webView, String url)? + urlLoading, + }) => + mockWebViewClient ?? MockWebViewClient(), + createFlutterAssetManager: () => + mockFlutterAssetManager ?? MockFlutterAssetManager(), + createJavaScriptChannel: ( + String channelName, { + required void Function(String) postMessage, + }) => + mockJavaScriptChannel ?? MockJavaScriptChannel(), + )); + + when(nonNullMockWebView.settings) + .thenReturn(mockSettings ?? MockWebSettings()); + + return AndroidWebViewController(creationParams); + } + + group('AndroidWebViewController', () { + AndroidJavaScriptChannelParams + createAndroidJavaScriptChannelParamsWithMocks({ + String? name, + MockJavaScriptChannel? mockJavaScriptChannel, + }) { + return AndroidJavaScriptChannelParams( + name: name ?? 'test', + onMessageReceived: (JavaScriptMessage message) {}, + webViewProxy: AndroidWebViewProxy( + createJavaScriptChannel: ( + String channelName, { + required void Function(String) postMessage, + }) => + mockJavaScriptChannel ?? MockJavaScriptChannel(), + )); + } + + test('loadFile without file prefix', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + verify(mockWebSettings.setBuiltInZoomControls(true)).called(1); + verify(mockWebSettings.setDisplayZoomControls(false)).called(1); + verify(mockWebSettings.setDomStorageEnabled(true)).called(1); + verify(mockWebSettings.setJavaScriptCanOpenWindowsAutomatically(true)) + .called(1); + verify(mockWebSettings.setLoadWithOverviewMode(true)).called(1); + verify(mockWebSettings.setSupportMultipleWindows(true)).called(1); + verify(mockWebSettings.setUseWideViewPort(true)).called(1); + }); + + test('loadFile without file prefix', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + await controller.loadFile('/path/to/file.html'); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify(mockWebView.loadUrl( + 'file:///path/to/file.html', + {}, + )).called(1); + }); + + test('loadFile without file prefix and characters to be escaped', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + await controller.loadFile('/path/to/?_<_>_.html'); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify(mockWebView.loadUrl( + 'file:///path/to/%3F_%3C_%3E_.html', + {}, + )).called(1); + }); + + test('loadFile with file prefix', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.settings).thenReturn(mockWebSettings); + + await controller.loadFile('file:///path/to/file.html'); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify(mockWebView.loadUrl( + 'file:///path/to/file.html', + {}, + )).called(1); + }); + + test('loadFlutterAsset when asset does not exists', () async { + final MockWebView mockWebView = MockWebView(); + final MockFlutterAssetManager mockAssetManager = + MockFlutterAssetManager(); + final AndroidWebViewController controller = createControllerWithMocks( + mockFlutterAssetManager: mockAssetManager, + mockWebView: mockWebView, + ); + + when(mockAssetManager.getAssetFilePathByName('mock_key')) + .thenAnswer((_) => Future.value('')); + when(mockAssetManager.list('')) + .thenAnswer((_) => Future>.value([])); + + try { + await controller.loadFlutterAsset('mock_key'); + fail('Expected an `ArgumentError`.'); + } on ArgumentError catch (e) { + expect(e.message, 'Asset for key "mock_key" not found.'); + expect(e.name, 'key'); + } on Error { + fail('Expect an `ArgumentError`.'); + } + + verify(mockAssetManager.getAssetFilePathByName('mock_key')).called(1); + verify(mockAssetManager.list('')).called(1); + verifyNever(mockWebView.loadUrl(any, any)); + }); + + test('loadFlutterAsset when asset does exists', () async { + final MockWebView mockWebView = MockWebView(); + final MockFlutterAssetManager mockAssetManager = + MockFlutterAssetManager(); + final AndroidWebViewController controller = createControllerWithMocks( + mockFlutterAssetManager: mockAssetManager, + mockWebView: mockWebView, + ); + + when(mockAssetManager.getAssetFilePathByName('mock_key')) + .thenAnswer((_) => Future.value('www/mock_file.html')); + when(mockAssetManager.list('www')).thenAnswer( + (_) => Future>.value(['mock_file.html'])); + + await controller.loadFlutterAsset('mock_key'); + + verify(mockAssetManager.getAssetFilePathByName('mock_key')).called(1); + verify(mockAssetManager.list('www')).called(1); + verify(mockWebView.loadUrl( + 'file:///android_asset/www/mock_file.html', {})); + }); + + test( + 'loadFlutterAsset when asset name contains characters that should be escaped', + () async { + final MockWebView mockWebView = MockWebView(); + final MockFlutterAssetManager mockAssetManager = + MockFlutterAssetManager(); + final AndroidWebViewController controller = createControllerWithMocks( + mockFlutterAssetManager: mockAssetManager, + mockWebView: mockWebView, + ); + + when(mockAssetManager.getAssetFilePathByName('mock_key')) + .thenAnswer((_) => Future.value('www/?_<_>_.html')); + when(mockAssetManager.list('www')).thenAnswer( + (_) => Future>.value(['?_<_>_.html'])); + + await controller.loadFlutterAsset('mock_key'); + + verify(mockAssetManager.getAssetFilePathByName('mock_key')).called(1); + verify(mockAssetManager.list('www')).called(1); + verify(mockWebView.loadUrl( + 'file:///android_asset/www/%3F_%3C_%3E_.html', {})); + }); + + test('loadHtmlString without baseUrl', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.loadHtmlString('

Hello Test!

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

Hello Test!

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

Hello Test!

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

Hello Test!

', + baseUrl: 'https://flutter.dev', + mimeType: 'text/html', + )).called(1); + }); + + test('loadRequest without URI scheme', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final LoadRequestParams requestParams = LoadRequestParams( + uri: Uri.parse('flutter.dev'), + ); + + try { + await controller.loadRequest(requestParams); + fail('Expect an `ArgumentError`.'); + } on ArgumentError catch (e) { + expect(e.message, 'WebViewRequest#uri is required to have a scheme.'); + } on Error { + fail('Expect a `ArgumentError`.'); + } + + verifyNever(mockWebView.loadUrl(any, any)); + verifyNever(mockWebView.postUrl(any, any)); + }); + + test('loadRequest using the GET method', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final LoadRequestParams requestParams = LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + headers: const {'X-Test': 'Testing'}, + ); + + await controller.loadRequest(requestParams); + + verify(mockWebView.loadUrl( + 'https://flutter.dev', + {'X-Test': 'Testing'}, + )); + verifyNever(mockWebView.postUrl(any, any)); + }); + + test('loadRequest using the POST method without body', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final LoadRequestParams requestParams = LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + method: LoadRequestMethod.post, + headers: const {'X-Test': 'Testing'}, + ); + + await controller.loadRequest(requestParams); + + verify(mockWebView.postUrl( + 'https://flutter.dev', + Uint8List(0), + )); + verifyNever(mockWebView.loadUrl(any, any)); + }); + + test('loadRequest using the POST method with body', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final LoadRequestParams requestParams = LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + method: LoadRequestMethod.post, + headers: const {'X-Test': 'Testing'}, + body: Uint8List.fromList('{"message": "Hello World!"}'.codeUnits), + ); + + await controller.loadRequest(requestParams); + + verify(mockWebView.postUrl( + 'https://flutter.dev', + Uint8List.fromList('{"message": "Hello World!"}'.codeUnits), + )); + verifyNever(mockWebView.loadUrl(any, any)); + }); + + test('currentUrl', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.currentUrl(); + + verify(mockWebView.getUrl()).called(1); + }); + + test('canGoBack', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.canGoBack(); + + verify(mockWebView.canGoBack()).called(1); + }); + + test('canGoForward', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.canGoForward(); + + verify(mockWebView.canGoForward()).called(1); + }); + + test('goBack', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.goBack(); + + verify(mockWebView.goBack()).called(1); + }); + + test('goForward', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.goForward(); + + verify(mockWebView.goForward()).called(1); + }); + + test('reload', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.reload(); + + verify(mockWebView.reload()).called(1); + }); + + test('clearCache', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.clearCache(); + + verify(mockWebView.clearCache(true)).called(1); + }); + + test('clearLocalStorage', () async { + final MockWebStorage mockWebStorage = MockWebStorage(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebStorage: mockWebStorage, + ); + + await controller.clearLocalStorage(); + + verify(mockWebStorage.deleteAllData()).called(1); + }); + + test('setPlatformNavigationDelegate', () async { + final MockAndroidNavigationDelegate mockNavigationDelegate = + MockAndroidNavigationDelegate(); + final MockWebView mockWebView = MockWebView(); + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + final MockWebViewClient mockWebViewClient = MockWebViewClient(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockNavigationDelegate.androidWebChromeClient) + .thenReturn(mockWebChromeClient); + when(mockNavigationDelegate.androidWebViewClient) + .thenReturn(mockWebViewClient); + + await controller.setPlatformNavigationDelegate(mockNavigationDelegate); + + verifyInOrder([ + mockWebView.setWebViewClient(mockWebViewClient), + mockWebView.setWebChromeClient(mockWebChromeClient), + ]); + }); + + test('runJavaScript', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.runJavaScript('alert("This is a test.");'); + + verify(mockWebView.evaluateJavascript('alert("This is a test.");')) + .called(1); + }); + + test('runJavaScriptReturningResult with return value', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.evaluateJavascript('return "Hello" + " World!";')) + .thenAnswer((_) => Future.value('Hello World!')); + + final String message = await controller.runJavaScriptReturningResult( + 'return "Hello" + " World!";') as String; + + expect(message, 'Hello World!'); + }); + + test('runJavaScriptReturningResult returning null', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.evaluateJavascript('alert("This is a test.");')) + .thenAnswer((_) => Future.value()); + + final String message = await controller + .runJavaScriptReturningResult('alert("This is a test.");') as String; + + expect(message, ''); + }); + + test('runJavaScriptReturningResult parses num', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.evaluateJavascript('alert("This is a test.");')) + .thenAnswer((_) => Future.value('3.14')); + + final num message = await controller + .runJavaScriptReturningResult('alert("This is a test.");') as num; + + expect(message, 3.14); + }); + + test('runJavaScriptReturningResult parses true', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.evaluateJavascript('alert("This is a test.");')) + .thenAnswer((_) => Future.value('true')); + + final bool message = await controller + .runJavaScriptReturningResult('alert("This is a test.");') as bool; + + expect(message, true); + }); + + test('runJavaScriptReturningResult parses false', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.evaluateJavascript('alert("This is a test.");')) + .thenAnswer((_) => Future.value('false')); + + final bool message = await controller + .runJavaScriptReturningResult('alert("This is a test.");') as bool; + + expect(message, false); + }); + + test('addJavaScriptChannel', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final AndroidJavaScriptChannelParams paramsWithMock = + createAndroidJavaScriptChannelParamsWithMocks(name: 'test'); + await controller.addJavaScriptChannel(paramsWithMock); + verify(mockWebView.addJavaScriptChannel( + argThat(isA()))) + .called(1); + }); + + test( + 'addJavaScriptChannel add channel with same name should remove existing channel', + () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final AndroidJavaScriptChannelParams paramsWithMock = + createAndroidJavaScriptChannelParamsWithMocks(name: 'test'); + await controller.addJavaScriptChannel(paramsWithMock); + verify(mockWebView.addJavaScriptChannel( + argThat(isA()))) + .called(1); + + await controller.addJavaScriptChannel(paramsWithMock); + verifyInOrder([ + mockWebView.removeJavaScriptChannel( + argThat(isA())), + mockWebView.addJavaScriptChannel( + argThat(isA())), + ]); + }); + + test('removeJavaScriptChannel when channel is not registered', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.removeJavaScriptChannel('test'); + verifyNever(mockWebView.removeJavaScriptChannel(any)); + }); + + test('removeJavaScriptChannel when channel exists', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final AndroidJavaScriptChannelParams paramsWithMock = + createAndroidJavaScriptChannelParamsWithMocks(name: 'test'); + + // Make sure channel exists before removing it. + await controller.addJavaScriptChannel(paramsWithMock); + verify(mockWebView.addJavaScriptChannel( + argThat(isA()))) + .called(1); + + await controller.removeJavaScriptChannel('test'); + verify(mockWebView.removeJavaScriptChannel( + argThat(isA()))) + .called(1); + }); + + test('getTitle', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.getTitle(); + + verify(mockWebView.getTitle()).called(1); + }); + + test('scrollTo', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.scrollTo(4, 2); + + verify(mockWebView.scrollTo(4, 2)).called(1); + }); + + test('scrollBy', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.scrollBy(4, 2); + + verify(mockWebView.scrollBy(4, 2)).called(1); + }); + + test('getScrollPosition', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + when(mockWebView.getScrollPosition()) + .thenAnswer((_) => Future.value(const Offset(4, 2))); + + final Offset position = await controller.getScrollPosition(); + + verify(mockWebView.getScrollPosition()).called(1); + expect(position.dx, 4); + expect(position.dy, 2); + }); + + test('enableDebugging', () async { + final MockAndroidWebViewProxy mockProxy = MockAndroidWebViewProxy(); + + await AndroidWebViewController.enableDebugging( + true, + webViewProxy: mockProxy, + ); + verify(mockProxy.setWebContentsDebuggingEnabled(true)).called(1); + }); + + test('enableZoom', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + clearInteractions(mockWebView); + + await controller.enableZoom(true); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setSupportZoom(true)).called(1); + }); + + test('setBackgroundColor', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.setBackgroundColor(Colors.blue); + + verify(mockWebView.setBackgroundColor(Colors.blue)).called(1); + }); + + test('setJavaScriptMode', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + clearInteractions(mockWebView); + + await controller.setJavaScriptMode(JavaScriptMode.disabled); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setJavaScriptEnabled(false)).called(1); + }); + + test('setUserAgent', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + clearInteractions(mockWebView); + + await controller.setUserAgent('Test Framework'); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setUserAgentString('Test Framework')).called(1); + }); + }); + + test('setMediaPlaybackRequiresUserGesture', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + await controller.setMediaPlaybackRequiresUserGesture(true); + + verify(mockSettings.setMediaPlaybackRequiresUserGesture(true)).called(1); + }); + + group('AndroidWebViewWidget', () { + testWidgets('Builds AndroidView using supplied parameters', + (WidgetTester tester) async { + final MockAndroidWebViewWidgetCreationParams mockParams = + MockAndroidWebViewWidgetCreationParams(); + final MockInstanceManager mockInstanceManager = MockInstanceManager(); + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = + createControllerWithMocks(mockWebView: mockWebView); + + when(mockParams.key).thenReturn(const Key('test_web_view')); + when(mockParams.instanceManager).thenReturn(mockInstanceManager); + when(mockParams.controller).thenReturn(controller); + when(mockInstanceManager.getIdentifier(mockWebView)).thenReturn(42); + + final AndroidWebViewWidget webViewWidget = + AndroidWebViewWidget(mockParams); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) => webViewWidget.build(context), + )); + + expect(find.byType(PlatformViewLink), findsOneWidget); + expect(find.byKey(const Key('test_web_view')), findsOneWidget); + }); + }); +} diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart new file mode 100644 index 000000000000..d6147a543ef1 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart @@ -0,0 +1,1679 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in webview_flutter_android/test/android_webview_controller_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i7; +import 'dart:typed_data' as _i12; +import 'dart:ui' as _i4; + +import 'package:flutter/foundation.dart' as _i10; +import 'package:flutter/gestures.dart' as _i11; +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_android/src/android_navigation_delegate.dart' + as _i6; +import 'package:webview_flutter_android/src/android_proxy.dart' as _i9; +import 'package:webview_flutter_android/src/android_webview.dart' as _i2; +import 'package:webview_flutter_android/src/android_webview_controller.dart' + as _i8; +import 'package:webview_flutter_android/src/instance_manager.dart' as _i5; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' + as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeWebChromeClient_0 extends _i1.SmartFake + implements _i2.WebChromeClient { + _FakeWebChromeClient_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWebViewClient_1 extends _i1.SmartFake implements _i2.WebViewClient { + _FakeWebViewClient_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeDownloadListener_2 extends _i1.SmartFake + implements _i2.DownloadListener { + _FakeDownloadListener_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformNavigationDelegateCreationParams_3 extends _i1.SmartFake + implements _i3.PlatformNavigationDelegateCreationParams { + _FakePlatformNavigationDelegateCreationParams_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformWebViewControllerCreationParams_4 extends _i1.SmartFake + implements _i3.PlatformWebViewControllerCreationParams { + _FakePlatformWebViewControllerCreationParams_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeObject_5 extends _i1.SmartFake implements Object { + _FakeObject_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeOffset_6 extends _i1.SmartFake implements _i4.Offset { + _FakeOffset_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWebView_7 extends _i1.SmartFake implements _i2.WebView { + _FakeWebView_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeFlutterAssetManager_8 extends _i1.SmartFake + implements _i2.FlutterAssetManager { + _FakeFlutterAssetManager_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeJavaScriptChannel_9 extends _i1.SmartFake + implements _i2.JavaScriptChannel { + _FakeJavaScriptChannel_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeInstanceManager_10 extends _i1.SmartFake + implements _i5.InstanceManager { + _FakeInstanceManager_10( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformWebViewController_11 extends _i1.SmartFake + implements _i3.PlatformWebViewController { + _FakePlatformWebViewController_11( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWebSettings_12 extends _i1.SmartFake implements _i2.WebSettings { + _FakeWebSettings_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWebStorage_13 extends _i1.SmartFake implements _i2.WebStorage { + _FakeWebStorage_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [AndroidNavigationDelegate]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAndroidNavigationDelegate extends _i1.Mock + implements _i6.AndroidNavigationDelegate { + @override + _i2.WebChromeClient get androidWebChromeClient => (super.noSuchMethod( + Invocation.getter(#androidWebChromeClient), + returnValue: _FakeWebChromeClient_0( + this, + Invocation.getter(#androidWebChromeClient), + ), + returnValueForMissingStub: _FakeWebChromeClient_0( + this, + Invocation.getter(#androidWebChromeClient), + ), + ) as _i2.WebChromeClient); + @override + _i2.WebViewClient get androidWebViewClient => (super.noSuchMethod( + Invocation.getter(#androidWebViewClient), + returnValue: _FakeWebViewClient_1( + this, + Invocation.getter(#androidWebViewClient), + ), + returnValueForMissingStub: _FakeWebViewClient_1( + this, + Invocation.getter(#androidWebViewClient), + ), + ) as _i2.WebViewClient); + @override + _i2.DownloadListener get androidDownloadListener => (super.noSuchMethod( + Invocation.getter(#androidDownloadListener), + returnValue: _FakeDownloadListener_2( + this, + Invocation.getter(#androidDownloadListener), + ), + returnValueForMissingStub: _FakeDownloadListener_2( + this, + Invocation.getter(#androidDownloadListener), + ), + ) as _i2.DownloadListener); + @override + _i3.PlatformNavigationDelegateCreationParams get params => + (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformNavigationDelegateCreationParams_3( + this, + Invocation.getter(#params), + ), + returnValueForMissingStub: + _FakePlatformNavigationDelegateCreationParams_3( + this, + Invocation.getter(#params), + ), + ) as _i3.PlatformNavigationDelegateCreationParams); + @override + _i7.Future setOnLoadRequest(_i6.LoadRequestCallback? onLoadRequest) => + (super.noSuchMethod( + Invocation.method( + #setOnLoadRequest, + [onLoadRequest], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setOnNavigationRequest( + _i3.NavigationRequestCallback? onNavigationRequest) => + (super.noSuchMethod( + Invocation.method( + #setOnNavigationRequest, + [onNavigationRequest], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setOnPageStarted(_i3.PageEventCallback? onPageStarted) => + (super.noSuchMethod( + Invocation.method( + #setOnPageStarted, + [onPageStarted], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setOnPageFinished(_i3.PageEventCallback? onPageFinished) => + (super.noSuchMethod( + Invocation.method( + #setOnPageFinished, + [onPageFinished], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setOnProgress(_i3.ProgressCallback? onProgress) => + (super.noSuchMethod( + Invocation.method( + #setOnProgress, + [onProgress], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setOnWebResourceError( + _i3.WebResourceErrorCallback? onWebResourceError) => + (super.noSuchMethod( + Invocation.method( + #setOnWebResourceError, + [onWebResourceError], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); +} + +/// A class which mocks [AndroidWebViewController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAndroidWebViewController extends _i1.Mock + implements _i8.AndroidWebViewController { + @override + _i3.PlatformWebViewControllerCreationParams get params => (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformWebViewControllerCreationParams_4( + this, + Invocation.getter(#params), + ), + returnValueForMissingStub: + _FakePlatformWebViewControllerCreationParams_4( + this, + Invocation.getter(#params), + ), + ) as _i3.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(_i3.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(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future canGoBack() => (super.noSuchMethod( + Invocation.method( + #canGoBack, + [], + ), + returnValue: _i7.Future.value(false), + returnValueForMissingStub: _i7.Future.value(false), + ) as _i7.Future); + @override + _i7.Future canGoForward() => (super.noSuchMethod( + Invocation.method( + #canGoForward, + [], + ), + returnValue: _i7.Future.value(false), + returnValueForMissingStub: _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( + _i3.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_5( + this, + Invocation.method( + #runJavaScriptReturningResult, + [javaScript], + ), + )), + returnValueForMissingStub: _i7.Future.value(_FakeObject_5( + this, + Invocation.method( + #runJavaScriptReturningResult, + [javaScript], + ), + )), + ) as _i7.Future); + @override + _i7.Future addJavaScriptChannel( + _i3.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(), + returnValueForMissingStub: _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<_i4.Offset> getScrollPosition() => (super.noSuchMethod( + Invocation.method( + #getScrollPosition, + [], + ), + returnValue: _i7.Future<_i4.Offset>.value(_FakeOffset_6( + this, + Invocation.method( + #getScrollPosition, + [], + ), + )), + returnValueForMissingStub: _i7.Future<_i4.Offset>.value(_FakeOffset_6( + this, + Invocation.method( + #getScrollPosition, + [], + ), + )), + ) as _i7.Future<_i4.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(_i4.Color? color) => (super.noSuchMethod( + Invocation.method( + #setBackgroundColor, + [color], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setJavaScriptMode(_i3.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); + @override + _i7.Future setMediaPlaybackRequiresUserGesture(bool? require) => + (super.noSuchMethod( + Invocation.method( + #setMediaPlaybackRequiresUserGesture, + [require], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); +} + +/// A class which mocks [AndroidWebViewProxy]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAndroidWebViewProxy extends _i1.Mock + implements _i9.AndroidWebViewProxy { + @override + _i2.WebView Function({required bool useHybridComposition}) + get createAndroidWebView => (super.noSuchMethod( + Invocation.getter(#createAndroidWebView), + returnValue: ({required bool useHybridComposition}) => + _FakeWebView_7( + this, + Invocation.getter(#createAndroidWebView), + ), + returnValueForMissingStub: ({required bool useHybridComposition}) => + _FakeWebView_7( + this, + Invocation.getter(#createAndroidWebView), + ), + ) as _i2.WebView Function({required bool useHybridComposition})); + @override + _i2.WebChromeClient Function( + {void Function( + _i2.WebView, + int, + )? + onProgressChanged}) get createAndroidWebChromeClient => + (super.noSuchMethod( + Invocation.getter(#createAndroidWebChromeClient), + returnValue: ( + {void Function( + _i2.WebView, + int, + )? + onProgressChanged}) => + _FakeWebChromeClient_0( + this, + Invocation.getter(#createAndroidWebChromeClient), + ), + returnValueForMissingStub: ( + {void Function( + _i2.WebView, + int, + )? + onProgressChanged}) => + _FakeWebChromeClient_0( + this, + Invocation.getter(#createAndroidWebChromeClient), + ), + ) as _i2.WebChromeClient Function( + {void Function( + _i2.WebView, + int, + )? + onProgressChanged})); + @override + _i2.WebViewClient Function({ + void Function( + _i2.WebView, + String, + )? + onPageFinished, + void Function( + _i2.WebView, + String, + )? + onPageStarted, + void Function( + _i2.WebView, + int, + String, + String, + )? + onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + )? + requestLoading, + void Function( + _i2.WebView, + String, + )? + urlLoading, + }) get createAndroidWebViewClient => (super.noSuchMethod( + Invocation.getter(#createAndroidWebViewClient), + returnValue: ({ + void Function( + _i2.WebView, + String, + )? + onPageFinished, + void Function( + _i2.WebView, + String, + )? + onPageStarted, + void Function( + _i2.WebView, + int, + String, + String, + )? + onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + )? + requestLoading, + void Function( + _i2.WebView, + String, + )? + urlLoading, + }) => + _FakeWebViewClient_1( + this, + Invocation.getter(#createAndroidWebViewClient), + ), + returnValueForMissingStub: ({ + void Function( + _i2.WebView, + String, + )? + onPageFinished, + void Function( + _i2.WebView, + String, + )? + onPageStarted, + void Function( + _i2.WebView, + int, + String, + String, + )? + onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + )? + requestLoading, + void Function( + _i2.WebView, + String, + )? + urlLoading, + }) => + _FakeWebViewClient_1( + this, + Invocation.getter(#createAndroidWebViewClient), + ), + ) as _i2.WebViewClient Function({ + void Function( + _i2.WebView, + String, + )? + onPageFinished, + void Function( + _i2.WebView, + String, + )? + onPageStarted, + void Function( + _i2.WebView, + int, + String, + String, + )? + onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + )? + requestLoading, + void Function( + _i2.WebView, + String, + )? + urlLoading, + })); + @override + _i2.FlutterAssetManager Function() get createFlutterAssetManager => + (super.noSuchMethod( + Invocation.getter(#createFlutterAssetManager), + returnValue: () => _FakeFlutterAssetManager_8( + this, + Invocation.getter(#createFlutterAssetManager), + ), + returnValueForMissingStub: () => _FakeFlutterAssetManager_8( + this, + Invocation.getter(#createFlutterAssetManager), + ), + ) as _i2.FlutterAssetManager Function()); + @override + _i2.JavaScriptChannel Function( + String, { + required void Function(String) postMessage, + }) get createJavaScriptChannel => (super.noSuchMethod( + Invocation.getter(#createJavaScriptChannel), + returnValue: ( + String channelName, { + required void Function(String) postMessage, + }) => + _FakeJavaScriptChannel_9( + this, + Invocation.getter(#createJavaScriptChannel), + ), + returnValueForMissingStub: ( + String channelName, { + required void Function(String) postMessage, + }) => + _FakeJavaScriptChannel_9( + this, + Invocation.getter(#createJavaScriptChannel), + ), + ) as _i2.JavaScriptChannel Function( + String, { + required void Function(String) postMessage, + })); + @override + _i2.DownloadListener Function( + {required void Function( + String, + String, + String, + String, + int, + ) + onDownloadStart}) get createDownloadListener => (super.noSuchMethod( + Invocation.getter(#createDownloadListener), + returnValue: ( + {required void Function( + String, + String, + String, + String, + int, + ) + onDownloadStart}) => + _FakeDownloadListener_2( + this, + Invocation.getter(#createDownloadListener), + ), + returnValueForMissingStub: ( + {required void Function( + String, + String, + String, + String, + int, + ) + onDownloadStart}) => + _FakeDownloadListener_2( + this, + Invocation.getter(#createDownloadListener), + ), + ) as _i2.DownloadListener Function( + {required void Function( + String, + String, + String, + String, + int, + ) + onDownloadStart})); + @override + _i7.Future setWebContentsDebuggingEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method( + #setWebContentsDebuggingEnabled, + [enabled], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); +} + +/// A class which mocks [AndroidWebViewWidgetCreationParams]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockAndroidWebViewWidgetCreationParams extends _i1.Mock + implements _i8.AndroidWebViewWidgetCreationParams { + @override + _i5.InstanceManager get instanceManager => (super.noSuchMethod( + Invocation.getter(#instanceManager), + returnValue: _FakeInstanceManager_10( + this, + Invocation.getter(#instanceManager), + ), + returnValueForMissingStub: _FakeInstanceManager_10( + this, + Invocation.getter(#instanceManager), + ), + ) as _i5.InstanceManager); + @override + _i3.PlatformWebViewController get controller => (super.noSuchMethod( + Invocation.getter(#controller), + returnValue: _FakePlatformWebViewController_11( + this, + Invocation.getter(#controller), + ), + returnValueForMissingStub: _FakePlatformWebViewController_11( + this, + Invocation.getter(#controller), + ), + ) as _i3.PlatformWebViewController); + @override + _i4.TextDirection get layoutDirection => (super.noSuchMethod( + Invocation.getter(#layoutDirection), + returnValue: _i4.TextDirection.rtl, + returnValueForMissingStub: _i4.TextDirection.rtl, + ) as _i4.TextDirection); + @override + Set<_i10.Factory<_i11.OneSequenceGestureRecognizer>> get gestureRecognizers => + (super.noSuchMethod( + Invocation.getter(#gestureRecognizers), + returnValue: <_i10.Factory<_i11.OneSequenceGestureRecognizer>>{}, + returnValueForMissingStub: < + _i10.Factory<_i11.OneSequenceGestureRecognizer>>{}, + ) as Set<_i10.Factory<_i11.OneSequenceGestureRecognizer>>); +} + +/// A class which mocks [FlutterAssetManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFlutterAssetManager extends _i1.Mock + implements _i2.FlutterAssetManager { + @override + _i7.Future> list(String? path) => (super.noSuchMethod( + Invocation.method( + #list, + [path], + ), + returnValue: _i7.Future>.value([]), + returnValueForMissingStub: _i7.Future>.value([]), + ) as _i7.Future>); + @override + _i7.Future getAssetFilePathByName(String? name) => + (super.noSuchMethod( + Invocation.method( + #getAssetFilePathByName, + [name], + ), + returnValue: _i7.Future.value(''), + returnValueForMissingStub: _i7.Future.value(''), + ) as _i7.Future); +} + +/// A class which mocks [JavaScriptChannel]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockJavaScriptChannel extends _i1.Mock implements _i2.JavaScriptChannel { + @override + String get channelName => (super.noSuchMethod( + Invocation.getter(#channelName), + returnValue: '', + returnValueForMissingStub: '', + ) as String); + @override + void Function(String) get postMessage => (super.noSuchMethod( + Invocation.getter(#postMessage), + returnValue: (String message) {}, + returnValueForMissingStub: (String message) {}, + ) as void Function(String)); + @override + _i2.JavaScriptChannel copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeJavaScriptChannel_9( + this, + Invocation.method( + #copy, + [], + ), + ), + returnValueForMissingStub: _FakeJavaScriptChannel_9( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.JavaScriptChannel); +} + +/// A class which mocks [WebChromeClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { + @override + _i2.WebChromeClient copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebChromeClient_0( + this, + Invocation.method( + #copy, + [], + ), + ), + returnValueForMissingStub: _FakeWebChromeClient_0( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebChromeClient); +} + +/// A class which mocks [WebSettings]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebSettings extends _i1.Mock implements _i2.WebSettings { + @override + _i7.Future setDomStorageEnabled(bool? flag) => (super.noSuchMethod( + Invocation.method( + #setDomStorageEnabled, + [flag], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => + (super.noSuchMethod( + Invocation.method( + #setJavaScriptCanOpenWindowsAutomatically, + [flag], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setSupportMultipleWindows(bool? support) => + (super.noSuchMethod( + Invocation.method( + #setSupportMultipleWindows, + [support], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setJavaScriptEnabled(bool? flag) => (super.noSuchMethod( + Invocation.method( + #setJavaScriptEnabled, + [flag], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setUserAgentString(String? userAgentString) => + (super.noSuchMethod( + Invocation.method( + #setUserAgentString, + [userAgentString], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setMediaPlaybackRequiresUserGesture(bool? require) => + (super.noSuchMethod( + Invocation.method( + #setMediaPlaybackRequiresUserGesture, + [require], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setSupportZoom(bool? support) => (super.noSuchMethod( + Invocation.method( + #setSupportZoom, + [support], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setLoadWithOverviewMode(bool? overview) => + (super.noSuchMethod( + Invocation.method( + #setLoadWithOverviewMode, + [overview], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setUseWideViewPort(bool? use) => (super.noSuchMethod( + Invocation.method( + #setUseWideViewPort, + [use], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setDisplayZoomControls(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #setDisplayZoomControls, + [enabled], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setBuiltInZoomControls(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #setBuiltInZoomControls, + [enabled], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setAllowFileAccess(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #setAllowFileAccess, + [enabled], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i2.WebSettings copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebSettings_12( + this, + Invocation.method( + #copy, + [], + ), + ), + returnValueForMissingStub: _FakeWebSettings_12( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebSettings); +} + +/// A class which mocks [WebView]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebView extends _i1.Mock implements _i2.WebView { + @override + bool get useHybridComposition => (super.noSuchMethod( + Invocation.getter(#useHybridComposition), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + _i2.WebSettings get settings => (super.noSuchMethod( + Invocation.getter(#settings), + returnValue: _FakeWebSettings_12( + this, + Invocation.getter(#settings), + ), + returnValueForMissingStub: _FakeWebSettings_12( + this, + Invocation.getter(#settings), + ), + ) as _i2.WebSettings); + @override + _i7.Future loadData({ + required String? data, + String? mimeType, + String? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #loadData, + [], + { + #data: data, + #mimeType: mimeType, + #encoding: encoding, + }, + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future loadDataWithBaseUrl({ + String? baseUrl, + required String? data, + String? mimeType, + String? encoding, + String? historyUrl, + }) => + (super.noSuchMethod( + Invocation.method( + #loadDataWithBaseUrl, + [], + { + #baseUrl: baseUrl, + #data: data, + #mimeType: mimeType, + #encoding: encoding, + #historyUrl: historyUrl, + }, + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future loadUrl( + String? url, + Map? headers, + ) => + (super.noSuchMethod( + Invocation.method( + #loadUrl, + [ + url, + headers, + ], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future postUrl( + String? url, + _i12.Uint8List? data, + ) => + (super.noSuchMethod( + Invocation.method( + #postUrl, + [ + url, + data, + ], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future getUrl() => (super.noSuchMethod( + Invocation.method( + #getUrl, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future canGoBack() => (super.noSuchMethod( + Invocation.method( + #canGoBack, + [], + ), + returnValue: _i7.Future.value(false), + returnValueForMissingStub: _i7.Future.value(false), + ) as _i7.Future); + @override + _i7.Future canGoForward() => (super.noSuchMethod( + Invocation.method( + #canGoForward, + [], + ), + returnValue: _i7.Future.value(false), + returnValueForMissingStub: _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(bool? includeDiskFiles) => (super.noSuchMethod( + Invocation.method( + #clearCache, + [includeDiskFiles], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future evaluateJavascript(String? javascriptString) => + (super.noSuchMethod( + Invocation.method( + #evaluateJavascript, + [javascriptString], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future getTitle() => (super.noSuchMethod( + Invocation.method( + #getTitle, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _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 getScrollX() => (super.noSuchMethod( + Invocation.method( + #getScrollX, + [], + ), + returnValue: _i7.Future.value(0), + returnValueForMissingStub: _i7.Future.value(0), + ) as _i7.Future); + @override + _i7.Future getScrollY() => (super.noSuchMethod( + Invocation.method( + #getScrollY, + [], + ), + returnValue: _i7.Future.value(0), + returnValueForMissingStub: _i7.Future.value(0), + ) as _i7.Future); + @override + _i7.Future<_i4.Offset> getScrollPosition() => (super.noSuchMethod( + Invocation.method( + #getScrollPosition, + [], + ), + returnValue: _i7.Future<_i4.Offset>.value(_FakeOffset_6( + this, + Invocation.method( + #getScrollPosition, + [], + ), + )), + returnValueForMissingStub: _i7.Future<_i4.Offset>.value(_FakeOffset_6( + this, + Invocation.method( + #getScrollPosition, + [], + ), + )), + ) as _i7.Future<_i4.Offset>); + @override + _i7.Future setWebViewClient(_i2.WebViewClient? webViewClient) => + (super.noSuchMethod( + Invocation.method( + #setWebViewClient, + [webViewClient], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future addJavaScriptChannel( + _i2.JavaScriptChannel? javaScriptChannel) => + (super.noSuchMethod( + Invocation.method( + #addJavaScriptChannel, + [javaScriptChannel], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future removeJavaScriptChannel( + _i2.JavaScriptChannel? javaScriptChannel) => + (super.noSuchMethod( + Invocation.method( + #removeJavaScriptChannel, + [javaScriptChannel], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setDownloadListener(_i2.DownloadListener? listener) => + (super.noSuchMethod( + Invocation.method( + #setDownloadListener, + [listener], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setWebChromeClient(_i2.WebChromeClient? client) => + (super.noSuchMethod( + Invocation.method( + #setWebChromeClient, + [client], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i7.Future setBackgroundColor(_i4.Color? color) => (super.noSuchMethod( + Invocation.method( + #setBackgroundColor, + [color], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i2.WebView copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebView_7( + this, + Invocation.method( + #copy, + [], + ), + ), + returnValueForMissingStub: _FakeWebView_7( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebView); +} + +/// A class which mocks [WebViewClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewClient extends _i1.Mock implements _i2.WebViewClient { + @override + _i7.Future setSynchronousReturnValueForShouldOverrideUrlLoading( + bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForShouldOverrideUrlLoading, + [value], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i2.WebViewClient copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebViewClient_1( + this, + Invocation.method( + #copy, + [], + ), + ), + returnValueForMissingStub: _FakeWebViewClient_1( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebViewClient); +} + +/// A class which mocks [WebStorage]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebStorage extends _i1.Mock implements _i2.WebStorage { + @override + _i7.Future deleteAllData() => (super.noSuchMethod( + Invocation.method( + #deleteAllData, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + _i2.WebStorage copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebStorage_13( + this, + Invocation.method( + #copy, + [], + ), + ), + returnValueForMissingStub: _FakeWebStorage_13( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebStorage); +} + +/// A class which mocks [InstanceManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockInstanceManager extends _i1.Mock implements _i5.InstanceManager { + @override + void Function(int) get onWeakReferenceRemoved => (super.noSuchMethod( + Invocation.getter(#onWeakReferenceRemoved), + returnValue: (int __p0) {}, + returnValueForMissingStub: (int __p0) {}, + ) as void Function(int)); + @override + set onWeakReferenceRemoved(void Function(int)? _onWeakReferenceRemoved) => + super.noSuchMethod( + Invocation.setter( + #onWeakReferenceRemoved, + _onWeakReferenceRemoved, + ), + returnValueForMissingStub: null, + ); + @override + int addDartCreatedInstance(_i5.Copyable? instance) => (super.noSuchMethod( + Invocation.method( + #addDartCreatedInstance, + [instance], + ), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + @override + int? removeWeakReference(_i5.Copyable? instance) => (super.noSuchMethod( + Invocation.method( + #removeWeakReference, + [instance], + ), + returnValueForMissingStub: null, + ) as int?); + @override + T? remove(int? identifier) => (super.noSuchMethod( + Invocation.method( + #remove, + [identifier], + ), + returnValueForMissingStub: null, + ) as T?); + @override + T? getInstanceWithWeakReference(int? identifier) => + (super.noSuchMethod( + Invocation.method( + #getInstanceWithWeakReference, + [identifier], + ), + returnValueForMissingStub: null, + ) as T?); + @override + int? getIdentifier(_i5.Copyable? instance) => (super.noSuchMethod( + Invocation.method( + #getIdentifier, + [instance], + ), + returnValueForMissingStub: null, + ) as int?); + @override + void addHostCreatedInstance( + _i5.Copyable? instance, + int? identifier, + ) => + super.noSuchMethod( + Invocation.method( + #addHostCreatedInstance, + [ + instance, + identifier, + ], + ), + returnValueForMissingStub: null, + ); + @override + bool containsIdentifier(int? identifier) => (super.noSuchMethod( + Invocation.method( + #containsIdentifier, + [identifier], + ), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); +} diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.dart new file mode 100644 index 000000000000..9e7422fba88a --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.dart @@ -0,0 +1,79 @@ +// 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:webview_flutter_android/src/android_webview.dart' + as android_webview; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_platform_interface/src/webview_platform.dart'; + +import 'android_webview_cookie_manager_test.mocks.dart'; + +@GenerateMocks([android_webview.CookieManager]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + test('clearCookies should call android_webview.clearCookies', () async { + final android_webview.CookieManager mockCookieManager = MockCookieManager(); + + when(mockCookieManager.clearCookies()) + .thenAnswer((_) => Future.value(true)); + + final AndroidWebViewCookieManagerCreationParams params = + AndroidWebViewCookieManagerCreationParams + .fromPlatformWebViewCookieManagerCreationParams( + const PlatformWebViewCookieManagerCreationParams()); + + final bool hasClearedCookies = await AndroidWebViewCookieManager(params, + cookieManager: mockCookieManager) + .clearCookies(); + + expect(hasClearedCookies, true); + verify(mockCookieManager.clearCookies()); + }); + + test('setCookie should throw ArgumentError for cookie with invalid path', () { + final AndroidWebViewCookieManagerCreationParams params = + AndroidWebViewCookieManagerCreationParams + .fromPlatformWebViewCookieManagerCreationParams( + const PlatformWebViewCookieManagerCreationParams()); + + final AndroidWebViewCookieManager androidCookieManager = + AndroidWebViewCookieManager(params, cookieManager: MockCookieManager()); + + expect( + () => androidCookieManager.setCookie(const WebViewCookie( + name: 'foo', + value: 'bar', + domain: 'flutter.dev', + path: 'invalid;path', + )), + throwsA(const TypeMatcher()), + ); + }); + + test( + 'setCookie should call android_webview.csetCookie with properly formatted cookie value', + () { + final android_webview.CookieManager mockCookieManager = MockCookieManager(); + final AndroidWebViewCookieManagerCreationParams params = + AndroidWebViewCookieManagerCreationParams + .fromPlatformWebViewCookieManagerCreationParams( + const PlatformWebViewCookieManagerCreationParams()); + + AndroidWebViewCookieManager(params, cookieManager: mockCookieManager) + .setCookie(const WebViewCookie( + name: 'foo&', + value: 'bar@', + domain: 'flutter.dev', + )); + + verify(mockCookieManager.setCookie( + 'flutter.dev', + 'foo%26=bar%40; path=/', + )); + }); +} diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart similarity index 52% rename from packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.mocks.dart rename to packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart index 308aba4fa1b0..07321805a559 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart @@ -1,7 +1,8 @@ -// Mocks generated by Mockito 5.2.0 from annotations -// in webview_flutter_android/test/webview_android_cookie_manager_test.dart. +// Mocks generated by Mockito 5.3.2 from annotations +// in webview_flutter_android/test/android_webview_cookie_manager_test.dart. // Do not manually edit this file. +// ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; @@ -16,6 +17,7 @@ import 'package:webview_flutter_android/src/android_webview.dart' as _i2; // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class /// A class which mocks [CookieManager]. /// @@ -26,12 +28,27 @@ class MockCookieManager extends _i1.Mock implements _i2.CookieManager { } @override - _i3.Future setCookie(String? url, String? value) => - (super.noSuchMethod(Invocation.method(#setCookie, [url, value]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i3.Future); + _i3.Future setCookie( + String? url, + String? value, + ) => + (super.noSuchMethod( + Invocation.method( + #setCookie, + [ + url, + value, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); @override - _i3.Future clearCookies() => - (super.noSuchMethod(Invocation.method(#clearCookies, []), - returnValue: Future.value(false)) as _i3.Future); + _i3.Future clearCookies() => (super.noSuchMethod( + Invocation.method( + #clearCookies, + [], + ), + returnValue: _i3.Future.value(false), + ) as _i3.Future); } diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart index f3ec4bd0cb9f..4e972fe7b98d 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart @@ -269,7 +269,7 @@ void main() { final WebViewClient mockWebViewClient = MockWebViewClient(); when(mockWebViewClient.copy()).thenReturn(MockWebViewClient()); - when(mockWebViewClient.shouldOverrideUrlLoading).thenReturn(false); + instanceManager.addDartCreatedInstance(mockWebViewClient); webView.setWebViewClient(mockWebViewClient); final int webViewClientInstanceId = @@ -334,6 +334,7 @@ void main() { final DownloadListener mockDownloadListener = MockDownloadListener(); when(mockDownloadListener.copy()).thenReturn(MockDownloadListener()); + instanceManager.addDartCreatedInstance(mockDownloadListener); webView.setDownloadListener(mockDownloadListener); final int downloadListenerInstanceId = @@ -345,16 +346,6 @@ void main() { }); test('setWebChromeClient', () { - // Setting a WebChromeClient requires setting a WebViewClient first. - TestWebViewClientHostApi.setup(MockTestWebViewClientHostApi()); - WebViewClient.api = WebViewClientHostApiImpl( - instanceManager: instanceManager, - ); - final WebViewClient mockWebViewClient = MockWebViewClient(); - when(mockWebViewClient.copy()).thenReturn(MockWebViewClient()); - when(mockWebViewClient.shouldOverrideUrlLoading).thenReturn(false); - webView.setWebViewClient(mockWebViewClient); - TestWebChromeClientHostApi.setup(MockTestWebChromeClientHostApi()); WebChromeClient.api = WebChromeClientHostApiImpl( instanceManager: instanceManager, @@ -362,6 +353,7 @@ void main() { final WebChromeClient mockWebChromeClient = MockWebChromeClient(); when(mockWebChromeClient.copy()).thenReturn(MockWebChromeClient()); + instanceManager.addDartCreatedInstance(mockWebChromeClient); webView.setWebChromeClient(mockWebChromeClient); final int webChromeClientInstanceId = @@ -372,21 +364,6 @@ void main() { )); }); - test('release', () { - final MockTestWebSettingsHostApi mockWebSettingsPlatformHostApi = - MockTestWebSettingsHostApi(); - TestWebSettingsHostApi.setup(mockWebSettingsPlatformHostApi); - - WebSettings.api = - WebSettingsHostApiImpl(instanceManager: instanceManager); - final int webSettingsInstanceId = - instanceManager.getIdentifier(webView.settings)!; - - webView.release(); - verify(mockWebSettingsPlatformHostApi.dispose(webSettingsInstanceId)); - verify(mockPlatformHostApi.dispose(webViewInstanceId)); - }); - test('copy', () { expect(webView.copy(), isA()); }); @@ -544,16 +521,22 @@ void main() { }); test('postMessage', () { + late final String result; + when(mockJavaScriptChannel.postMessage).thenReturn((String message) { + result = message; + }); + flutterApi.postMessage( mockJavaScriptChannelInstanceId, 'Hello, World!', ); - verify(mockJavaScriptChannel.postMessage('Hello, World!')); + + expect(result, 'Hello, World!'); }); test('copy', () { expect( - JavaScriptChannel.detached('channel').copy(), + JavaScriptChannel.detached('channel', postMessage: (_) {}).copy(), isA(), ); }); @@ -588,30 +571,51 @@ void main() { }); test('onPageStarted', () { + late final List result; + when(mockWebViewClient.onPageStarted).thenReturn( + (WebView webView, String url) { + result = [webView, url]; + }, + ); + flutterApi.onPageStarted( mockWebViewClientInstanceId, mockWebViewInstanceId, 'https://www.google.com', ); - verify(mockWebViewClient.onPageStarted( - mockWebView, - 'https://www.google.com', - )); + + expect(result, [mockWebView, 'https://www.google.com']); }); test('onPageFinished', () { + late final List result; + when(mockWebViewClient.onPageFinished).thenReturn( + (WebView webView, String url) { + result = [webView, url]; + }, + ); + flutterApi.onPageFinished( mockWebViewClientInstanceId, mockWebViewInstanceId, 'https://www.google.com', ); - verify(mockWebViewClient.onPageFinished( - mockWebView, - 'https://www.google.com', - )); + + expect(result, [mockWebView, 'https://www.google.com']); }); test('onReceivedRequestError', () { + late final List result; + when(mockWebViewClient.onReceivedRequestError).thenReturn( + ( + WebView webView, + WebResourceRequest request, + WebResourceError error, + ) { + result = [webView, request, error]; + }, + ); + flutterApi.onReceivedRequestError( mockWebViewClientInstanceId, mockWebViewInstanceId, @@ -626,14 +630,25 @@ void main() { WebResourceErrorData(errorCode: 34, description: 'error description'), ); - verify(mockWebViewClient.onReceivedRequestError( - mockWebView, - argThat(isNotNull), - argThat(isNotNull), - )); + expect( + result, + containsAllInOrder([mockWebView, isNotNull, isNotNull]), + ); }); test('onReceivedError', () { + late final List result; + when(mockWebViewClient.onReceivedError).thenReturn( + ( + WebView webView, + int errorCode, + String description, + String failingUrl, + ) { + result = [webView, errorCode, description, failingUrl]; + }, + ); + flutterApi.onReceivedError( mockWebViewClientInstanceId, mockWebViewInstanceId, @@ -642,15 +657,22 @@ void main() { 'https://www.google.com', ); - verify(mockWebViewClient.onReceivedError( - mockWebView, - 14, - 'desc', - 'https://www.google.com', - )); + expect( + result, + containsAllInOrder( + [mockWebView, 14, 'desc', 'https://www.google.com'], + ), + ); }); test('requestLoading', () { + late final List result; + when(mockWebViewClient.requestLoading).thenReturn( + (WebView webView, WebResourceRequest request) { + result = [webView, request]; + }, + ); + flutterApi.requestLoading( mockWebViewClientInstanceId, mockWebViewInstanceId, @@ -664,20 +686,27 @@ void main() { ), ); - verify(mockWebViewClient.requestLoading( - mockWebView, - argThat(isNotNull), - )); + expect( + result, + containsAllInOrder([mockWebView, isNotNull]), + ); }); test('urlLoading', () { + late final List result; + when(mockWebViewClient.urlLoading).thenReturn( + (WebView webView, String url) { + result = [webView, url]; + }, + ); + flutterApi.urlLoading(mockWebViewClientInstanceId, mockWebViewInstanceId, 'https://www.google.com'); - verify(mockWebViewClient.urlLoading( - mockWebView, - 'https://www.google.com', - )); + expect( + result, + containsAllInOrder([mockWebView, 'https://www.google.com']), + ); }); test('copy', () { @@ -705,7 +734,26 @@ void main() { instanceManager.addDartCreatedInstance(mockDownloadListener); }); - test('onPageStarted', () { + test('onDownloadStart', () { + late final List result; + when(mockDownloadListener.onDownloadStart).thenReturn( + ( + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) { + result = [ + url, + userAgent, + contentDisposition, + mimetype, + contentLength, + ]; + }, + ); + flutterApi.onDownloadStart( mockDownloadListenerInstanceId, 'url', @@ -714,17 +762,26 @@ void main() { 'mimetype', 45, ); - verify(mockDownloadListener.onDownloadStart( - 'url', - 'userAgent', - 'contentDescription', - 'mimetype', - 45, - )); + + expect( + result, + containsAllInOrder([ + 'url', + 'userAgent', + 'contentDescription', + 'mimetype', + 45, + ]), + ); }); test('copy', () { - expect(DownloadListener.detached().copy(), isA()); + expect( + DownloadListener.detached( + onDownloadStart: (_, __, ____, _____, ______) {}, + ).copy(), + isA(), + ); }); }); @@ -757,13 +814,21 @@ void main() { instanceManager.addDartCreatedInstance(mockWebView); }); - test('onPageStarted', () { + test('onProgressChanged', () { + late final List result; + when(mockWebChromeClient.onProgressChanged).thenReturn( + (WebView webView, int progress) { + result = [webView, progress]; + }, + ); + flutterApi.onProgressChanged( mockWebChromeClientInstanceId, mockWebViewInstanceId, 76, ); - verify(mockWebChromeClient.onProgressChanged(mockWebView, 76)); + + expect(result, containsAllInOrder([mockWebView, 76])); }); test('copy', () { diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index 116ac834f1d6..81cdc9ef545f 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart @@ -1,7 +1,8 @@ -// Mocks generated by Mockito 5.2.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_android/test/android_webview_test.dart. // Do not manually edit this file. +// ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; import 'dart:typed_data' as _i7; import 'dart:ui' as _i4; @@ -21,24 +22,90 @@ import 'test_android_webview.pigeon.dart' as _i6; // 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 _FakeDownloadListener_0 extends _i1.Fake - implements _i2.DownloadListener {} +class _FakeDownloadListener_0 extends _i1.SmartFake + implements _i2.DownloadListener { + _FakeDownloadListener_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeJavaScriptChannel_1 extends _i1.Fake - implements _i2.JavaScriptChannel {} +class _FakeJavaScriptChannel_1 extends _i1.SmartFake + implements _i2.JavaScriptChannel { + _FakeJavaScriptChannel_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeWebViewPoint_2 extends _i1.Fake implements _i3.WebViewPoint {} +class _FakeWebViewPoint_2 extends _i1.SmartFake implements _i3.WebViewPoint { + _FakeWebViewPoint_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeWebChromeClient_3 extends _i1.Fake implements _i2.WebChromeClient {} +class _FakeWebChromeClient_3 extends _i1.SmartFake + implements _i2.WebChromeClient { + _FakeWebChromeClient_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeWebSettings_4 extends _i1.Fake implements _i2.WebSettings {} +class _FakeWebSettings_4 extends _i1.SmartFake implements _i2.WebSettings { + _FakeWebSettings_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeOffset_5 extends _i1.Fake implements _i4.Offset {} +class _FakeOffset_5 extends _i1.SmartFake implements _i4.Offset { + _FakeOffset_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeWebView_6 extends _i1.Fake implements _i2.WebView {} +class _FakeWebView_6 extends _i1.SmartFake implements _i2.WebView { + _FakeWebView_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeWebViewClient_7 extends _i1.Fake implements _i2.WebViewClient {} +class _FakeWebViewClient_7 extends _i1.SmartFake implements _i2.WebViewClient { + _FakeWebViewClient_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} /// A class which mocks [CookieManagerHostApi]. /// @@ -50,14 +117,29 @@ class MockCookieManagerHostApi extends _i1.Mock } @override - _i5.Future clearCookies() => - (super.noSuchMethod(Invocation.method(#clearCookies, []), - returnValue: Future.value(false)) as _i5.Future); - @override - _i5.Future setCookie(String? arg_url, String? arg_value) => - (super.noSuchMethod(Invocation.method(#setCookie, [arg_url, arg_value]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + _i5.Future clearCookies() => (super.noSuchMethod( + Invocation.method( + #clearCookies, + [], + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + _i5.Future setCookie( + String? arg_url, + String? arg_value, + ) => + (super.noSuchMethod( + Invocation.method( + #setCookie, + [ + arg_url, + arg_value, + ], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } /// A class which mocks [DownloadListener]. @@ -69,16 +151,42 @@ class MockDownloadListener extends _i1.Mock implements _i2.DownloadListener { } @override - void onDownloadStart(String? url, String? userAgent, - String? contentDisposition, String? mimetype, int? contentLength) => - super.noSuchMethod( - Invocation.method(#onDownloadStart, - [url, userAgent, contentDisposition, mimetype, contentLength]), - returnValueForMissingStub: null); - @override - _i2.DownloadListener copy() => - (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeDownloadListener_0()) as _i2.DownloadListener); + void Function( + String, + String, + String, + String, + int, + ) get onDownloadStart => (super.noSuchMethod( + Invocation.getter(#onDownloadStart), + returnValue: ( + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) {}, + ) as void Function( + String, + String, + String, + String, + int, + )); + @override + _i2.DownloadListener copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeDownloadListener_0( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.DownloadListener); } /// A class which mocks [JavaScriptChannel]. @@ -90,17 +198,29 @@ class MockJavaScriptChannel extends _i1.Mock implements _i2.JavaScriptChannel { } @override - String get channelName => - (super.noSuchMethod(Invocation.getter(#channelName), returnValue: '') - as String); - @override - void postMessage(String? message) => - super.noSuchMethod(Invocation.method(#postMessage, [message]), - returnValueForMissingStub: null); - @override - _i2.JavaScriptChannel copy() => - (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeJavaScriptChannel_1()) as _i2.JavaScriptChannel); + String get channelName => (super.noSuchMethod( + Invocation.getter(#channelName), + returnValue: '', + ) as String); + @override + void Function(String) get postMessage => (super.noSuchMethod( + Invocation.getter(#postMessage), + returnValue: (String message) {}, + ) as void Function(String)); + @override + _i2.JavaScriptChannel copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeJavaScriptChannel_1( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.JavaScriptChannel); } /// A class which mocks [TestDownloadListenerHostApi]. @@ -113,9 +233,13 @@ class MockTestDownloadListenerHostApi extends _i1.Mock } @override - void create(int? instanceId) => - super.noSuchMethod(Invocation.method(#create, [instanceId]), - returnValueForMissingStub: null); + void create(int? instanceId) => super.noSuchMethod( + Invocation.method( + #create, + [instanceId], + ), + returnValueForMissingStub: null, + ); } /// A class which mocks [TestJavaObjectHostApi]. @@ -128,9 +252,13 @@ class MockTestJavaObjectHostApi extends _i1.Mock } @override - void dispose(int? identifier) => - super.noSuchMethod(Invocation.method(#dispose, [identifier]), - returnValueForMissingStub: null); + void dispose(int? identifier) => super.noSuchMethod( + Invocation.method( + #dispose, + [identifier], + ), + returnValueForMissingStub: null, + ); } /// A class which mocks [TestJavaScriptChannelHostApi]. @@ -143,9 +271,20 @@ class MockTestJavaScriptChannelHostApi extends _i1.Mock } @override - void create(int? instanceId, String? channelName) => - super.noSuchMethod(Invocation.method(#create, [instanceId, channelName]), - returnValueForMissingStub: null); + void create( + int? instanceId, + String? channelName, + ) => + super.noSuchMethod( + Invocation.method( + #create, + [ + instanceId, + channelName, + ], + ), + returnValueForMissingStub: null, + ); } /// A class which mocks [TestWebChromeClientHostApi]. @@ -158,10 +297,13 @@ class MockTestWebChromeClientHostApi extends _i1.Mock } @override - void create(int? instanceId, int? webViewClientInstanceId) => - super.noSuchMethod( - Invocation.method(#create, [instanceId, webViewClientInstanceId]), - returnValueForMissingStub: null); + void create(int? instanceId) => super.noSuchMethod( + Invocation.method( + #create, + [instanceId], + ), + returnValueForMissingStub: null, + ); } /// A class which mocks [TestWebSettingsHostApi]. @@ -174,70 +316,200 @@ class MockTestWebSettingsHostApi extends _i1.Mock } @override - void create(int? instanceId, int? webViewInstanceId) => super.noSuchMethod( - Invocation.method(#create, [instanceId, webViewInstanceId]), - returnValueForMissingStub: null); - @override - void dispose(int? instanceId) => - super.noSuchMethod(Invocation.method(#dispose, [instanceId]), - returnValueForMissingStub: null); - @override - void setDomStorageEnabled(int? instanceId, bool? flag) => super.noSuchMethod( - Invocation.method(#setDomStorageEnabled, [instanceId, flag]), - returnValueForMissingStub: null); - @override - void setJavaScriptCanOpenWindowsAutomatically(int? instanceId, bool? flag) => + void create( + int? instanceId, + int? webViewInstanceId, + ) => super.noSuchMethod( - Invocation.method( - #setJavaScriptCanOpenWindowsAutomatically, [instanceId, flag]), - returnValueForMissingStub: null); - @override - void setSupportMultipleWindows(int? instanceId, bool? support) => + Invocation.method( + #create, + [ + instanceId, + webViewInstanceId, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setDomStorageEnabled( + int? instanceId, + bool? flag, + ) => super.noSuchMethod( - Invocation.method(#setSupportMultipleWindows, [instanceId, support]), - returnValueForMissingStub: null); - @override - void setJavaScriptEnabled(int? instanceId, bool? flag) => super.noSuchMethod( - Invocation.method(#setJavaScriptEnabled, [instanceId, flag]), - returnValueForMissingStub: null); - @override - void setUserAgentString(int? instanceId, String? userAgentString) => + Invocation.method( + #setDomStorageEnabled, + [ + instanceId, + flag, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setJavaScriptCanOpenWindowsAutomatically( + int? instanceId, + bool? flag, + ) => super.noSuchMethod( - Invocation.method(#setUserAgentString, [instanceId, userAgentString]), - returnValueForMissingStub: null); - @override - void setMediaPlaybackRequiresUserGesture(int? instanceId, bool? require) => + Invocation.method( + #setJavaScriptCanOpenWindowsAutomatically, + [ + instanceId, + flag, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setSupportMultipleWindows( + int? instanceId, + bool? support, + ) => super.noSuchMethod( - Invocation.method( - #setMediaPlaybackRequiresUserGesture, [instanceId, require]), - returnValueForMissingStub: null); - @override - void setSupportZoom(int? instanceId, bool? support) => super.noSuchMethod( - Invocation.method(#setSupportZoom, [instanceId, support]), - returnValueForMissingStub: null); - @override - void setLoadWithOverviewMode(int? instanceId, bool? overview) => + Invocation.method( + #setSupportMultipleWindows, + [ + instanceId, + support, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setJavaScriptEnabled( + int? instanceId, + bool? flag, + ) => super.noSuchMethod( - Invocation.method(#setLoadWithOverviewMode, [instanceId, overview]), - returnValueForMissingStub: null); - @override - void setUseWideViewPort(int? instanceId, bool? use) => super.noSuchMethod( - Invocation.method(#setUseWideViewPort, [instanceId, use]), - returnValueForMissingStub: null); - @override - void setDisplayZoomControls(int? instanceId, bool? enabled) => + Invocation.method( + #setJavaScriptEnabled, + [ + instanceId, + flag, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setUserAgentString( + int? instanceId, + String? userAgentString, + ) => super.noSuchMethod( - Invocation.method(#setDisplayZoomControls, [instanceId, enabled]), - returnValueForMissingStub: null); - @override - void setBuiltInZoomControls(int? instanceId, bool? enabled) => + Invocation.method( + #setUserAgentString, + [ + instanceId, + userAgentString, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setMediaPlaybackRequiresUserGesture( + int? instanceId, + bool? require, + ) => super.noSuchMethod( - Invocation.method(#setBuiltInZoomControls, [instanceId, enabled]), - returnValueForMissingStub: null); - @override - void setAllowFileAccess(int? instanceId, bool? enabled) => super.noSuchMethod( - Invocation.method(#setAllowFileAccess, [instanceId, enabled]), - returnValueForMissingStub: null); + Invocation.method( + #setMediaPlaybackRequiresUserGesture, + [ + instanceId, + require, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setSupportZoom( + int? instanceId, + bool? support, + ) => + super.noSuchMethod( + Invocation.method( + #setSupportZoom, + [ + instanceId, + support, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setLoadWithOverviewMode( + int? instanceId, + bool? overview, + ) => + super.noSuchMethod( + Invocation.method( + #setLoadWithOverviewMode, + [ + instanceId, + overview, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setUseWideViewPort( + int? instanceId, + bool? use, + ) => + super.noSuchMethod( + Invocation.method( + #setUseWideViewPort, + [ + instanceId, + use, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setDisplayZoomControls( + int? instanceId, + bool? enabled, + ) => + super.noSuchMethod( + Invocation.method( + #setDisplayZoomControls, + [ + instanceId, + enabled, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setBuiltInZoomControls( + int? instanceId, + bool? enabled, + ) => + super.noSuchMethod( + Invocation.method( + #setBuiltInZoomControls, + [ + instanceId, + enabled, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setAllowFileAccess( + int? instanceId, + bool? enabled, + ) => + super.noSuchMethod( + Invocation.method( + #setAllowFileAccess, + [ + instanceId, + enabled, + ], + ), + returnValueForMissingStub: null, + ); } /// A class which mocks [TestWebStorageHostApi]. @@ -250,13 +522,21 @@ class MockTestWebStorageHostApi extends _i1.Mock } @override - void create(int? instanceId) => - super.noSuchMethod(Invocation.method(#create, [instanceId]), - returnValueForMissingStub: null); - @override - void deleteAllData(int? instanceId) => - super.noSuchMethod(Invocation.method(#deleteAllData, [instanceId]), - returnValueForMissingStub: null); + void create(int? instanceId) => super.noSuchMethod( + Invocation.method( + #create, + [instanceId], + ), + returnValueForMissingStub: null, + ); + @override + void deleteAllData(int? instanceId) => super.noSuchMethod( + Invocation.method( + #deleteAllData, + [instanceId], + ), + returnValueForMissingStub: null, + ); } /// A class which mocks [TestWebViewClientHostApi]. @@ -269,10 +549,28 @@ class MockTestWebViewClientHostApi extends _i1.Mock } @override - void create(int? instanceId, bool? shouldOverrideUrlLoading) => + void create(int? instanceId) => super.noSuchMethod( + Invocation.method( + #create, + [instanceId], + ), + returnValueForMissingStub: null, + ); + @override + void setSynchronousReturnValueForShouldOverrideUrlLoading( + int? instanceId, + bool? value, + ) => super.noSuchMethod( - Invocation.method(#create, [instanceId, shouldOverrideUrlLoading]), - returnValueForMissingStub: null); + Invocation.method( + #setSynchronousReturnValueForShouldOverrideUrlLoading, + [ + instanceId, + value, + ], + ), + returnValueForMissingStub: null, + ); } /// A class which mocks [TestWebViewHostApi]. @@ -285,135 +583,338 @@ class MockTestWebViewHostApi extends _i1.Mock } @override - void create(int? instanceId, bool? useHybridComposition) => + void create( + int? instanceId, + bool? useHybridComposition, + ) => super.noSuchMethod( - Invocation.method(#create, [instanceId, useHybridComposition]), - returnValueForMissingStub: null); - @override - void dispose(int? instanceId) => - super.noSuchMethod(Invocation.method(#dispose, [instanceId]), - returnValueForMissingStub: null); + Invocation.method( + #create, + [ + instanceId, + useHybridComposition, + ], + ), + returnValueForMissingStub: null, + ); @override void loadData( - int? instanceId, String? data, String? mimeType, String? encoding) => + int? instanceId, + String? data, + String? mimeType, + String? encoding, + ) => super.noSuchMethod( - Invocation.method(#loadData, [instanceId, data, mimeType, encoding]), - returnValueForMissingStub: null); - @override - void loadDataWithBaseUrl(int? instanceId, String? baseUrl, String? data, - String? mimeType, String? encoding, String? historyUrl) => + Invocation.method( + #loadData, + [ + instanceId, + data, + mimeType, + encoding, + ], + ), + returnValueForMissingStub: null, + ); + @override + void loadDataWithBaseUrl( + int? instanceId, + String? baseUrl, + String? data, + String? mimeType, + String? encoding, + String? historyUrl, + ) => super.noSuchMethod( - Invocation.method(#loadDataWithBaseUrl, - [instanceId, baseUrl, data, mimeType, encoding, historyUrl]), - returnValueForMissingStub: null); - @override - void loadUrl(int? instanceId, String? url, Map? headers) => + Invocation.method( + #loadDataWithBaseUrl, + [ + instanceId, + baseUrl, + data, + mimeType, + encoding, + historyUrl, + ], + ), + returnValueForMissingStub: null, + ); + @override + void loadUrl( + int? instanceId, + String? url, + Map? headers, + ) => super.noSuchMethod( - Invocation.method(#loadUrl, [instanceId, url, headers]), - returnValueForMissingStub: null); - @override - void postUrl(int? instanceId, String? url, _i7.Uint8List? data) => - super.noSuchMethod(Invocation.method(#postUrl, [instanceId, url, data]), - returnValueForMissingStub: null); - @override - String? getUrl(int? instanceId) => - (super.noSuchMethod(Invocation.method(#getUrl, [instanceId])) as String?); - @override - bool canGoBack(int? instanceId) => - (super.noSuchMethod(Invocation.method(#canGoBack, [instanceId]), - returnValue: false) as bool); - @override - bool canGoForward(int? instanceId) => - (super.noSuchMethod(Invocation.method(#canGoForward, [instanceId]), - returnValue: false) as bool); - @override - void goBack(int? instanceId) => - super.noSuchMethod(Invocation.method(#goBack, [instanceId]), - returnValueForMissingStub: null); - @override - void goForward(int? instanceId) => - super.noSuchMethod(Invocation.method(#goForward, [instanceId]), - returnValueForMissingStub: null); - @override - void reload(int? instanceId) => - super.noSuchMethod(Invocation.method(#reload, [instanceId]), - returnValueForMissingStub: null); - @override - void clearCache(int? instanceId, bool? includeDiskFiles) => + Invocation.method( + #loadUrl, + [ + instanceId, + url, + headers, + ], + ), + returnValueForMissingStub: null, + ); + @override + void postUrl( + int? instanceId, + String? url, + _i7.Uint8List? data, + ) => + super.noSuchMethod( + Invocation.method( + #postUrl, + [ + instanceId, + url, + data, + ], + ), + returnValueForMissingStub: null, + ); + @override + String? getUrl(int? instanceId) => (super.noSuchMethod(Invocation.method( + #getUrl, + [instanceId], + )) as String?); + @override + bool canGoBack(int? instanceId) => (super.noSuchMethod( + Invocation.method( + #canGoBack, + [instanceId], + ), + returnValue: false, + ) as bool); + @override + bool canGoForward(int? instanceId) => (super.noSuchMethod( + Invocation.method( + #canGoForward, + [instanceId], + ), + returnValue: false, + ) as bool); + @override + void goBack(int? instanceId) => super.noSuchMethod( + Invocation.method( + #goBack, + [instanceId], + ), + returnValueForMissingStub: null, + ); + @override + void goForward(int? instanceId) => super.noSuchMethod( + Invocation.method( + #goForward, + [instanceId], + ), + returnValueForMissingStub: null, + ); + @override + void reload(int? instanceId) => super.noSuchMethod( + Invocation.method( + #reload, + [instanceId], + ), + returnValueForMissingStub: null, + ); + @override + void clearCache( + int? instanceId, + bool? includeDiskFiles, + ) => super.noSuchMethod( - Invocation.method(#clearCache, [instanceId, includeDiskFiles]), - returnValueForMissingStub: null); + Invocation.method( + #clearCache, + [ + instanceId, + includeDiskFiles, + ], + ), + returnValueForMissingStub: null, + ); @override _i5.Future evaluateJavascript( - int? instanceId, String? javascriptString) => + int? instanceId, + String? javascriptString, + ) => (super.noSuchMethod( + Invocation.method( + #evaluateJavascript, + [ + instanceId, + javascriptString, + ], + ), + returnValue: _i5.Future.value(), + ) as _i5.Future); + @override + String? getTitle(int? instanceId) => (super.noSuchMethod(Invocation.method( + #getTitle, + [instanceId], + )) as String?); + @override + void scrollTo( + int? instanceId, + int? x, + int? y, + ) => + super.noSuchMethod( + Invocation.method( + #scrollTo, + [ + instanceId, + x, + y, + ], + ), + returnValueForMissingStub: null, + ); + @override + void scrollBy( + int? instanceId, + int? x, + int? y, + ) => + super.noSuchMethod( + Invocation.method( + #scrollBy, + [ + instanceId, + x, + y, + ], + ), + returnValueForMissingStub: null, + ); + @override + int getScrollX(int? instanceId) => (super.noSuchMethod( + Invocation.method( + #getScrollX, + [instanceId], + ), + returnValue: 0, + ) as int); + @override + int getScrollY(int? instanceId) => (super.noSuchMethod( + Invocation.method( + #getScrollY, + [instanceId], + ), + returnValue: 0, + ) as int); + @override + _i3.WebViewPoint getScrollPosition(int? instanceId) => (super.noSuchMethod( + Invocation.method( + #getScrollPosition, + [instanceId], + ), + returnValue: _FakeWebViewPoint_2( + this, Invocation.method( - #evaluateJavascript, [instanceId, javascriptString]), - returnValue: Future.value()) as _i5.Future); - @override - String? getTitle(int? instanceId) => - (super.noSuchMethod(Invocation.method(#getTitle, [instanceId])) - as String?); - @override - void scrollTo(int? instanceId, int? x, int? y) => - super.noSuchMethod(Invocation.method(#scrollTo, [instanceId, x, y]), - returnValueForMissingStub: null); - @override - void scrollBy(int? instanceId, int? x, int? y) => - super.noSuchMethod(Invocation.method(#scrollBy, [instanceId, x, y]), - returnValueForMissingStub: null); - @override - int getScrollX(int? instanceId) => - (super.noSuchMethod(Invocation.method(#getScrollX, [instanceId]), - returnValue: 0) as int); - @override - int getScrollY(int? instanceId) => - (super.noSuchMethod(Invocation.method(#getScrollY, [instanceId]), - returnValue: 0) as int); - @override - _i3.WebViewPoint getScrollPosition(int? instanceId) => - (super.noSuchMethod(Invocation.method(#getScrollPosition, [instanceId]), - returnValue: _FakeWebViewPoint_2()) as _i3.WebViewPoint); + #getScrollPosition, + [instanceId], + ), + ), + ) as _i3.WebViewPoint); @override void setWebContentsDebuggingEnabled(bool? enabled) => super.noSuchMethod( - Invocation.method(#setWebContentsDebuggingEnabled, [enabled]), - returnValueForMissingStub: null); - @override - void setWebViewClient(int? instanceId, int? webViewClientInstanceId) => + Invocation.method( + #setWebContentsDebuggingEnabled, + [enabled], + ), + returnValueForMissingStub: null, + ); + @override + void setWebViewClient( + int? instanceId, + int? webViewClientInstanceId, + ) => super.noSuchMethod( - Invocation.method( - #setWebViewClient, [instanceId, webViewClientInstanceId]), - returnValueForMissingStub: null); + Invocation.method( + #setWebViewClient, + [ + instanceId, + webViewClientInstanceId, + ], + ), + returnValueForMissingStub: null, + ); @override void addJavaScriptChannel( - int? instanceId, int? javaScriptChannelInstanceId) => + int? instanceId, + int? javaScriptChannelInstanceId, + ) => super.noSuchMethod( - Invocation.method( - #addJavaScriptChannel, [instanceId, javaScriptChannelInstanceId]), - returnValueForMissingStub: null); + Invocation.method( + #addJavaScriptChannel, + [ + instanceId, + javaScriptChannelInstanceId, + ], + ), + returnValueForMissingStub: null, + ); @override void removeJavaScriptChannel( - int? instanceId, int? javaScriptChannelInstanceId) => + int? instanceId, + int? javaScriptChannelInstanceId, + ) => super.noSuchMethod( - Invocation.method(#removeJavaScriptChannel, - [instanceId, javaScriptChannelInstanceId]), - returnValueForMissingStub: null); - @override - void setDownloadListener(int? instanceId, int? listenerInstanceId) => + Invocation.method( + #removeJavaScriptChannel, + [ + instanceId, + javaScriptChannelInstanceId, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setDownloadListener( + int? instanceId, + int? listenerInstanceId, + ) => super.noSuchMethod( - Invocation.method( - #setDownloadListener, [instanceId, listenerInstanceId]), - returnValueForMissingStub: null); - @override - void setWebChromeClient(int? instanceId, int? clientInstanceId) => + Invocation.method( + #setDownloadListener, + [ + instanceId, + listenerInstanceId, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setWebChromeClient( + int? instanceId, + int? clientInstanceId, + ) => super.noSuchMethod( - Invocation.method( - #setWebChromeClient, [instanceId, clientInstanceId]), - returnValueForMissingStub: null); - @override - void setBackgroundColor(int? instanceId, int? color) => super.noSuchMethod( - Invocation.method(#setBackgroundColor, [instanceId, color]), - returnValueForMissingStub: null); + Invocation.method( + #setWebChromeClient, + [ + instanceId, + clientInstanceId, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setBackgroundColor( + int? instanceId, + int? color, + ) => + super.noSuchMethod( + Invocation.method( + #setBackgroundColor, + [ + instanceId, + color, + ], + ), + returnValueForMissingStub: null, + ); } /// A class which mocks [TestAssetManagerHostApi]. @@ -426,13 +927,21 @@ class MockTestAssetManagerHostApi extends _i1.Mock } @override - List list(String? path) => - (super.noSuchMethod(Invocation.method(#list, [path]), - returnValue: []) as List); - @override - String getAssetFilePathByName(String? name) => - (super.noSuchMethod(Invocation.method(#getAssetFilePathByName, [name]), - returnValue: '') as String); + List list(String? path) => (super.noSuchMethod( + Invocation.method( + #list, + [path], + ), + returnValue: [], + ) as List); + @override + String getAssetFilePathByName(String? name) => (super.noSuchMethod( + Invocation.method( + #getAssetFilePathByName, + [name], + ), + returnValue: '', + ) as String); } /// A class which mocks [WebChromeClient]. @@ -444,13 +953,19 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { } @override - void onProgressChanged(_i2.WebView? webView, int? progress) => super - .noSuchMethod(Invocation.method(#onProgressChanged, [webView, progress]), - returnValueForMissingStub: null); - @override - _i2.WebChromeClient copy() => - (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeWebChromeClient_3()) as _i2.WebChromeClient); + _i2.WebChromeClient copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebChromeClient_3( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebChromeClient); } /// A class which mocks [WebView]. @@ -462,153 +977,306 @@ class MockWebView extends _i1.Mock implements _i2.WebView { } @override - bool get useHybridComposition => - (super.noSuchMethod(Invocation.getter(#useHybridComposition), - returnValue: false) as bool); - @override - _i2.WebSettings get settings => - (super.noSuchMethod(Invocation.getter(#settings), - returnValue: _FakeWebSettings_4()) as _i2.WebSettings); - @override - _i5.Future loadData( - {String? data, String? mimeType, String? encoding}) => + bool get useHybridComposition => (super.noSuchMethod( + Invocation.getter(#useHybridComposition), + returnValue: false, + ) as bool); + @override + _i2.WebSettings get settings => (super.noSuchMethod( + Invocation.getter(#settings), + returnValue: _FakeWebSettings_4( + this, + Invocation.getter(#settings), + ), + ) as _i2.WebSettings); + @override + _i5.Future loadData({ + required String? data, + String? mimeType, + String? encoding, + }) => (super.noSuchMethod( - Invocation.method(#loadData, [], - {#data: data, #mimeType: mimeType, #encoding: encoding}), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future loadDataWithBaseUrl( - {String? baseUrl, - String? data, - String? mimeType, - String? encoding, - String? historyUrl}) => + Invocation.method( + #loadData, + [], + { + #data: data, + #mimeType: mimeType, + #encoding: encoding, + }, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future loadDataWithBaseUrl({ + String? baseUrl, + required String? data, + String? mimeType, + String? encoding, + String? historyUrl, + }) => (super.noSuchMethod( - Invocation.method(#loadDataWithBaseUrl, [], { + Invocation.method( + #loadDataWithBaseUrl, + [], + { #baseUrl: baseUrl, #data: data, #mimeType: mimeType, #encoding: encoding, - #historyUrl: historyUrl - }), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future loadUrl(String? url, Map? headers) => - (super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future postUrl(String? url, _i7.Uint8List? data) => - (super.noSuchMethod(Invocation.method(#postUrl, [url, data]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future getUrl() => - (super.noSuchMethod(Invocation.method(#getUrl, []), - returnValue: Future.value()) as _i5.Future); - @override - _i5.Future canGoBack() => - (super.noSuchMethod(Invocation.method(#canGoBack, []), - returnValue: Future.value(false)) as _i5.Future); - @override - _i5.Future canGoForward() => - (super.noSuchMethod(Invocation.method(#canGoForward, []), - returnValue: Future.value(false)) as _i5.Future); - @override - _i5.Future goBack() => - (super.noSuchMethod(Invocation.method(#goBack, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future goForward() => - (super.noSuchMethod(Invocation.method(#goForward, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future reload() => - (super.noSuchMethod(Invocation.method(#reload, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future clearCache(bool? includeDiskFiles) => - (super.noSuchMethod(Invocation.method(#clearCache, [includeDiskFiles]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future evaluateJavascript(String? javascriptString) => (super - .noSuchMethod(Invocation.method(#evaluateJavascript, [javascriptString]), - returnValue: Future.value()) as _i5.Future); - @override - _i5.Future getTitle() => - (super.noSuchMethod(Invocation.method(#getTitle, []), - returnValue: Future.value()) as _i5.Future); - @override - _i5.Future scrollTo(int? x, int? y) => - (super.noSuchMethod(Invocation.method(#scrollTo, [x, y]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future scrollBy(int? x, int? y) => - (super.noSuchMethod(Invocation.method(#scrollBy, [x, y]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future getScrollX() => - (super.noSuchMethod(Invocation.method(#getScrollX, []), - returnValue: Future.value(0)) as _i5.Future); - @override - _i5.Future getScrollY() => - (super.noSuchMethod(Invocation.method(#getScrollY, []), - returnValue: Future.value(0)) as _i5.Future); - @override - _i5.Future<_i4.Offset> getScrollPosition() => - (super.noSuchMethod(Invocation.method(#getScrollPosition, []), - returnValue: Future<_i4.Offset>.value(_FakeOffset_5())) - as _i5.Future<_i4.Offset>); + #historyUrl: historyUrl, + }, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future loadUrl( + String? url, + Map? headers, + ) => + (super.noSuchMethod( + Invocation.method( + #loadUrl, + [ + url, + headers, + ], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future postUrl( + String? url, + _i7.Uint8List? data, + ) => + (super.noSuchMethod( + Invocation.method( + #postUrl, + [ + url, + data, + ], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future getUrl() => (super.noSuchMethod( + Invocation.method( + #getUrl, + [], + ), + 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(bool? includeDiskFiles) => (super.noSuchMethod( + Invocation.method( + #clearCache, + [includeDiskFiles], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future evaluateJavascript(String? javascriptString) => + (super.noSuchMethod( + Invocation.method( + #evaluateJavascript, + [javascriptString], + ), + returnValue: _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 getScrollX() => (super.noSuchMethod( + Invocation.method( + #getScrollX, + [], + ), + returnValue: _i5.Future.value(0), + ) as _i5.Future); + @override + _i5.Future getScrollY() => (super.noSuchMethod( + Invocation.method( + #getScrollY, + [], + ), + returnValue: _i5.Future.value(0), + ) as _i5.Future); + @override + _i5.Future<_i4.Offset> getScrollPosition() => (super.noSuchMethod( + Invocation.method( + #getScrollPosition, + [], + ), + returnValue: _i5.Future<_i4.Offset>.value(_FakeOffset_5( + this, + Invocation.method( + #getScrollPosition, + [], + ), + )), + ) as _i5.Future<_i4.Offset>); @override _i5.Future setWebViewClient(_i2.WebViewClient? webViewClient) => - (super.noSuchMethod(Invocation.method(#setWebViewClient, [webViewClient]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + (super.noSuchMethod( + Invocation.method( + #setWebViewClient, + [webViewClient], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i5.Future addJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( - Invocation.method(#addJavaScriptChannel, [javaScriptChannel]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + Invocation.method( + #addJavaScriptChannel, + [javaScriptChannel], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i5.Future removeJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( - Invocation.method(#removeJavaScriptChannel, [javaScriptChannel]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + Invocation.method( + #removeJavaScriptChannel, + [javaScriptChannel], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i5.Future setDownloadListener(_i2.DownloadListener? listener) => - (super.noSuchMethod(Invocation.method(#setDownloadListener, [listener]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + (super.noSuchMethod( + Invocation.method( + #setDownloadListener, + [listener], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i5.Future setWebChromeClient(_i2.WebChromeClient? client) => - (super.noSuchMethod(Invocation.method(#setWebChromeClient, [client]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setBackgroundColor(_i4.Color? color) => - (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future release() => - (super.noSuchMethod(Invocation.method(#release, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i2.WebView copy() => (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeWebView_6()) as _i2.WebView); + (super.noSuchMethod( + Invocation.method( + #setWebChromeClient, + [client], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setBackgroundColor(_i4.Color? color) => (super.noSuchMethod( + Invocation.method( + #setBackgroundColor, + [color], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i2.WebView copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebView_6( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebView); } /// A class which mocks [WebViewClient]. @@ -620,39 +1288,28 @@ class MockWebViewClient extends _i1.Mock implements _i2.WebViewClient { } @override - bool get shouldOverrideUrlLoading => - (super.noSuchMethod(Invocation.getter(#shouldOverrideUrlLoading), - returnValue: false) as bool); - @override - void onPageStarted(_i2.WebView? webView, String? url) => - super.noSuchMethod(Invocation.method(#onPageStarted, [webView, url]), - returnValueForMissingStub: null); - @override - void onPageFinished(_i2.WebView? webView, String? url) => - super.noSuchMethod(Invocation.method(#onPageFinished, [webView, url]), - returnValueForMissingStub: null); - @override - void onReceivedRequestError(_i2.WebView? webView, - _i2.WebResourceRequest? request, _i2.WebResourceError? error) => - super.noSuchMethod( - Invocation.method(#onReceivedRequestError, [webView, request, error]), - returnValueForMissingStub: null); - @override - void onReceivedError(_i2.WebView? webView, int? errorCode, - String? description, String? failingUrl) => - super.noSuchMethod( + _i5.Future setSynchronousReturnValueForShouldOverrideUrlLoading( + bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForShouldOverrideUrlLoading, + [value], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i2.WebViewClient copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebViewClient_7( + this, Invocation.method( - #onReceivedError, [webView, errorCode, description, failingUrl]), - returnValueForMissingStub: null); - @override - void requestLoading(_i2.WebView? webView, _i2.WebResourceRequest? request) => - super.noSuchMethod(Invocation.method(#requestLoading, [webView, request]), - returnValueForMissingStub: null); - @override - void urlLoading(_i2.WebView? webView, String? url) => - super.noSuchMethod(Invocation.method(#urlLoading, [webView, url]), - returnValueForMissingStub: null); - @override - _i2.WebViewClient copy() => (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeWebViewClient_7()) as _i2.WebViewClient); + #copy, + [], + ), + ), + ) as _i2.WebViewClient); } diff --git a/packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/surface_android_test.dart similarity index 94% rename from packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart rename to packages/webview_flutter/webview_flutter_android/test/legacy/surface_android_test.dart index 63e752b419e6..1657e1b0d525 100644 --- a/packages/webview_flutter/webview_flutter_android/test/surface_android_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/surface_android_test.dart @@ -7,8 +7,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.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_android/src/legacy/webview_surface_android.dart'; +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.dart similarity index 89% rename from packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart rename to packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.dart index 4f274ff4499f..e4cd61634864 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.dart @@ -7,8 +7,8 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_android/src/android_webview.dart' as android_webview; -import 'package:webview_flutter_android/webview_android_cookie_manager.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter_android/src/legacy/webview_android_cookie_manager.dart'; +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import 'webview_android_cookie_manager_test.mocks.dart'; diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.mocks.dart new file mode 100644 index 000000000000..85aed145bfbe --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.mocks.dart @@ -0,0 +1,54 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in webview_flutter_android/test/legacy/webview_android_cookie_manager_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_android/src/android_webview.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 + +/// A class which mocks [CookieManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCookieManager extends _i1.Mock implements _i2.CookieManager { + MockCookieManager() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future setCookie( + String? url, + String? value, + ) => + (super.noSuchMethod( + Invocation.method( + #setCookie, + [ + url, + value, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + @override + _i3.Future clearCookies() => (super.noSuchMethod( + Invocation.method( + #clearCookies, + [], + ), + returnValue: _i3.Future.value(false), + ) as _i3.Future); +} diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart similarity index 75% rename from packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart rename to packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart index b6d623970543..6967f65e76f0 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/widgets.dart'; @@ -13,11 +12,11 @@ import 'package:webview_flutter_android/src/android_webview.dart' as android_webview; import 'package:webview_flutter_android/src/android_webview_api_impls.dart'; import 'package:webview_flutter_android/src/instance_manager.dart'; -import 'package:webview_flutter_android/webview_android_widget.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter_android/src/legacy/webview_android_widget.dart'; +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; -import 'android_webview_test.mocks.dart' show MockTestWebViewHostApi; -import 'test_android_webview.pigeon.dart'; +import '../android_webview_test.mocks.dart' show MockTestWebViewHostApi; +import '../test_android_webview.pigeon.dart'; import 'webview_android_widget_test.mocks.dart'; @GenerateMocks([ @@ -26,10 +25,10 @@ import 'webview_android_widget_test.mocks.dart'; android_webview.WebStorage, android_webview.WebView, android_webview.WebResourceRequest, - WebViewAndroidDownloadListener, + android_webview.DownloadListener, WebViewAndroidJavaScriptChannel, - WebViewAndroidWebChromeClient, - WebViewAndroidWebViewClient, + android_webview.WebChromeClient, + android_webview.WebViewClient, JavascriptChannelRegistry, WebViewPlatformCallbacksHandler, WebViewProxy, @@ -45,9 +44,9 @@ void main() { late MockWebViewProxy mockWebViewProxy; late MockWebViewPlatformCallbacksHandler mockCallbacksHandler; - late WebViewAndroidWebViewClient webViewClient; - late WebViewAndroidDownloadListener downloadListener; - late WebViewAndroidWebChromeClient webChromeClient; + late MockWebViewClient mockWebViewClient; + late android_webview.DownloadListener downloadListener; + late android_webview.WebChromeClient webChromeClient; late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; @@ -58,12 +57,21 @@ void main() { mockWebView = MockWebView(); mockWebSettings = MockWebSettings(); mockWebStorage = MockWebStorage(); + mockWebViewClient = MockWebViewClient(); when(mockWebView.settings).thenReturn(mockWebSettings); mockWebViewProxy = MockWebViewProxy(); when(mockWebViewProxy.createWebView( useHybridComposition: anyNamed('useHybridComposition'), )).thenReturn(mockWebView); + when(mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedError: anyNamed('onReceivedError'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: anyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + )).thenReturn(mockWebViewClient); mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); @@ -97,7 +105,7 @@ void main() { }, )); - webViewClient = testController.webViewClient; + mockWebViewClient = testController.webViewClient as MockWebViewClient; downloadListener = testController.downloadListener; webChromeClient = testController.webChromeClient; } @@ -114,9 +122,9 @@ void main() { verify(mockWebSettings.setBuiltInZoomControls(true)); verifyInOrder(>[ - mockWebView.setWebViewClient(webViewClient), mockWebView.setDownloadListener(downloadListener), mockWebView.setWebChromeClient(webChromeClient), + mockWebView.setWebViewClient(mockWebViewClient), ]); }); @@ -225,6 +233,16 @@ void main() { }); testWidgets('hasNavigationDelegate', (WidgetTester tester) async { + final MockWebViewClient mockWebViewClient = MockWebViewClient(); + when(mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedError: anyNamed('onReceivedError'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: anyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + )).thenReturn(mockWebViewClient); + await buildWidget( tester, creationParams: CreationParams( @@ -235,8 +253,10 @@ void main() { ), ); - expect(testController.webViewClient.handlesNavigation, isTrue); - expect(testController.webViewClient.shouldOverrideUrlLoading, isTrue); + verify( + mockWebViewClient + .setSynchronousReturnValueForShouldOverrideUrlLoading(true), + ); }); testWidgets('debuggingEnabled true', (WidgetTester tester) async { @@ -445,7 +465,7 @@ void main() { await buildWidget(tester); expect( - () async => testController.loadRequest( + () async => await testController.loadRequest( WebViewRequest( uri: Uri.parse('www.google.com'), method: WebViewRequestMethod.get, @@ -693,20 +713,53 @@ void main() { group('WebViewPlatformCallbacksHandler', () { testWidgets('onPageStarted', (WidgetTester tester) async { await buildWidget(tester); - webViewClient.onPageStarted(mockWebView, 'https://google.com'); + final void Function(android_webview.WebView, String) onPageStarted = + verify(mockWebViewProxy.createWebViewClient( + onPageStarted: captureAnyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedError: anyNamed('onReceivedError'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: anyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + )).captured.single as Function(android_webview.WebView, String); + + onPageStarted(mockWebView, 'https://google.com'); verify(mockCallbacksHandler.onPageStarted('https://google.com')); }); testWidgets('onPageFinished', (WidgetTester tester) async { await buildWidget(tester); - webViewClient.onPageFinished(mockWebView, 'https://google.com'); + + final void Function(android_webview.WebView, String) onPageFinished = + verify(mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: captureAnyNamed('onPageFinished'), + onReceivedError: anyNamed('onReceivedError'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: anyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + )).captured.single as Function(android_webview.WebView, String); + + onPageFinished(mockWebView, 'https://google.com'); verify(mockCallbacksHandler.onPageFinished('https://google.com')); }); testWidgets('onWebResourceError from onReceivedError', (WidgetTester tester) async { await buildWidget(tester); - webViewClient.onReceivedError( + + final void Function(android_webview.WebView, int, String, String) + onReceivedError = verify(mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedError: captureAnyNamed('onReceivedError'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: anyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + )).captured.single as Function( + android_webview.WebView, int, String, String); + + onReceivedError( mockWebView, android_webview.WebViewClient.errorAuthentication, 'description', @@ -727,7 +780,25 @@ void main() { testWidgets('onWebResourceError from onReceivedRequestError', (WidgetTester tester) async { await buildWidget(tester); - webViewClient.onReceivedRequestError( + + final void Function( + android_webview.WebView, + android_webview.WebResourceRequest, + android_webview.WebResourceError, + ) onReceivedRequestError = verify(mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedError: anyNamed('onReceivedError'), + onReceivedRequestError: captureAnyNamed('onReceivedRequestError'), + requestLoading: anyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + )).captured.single as Function( + android_webview.WebView, + android_webview.WebResourceRequest, + android_webview.WebResourceError, + ); + + onReceivedRequestError( mockWebView, android_webview.WebResourceRequest( url: 'https://google.com', @@ -762,7 +833,17 @@ void main() { url: 'https://google.com', )).thenReturn(true); - webViewClient.urlLoading(mockWebView, 'https://google.com'); + final void Function(android_webview.WebView, String) urlLoading = + verify(mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedError: anyNamed('onReceivedError'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: anyNamed('requestLoading'), + urlLoading: captureAnyNamed('urlLoading'), + )).captured.single as Function(android_webview.WebView, String); + + urlLoading(mockWebView, 'https://google.com'); verify(mockCallbacksHandler.onNavigationRequest( url: 'https://google.com', isForMainFrame: true, @@ -778,7 +859,22 @@ void main() { url: 'https://google.com', )).thenReturn(true); - webViewClient.requestLoading( + final void Function( + android_webview.WebView, + android_webview.WebResourceRequest, + ) requestLoading = verify(mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedError: anyNamed('onReceivedError'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: captureAnyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + )).captured.single as Function( + android_webview.WebView, + android_webview.WebResourceRequest, + ); + + requestLoading( mockWebView, android_webview.WebResourceRequest( url: 'https://google.com', @@ -843,192 +939,4 @@ void main() { verify(mockPlatformHostApi.setWebContentsDebuggingEnabled(false)); }); }); - - group('WebViewAndroidWebViewClient', () { - test( - 'urlLoading should call loadUrl when onNavigationRequestCallback returns true', - () { - final Completer completer = Completer(); - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - true, - loadUrl: (String url, Map? headers) async { - completer.complete(); - }); - - webViewClient.urlLoading(MockWebView(), 'https://flutter.dev'); - expect(completer.isCompleted, isTrue); - }); - - test( - 'urlLoading should call loadUrl when onNavigationRequestCallback returns a Future true', - () async { - final Completer completer = Completer(); - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - Future.value(true), - loadUrl: (String url, Map? headers) async { - completer.complete(); - }); - - webViewClient.urlLoading(MockWebView(), 'https://flutter.dev'); - expect(completer.future, completes); - }); - - test( - 'urlLoading should not call laodUrl when onNavigationRequestCallback returns false', - () async { - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - false, - loadUrl: (String url, Map? headers) async { - fail( - 'loadUrl should not be called if onNavigationRequestCallback returns false.'); - }); - - webViewClient.urlLoading(MockWebView(), 'https://flutter.dev'); - }); - - test( - 'urlLoading should not call loadUrl when onNavigationRequestCallback returns a Future false', - () { - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - Future.value(false), - loadUrl: (String url, Map? headers) async { - fail( - 'loadUrl should not be called if onNavigationRequestCallback returns false.'); - }); - - webViewClient.urlLoading(MockWebView(), 'https://flutter.dev'); - }); - - test( - 'requestLoading should call loadUrl when onNavigationRequestCallback returns true', - () { - final Completer completer = Completer(); - final MockWebResourceRequest mockRequest = MockWebResourceRequest(); - when(mockRequest.isForMainFrame).thenReturn(true); - when(mockRequest.url).thenReturn('https://flutter.dev'); - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - true, - loadUrl: (String url, Map? headers) async { - expect(url, 'https://flutter.dev'); - completer.complete(); - }); - - webViewClient.requestLoading(MockWebView(), mockRequest); - expect(completer.isCompleted, isTrue); - }); - - test( - 'requestLoading should call loadUrl when onNavigationRequestCallback returns a Future true', - () async { - final Completer completer = Completer(); - final MockWebResourceRequest mockRequest = MockWebResourceRequest(); - when(mockRequest.isForMainFrame).thenReturn(true); - when(mockRequest.url).thenReturn('https://flutter.dev'); - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - Future.value(true), - loadUrl: (String url, Map? headers) async { - expect(url, 'https://flutter.dev'); - completer.complete(); - }); - - webViewClient.requestLoading(MockWebView(), mockRequest); - expect(completer.future, completes); - }); - - test( - 'requestLoading should not call loadUrl when onNavigationRequestCallback returns false', - () { - final MockWebResourceRequest mockRequest = MockWebResourceRequest(); - when(mockRequest.isForMainFrame).thenReturn(true); - when(mockRequest.url).thenReturn('https://flutter.dev'); - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - false, - loadUrl: (String url, Map? headers) { - fail( - 'loadUrl should not be called if onNavigationRequestCallback returns false.'); - }); - - webViewClient.requestLoading(MockWebView(), mockRequest); - }); - - test( - 'requestLoading should not call loadUrl when onNavigationRequestCallback returns a Future false', - () { - final MockWebResourceRequest mockRequest = MockWebResourceRequest(); - when(mockRequest.isForMainFrame).thenReturn(true); - when(mockRequest.url).thenReturn('https://flutter.dev'); - final WebViewAndroidWebViewClient webViewClient = - WebViewAndroidWebViewClient.handlesNavigation( - onPageStartedCallback: (_) {}, - onPageFinishedCallback: (_) {}, - onWebResourceErrorCallback: (_) {}, - onNavigationRequestCallback: ({ - required bool isForMainFrame, - required String url, - }) => - Future.value(false), - loadUrl: (String url, Map? headers) { - fail( - 'loadUrl should not be called if onNavigationRequestCallback returns false.'); - }); - - webViewClient.requestLoading(MockWebView(), mockRequest); - }); - }); } diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart new file mode 100644 index 000000000000..1f16c29aa953 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart @@ -0,0 +1,1016 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in webview_flutter_android/test/legacy/webview_android_widget_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; +import 'dart:typed_data' as _i6; +import 'dart:ui' as _i3; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_android/src/android_webview.dart' as _i2; +import 'package:webview_flutter_android/src/legacy/webview_android_widget.dart' + as _i7; +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart' + as _i4; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeWebSettings_0 extends _i1.SmartFake implements _i2.WebSettings { + _FakeWebSettings_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWebStorage_1 extends _i1.SmartFake implements _i2.WebStorage { + _FakeWebStorage_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 _FakeWebView_3 extends _i1.SmartFake implements _i2.WebView { + _FakeWebView_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeDownloadListener_4 extends _i1.SmartFake + implements _i2.DownloadListener { + _FakeDownloadListener_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeJavascriptChannelRegistry_5 extends _i1.SmartFake + implements _i4.JavascriptChannelRegistry { + _FakeJavascriptChannelRegistry_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeJavaScriptChannel_6 extends _i1.SmartFake + implements _i2.JavaScriptChannel { + _FakeJavaScriptChannel_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWebChromeClient_7 extends _i1.SmartFake + implements _i2.WebChromeClient { + _FakeWebChromeClient_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWebViewClient_8 extends _i1.SmartFake implements _i2.WebViewClient { + _FakeWebViewClient_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [FlutterAssetManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFlutterAssetManager extends _i1.Mock + implements _i2.FlutterAssetManager { + MockFlutterAssetManager() { + _i1.throwOnMissingStub(this); + } + + @override + _i5.Future> list(String? path) => (super.noSuchMethod( + Invocation.method( + #list, + [path], + ), + returnValue: _i5.Future>.value([]), + ) as _i5.Future>); + @override + _i5.Future getAssetFilePathByName(String? name) => + (super.noSuchMethod( + Invocation.method( + #getAssetFilePathByName, + [name], + ), + returnValue: _i5.Future.value(''), + ) as _i5.Future); +} + +/// A class which mocks [WebSettings]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebSettings extends _i1.Mock implements _i2.WebSettings { + MockWebSettings() { + _i1.throwOnMissingStub(this); + } + + @override + _i5.Future setDomStorageEnabled(bool? flag) => (super.noSuchMethod( + Invocation.method( + #setDomStorageEnabled, + [flag], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => + (super.noSuchMethod( + Invocation.method( + #setJavaScriptCanOpenWindowsAutomatically, + [flag], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setSupportMultipleWindows(bool? support) => + (super.noSuchMethod( + Invocation.method( + #setSupportMultipleWindows, + [support], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setJavaScriptEnabled(bool? flag) => (super.noSuchMethod( + Invocation.method( + #setJavaScriptEnabled, + [flag], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setUserAgentString(String? userAgentString) => + (super.noSuchMethod( + Invocation.method( + #setUserAgentString, + [userAgentString], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setMediaPlaybackRequiresUserGesture(bool? require) => + (super.noSuchMethod( + Invocation.method( + #setMediaPlaybackRequiresUserGesture, + [require], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setSupportZoom(bool? support) => (super.noSuchMethod( + Invocation.method( + #setSupportZoom, + [support], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setLoadWithOverviewMode(bool? overview) => + (super.noSuchMethod( + Invocation.method( + #setLoadWithOverviewMode, + [overview], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setUseWideViewPort(bool? use) => (super.noSuchMethod( + Invocation.method( + #setUseWideViewPort, + [use], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setDisplayZoomControls(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #setDisplayZoomControls, + [enabled], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setBuiltInZoomControls(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #setBuiltInZoomControls, + [enabled], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setAllowFileAccess(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #setAllowFileAccess, + [enabled], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i2.WebSettings copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebSettings_0( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebSettings); +} + +/// A class which mocks [WebStorage]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebStorage extends _i1.Mock implements _i2.WebStorage { + MockWebStorage() { + _i1.throwOnMissingStub(this); + } + + @override + _i5.Future deleteAllData() => (super.noSuchMethod( + Invocation.method( + #deleteAllData, + [], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i2.WebStorage copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebStorage_1( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebStorage); +} + +/// A class which mocks [WebView]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebView extends _i1.Mock implements _i2.WebView { + MockWebView() { + _i1.throwOnMissingStub(this); + } + + @override + bool get useHybridComposition => (super.noSuchMethod( + Invocation.getter(#useHybridComposition), + returnValue: false, + ) as bool); + @override + _i2.WebSettings get settings => (super.noSuchMethod( + Invocation.getter(#settings), + returnValue: _FakeWebSettings_0( + this, + Invocation.getter(#settings), + ), + ) as _i2.WebSettings); + @override + _i5.Future loadData({ + required String? data, + String? mimeType, + String? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #loadData, + [], + { + #data: data, + #mimeType: mimeType, + #encoding: encoding, + }, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future loadDataWithBaseUrl({ + String? baseUrl, + required String? data, + String? mimeType, + String? encoding, + String? historyUrl, + }) => + (super.noSuchMethod( + Invocation.method( + #loadDataWithBaseUrl, + [], + { + #baseUrl: baseUrl, + #data: data, + #mimeType: mimeType, + #encoding: encoding, + #historyUrl: historyUrl, + }, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future loadUrl( + String? url, + Map? headers, + ) => + (super.noSuchMethod( + Invocation.method( + #loadUrl, + [ + url, + headers, + ], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future postUrl( + String? url, + _i6.Uint8List? data, + ) => + (super.noSuchMethod( + Invocation.method( + #postUrl, + [ + url, + data, + ], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future getUrl() => (super.noSuchMethod( + Invocation.method( + #getUrl, + [], + ), + 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(bool? includeDiskFiles) => (super.noSuchMethod( + Invocation.method( + #clearCache, + [includeDiskFiles], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future evaluateJavascript(String? javascriptString) => + (super.noSuchMethod( + Invocation.method( + #evaluateJavascript, + [javascriptString], + ), + returnValue: _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 getScrollX() => (super.noSuchMethod( + Invocation.method( + #getScrollX, + [], + ), + returnValue: _i5.Future.value(0), + ) as _i5.Future); + @override + _i5.Future getScrollY() => (super.noSuchMethod( + Invocation.method( + #getScrollY, + [], + ), + returnValue: _i5.Future.value(0), + ) 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 setWebViewClient(_i2.WebViewClient? webViewClient) => + (super.noSuchMethod( + Invocation.method( + #setWebViewClient, + [webViewClient], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future addJavaScriptChannel( + _i2.JavaScriptChannel? javaScriptChannel) => + (super.noSuchMethod( + Invocation.method( + #addJavaScriptChannel, + [javaScriptChannel], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future removeJavaScriptChannel( + _i2.JavaScriptChannel? javaScriptChannel) => + (super.noSuchMethod( + Invocation.method( + #removeJavaScriptChannel, + [javaScriptChannel], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setDownloadListener(_i2.DownloadListener? listener) => + (super.noSuchMethod( + Invocation.method( + #setDownloadListener, + [listener], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setWebChromeClient(_i2.WebChromeClient? client) => + (super.noSuchMethod( + Invocation.method( + #setWebChromeClient, + [client], + ), + 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 + _i2.WebView copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebView_3( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebView); +} + +/// A class which mocks [WebResourceRequest]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebResourceRequest extends _i1.Mock + implements _i2.WebResourceRequest { + MockWebResourceRequest() { + _i1.throwOnMissingStub(this); + } + + @override + String get url => (super.noSuchMethod( + Invocation.getter(#url), + returnValue: '', + ) as String); + @override + bool get isForMainFrame => (super.noSuchMethod( + Invocation.getter(#isForMainFrame), + returnValue: false, + ) as bool); + @override + bool get hasGesture => (super.noSuchMethod( + Invocation.getter(#hasGesture), + returnValue: false, + ) as bool); + @override + String get method => (super.noSuchMethod( + Invocation.getter(#method), + returnValue: '', + ) as String); + @override + Map get requestHeaders => (super.noSuchMethod( + Invocation.getter(#requestHeaders), + returnValue: {}, + ) as Map); +} + +/// A class which mocks [DownloadListener]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDownloadListener extends _i1.Mock implements _i2.DownloadListener { + MockDownloadListener() { + _i1.throwOnMissingStub(this); + } + + @override + void Function( + String, + String, + String, + String, + int, + ) get onDownloadStart => (super.noSuchMethod( + Invocation.getter(#onDownloadStart), + returnValue: ( + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) {}, + ) as void Function( + String, + String, + String, + String, + int, + )); + @override + _i2.DownloadListener copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeDownloadListener_4( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.DownloadListener); +} + +/// A class which mocks [WebViewAndroidJavaScriptChannel]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewAndroidJavaScriptChannel extends _i1.Mock + implements _i7.WebViewAndroidJavaScriptChannel { + MockWebViewAndroidJavaScriptChannel() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.JavascriptChannelRegistry get javascriptChannelRegistry => + (super.noSuchMethod( + Invocation.getter(#javascriptChannelRegistry), + returnValue: _FakeJavascriptChannelRegistry_5( + this, + Invocation.getter(#javascriptChannelRegistry), + ), + ) as _i4.JavascriptChannelRegistry); + @override + String get channelName => (super.noSuchMethod( + Invocation.getter(#channelName), + returnValue: '', + ) as String); + @override + void Function(String) get postMessage => (super.noSuchMethod( + Invocation.getter(#postMessage), + returnValue: (String message) {}, + ) as void Function(String)); + @override + _i2.JavaScriptChannel copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeJavaScriptChannel_6( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.JavaScriptChannel); +} + +/// A class which mocks [WebChromeClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { + MockWebChromeClient() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.WebChromeClient copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebChromeClient_7( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebChromeClient); +} + +/// A class which mocks [WebViewClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewClient extends _i1.Mock implements _i2.WebViewClient { + MockWebViewClient() { + _i1.throwOnMissingStub(this); + } + + @override + _i5.Future setSynchronousReturnValueForShouldOverrideUrlLoading( + bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForShouldOverrideUrlLoading, + [value], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i2.WebViewClient copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeWebViewClient_8( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.WebViewClient); +} + +/// A class which mocks [JavascriptChannelRegistry]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockJavascriptChannelRegistry extends _i1.Mock + implements _i4.JavascriptChannelRegistry { + MockJavascriptChannelRegistry() { + _i1.throwOnMissingStub(this); + } + + @override + Map get channels => (super.noSuchMethod( + Invocation.getter(#channels), + returnValue: {}, + ) as Map); + @override + void onJavascriptChannelMessage( + String? channel, + String? message, + ) => + super.noSuchMethod( + Invocation.method( + #onJavascriptChannelMessage, + [ + channel, + message, + ], + ), + returnValueForMissingStub: null, + ); + @override + void updateJavascriptChannelsFromSet(Set<_i4.JavascriptChannel>? channels) => + super.noSuchMethod( + Invocation.method( + #updateJavascriptChannelsFromSet, + [channels], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [WebViewPlatformCallbacksHandler]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewPlatformCallbacksHandler extends _i1.Mock + implements _i4.WebViewPlatformCallbacksHandler { + MockWebViewPlatformCallbacksHandler() { + _i1.throwOnMissingStub(this); + } + + @override + _i5.FutureOr onNavigationRequest({ + required String? url, + required bool? isForMainFrame, + }) => + (super.noSuchMethod( + Invocation.method( + #onNavigationRequest, + [], + { + #url: url, + #isForMainFrame: isForMainFrame, + }, + ), + returnValue: _i5.Future.value(false), + ) as _i5.FutureOr); + @override + void onPageStarted(String? url) => super.noSuchMethod( + Invocation.method( + #onPageStarted, + [url], + ), + returnValueForMissingStub: null, + ); + @override + void onPageFinished(String? url) => super.noSuchMethod( + Invocation.method( + #onPageFinished, + [url], + ), + returnValueForMissingStub: null, + ); + @override + void onProgress(int? progress) => super.noSuchMethod( + Invocation.method( + #onProgress, + [progress], + ), + returnValueForMissingStub: null, + ); + @override + void onWebResourceError(_i4.WebResourceError? error) => super.noSuchMethod( + Invocation.method( + #onWebResourceError, + [error], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [WebViewProxy]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewProxy extends _i1.Mock implements _i7.WebViewProxy { + MockWebViewProxy() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.WebView createWebView({required bool? useHybridComposition}) => + (super.noSuchMethod( + Invocation.method( + #createWebView, + [], + {#useHybridComposition: useHybridComposition}, + ), + returnValue: _FakeWebView_3( + this, + Invocation.method( + #createWebView, + [], + {#useHybridComposition: useHybridComposition}, + ), + ), + ) as _i2.WebView); + @override + _i2.WebViewClient createWebViewClient({ + void Function( + _i2.WebView, + String, + )? + onPageStarted, + void Function( + _i2.WebView, + String, + )? + onPageFinished, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebView, + int, + String, + String, + )? + onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + )? + requestLoading, + void Function( + _i2.WebView, + String, + )? + urlLoading, + }) => + (super.noSuchMethod( + Invocation.method( + #createWebViewClient, + [], + { + #onPageStarted: onPageStarted, + #onPageFinished: onPageFinished, + #onReceivedRequestError: onReceivedRequestError, + #onReceivedError: onReceivedError, + #requestLoading: requestLoading, + #urlLoading: urlLoading, + }, + ), + returnValue: _FakeWebViewClient_8( + this, + Invocation.method( + #createWebViewClient, + [], + { + #onPageStarted: onPageStarted, + #onPageFinished: onPageFinished, + #onReceivedRequestError: onReceivedRequestError, + #onReceivedError: onReceivedError, + #requestLoading: requestLoading, + #urlLoading: urlLoading, + }, + ), + ), + ) as _i2.WebViewClient); + @override + _i5.Future setWebContentsDebuggingEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method( + #setWebContentsDebuggingEnabled, + [enabled], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); +} diff --git a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart index afc80ab53b41..bcfb453e85c4 100644 --- a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v4.0.2), do not edit directly. +// Autogenerated from Pigeon (v4.2.3), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // ignore_for_file: avoid_relative_lib_imports @@ -13,12 +13,13 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:webview_flutter_android/src/android_webview.pigeon.dart'; -class _TestJavaObjectHostApiCodec extends StandardMessageCodec { - const _TestJavaObjectHostApiCodec(); -} - +/// Handles methods calls to the native Java Object class. +/// +/// Also handles calls to remove the reference to an instance with `dispose`. +/// +/// See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. abstract class TestJavaObjectHostApi { - static const MessageCodec codec = _TestJavaObjectHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); void dispose(int identifier); static void setup(TestJavaObjectHostApi? api, @@ -73,7 +74,6 @@ abstract class TestWebViewHostApi { static const MessageCodec codec = _TestWebViewHostApiCodec(); void create(int instanceId, bool useHybridComposition); - void dispose(int instanceId); void loadData( int instanceId, String data, String? mimeType, String? encoding); void loadDataWithBaseUrl(int instanceId, String? baseUrl, String data, @@ -125,25 +125,6 @@ abstract class TestWebViewHostApi { }); } } - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.WebViewHostApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMockMessageHandler(null); - } else { - channel.setMockMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.WebViewHostApi.dispose was null.'); - final List args = (message as List?)!; - final int? arg_instanceId = (args[0] as int?); - assert(arg_instanceId != null, - 'Argument for dev.flutter.pigeon.WebViewHostApi.dispose was null, expected non-null int.'); - api.dispose(arg_instanceId!); - return {}; - }); - } - } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadData', codec, @@ -684,15 +665,10 @@ abstract class TestWebViewHostApi { } } -class _TestWebSettingsHostApiCodec extends StandardMessageCodec { - const _TestWebSettingsHostApiCodec(); -} - abstract class TestWebSettingsHostApi { - static const MessageCodec codec = _TestWebSettingsHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId, int webViewInstanceId); - void dispose(int instanceId); void setDomStorageEnabled(int instanceId, bool flag); void setJavaScriptCanOpenWindowsAutomatically(int instanceId, bool flag); void setSupportMultipleWindows(int instanceId, bool support); @@ -729,25 +705,6 @@ abstract class TestWebSettingsHostApi { }); } } - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.WebSettingsHostApi.dispose', codec, - binaryMessenger: binaryMessenger); - if (api == null) { - channel.setMockMessageHandler(null); - } else { - channel.setMockMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.WebSettingsHostApi.dispose was null.'); - final List args = (message as List?)!; - final int? arg_instanceId = (args[0] as int?); - assert(arg_instanceId != null, - 'Argument for dev.flutter.pigeon.WebSettingsHostApi.dispose was null, expected non-null int.'); - api.dispose(arg_instanceId!); - return {}; - }); - } - } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled', codec, @@ -1019,13 +976,8 @@ abstract class TestWebSettingsHostApi { } } -class _TestJavaScriptChannelHostApiCodec extends StandardMessageCodec { - const _TestJavaScriptChannelHostApiCodec(); -} - abstract class TestJavaScriptChannelHostApi { - static const MessageCodec codec = - _TestJavaScriptChannelHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId, String channelName); static void setup(TestJavaScriptChannelHostApi? api, @@ -1055,14 +1007,12 @@ abstract class TestJavaScriptChannelHostApi { } } -class _TestWebViewClientHostApiCodec extends StandardMessageCodec { - const _TestWebViewClientHostApiCodec(); -} - abstract class TestWebViewClientHostApi { - static const MessageCodec codec = _TestWebViewClientHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); - void create(int instanceId, bool shouldOverrideUrlLoading); + void create(int instanceId); + void setSynchronousReturnValueForShouldOverrideUrlLoading( + int instanceId, bool value); static void setup(TestWebViewClientHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1079,10 +1029,31 @@ abstract class TestWebViewClientHostApi { final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewClientHostApi.create was null, expected non-null int.'); - final bool? arg_shouldOverrideUrlLoading = (args[1] as bool?); - assert(arg_shouldOverrideUrlLoading != null, - 'Argument for dev.flutter.pigeon.WebViewClientHostApi.create was null, expected non-null bool.'); - api.create(arg_instanceId!, arg_shouldOverrideUrlLoading!); + api.create(arg_instanceId!); + return {}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading was null, expected non-null int.'); + final bool? arg_value = (args[1] as bool?); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading was null, expected non-null bool.'); + api.setSynchronousReturnValueForShouldOverrideUrlLoading( + arg_instanceId!, arg_value!); return {}; }); } @@ -1090,13 +1061,8 @@ abstract class TestWebViewClientHostApi { } } -class _TestDownloadListenerHostApiCodec extends StandardMessageCodec { - const _TestDownloadListenerHostApiCodec(); -} - abstract class TestDownloadListenerHostApi { - static const MessageCodec codec = - _TestDownloadListenerHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId); static void setup(TestDownloadListenerHostApi? api, @@ -1123,14 +1089,10 @@ abstract class TestDownloadListenerHostApi { } } -class _TestWebChromeClientHostApiCodec extends StandardMessageCodec { - const _TestWebChromeClientHostApiCodec(); -} - abstract class TestWebChromeClientHostApi { - static const MessageCodec codec = _TestWebChromeClientHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); - void create(int instanceId, int webViewClientInstanceId); + void create(int instanceId); static void setup(TestWebChromeClientHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1147,10 +1109,7 @@ abstract class TestWebChromeClientHostApi { final int? arg_instanceId = (args[0] as int?); assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.create was null, expected non-null int.'); - final int? arg_webViewClientInstanceId = (args[1] as int?); - assert(arg_webViewClientInstanceId != null, - 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.create was null, expected non-null int.'); - api.create(arg_instanceId!, arg_webViewClientInstanceId!); + api.create(arg_instanceId!); return {}; }); } @@ -1158,12 +1117,8 @@ abstract class TestWebChromeClientHostApi { } } -class _TestAssetManagerHostApiCodec extends StandardMessageCodec { - const _TestAssetManagerHostApiCodec(); -} - abstract class TestAssetManagerHostApi { - static const MessageCodec codec = _TestAssetManagerHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); List list(String path); String getAssetFilePathByName(String name); @@ -1211,12 +1166,8 @@ abstract class TestAssetManagerHostApi { } } -class _TestWebStorageHostApiCodec extends StandardMessageCodec { - const _TestWebStorageHostApiCodec(); -} - abstract class TestWebStorageHostApi { - static const MessageCodec codec = _TestWebStorageHostApiCodec(); + static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId); void deleteAllData(int instanceId); diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart deleted file mode 100644 index acaff1c0af8b..000000000000 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart +++ /dev/null @@ -1,572 +0,0 @@ -// Mocks generated by Mockito 5.2.0 from annotations -// in webview_flutter_android/test/webview_android_widget_test.dart. -// Do not manually edit this file. - -import 'dart:async' as _i5; -import 'dart:typed_data' as _i6; -import 'dart:ui' as _i3; - -import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_android/src/android_webview.dart' as _i2; -import 'package:webview_flutter_android/webview_android_widget.dart' as _i7; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' - as _i4; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types - -class _FakeWebSettings_0 extends _i1.Fake implements _i2.WebSettings {} - -class _FakeWebStorage_1 extends _i1.Fake implements _i2.WebStorage {} - -class _FakeOffset_2 extends _i1.Fake implements _i3.Offset {} - -class _FakeWebView_3 extends _i1.Fake implements _i2.WebView {} - -class _FakeDownloadListener_4 extends _i1.Fake - implements _i2.DownloadListener {} - -class _FakeJavascriptChannelRegistry_5 extends _i1.Fake - implements _i4.JavascriptChannelRegistry {} - -class _FakeJavaScriptChannel_6 extends _i1.Fake - implements _i2.JavaScriptChannel {} - -class _FakeWebChromeClient_7 extends _i1.Fake implements _i2.WebChromeClient {} - -class _FakeWebViewClient_8 extends _i1.Fake implements _i2.WebViewClient {} - -/// A class which mocks [FlutterAssetManager]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockFlutterAssetManager extends _i1.Mock - implements _i2.FlutterAssetManager { - MockFlutterAssetManager() { - _i1.throwOnMissingStub(this); - } - - @override - _i5.Future> list(String? path) => - (super.noSuchMethod(Invocation.method(#list, [path]), - returnValue: Future>.value([])) - as _i5.Future>); - @override - _i5.Future getAssetFilePathByName(String? name) => - (super.noSuchMethod(Invocation.method(#getAssetFilePathByName, [name]), - returnValue: Future.value('')) as _i5.Future); -} - -/// A class which mocks [WebSettings]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWebSettings extends _i1.Mock implements _i2.WebSettings { - MockWebSettings() { - _i1.throwOnMissingStub(this); - } - - @override - _i5.Future setDomStorageEnabled(bool? flag) => - (super.noSuchMethod(Invocation.method(#setDomStorageEnabled, [flag]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => - (super.noSuchMethod( - Invocation.method(#setJavaScriptCanOpenWindowsAutomatically, [flag]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setSupportMultipleWindows(bool? support) => (super - .noSuchMethod(Invocation.method(#setSupportMultipleWindows, [support]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setJavaScriptEnabled(bool? flag) => - (super.noSuchMethod(Invocation.method(#setJavaScriptEnabled, [flag]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setUserAgentString(String? userAgentString) => (super - .noSuchMethod(Invocation.method(#setUserAgentString, [userAgentString]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setMediaPlaybackRequiresUserGesture(bool? require) => - (super.noSuchMethod( - Invocation.method(#setMediaPlaybackRequiresUserGesture, [require]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setSupportZoom(bool? support) => - (super.noSuchMethod(Invocation.method(#setSupportZoom, [support]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setLoadWithOverviewMode(bool? overview) => (super - .noSuchMethod(Invocation.method(#setLoadWithOverviewMode, [overview]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setUseWideViewPort(bool? use) => - (super.noSuchMethod(Invocation.method(#setUseWideViewPort, [use]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setDisplayZoomControls(bool? enabled) => - (super.noSuchMethod(Invocation.method(#setDisplayZoomControls, [enabled]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setBuiltInZoomControls(bool? enabled) => - (super.noSuchMethod(Invocation.method(#setBuiltInZoomControls, [enabled]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setAllowFileAccess(bool? enabled) => - (super.noSuchMethod(Invocation.method(#setAllowFileAccess, [enabled]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i2.WebSettings copy() => (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeWebSettings_0()) as _i2.WebSettings); -} - -/// A class which mocks [WebStorage]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWebStorage extends _i1.Mock implements _i2.WebStorage { - MockWebStorage() { - _i1.throwOnMissingStub(this); - } - - @override - _i5.Future deleteAllData() => - (super.noSuchMethod(Invocation.method(#deleteAllData, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i2.WebStorage copy() => (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeWebStorage_1()) as _i2.WebStorage); -} - -/// A class which mocks [WebView]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWebView extends _i1.Mock implements _i2.WebView { - MockWebView() { - _i1.throwOnMissingStub(this); - } - - @override - bool get useHybridComposition => - (super.noSuchMethod(Invocation.getter(#useHybridComposition), - returnValue: false) as bool); - @override - _i2.WebSettings get settings => - (super.noSuchMethod(Invocation.getter(#settings), - returnValue: _FakeWebSettings_0()) as _i2.WebSettings); - @override - _i5.Future loadData( - {String? data, String? mimeType, String? encoding}) => - (super.noSuchMethod( - Invocation.method(#loadData, [], - {#data: data, #mimeType: mimeType, #encoding: encoding}), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future loadDataWithBaseUrl( - {String? baseUrl, - String? data, - String? mimeType, - String? encoding, - String? historyUrl}) => - (super.noSuchMethod( - Invocation.method(#loadDataWithBaseUrl, [], { - #baseUrl: baseUrl, - #data: data, - #mimeType: mimeType, - #encoding: encoding, - #historyUrl: historyUrl - }), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future loadUrl(String? url, Map? headers) => - (super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future postUrl(String? url, _i6.Uint8List? data) => - (super.noSuchMethod(Invocation.method(#postUrl, [url, data]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future getUrl() => - (super.noSuchMethod(Invocation.method(#getUrl, []), - returnValue: Future.value()) as _i5.Future); - @override - _i5.Future canGoBack() => - (super.noSuchMethod(Invocation.method(#canGoBack, []), - returnValue: Future.value(false)) as _i5.Future); - @override - _i5.Future canGoForward() => - (super.noSuchMethod(Invocation.method(#canGoForward, []), - returnValue: Future.value(false)) as _i5.Future); - @override - _i5.Future goBack() => - (super.noSuchMethod(Invocation.method(#goBack, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future goForward() => - (super.noSuchMethod(Invocation.method(#goForward, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future reload() => - (super.noSuchMethod(Invocation.method(#reload, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future clearCache(bool? includeDiskFiles) => - (super.noSuchMethod(Invocation.method(#clearCache, [includeDiskFiles]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future evaluateJavascript(String? javascriptString) => (super - .noSuchMethod(Invocation.method(#evaluateJavascript, [javascriptString]), - returnValue: Future.value()) as _i5.Future); - @override - _i5.Future getTitle() => - (super.noSuchMethod(Invocation.method(#getTitle, []), - returnValue: Future.value()) as _i5.Future); - @override - _i5.Future scrollTo(int? x, int? y) => - (super.noSuchMethod(Invocation.method(#scrollTo, [x, y]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future scrollBy(int? x, int? y) => - (super.noSuchMethod(Invocation.method(#scrollBy, [x, y]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future getScrollX() => - (super.noSuchMethod(Invocation.method(#getScrollX, []), - returnValue: Future.value(0)) as _i5.Future); - @override - _i5.Future getScrollY() => - (super.noSuchMethod(Invocation.method(#getScrollY, []), - returnValue: Future.value(0)) as _i5.Future); - @override - _i5.Future<_i3.Offset> getScrollPosition() => - (super.noSuchMethod(Invocation.method(#getScrollPosition, []), - returnValue: Future<_i3.Offset>.value(_FakeOffset_2())) - as _i5.Future<_i3.Offset>); - @override - _i5.Future setWebViewClient(_i2.WebViewClient? webViewClient) => - (super.noSuchMethod(Invocation.method(#setWebViewClient, [webViewClient]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future addJavaScriptChannel( - _i2.JavaScriptChannel? javaScriptChannel) => - (super.noSuchMethod( - Invocation.method(#addJavaScriptChannel, [javaScriptChannel]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future removeJavaScriptChannel( - _i2.JavaScriptChannel? javaScriptChannel) => - (super.noSuchMethod( - Invocation.method(#removeJavaScriptChannel, [javaScriptChannel]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setDownloadListener(_i2.DownloadListener? listener) => - (super.noSuchMethod(Invocation.method(#setDownloadListener, [listener]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setWebChromeClient(_i2.WebChromeClient? client) => - (super.noSuchMethod(Invocation.method(#setWebChromeClient, [client]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future setBackgroundColor(_i3.Color? color) => - (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future release() => - (super.noSuchMethod(Invocation.method(#release, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i2.WebView copy() => (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeWebView_3()) as _i2.WebView); -} - -/// A class which mocks [WebResourceRequest]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWebResourceRequest extends _i1.Mock - implements _i2.WebResourceRequest { - MockWebResourceRequest() { - _i1.throwOnMissingStub(this); - } - - @override - String get url => - (super.noSuchMethod(Invocation.getter(#url), returnValue: '') as String); - @override - bool get isForMainFrame => (super - .noSuchMethod(Invocation.getter(#isForMainFrame), returnValue: false) - as bool); - @override - bool get hasGesture => - (super.noSuchMethod(Invocation.getter(#hasGesture), returnValue: false) - as bool); - @override - String get method => - (super.noSuchMethod(Invocation.getter(#method), returnValue: '') - as String); - @override - Map get requestHeaders => - (super.noSuchMethod(Invocation.getter(#requestHeaders), - returnValue: {}) as Map); -} - -/// A class which mocks [WebViewAndroidDownloadListener]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWebViewAndroidDownloadListener extends _i1.Mock - implements _i7.WebViewAndroidDownloadListener { - MockWebViewAndroidDownloadListener() { - _i1.throwOnMissingStub(this); - } - - @override - _i5.Future Function(String, Map?) get loadUrl => - (super.noSuchMethod(Invocation.getter(#loadUrl), - returnValue: (String url, Map? headers) => - Future.value()) as _i5.Future Function( - String, Map?)); - @override - void onDownloadStart(String? url, String? userAgent, - String? contentDisposition, String? mimetype, int? contentLength) => - super.noSuchMethod( - Invocation.method(#onDownloadStart, - [url, userAgent, contentDisposition, mimetype, contentLength]), - returnValueForMissingStub: null); - @override - _i2.DownloadListener copy() => - (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeDownloadListener_4()) as _i2.DownloadListener); -} - -/// A class which mocks [WebViewAndroidJavaScriptChannel]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWebViewAndroidJavaScriptChannel extends _i1.Mock - implements _i7.WebViewAndroidJavaScriptChannel { - MockWebViewAndroidJavaScriptChannel() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.JavascriptChannelRegistry get javascriptChannelRegistry => - (super.noSuchMethod(Invocation.getter(#javascriptChannelRegistry), - returnValue: _FakeJavascriptChannelRegistry_5()) - as _i4.JavascriptChannelRegistry); - @override - String get channelName => - (super.noSuchMethod(Invocation.getter(#channelName), returnValue: '') - as String); - @override - void postMessage(String? message) => - super.noSuchMethod(Invocation.method(#postMessage, [message]), - returnValueForMissingStub: null); - @override - _i2.JavaScriptChannel copy() => - (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeJavaScriptChannel_6()) as _i2.JavaScriptChannel); -} - -/// A class which mocks [WebViewAndroidWebChromeClient]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWebViewAndroidWebChromeClient extends _i1.Mock - implements _i7.WebViewAndroidWebChromeClient { - MockWebViewAndroidWebChromeClient() { - _i1.throwOnMissingStub(this); - } - - @override - void onProgressChanged(_i2.WebView? webView, int? progress) => super - .noSuchMethod(Invocation.method(#onProgressChanged, [webView, progress]), - returnValueForMissingStub: null); - @override - _i2.WebChromeClient copy() => - (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeWebChromeClient_7()) as _i2.WebChromeClient); -} - -/// A class which mocks [WebViewAndroidWebViewClient]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWebViewAndroidWebViewClient extends _i1.Mock - implements _i7.WebViewAndroidWebViewClient { - MockWebViewAndroidWebViewClient() { - _i1.throwOnMissingStub(this); - } - - @override - void Function(String) get onPageStartedCallback => - (super.noSuchMethod(Invocation.getter(#onPageStartedCallback), - returnValue: (String url) {}) as void Function(String)); - @override - void Function(String) get onPageFinishedCallback => - (super.noSuchMethod(Invocation.getter(#onPageFinishedCallback), - returnValue: (String url) {}) as void Function(String)); - @override - void Function(_i4.WebResourceError) get onWebResourceErrorCallback => - (super.noSuchMethod(Invocation.getter(#onWebResourceErrorCallback), - returnValue: (_i4.WebResourceError error) {}) - as void Function(_i4.WebResourceError)); - @override - set onWebResourceErrorCallback( - void Function(_i4.WebResourceError)? _onWebResourceErrorCallback) => - super.noSuchMethod( - Invocation.setter( - #onWebResourceErrorCallback, _onWebResourceErrorCallback), - returnValueForMissingStub: null); - @override - bool get handlesNavigation => - (super.noSuchMethod(Invocation.getter(#handlesNavigation), - returnValue: false) as bool); - @override - bool get shouldOverrideUrlLoading => - (super.noSuchMethod(Invocation.getter(#shouldOverrideUrlLoading), - returnValue: false) as bool); - @override - void onPageStarted(_i2.WebView? webView, String? url) => - super.noSuchMethod(Invocation.method(#onPageStarted, [webView, url]), - returnValueForMissingStub: null); - @override - void onPageFinished(_i2.WebView? webView, String? url) => - super.noSuchMethod(Invocation.method(#onPageFinished, [webView, url]), - returnValueForMissingStub: null); - @override - void onReceivedError(_i2.WebView? webView, int? errorCode, - String? description, String? failingUrl) => - super.noSuchMethod( - Invocation.method( - #onReceivedError, [webView, errorCode, description, failingUrl]), - returnValueForMissingStub: null); - @override - void onReceivedRequestError(_i2.WebView? webView, - _i2.WebResourceRequest? request, _i2.WebResourceError? error) => - super.noSuchMethod( - Invocation.method(#onReceivedRequestError, [webView, request, error]), - returnValueForMissingStub: null); - @override - void urlLoading(_i2.WebView? webView, String? url) => - super.noSuchMethod(Invocation.method(#urlLoading, [webView, url]), - returnValueForMissingStub: null); - @override - void requestLoading(_i2.WebView? webView, _i2.WebResourceRequest? request) => - super.noSuchMethod(Invocation.method(#requestLoading, [webView, request]), - returnValueForMissingStub: null); - @override - _i2.WebViewClient copy() => (super.noSuchMethod(Invocation.method(#copy, []), - returnValue: _FakeWebViewClient_8()) as _i2.WebViewClient); -} - -/// A class which mocks [JavascriptChannelRegistry]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockJavascriptChannelRegistry extends _i1.Mock - implements _i4.JavascriptChannelRegistry { - MockJavascriptChannelRegistry() { - _i1.throwOnMissingStub(this); - } - - @override - Map get channels => - (super.noSuchMethod(Invocation.getter(#channels), - returnValue: {}) - as Map); - @override - void onJavascriptChannelMessage(String? channel, String? message) => - super.noSuchMethod( - Invocation.method(#onJavascriptChannelMessage, [channel, message]), - returnValueForMissingStub: null); - @override - void updateJavascriptChannelsFromSet(Set<_i4.JavascriptChannel>? channels) => - super.noSuchMethod( - Invocation.method(#updateJavascriptChannelsFromSet, [channels]), - returnValueForMissingStub: null); -} - -/// A class which mocks [WebViewPlatformCallbacksHandler]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWebViewPlatformCallbacksHandler extends _i1.Mock - implements _i4.WebViewPlatformCallbacksHandler { - MockWebViewPlatformCallbacksHandler() { - _i1.throwOnMissingStub(this); - } - - @override - _i5.FutureOr onNavigationRequest({String? url, bool? isForMainFrame}) => - (super.noSuchMethod( - Invocation.method(#onNavigationRequest, [], - {#url: url, #isForMainFrame: isForMainFrame}), - returnValue: Future.value(false)) as _i5.FutureOr); - @override - void onPageStarted(String? url) => - super.noSuchMethod(Invocation.method(#onPageStarted, [url]), - returnValueForMissingStub: null); - @override - void onPageFinished(String? url) => - super.noSuchMethod(Invocation.method(#onPageFinished, [url]), - returnValueForMissingStub: null); - @override - void onProgress(int? progress) => - super.noSuchMethod(Invocation.method(#onProgress, [progress]), - returnValueForMissingStub: null); - @override - void onWebResourceError(_i4.WebResourceError? error) => - super.noSuchMethod(Invocation.method(#onWebResourceError, [error]), - returnValueForMissingStub: null); -} - -/// A class which mocks [WebViewProxy]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWebViewProxy extends _i1.Mock implements _i7.WebViewProxy { - MockWebViewProxy() { - _i1.throwOnMissingStub(this); - } - - @override - _i2.WebView createWebView({bool? useHybridComposition}) => - (super.noSuchMethod( - Invocation.method(#createWebView, [], - {#useHybridComposition: useHybridComposition}), - returnValue: _FakeWebView_3()) as _i2.WebView); - @override - _i5.Future setWebContentsDebuggingEnabled(bool? enabled) => - (super.noSuchMethod( - Invocation.method(#setWebContentsDebuggingEnabled, [enabled]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); -} From 1e8aec6258db309540836e56669660e95836ac47 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 15 Dec 2022 10:33:05 -0500 Subject: [PATCH 2/4] Version bump --- .../webview_flutter/webview_flutter_android/CHANGELOG.md | 5 +++++ .../webview_flutter_android/example/pubspec.yaml | 3 +-- .../webview_flutter/webview_flutter_android/pubspec.yaml | 6 ++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index fbb502c7cca8..e8c65a69d540 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.0.0 + +* **BREAKING CHANGE** Updates platform implementation to `2.0.0` release of + `webview_flutter_platform_interface`. + ## 2.10.4 * Updates code for `no_leading_underscores_for_local_identifiers` lint. diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml index 29c320a45cc9..0daacb07b13f 100644 --- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml @@ -18,8 +18,7 @@ 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_platform_interface: - path: ../../webview_flutter_platform_interface + webview_flutter_platform_interface: ^2.0.0 dev_dependencies: espresso: ^0.2.0 diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 8155965f8771..4b22a1f3998e 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,8 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.10.4 -publish_to: none +version: 3.0.0 environment: sdk: ">=2.17.0 <3.0.0" @@ -21,8 +20,7 @@ flutter: dependencies: flutter: sdk: flutter - webview_flutter_platform_interface: - path: ../webview_flutter_platform_interface + webview_flutter_platform_interface: ^2.0.0 dev_dependencies: build_runner: ^2.1.4 From 000dcf54dde2d5bb028b87d2801498cee830fb72 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 15 Dec 2022 10:37:39 -0500 Subject: [PATCH 3/4] update all plugins config --- script/configs/exclude_all_plugins_app.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/script/configs/exclude_all_plugins_app.yaml b/script/configs/exclude_all_plugins_app.yaml index 62b20d9596bd..bf00b6c1bf9d 100644 --- a/script/configs/exclude_all_plugins_app.yaml +++ b/script/configs/exclude_all_plugins_app.yaml @@ -8,4 +8,8 @@ # 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_android From f5664db180a15500abb726008a7732e9018f1103 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 15 Dec 2022 12:55:51 -0500 Subject: [PATCH 4/4] fix lints and add action item to changelog --- .../webview_flutter_android/CHANGELOG.md | 3 ++- .../example/lib/legacy/web_view.dart | 4 ++-- .../webview_flutter_android/example/lib/main.dart | 12 ++++++------ .../test/legacy/webview_android_widget_test.dart | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index e8c65a69d540..a7a820db3141 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,7 +1,8 @@ ## 3.0.0 * **BREAKING CHANGE** Updates platform implementation to `2.0.0` release of - `webview_flutter_platform_interface`. + `webview_flutter_platform_interface`. See + [webview_flutter](https://pub.dev/packages/webview_flutter/versions/4.0.0) for updated usage. ## 2.10.4 diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/legacy/web_view.dart b/packages/webview_flutter/webview_flutter_android/example/lib/legacy/web_view.dart index 3129968a04c4..b77a503c959a 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/legacy/web_view.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/legacy/web_view.dart @@ -320,10 +320,10 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { required bool isForMainFrame, }) async { if (url.startsWith('https://www.youtube.com/')) { - print('blocking navigation to $url'); + debugPrint('blocking navigation to $url'); return false; } - print('allowing navigation to $url'); + debugPrint('allowing navigation to $url'); return true; } diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart index 56a9a00c7ad0..fe6d723c058f 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -99,16 +99,16 @@ class _WebViewExampleState extends State { const PlatformNavigationDelegateCreationParams(), ) ..setOnProgress((int progress) { - print('WebView is loading (progress : $progress%)'); + debugPrint('WebView is loading (progress : $progress%)'); }) ..setOnPageStarted((String url) { - print('Page started loading: $url'); + debugPrint('Page started loading: $url'); }) ..setOnPageFinished((String url) { - print('Page finished loading: $url'); + debugPrint('Page finished loading: $url'); }) ..setOnWebResourceError((WebResourceError error) { - print(''' + debugPrint(''' Page resource error: code: ${error.errorCode} description: ${error.description} @@ -118,10 +118,10 @@ Page resource error: }) ..setOnNavigationRequest((NavigationRequest request) { if (request.url.startsWith('https://www.youtube.com/')) { - print('blocking navigation to ${request.url}'); + debugPrint('blocking navigation to ${request.url}'); return NavigationDecision.prevent; } - print('allowing navigation to ${request.url}'); + debugPrint('allowing navigation to ${request.url}'); return NavigationDecision.navigate; }), ) diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart index 6967f65e76f0..909607ac8a71 100644 --- a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart @@ -465,7 +465,7 @@ void main() { await buildWidget(tester); expect( - () async => await testController.loadRequest( + () async => testController.loadRequest( WebViewRequest( uri: Uri.parse('www.google.com'), method: WebViewRequestMethod.get,