[webview_flutter_android] Clear local storage when `clearCache` is called (#5086)

diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart
index 02ee197..70e8179 100644
--- a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart
@@ -1217,6 +1217,52 @@
     },
     skip: _skipDueToIssue86757,
   );
+
+  testWidgets(
+    'clearCache should clear local storage',
+    (WidgetTester tester) async {
+      final Completer<WebViewController> controllerCompleter =
+          Completer<WebViewController>();
+      final Completer<void> onPageFinished = Completer<void>();
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: WebView(
+            key: GlobalKey(),
+            initialUrl: primaryUrl,
+            javascriptMode: JavascriptMode.unrestricted,
+            onPageFinished: (_) => onPageFinished.complete(),
+            onWebViewCreated: (WebViewController controller) {
+              controllerCompleter.complete(controller);
+            },
+          ),
+        ),
+      );
+
+      final WebViewController controller = await controllerCompleter.future;
+      await onPageFinished.future;
+
+      await controller.runJavascript('localStorage.setItem("myCat", "Tom");');
+
+      expect(
+        controller.runJavascriptReturningResult(
+          'localStorage.getItem("myCat");',
+        ),
+        completion(_webviewString('Tom')),
+      );
+
+      await controller.clearCache();
+
+      expect(
+        controller.runJavascriptReturningResult(
+          'localStorage.getItem("myCat");',
+        ),
+        completion(_webviewNull()),
+      );
+    },
+    // TODO(bparrishMines): Unskip once https://github.com/flutter/plugins/pull/5086 lands and is published.
+    skip: Platform.isAndroid,
+  );
 }
 
 // JavaScript booleans evaluate to different string values on Android and iOS.
@@ -1228,6 +1274,24 @@
   return value ? 'true' : 'false';
 }
 
+// JavaScript `null` evaluate to different string values on Android and iOS.
+// This utility method returns the string boolean value of the current platform.
+String _webviewNull() {
+  if (defaultTargetPlatform == TargetPlatform.iOS) {
+    return '<null>';
+  }
+  return 'null';
+}
+
+// JavaScript String evaluate to different string values on Android and iOS.
+// This utility method returns the string boolean value of the current platform.
+String _webviewString(String value) {
+  if (defaultTargetPlatform == TargetPlatform.iOS) {
+    return value;
+  }
+  return '"$value"';
+}
+
 /// 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;');
diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index 12d20b0..ad81b0f 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,6 +1,8 @@
-## NEXT
+## 2.8.4
 
 * Fixes bug preventing `mockito` code generation for tests.
+* Fixes regression where local storage wasn't cleared when `WebViewController.clearCache` was
+  called.
 
 ## 2.8.3
 
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
index 15b78b7..afca5ee 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
@@ -2199,6 +2199,80 @@
     }
   }
 
