diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index 22f45f27c4d7..c1d27121f9ee 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 8806495e629c..1e4adb9f7de0 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 @@ String _webviewBool(bool value) { /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. Future _getUserAgent(WebViewController controller) async { - return _runJavascriptReturningResult(controller, 'navigator.userAgent;'); -} - -Future _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 e292b1bd52fa..b681c4704ddd 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 = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 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 = ""; }; 686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTWKNavigationDelegateTests.m; sourceTree = ""; }; 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 = ""; }; @@ -74,7 +76,7 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 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 = ""; }; - 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 = ""; }; + E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCookieManagerTests.m; sourceTree = ""; }; 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 = ""; }; F7151F78266057800028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -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 = ""; @@ -123,6 +125,7 @@ 686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */, 68BDCAF523C3F97800D9C032 /* FLTWebViewTests.m */, 68BDCAED23C3F7CB00D9C032 /* Info.plist */, + E43693B427512C0F00382F85 /* FLTCookieManagerTests.m */, ); path = RunnerTests; sourceTree = ""; @@ -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 = ""; @@ -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,7 +344,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0067CEC0658A36CBFF8074E7 /* [CP] Check Pods Manifest.lock */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; + }; + 6F536C27DD48B395A30EBB65 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -353,28 +373,28 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Runner-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 */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Thin Binary"; + name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; - 6F536C27DD48B395A30EBB65 /* [CP] Check Pods Manifest.lock */ = { + AA38EF430495C2FB50F0F114 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -389,27 +409,13 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "$(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; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; - }; /* 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 000000000000..837c0d892b64 --- /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 + +@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 *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 a819a9b53d60..d39a9f203d70 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 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 a3c314a79d2a..6c7c6bffbc3f 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 @@ @interface FLTWebViewTests : XCTestCase @property(strong, nonatomic) NSObject *mockBinaryMessenger; +@property(strong, nonatomic) FLTCookieManager *mockCookieManager; + @end @implementation FLTWebViewTests @@ -23,6 +25,7 @@ @implementation FLTWebViewTests - (void)setUp { [super setUp]; self.mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + self.mockCookieManager = OCMClassMock(FLTCookieManager.class); } - (void)testCanInitFLTWebViewController { @@ -35,8 +38,8 @@ - (void)testCanInitFLTWebViewController { } - (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 @@ - (void)testOnLoadUrlLoadsRequestWithSuccessResult { [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 @@ - (void)testOnLoadRequestLoadsRequestWithSuccessResult { [self waitForExpectationsWithTimeout:30.0 handler:nil]; } +- (void)testCreateWithFrameShouldSetCookiesOnIOS11 { + if (@available(iOS 11, *)) { + // Setup + FLTWebViewFactory *factory = + [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger + cookieManager:self.mockCookieManager]; + NSArray *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 41297bfd94fe..ce7548a87cab 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart @@ -88,6 +88,11 @@ class _WebViewExampleState extends State<_WebViewExample> { final Completer _controller = Completer(); + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -104,7 +109,7 @@ class _WebViewExampleState extends State<_WebViewExample> { // 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 @@ enum _MenuOptions { loadLocalFile, loadHtmlString, doPostRequest, + setCookie, transparentBackground, } @@ -217,6 +223,9 @@ class _SampleMenu extends StatelessWidget { case _MenuOptions.doPostRequest: _onDoPostRequest(controller.data!, context); break; + case _MenuOptions.setCookie: + _onSetCookie(controller.data!, context); + break; case _MenuOptions.transparentBackground: _onTransparentBackground(controller.data!, context); break; @@ -264,6 +273,10 @@ class _SampleMenu extends StatelessWidget { value: _MenuOptions.doPostRequest, child: Text('Post Request'), ), + const PopupMenuItem<_MenuOptions>( + value: _MenuOptions.setCookie, + child: Text('Set Cookie'), + ), const PopupMenuItem<_MenuOptions>( key: ValueKey('ShowTransparentBackgroundExample'), value: _MenuOptions.transparentBackground, @@ -365,6 +378,15 @@ class _SampleMenu extends StatelessWidget { await controller.loadRequest(request); } + Future _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 69fa2bdb79e3..1efd0315072b 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 @@ class WebView extends StatefulWidget { Key? key, this.onWebViewCreated, this.initialUrl, + this.initialCookies = const [], this.javascriptMode = JavascriptMode.disabled, this.javascriptChannels, this.navigationDelegate, @@ -95,6 +97,9 @@ class WebView extends StatefulWidget { /// The initial URL to load. final String? initialUrl; + /// The initial cookies to set. + final List initialCookies; + /// Whether JavaScript execution is enabled. final JavascriptMode javascriptMode; @@ -285,6 +290,7 @@ class _WebViewState extends State { _javascriptChannelRegistry.channels.keys.toSet(), autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, userAgent: widget.userAgent, + cookies: widget.initialCookies, backgroundColor: widget.backgroundColor, ), javascriptChannelRegistry: _javascriptChannelRegistry, @@ -660,3 +666,21 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { } } } + +/// 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 8fe331875250..dc5b8f589ade 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 @@ NS_ASSUME_NONNULL_BEGIN @interface FLTCookieManager : NSObject ++ (FLTCookieManager*)instance; + +- (void)setCookiesForData:(NSArray*)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 f4783ffb4123..39976d11153e 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)); } -+ (void)registerWithRegistrar:(NSObject *)registrar { - FLTCookieManager *instance = [[FLTCookieManager alloc] init]; ++ (FLTCookieManager *)instance { + static FLTCookieManager *instance = nil; + if (instance == nil) { + instance = [[FLTCookieManager alloc] init]; + } + return instance; +} ++ (void)registerWithRegistrar:(NSObject *)registrar { 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)clearCookies:(FlutterResult)result { } } +- (void)setCookiesForData:(NSArray *)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 000000000000..fecec4932570 --- /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 + +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 9f01416acc6a..0d04d6c3a9ee 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*)registrar { + [FLTCookieManager registerWithRegistrar:registrar]; FLTWebViewFactory* webviewFactory = - [[FLTWebViewFactory alloc] initWithMessenger:registrar.messenger]; + [[FLTWebViewFactory alloc] initWithMessenger:registrar.messenger + cookieManager:[FLTCookieManager instance]]; [registrar registerViewFactory:webviewFactory withId:@"plugins.flutter.io/webview"]; - [FLTCookieManager registerWithRegistrar:registrar]; } @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 6d8e463c7b13..145a772c5015 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 #import #import @@ -31,7 +32,8 @@ NS_ASSUME_NONNULL_BEGIN @end @interface FLTWebViewFactory : NSObject -- (instancetype)initWithMessenger:(NSObject*)messenger; +- (instancetype)initWithMessenger:(NSObject*)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 25a93a67addc..ea45a8b9b534 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* _messenger; + FLTCookieManager* _cookieManager; } -- (instancetype)initWithMessenger:(NSObject*)messenger { +- (instancetype)initWithMessenger:(NSObject*)messenger + cookieManager:(FLTCookieManager*)cookieManager { self = [super init]; if (self) { _messenger = messenger; + _cookieManager = cookieManager; } return self; } @@ -27,6 +30,10 @@ - (instancetype)initWithMessenger:(NSObject*)messenger { - (NSObject*)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 fa5143060381..096507557688 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 @@ framework module webview_flutter_wkwebview { 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 05b79d0a72e4..cd00d9a7d14b 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 @@ class CupertinoWebView implements WebViewPlatform { } @override - Future clearCookies() => MethodChannelWebViewPlatform.clearCookies(); + Future 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 000000000000..d460ce02ab68 --- /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 clearCookies() => MethodChannelWebViewPlatform.clearCookies(); + + @override + Future 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 bbec415dccd0..f647ab38a41b 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 702473382793..fc274b453995 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview 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 @@ flutter: 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 000000000000..54b1921583b9 --- /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 log = []; + + 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, + [ + 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, + [ + isMethodCall( + 'setCookie', + arguments: { + 'name': 'foo', + 'value': 'bar', + 'domain': 'flutter.dev', + 'path': '/', + }, + ), + ], + ); + }); +}