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

Fixes https://github.com/flutter/flutter/issues/125682
This commit is contained in:
Maurice Parrish
2023-07-13 20:49:22 -04:00
committed by GitHub
parent b166a0fd91
commit 13557d6b60
29 changed files with 177 additions and 62 deletions

View File

@ -1,3 +1,7 @@
## 3.9.0
* Adds support for `WebResouceError.url`.
## 3.8.2 ## 3.8.2
* Fixes unawaited_futures violations. * Fixes unawaited_futures violations.

View File

@ -826,9 +826,7 @@ Future<void> main() async {
expect(error.errorType, isNotNull); expect(error.errorType, isNotNull);
expect( expect(
(error as AndroidWebResourceError) error.url?.startsWith('https://www.notawebsite..com'),
.failingUrl
?.startsWith('https://www.notawebsite..com'),
isTrue, isTrue,
); );
}); });

View File

@ -114,6 +114,7 @@ Page resource error:
description: ${error.description} description: ${error.description}
errorType: ${error.errorType} errorType: ${error.errorType}
isForMainFrame: ${error.isForMainFrame} isForMainFrame: ${error.isForMainFrame}
url: ${error.url}
'''); ''');
}) })
..setOnNavigationRequest((NavigationRequest request) { ..setOnNavigationRequest((NavigationRequest request) {

View File

@ -17,7 +17,7 @@ dependencies:
# The example app is bundled with the plugin so we use a path dependency on # 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. # the parent directory to use the current plugin's version.
path: ../ path: ../
webview_flutter_platform_interface: ^2.3.0 webview_flutter_platform_interface: ^2.4.0
dev_dependencies: dev_dependencies:
espresso: ^0.2.0 espresso: ^0.2.0

View File

@ -825,12 +825,14 @@ class AndroidWebResourceError extends WebResourceError {
required super.errorCode, required super.errorCode,
required super.description, required super.description,
super.isForMainFrame, super.isForMainFrame,
this.failingUrl, super.url,
}) : super( }) : failingUrl = url,
super(
errorType: _errorCodeToErrorType(errorCode), errorType: _errorCodeToErrorType(errorCode),
); );
/// Gets the URL for which the failing resource request was made. /// Gets the URL for which the failing resource request was made.
@Deprecated('Please use `url`.')
final String? failingUrl; final String? failingUrl;
static WebResourceErrorType? _errorCodeToErrorType(int errorCode) { static WebResourceErrorType? _errorCodeToErrorType(int errorCode) {
@ -954,7 +956,7 @@ class AndroidNavigationDelegate extends PlatformNavigationDelegate {
callback(AndroidWebResourceError._( callback(AndroidWebResourceError._(
errorCode: error.errorCode, errorCode: error.errorCode,
description: error.description, description: error.description,
failingUrl: request.url, url: request.url,
isForMainFrame: request.isForMainFrame, isForMainFrame: request.isForMainFrame,
)); ));
} }
@ -971,7 +973,7 @@ class AndroidNavigationDelegate extends PlatformNavigationDelegate {
callback(AndroidWebResourceError._( callback(AndroidWebResourceError._(
errorCode: errorCode, errorCode: errorCode,
description: description, description: description,
failingUrl: failingUrl, url: failingUrl,
isForMainFrame: true, isForMainFrame: true,
)); ));
} }

View File

@ -2,7 +2,7 @@ name: webview_flutter_android
description: A Flutter plugin that provides a WebView widget on Android. 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 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 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: environment:
sdk: ">=2.18.0 <4.0.0" sdk: ">=2.18.0 <4.0.0"
@ -20,7 +20,7 @@ flutter:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
webview_flutter_platform_interface: ^2.3.0 webview_flutter_platform_interface: ^2.4.0
dev_dependencies: dev_dependencies:
build_runner: ^2.1.4 build_runner: ^2.1.4

View File

@ -1,3 +1,7 @@
## 3.7.0
* Adds support for `WebResouceError.url`.
## 3.6.3 ## 3.6.3
* Introduces `NSError.toString` for better diagnostics. * Introduces `NSError.toString` for better diagnostics.

View File

@ -834,6 +834,10 @@ Future<void> main() async {
final WebResourceError error = await errorCompleter.future; final WebResourceError error = await errorCompleter.future;
expect(error, isNotNull); expect(error, isNotNull);
expect(
error.url?.startsWith('https://www.notawebsite..com'),
isTrue,
);
expect((error as WebKitWebResourceError).domain, isNotNull); expect((error as WebKitWebResourceError).domain, isNotNull);
}); });