+  private static class WebStorageHostApiCodec extends StandardMessageCodec {
+    public static final WebStorageHostApiCodec INSTANCE = new WebStorageHostApiCodec();
+
+    private WebStorageHostApiCodec() {}
+  }
+
+  /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
+  public interface WebStorageHostApi {
+    void create(Long instanceId);
+
+    void deleteAllData(Long instanceId);
+
+    /** The codec used by WebStorageHostApi. */
+    static MessageCodec<Object> getCodec() {
+      return WebStorageHostApiCodec.INSTANCE;
+    }
+
+    /**
+     * Sets up an instance of `WebStorageHostApi` to handle messages through the `binaryMessenger`.
+     */
+    static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) {
+      {
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger, "dev.flutter.pigeon.WebStorageHostApi.create", getCodec());
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                Map<String, Object> wrapped = new HashMap<>();
+                try {
+                  ArrayList<Object> args = (ArrayList<Object>) message;
+                  Number instanceIdArg = (Number) args.get(0);
+                  if (instanceIdArg == null) {
+                    throw new NullPointerException("instanceIdArg unexpectedly null.");
+                  }
+                  api.create(instanceIdArg.longValue());
+                  wrapped.put("result", null);
+                } catch (Error | RuntimeException exception) {
+                  wrapped.put("error", wrapError(exception));
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+      {
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger, "dev.flutter.pigeon.WebStorageHostApi.deleteAllData", getCodec());
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                Map<String, Object> wrapped = new HashMap<>();
+                try {
+                  ArrayList<Object> args = (ArrayList<Object>) message;
+                  Number instanceIdArg = (Number) args.get(0);
+                  if (instanceIdArg == null) {
+                    throw new NullPointerException("instanceIdArg unexpectedly null.");
+                  }
+                  api.deleteAllData(instanceIdArg.longValue());
+                  wrapped.put("result", null);
+                } catch (Error | RuntimeException exception) {
+                  wrapped.put("error", wrapError(exception));
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+    }
+  }
+
   private static Map<String, Object> wrapError(Throwable exception) {
     Map<String, Object> errorMap = new HashMap<>();
     errorMap.put("message", exception.toString());
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImpl.java
new file mode 100644
index 0000000..42e7603
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImpl.java
@@ -0,0 +1,53 @@
+// 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.
+
+package io.flutter.plugins.webviewflutter;
+
+import android.webkit.WebStorage;
+import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebStorageHostApi;
+
+/**
+ * Host api implementation for {@link WebStorage}.
+ *
+ * <p>Handles creating {@link WebStorage}s that intercommunicate with a paired Dart object.
+ */
+public class WebStorageHostApiImpl implements WebStorageHostApi {
+  private final InstanceManager instanceManager;
+  private final WebStorageCreator webStorageCreator;
+
+  /** Handles creating {@link WebStorage} for a {@link WebStorageHostApiImpl}. */
+  public static class WebStorageCreator {
+    /**
+     * Creates a {@link WebStorage}.
+     *
+     * @return the created {@link WebStorage}. Defaults to {@link WebStorage#getInstance}
+     */
+    public WebStorage createWebStorage() {
+      return WebStorage.getInstance();
+    }
+  }
+
+  /**
+   * Creates a host API that handles creating {@link WebStorage} and invoke its methods.
+   *
+   * @param instanceManager maintains instances stored to communicate with Dart objects
+   * @param webStorageCreator handles creating {@link WebStorage}s
+   */
+  public WebStorageHostApiImpl(
+      InstanceManager instanceManager, WebStorageCreator webStorageCreator) {
+    this.instanceManager = instanceManager;
+    this.webStorageCreator = webStorageCreator;
+  }
+
+  @Override
+  public void create(Long instanceId) {
+    instanceManager.addInstance(webStorageCreator.createWebStorage(), instanceId);
+  }
+
+  @Override
+  public void deleteAllData(Long instanceId) {
+    final WebStorage webStorage = (WebStorage) instanceManager.getInstance(instanceId);
+    webStorage.deleteAllData();
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
index 4ef622f..67202eb 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
@@ -19,6 +19,7 @@
 import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelHostApi;
 import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientHostApi;
 import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebSettingsHostApi;
+import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebStorageHostApi;
 import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewClientHostApi;
 import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewHostApi;
 
@@ -116,6 +117,9 @@
     FlutterAssetManagerHostApi.setup(
         binaryMessenger, new FlutterAssetManagerHostApiImpl(flutterAssetManager));
     CookieManagerHostApi.setup(binaryMessenger, new CookieManagerHostApiImpl());
+    WebStorageHostApi.setup(
+        binaryMessenger,
+        new WebStorageHostApiImpl(instanceManager, new WebStorageHostApiImpl.WebStorageCreator()));
   }
 
   @Override
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImplTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImplTest.java
new file mode 100644
index 0000000..e2845c2
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImplTest.java
@@ -0,0 +1,41 @@
+// 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.
+
+package io.flutter.plugins.webviewflutter;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.webkit.WebStorage;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class WebStorageHostApiImplTest {
+  @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+  @Mock public WebStorage mockWebStorage;
+
+  @Mock WebStorageHostApiImpl.WebStorageCreator mockWebStorageCreator;
+
+  InstanceManager testInstanceManager;
+  WebStorageHostApiImpl testHostApiImpl;
+
+  @Before
+  public void setUp() {
+    testInstanceManager = new InstanceManager();
+    when(mockWebStorageCreator.createWebStorage()).thenReturn(mockWebStorage);
+    testHostApiImpl = new WebStorageHostApiImpl(testInstanceManager, mockWebStorageCreator);
+    testHostApiImpl.create(0L);
+  }
+
+  @Test
+  public void deleteAllData() {
+    testHostApiImpl.deleteAllData(0L);
+    verify(mockWebStorage).deleteAllData();
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart
index 58f2f36..f1e9528 100644
--- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart
@@ -1377,6 +1377,50 @@
       );
     },
   );
+
+  testWidgets(
+    'clearCache should clear local storage',
+    (WidgetTester tester) async {
+      final Completer<WebViewController> controllerCompleter =
+          Completer<WebViewController>();
+      final Completer<void> onPageFinished = Completer<void>();
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: WebView(
+            key: GlobalKey(),
+            initialUrl: primaryUrl,
+            javascriptMode: JavascriptMode.unrestricted,
+            onPageFinished: (_) => onPageFinished.complete(),
+            onWebViewCreated: (WebViewController controller) {
+              controllerCompleter.complete(controller);
+            },
+          ),
+        ),
+      );
+
+      final WebViewController controller = await controllerCompleter.future;
+      await onPageFinished.future;
+
+      await controller.runJavascript('localStorage.setItem("myCat", "Tom");');
+
+      expect(
+        controller.runJavascriptReturningResult(
+          'localStorage.getItem("myCat");',
+        ),
+        completion('"Tom"'),
+      );
+
+      await controller.clearCache();
+
+      expect(
+        controller.runJavascriptReturningResult(
+          'localStorage.getItem("myCat");',
+        ),
+        completion('null'),
+      );
+    },
+  );
 }
 
 // JavaScript booleans evaluate to different string values on Android and iOS.
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
index 1098932..bd50640 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
@@ -852,3 +852,30 @@
   Future<String> getAssetFilePathByName(String name) =>
       api.getAssetFilePathByName(name);
 }
