[webview_flutter] Add iOS implementations for new cookie manager, to allow setting cookies directly and on webview creation. (#4556)
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
index 22f45f2..c1d2712 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.6.0
+
+* Implements new cookie manager for setting cookies and providing initial cookies.
+
## 2.5.0
* Adds an option to set the background color of the webview.
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart
index 8806495..1e4adb9 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart
@@ -1161,16 +1161,7 @@
/// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests.
Future<String> _getUserAgent(WebViewController controller) async {
- return _runJavascriptReturningResult(controller, 'navigator.userAgent;');
-}
-
-Future<String> _runJavascriptReturningResult(
- WebViewController controller, String js) async {
- if (defaultTargetPlatform == TargetPlatform.iOS) {
- return await controller.runJavascriptReturningResult(js);
- }
- return jsonDecode(await controller.runJavascriptReturningResult(js))
- as String;
+ return await controller.runJavascriptReturningResult('navigator.userAgent;');
}
class ResizableWebView extends StatefulWidget {
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj
index e292b1b..b681c47 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 50;
+ objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
@@ -16,8 +16,9 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
- AE8C124DC8CA68E4D9B30EAB /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */; };
+ D7587C3652F6906210B3AE88 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */; };
DAF0E91266956134538CC667 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 572FFC2B2BA326B420B22679 /* libPods-Runner.a */; };
+ E43693B527512C0F00382F85 /* FLTCookieManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */; };
F7151F77266057800028CB91 /* FLTWebViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F76266057800028CB91 /* FLTWebViewUITests.m */; };
/* End PBXBuildFile section */
@@ -54,10 +55,11 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+ 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
+ 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
- 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
572FFC2B2BA326B420B22679 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
- 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTWKNavigationDelegateTests.m; sourceTree = "<group>"; };
68BDCAE923C3F7CB00D9C032 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
68BDCAED23C3F7CB00D9C032 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -74,7 +76,7 @@
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B89AA31A64040E4A2F1E0CAF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
- C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
+ E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCookieManagerTests.m; sourceTree = "<group>"; };
F7151F74266057800028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
F7151F76266057800028CB91 /* FLTWebViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTWebViewUITests.m; sourceTree = "<group>"; };
F7151F78266057800028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -86,7 +88,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- AE8C124DC8CA68E4D9B30EAB /* libPods-RunnerTests.a in Frameworks */,
+ D7587C3652F6906210B3AE88 /* libPods-RunnerTests.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -112,7 +114,7 @@
isa = PBXGroup;
children = (
572FFC2B2BA326B420B22679 /* libPods-Runner.a */,
- 528CB85D53C983D2C5DAFDC5 /* libPods-RunnerTests.a */,
+ 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -123,6 +125,7 @@
686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */,
68BDCAF523C3F97800D9C032 /* FLTWebViewTests.m */,
68BDCAED23C3F7CB00D9C032 /* Info.plist */,
+ E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */,
);
path = RunnerTests;
sourceTree = "<group>";
@@ -190,8 +193,8 @@
children = (
F7A1921261392D1CBDAEC2E8 /* Pods-Runner.debug.xcconfig */,
B89AA31A64040E4A2F1E0CAF /* Pods-Runner.release.xcconfig */,
- C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */,
- 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */,
+ 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */,
+ 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
@@ -212,7 +215,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 68BDCAF223C3F7CB00D9C032 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
- 0067CEC0658A36CBFF8074E7 /* [CP] Check Pods Manifest.lock */,
+ AA38EF430495C2FB50F0F114 /* [CP] Check Pods Manifest.lock */,
68BDCAE523C3F7CB00D9C032 /* Sources */,
68BDCAE623C3F7CB00D9C032 /* Frameworks */,
68BDCAE723C3F7CB00D9C032 /* Resources */,
@@ -277,13 +280,16 @@
ORGANIZATIONNAME = "The Flutter Authors";
TargetAttributes = {
68BDCAE823C3F7CB00D9C032 = {
+ DevelopmentTeam = 7624MWN53C;
ProvisioningStyle = Automatic;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
+ DevelopmentTeam = 7624MWN53C;
};
F7151F73266057800028CB91 = {
CreatedOnToolsVersion = 12.5;
+ DevelopmentTeam = 7624MWN53C;
ProvisioningStyle = Automatic;
TestTargetID = 97C146ED1CF9000F007C117D;
};
@@ -338,28 +344,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 0067CEC0658A36CBFF8074E7 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -410,6 +394,28 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
};
+ AA38EF430495C2FB50F0F114 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -419,6 +425,7 @@
files = (
334734012669319100DCC49E /* FLTWebViewTests.m in Sources */,
334734022669319400DCC49E /* FLTWKNavigationDelegateTests.m in Sources */,
+ E43693B527512C0F00382F85 /* FLTCookieManagerTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -477,7 +484,7 @@
/* Begin XCBuildConfiguration section */
68BDCAF023C3F7CB00D9C032 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = C370F140C3A19241FD8C5E64 /* Pods-RunnerTests.debug.xcconfig */;
+ baseConfigurationReference = 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -491,7 +498,7 @@
};
68BDCAF123C3F7CB00D9C032 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 5C776D27D0DDA247ED5EA72B /* Pods-RunnerTests.release.xcconfig */;
+ baseConfigurationReference = 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m
new file mode 100644
index 0000000..837c0d8
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTCookieManagerTests.m
@@ -0,0 +1,127 @@
+// 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 Flutter;
+@import XCTest;
+@import webview_flutter_wkwebview;
+@import webview_flutter_wkwebview.Test;
+
+// OCMock library doesn't generate a valid modulemap.
+#import <OCMock/OCMock.h>
+
+@interface FLTCookieManagerTests : XCTestCase
+
+@end
+
+@implementation FLTCookieManagerTests
+
+- (void)setUp {
+ [super setUp];
+}
+
+- (void)testSetCookieForResultSetsCookieAndReturnsResultOnIOS11 {
+ if (@available(iOS 11.0, *)) {
+ // Setup
+ XCTestExpectation *resultExpectation = [self
+ expectationWithDescription:@"Should return success result when setting cookie completes."];
+ [FLTCookieManager.instance setHttpCookieStore:OCMClassMock(WKHTTPCookieStore.class)];
+ NSDictionary *arguments = @{
+ @"name" : @"foo",
+ @"value" : @"bar",
+ @"domain" : @"flutter.dev",
+ @"path" : @"/",
+ };
+ NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{
+ NSHTTPCookieName : arguments[@"name"],
+ NSHTTPCookieValue : arguments[@"value"],
+ NSHTTPCookieDomain : arguments[@"domain"],
+ NSHTTPCookiePath : arguments[@"path"],
+ }];
+ [OCMStub([FLTCookieManager.instance.httpCookieStore setCookie:[OCMArg isEqual:cookie]
+ completionHandler:[OCMArg any]])
+ andDo:^(NSInvocation *invocation) {
+ void (^setCookieCompletionHandler)(void);
+ [invocation getArgument:&setCookieCompletionHandler atIndex:3];
+ setCookieCompletionHandler();
+ }];
+ // Run
+ [[FLTCookieManager instance]
+ setCookieForResult:^(id _Nullable result) {
+ XCTAssertNil(result);
+ [resultExpectation fulfill];
+ }
+ arguments:arguments];
+ // Verify
+ [self waitForExpectationsWithTimeout:30.0 handler:nil];
+ }
+}
+
+- (void)testSetCookieForDataSetsCookieOnIOS11 {
+ if (@available(iOS 11.0, *)) {
+ // Setup
+ WKHTTPCookieStore *mockHttpCookieStore = OCMClassMock(WKHTTPCookieStore.class);
+ [FLTCookieManager.instance setHttpCookieStore:mockHttpCookieStore];
+ NSDictionary *cookieData = @{
+ @"name" : @"foo",
+ @"value" : @"bar",
+ @"domain" : @"flutter.dev",
+ @"path" : @"/",
+ };
+ // Run
+ [[FLTCookieManager instance] setCookieForData:cookieData];
+ // Verify
+ NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{
+ NSHTTPCookieName : cookieData[@"name"],
+ NSHTTPCookieValue : cookieData[@"value"],
+ NSHTTPCookieDomain : cookieData[@"domain"],
+ NSHTTPCookiePath : cookieData[@"path"],
+ }];
+ OCMVerify([mockHttpCookieStore setCookie:[OCMArg isEqual:cookie]
+ completionHandler:[OCMArg any]]);
+ }
+}
+
+- (void)testSetCookiesForDataSetsCookiesOnIOS11 {
+ if (@available(iOS 11.0, *)) {
+ // Setup
+ WKHTTPCookieStore *mockHttpCookieStore = OCMClassMock(WKHTTPCookieStore.class);
+ [FLTCookieManager.instance setHttpCookieStore:mockHttpCookieStore];
+ NSArray<NSDictionary *> *cookieDatas = @[
+ @{
+ @"name" : @"foo1",
+ @"value" : @"bar1",
+ @"domain" : @"flutter.dev",
+ @"path" : @"/",
+ },
+ @{
+ @"name" : @"foo2",
+ @"value" : @"bar2",
+ @"domain" : @"flutter2.dev",
+ @"path" : @"/2",
+ }
+ ];
+ // Run
+ [[FLTCookieManager instance] setCookiesForData:cookieDatas];
+ // Verify
+ NSHTTPCookie *cookie1 = [NSHTTPCookie cookieWithProperties:@{
+ NSHTTPCookieName : cookieDatas[0][@"name"],
+ NSHTTPCookieValue : cookieDatas[0][@"value"],
+ NSHTTPCookieDomain : cookieDatas[0][@"domain"],
+ NSHTTPCookiePath : cookieDatas[0][@"path"],
+ }];
+
+ OCMVerify([mockHttpCookieStore setCookie:[OCMArg isEqual:cookie1]
+ completionHandler:[OCMArg any]]);
+ NSHTTPCookie *cookie2 = [NSHTTPCookie cookieWithProperties:@{
+ NSHTTPCookieName : cookieDatas[1][@"name"],
+ NSHTTPCookieValue : cookieDatas[1][@"value"],
+ NSHTTPCookieDomain : cookieDatas[1][@"domain"],
+ NSHTTPCookiePath : cookieDatas[1][@"path"],
+ }];
+ OCMVerify([mockHttpCookieStore setCookie:[OCMArg isEqual:cookie2]
+ completionHandler:[OCMArg any]]);
+ }
+}
+
+@end
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m
index a819a9b..d39a9f2 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m
@@ -5,6 +5,7 @@
@import Flutter;
@import XCTest;
@import webview_flutter_wkwebview;
+@import webview_flutter_wkwebview.Test;
// OCMock library doesn't generate a valid modulemap.
#import <OCMock/OCMock.h>
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m
index a3c314a..6c7c6bf 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m
@@ -16,6 +16,8 @@
@property(strong, nonatomic) NSObject<FlutterBinaryMessenger> *mockBinaryMessenger;
+@property(strong, nonatomic) FLTCookieManager *mockCookieManager;
+
@end
@implementation FLTWebViewTests
@@ -23,6 +25,7 @@
- (void)setUp {
[super setUp];
self.mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
+ self.mockCookieManager = OCMClassMock(FLTCookieManager.class);
}
- (void)testCanInitFLTWebViewController {
@@ -35,8 +38,8 @@
}
- (void)testCanInitFLTWebViewFactory {
- FLTWebViewFactory *factory =
- [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger];
+ FLTWebViewFactory *factory = [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger
+ cookieManager:self.mockCookieManager];
XCTAssertNotNil(factory);
}
@@ -648,7 +651,7 @@
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
-- (void)testOnLoadRequestReturnsErroResultForInvalidRequest {
+- (void)testOnLoadRequestReturnsErrorResultForInvalidRequest {
// Setup
FLTWebViewController *controller =
[[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400)
@@ -697,4 +700,21 @@
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
+- (void)testCreateWithFrameShouldSetCookiesOnIOS11 {
+ if (@available(iOS 11, *)) {
+ // Setup
+ FLTWebViewFactory *factory =
+ [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger
+ cookieManager:self.mockCookieManager];
+ NSArray<NSDictionary *> *cookies =
+ @[ @{@"name" : @"foo", @"value" : @"bar", @"domain" : @"flutter.dev", @"path" : @"/"} ];
+ // Run
+ [factory createWithFrame:CGRectMake(0, 0, 300, 400)
+ viewIdentifier:1
+ arguments:@{@"cookies" : cookies}];
+ // Verify
+ OCMVerify([_mockCookieManager setCookiesForData:cookies]);
+ }
+}
+
@end
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
index 41297bf..ce7548a 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
@@ -89,6 +89,11 @@
Completer<WebViewController>();
@override
+ void initState() {
+ super.initState();
+ }
+
+ @override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF4CAF50),
@@ -104,7 +109,7 @@
// to allow calling Scaffold.of(context) so we can show a snackbar.
body: Builder(builder: (BuildContext context) {
return WebView(
- initialUrl: 'https://flutter.dev',
+ initialUrl: 'https://flutter.dev/',
onWebViewCreated: (WebViewController controller) {
_controller.complete(controller);
},
@@ -169,6 +174,7 @@
loadLocalFile,
loadHtmlString,
doPostRequest,
+ setCookie,
transparentBackground,
}
@@ -217,6 +223,9 @@
case _MenuOptions.doPostRequest:
_onDoPostRequest(controller.data!, context);
break;
+ case _MenuOptions.setCookie:
+ _onSetCookie(controller.data!, context);
+ break;
case _MenuOptions.transparentBackground:
_onTransparentBackground(controller.data!, context);
break;
@@ -265,6 +274,10 @@
child: Text('Post Request'),
),
const PopupMenuItem<_MenuOptions>(
+ value: _MenuOptions.setCookie,
+ child: Text('Set Cookie'),
+ ),
+ const PopupMenuItem<_MenuOptions>(
key: ValueKey<String>('ShowTransparentBackgroundExample'),
value: _MenuOptions.transparentBackground,
child: Text('Transparent background example'),
@@ -365,6 +378,15 @@
await controller.loadRequest(request);
}
+ Future<void> _onSetCookie(
+ WebViewController controller, BuildContext context) async {
+ await WebViewCookieManager.instance.setCookie(
+ const WebViewCookie(
+ name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'),
+ );
+ await controller.loadUrl('https://httpbin.org/anything');
+ }
+
Widget _getCookieList(String cookies) {
if (cookies == null || cookies == '""') {
return Container();
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart
index 69fa2bd..1efd031 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
@@ -54,6 +55,7 @@
Key? key,
this.onWebViewCreated,
this.initialUrl,
+ this.initialCookies = const <WebViewCookie>[],
this.javascriptMode = JavascriptMode.disabled,
this.javascriptChannels,
this.navigationDelegate,
@@ -95,6 +97,9 @@
/// The initial URL to load.
final String? initialUrl;
+ /// The initial cookies to set.
+ final List<WebViewCookie> initialCookies;
+
/// Whether JavaScript execution is enabled.
final JavascriptMode javascriptMode;
@@ -285,6 +290,7 @@
_javascriptChannelRegistry.channels.keys.toSet(),
autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy,
userAgent: widget.userAgent,
+ cookies: widget.initialCookies,
backgroundColor: widget.backgroundColor,
),
javascriptChannelRegistry: _javascriptChannelRegistry,
@@ -660,3 +666,21 @@
}
}
}
+
+/// App-facing cookie manager that exposes the correct platform implementation.
+class WebViewCookieManager extends WebViewCookieManagerPlatform {
+ WebViewCookieManager._();
+
+ /// Returns an instance of the cookie manager for the current platform.
+ static WebViewCookieManagerPlatform get instance {
+ if (WebViewCookieManagerPlatform.instance == null) {
+ if (Platform.isIOS) {
+ WebViewCookieManagerPlatform.instance = WKWebViewCookieManager();
+ } else {
+ throw AssertionError(
+ 'This platform is currently unsupported for webview_flutter_wkwebview.');
+ }
+ }
+ return WebViewCookieManagerPlatform.instance!;
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h
index 8fe3318..dc5b8f5 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.h
@@ -9,6 +9,12 @@
@interface FLTCookieManager : NSObject <FlutterPlugin>
++ (FLTCookieManager*)instance;
+
+- (void)setCookiesForData:(NSArray<NSDictionary*>*)cookies;
+
+- (void)setCookieForData:(NSDictionary*)cookie;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m
index f4783ff..39976d1 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager.m
@@ -3,22 +3,32 @@
// found in the LICENSE file.
#import "FLTCookieManager.h"
+#import "FLTCookieManager_Test.h"
@implementation FLTCookieManager {
+ WKHTTPCookieStore *_httpCookieStore API_AVAILABLE(macos(10.13), ios(11.0));
+}
+
++ (FLTCookieManager *)instance {
+ static FLTCookieManager *instance = nil;
+ if (instance == nil) {
+ instance = [[FLTCookieManager alloc] init];
+ }
+ return instance;
}
+ (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];
+ [registrar addMethodCallDelegate:[self instance] channel:channel];
}
- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
if ([[call method] isEqualToString:@"clearCookies"]) {
[self clearCookies:result];
+ } else if ([[call method] isEqualToString:@"setCookie"]) {
+ [self setCookieForResult:result arguments:[call arguments]];
} else {
result(FlutterMethodNotImplemented);
}
@@ -46,4 +56,50 @@
}
}
+- (void)setCookiesForData:(NSArray<NSDictionary *> *)cookies {
+ for (id cookie in cookies) {
+ [self setCookieForData:cookie];
+ }
+}
+
+- (void)setCookieForData:(NSDictionary *)cookieData {
+ if (@available(iOS 11.0, *)) {
+ if (!_httpCookieStore) {
+ _httpCookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore];
+ }
+ NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{
+ NSHTTPCookieName : cookieData[@"name"],
+ NSHTTPCookieValue : cookieData[@"value"],
+ NSHTTPCookieDomain : cookieData[@"domain"],
+ NSHTTPCookiePath : cookieData[@"path"],
+ }];
+ [_httpCookieStore setCookie:cookie
+ completionHandler:^{
+ }];
+ } else {
+ NSLog(@"Setting cookies is not supported for Flutter WebViews prior to iOS 11.");
+ }
+}
+
+- (void)setCookieForResult:(FlutterResult)result arguments:(NSDictionary *)arguments {
+ if (@available(iOS 11.0, *)) {
+ if (!_httpCookieStore) {
+ _httpCookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore];
+ }
+ NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{
+ NSHTTPCookieName : arguments[@"name"],
+ NSHTTPCookieValue : arguments[@"value"],
+ NSHTTPCookieDomain : arguments[@"domain"],
+ NSHTTPCookiePath : arguments[@"path"],
+ }];
+ [_httpCookieStore setCookie:cookie
+ completionHandler:^{
+ result(nil);
+ }];
+ } else {
+ NSLog(@"Setting cookies is not supported for Flutter WebViews prior to iOS 11.");
+ result(nil);
+ }
+}
+
@end
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h
new file mode 100644
index 0000000..fecec49
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTCookieManager_Test.h
@@ -0,0 +1,20 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This header is available in the Test module. Import via "@import webview_flutter_wkwebview.Test;"
+
+#import <webview_flutter_wkwebview/FLTCookieManager.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FLTCookieManager ()
+
+@property(nonatomic, strong)
+ WKHTTPCookieStore *httpCookieStore API_AVAILABLE(macos(10.13), ios(11.0));
+
+- (void)setCookieForResult:(FlutterResult)result arguments:(NSDictionary *)arguments;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m
index 9f01416..0d04d6c 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m
@@ -9,10 +9,11 @@
@implementation FLTWebViewFlutterPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
- FLTWebViewFactory* webviewFactory =
- [[FLTWebViewFactory alloc] initWithMessenger:registrar.messenger];
- [registrar registerViewFactory:webviewFactory withId:@"plugins.flutter.io/webview"];
[FLTCookieManager registerWithRegistrar:registrar];
+ FLTWebViewFactory* webviewFactory =
+ [[FLTWebViewFactory alloc] initWithMessenger:registrar.messenger
+ cookieManager:[FLTCookieManager instance]];
+ [registrar registerViewFactory:webviewFactory withId:@"plugins.flutter.io/webview"];
}
@end
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h
index 6d8e463..145a772 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.h
@@ -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 <FLTCookieManager.h>
#import <Flutter/Flutter.h>
#import <WebKit/WebKit.h>
@@ -31,7 +32,8 @@
@end
@interface FLTWebViewFactory : NSObject <FlutterPlatformViewFactory>
-- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
+- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
+ cookieManager:(FLTCookieManager*)cookieManager;
@end
NS_ASSUME_NONNULL_END
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
index 25a93a6..ea45a8b 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m
@@ -10,12 +10,15 @@
@implementation FLTWebViewFactory {
NSObject<FlutterBinaryMessenger>* _messenger;
+ FLTCookieManager* _cookieManager;
}
-- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
+- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
+ cookieManager:(FLTCookieManager*)cookieManager {
self = [super init];
if (self) {
_messenger = messenger;
+ _cookieManager = cookieManager;
}
return self;
}
@@ -27,6 +30,10 @@
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args {
+ if (@available(iOS 11.0, *)) {
+ [_cookieManager setCookiesForData:args[@"cookies"]];
+ }
+
FLTWebViewController* webviewController = [[FLTWebViewController alloc] initWithFrame:frame
viewIdentifier:viewId
arguments:args
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap
index fa51430..0965075 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap
@@ -6,5 +6,6 @@
explicit module Test {
header "FlutterWebView_Test.h"
+ header "FLTCookieManager_Test.h"
}
}
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_cupertino.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_cupertino.dart
index 05b79d0..cd00d9a 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_cupertino.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_cupertino.dart
@@ -45,5 +45,11 @@
}
@override
- Future<bool> clearCookies() => MethodChannelWebViewPlatform.clearCookies();
+ Future<bool> clearCookies() {
+ if (WebViewCookieManagerPlatform.instance == null) {
+ throw Exception(
+ 'Could not clear cookies as no implementation for WebViewCookieManagerPlatform has been registered.');
+ }
+ return WebViewCookieManagerPlatform.instance!.clearCookies();
+ }
}
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/wkwebview_cookie_manager.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/wkwebview_cookie_manager.dart
new file mode 100644
index 0000000..d460ce0
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/wkwebview_cookie_manager.dart
@@ -0,0 +1,30 @@
+// 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';
+
+/// Handles all cookie operations for the current platform.
+class WKWebViewCookieManager extends WebViewCookieManagerPlatform {
+ @override
+ Future<bool> clearCookies() => MethodChannelWebViewPlatform.clearCookies();
+
+ @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 MethodChannelWebViewPlatform.setCookie(cookie);
+ }
+
+ bool _isValidPath(String path) {
+ // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1
+ for (final int char in path.codeUnits) {
+ if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart
index bbec415..f647ab3 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart
@@ -3,3 +3,4 @@
// found in the LICENSE file.
export 'src/webview_cupertino.dart';
+export 'src/wkwebview_cookie_manager.dart';
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
index 7024733..fc274b4 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
@@ -2,7 +2,7 @@
description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control.
repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_wkwebview
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.5.0
+version: 2.6.0
environment:
sdk: ">=2.14.0 <3.0.0"
@@ -18,7 +18,7 @@
dependencies:
flutter:
sdk: flutter
- webview_flutter_platform_interface: ^1.7.0
+ webview_flutter_platform_interface: ^1.8.0
dev_dependencies:
flutter_driver:
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/wkwebview_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/wkwebview_cookie_manager_test.dart
new file mode 100644
index 0000000..54b1921
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/wkwebview_cookie_manager_test.dart
@@ -0,0 +1,65 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
+import 'package:webview_flutter_wkwebview/src/wkwebview_cookie_manager.dart';
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+ const MethodChannel cookieChannel =
+ MethodChannel('plugins.flutter.io/cookie_manager');
+ final List<MethodCall> log = <MethodCall>[];
+
+ cookieChannel.setMockMethodCallHandler((MethodCall methodCall) async {
+ log.add(methodCall);
+
+ if (methodCall.method == 'clearCookies') {
+ return true;
+ }
+
+ // Return null explicitly instead of relying on the implicit null
+ // returned by the method channel if no return statement is specified.
+ return null;
+ });
+
+ tearDown(() {
+ log.clear();
+ });
+
+ test('clearCookies should call `clearCookies` on the method channel',
+ () async {
+ await WKWebViewCookieManager().clearCookies();
+ expect(
+ log,
+ <Matcher>[
+ isMethodCall(
+ 'clearCookies',
+ arguments: null,
+ ),
+ ],
+ );
+ });
+
+ test('setCookie should call `setCookie` on the method channel', () async {
+ await WKWebViewCookieManager().setCookie(
+ const WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev'),
+ );
+ expect(
+ log,
+ <Matcher>[
+ isMethodCall(
+ 'setCookie',
+ arguments: <String, String>{
+ 'name': 'foo',
+ 'value': 'bar',
+ 'domain': 'flutter.dev',
+ 'path': '/',
+ },
+ ),
+ ],
+ );
+ });
+}