Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 60cd47c

Browse files
authored
[webview_flutter] Add Android implementations for new cookie manager, to allow setting cookies directly and on webview creation. (#4557)
1 parent c691451 commit 60cd47c

20 files changed

+660
-132
lines changed

packages/webview_flutter/webview_flutter_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.8.0
2+
3+
* Implements new cookie manager for setting cookies and providing initial cookies.
4+
15
## 2.7.0
26

37
* Adds support for the `loadRequest` method from the platform interface.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutter;
6+
7+
import android.os.Build;
8+
import android.webkit.CookieManager;
9+
10+
class CookieManagerHostApiImpl implements GeneratedAndroidWebView.CookieManagerHostApi {
11+
@Override
12+
public void clearCookies(GeneratedAndroidWebView.Result<Boolean> result) {
13+
CookieManager cookieManager = CookieManager.getInstance();
14+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
15+
cookieManager.removeAllCookies(result::success);
16+
} else {
17+
final boolean hasCookies = cookieManager.hasCookies();
18+
if (hasCookies) {
19+
cookieManager.removeAllCookie();
20+
}
21+
result.success(hasCookies);
22+
}
23+
}
24+
25+
@Override
26+
public void setCookie(String url, String value) {
27+
CookieManager.getInstance().setCookie(url, value);
28+
}
29+
}

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java

Lines changed: 0 additions & 56 deletions
This file was deleted.

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,94 @@ public interface Result<T> {
162162
void error(Throwable error);
163163
}
164164

165+
private static class CookieManagerHostApiCodec extends StandardMessageCodec {
166+
public static final CookieManagerHostApiCodec INSTANCE = new CookieManagerHostApiCodec();
167+
168+
private CookieManagerHostApiCodec() {}
169+
}
170+
171+
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
172+
public interface CookieManagerHostApi {
173+
void clearCookies(Result<Boolean> result);
174+
175+
void setCookie(String url, String value);
176+
177+
/** The codec used by CookieManagerHostApi. */
178+
static MessageCodec<Object> getCodec() {
179+
return CookieManagerHostApiCodec.INSTANCE;
180+
}
181+
182+
/**
183+
* Sets up an instance of `CookieManagerHostApi` to handle messages through the
184+
* `binaryMessenger`.
185+
*/
186+
static void setup(BinaryMessenger binaryMessenger, CookieManagerHostApi api) {
187+
{
188+
BasicMessageChannel<Object> channel =
189+
new BasicMessageChannel<>(
190+
binaryMessenger,
191+
"dev.flutter.pigeon.CookieManagerHostApi.clearCookies",
192+
getCodec());
193+
if (api != null) {
194+
channel.setMessageHandler(
195+
(message, reply) -> {
196+
Map<String, Object> wrapped = new HashMap<>();
197+
try {
198+
Result<Boolean> resultCallback =
199+
new Result<Boolean>() {
200+
public void success(Boolean result) {
201+
wrapped.put("result", result);
202+
reply.reply(wrapped);
203+
}
204+
205+
public void error(Throwable error) {
206+
wrapped.put("error", wrapError(error));
207+
reply.reply(wrapped);
208+
}
209+
};
210+
211+
api.clearCookies(resultCallback);
212+
} catch (Error | RuntimeException exception) {
213+
wrapped.put("error", wrapError(exception));
214+
reply.reply(wrapped);
215+
}
216+
});
217+
} else {
218+
channel.setMessageHandler(null);
219+
}
220+
}
221+
{
222+
BasicMessageChannel<Object> channel =
223+
new BasicMessageChannel<>(
224+
binaryMessenger, "dev.flutter.pigeon.CookieManagerHostApi.setCookie", getCodec());
225+
if (api != null) {
226+
channel.setMessageHandler(
227+
(message, reply) -> {
228+
Map<String, Object> wrapped = new HashMap<>();
229+
try {
230+
ArrayList<Object> args = (ArrayList<Object>) message;
231+
String urlArg = (String) args.get(0);
232+
if (urlArg == null) {
233+
throw new NullPointerException("urlArg unexpectedly null.");
234+
}
235+
String valueArg = (String) args.get(1);
236+
if (valueArg == null) {
237+
throw new NullPointerException("valueArg unexpectedly null.");
238+
}
239+
api.setCookie(urlArg, valueArg);
240+
wrapped.put("result", null);
241+
} catch (Error | RuntimeException exception) {
242+
wrapped.put("error", wrapError(exception));
243+
}
244+
reply.reply(wrapped);
245+
});
246+
} else {
247+
channel.setMessageHandler(null);
248+
}
249+
}
250+
}
251+
}
252+
165253
private static class WebViewHostApiCodec extends StandardMessageCodec {
166254
public static final WebViewHostApiCodec INSTANCE = new WebViewHostApiCodec();
167255

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
1414
import io.flutter.plugin.common.BinaryMessenger;
1515
import io.flutter.plugin.platform.PlatformViewRegistry;
16+
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.CookieManagerHostApi;
1617
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerHostApi;
1718
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.FlutterAssetManagerHostApi;
1819
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelHostApi;
@@ -30,7 +31,6 @@
3031
*/
3132
public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
3233
private FlutterPluginBinding pluginBinding;
33-
private FlutterCookieManager flutterCookieManager;
3434
private WebViewHostApiImpl webViewHostApi;
3535
private JavaScriptChannelHostApiImpl javaScriptChannelHostApi;
3636

@@ -65,7 +65,6 @@ public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registra
6565
registrar.view(),
6666
new FlutterAssetManager.RegistrarFlutterAssetManager(
6767
registrar.context().getAssets(), registrar));
68-
new FlutterCookieManager(registrar.messenger());
6968
}
7069