View File

@ -11,6 +11,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */; }; 8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */; };
8F4FF94B29AC223F000A6586 /* FWFURLTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF94A29AC223F000A6586 /* FWFURLTests.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 */; }; 8FA6A87928062CD000A4B183 /* FWFInstanceManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */; };
8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */; }; 8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */; };
8FB79B55281B24F600C101D3 /* FWFDataConvertersTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFDataConvertersTests.m; sourceTree = "<group>"; };
@ -165,6 +167,7 @@
8FB79B902820BAC700C101D3 /* FWFUIViewHostApiTests.m */, 8FB79B902820BAC700C101D3 /* FWFUIViewHostApiTests.m */,
8FB79B962821985200C101D3 /* FWFObjectHostApiTests.m */, 8FB79B962821985200C101D3 /* FWFObjectHostApiTests.m */,
8F4FF94A29AC223F000A6586 /* FWFURLTests.m */, 8F4FF94A29AC223F000A6586 /* FWFURLTests.m */,
8F78EAA92A02CB9100C2E520 /* FWFErrorTests.m */,
); );
path = RunnerTests; path = RunnerTests;
sourceTree = "<group>"; sourceTree = "<group>";
@ -466,6 +469,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
8FA6A87928062CD000A4B183 /* FWFInstanceManagerTests.m in Sources */, 8FA6A87928062CD000A4B183 /* FWFInstanceManagerTests.m in Sources */,
8F78EAAA2A02CB9100C2E520 /* FWFErrorTests.m in Sources */,
8F4FF94B29AC223F000A6586 /* FWFURLTests.m in Sources */, 8F4FF94B29AC223F000A6586 /* FWFURLTests.m in Sources */,
8FB79B852820A3A400C101D3 /* FWFUIDelegateHostApiTests.m in Sources */, 8FB79B852820A3A400C101D3 /* FWFUIDelegateHostApiTests.m in Sources */,
8FB79B972821985200C101D3 /* FWFObjectHostApiTests.m in Sources */, 8FB79B972821985200C101D3 /* FWFObjectHostApiTests.m in Sources */,

View File

