[webview_flutter] Add setCookie to CookieManager and support initial cookies in creation params. (#4561)

This PR adds a function for setting cookies to the CookieManager, as well support for setting initial cookies through the `CreationParams` object.

Fixes flutter/flutter#27597
diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md
index a9e133d..1df7e53 100644
--- a/packages/webview_flutter/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.7.0
+
+* Adds `setCookie` to CookieManager.
+* CreationParams now supports setting `initialCookies`.
+
 ## 2.6.0
 
 * Adds support for the `loadRequest` method.
diff --git a/packages/webview_flutter/webview_flutter/example/lib/main.dart b/packages/webview_flutter/webview_flutter/example/lib/main.dart
index 02e6ef8..65786de 100644
--- a/packages/webview_flutter/webview_flutter/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter/example/lib/main.dart
@@ -181,6 +181,7 @@
   loadLocalFile,
   loadHtmlString,
   transparentBackground,
+  setCookie,
 }
 
 class SampleMenu extends StatelessWidget {
@@ -232,6 +233,9 @@
               case MenuOptions.transparentBackground:
                 _onTransparentBackground(controller.data!, context);
                 break;
+              case MenuOptions.setCookie:
+                _onSetCookie(controller.data!, context);
+                break;
             }
           },
           itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
@@ -281,6 +285,10 @@
               value: MenuOptions.transparentBackground,
               child: Text('Transparent background example'),
             ),
+            const PopupMenuItem<MenuOptions>(
+              value: MenuOptions.setCookie,
+              child: Text('Set cookie'),
+            ),
           ],
         );
       },
@@ -357,6 +365,15 @@
     await controller.loadUrl('data:text/html;base64,$contentBase64');
   }
 
+  Future<void> _onSetCookie(
+      WebViewController controller, BuildContext context) async {
+    await CookieManager().setCookie(
+      const WebViewCookie(
+          name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'),
+    );
+    await controller.loadUrl('https://httpbin.org/anything');
+  }
+
   Future<void> _onDoPostRequest(
       WebViewController controller, BuildContext context) async {
     final WebViewRequest request = WebViewRequest(
diff --git a/packages/webview_flutter/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/webview_flutter/lib/platform_interface.dart
index ab1cbb1..48f7434 100644
--- a/packages/webview_flutter/webview_flutter/lib/platform_interface.dart
+++ b/packages/webview_flutter/webview_flutter/lib/platform_interface.dart
@@ -22,5 +22,6 @@
         WebSettings,
         WebResourceError,
         WebResourceErrorType,
+        WebViewCookie,
         WebViewRequest,
         WebViewRequestMethod;
diff --git a/packages/webview_flutter/webview_flutter/lib/src/webview.dart b/packages/webview_flutter/webview_flutter/lib/src/webview.dart
index d76f7b3..8fe4f41 100644
--- a/packages/webview_flutter/webview_flutter/lib/src/webview.dart
+++ b/packages/webview_flutter/webview_flutter/lib/src/webview.dart
@@ -3,12 +3,15 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:io';
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/widgets.dart';
 import 'package:webview_flutter_android/webview_android.dart';
+import 'package:webview_flutter_android/webview_android_cookie_manager.dart';
+import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
 import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
 
 import '../platform_interface.dart';
@@ -79,6 +82,7 @@
     Key? key,
     this.onWebViewCreated,
     this.initialUrl,
+    this.initialCookies = const <WebViewCookie>[],
     this.javascriptMode = JavascriptMode.disabled,
     this.javascriptChannels,
     this.navigationDelegate,
@@ -150,6 +154,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;
 
@@ -365,6 +372,7 @@
     userAgent: widget.userAgent,
     autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy,
     backgroundColor: widget.backgroundColor,
+    cookies: widget.initialCookies,
   );
 }
 
@@ -779,16 +787,32 @@
     return _instance ??= CookieManager._();
   }
 
-  CookieManager._();
+  CookieManager._() {
+    if (WebViewCookieManagerPlatform.instance == null) {
+      if (Platform.isAndroid) {
+        WebViewCookieManagerPlatform.instance = WebViewAndroidCookieManager();
+      } else if (Platform.isIOS) {
+        WebViewCookieManagerPlatform.instance = WKWebViewCookieManager();
+      } else {
+        throw AssertionError(
+            'This platform is currently unsupported by webview_flutter.');
+      }
+    }
+  }
 
   static CookieManager? _instance;
 
   /// Clears all cookies for all [WebView] instances.
   ///
