[webview_flutter] Adds support for receiving a url with WebResourceError (#3884)

Fixes https://github.com/flutter/flutter/issues/125682
diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index 114f2a7..9dc13cc 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.9.0
+
+* Adds support for `WebResouceError.url`.
+
 ## 3.8.2
 
 * Fixes unawaited_futures violations.
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 462027e..53b8a00 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
@@ -826,9 +826,7 @@
 
       expect(error.errorType, isNotNull);
       expect(
-        (error as AndroidWebResourceError)
-            .failingUrl
-            ?.startsWith('https://www.notawebsite..com'),
+        error.url?.startsWith('https://www.notawebsite..com'),
         isTrue,
       );
     });
diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
index b618c8f..0c3e341 100644
--- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
@@ -114,6 +114,7 @@
   description: ${error.description}
   errorType: ${error.errorType}
   isForMainFrame: ${error.isForMainFrame}
+  url: ${error.url}
           ''');
           })
           ..setOnNavigationRequest((NavigationRequest request) {
diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml
index 16cc964..0b23ead 100644
--- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml
@@ -17,7 +17,7 @@
     # The example app is bundled with the plugin so we use a path dependency on
     # the parent directory to use the current plugin's version.
     path: ../
-  webview_flutter_platform_interface: ^2.3.0
+  webview_flutter_platform_interface: ^2.4.0
 
 dev_dependencies:
   espresso: ^0.2.0
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart
index 6a4c918..7c5f092 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart
@@ -825,12 +825,14 @@
     required super.errorCode,
     required super.description,
     super.isForMainFrame,
-    this.failingUrl,
-  }) : super(
+    super.url,
+  })  : failingUrl = url,
+        super(
           errorType: _errorCodeToErrorType(errorCode),
         );
 
   /// Gets the URL for which the failing resource request was made.
+  @Deprecated('Please use `url`.')
   final String? failingUrl;
 
   static WebResourceErrorType? _errorCodeToErrorType(int errorCode) {
@@ -954,7 +956,7 @@
           callback(AndroidWebResourceError._(
             errorCode: error.errorCode,
             description: error.description,
-            failingUrl: request.url,
+            url: request.url,
             isForMainFrame: request.isForMainFrame,
           ));
         }
@@ -971,7 +973,7 @@
           callback(AndroidWebResourceError._(
             errorCode: errorCode,
             description: description,
-            failingUrl: failingUrl,
+            url: failingUrl,
             isForMainFrame: true,
           ));
         }
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index a58c031..6755ed3 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/packages/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: 3.8.2
+version: 3.9.0
 
 environment:
   sdk: ">=2.18.0 <4.0.0"
@@ -20,7 +20,7 @@
 dependencies:
   flutter:
     sdk: flutter
-  webview_flutter_platform_interface: ^2.3.0
+  webview_flutter_platform_interface: ^2.4.0
 
 dev_dependencies:
   build_runner: ^2.1.4
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
index e24e3e0..26cd8a2 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.7.0
+
+* Adds support for `WebResouceError.url`.
+
 ## 3.6.3
 
 * Introduces `NSError.toString` for better diagnostics.
@@ -16,7 +20,7 @@
 
 * Adds support to enable debugging of web contents on the latest versions of WebKit. See
   `WebKitWebViewController.setInspectable`.
-  
+
 ## 3.5.0
 
 * Adds support to limit navigation to pages within the app’s domain. See
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 4b457d0..edbe5e3 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
@@ -834,6 +834,10 @@
 
       final WebResourceError error = await errorCompleter.future;
       expect(error, isNotNull);
+      expect(
+        error.url?.startsWith('https://www.notawebsite..com'),
+        isTrue,
+      );
 
       expect((error as WebKitWebResourceError).domain, isNotNull);
     });
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 19e2b39..32c0bf9 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
@@ -11,6 +11,7 @@
 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
 		8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */; };
 		8F4FF94B29AC223F000A6586 /* FWFURLTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF94A29AC223F000A6586 /* FWFURLTests.m */; };
+		8F78EAAA2A02CB9100C2E520 /* FWFErrorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F78EAA92A02CB9100C2E520 /* FWFErrorTests.m */; };
 		8FA6A87928062CD000A4B183 /* FWFInstanceManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */; };
 		8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */; };
 		8FB79B55281B24F600C101D3 /* FWFDataConvertersTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */; };
@@ -80,6 +81,7 @@
 		7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
 		8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewFlutterWKWebViewExternalAPITests.m; sourceTree = "<group>"; };
 		8F4FF94A29AC223F000A6586 /* FWFURLTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFURLTests.m; sourceTree = "<group>"; };
+		8F78EAA92A02CB9100C2E520 /* FWFErrorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFErrorTests.m; sourceTree = "<group>"; };
 		8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFInstanceManagerTests.m; sourceTree = "<group>"; };
 		8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewHostApiTests.m; sourceTree = "<group>"; };
 		8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFDataConvertersTests.m; sourceTree = "<group>"; };
@@ -165,6 +167,7 @@
 				8FB79B902820BAC700C101D3 /* FWFUIViewHostApiTests.m */,
 				8FB79B962821985200C101D3 /* FWFObjectHostApiTests.m */,
 				8F4FF94A29AC223F000A6586 /* FWFURLTests.m */,
+				8F78EAA92A02CB9100C2E520 /* FWFErrorTests.m */,
 			);
 			path = RunnerTests;
 			sourceTree = "<group>";
@@ -466,6 +469,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				8FA6A87928062CD000A4B183 /* FWFInstanceManagerTests.m in Sources */,
+				8F78EAAA2A02CB9100C2E520 /* FWFErrorTests.m in Sources */,
 				8F4FF94B29AC223F000A6586 /* FWFURLTests.m in Sources */,
 				8FB79B852820A3A400C101D3 /* FWFUIDelegateHostApiTests.m in Sources */,
 				8FB79B972821985200C101D3 /* FWFObjectHostApiTests.m in Sources */,
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFDataConvertersTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFDataConvertersTests.m
index f580b60..1a64148 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFDataConvertersTests.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFDataConvertersTests.m
@@ -98,14 +98,20 @@
 }
 
 - (void)testFWFNSErrorDataFromNSError {
+  NSObject *unsupportedType = [[NSObject alloc] init];
   NSError *error = [NSError errorWithDomain:@"domain"
                                        code:23
-                                   userInfo:@{NSLocalizedDescriptionKey : @"description"}];
+                                   userInfo:@{@"a" : @"b", @"c" : unsupportedType}];
 
   FWFNSErrorData *data = FWFNSErrorDataFromNativeNSError(error);
   XCTAssertEqualObjects(data.code, @23);
   XCTAssertEqualObjects(data.domain, @"domain");
-  XCTAssertEqualObjects(data.localizedDescription, @"description");
+
+  NSDictionary *userInfo = @{
+    @"a" : @"b",
+    @"c" : [NSString stringWithFormat:@"Unsupported Type: %@", unsupportedType.description]
+  };
+  XCTAssertEqualObjects(data.userInfo, userInfo);
 }
 
 - (void)testFWFWKScriptMessageDataFromWKScriptMessage {
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFErrorTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFErrorTests.m
new file mode 100644
index 0000000..422b7e4
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFErrorTests.m
@@ -0,0 +1,18 @@
+// 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 XCTest;
+
+#import <XCTest/XCTest.h>
+
+@interface FWFErrorTests : XCTestCase
+@end
+
+@implementation FWFErrorTests
+- (void)testNSErrorUserInfoKey {
+  // These MUST match the String values in the Dart class NSErrorUserInfoKey.
+  XCTAssertEqualObjects(NSLocalizedDescriptionKey, @"NSLocalizedDescription");
+  XCTAssertEqualObjects(NSURLErrorFailingURLStringErrorKey, @"NSErrorFailingURLStringKey");
+}
+@end
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewHostApiTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewHostApiTests.m
index 248f947..a89bf33 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewHostApiTests.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewHostApiTests.m
@@ -408,7 +408,7 @@
   XCTAssertTrue([errorData isKindOfClass:[FWFNSErrorData class]]);
   XCTAssertEqualObjects(errorData.code, @0);
   XCTAssertEqualObjects(errorData.domain, @"errorDomain");
-  XCTAssertEqualObjects(errorData.localizedDescription, @"description");
+  XCTAssertEqualObjects(errorData.userInfo, @{NSLocalizedDescriptionKey : @"description"});
 }
 
 - (void)testWebViewContentInsetBehaviorShouldBeNever {
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 8c3f753..7367828 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart
@@ -115,6 +115,7 @@
   description: ${error.description}
   errorType: ${error.errorType}
   isForMainFrame: ${error.isForMainFrame}
+  url: ${error.url}
           ''');
           })
           ..setOnNavigationRequest((NavigationRequest request) {
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml
index 2ddb76a..aae7c82 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.3.0
+  webview_flutter_platform_interface: ^2.4.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/ios/Classes/FWFDataConverters.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m
index 20607da..28c0298 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m
@@ -192,9 +192,19 @@
 }
 
 FWFNSErrorData *FWFNSErrorDataFromNativeNSError(NSError *error) {
-  return [FWFNSErrorData makeWithCode:@(error.code)
-                               domain:error.domain
-                 localizedDescription:error.localizedDescription];
+  NSMutableDictionary *userInfo;
+  if (error.userInfo) {
+    userInfo = [NSMutableDictionary dictionary];
+    for (NSErrorUserInfoKey key in error.userInfo.allKeys) {
+      NSObject *value = error.userInfo[key];
+      if ([value isKindOfClass:[NSString class]]) {
+        userInfo[key] = value;
+      } else {
+        userInfo[key] = [NSString stringWithFormat:@"Unsupported Type: %@", value.description];
+      }
+    }
+  }
+  return [FWFNSErrorData makeWithCode:@(error.code) domain:error.domain userInfo:userInfo];
 }
 
 FWFNSKeyValueChangeKeyEnumData *FWFNSKeyValueChangeKeyEnumDataFromNativeNSKeyValueChangeKey(
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h
index e302568..3aa3fb7 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h
@@ -342,10 +342,10 @@
 - (instancetype)init NS_UNAVAILABLE;
 + (instancetype)makeWithCode:(NSNumber *)code
                       domain:(NSString *)domain
-        localizedDescription:(NSString *)localizedDescription;
+                    userInfo:(nullable NSDictionary<NSString *, id> *)userInfo;
 @property(nonatomic, strong) NSNumber *code;
 @property(nonatomic, copy) NSString *domain;
-@property(nonatomic, copy) NSString *localizedDescription;
+@property(nonatomic, strong, nullable) NSDictionary<NSString *, id> *userInfo;
 @end
 
 /// Mirror of WKScriptMessage.
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m
index 8104a5d..66264eb 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m
+++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m
@@ -455,11 +455,11 @@
 @implementation FWFNSErrorData
 + (instancetype)makeWithCode:(NSNumber *)code
                       domain:(NSString *)domain
-        localizedDescription:(NSString *)localizedDescription {
+                    userInfo:(nullable NSDictionary<NSString *, id> *)userInfo {
   FWFNSErrorData *pigeonResult = [[FWFNSErrorData alloc] init];
   pigeonResult.code = code;
   pigeonResult.domain = domain;
-  pigeonResult.localizedDescription = localizedDescription;
+  pigeonResult.userInfo = userInfo;
   return pigeonResult;
 }
 + (FWFNSErrorData *)fromList:(NSArray *)list {
@@ -468,8 +468,7 @@
   NSAssert(pigeonResult.code != nil, @"");
   pigeonResult.domain = GetNullableObjectAtIndex(list, 1);
   NSAssert(pigeonResult.domain != nil, @"");
-  pigeonResult.localizedDescription = GetNullableObjectAtIndex(list, 2);
-  NSAssert(pigeonResult.localizedDescription != nil, @"");
+  pigeonResult.userInfo = GetNullableObjectAtIndex(list, 2);
   return pigeonResult;
 }
 + (nullable FWFNSErrorData *)nullableFromList:(NSArray *)list {
@@ -479,7 +478,7 @@
   return @[
     (self.code ?: [NSNull null]),
     (self.domain ?: [NSNull null]),
-    (self.localizedDescription ?: [NSNull null]),
+    (self.userInfo ?: [NSNull null]),
   ];
 }
 @end
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart
index 91c81fe..0f7fd8f 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart
@@ -522,20 +522,20 @@
   NSErrorData({
     required this.code,
     required this.domain,
-    required this.localizedDescription,
+    this.userInfo,
   });
 
   int code;
 
   String domain;
 
-  String localizedDescription;
+  Map<String?, Object?>? userInfo;
 
   Object encode() {
     return <Object?>[
       code,
       domain,
-      localizedDescription,
+      userInfo,
     ];
   }
 
@@ -544,7 +544,7 @@
     return NSErrorData(
       code: result[0]! as int,
       domain: result[1]! as String,
-      localizedDescription: result[2]! as String,
+      userInfo: (result[2] as Map<Object?, Object?>?)?.cast<String?, Object?>(),
     );
   }
 }
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart
index e8dde5e..3fe1a57 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart
@@ -201,6 +201,23 @@
   final Map<String, String> allHttpHeaderFields;
 }
 