+
+/// Manages the JavaScript storage APIs provided by the [WebView].
+///
+/// Wraps [WebStorage](https://developer.android.com/reference/android/webkit/WebStorage).
+class WebStorage {
+  /// Constructs a [WebStorage].
+  ///
+  /// This constructor is only used for testing. An instance should be obtained
+  /// with [WebStorage.instance].
+  @visibleForTesting
+  WebStorage() {
+    AndroidWebViewFlutterApis.instance.ensureSetUp();
+    api.createFromInstance(this);
+  }
+
+  /// Pigeon Host Api implementation for [WebStorage].
+  @visibleForTesting
+  static WebStorageHostApiImpl api = WebStorageHostApiImpl();
+
+  /// The singleton instance of this class.
+  static WebStorage instance = WebStorage();
+
+  /// Clears all storage currently being used by the JavaScript storage APIs.
+  Future<void> deleteAllData() {
+    return api.deleteAllDataFromInstance(this);
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
index 20391c4..4a0965e 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
@@ -1860,3 +1860,69 @@
     }
   }
 }
+
+class _WebStorageHostApiCodec extends StandardMessageCodec {
+  const _WebStorageHostApiCodec();
+}
+
+class WebStorageHostApi {
+  /// Constructor for [WebStorageHostApi].  The [binaryMessenger] named argument is
+  /// available for dependency injection.  If it is left null, the default
+  /// BinaryMessenger will be used which routes to the host platform.
+  WebStorageHostApi({BinaryMessenger? binaryMessenger})
+      : _binaryMessenger = binaryMessenger;
+
+  final BinaryMessenger? _binaryMessenger;
+
+  static const MessageCodec<Object?> codec = _WebStorageHostApiCodec();
+
+  Future<void> create(int arg_instanceId) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.WebStorageHostApi.create', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(<Object>[arg_instanceId]) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return;
+    }
+  }
+
+  Future<void> deleteAllData(int arg_instanceId) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.WebStorageHostApi.deleteAllData', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(<Object>[arg_instanceId]) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return;
+    }
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
index ead60f6..9c980c8 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
@@ -787,3 +787,30 @@
     instance!.onProgressChanged(webViewInstance!, progress);
   }
 }
+
+/// Host api implementation for [WebStorage].
+class WebStorageHostApiImpl extends WebStorageHostApi {
+  /// Constructs a [WebStorageHostApiImpl].
+  WebStorageHostApiImpl({
+    BinaryMessenger? binaryMessenger,
+    InstanceManager? instanceManager,
+  }) : super(binaryMessenger: binaryMessenger) {
+    this.instanceManager = instanceManager ?? InstanceManager.instance;
+  }
+
+  /// Maintains instances stored to communicate with java objects.
+  late final InstanceManager instanceManager;
+
+  /// Helper method to convert instances ids to objects.
+  Future<void> createFromInstance(WebStorage instance) async {
+    final int? instanceId = instanceManager.tryAddInstance(instance);
+    if (instanceId != null) {
+      return create(instanceId);
+    }
+  }
+
+  /// Helper method to convert instances ids to objects.
+  Future<void> deleteAllDataFromInstance(WebStorage instance) {
+    return deleteAllData(instanceManager.getInstanceId(instance)!);
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
index 7200aaa..28d169c 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
@@ -23,6 +23,7 @@
     @visibleForTesting this.webViewProxy = const WebViewProxy(),
     @visibleForTesting
         this.flutterAssetManager = const android_webview.FlutterAssetManager(),
+    @visibleForTesting this.webStorage,
   });
 
   /// Initial parameters used to setup the WebView.