7170
private void setUp(
@@ -74,7 +73,6 @@ private void setUp(
7473
Context context,
7574
View containerView,
7675
FlutterAssetManager flutterAssetManager) {
77-
new FlutterCookieManager(binaryMessenger);
7876

7977
InstanceManager instanceManager = new InstanceManager();
8078

@@ -117,6 +115,7 @@ private void setUp(
117115
instanceManager, new WebSettingsHostApiImpl.WebSettingsCreator()));
118116
FlutterAssetManagerHostApi.setup(
119117
binaryMessenger, new FlutterAssetManagerHostApiImpl(flutterAssetManager));
118+
CookieManagerHostApi.setup(binaryMessenger, new CookieManagerHostApiImpl());
120119
}
121120

122121
@Override
@@ -132,14 +131,7 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
132131
}
133132

134133
@Override
135-
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
136-
if (flutterCookieManager == null) {
137-
return;
138-
}
139-
140-
flutterCookieManager.dispose();
141-
flutterCookieManager = null;
142-
}
134+
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
143135

144136
@Override
145137
public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutter;
6+
7+
import static org.mockito.ArgumentMatchers.any;
8+
import static org.mockito.Mockito.doAnswer;
9+
import static org.mockito.Mockito.mock;
10+
import static org.mockito.Mockito.mockStatic;
11+
import static org.mockito.Mockito.verify;
12+
import static org.mockito.Mockito.when;
13+
14+
import android.os.Build;
15+
import android.webkit.CookieManager;
16+
import android.webkit.ValueCallback;
17+
import io.flutter.plugins.webviewflutter.utils.TestUtils;
18+
import org.junit.After;
19+
import org.junit.Before;
20+
import org.junit.Test;
21+
import org.mockito.MockedStatic;
22+
23+
public class CookieManagerHostApiImplTest {
24+
25+
private CookieManager cookieManager;
26+
private MockedStatic<CookieManager> staticMockCookieManager;
27+
28+
@Before
29+
public void setup() {
30+
staticMockCookieManager = mockStatic(CookieManager.class);
31+
cookieManager = mock(CookieManager.class);
32+
when(CookieManager.getInstance()).thenReturn(cookieManager);
33+
when(cookieManager.hasCookies()).thenReturn(true);
34+
doAnswer(
35+
answer -> {
36+
((ValueCallback<Boolean>) answer.getArgument(0)).onReceiveValue(true);
37+
return null;
38+
})
39+
.when(cookieManager)
40+
.removeAllCookies(any());
41+
}
42+
43+
@After
44+
public void tearDown() {
45+
staticMockCookieManager.close();
46+
}
47+
48+
@Test
49+
public void setCookieShouldCallSetCookie() {
50+
// Setup
51+
CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl();
52+
// Run
53+
impl.setCookie("flutter.dev", "foo=bar; path=/");
54+
// Verify
55+
verify(cookieManager).setCookie("flutter.dev", "foo=bar; path=/");
56+
}
57+
58+
@Test
59+
public void clearCookiesShouldCallRemoveAllCookiesOnAndroidLAbove() {
60+
// Setup
61+
TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.LOLLIPOP);
62+
GeneratedAndroidWebView.Result<Boolean> result = mock(GeneratedAndroidWebView.Result.class);
63+
CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl();
64+
// Run
65+
impl.clearCookies(result);
66+
// Verify
67+
verify(cookieManager).removeAllCookies(any());
68+
verify(result).success(true);
69+
}
70+
71+
@Test
72+
public void clearCookiesShouldCallRemoveAllCookieBelowAndroidL() {
73+
// Setup
74+
TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.KITKAT_WATCH);
75+
GeneratedAndroidWebView.Result<Boolean> result = mock(GeneratedAndroidWebView.Result.class);
76+
CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl();
77+
// Run
78+
impl.clearCookies(result);
79+
// Verify
80+
verify(cookieManager).removeAllCookie();
81+
verify(result).success(true);
82+
}
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutter.utils;
6+
7+
import java.lang.reflect.Field;
8+
import java.lang.reflect.Modifier;
9+
import org.junit.Assert;
10+
11+
public class TestUtils {
12+
public static <T> void setFinalStatic(Class<T> classToModify, String fieldName, Object newValue) {
13+
try {
14+
Field field = classToModify.getField(fieldName);
15+
field.setAccessible(true);
16+
17+
Field modifiersField = Field.class.getDeclaredField("modifiers");
18+
modifiersField.setAccessible(true);
19+
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
20+
21+
field.set(null, newValue);
22+
} catch (Exception e) {
23+
Assert.fail("Unable to mock static field: " + fieldName);
24+
}
25+
}
26+
27+
public static <T> void setPrivateField(T instance, String fieldName, Object newValue) {
28+
try {
29+
Field field = instance.getClass().getDeclaredField(fieldName);
30+
field.setAccessible(true);
31+
field.set(instance, newValue);
32+
} catch (Exception e) {
33+
Assert.fail("Unable to mock private field: " + fieldName);
34+
}
35+
}
36+
37+
public static <T> Object getPrivateField(T instance, String fieldName) {
38+
try {
39+
Field field = instance.getClass().getDeclaredField(fieldName);
40+
field.setAccessible(true);
41+
return field.get(instance);
42+
} catch (Exception e) {
43+
Assert.fail("Unable to mock private field: " + fieldName);
44+
return null;
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)