-  /// This is a no op on iOS version smaller than 9.
-  ///
   /// Returns true if cookies were present before clearing, else false.
-  Future<bool> clearCookies() => WebView.platform.clearCookies();
+  Future<bool> clearCookies() =>
+      WebViewCookieManagerPlatform.instance!.clearCookies();
+
+  /// Sets a cookie for all [WebView] instances.
+  ///
+  /// This is a no op on iOS versions below 11.
+  Future<void> setCookie(WebViewCookie cookie) =>
+      WebViewCookieManagerPlatform.instance!.setCookie(cookie);
 }
 
 // Throws an ArgumentError if `url` is not a valid URL string.
diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml
index 1bc6bab..cd6c618 100644
--- a/packages/webview_flutter/webview_flutter/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin that provides a WebView widget on Android and iOS.
 repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.6.0
+version: 2.7.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
@@ -19,9 +19,9 @@
 dependencies:
   flutter:
     sdk: flutter
-  webview_flutter_android: ^2.7.0
+  webview_flutter_android: ^2.8.0
   webview_flutter_platform_interface: ^1.8.0
-  webview_flutter_wkwebview: ^2.5.0
+  webview_flutter_wkwebview: ^2.6.0
 
 dev_dependencies:
   build_runner: ^2.1.5
diff --git a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart
index d422402..40baef7 100644
--- a/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart
@@ -23,10 +23,12 @@
 
   late MockWebViewPlatform mockWebViewPlatform;
   late MockWebViewPlatformController mockWebViewPlatformController;
+  late MockWebViewCookieManagerPlatform mockWebViewCookieManagerPlatform;
 
   setUp(() {
     mockWebViewPlatformController = MockWebViewPlatformController();
     mockWebViewPlatform = MockWebViewPlatform();
+    mockWebViewCookieManagerPlatform = MockWebViewCookieManagerPlatform();
     when(mockWebViewPlatform.build(
       context: anyNamed('context'),
       creationParams: anyNamed('creationParams'),
@@ -46,6 +48,11 @@
     });
 
     WebView.platform = mockWebViewPlatform;
+    WebViewCookieManagerPlatform.instance = mockWebViewCookieManagerPlatform;
+  });
+
+  tearDown(() {
+    mockWebViewCookieManagerPlatform.reset();
   });
 
   testWidgets('Create WebView', (WidgetTester tester) async {
@@ -499,9 +506,6 @@
   });
 
   testWidgets('Cookies can be cleared once', (WidgetTester tester) async {
-    when(mockWebViewPlatform.clearCookies())
-        .thenAnswer((_) => Future<bool>.value(true));
-
     await tester.pumpWidget(
       const WebView(
         initialUrl: 'https://flutter.io',
@@ -512,6 +516,21 @@
     expect(hasCookies, true);
   });
 
+  testWidgets('Cookies can be set', (WidgetTester tester) async {
+    const WebViewCookie cookie =
+        WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev');
+
+    await tester.pumpWidget(
+      const WebView(
+        initialUrl: 'https://flutter.io',
+      ),
+    );
+    final CookieManager cookieManager = CookieManager();
+    await cookieManager.setCookie(cookie);
+    expect(mockWebViewCookieManagerPlatform.setCookieCalls,
+        <WebViewCookie>[cookie]);
+  });
+
   testWidgets('Initial JavaScript channels', (WidgetTester tester) async {
     await tester.pumpWidget(
       WebView(
@@ -1308,3 +1327,19 @@
             .matches(creationParams.javascriptChannelNames, matchState);
   }
 }
+
+class MockWebViewCookieManagerPlatform extends WebViewCookieManagerPlatform {
+  List<WebViewCookie> setCookieCalls = <WebViewCookie>[];
+
+  @override
+  Future<bool> clearCookies() async => true;
+
+  @override
+  Future<void> setCookie(WebViewCookie cookie) async {
+    setCookieCalls.add(cookie);
+  }
+
+  void reset() {
+    setCookieCalls = <WebViewCookie>[];
+  }
+}