@@ -59,6 +60,9 @@
   final Widget Function(WebViewAndroidPlatformController controller)
       onBuildWidget;
 
+  /// Manages the JavaScript storage APIs.
+  final android_webview.WebStorage? webStorage;
+
   @override
   State<StatefulWidget> createState() => _WebViewAndroidWidgetState();
 }
@@ -76,6 +80,7 @@
       javascriptChannelRegistry: widget.javascriptChannelRegistry,
       webViewProxy: widget.webViewProxy,
       flutterAssetManager: widget.flutterAssetManager,
+      webStorage: widget.webStorage,
     );
   }
 
@@ -102,7 +107,9 @@
     @visibleForTesting this.webViewProxy = const WebViewProxy(),
     @visibleForTesting
         this.flutterAssetManager = const android_webview.FlutterAssetManager(),
-  })  : assert(creationParams.webSettings?.hasNavigationDelegate != null),
+    @visibleForTesting android_webview.WebStorage? webStorage,
+  })  : webStorage = webStorage ?? android_webview.WebStorage.instance,
+        assert(creationParams.webSettings?.hasNavigationDelegate != null),
         super(callbacksHandler) {
     webView = webViewProxy.createWebView(
       useHybridComposition: useHybridComposition,
@@ -160,6 +167,9 @@
   late final WebViewAndroidWebChromeClient webChromeClient =
       WebViewAndroidWebChromeClient();
 
+  /// Manages the JavaScript storage APIs.
+  final android_webview.WebStorage webStorage;
+
   /// Receive various notifications and requests for [android_webview.WebView].
   @visibleForTesting
   WebViewAndroidWebViewClient get webViewClient => _webViewClient;
@@ -254,7 +264,10 @@
   Future<void> reload() => webView.reload();
 
   @override
-  Future<void> clearCache() => webView.clearCache(true);
+  Future<void> clearCache() {
+    webView.clearCache(true);
+    return webStorage.deleteAllData();
+  }
 
   @override
   Future<void> updateSettings(WebSettings setting) async {
diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
index b298352..d3d18f6 100644
--- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
@@ -222,3 +222,10 @@
 
   void onProgressChanged(int instanceId, int webViewInstanceId, int progress);
 }
+
+@HostApi(dartHostTestHandler: 'TestWebStorageHostApi')
+abstract class WebStorageHostApi {
+  void create(int instanceId);
+
+  void deleteAllData(int instanceId);
+}
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index 35f0e2f..5c3c83a 100644
--- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin that provides a WebView widget on Android.
 repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.8.3
+version: 2.8.4
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart
index 7461263..4ee4b1d 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart
@@ -1157,3 +1157,55 @@
     }
   }
 }
+
+class _TestWebStorageHostApiCodec extends StandardMessageCodec {
+  const _TestWebStorageHostApiCodec();
+}
+
+abstract class TestWebStorageHostApi {
+  static const MessageCodec<Object?> codec = _TestWebStorageHostApiCodec();
+
+  void create(int instanceId);
+  void deleteAllData(int instanceId);
+  static void setup(TestWebStorageHostApi? api,
+      {BinaryMessenger? binaryMessenger}) {
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.WebStorageHostApi.create', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.WebStorageHostApi.create was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final int? arg_instanceId = (args[0] as int?);
+          assert(arg_instanceId != null,
+              'Argument for dev.flutter.pigeon.WebStorageHostApi.create was null, expected non-null int.');
+          api.create(arg_instanceId!);
+          return <Object?, Object?>{};
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.WebStorageHostApi.deleteAllData', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.WebStorageHostApi.deleteAllData was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final int? arg_instanceId = (args[0] as int?);
+          assert(arg_instanceId != null,
+              'Argument for dev.flutter.pigeon.WebStorageHostApi.deleteAllData was null, expected non-null int.');
+          api.deleteAllData(arg_instanceId!);
+          return <Object?, Object?>{};
+        });
+      }
+    }
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
index 91385ff..e2e6513 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
@@ -21,6 +21,7 @@
   TestJavaScriptChannelHostApi,
   TestWebChromeClientHostApi,
   TestWebSettingsHostApi,