+/// Keys that may exist in the user info map of `NSError`.
+class NSErrorUserInfoKey {
+  NSErrorUserInfoKey._();
+
+  /// The corresponding value is a localized string representation of the error
+  /// that, if present, will be returned by [NSError.localizedDescription].
+  ///
+  /// See https://developer.apple.com/documentation/foundation/nslocalizeddescriptionkey.
+  static const String NSLocalizedDescription = 'NSLocalizedDescription';
+
+  /// The URL which caused a load to fail.
+  ///
+  /// See https://developer.apple.com/documentation/foundation/nsurlerrorfailingurlstringerrorkey?language=objc.
+  static const String NSURLErrorFailingURLStringError =
+      'NSErrorFailingURLStringKey';
+}
+
 /// Information about an error condition.
 ///
 /// Wraps [NSError](https://developer.apple.com/documentation/foundation/nserror?language=objc).
@@ -210,7 +227,7 @@
   const NSError({
     required this.code,
     required this.domain,
-    required this.localizedDescription,
+    this.userInfo = const <String, Object?>{},
   });
 
   /// The error code.
@@ -221,15 +238,23 @@
   /// A string containing the error domain.
   final String domain;
 
+  /// Map of arbitrary data.
+  ///
+  /// See [NSErrorUserInfoKey] for possible keys (non-exhaustive).
+  ///
+  /// This currently only supports values that are a String.
+  final Map<String, Object?> userInfo;
+
   /// A string containing the localized description of the error.