@ -98,14 +98,20 @@
} }
- (void)testFWFNSErrorDataFromNSError { - (void)testFWFNSErrorDataFromNSError {
NSObject *unsupportedType = [[NSObject alloc] init];
NSError *error = [NSError errorWithDomain:@"domain" NSError *error = [NSError errorWithDomain:@"domain"
code:23 code:23
userInfo:@{NSLocalizedDescriptionKey : @"description"}]; userInfo:@{@"a" : @"b", @"c" : unsupportedType}];
FWFNSErrorData *data = FWFNSErrorDataFromNativeNSError(error); FWFNSErrorData *data = FWFNSErrorDataFromNativeNSError(error);
XCTAssertEqualObjects(data.code, @23); XCTAssertEqualObjects(data.code, @23);
XCTAssertEqualObjects(data.domain, @"domain"); 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 { - (void)testFWFWKScriptMessageDataFromWKScriptMessage {

View File

@ -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

View File

@ -408,7 +408,7 @@ static bool feq(CGFloat a, CGFloat b) { return fabs(b - a) < FLT_EPSILON; }
XCTAssertTrue([errorData isKindOfClass:[FWFNSErrorData class]]); XCTAssertTrue([errorData isKindOfClass:[FWFNSErrorData class]]);
XCTAssertEqualObjects(errorData.code, @0); XCTAssertEqualObjects(errorData.code, @0);
XCTAssertEqualObjects(errorData.domain, @"errorDomain"); XCTAssertEqualObjects(errorData.domain, @"errorDomain");
XCTAssertEqualObjects(errorData.localizedDescription, @"description"); XCTAssertEqualObjects(errorData.userInfo, @{NSLocalizedDescriptionKey : @"description"});
} }
- (void)testWebViewContentInsetBehaviorShouldBeNever { - (void)testWebViewContentInsetBehaviorShouldBeNever {

View File

@ -115,6 +115,7 @@ Page resource error:
description: ${error.description} description: ${error.description}
errorType: ${error.errorType} errorType: ${error.errorType}
isForMainFrame: ${error.isForMainFrame} isForMainFrame: ${error.isForMainFrame}
url: ${error.url}
'''); ''');
}) })
..setOnNavigationRequest((NavigationRequest request) { ..setOnNavigationRequest((NavigationRequest request) {

View File

@ -10,7 +10,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
path_provider: ^2.0.6 path_provider: ^2.0.6
webview_flutter_platform_interface: ^2.3.0 webview_flutter_platform_interface: ^2.4.0
webview_flutter_wkwebview: webview_flutter_wkwebview:
# When depending on this package from a real application you should use: # When depending on this package from a real application you should use:
# webview_flutter: ^x.y.z # webview_flutter: ^x.y.z

View File

@ -192,9 +192,19 @@ WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData(
} }
FWFNSErrorData *FWFNSErrorDataFromNativeNSError(NSError *error) { FWFNSErrorData *FWFNSErrorDataFromNativeNSError(NSError *error) {
return [FWFNSErrorData makeWithCode:@(error.code) NSMutableDictionary *userInfo;
domain:error.domain if (error.userInfo) {
localizedDescription:error.localizedDescription]; 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( FWFNSKeyValueChangeKeyEnumData *FWFNSKeyValueChangeKeyEnumDataFromNativeNSKeyValueChangeKey(

View File

@ -342,10 +342,10 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) {
- (instancetype)init NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE;
+ (instancetype)makeWithCode:(NSNumber *)code + (instancetype)makeWithCode:(NSNumber *)code
domain:(NSString *)domain domain:(NSString *)domain
localizedDescription:(NSString *)localizedDescription; userInfo:(nullable NSDictionary<NSString *, id> *)userInfo;
@property(nonatomic, strong) NSNumber *code; @property(nonatomic, strong) NSNumber *code;
@property(nonatomic, copy) NSString *domain; @property(nonatomic, copy) NSString *domain;
@property(nonatomic, copy) NSString *localizedDescription; @property(nonatomic, strong, nullable) NSDictionary<NSString *, id> *userInfo;
@end @end
/// Mirror of WKScriptMessage. /// Mirror of WKScriptMessage.

View File

@ -455,11 +455,11 @@ static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) {
@implementation FWFNSErrorData @implementation FWFNSErrorData
+ (instancetype)makeWithCode:(NSNumber *)code + (instancetype)makeWithCode:(NSNumber *)code
domain:(NSString *)domain domain:(NSString *)domain
localizedDescription:(NSString *)localizedDescription { userInfo:(nullable NSDictionary<NSString *, id> *)userInfo {
FWFNSErrorData *pigeonResult = [[FWFNSErrorData alloc] init]; FWFNSErrorData *pigeonResult = [[FWFNSErrorData alloc] init];
pigeonResult.code = code; pigeonResult.code = code;
pigeonResult.domain = domain; pigeonResult.domain = domain;
pigeonResult.localizedDescription = localizedDescription; pigeonResult.userInfo = userInfo;
return pigeonResult; return pigeonResult;
} }
+ (FWFNSErrorData *)fromList:(NSArray *)list { + (FWFNSErrorData *)fromList:(NSArray *)list {
@ -468,8 +468,7 @@ static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) {
NSAssert(pigeonResult.code != nil, @""); NSAssert(pigeonResult.code != nil, @"");
pigeonResult.domain = GetNullableObjectAtIndex(list, 1); pigeonResult.domain = GetNullableObjectAtIndex(list, 1);
NSAssert(pigeonResult.domain != nil, @""); NSAssert(pigeonResult.domain != nil, @"");
pigeonResult.localizedDescription = GetNullableObjectAtIndex(list, 2); pigeonResult.userInfo = GetNullableObjectAtIndex(list, 2);
NSAssert(pigeonResult.localizedDescription != nil, @"");
return pigeonResult; return pigeonResult;
} }
+ (nullable FWFNSErrorData *)nullableFromList:(NSArray *)list { + (nullable FWFNSErrorData *)nullableFromList:(NSArray *)list {
@ -479,7 +478,7 @@ static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) {
return @[ return @[
(self.code ?: [NSNull null]), (self.code ?: [NSNull null]),
(self.domain ?: [NSNull null]), (self.domain ?: [NSNull null]),
(self.localizedDescription ?: [NSNull null]), (self.userInfo ?: [NSNull null]),
]; ];
} }
@end @end

View File

@ -522,20 +522,20 @@ class NSErrorData {
NSErrorData({ NSErrorData({
required this.code, required this.code,
required this.domain, required this.domain,
required this.localizedDescription, this.userInfo,
}); });
int code; int code;
String domain; String domain;
String localizedDescription; Map<String?, Object?>? userInfo;
Object encode() { Object encode() {
return <Object?>[ return <Object?>[
code, code,
domain, domain,
localizedDescription, userInfo,
]; ];
} }
@ -544,7 +544,7 @@ class NSErrorData {
return NSErrorData( return NSErrorData(
code: result[0]! as int, code: result[0]! as int,
domain: result[1]! as String, domain: result[1]! as String,
localizedDescription: result[2]! as String, userInfo: (result[2] as Map<Object?, Object?>?)?.cast<String?, Object?>(),
); );
} }
} }

View File

@ -201,6 +201,23 @@ class NSUrlRequest {
final Map<String, String> allHttpHeaderFields; 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. /// Information about an error condition.
/// ///
/// Wraps [NSError](https://developer.apple.com/documentation/foundation/nserror?language=objc). /// Wraps [NSError](https://developer.apple.com/documentation/foundation/nserror?language=objc).
@ -210,7 +227,7 @@ class NSError {
const NSError({ const NSError({
required this.code, required this.code,
required this.domain, required this.domain,
required this.localizedDescription, this.userInfo = const <String, Object?>{},
}); });
/// The error code. /// The error code.
@ -221,15 +238,23 @@ class NSError {
/// A string containing the error domain. /// A string containing the error domain.
final String 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. /// A string containing the localized description of the error.
final String localizedDescription; String? get localizedDescription =>
userInfo[NSErrorUserInfoKey.NSLocalizedDescription] as String?;
@override @override
String toString() { String toString() {
if (localizedDescription.isEmpty) { if (localizedDescription?.isEmpty ?? true) {
return 'Error $domain:$code'; return 'Error $domain:$code:$userInfo';
} }
return '$localizedDescription ($domain:$code)'; return '$localizedDescription ($domain:$code:$userInfo)';
} }
} }

View File

@ -596,7 +596,7 @@ class WebKitWebViewPlatformController extends WebViewPlatformController {
return WebResourceError( return WebResourceError(
errorCode: error.code, errorCode: error.code,
domain: error.domain, domain: error.domain,
description: error.localizedDescription, description: error.localizedDescription ?? '',
errorType: errorType, errorType: errorType,
); );
} }

View File

@ -199,7 +199,7 @@ extension _WKNSErrorDataConverter on NSErrorData {
return NSError( return NSError(
domain: domain, domain: domain,
code: code, code: code,
localizedDescription: localizedDescription, userInfo: userInfo?.cast<String, Object?>() ?? <String, Object?>{},
); );
} }
} }

View File

@ -665,10 +665,13 @@ class WebKitWebViewWidget extends PlatformWebViewWidget {
/// An implementation of [WebResourceError] with the WebKit API. /// An implementation of [WebResourceError] with the WebKit API.
class WebKitWebResourceError extends WebResourceError { class WebKitWebResourceError extends WebResourceError {
WebKitWebResourceError._(this._nsError, {required bool isForMainFrame}) WebKitWebResourceError._(
: super( this._nsError, {
required bool isForMainFrame,
required super.url,
}) : super(
errorCode: _nsError.code, errorCode: _nsError.code,
description: _nsError.localizedDescription, description: _nsError.localizedDescription ?? '',
errorType: _toWebResourceErrorType(_nsError.code), errorType: _toWebResourceErrorType(_nsError.code),
isForMainFrame: isForMainFrame, isForMainFrame: isForMainFrame,
); );
@ -766,14 +769,24 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate {
didFailNavigation: (WKWebView webView, NSError error) { didFailNavigation: (WKWebView webView, NSError error) {
if (weakThis.target?._onWebResourceError != null) { if (weakThis.target?._onWebResourceError != null) {
weakThis.target!._onWebResourceError!( weakThis.target!._onWebResourceError!(
WebKitWebResourceError._(error, isForMainFrame: true), WebKitWebResourceError._(
error,
isForMainFrame: true,
url: error.userInfo[NSErrorUserInfoKey
.NSURLErrorFailingURLStringError] as String?,
),
); );
} }
}, },
didFailProvisionalNavigation: (WKWebView webView, NSError error) { didFailProvisionalNavigation: (WKWebView webView, NSError error) {
if (weakThis.target?._onWebResourceError != null) { if (weakThis.target?._onWebResourceError != null) {
weakThis.target!._onWebResourceError!( weakThis.target!._onWebResourceError!(
WebKitWebResourceError._(error, isForMainFrame: true), WebKitWebResourceError._(
error,
isForMainFrame: true,
url: error.userInfo[NSErrorUserInfoKey
.NSURLErrorFailingURLStringError] as String?,
),
); );
} }
}, },
@ -785,9 +798,9 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate {
code: WKErrorCode.webContentProcessTerminated, code: WKErrorCode.webContentProcessTerminated,
// Value from https://developer.apple.com/documentation/webkit/wkerrordomain?language=objc. // Value from https://developer.apple.com/documentation/webkit/wkerrordomain?language=objc.
domain: 'WKErrorDomain', domain: 'WKErrorDomain',
localizedDescription: '',
), ),
isForMainFrame: true, isForMainFrame: true,
url: null,
), ),
); );
} }

