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

[webview_flutter_wkwebview] Add support for cookie manager #5203

Merged
merged 6 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ class WKWebsiteDataStoreHostApi {
}
}

Future<void> removeDataOfTypes(
Future<bool> removeDataOfTypes(
int arg_instanceId,
List<WKWebsiteDataTypesEnumData?> arg_dataTypes,
double arg_secondsModifiedSinceEpoch) async {
Expand All @@ -308,8 +308,13 @@ class WKWebsiteDataStoreHostApi {
message: error['message'] as String?,
details: error['details'],
);
} else if (replyMap['result'] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return;
return (replyMap['result'] as bool?)!;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,85 @@ enum NSKeyValueChangeKey {

/// Indicates the value of this key is the value before the attribute was changed.
///
/// https://developer.apple.com/documentation/foundation/nskeyvaluechangeoldkey?language=objc.
/// See https://developer.apple.com/documentation/foundation/nskeyvaluechangeoldkey?language=objc.
oldValue,
}

/// The supported keys in a cookie attributes dictionary.
///
/// Wraps [NSHTTPCookiePropertyKey](https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey).
enum NSHttpCookiePropertyKey {
/// A String object containing the comment for the cookie.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookiecomment.
comment,

/// A String object containing the comment URL for the cookie.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookiecommenturl.
commentUrl,

/// A String object stating whether the cookie should be discarded at the end of the session.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookiediscard.
discard,

/// A String object specifying the expiration date for the cookie.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookiedomain.
domain,

/// A String object specifying the expiration date for the cookie.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookieexpires.
expires,

/// A String object containing an integer value stating how long in seconds the cookie should be kept, at most.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookiemaximumage.
maximumAge,

/// A String object containing the name of the cookie (required).
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookiename.
name,

/// A String object containing the URL that set this cookie.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookieoriginurl.
originUrl,

/// A String object containing the path for the cookie.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookiepath.
path,

/// A String object containing comma-separated integer values specifying the ports for the cookie.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookieport.
port,

/// A String indicating the same-site policy for the cookie.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookiesamesitepolicy.
sameSitePolicy,

/// A String object indicating that the cookie should be transmitted only over secure channels.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookiesecure.
secure,

/// A String object containing the value of the cookie.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookievalue.
value,

/// A String object that specifies the version of the cookie.
///
/// See https://developer.apple.com/documentation/foundation/nshttpcookieversion.
version,
}

/// A URL load request that is independent of protocol or URL scheme.
///
/// Wraps [NSUrlRequest](https://developer.apple.com/documentation/foundation/nsurlrequest?language=objc).
Expand Down Expand Up @@ -142,6 +217,18 @@ class NSError {
final String localizedDescription;
}

/// A representation of an HTTP cookie.
///
/// Wraps [NSHTTPCookie](https://developer.apple.com/documentation/foundation/nshttpcookie).
@immutable
class NSHttpCookie {
/// Initializes an HTTP cookie object using the provided properties.
const NSHttpCookie.withProperties(this.properties);

/// Properties of the new cookie object.
final Map<NSHttpCookiePropertyKey, Object> properties;
}

/// The root class of most Objective-C class hierarchies.
class NSObject {
/// Constructs an [NSObject].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ class WKErrorCode {
static const int javaScriptResultTypeIsUnsupported = 5;
}

/// A record of the data that a particular website stores persistently.
///
/// Wraps [WKWebsiteDataRecord](https://developer.apple.com/documentation/webkit/wkwebsitedatarecord?language=objc).
@immutable
class WKWebsiteDataRecord {
/// Constructs a [WKWebsiteDataRecord].
const WKWebsiteDataRecord({required this.displayName});

/// Identifying information that you display to users.
final String displayName;
}

/// An object that contains information about an action that causes navigation to occur.
///
/// Wraps [WKNavigationAction](https://developer.apple.com/documentation/webkit/wknavigationaction?language=objc).
Expand Down Expand Up @@ -230,26 +242,51 @@ class WKPreferences {
///
/// Wraps [WKWebsiteDataStore](https://developer.apple.com/documentation/webkit/wkwebsitedatastore?language=objc).
class WKWebsiteDataStore {
/// Constructs a [WKWebsiteDataStore] that is owned by [configuration].
@visibleForTesting
WKWebsiteDataStore.fromWebViewConfiguration(
WKWebViewConfiguration configuration, {
WKWebsiteDataStore._({
BinaryMessenger? binaryMessenger,
InstanceManager? instanceManager,
}) : _websiteDataStoreApi = WKWebsiteDataStoreHostApiImpl(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
) {
_websiteDataStoreApi.createFromWebViewConfigurationForInstances(
this,
);

factory WKWebsiteDataStore._defaultDataStore() {
throw UnimplementedError();
}

/// Constructs a [WKWebsiteDataStore] that is owned by [configuration].
@visibleForTesting
factory WKWebsiteDataStore.fromWebViewConfiguration(
WKWebViewConfiguration configuration, {
BinaryMessenger? binaryMessenger,
InstanceManager? instanceManager,
}) {
final WKWebsiteDataStore websiteDataStore = WKWebsiteDataStore._(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
);
websiteDataStore._websiteDataStoreApi
.createFromWebViewConfigurationForInstances(
websiteDataStore,
configuration,
);
return websiteDataStore;
}

/// Default data store that stores data persistently to disk.
static final WKWebsiteDataStore defaultDataStore =
WKWebsiteDataStore._defaultDataStore();

final WKWebsiteDataStoreHostApiImpl _websiteDataStoreApi;

/// Manages the HTTP cookies associated with a particular web view.
late final WKHttpCookieStore httpCookieStore =
WKHttpCookieStore.fromWebsiteDataStore(this);

/// Removes website data that changed after the specified date.
Future<void> removeDataOfTypes(
///
/// Returns whether any data was removed.
Future<bool> removeDataOfTypes(
Copy link
Contributor Author

@bparrishMines bparrishMines Apr 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Objective-C method doesn't return a bool, but I decided to just add this feature for simplicity of adding CookieManager.setCookie.

Set<WKWebsiteDataTypes> dataTypes,
DateTime since,
) {
Expand All @@ -261,6 +298,26 @@ class WKWebsiteDataStore {
}
}

/// An object that manages the HTTP cookies associated with a particular web view.
///
/// Wraps [WKHTTPCookieStore](https://developer.apple.com/documentation/webkit/wkhttpcookiestore?language=objc).
class WKHttpCookieStore {
/// Constructs a [WKHttpCookieStore] that is owned by [dataStore].
@visibleForTesting
WKHttpCookieStore.fromWebsiteDataStore(
// TODO(bparrishMines): Remove ignore on implementation.
// ignore: avoid_unused_constructor_parameters
WKWebsiteDataStore dataStore,
) {
throw UnimplementedError();
}

/// Adds a cookie to the cookie store.
Future<void> setCookie(NSHttpCookie cookie) {
throw UnimplementedError();
}
}

/// An interface for receiving messages from JavaScript code running in a webpage.
///
/// Wraps [WKScriptMessageHandler](https://developer.apple.com/documentation/webkit/wkscriptmessagehandler?language=objc)
Expand Down Expand Up @@ -471,7 +528,6 @@ class WKWebViewConfiguration {
Future<void> setMediaTypesRequiringUserActionForPlayback(
Set<WKAudiovisualMediaType> types,
) {
assert(types.isNotEmpty);
return _webViewConfigurationApi
.setMediaTypesRequiringUserActionForPlaybackForInstances(
this,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class WKWebsiteDataStoreHostApiImpl extends WKWebsiteDataStoreHostApi {
}

/// Calls [removeDataOfTypes] with the ids of the provided object instances.
Future<void> removeDataOfTypesForInstances(
Future<bool> removeDataOfTypesForInstances(
WKWebsiteDataStore instance,
Set<WKWebsiteDataTypes> dataTypes, {
required double secondsModifiedSinceEpoch,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart';
import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart';

/// Handles all cookie operations for the WebView platform.
class WebKitCookieManager extends WebViewCookieManagerPlatform {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stuartmorgan @mvanbeusekom

In the WebKit library, the clearCookies and setCookies methods can be called for a single WKWebView by changing the WKWebViewConfiguration.websiteDatastore. For Android there is only a single global CookieManager instance. To accommodate platforms like WebKit, I think the new interface should move both of these methods to WebViewController or there should be an instanced WebViewController.cookieManager.

/// Constructs a [WebKitCookieManager].
WebKitCookieManager({WKWebsiteDataStore? websiteDataStore})
: websiteDataStore =
websiteDataStore ?? WKWebsiteDataStore.defaultDataStore;

/// Manages stored data for [WKWebView]s.
final WKWebsiteDataStore websiteDataStore;

@override
Future<bool> clearCookies() async {
return websiteDataStore.removeDataOfTypes(
<WKWebsiteDataTypes>{WKWebsiteDataTypes.cookies},
DateTime.fromMillisecondsSinceEpoch(0),
);
}

@override
Future<void> setCookie(WebViewCookie cookie) {
if (!_isValidPath(cookie.path)) {
throw ArgumentError(
'The path property for the provided cookie was not given a legal value.');
}

return websiteDataStore.httpCookieStore.setCookie(
NSHttpCookie.withProperties(
<NSHttpCookiePropertyKey, Object>{
NSHttpCookiePropertyKey.name: cookie.name,
NSHttpCookiePropertyKey.value: cookie.value,
NSHttpCookiePropertyKey.domain: cookie.domain,
NSHttpCookiePropertyKey.path: cookie.path,
},
),
);
}

bool _isValidPath(String path) {
// Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1
return !path.codeUnits.any(
(int char) {
return (char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E);
},
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ abstract class WKWebsiteDataStoreHostApi {
);

@async
void removeDataOfTypes(
bool removeDataOfTypes(
int instanceId,
List<WKWebsiteDataTypesEnumData> dataTypes,
double secondsModifiedSinceEpoch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ abstract class TestWKWebsiteDataStoreHostApi {

void createFromWebViewConfiguration(
int instanceId, int configurationInstanceId);
Future<void> removeDataOfTypes(
Future<bool> removeDataOfTypes(
int instanceId,
List<WKWebsiteDataTypesEnumData?> dataTypes,
double secondsModifiedSinceEpoch);
Expand Down Expand Up @@ -96,9 +96,9 @@ abstract class TestWKWebsiteDataStoreHostApi {
final double? arg_secondsModifiedSinceEpoch = (args[2] as double?);
assert(arg_secondsModifiedSinceEpoch != null,
'Argument for dev.flutter.pigeon.WKWebsiteDataStoreHostApi.removeDataOfTypes was null, expected non-null double.');
await api.removeDataOfTypes(
final bool output = await api.removeDataOfTypes(
arg_instanceId!, arg_dataTypes!, arg_secondsModifiedSinceEpoch!);
return <Object?, Object?>{};
return <Object?, Object?>{'result': output};
});
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Mocks generated by Mockito 5.1.0 from annotations
// in webview_flutter_wkwebview/example/ios/.symlinks/plugins/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart.
// in webview_flutter_wkwebview/test/src/foundation/foundation_test.dart.
// Do not manually edit this file.

import 'package:mockito/mockito.dart' as _i1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,29 @@ void main() {
});

test('removeDataOfTypes', () {
websiteDataStore.removeDataOfTypes(
<WKWebsiteDataTypes>{WKWebsiteDataTypes.cookies},
DateTime.fromMillisecondsSinceEpoch(5000),
when(mockPlatformHostApi.removeDataOfTypes(
any,
any,
any,
)).thenAnswer((_) => Future<bool>.value(true));

expect(
websiteDataStore.removeDataOfTypes(
<WKWebsiteDataTypes>{WKWebsiteDataTypes.cookies},
DateTime.fromMillisecondsSinceEpoch(5000),
),
completion(true),
);

final WKWebsiteDataTypesEnumData typeData =
final List<WKWebsiteDataTypesEnumData> typeData =
verify(mockPlatformHostApi.removeDataOfTypes(
instanceManager.getInstanceId(websiteDataStore),
captureAny,
5.0,
)).captured.single.single as WKWebsiteDataTypesEnumData;
)).captured.single.cast<WKWebsiteDataTypesEnumData>()
as List<WKWebsiteDataTypesEnumData>;

expect(typeData.value, WKWebsiteDataTypesEnum.cookies);
expect(typeData.single.value, WKWebsiteDataTypesEnum.cookies);
});
});

Expand Down
Loading