-  final String localizedDescription;
+  String? get localizedDescription =>
+      userInfo[NSErrorUserInfoKey.NSLocalizedDescription] as String?;
 
   @override
   String toString() {
-    if (localizedDescription.isEmpty) {
-      return 'Error $domain:$code';
+    if (localizedDescription?.isEmpty ?? true) {
+      return 'Error $domain:$code:$userInfo';
     }
-    return '$localizedDescription ($domain:$code)';
+    return '$localizedDescription ($domain:$code:$userInfo)';
   }
 }
 
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/legacy/web_kit_webview_widget.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/legacy/web_kit_webview_widget.dart
index da0745a..f344edb 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/legacy/web_kit_webview_widget.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/legacy/web_kit_webview_widget.dart
@@ -596,7 +596,7 @@
     return WebResourceError(
       errorCode: error.code,
       domain: error.domain,
-      description: error.localizedDescription,
+      description: error.localizedDescription ?? '',
       errorType: errorType,
     );
   }
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart
index b0ce519..ccc8737 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart
@@ -199,7 +199,7 @@
     return NSError(
       domain: domain,
       code: code,
-      localizedDescription: localizedDescription,
+      userInfo: userInfo?.cast<String, Object?>() ?? <String, Object?>{},
     );
   }
 }
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 5df538c..14cba33 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
@@ -665,10 +665,13 @@
 
 /// An implementation of [WebResourceError] with the WebKit API.
 class WebKitWebResourceError extends WebResourceError {
-  WebKitWebResourceError._(this._nsError, {required bool isForMainFrame})
-      : super(
+  WebKitWebResourceError._(
+    this._nsError, {
+    required bool isForMainFrame,
+    required super.url,
+  }) : super(
           errorCode: _nsError.code,
-          description: _nsError.localizedDescription,
+          description: _nsError.localizedDescription ?? '',
           errorType: _toWebResourceErrorType(_nsError.code),
           isForMainFrame: isForMainFrame,
         );
@@ -766,14 +769,24 @@
       didFailNavigation: (WKWebView webView, NSError error) {
         if (weakThis.target?._onWebResourceError != null) {
           weakThis.target!._onWebResourceError!(
-            WebKitWebResourceError._(error, isForMainFrame: true),
+            WebKitWebResourceError._(
+              error,
+              isForMainFrame: true,
+              url: error.userInfo[NSErrorUserInfoKey
+                  .NSURLErrorFailingURLStringError] as String?,
+            ),
           );
         }
       },
       didFailProvisionalNavigation: (WKWebView webView, NSError error) {
         if (weakThis.target?._onWebResourceError != null) {
           weakThis.target!._onWebResourceError!(
-            WebKitWebResourceError._(error, isForMainFrame: true),
+            WebKitWebResourceError._(
+              error,
+              isForMainFrame: true,
+              url: error.userInfo[NSErrorUserInfoKey
+                  .NSURLErrorFailingURLStringError] as String?,
+            ),
           );
         }
       },
@@ -785,9 +798,9 @@
                 code: WKErrorCode.webContentProcessTerminated,
                 // Value from https://developer.apple.com/documentation/webkit/wkerrordomain?language=objc.
                 domain: 'WKErrorDomain',
-                localizedDescription: '',
               ),
               isForMainFrame: true,
+              url: null,
             ),
           );
         }
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart
index 16248e8..dea5080 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart
@@ -299,7 +299,7 @@
 class NSErrorData {
   late int code;
   late String domain;
-  late String localizedDescription;
+  late Map<String?, Object?>? userInfo;
 }
 
 /// Mirror of WKScriptMessage.
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
index 9c313c0..a88d74d 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.6.3
+version: 3.7.0
 
 environment:
   sdk: ">=2.18.0 <4.0.0"