View File

@ -299,7 +299,7 @@ class WKFrameInfoData {
class NSErrorData { class NSErrorData {
late int code; late int code;
late String domain; late String domain;
late String localizedDescription; late Map<String?, Object?>? userInfo;
} }
/// Mirror of WKScriptMessage. /// Mirror of WKScriptMessage.

View File

@ -2,7 +2,7 @@ name: webview_flutter_wkwebview
description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. 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 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 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: environment:
sdk: ">=2.18.0 <4.0.0" sdk: ">=2.18.0 <4.0.0"
@ -20,7 +20,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
path: ^1.8.0 path: ^1.8.0
webview_flutter_platform_interface: ^2.3.0 webview_flutter_platform_interface: ^2.4.0
dev_dependencies: dev_dependencies:
build_runner: ^2.1.5 build_runner: ^2.1.5

View File

@ -777,7 +777,6 @@ void main() {
details: const NSError( details: const NSError(
code: WKErrorCode.javaScriptResultTypeIsUnsupported, code: WKErrorCode.javaScriptResultTypeIsUnsupported,
domain: '', domain: '',
localizedDescription: '',
), ),
)); ));
expect( expect(
@ -1049,7 +1048,9 @@ void main() {
const NSError( const NSError(
code: WKErrorCode.webViewInvalidated, code: WKErrorCode.webViewInvalidated,
domain: 'domain', domain: 'domain',
localizedDescription: 'my desc', userInfo: <String, Object?>{
NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
},
), ),
); );
@ -1086,7 +1087,9 @@ void main() {
const NSError( const NSError(
code: WKErrorCode.webContentProcessTerminated, code: WKErrorCode.webContentProcessTerminated,
domain: 'domain', domain: 'domain',
localizedDescription: 'my desc', userInfo: <String, Object?>{
NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
},
), ),
); );

