diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 82dbbd49f21..3f95f7a67a9 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.7.0 + +* Adds support to accept third party cookies. See + `AndroidWebViewCookieManager.setAcceptThirdPartyCookies`. + ## 3.6.3 * Updates gradle, AGP and fixes some lint errors. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java index 9cd785fc33f..6fdaeeaded0 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java @@ -7,22 +7,93 @@ import android.os.Build; import android.webkit.CookieManager; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.CookieManagerHostApi; +import java.util.Objects; + +/** + * Host API implementation for `CookieManager`. + * + *

This class may handle instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class CookieManagerHostApiImpl implements CookieManagerHostApi { + // To ease adding additional methods, this value is added prematurely. + @SuppressWarnings({"unused", "FieldCanBeLocal"}) + private final BinaryMessenger binaryMessenger; + + private final InstanceManager instanceManager; + private final CookieManagerProxy proxy; + + /** Proxy for constructors and static method of `CookieManager`. */ + @VisibleForTesting + static class CookieManagerProxy { + /** Handles the Dart static method `MyClass.myStaticMethod`. */ + @NonNull + public CookieManager getInstance() { + return CookieManager.getInstance(); + } + } + + /** + * Constructs a {@link CookieManagerHostApiImpl}. + * + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager maintains instances stored to communicate with attached Dart objects + */ + public CookieManagerHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + this(binaryMessenger, instanceManager, new CookieManagerProxy()); + } + + /** + * Constructs a {@link CookieManagerHostApiImpl}. + * + * @param binaryMessenger used to communicate with Dart over asynchronous messages + * @param instanceManager maintains instances stored to communicate with attached Dart objects + * @param proxy proxy for constructors and static methods of `CookieManager` + */ + public CookieManagerHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, + @NonNull InstanceManager instanceManager, + @NonNull CookieManagerProxy proxy) { + this.binaryMessenger = binaryMessenger; + this.instanceManager = instanceManager; + this.proxy = proxy; + } -class CookieManagerHostApiImpl implements CookieManagerHostApi { @Override - public void clearCookies(@NonNull GeneratedAndroidWebView.Result result) { - CookieManager cookieManager = CookieManager.getInstance(); + public void attachInstance(@NonNull Long instanceIdentifier) { + instanceManager.addDartCreatedInstance(proxy.getInstance(), instanceIdentifier); + } + + @Override + public void setCookie(@NonNull Long identifier, @NonNull String url, @NonNull String value) { + getCookieManagerInstance(identifier).setCookie(url, value); + } + + @Override + public void removeAllCookies( + @NonNull Long identifier, @NonNull GeneratedAndroidWebView.Result result) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - cookieManager.removeAllCookies(result::success); + getCookieManagerInstance(identifier).removeAllCookies(result::success); } else { - result.success(removeCookiesPreL(cookieManager)); + result.success(removeCookiesPreL(getCookieManagerInstance(identifier))); } } @Override - public void setCookie(@NonNull String url, @NonNull String value) { - CookieManager.getInstance().setCookie(url, value); + public void setAcceptThirdPartyCookies( + @NonNull Long identifier, @NonNull Long webViewIdentifier, @NonNull Boolean accept) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getCookieManagerInstance(identifier) + .setAcceptThirdPartyCookies( + Objects.requireNonNull(instanceManager.getInstance(webViewIdentifier)), accept); + } else { + throw new UnsupportedOperationException( + "`setAcceptThirdPartyCookies` is unsupported on versions below `Build.VERSION_CODES.LOLLIPOP`."); + } } /** @@ -40,4 +111,9 @@ private boolean removeCookiesPreL(CookieManager cookieManager) { } return hasCookies; } + + @NonNull + private CookieManager getCookieManagerInstance(@NonNull Long identifier) { + return Objects.requireNonNull(instanceManager.getInstance(identifier)); + } } 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 a9dc42f4010..4dc43fa1d9d 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 (v9.2.4), do not edit directly. +// Autogenerated from Pigeon (v9.2.5), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.webviewflutter; @@ -589,12 +589,25 @@ public void dispose(@NonNull Long identifierArg, @NonNull Reply callback) channelReply -> callback.reply(null)); } } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + /** + * Host API for `CookieManager`. + * + *

This class may handle instantiating and adding native object instances that are attached to + * a Dart instance or handle method calls on the associated native class or an instance of the + * class. + * + *

Generated interface from Pigeon that represents a handler of messages from Flutter. + */ public interface CookieManagerHostApi { - - void clearCookies(@NonNull Result result); - - void setCookie(@NonNull String url, @NonNull String value); + /** Handles attaching `CookieManager.instance` to a native instance. */ + void attachInstance(@NonNull Long instanceIdentifier); + /** Handles Dart method `CookieManager.setCookie`. */ + void setCookie(@NonNull Long identifier, @NonNull String url, @NonNull String value); + /** Handles Dart method `CookieManager.removeAllCookies`. */ + void removeAllCookies(@NonNull Long identifier, @NonNull Result result); + /** Handles Dart method `CookieManager.setAcceptThirdPartyCookies`. */ + void setAcceptThirdPartyCookies( + @NonNull Long identifier, @NonNull Long webViewIdentifier, @NonNull Boolean accept); /** The codec used by CookieManagerHostApi. */ static @NonNull MessageCodec getCodec() { @@ -610,12 +623,66 @@ static void setup( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.CookieManagerHostApi.clearCookies", + "dev.flutter.pigeon.CookieManagerHostApi.attachInstance", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number instanceIdentifierArg = (Number) args.get(0); + try { + api.attachInstance( + (instanceIdentifierArg == null) ? null : instanceIdentifierArg.longValue()); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.CookieManagerHostApi.setCookie", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + String urlArg = (String) args.get(1); + String valueArg = (String) args.get(2); + try { + api.setCookie( + (identifierArg == null) ? null : identifierArg.longValue(), urlArg, valueArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.CookieManagerHostApi.removeAllCookies", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); Result resultCallback = new Result() { public void success(Boolean result) { @@ -629,7 +696,8 @@ public void error(Throwable error) { } }; - api.clearCookies(resultCallback); + api.removeAllCookies( + (identifierArg == null) ? null : identifierArg.longValue(), resultCallback); }); } else { channel.setMessageHandler(null); @@ -638,16 +706,22 @@ public void error(Throwable error) { { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.CookieManagerHostApi.setCookie", getCodec()); + binaryMessenger, + "dev.flutter.pigeon.CookieManagerHostApi.setAcceptThirdPartyCookies", + getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; - String urlArg = (String) args.get(0); - String valueArg = (String) args.get(1); + Number identifierArg = (Number) args.get(0); + Number webViewIdentifierArg = (Number) args.get(1); + Boolean acceptArg = (Boolean) args.get(2); try { - api.setCookie(urlArg, valueArg); + api.setAcceptThirdPartyCookies( + (identifierArg == null) ? null : identifierArg.longValue(), + (webViewIdentifierArg == null) ? null : webViewIdentifierArg.longValue(), + acceptArg); wrapped.add(0, null); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); 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 af34649211a..79640b90b0b 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 @@ -127,7 +127,8 @@ private void setUp( instanceManager, new WebSettingsHostApiImpl.WebSettingsCreator())); FlutterAssetManagerHostApi.setup( binaryMessenger, new FlutterAssetManagerHostApiImpl(flutterAssetManager)); - CookieManagerHostApi.setup(binaryMessenger, new CookieManagerHostApiImpl()); + CookieManagerHostApi.setup( + binaryMessenger, new CookieManagerHostApiImpl(binaryMessenger, instanceManager)); WebStorageHostApi.setup( binaryMessenger, new WebStorageHostApiImpl(instanceManager, new WebStorageHostApiImpl.WebStorageCreator())); diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java deleted file mode 100644 index 11cdddfd333..00000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java +++ /dev/null @@ -1,87 +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; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.os.Build; -import android.webkit.CookieManager; -import android.webkit.ValueCallback; -import io.flutter.plugins.webviewflutter.utils.TestUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.MockedStatic; - -public class CookieManagerHostApiImplTest { - - private CookieManager cookieManager; - private MockedStatic staticMockCookieManager; - - @Before - public void setup() { - staticMockCookieManager = mockStatic(CookieManager.class); - cookieManager = mock(CookieManager.class); - when(CookieManager.getInstance()).thenReturn(cookieManager); - when(cookieManager.hasCookies()).thenReturn(true); - doAnswer( - answer -> { - ValueCallback callback = answer.getArgument(0); - (callback).onReceiveValue(true); - return null; - }) - .when(cookieManager) - .removeAllCookies(any()); - } - - @After - public void tearDown() { - staticMockCookieManager.close(); - } - - @Test - public void setCookieShouldCallSetCookie() { - // Setup - CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl(); - // Run - impl.setCookie("flutter.dev", "foo=bar; path=/"); - // Verify - verify(cookieManager).setCookie("flutter.dev", "foo=bar; path=/"); - } - - @Test - public void clearCookiesShouldCallRemoveAllCookiesOnAndroidLAbove() { - // Setup - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.LOLLIPOP); - @SuppressWarnings("unchecked") - GeneratedAndroidWebView.Result result = mock(GeneratedAndroidWebView.Result.class); - CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl(); - // Run - impl.clearCookies(result); - // Verify - verify(cookieManager).removeAllCookies(any()); - verify(result).success(true); - } - - @Test - @SuppressWarnings("deprecation") - public void clearCookiesShouldCallRemoveAllCookieBelowAndroidL() { - // Setup - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.KITKAT_WATCH); - @SuppressWarnings("unchecked") - GeneratedAndroidWebView.Result result = mock(GeneratedAndroidWebView.Result.class); - CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl(); - // Run - impl.clearCookies(result); - // Verify - verify(cookieManager).removeAllCookie(); - verify(result).success(true); - } -} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerTest.java new file mode 100644 index 00000000000..5c1abd19991 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerTest.java @@ -0,0 +1,129 @@ +// 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; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Build; +import android.webkit.CookieManager; +import android.webkit.ValueCallback; +import android.webkit.WebView; +import androidx.annotation.NonNull; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.webviewflutter.utils.TestUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class CookieManagerTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock public CookieManager mockCookieManager; + @Mock public BinaryMessenger mockBinaryMessenger; + @Mock public CookieManagerHostApiImpl.CookieManagerProxy mockProxy; + InstanceManager instanceManager; + + @Before + public void setUp() { + instanceManager = InstanceManager.create(identifier -> {}); + } + + @After + public void tearDown() { + instanceManager.stopFinalizationListener(); + } + + @Test + public void getInstance() { + final CookieManager mockCookieManager = mock(CookieManager.class); + final long instanceIdentifier = 1; + + when(mockProxy.getInstance()).thenReturn(mockCookieManager); + + final CookieManagerHostApiImpl hostApi = + new CookieManagerHostApiImpl(mockBinaryMessenger, instanceManager, mockProxy); + hostApi.attachInstance(instanceIdentifier); + + assertEquals(instanceManager.getInstance(instanceIdentifier), mockCookieManager); + } + + @Test + public void setCookie() { + final String url = "testString"; + final String value = "testString2"; + + final long instanceIdentifier = 0; + instanceManager.addDartCreatedInstance(mockCookieManager, instanceIdentifier); + + final CookieManagerHostApiImpl hostApi = + new CookieManagerHostApiImpl(mockBinaryMessenger, instanceManager); + + hostApi.setCookie(instanceIdentifier, url, value); + + verify(mockCookieManager).setCookie(url, value); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Test + public void clearCookies() { + TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.LOLLIPOP); + + final long instanceIdentifier = 0; + instanceManager.addDartCreatedInstance(mockCookieManager, instanceIdentifier); + + final CookieManagerHostApiImpl hostApi = + new CookieManagerHostApiImpl(mockBinaryMessenger, instanceManager); + + final Boolean[] successResult = new Boolean[1]; + hostApi.removeAllCookies( + instanceIdentifier, + new GeneratedAndroidWebView.Result() { + @Override + public void success(Boolean result) { + successResult[0] = result; + } + + @Override + public void error(@NonNull Throwable error) {} + }); + + final ArgumentCaptor valueCallbackArgumentCaptor = + ArgumentCaptor.forClass(ValueCallback.class); + verify(mockCookieManager).removeAllCookies(valueCallbackArgumentCaptor.capture()); + + final Boolean returnValue = true; + valueCallbackArgumentCaptor.getValue().onReceiveValue(returnValue); + + assertEquals(successResult[0], returnValue); + } + + @Test + public void setAcceptThirdPartyCookies() { + TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.LOLLIPOP); + + final WebView mockWebView = mock(WebView.class); + final long webViewIdentifier = 4; + instanceManager.addDartCreatedInstance(mockWebView, webViewIdentifier); + + final boolean accept = true; + + final long instanceIdentifier = 0; + instanceManager.addDartCreatedInstance(mockCookieManager, instanceIdentifier); + + final CookieManagerHostApiImpl hostApi = + new CookieManagerHostApiImpl(mockBinaryMessenger, instanceManager); + + hostApi.setAcceptThirdPartyCookies(instanceIdentifier, webViewIdentifier, accept); + + verify(mockCookieManager).setAcceptThirdPartyCookies(mockWebView, accept); + } +} 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 35e1767dd6b..fedd731bd6e 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 @@ -407,21 +407,34 @@ class WebView extends JavaObject { } /// Manages cookies globally for all webviews. -class CookieManager { - CookieManager._(); - - static CookieManager? _instance; +/// +/// See https://developer.android.com/reference/android/webkit/CookieManager. +class CookieManager extends JavaObject { + /// Instantiates a [CookieManager] without creating and attaching to an + /// instance of the associated native class. + /// + /// This should only be used outside of tests by subclasses created by this + /// library or to create a copy for an [InstanceManager]. + @protected + CookieManager.detached({super.binaryMessenger, super.instanceManager}) + : _cookieManagerApi = CookieManagerHostApiImpl( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + ), + super.detached(); - /// Gets the globally set CookieManager instance. - static CookieManager get instance => _instance ??= CookieManager._(); + static final CookieManager _instance = + CookieManagerHostApiImpl().attachInstanceFromInstances( + CookieManager.detached(), + ); - /// Setter for the singleton value, for testing purposes only. - @visibleForTesting - static set instance(CookieManager value) => _instance = value; + final CookieManagerHostApiImpl _cookieManagerApi; - /// Pigeon Host Api implementation for [CookieManager]. - @visibleForTesting - static CookieManagerHostApi api = CookieManagerHostApi(); + /// Access a static field synchronously. + static CookieManager get instance { + AndroidWebViewFlutterApis.instance.ensureSetUp(); + return _instance; + } /// Sets a single cookie (key-value pair) for the given URL. Any existing /// cookie with the same host, path and name will be replaced with the new @@ -441,12 +454,37 @@ class CookieManager { /// Params: /// url – the URL for which the cookie is to be set /// value – the cookie as a string, using the format of the 'Set-Cookie' HTTP response header - Future setCookie(String url, String value) => api.setCookie(url, value); + Future setCookie(String url, String value) { + return _cookieManagerApi.setCookieFromInstances(this, url, value); + } /// Removes all cookies. /// /// The returned future resolves to true if any cookies were removed. - Future clearCookies() => api.clearCookies(); + Future removeAllCookies() { + return _cookieManagerApi.removeAllCookiesFromInstances(this); + } + + /// Sets whether the WebView should allow third party cookies to be set. + /// + /// Apps that target `Build.VERSION_CODES.KITKAT` or below default to allowing + /// third party cookies. Apps targeting `Build.VERSION_CODES.LOLLIPOP` or + /// later default to disallowing third party cookies. + Future setAcceptThirdPartyCookies(WebView webView, bool accept) { + return _cookieManagerApi.setAcceptThirdPartyCookiesFromInstances( + this, + webView, + accept, + ); + } + + @override + CookieManager copy() { + return CookieManager.detached( + binaryMessenger: _cookieManagerApi.binaryMessenger, + instanceManager: _cookieManagerApi.instanceManager, + ); + } } /// Manages settings state for a [WebView]. diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart index 7488cd86337..5a2c56ef16a 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.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 (v9.2.4), do not edit directly. +// Autogenerated from Pigeon (v9.2.5), 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 @@ -258,6 +258,11 @@ abstract class JavaObjectFlutterApi { } } +/// Host API for `CookieManager`. +/// +/// This class may handle instantiating and adding native object instances that +/// are attached to a Dart instance or handle method calls on the associated +/// native class or an instance of the class. class CookieManagerHostApi { /// Constructor for [CookieManagerHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default @@ -268,11 +273,60 @@ class CookieManagerHostApi { static const MessageCodec codec = StandardMessageCodec(); - Future clearCookies() async { + /// Handles attaching `CookieManager.instance` to a native instance. + Future attachInstance(int arg_instanceIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.CookieManagerHostApi.clearCookies', codec, + 'dev.flutter.pigeon.CookieManagerHostApi.attachInstance', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send(null) as List?; + final List? replyList = + await channel.send([arg_instanceIdentifier]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + /// Handles Dart method `CookieManager.setCookie`. + Future setCookie( + int arg_identifier, String arg_url, String arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CookieManagerHostApi.setCookie', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_identifier, arg_url, arg_value]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + /// Handles Dart method `CookieManager.removeAllCookies`. + Future removeAllCookies(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CookieManagerHostApi.removeAllCookies', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -294,12 +348,16 @@ class CookieManagerHostApi { } } - Future setCookie(String arg_url, String arg_value) async { + /// Handles Dart method `CookieManager.setAcceptThirdPartyCookies`. + Future setAcceptThirdPartyCookies( + int arg_identifier, int arg_webViewIdentifier, bool arg_accept) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.CookieManagerHostApi.setCookie', codec, + 'dev.flutter.pigeon.CookieManagerHostApi.setAcceptThirdPartyCookies', + codec, binaryMessenger: _binaryMessenger); - final List? replyList = - await channel.send([arg_url, arg_value]) as List?; + final List? replyList = await channel + .send([arg_identifier, arg_webViewIdentifier, arg_accept]) + as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', 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 f1010d76f63..7f7c3427ead 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 @@ -1075,3 +1075,59 @@ class PermissionRequestFlutterApiImpl implements PermissionRequestFlutterApi { ); } } + +/// Host api implementation for [CookieManager]. +class CookieManagerHostApiImpl extends CookieManagerHostApi { + /// Constructs a [CookieManagerHostApiImpl]. + CookieManagerHostApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, + super(binaryMessenger: binaryMessenger); + + /// Sends binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + /// Helper method to convert instance ids to objects. + CookieManager attachInstanceFromInstances(CookieManager instance) { + attachInstance(instanceManager.addDartCreatedInstance(instance)); + return instance; + } + + /// Helper method to convert instance ids to objects. + Future setCookieFromInstances( + CookieManager instance, + String url, + String value, + ) { + return setCookie( + instanceManager.getIdentifier(instance)!, + url, + value, + ); + } + + /// Helper method to convert instance ids to objects. + Future removeAllCookiesFromInstances(CookieManager instance) { + return removeAllCookies(instanceManager.getIdentifier(instance)!); + } + + /// Helper method to convert instance ids to objects. + Future setAcceptThirdPartyCookiesFromInstances( + CookieManager instance, + WebView webView, + bool accept, + ) { + return setAcceptThirdPartyCookies( + instanceManager.getIdentifier(instance)!, + instanceManager.getIdentifier(webView)!, + accept, + ); + } +} 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 index 5174ca57608..0c1be7c178e 100644 --- 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 @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'android_webview.dart'; +import 'android_webview_controller.dart'; /// Object specifying creation parameters for creating a [AndroidWebViewCookieManager]. /// @@ -47,7 +48,7 @@ class AndroidWebViewCookieManager extends PlatformWebViewCookieManager { @override Future clearCookies() { - return _cookieManager.clearCookies(); + return _cookieManager.removeAllCookies(); } @override @@ -71,4 +72,19 @@ class AndroidWebViewCookieManager extends PlatformWebViewCookieManager { } return true; } + + /// Sets whether the WebView should allow third party cookies to be set. + /// + /// Apps that target `Build.VERSION_CODES.KITKAT` or below default to allowing + /// third party cookies. Apps targeting `Build.VERSION_CODES.LOLLIPOP` or + /// later default to disallowing third party cookies. + Future setAcceptThirdPartyCookies( + AndroidWebViewController controller, + bool accept, + ) { + // ignore: invalid_use_of_visible_for_testing_member + final WebView webView = WebView.api.instanceManager + .getInstanceWithWeakReference(controller.webViewIdentifier)!; + return _cookieManager.setAcceptThirdPartyCookies(webView, accept); + } } diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_cookie_manager.dart b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_cookie_manager.dart index 663a2076b41..a24f625d878 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_cookie_manager.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_cookie_manager.dart @@ -2,6 +2,7 @@ // 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'; // ignore: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; @@ -9,9 +10,15 @@ import '../android_webview.dart' as android_webview; /// Handles all cookie operations for the current platform. class WebViewAndroidCookieManager extends WebViewCookieManagerPlatform { + /// Constructs a [WebViewAndroidCookieManager]. + WebViewAndroidCookieManager({ + @visibleForTesting android_webview.CookieManager? cookieManager, + }) : _cookieManager = cookieManager ?? android_webview.CookieManager.instance; + + final android_webview.CookieManager _cookieManager; + @override - Future clearCookies() => - android_webview.CookieManager.instance.clearCookies(); + Future clearCookies() => _cookieManager.removeAllCookies(); @override Future setCookie(WebViewCookie cookie) { @@ -19,7 +26,7 @@ class WebViewAndroidCookieManager extends WebViewCookieManagerPlatform { throw ArgumentError( 'The path property for the provided cookie was not given a legal value.'); } - return android_webview.CookieManager.instance.setCookie( + return _cookieManager.setCookie( cookie.domain, '${Uri.encodeComponent(cookie.name)}=${Uri.encodeComponent(cookie.value)}; path=${cookie.path}', ); 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 62a7aa8a25b..a4886b9bc5c 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -113,12 +113,29 @@ abstract class JavaObjectFlutterApi { void dispose(int identifier); } -@HostApi() +/// Host API for `CookieManager`. +/// +/// This class may handle instantiating and adding native object instances that +/// are attached to a Dart instance or handle method calls on the associated +/// native class or an instance of the class. +@HostApi(dartHostTestHandler: 'TestCookieManagerHostApi') abstract class CookieManagerHostApi { + /// Handles attaching `CookieManager.instance` to a native instance. + void attachInstance(int instanceIdentifier); + + /// Handles Dart method `CookieManager.setCookie`. + void setCookie(int identifier, String url, String value); + + /// Handles Dart method `CookieManager.removeAllCookies`. @async - bool clearCookies(); + bool removeAllCookies(int identifier); - void setCookie(String url, String value); + /// Handles Dart method `CookieManager.setAcceptThirdPartyCookies`. + void setAcceptThirdPartyCookies( + int identifier, + int webViewIdentifier, + bool accept, + ); } @HostApi(dartHostTestHandler: 'TestWebViewHostApi') diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index d9053493cd9..b674a5a6b64 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/packages/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: 3.6.3 +version: 3.7.0 environment: sdk: ">=2.18.0 <4.0.0" 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 index 8e2be2ba48d..e17347a2c0e 100644 --- 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 @@ -7,19 +7,29 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_android/src/android_webview.dart' as android_webview; +import 'package:webview_flutter_android/src/android_webview_api_impls.dart'; +import 'package:webview_flutter_android/src/instance_manager.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'android_webview_cookie_manager_test.mocks.dart'; +import 'test_android_webview.g.dart'; -@GenerateMocks([android_webview.CookieManager]) +@GenerateMocks([ + android_webview.CookieManager, + AndroidWebViewController, + TestInstanceManagerHostApi, +]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); + // Mocks the call to clear the native InstanceManager. + TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + test('clearCookies should call android_webview.clearCookies', () async { final android_webview.CookieManager mockCookieManager = MockCookieManager(); - when(mockCookieManager.clearCookies()) + when(mockCookieManager.removeAllCookies()) .thenAnswer((_) => Future.value(true)); final AndroidWebViewCookieManagerCreationParams params = @@ -32,7 +42,7 @@ void main() { .clearCookies(); expect(hasClearedCookies, true); - verify(mockCookieManager.clearCookies()); + verify(mockCookieManager.removeAllCookies()); }); test('setCookie should throw ArgumentError for cookie with invalid path', () { @@ -56,7 +66,7 @@ void main() { }); test( - 'setCookie should call android_webview.csetCookie with properly formatted cookie value', + 'setCookie should call android_webview.setCookie with properly formatted cookie value', () { final android_webview.CookieManager mockCookieManager = MockCookieManager(); final AndroidWebViewCookieManagerCreationParams params = @@ -76,4 +86,39 @@ void main() { 'foo%26=bar%40; path=/', )); }); + + test('setAcceptThirdPartyCookies', () async { + final MockAndroidWebViewController mockController = + MockAndroidWebViewController(); + + final InstanceManager instanceManager = + InstanceManager(onWeakReferenceRemoved: (_) {}); + android_webview.WebView.api = WebViewHostApiImpl( + instanceManager: instanceManager, + ); + final android_webview.WebView webView = android_webview.WebView.detached( + instanceManager: instanceManager, + ); + + const int webViewIdentifier = 4; + instanceManager.addHostCreatedInstance(webView, webViewIdentifier); + + when(mockController.webViewIdentifier).thenReturn(webViewIdentifier); + + final AndroidWebViewCookieManagerCreationParams params = + AndroidWebViewCookieManagerCreationParams + .fromPlatformWebViewCookieManagerCreationParams( + const PlatformWebViewCookieManagerCreationParams()); + + final android_webview.CookieManager mockCookieManager = MockCookieManager(); + + await AndroidWebViewCookieManager( + params, + cookieManager: mockCookieManager, + ).setAcceptThirdPartyCookies(mockController, false); + + verify(mockCookieManager.setAcceptThirdPartyCookies(webView, false)); + + android_webview.WebView.api = WebViewHostApiImpl(); + }); } diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart index d2937d54f92..9f4aa1dfc70 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart @@ -3,10 +3,17 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i3; +import 'dart:async' as _i5; +import 'dart:ui' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; +import 'package:webview_flutter_android/src/android_webview_controller.dart' + as _i6; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' + as _i3; + +import 'test_android_webview.g.dart' as _i7; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -19,6 +26,47 @@ import 'package:webview_flutter_android/src/android_webview.dart' as _i2; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +class _FakeCookieManager_0 extends _i1.SmartFake implements _i2.CookieManager { + _FakeCookieManager_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformWebViewControllerCreationParams_1 extends _i1.SmartFake + implements _i3.PlatformWebViewControllerCreationParams { + _FakePlatformWebViewControllerCreationParams_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeObject_2 extends _i1.SmartFake implements Object { + _FakeObject_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeOffset_3 extends _i1.SmartFake implements _i4.Offset { + _FakeOffset_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [CookieManager]. /// /// See the documentation for Mockito's code generation for more information. @@ -28,7 +76,7 @@ class MockCookieManager extends _i1.Mock implements _i2.CookieManager { } @override - _i3.Future setCookie( + _i5.Future setCookie( String? url, String? value, ) => @@ -40,15 +88,389 @@ class MockCookieManager extends _i1.Mock implements _i2.CookieManager { value, ], ), - returnValue: _i3.Future.value(), - returnValueForMissingStub: _i3.Future.value(), - ) as _i3.Future); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future removeAllCookies() => (super.noSuchMethod( + Invocation.method( + #removeAllCookies, + [], + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + _i5.Future setAcceptThirdPartyCookies( + _i2.WebView? webView, + bool? accept, + ) => + (super.noSuchMethod( + Invocation.method( + #setAcceptThirdPartyCookies, + [ + webView, + accept, + ], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i2.CookieManager copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeCookieManager_0( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.CookieManager); +} + +/// A class which mocks [AndroidWebViewController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAndroidWebViewController extends _i1.Mock + implements _i6.AndroidWebViewController { + MockAndroidWebViewController() { + _i1.throwOnMissingStub(this); + } + + @override + int get webViewIdentifier => (super.noSuchMethod( + Invocation.getter(#webViewIdentifier), + returnValue: 0, + ) as int); + @override + _i3.PlatformWebViewControllerCreationParams get params => (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformWebViewControllerCreationParams_1( + this, + Invocation.getter(#params), + ), + ) as _i3.PlatformWebViewControllerCreationParams); + @override + _i5.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( + Invocation.method( + #loadFile, + [absoluteFilePath], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future loadFlutterAsset(String? key) => (super.noSuchMethod( + Invocation.method( + #loadFlutterAsset, + [key], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future loadHtmlString( + String? html, { + String? baseUrl, + }) => + (super.noSuchMethod( + Invocation.method( + #loadHtmlString, + [html], + {#baseUrl: baseUrl}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future loadRequest(_i3.LoadRequestParams? params) => + (super.noSuchMethod( + Invocation.method( + #loadRequest, + [params], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future currentUrl() => (super.noSuchMethod( + Invocation.method( + #currentUrl, + [], + ), + returnValue: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future canGoBack() => (super.noSuchMethod( + Invocation.method( + #canGoBack, + [], + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + _i5.Future canGoForward() => (super.noSuchMethod( + Invocation.method( + #canGoForward, + [], + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + _i5.Future goBack() => (super.noSuchMethod( + Invocation.method( + #goBack, + [], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future goForward() => (super.noSuchMethod( + Invocation.method( + #goForward, + [], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future reload() => (super.noSuchMethod( + Invocation.method( + #reload, + [], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future clearCache() => (super.noSuchMethod( + Invocation.method( + #clearCache, + [], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future clearLocalStorage() => (super.noSuchMethod( + Invocation.method( + #clearLocalStorage, + [], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setPlatformNavigationDelegate( + _i3.PlatformNavigationDelegate? handler) => + (super.noSuchMethod( + Invocation.method( + #setPlatformNavigationDelegate, + [handler], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future runJavaScript(String? javaScript) => (super.noSuchMethod( + Invocation.method( + #runJavaScript, + [javaScript], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future runJavaScriptReturningResult(String? javaScript) => + (super.noSuchMethod( + Invocation.method( + #runJavaScriptReturningResult, + [javaScript], + ), + returnValue: _i5.Future.value(_FakeObject_2( + this, + Invocation.method( + #runJavaScriptReturningResult, + [javaScript], + ), + )), + ) as _i5.Future); + @override + _i5.Future addJavaScriptChannel( + _i3.JavaScriptChannelParams? javaScriptChannelParams) => + (super.noSuchMethod( + Invocation.method( + #addJavaScriptChannel, + [javaScriptChannelParams], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future removeJavaScriptChannel(String? javaScriptChannelName) => + (super.noSuchMethod( + Invocation.method( + #removeJavaScriptChannel, + [javaScriptChannelName], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future getTitle() => (super.noSuchMethod( + Invocation.method( + #getTitle, + [], + ), + returnValue: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future scrollTo( + int? x, + int? y, + ) => + (super.noSuchMethod( + Invocation.method( + #scrollTo, + [ + x, + y, + ], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future scrollBy( + int? x, + int? y, + ) => + (super.noSuchMethod( + Invocation.method( + #scrollBy, + [ + x, + y, + ], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future<_i4.Offset> getScrollPosition() => (super.noSuchMethod( + Invocation.method( + #getScrollPosition, + [], + ), + returnValue: _i5.Future<_i4.Offset>.value(_FakeOffset_3( + this, + Invocation.method( + #getScrollPosition, + [], + ), + )), + ) as _i5.Future<_i4.Offset>); + @override + _i5.Future enableZoom(bool? enabled) => (super.noSuchMethod( + Invocation.method( + #enableZoom, + [enabled], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setBackgroundColor(_i4.Color? color) => (super.noSuchMethod( + Invocation.method( + #setBackgroundColor, + [color], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setJavaScriptMode(_i3.JavaScriptMode? javaScriptMode) => + (super.noSuchMethod( + Invocation.method( + #setJavaScriptMode, + [javaScriptMode], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setUserAgent(String? userAgent) => (super.noSuchMethod( + Invocation.method( + #setUserAgent, + [userAgent], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @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 setTextZoom(int? textZoom) => (super.noSuchMethod( + Invocation.method( + #setTextZoom, + [textZoom], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setOnShowFileSelector( + _i5.Future> Function(_i6.FileSelectorParams)? + onShowFileSelector) => + (super.noSuchMethod( + Invocation.method( + #setOnShowFileSelector, + [onShowFileSelector], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setOnPlatformPermissionRequest( + void Function(_i3.PlatformWebViewPermissionRequest)? + onPermissionRequest) => + (super.noSuchMethod( + Invocation.method( + #setOnPlatformPermissionRequest, + [onPermissionRequest], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); +} + +/// A class which mocks [TestInstanceManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestInstanceManagerHostApi extends _i1.Mock + implements _i7.TestInstanceManagerHostApi { + MockTestInstanceManagerHostApi() { + _i1.throwOnMissingStub(this); + } + @override - _i3.Future clearCookies() => (super.noSuchMethod( + void clear() => super.noSuchMethod( Invocation.method( - #clearCookies, + #clear, [], ), - returnValue: _i3.Future.value(false), - ) as _i3.Future); + returnValueForMissingStub: null, + ); } 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 73b54caabc1..922d7c78bbc 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 @@ -17,6 +17,7 @@ import 'test_android_webview.g.dart'; CookieManagerHostApi, DownloadListener, JavaScriptChannel, + TestCookieManagerHostApi, TestDownloadListenerHostApi, TestInstanceManagerHostApi, TestJavaObjectHostApi, @@ -35,8 +36,9 @@ import 'test_android_webview.g.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - // Mocks the call to clear the native InstanceManager. + // Mocks the calls to the native InstanceManager. TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi()); + TestJavaObjectHostApi.setup(MockTestJavaObjectHostApi()); group('Android WebView', () { group('JavaObject', () { @@ -47,10 +49,6 @@ void main() { TestJavaObjectHostApi.setup(mockPlatformHostApi); }); - tearDown(() { - TestJavaObjectHostApi.setup(null); - }); - test('JavaObject.dispose', () async { int? callbackIdentifier; final InstanceManager instanceManager = InstanceManager( @@ -1051,18 +1049,103 @@ void main() { }); group('CookieManager', () { - test('setCookie calls setCookie on CookieManagerHostApi', () { - CookieManager.api = MockCookieManagerHostApi(); - CookieManager.instance.setCookie('foo', 'bar'); - verify(CookieManager.api.setCookie('foo', 'bar')); + tearDown(() { + TestCookieManagerHostApi.setup(null); + }); + + test('instance', () { + final MockTestCookieManagerHostApi mockApi = + MockTestCookieManagerHostApi(); + TestCookieManagerHostApi.setup(mockApi); + + final CookieManager instance = CookieManager.instance; + + verify(mockApi.attachInstance( + JavaObject.globalInstanceManager.getIdentifier(instance), + )); + }); + + test('setCookie', () async { + final MockTestCookieManagerHostApi mockApi = + MockTestCookieManagerHostApi(); + TestCookieManagerHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final CookieManager instance = CookieManager.detached( + instanceManager: instanceManager, + ); + const int instanceIdentifier = 0; + instanceManager.addHostCreatedInstance(instance, instanceIdentifier); + + const String url = 'testString'; + const String value = 'testString2'; + + await instance.setCookie(url, value); + + verify(mockApi.setCookie(instanceIdentifier, url, value)); }); - test('clearCookies calls clearCookies on CookieManagerHostApi', () { - CookieManager.api = MockCookieManagerHostApi(); - when(CookieManager.api.clearCookies()) - .thenAnswer((_) => Future.value(true)); - CookieManager.instance.clearCookies(); - verify(CookieManager.api.clearCookies()); + test('clearCookies', () async { + final MockTestCookieManagerHostApi mockApi = + MockTestCookieManagerHostApi(); + TestCookieManagerHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final CookieManager instance = CookieManager.detached( + instanceManager: instanceManager, + ); + const int instanceIdentifier = 0; + instanceManager.addHostCreatedInstance(instance, instanceIdentifier); + + const bool result = true; + when(mockApi.removeAllCookies( + instanceIdentifier, + )).thenAnswer((_) => Future.value(result)); + + expect(await instance.removeAllCookies(), result); + + verify(mockApi.removeAllCookies(instanceIdentifier)); + }); + + test('setAcceptThirdPartyCookies', () async { + final MockTestCookieManagerHostApi mockApi = + MockTestCookieManagerHostApi(); + TestCookieManagerHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final CookieManager instance = CookieManager.detached( + instanceManager: instanceManager, + ); + const int instanceIdentifier = 0; + instanceManager.addHostCreatedInstance(instance, instanceIdentifier); + + final WebView webView = WebView.detached( + instanceManager: instanceManager, + ); + const int webViewIdentifier = 4; + instanceManager.addHostCreatedInstance(webView, webViewIdentifier); + + const bool accept = true; + + await instance.setAcceptThirdPartyCookies( + webView, + accept, + ); + + verify(mockApi.setAcceptThirdPartyCookies( + instanceIdentifier, + webViewIdentifier, + accept, + )); }); }); 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 58448c063de..3b55d856d05 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 @@ -117,15 +117,18 @@ class MockCookieManagerHostApi extends _i1.Mock } @override - _i5.Future clearCookies() => (super.noSuchMethod( + _i5.Future attachInstance(int? arg_instanceIdentifier) => + (super.noSuchMethod( Invocation.method( - #clearCookies, - [], + #attachInstance, + [arg_instanceIdentifier], ), - returnValue: _i5.Future.value(false), - ) as _i5.Future); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i5.Future setCookie( + int? arg_identifier, String? arg_url, String? arg_value, ) => @@ -133,6 +136,7 @@ class MockCookieManagerHostApi extends _i1.Mock Invocation.method( #setCookie, [ + arg_identifier, arg_url, arg_value, ], @@ -140,6 +144,32 @@ class MockCookieManagerHostApi extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); + @override + _i5.Future removeAllCookies(int? arg_identifier) => (super.noSuchMethod( + Invocation.method( + #removeAllCookies, + [arg_identifier], + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + _i5.Future setAcceptThirdPartyCookies( + int? arg_identifier, + int? arg_webViewIdentifier, + bool? arg_accept, + ) => + (super.noSuchMethod( + Invocation.method( + #setAcceptThirdPartyCookies, + [ + arg_identifier, + arg_webViewIdentifier, + arg_accept, + ], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } /// A class which mocks [DownloadListener]. @@ -223,6 +253,67 @@ class MockJavaScriptChannel extends _i1.Mock implements _i2.JavaScriptChannel { ) as _i2.JavaScriptChannel); } +/// A class which mocks [TestCookieManagerHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestCookieManagerHostApi extends _i1.Mock + implements _i6.TestCookieManagerHostApi { + MockTestCookieManagerHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void attachInstance(int? instanceIdentifier) => super.noSuchMethod( + Invocation.method( + #attachInstance, + [instanceIdentifier], + ), + returnValueForMissingStub: null, + ); + @override + void setCookie( + int? identifier, + String? url, + String? value, + ) => + super.noSuchMethod( + Invocation.method( + #setCookie, + [ + identifier, + url, + value, + ], + ), + returnValueForMissingStub: null, + ); + @override + _i5.Future removeAllCookies(int? identifier) => (super.noSuchMethod( + Invocation.method( + #removeAllCookies, + [identifier], + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + void setAcceptThirdPartyCookies( + int? identifier, + int? webViewIdentifier, + bool? accept, + ) => + super.noSuchMethod( + Invocation.method( + #setAcceptThirdPartyCookies, + [ + identifier, + webViewIdentifier, + accept, + ], + ), + returnValueForMissingStub: null, + ); +} + /// A class which mocks [TestDownloadListenerHostApi]. /// /// See the documentation for Mockito's code generation for more information. diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.dart index e4cd6163486..02576d0d653 100644 --- a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.dart @@ -16,20 +16,20 @@ import 'webview_android_cookie_manager_test.mocks.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - setUp(() { - android_webview.CookieManager.instance = MockCookieManager(); - }); - test('clearCookies should call android_webview.clearCookies', () { - when(android_webview.CookieManager.instance.clearCookies()) + final MockCookieManager mockCookieManager = MockCookieManager(); + when(mockCookieManager.removeAllCookies()) .thenAnswer((_) => Future.value(true)); - WebViewAndroidCookieManager().clearCookies(); - verify(android_webview.CookieManager.instance.clearCookies()); + WebViewAndroidCookieManager( + cookieManager: mockCookieManager, + ).clearCookies(); + verify(mockCookieManager.removeAllCookies()); }); test('setCookie should throw ArgumentError for cookie with invalid path', () { expect( - () => WebViewAndroidCookieManager().setCookie(const WebViewCookie( + () => WebViewAndroidCookieManager(cookieManager: MockCookieManager()) + .setCookie(const WebViewCookie( name: 'foo', value: 'bar', domain: 'flutter.dev', @@ -42,12 +42,13 @@ void main() { test( 'setCookie should call android_webview.csetCookie with properly formatted cookie value', () { - WebViewAndroidCookieManager().setCookie(const WebViewCookie( + final MockCookieManager mockCookieManager = MockCookieManager(); + WebViewAndroidCookieManager(cookieManager: mockCookieManager) + .setCookie(const WebViewCookie( name: 'foo&', value: 'bar@', domain: 'flutter.dev', )); - verify(android_webview.CookieManager.instance - .setCookie('flutter.dev', 'foo%26=bar%40; path=/')); + verify(mockCookieManager.setCookie('flutter.dev', 'foo%26=bar%40; path=/')); }); } 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 index b306f2c77d6..864dbaa04aa 100644 --- 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 @@ -19,6 +19,16 @@ import 'package:webview_flutter_android/src/android_webview.dart' as _i2; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +class _FakeCookieManager_0 extends _i1.SmartFake implements _i2.CookieManager { + _FakeCookieManager_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [CookieManager]. /// /// See the documentation for Mockito's code generation for more information. @@ -44,11 +54,41 @@ class MockCookieManager extends _i1.Mock implements _i2.CookieManager { returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override - _i3.Future clearCookies() => (super.noSuchMethod( + _i3.Future removeAllCookies() => (super.noSuchMethod( Invocation.method( - #clearCookies, + #removeAllCookies, [], ), returnValue: _i3.Future.value(false), ) as _i3.Future); + @override + _i3.Future setAcceptThirdPartyCookies( + _i2.WebView? webView, + bool? accept, + ) => + (super.noSuchMethod( + Invocation.method( + #setAcceptThirdPartyCookies, + [ + webView, + accept, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + @override + _i2.CookieManager copy() => (super.noSuchMethod( + Invocation.method( + #copy, + [], + ), + returnValue: _FakeCookieManager_0( + this, + Invocation.method( + #copy, + [], + ), + ), + ) as _i2.CookieManager); } diff --git a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart index fca0b96dd87..20b084e40c3 100644 --- a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart +++ b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.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 (v9.2.4), do not edit directly. +// Autogenerated from Pigeon (v9.2.5), 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 @@ -85,6 +85,136 @@ abstract class TestJavaObjectHostApi { } } +/// Host API for `CookieManager`. +/// +/// This class may handle instantiating and adding native object instances that +/// are attached to a Dart instance or handle method calls on the associated +/// native class or an instance of the class. +abstract class TestCookieManagerHostApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = StandardMessageCodec(); + + /// Handles attaching `CookieManager.instance` to a native instance. + void attachInstance(int instanceIdentifier); + + /// Handles Dart method `CookieManager.setCookie`. + void setCookie(int identifier, String url, String value); + + /// Handles Dart method `CookieManager.removeAllCookies`. + Future removeAllCookies(int identifier); + + /// Handles Dart method `CookieManager.setAcceptThirdPartyCookies`. + void setAcceptThirdPartyCookies( + int identifier, int webViewIdentifier, bool accept); + + static void setup(TestCookieManagerHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CookieManagerHostApi.attachInstance', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.CookieManagerHostApi.attachInstance was null.'); + final List args = (message as List?)!; + final int? arg_instanceIdentifier = (args[0] as int?); + assert(arg_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.CookieManagerHostApi.attachInstance was null, expected non-null int.'); + api.attachInstance(arg_instanceIdentifier!); + return []; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CookieManagerHostApi.setCookie', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.CookieManagerHostApi.setCookie was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.CookieManagerHostApi.setCookie was null, expected non-null int.'); + final String? arg_url = (args[1] as String?); + assert(arg_url != null, + 'Argument for dev.flutter.pigeon.CookieManagerHostApi.setCookie was null, expected non-null String.'); + final String? arg_value = (args[2] as String?); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.CookieManagerHostApi.setCookie was null, expected non-null String.'); + api.setCookie(arg_identifier!, arg_url!, arg_value!); + return []; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CookieManagerHostApi.removeAllCookies', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.CookieManagerHostApi.removeAllCookies was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.CookieManagerHostApi.removeAllCookies was null, expected non-null int.'); + final bool output = await api.removeAllCookies(arg_identifier!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CookieManagerHostApi.setAcceptThirdPartyCookies', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.CookieManagerHostApi.setAcceptThirdPartyCookies was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.CookieManagerHostApi.setAcceptThirdPartyCookies was null, expected non-null int.'); + final int? arg_webViewIdentifier = (args[1] as int?); + assert(arg_webViewIdentifier != null, + 'Argument for dev.flutter.pigeon.CookieManagerHostApi.setAcceptThirdPartyCookies was null, expected non-null int.'); + final bool? arg_accept = (args[2] as bool?); + assert(arg_accept != null, + 'Argument for dev.flutter.pigeon.CookieManagerHostApi.setAcceptThirdPartyCookies was null, expected non-null bool.'); + api.setAcceptThirdPartyCookies( + arg_identifier!, arg_webViewIdentifier!, arg_accept!); + return []; + }); + } + } + } +} + class _TestWebViewHostApiCodec extends StandardMessageCodec { const _TestWebViewHostApiCodec(); @override