@@ -20,7 +20,7 @@
   flutter:
     sdk: flutter
   path: ^1.8.0
-  webview_flutter_platform_interface: ^2.3.0
+  webview_flutter_platform_interface: ^2.4.0
 
 dev_dependencies:
   build_runner: ^2.1.5
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart
index 9388bdd..2033d39 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart
@@ -777,7 +777,6 @@
           details: const NSError(
             code: WKErrorCode.javaScriptResultTypeIsUnsupported,
             domain: '',
-            localizedDescription: '',
           ),
         ));
         expect(
@@ -1049,7 +1048,9 @@
           const NSError(
             code: WKErrorCode.webViewInvalidated,
             domain: 'domain',
-            localizedDescription: 'my desc',
+            userInfo: <String, Object?>{
+              NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
+            },
           ),
         );
 
@@ -1086,7 +1087,9 @@
           const NSError(
             code: WKErrorCode.webContentProcessTerminated,
             domain: 'domain',
-            localizedDescription: 'my desc',
+            userInfo: <String, Object?>{
+              NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
+            },
           ),
         );
 
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart
index 3dbaea0..ea2e37e 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart
@@ -252,33 +252,41 @@
       const NSError(
         code: 0,
         domain: 'domain',
-        localizedDescription: 'desc',
+        userInfo: <String, Object?>{
+          NSErrorUserInfoKey.NSLocalizedDescription: 'desc',
+        },
       ).toString(),