View File

@ -252,33 +252,41 @@ void main() {
const NSError( const NSError(
code: 0, code: 0,
domain: 'domain', domain: 'domain',
localizedDescription: 'desc', userInfo: <String, Object?>{
NSErrorUserInfoKey.NSLocalizedDescription: 'desc',
},
).toString(), ).toString(),
'desc (domain:0)', 'desc (domain:0:{NSLocalizedDescription: desc})',
); );
expect( expect(
const NSError( const NSError(
code: 0, code: 0,
domain: 'domain', domain: 'domain',
localizedDescription: '', userInfo: <String, Object?>{
NSErrorUserInfoKey.NSLocalizedDescription: '',
},
).toString(), ).toString(),
'Error domain:0', 'Error domain:0:{NSLocalizedDescription: }',
); );
expect( expect(
const NSError( const NSError(
code: 255, code: 255,
domain: 'bar', domain: 'bar',
localizedDescription: 'baz', userInfo: <String, Object?>{
NSErrorUserInfoKey.NSLocalizedDescription: 'baz',
},
).toString(), ).toString(),
'baz (bar:255)', 'baz (bar:255:{NSLocalizedDescription: baz})',
); );
expect( expect(
const NSError( const NSError(
code: 255, code: 255,
domain: 'bar', domain: 'bar',
localizedDescription: '', userInfo: <String, Object?>{
NSErrorUserInfoKey.NSLocalizedDescription: '',
},
).toString(), ).toString(),
'Error bar:255', 'Error bar:255:{NSLocalizedDescription: }',
); );
}); });
} }