+  TestWebStorageHostApi,
   TestWebViewClientHostApi,
   TestWebViewHostApi,
   TestAssetManagerHostApi,
@@ -677,4 +678,29 @@
       verify(CookieManager.api.clearCookies());
     });
   });
+
+  group('WebStorage', () {
+    late MockTestWebStorageHostApi mockPlatformHostApi;
+
+    late WebStorage webStorage;
+    late int webStorageInstanceId;
+
+    setUp(() {
+      mockPlatformHostApi = MockTestWebStorageHostApi();
+      TestWebStorageHostApi.setup(mockPlatformHostApi);
+
+      webStorage = WebStorage();
+      webStorageInstanceId =
+          WebStorage.api.instanceManager.getInstanceId(webStorage)!;
+    });
+
+    test('create', () {
+      verify(mockPlatformHostApi.create(webStorageInstanceId));
+    });
+
+    test('deleteAllData', () {
+      webStorage.deleteAllData();
+      verify(mockPlatformHostApi.deleteAllData(webStorageInstanceId));
+    });
+  });
 }
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
index d6023d7..e8859c9 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
@@ -201,6 +201,25 @@
       returnValueForMissingStub: null);
 }
 
+/// A class which mocks [TestWebStorageHostApi].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockTestWebStorageHostApi extends _i1.Mock
+    implements _i5.TestWebStorageHostApi {
+  MockTestWebStorageHostApi() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  void create(int? instanceId) =>
+      super.noSuchMethod(Invocation.method(#create, [instanceId]),
+          returnValueForMissingStub: null);
+  @override
+  void deleteAllData(int? instanceId) =>
+      super.noSuchMethod(Invocation.method(#deleteAllData, [instanceId]),
+          returnValueForMissingStub: null);
+}
+
 /// A class which mocks [TestWebViewClientHostApi].
 ///
 /// See the documentation for Mockito's code generation for more information.
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
index af10939..46d6a29 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
@@ -23,6 +23,7 @@
 @GenerateMocks(<Type>[
   android_webview.FlutterAssetManager,
   android_webview.WebSettings,
+  android_webview.WebStorage,
   android_webview.WebView,
   WebViewAndroidDownloadListener,
   WebViewAndroidJavaScriptChannel,
@@ -39,6 +40,7 @@
     late MockFlutterAssetManager mockFlutterAssetManager;
     late MockWebView mockWebView;
     late MockWebSettings mockWebSettings;
+    late MockWebStorage mockWebStorage;
     late MockWebViewProxy mockWebViewProxy;
 
     late MockWebViewPlatformCallbacksHandler mockCallbacksHandler;
@@ -54,6 +56,7 @@
       mockFlutterAssetManager = MockFlutterAssetManager();
       mockWebView = MockWebView();
       mockWebSettings = MockWebSettings();
+      mockWebStorage = MockWebStorage();
       when(mockWebView.settings).thenReturn(mockWebSettings);
 
       mockWebViewProxy = MockWebViewProxy();
@@ -86,6 +89,7 @@
         javascriptChannelRegistry: mockJavascriptChannelRegistry,
         webViewProxy: mockWebViewProxy,
         flutterAssetManager: mockFlutterAssetManager,
+        webStorage: mockWebStorage,
         onBuildWidget: (WebViewAndroidPlatformController controller) {
           testController = controller;
           return Container();
@@ -590,6 +594,7 @@
 
         await testController.clearCache();
         verify(mockWebView.clearCache(true));
+        verify(mockWebStorage.deleteAllData());
       });
 
       testWidgets('evaluateJavascript', (WidgetTester tester) async {
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
index f4d9abb..3385e79 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
@@ -121,6 +121,21 @@
           returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
 }
 
+/// A class which mocks [WebStorage].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockWebStorage extends _i1.Mock implements _i2.WebStorage {
+  MockWebStorage() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  _i4.Future<void> deleteAllData() =>
+      (super.noSuchMethod(Invocation.method(#deleteAllData, []),
+          returnValue: Future<void>.value(),
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+}
+
 /// A class which mocks [WebView].
 ///
 /// See the documentation for Mockito's code generation for more information.