-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Allow clearing cookies for FlutterWebView #1149
Changes from 17 commits
02f74bb
e44c7eb
3cb4073
0499934
1652b45
656a442
ffa8007
fe27c42
fe02c64
e3afcc4
ed66d5c
b8922ee
5257258
ac23c4a
11dadf9
9609895
85b9f7e
53ae5ed
7a20956
02da0fb
2dd3756
be3851f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package io.flutter.plugins.webviewflutter; | ||
|
|
||
| import android.os.Build; | ||
| import android.os.Build.VERSION_CODES; | ||
| import android.webkit.CookieManager; | ||
| import android.webkit.ValueCallback; | ||
| import io.flutter.plugin.common.BinaryMessenger; | ||
| import io.flutter.plugin.common.MethodCall; | ||
| import io.flutter.plugin.common.MethodChannel; | ||
| import io.flutter.plugin.common.MethodChannel.MethodCallHandler; | ||
| import io.flutter.plugin.common.MethodChannel.Result; | ||
|
|
||
| class FlutterCookieManager implements MethodCallHandler { | ||
|
|
||
| private FlutterCookieManager() { | ||
| // Do not instantiate. | ||
| // This class should only be used in context of a BinaryMessenger. | ||
| // Use FlutterCookieManager#registerWith instead. | ||
| } | ||
|
|
||
| static void registerWith(BinaryMessenger messenger) { | ||
| MethodChannel methodChannel = new MethodChannel(messenger, "plugins.flutter.io/cookie_manager"); | ||
| FlutterCookieManager cookieManager = new FlutterCookieManager(); | ||
| methodChannel.setMethodCallHandler(cookieManager); | ||
| } | ||
|
|
||
| @Override | ||
| public void onMethodCall(MethodCall methodCall, Result result) { | ||
| switch (methodCall.method) { | ||
| case "clearCookies": | ||
| clearCookies(result); | ||
| break; | ||
| default: | ||
| result.notImplemented(); | ||
| } | ||
| } | ||
|
|
||
| private static void clearCookies(final Result result) { | ||
| CookieManager cookieManager = CookieManager.getInstance(); | ||
| final boolean hasCookies = cookieManager.hasCookies(); | ||
| if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { | ||
| cookieManager.removeAllCookies( | ||
| new ValueCallback<Boolean>() { | ||
| @Override | ||
| public void onReceiveValue(Boolean value) { | ||
| result.success(hasCookies); | ||
| } | ||
| }); | ||
| } else { | ||
| cookieManager.removeAllCookie(); | ||
| result.success(hasCookies); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -75,11 +75,14 @@ class WebViewExample extends StatelessWidget { | |
| enum MenuOptions { | ||
| showUserAgent, | ||
| toast, | ||
| clearCookies, | ||
| } | ||
|
|
||
| class SampleMenu extends StatelessWidget { | ||
| SampleMenu(this.controller); | ||
|
|
||
| final Future<WebViewController> controller; | ||
| final CookieManager cookieManager = CookieManager(); | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
|
|
@@ -100,6 +103,9 @@ class SampleMenu extends StatelessWidget { | |
| ), | ||
| ); | ||
| break; | ||
| case MenuOptions.clearCookies: | ||
| _onClearCookies(controller.data, context); | ||
| break; | ||
| } | ||
| }, | ||
| itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[ | ||
|
|
@@ -112,6 +118,10 @@ class SampleMenu extends StatelessWidget { | |
| value: MenuOptions.toast, | ||
| child: Text('Make a toast'), | ||
| ), | ||
| const PopupMenuItem<MenuOptions>( | ||
| value: MenuOptions.clearCookies, | ||
| child: Text('Clear cookies'), | ||
| ), | ||
| ], | ||
| ); | ||
| }, | ||
|
|
@@ -125,6 +135,42 @@ class SampleMenu extends StatelessWidget { | |
| controller.evaluateJavascript( | ||
| 'Toaster.postMessage("User Agent: " + navigator.userAgent);'); | ||
| } | ||
|
|
||
| void _onClearCookies( | ||
| WebViewController controller, BuildContext context) async { | ||
| final String cookies = | ||
| await controller.evaluateJavascript('document.cookie'); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about adding a menu entry that just shows a toast with the cookies?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. |
||
| final bool hadCookies = await cookieManager.clearCookies(); | ||
| String message = 'There were cookies. Now, they are gone!'; | ||
| if (!hadCookies) { | ||
| message = 'There are no cookies.'; | ||
| } | ||
| Scaffold.of(context).showSnackBar(SnackBar( | ||
| content: Column( | ||
| mainAxisAlignment: MainAxisAlignment.end, | ||
| mainAxisSize: MainAxisSize.min, | ||
| children: <Widget>[ | ||
| Text(message), | ||
| const Text('Cookies:'), | ||
| _getCookieList(cookies), | ||
| ], | ||
| ), | ||
| )); | ||
| } | ||
|
|
||
| Widget _getCookieList(String cookies) { | ||
| if (cookies == null || cookies == '""') { | ||
| return Container(); | ||
| } | ||
| final List<String> cookieList = cookies.split(';'); | ||
| final Iterable<Text> cookieWidgets = | ||
| cookieList.map((String cookie) => Text(cookie)); | ||
| return Column( | ||
| mainAxisAlignment: MainAxisAlignment.end, | ||
| mainAxisSize: MainAxisSize.min, | ||
| children: cookieWidgets.toList(), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| class NavigationControls extends StatelessWidget { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| #import <Flutter/Flutter.h> | ||
| #import <WebKit/WebKit.h> | ||
|
|
||
| NS_ASSUME_NONNULL_BEGIN | ||
|
|
||
| @interface FLTCookieManager : NSObject <FlutterPlugin> | ||
|
|
||
| @end | ||
|
|
||
| NS_ASSUME_NONNULL_END |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| #import "FLTCookieManager.h" | ||
|
|
||
| @implementation FLTCookieManager { | ||
| } | ||
|
|
||
| + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar { | ||
| FLTCookieManager *instance = [[FLTCookieManager alloc] init]; | ||
|
|
||
| FlutterMethodChannel *channel = | ||
| [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/cookie_manager" | ||
| binaryMessenger:[registrar messenger]]; | ||
| [registrar addMethodCallDelegate:instance channel:channel]; | ||
| } | ||
|
|
||
| - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { | ||
| if ([[call method] isEqualToString:@"clearCookies"]) { | ||
| [self clearCookies:result]; | ||
| } else { | ||
| result(FlutterMethodNotImplemented); | ||
| } | ||
| } | ||
|
|
||
| - (void)clearCookies:(FlutterResult)result { | ||
| NSOperatingSystemVersion ios9 = (NSOperatingSystemVersion){9, 0}; | ||
| if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:ios9]) { | ||
|
||
| [self clearCookiesIos9AndLater:result]; | ||
| } else { | ||
| // support for IOS-8 tracked in https://github.com/flutter/flutter/issues/27624. | ||
| NSLog(@"Clearing cookies is not supported for Flutter WebViews prior to iOS9."); | ||
|
||
| } | ||
| } | ||
|
|
||
| - (void)clearCookiesIos9AndLater:(FlutterResult)result { | ||
| NSSet *websiteDataTypes = [NSSet setWithArray:@[ WKWebsiteDataTypeCookies ]]; | ||
| WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore]; | ||
|
|
||
| void (^deleteAndNotify)(NSArray<WKWebsiteDataRecord *> *) = | ||
| ^(NSArray<WKWebsiteDataRecord *> *cookies) { | ||
| BOOL hasCookies = cookies.count > 0; | ||
| [dataStore removeDataOfTypes:websiteDataTypes | ||
| forDataRecords:cookies | ||
| completionHandler:^{ | ||
| result(@(hasCookies)); | ||
| }]; | ||
| }; | ||
|
|
||
| [dataStore fetchDataRecordsOfTypes:websiteDataTypes completionHandler:deleteAndNotify]; | ||
| } | ||
|
|
||
| @end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -469,6 +469,32 @@ class WebViewController { | |
| } | ||
| } | ||
|
|
||
| /// Manages cookies pertaining to all [WebView]s. | ||
| class CookieManager { | ||
| factory CookieManager() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs a Dartdoc, it should mention that it returns or created the singleton instance.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: not sure if '--' is common in Flutter dartdocs (of if it is grammatically correct), I'd just say: |
||
| if (_instance == null) { | ||
| final MethodChannel methodChannel = | ||
|
||
| const MethodChannel('plugins.flutter.io/cookie_manager'); | ||
| _instance = CookieManager.private(methodChannel); | ||
| } | ||
| return _instance; | ||
| } | ||
|
|
||
| CookieManager.private(this._channel); | ||
|
||
|
|
||
| static CookieManager _instance; | ||
| final MethodChannel _channel; | ||
|
|
||
| /// Clears all cookies. | ||
| /// | ||
| /// This is supported for >= IOS 9 and Android api level >= 16. | ||
|
||
| /// returns true if cookies were present before clearing, else false. | ||
|
||
| Future<bool> clearCookies() => _channel | ||
| // ignore: strong_mode_implicit_dynamic_method | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add the same comment we have everywhere(minus the typo 😄 ) so it's easy to find all of these at once (and we need a TODO here anyway)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. keep it TODO(amirh), most likely someone will end up searching for all these |
||
| .invokeMethod('clearCookies') | ||
| .then<bool>((dynamic result) => result); | ||
| } | ||
|
|
||
| // Throws an ArgumentError if `url` is not a valid URL string. | ||
| void _validateUrlString(String url) { | ||
| try { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
explain why