View File

@ -614,7 +614,9 @@ void main() {
NSErrorData( NSErrorData(
code: 23, code: 23,
domain: 'Hello', domain: 'Hello',
localizedDescription: 'localiziedDescription', userInfo: <String, Object?>{
NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
},
), ),
); );
@ -646,7 +648,9 @@ void main() {
NSErrorData( NSErrorData(
code: 23, code: 23,
domain: 'Hello', domain: 'Hello',
localizedDescription: 'localiziedDescription', userInfo: <String, Object?>{
NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
},
), ),
); );
@ -851,7 +855,9 @@ void main() {
details: NSErrorData( details: NSErrorData(
code: 0, code: 0,
domain: 'domain', domain: 'domain',
localizedDescription: 'desc', userInfo: <String, Object?>{
NSErrorUserInfoKey.NSLocalizedDescription: 'desc',
},
), ),
), ),
); );

View File

@ -92,12 +92,17 @@ void main() {
const NSError( const NSError(
code: WKErrorCode.webViewInvalidated, code: WKErrorCode.webViewInvalidated,
domain: 'domain', domain: 'domain',
localizedDescription: 'my desc', userInfo: <String, Object?>{
NSErrorUserInfoKey.NSURLErrorFailingURLStringError:
'www.flutter.dev',
NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
},
), ),
); );
expect(callbackError.description, 'my desc'); expect(callbackError.description, 'my desc');
expect(callbackError.errorCode, WKErrorCode.webViewInvalidated); expect(callbackError.errorCode, WKErrorCode.webViewInvalidated);
expect(callbackError.url, 'www.flutter.dev');
expect(callbackError.domain, 'domain'); expect(callbackError.domain, 'domain');
expect(callbackError.errorType, WebResourceErrorType.webViewInvalidated); expect(callbackError.errorType, WebResourceErrorType.webViewInvalidated);
expect(callbackError.isForMainFrame, true); expect(callbackError.isForMainFrame, true);
@ -126,11 +131,16 @@ void main() {
const NSError( const NSError(
code: WKErrorCode.webViewInvalidated, code: WKErrorCode.webViewInvalidated,
domain: 'domain', domain: 'domain',
localizedDescription: 'my desc', userInfo: <String, Object?>{
NSErrorUserInfoKey.NSURLErrorFailingURLStringError:
'www.flutter.dev',
NSErrorUserInfoKey.NSLocalizedDescription: 'my desc',
},
), ),
); );
expect(callbackError.description, 'my desc'); expect(callbackError.description, 'my desc');
expect(callbackError.url, 'www.flutter.dev');
expect(callbackError.errorCode, WKErrorCode.webViewInvalidated); expect(callbackError.errorCode, WKErrorCode.webViewInvalidated);
expect(callbackError.domain, 'domain'); expect(callbackError.domain, 'domain');
expect(callbackError.errorType, WebResourceErrorType.webViewInvalidated); expect(callbackError.errorType, WebResourceErrorType.webViewInvalidated);

View File

@ -502,7 +502,6 @@ void main() {
details: const NSError( details: const NSError(
code: WKErrorCode.javaScriptResultTypeIsUnsupported, code: WKErrorCode.javaScriptResultTypeIsUnsupported,
domain: '', domain: '',
localizedDescription: '',
), ),
)); ));
expect( expect(