-      'desc (domain:0)',
+      'desc (domain:0:{NSLocalizedDescription: desc})',
     );
     expect(
       const NSError(
         code: 0,
         domain: 'domain',
-        localizedDescription: '',
+        userInfo: <String, Object?>{
+          NSErrorUserInfoKey.NSLocalizedDescription: '',
+        },
       ).toString(),
-      'Error domain:0',
+      'Error domain:0:{NSLocalizedDescription: }',
     );
     expect(
       const NSError(
         code: 255,
         domain: 'bar',
-        localizedDescription: 'baz',
+        userInfo: <String, Object?>{
+          NSErrorUserInfoKey.NSLocalizedDescription: 'baz',
+        },
       ).toString(),
-      'baz (bar:255)',
+      'baz (bar:255:{NSLocalizedDescription: baz})',
     );
     expect(
       const NSError(
         code: 255,
         domain: 'bar',
-        localizedDescription: '',
+        userInfo: <String, Object?>{
+          NSErrorUserInfoKey.NSLocalizedDescription: '',
+        },
       ).toString(),
-      'Error bar:255',
+      'Error bar:255:{NSLocalizedDescription: }',
     );
   });
 }
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart
index 856ca55..055f342 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart
@@ -614,7 +614,9 @@
           NSErrorData(
             code: 23,
             domain: 'Hello',
-            localizedDescription: 'localiziedDescription',
+            userInfo: <String, Object?>{
+              NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
+            },
           ),
         );
 
