[webview_flutter_wkwebview] Adds WKWebView implementation to override console log (#4703)

Adds the WKWebView implementation for registering a JavaScript console callback. This will allow developers to receive JavaScript console messages in a Dart callback.

This PR contains the `webview_flutter_wkwebview` specific changes from PR #4541.

Related issue: flutter/flutter#32908

*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
index 384b16b..5bb9cb3 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.8.0
+
+* Adds support to register a callback to receive JavaScript console messages. See `WebKitWebViewController.setOnConsoleMessage`.
+
 ## 3.7.4
 
 * Adds pub topics to package metadata.
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 66567ac..24b96d7 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
@@ -1196,6 +1196,48 @@
       await expectLater(controller.currentUrl(), completion(primaryUrl));
     },
   );
+
+  group('Logging', () {
+    testWidgets('can receive console log messages',
+        (WidgetTester tester) async {
+      const String testPage = '''
+          <!DOCTYPE html>
+          <html>
+          <head>
+            <title>WebResourceError test</title>
+          </head>
+          <body onload="console.debug('Debug message')">
+            <p>Test page</p>
+          </body>
+          </html>
+         ''';
+
+      final Completer<String> debugMessageReceived = Completer<String>();
+      final PlatformWebViewController controller = PlatformWebViewController(
+        const PlatformWebViewControllerCreationParams(),
+      );
+      unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted));
+
+      await controller
+          .setOnConsoleMessage((JavaScriptConsoleMessage consoleMessage) {
+        debugMessageReceived
+            .complete('${consoleMessage.level.name}:${consoleMessage.message}');
+      });
+
+      await controller.loadHtmlString(testPage);
+
+      await tester.pumpWidget(Builder(
+        builder: (BuildContext context) {
+          return PlatformWebViewWidget(
+            PlatformWebViewWidgetCreationParams(controller: controller),
+          ).build(context);
+        },
+      ));
+
+      await expectLater(
+          debugMessageReceived.future, completion('debug:Debug message'));
+    });
+  });
 }
 
 /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests.
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 7367828..4180650 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
@@ -74,6 +74,40 @@
 </html>
 ''';
 
+const String kLogExamplePage = '''
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<title>Load file or HTML string example</title>
+</head>
+<body onload="console.log('Logging that the page is loading.')">
+
+<h1>Local demo page</h1>
+<p>
+  This page is used to test the forwarding of console logs to Dart.
+</p>
+
+<style>
+    .btn-group button {
+      padding: 24px; 24px;
+      display: block;
+      width: 25%;
+      margin: 5px 0px 0px 0px;
+    }
+</style>
+
+<div class="btn-group">
+    <button onclick="console.error('This is an error message.')">Error</button>
+    <button onclick="console.warn('This is a warning message.')">Warning</button>
+    <button onclick="console.info('This is a info message.')">Info</button>
+    <button onclick="console.debug('This is a debug message.')">Debug</button>
+    <button onclick="console.log('This is a log message.')">Log</button>
+</div>
+
+</body>
+</html>
+''';
+
 class WebViewExample extends StatefulWidget {
   const WebViewExample({super.key, this.cookieManager});
 
@@ -202,6 +236,7 @@
   loadHtmlString,
   transparentBackground,
   setCookie,
+  logExample,
 }
 
 class SampleMenu extends StatelessWidget {
@@ -262,6 +297,9 @@
           case MenuOptions.setCookie:
             _onSetCookie();
             break;
+          case MenuOptions.logExample:
+            _onLogExample();
+            break;
         }
       },
       itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
@@ -318,6 +356,10 @@
           value: MenuOptions.transparentBackground,
           child: Text('Transparent background example'),
         ),
+        const PopupMenuItem<MenuOptions>(
+          value: MenuOptions.logExample,
+          child: Text('Log example'),
+        ),
       ],
     );
   }
@@ -466,6 +508,16 @@
 
     return indexFile.path;
   }
+
+  Future<void> _onLogExample() {
+    webViewController
+        .setOnConsoleMessage((JavaScriptConsoleMessage consoleMessage) {
+      debugPrint(
+          '== JS == ${consoleMessage.level.name}: ${consoleMessage.message}');
+    });
+
+    return webViewController.loadHtmlString(kLogExamplePage);
+  }
 }
 
 class NavigationControls extends StatelessWidget {
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
index b214ded..11f6bc6 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
@@ -10,7 +10,7 @@
   flutter:
     sdk: flutter
   path_provider: ^2.0.6
-  webview_flutter_platform_interface: ^2.4.0
+  webview_flutter_platform_interface: ^2.6.0
   webview_flutter_wkwebview:
     # When depending on this package from a real application you should use:
     #   webview_flutter: ^x.y.z
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart
index 764ed11..5162a06 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:convert';
 import 'dart:math';
 
 import 'package:flutter/material.dart';
@@ -269,6 +270,7 @@
   bool _zoomEnabled = true;
   WebKitNavigationDelegate? _currentNavigationDelegate;
 
+  void Function(JavaScriptConsoleMessage)? _onConsoleMessageCallback;
   void Function(PlatformWebViewPermissionRequest)? _onPermissionRequestCallback;
 
   WebKitWebViewControllerCreationParams get _webKitParams =>
@@ -327,7 +329,8 @@
         javaScriptChannelParams is WebKitJavaScriptChannelParams
             ? javaScriptChannelParams
             : WebKitJavaScriptChannelParams.fromJavaScriptChannelParams(
-                javaScriptChannelParams);
+                javaScriptChannelParams,
+              );
 
     _javaScriptChannelParams[webKitParams.name] = webKitParams;
 
@@ -512,6 +515,104 @@
         .addUserScript(userScript);
   }
 
+  /// Sets a callback that notifies the host application of any log messages
+  /// written to the JavaScript console.
+  ///
+  /// Because the iOS WKWebView doesn't provide a built-in way to access the
+  /// console, setting this callback will inject a custom [WKUserScript] which
+  /// overrides the JavaScript `console.debug`, `console.error`, `console.info`,
+  /// `console.log` and `console.warn` methods and forwards the console message
+  /// via a `JavaScriptChannel` to the host application.
+  @override
+  Future<void> setOnConsoleMessage(
+    void Function(JavaScriptConsoleMessage consoleMessage) onConsoleMessage,
+  ) {
+    _onConsoleMessageCallback = onConsoleMessage;
+
+    final JavaScriptChannelParams channelParams = WebKitJavaScriptChannelParams(
+        name: 'fltConsoleMessage',
+        webKitProxy: _webKitParams.webKitProxy,
+        onMessageReceived: (JavaScriptMessage message) {
+          if (_onConsoleMessageCallback == null) {
+            return;
+          }
+
+          final Map<String, dynamic> consoleLog =
+              jsonDecode(message.message) as Map<String, dynamic>;
+
+          JavaScriptLogLevel level;
+          switch (consoleLog['level']) {
+            case 'error':
+              level = JavaScriptLogLevel.error;
+              break;
+            case 'warning':
+              level = JavaScriptLogLevel.warning;
+              break;
+            case 'debug':
+              level = JavaScriptLogLevel.debug;
+              break;
+            case 'info':
+              level = JavaScriptLogLevel.info;
+              break;
+            case 'log':
+            default:
+              level = JavaScriptLogLevel.log;
+              break;
+          }
+
+          _onConsoleMessageCallback!(
+            JavaScriptConsoleMessage(
+              level: level,
+              message: consoleLog['message']! as String,
+            ),
+          );
+        });
+
+    addJavaScriptChannel(channelParams);
+    return _injectConsoleOverride();
+  }
+
+  Future<void> _injectConsoleOverride() {
+    const WKUserScript overrideScript = WKUserScript(
+      '''
+function log(type, args) {
+  var message =  Object.values(args)
+      .map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v) : v.toString())
+      .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
+      .join(", ");
+
+  var log = {
+    level: type,
+    message: message
+  };
+
+  window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log));
+}
+
+let originalLog = console.log;
+let originalInfo = console.info;
+let originalWarn = console.warn;
+let originalError = console.error;
+let originalDebug = console.debug;
+
+console.log = function() { log("log", arguments); originalLog.apply(null, arguments) };
+console.info = function() { log("info", arguments); originalInfo.apply(null, arguments) };
+console.warn = function() { log("warning", arguments); originalWarn.apply(null, arguments) };
+console.error = function() { log("error", arguments); originalError.apply(null, arguments) };
+console.debug = function() { log("debug", arguments); originalDebug.apply(null, arguments) };
+
+window.addEventListener("error", function(e) {
+  log("error", e.message + " at " + e.filename + ":" + e.lineno + ":" + e.colno);
+});
+      ''',
+      WKUserScriptInjectionTime.atDocumentStart,
+      isMainFrameOnly: true,
+    );
+
+    return _webView.configuration.userContentController
+        .addUserScript(overrideScript);
+  }
+
   // WKWebView does not support removing a single user script, so all user
   // scripts and all message handlers are removed instead. And the JavaScript
   // channels that shouldn't be removed are re-registered. Note that this
@@ -537,6 +638,9 @@
       // Zoom is disabled with a WKUserScript, so this adds it back if it was
       // removed above.
       if (!_zoomEnabled) _disableZoom(),
+      // Console logs are forwarded with a WKUserScript, so this adds it back
+      // if a console callback was registered with [setOnConsoleMessage].
+      if (_onConsoleMessageCallback != null) _injectConsoleOverride(),
     ]);
   }
 
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
index 2c88866..5f99bc9 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/packages/tree/main/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: 3.7.4
+version: 3.8.0
 
 environment:
   sdk: ">=2.19.0 <4.0.0"
@@ -20,7 +20,7 @@
   flutter:
     sdk: flutter
   path: ^1.8.0
-  webview_flutter_platform_interface: ^2.4.0
+  webview_flutter_platform_interface: ^2.6.0
 
 dev_dependencies:
   build_runner: ^2.1.5
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_cookie_manager_test.mocks.dart
index b88d686..5d0eede 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_cookie_manager_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_cookie_manager_test.mocks.dart
@@ -1,7 +1,9 @@
-// Mocks generated by Mockito 5.4.0 from annotations
+// Mocks generated by Mockito 5.4.1 from annotations
 // in webview_flutter_wkwebview/test/legacy/web_kit_cookie_manager_test.dart.
 // Do not manually edit this file.
 
+// @dart=2.19
+
 // ignore_for_file: no_leading_underscores_for_library_prefixes
 import 'dart:async' as _i3;
 
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.mocks.dart
index 3c4210e..b011270 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.mocks.dart
@@ -1,7 +1,9 @@
-// Mocks generated by Mockito 5.4.0 from annotations
+// Mocks generated by Mockito 5.4.1 from annotations
 // in webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart.
 // Do not manually edit this file.
 
+// @dart=2.19
+
 // ignore_for_file: no_leading_underscores_for_library_prefixes
 import 'dart:async' as _i5;
 import 'dart:math' as _i2;
@@ -760,6 +762,16 @@
         returnValueForMissingStub: _i5.Future<void>.value(),
       ) as _i5.Future<void>);
   @override
+  _i5.Future<void> setLimitsNavigationsToAppBoundDomains(bool? limit) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #setLimitsNavigationsToAppBoundDomains,
+          [limit],
+        ),
+        returnValue: _i5.Future<void>.value(),
+        returnValueForMissingStub: _i5.Future<void>.value(),
+      ) as _i5.Future<void>);
+  @override
   _i5.Future<void> setMediaTypesRequiringUserActionForPlayback(
           Set<_i4.WKAudiovisualMediaType>? types) =>
       (super.noSuchMethod(
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart
index a648fc6..60cb608 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart
@@ -1,7 +1,9 @@
-// Mocks generated by Mockito 5.4.0 from annotations
+// Mocks generated by Mockito 5.4.1 from annotations
 // in webview_flutter_wkwebview/test/src/foundation/foundation_test.dart.
 // Do not manually edit this file.
 
+// @dart=2.19
+
 // ignore_for_file: no_leading_underscores_for_library_prefixes
 import 'package:mockito/mockito.dart' as _i1;
 import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart' as _i3;
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart
index 65fdc41..515028b 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart
@@ -1,7 +1,9 @@
-// Mocks generated by Mockito 5.4.0 from annotations
+// Mocks generated by Mockito 5.4.1 from annotations
 // in webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart.
 // Do not manually edit this file.
 
+// @dart=2.19
+
 // ignore_for_file: no_leading_underscores_for_library_prefixes
 import 'dart:async' as _i4;
 
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart
index e92132b..e46a4ce 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart
@@ -1,7 +1,9 @@
-// Mocks generated by Mockito 5.4.0 from annotations
+// Mocks generated by Mockito 5.4.1 from annotations
 // in webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart.
 // Do not manually edit this file.
 
+// @dart=2.19
+
 // ignore_for_file: no_leading_underscores_for_library_prefixes
 import 'dart:async' as _i3;
 
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart
index 2cd20e0..a22e2f6 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart
@@ -28,6 +28,7 @@
   WKWebsiteDataStore,
   WKWebView,
   WKWebViewConfiguration,
+  WKScriptMessageHandler,
 ])
 void main() {
   WidgetsFlutterBinding.ensureInitialized();
@@ -98,6 +99,7 @@
                   requestMediaCapturePermission: requestMediaCapturePermission,
                 );
           },
+          createScriptMessageHandler: WKScriptMessageHandler.detached,
         ),
         instanceManager: instanceManager,
       );
@@ -1163,6 +1165,125 @@
       await controller.setInspectable(true);
       verify(mockWebView.setInspectable(true));
     });
+
+    group('Console logging', () {
+      test('setConsoleLogCallback should inject the correct JavaScript',
+          () async {
+        final MockWKUserContentController mockUserContentController =
+            MockWKUserContentController();
+        final WebKitWebViewController controller = createControllerWithMocks(
+          mockUserContentController: mockUserContentController,
+        );
+
+        await controller
+            .setOnConsoleMessage((JavaScriptConsoleMessage message) {});
+
+        final List<dynamic> capturedScripts =
+            verify(mockUserContentController.addUserScript(captureAny))
+                .captured
+                .toList();
+        final WKUserScript messageHandlerScript =
+            capturedScripts[0] as WKUserScript;
+        final WKUserScript overrideConsoleScript =
+            capturedScripts[1] as WKUserScript;
+
+        expect(messageHandlerScript.isMainFrameOnly, isFalse);
+        expect(messageHandlerScript.injectionTime,
+            WKUserScriptInjectionTime.atDocumentStart);
+        expect(messageHandlerScript.source,
+            'window.fltConsoleMessage = webkit.messageHandlers.fltConsoleMessage;');
+
+        expect(overrideConsoleScript.isMainFrameOnly, isTrue);
+        expect(overrideConsoleScript.injectionTime,
+            WKUserScriptInjectionTime.atDocumentStart);
+        expect(overrideConsoleScript.source, '''
+function log(type, args) {
+  var message =  Object.values(args)
+      .map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v) : v.toString())
+      .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
+      .join(", ");
+
+  var log = {
+    level: type,
+    message: message
+  };
+
+  window.webkit.messageHandlers.fltConsoleMessage.postMessage(JSON.stringify(log));
+}
+
+let originalLog = console.log;
+let originalInfo = console.info;
+let originalWarn = console.warn;
+let originalError = console.error;
+let originalDebug = console.debug;
+
+console.log = function() { log("log", arguments); originalLog.apply(null, arguments) };
+console.info = function() { log("info", arguments); originalInfo.apply(null, arguments) };
+console.warn = function() { log("warning", arguments); originalWarn.apply(null, arguments) };
+console.error = function() { log("error", arguments); originalError.apply(null, arguments) };
+console.debug = function() { log("debug", arguments); originalDebug.apply(null, arguments) };
+
+window.addEventListener("error", function(e) {
+  log("error", e.message + " at " + e.filename + ":" + e.lineno + ":" + e.colno);
+});
+      ''');
+      });
+
+      test('setConsoleLogCallback should parse levels correctly', () async {
+        final MockWKUserContentController mockUserContentController =
+            MockWKUserContentController();
+        final WebKitWebViewController controller = createControllerWithMocks(
+          mockUserContentController: mockUserContentController,
+        );
+
+        final Map<JavaScriptLogLevel, String> logs =
+            <JavaScriptLogLevel, String>{};
+        await controller.setOnConsoleMessage(
+            (JavaScriptConsoleMessage message) =>
+                logs[message.level] = message.message);
+
+        final List<dynamic> capturedParameters = verify(
+                mockUserContentController.addScriptMessageHandler(
+                    captureAny, any))
+            .captured
+            .toList();
+        final WKScriptMessageHandler scriptMessageHandler =
+            capturedParameters[0] as WKScriptMessageHandler;
+
+        scriptMessageHandler.didReceiveScriptMessage(
+            mockUserContentController,
+            const WKScriptMessage(
+                name: 'test',
+                body: '{"level": "debug", "message": "Debug message"}'));
+        scriptMessageHandler.didReceiveScriptMessage(
+            mockUserContentController,
+            const WKScriptMessage(
+                name: 'test',
+                body: '{"level": "error", "message": "Error message"}'));
+        scriptMessageHandler.didReceiveScriptMessage(
+            mockUserContentController,
+            const WKScriptMessage(
+                name: 'test',
+                body: '{"level": "info", "message": "Info message"}'));
+        scriptMessageHandler.didReceiveScriptMessage(
+            mockUserContentController,
+            const WKScriptMessage(
+                name: 'test',
+                body: '{"level": "log", "message": "Log message"}'));
+        scriptMessageHandler.didReceiveScriptMessage(
+            mockUserContentController,
+            const WKScriptMessage(
+                name: 'test',
+                body: '{"level": "warning", "message": "Warning message"}'));
+
+        expect(logs.length, 5);
+        expect(logs[JavaScriptLogLevel.debug], 'Debug message');
+        expect(logs[JavaScriptLogLevel.error], 'Error message');
+        expect(logs[JavaScriptLogLevel.info], 'Info message');
+        expect(logs[JavaScriptLogLevel.log], 'Log message');
+        expect(logs[JavaScriptLogLevel.warning], 'Warning message');
+      });
+    });
   });
 
   group('WebKitJavaScriptChannelParams', () {
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.mocks.dart
index 84f1587..7624a87 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.mocks.dart
@@ -1,7 +1,9 @@
-// Mocks generated by Mockito 5.4.0 from annotations
+// Mocks generated by Mockito 5.4.1 from annotations
 // in webview_flutter_wkwebview/test/webkit_webview_controller_test.dart.
 // Do not manually edit this file.
 
+// @dart=2.19
+
 // ignore_for_file: no_leading_underscores_for_library_prefixes
 import 'dart:async' as _i6;
 import 'dart:math' as _i3;
@@ -119,6 +121,17 @@
         );
 }
 
+class _FakeWKScriptMessageHandler_9 extends _i1.SmartFake
+    implements _i5.WKScriptMessageHandler {
+  _FakeWKScriptMessageHandler_9(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
 /// A class which mocks [NSUrl].
 ///
 /// See the documentation for Mockito's code generation for more information.
@@ -924,3 +937,75 @@
         returnValueForMissingStub: _i6.Future<void>.value(),
       ) as _i6.Future<void>);
 }
+
+/// A class which mocks [WKScriptMessageHandler].
+///
+/// See the documentation for Mockito's code generation for more information.
+// ignore: must_be_immutable
+class MockWKScriptMessageHandler extends _i1.Mock
+    implements _i5.WKScriptMessageHandler {
+  MockWKScriptMessageHandler() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  void Function(
+    _i5.WKUserContentController,
+    _i5.WKScriptMessage,
+  ) get didReceiveScriptMessage => (super.noSuchMethod(
+        Invocation.getter(#didReceiveScriptMessage),
+        returnValue: (
+          _i5.WKUserContentController userContentController,
+          _i5.WKScriptMessage message,
+        ) {},
+      ) as void Function(
+        _i5.WKUserContentController,
+        _i5.WKScriptMessage,
+      ));
+  @override
+  _i5.WKScriptMessageHandler copy() => (super.noSuchMethod(
+        Invocation.method(
+          #copy,
+          [],
+        ),
+        returnValue: _FakeWKScriptMessageHandler_9(
+          this,
+          Invocation.method(
+            #copy,
+            [],
+          ),
+        ),
+      ) as _i5.WKScriptMessageHandler);
+  @override
+  _i6.Future<void> addObserver(
+    _i2.NSObject? observer, {
+    required String? keyPath,
+    required Set<_i2.NSKeyValueObservingOptions>? options,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #addObserver,
+          [observer],
+          {
+            #keyPath: keyPath,
+            #options: options,
+          },
+        ),
+        returnValue: _i6.Future<void>.value(),
+        returnValueForMissingStub: _i6.Future<void>.value(),
+      ) as _i6.Future<void>);
+  @override
+  _i6.Future<void> removeObserver(
+    _i2.NSObject? observer, {
+    required String? keyPath,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #removeObserver,
+          [observer],
+          {#keyPath: keyPath},
+        ),
+        returnValue: _i6.Future<void>.value(),
+        returnValueForMissingStub: _i6.Future<void>.value(),
+      ) as _i6.Future<void>);
+}
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_cookie_manager_test.mocks.dart
index 95818c3..0426319 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_cookie_manager_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_cookie_manager_test.mocks.dart
@@ -1,7 +1,9 @@
-// Mocks generated by Mockito 5.4.0 from annotations
+// Mocks generated by Mockito 5.4.1 from annotations
 // in webview_flutter_wkwebview/test/webkit_webview_cookie_manager_test.dart.
 // Do not manually edit this file.
 
+// @dart=2.19
+
 // ignore_for_file: no_leading_underscores_for_library_prefixes
 import 'dart:async' as _i3;
 
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.mocks.dart
index b171e28..8d104c1 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.mocks.dart
@@ -1,7 +1,9 @@
-// Mocks generated by Mockito 5.4.0 from annotations
+// Mocks generated by Mockito 5.4.1 from annotations
 // in webview_flutter_wkwebview/test/webkit_webview_widget_test.dart.
 // Do not manually edit this file.
 
+// @dart=2.19
+
 // ignore_for_file: no_leading_underscores_for_library_prefixes
 import 'dart:async' as _i3;