@@ -646,7 +648,9 @@
           NSErrorData(
             code: 23,
             domain: 'Hello',
-            localizedDescription: 'localiziedDescription',
+            userInfo: <String, Object?>{
+              NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
+            },
           ),
         );
 
@@ -851,7 +855,9 @@
             details: NSErrorData(
               code: 0,
               domain: 'domain',
-              localizedDescription: 'desc',
+              userInfo: <String, Object?>{
+                NSErrorUserInfoKey.NSLocalizedDescription: 'desc',
+              },
             ),
           ),
         );
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart
index 0f754cb..4581c92 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart
@@ -92,12 +92,17 @@
         const NSError(
           code: WKErrorCode.webViewInvalidated,
           domain: 'domain',
-          localizedDescription: 'my desc',
+          userInfo: <String, Object?>{
+            NSErrorUserInfoKey.NSURLErrorFailingURLStringError:
+                'www.flutter.dev',
+            NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
+          },
         ),
       );
 
       expect(callbackError.description, 'my desc');
       expect(callbackError.errorCode, WKErrorCode.webViewInvalidated);
+      expect(callbackError.url, 'www.flutter.dev');
       expect(callbackError.domain, 'domain');
       expect(callbackError.errorType, WebResourceErrorType.webViewInvalidated);
       expect(callbackError.isForMainFrame, true);
@@ -126,11 +131,16 @@
         const NSError(
           code: WKErrorCode.webViewInvalidated,
           domain: 'domain',
-          localizedDescription: 'my desc',
+          userInfo: <String, Object?>{
+            NSErrorUserInfoKey.NSURLErrorFailingURLStringError:
+                'www.flutter.dev',
+            NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
+          },
         ),
       );
 
       expect(callbackError.description, 'my desc');
+      expect(callbackError.url, 'www.flutter.dev');
       expect(callbackError.errorCode, WKErrorCode.webViewInvalidated);
       expect(callbackError.domain, 'domain');
       expect(callbackError.errorType, WebResourceErrorType.webViewInvalidated);
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 89df634..2cd20e0 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
@@ -502,7 +502,6 @@
         details: const NSError(
           code: WKErrorCode.javaScriptResultTypeIsUnsupported,
           domain: '',
-          localizedDescription: '',
         ),
       ));
       expect(