Skip to content

Commit 40d7893

Browse files
[webview_flutter_android][webview_flutter_wkwebview] Adds platform implementations for onHttpError (#6149)
Copy of flutter/packages#3695 since it doesn't contain permission to edit from contributors. Part of flutter/flutter#39502 Full PR flutter/packages#3278
1 parent ada70fe commit 40d7893

30 files changed

+1689
-1074
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 3.13.0
2+
3+
* Adds `decidePolicyForNavigationResponse` to internal WKNavigationDelegate to support the
4+
`PlatformNavigationDelegate.onHttpError` callback.
5+
16
## 3.12.0
27

38
* Adds support for `setOnScrollPositionChange` method to the `WebKitWebViewController`.

example/integration_test/webview_flutter_test.dart

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,81 @@ Future<void> main() async {
987987
},
988988
);
989989

990+
testWidgets('onHttpError', (WidgetTester tester) async {
991+
final Completer<HttpResponseError> errorCompleter =
992+
Completer<HttpResponseError>();
993+
994+
final PlatformWebViewController controller = PlatformWebViewController(
995+
const PlatformWebViewControllerCreationParams(),
996+
);
997+
unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted));
998+
final PlatformNavigationDelegate delegate = PlatformNavigationDelegate(
999+
const PlatformNavigationDelegateCreationParams(),
1000+
);
1001+
unawaited(delegate.setOnHttpError((HttpResponseError error) {
1002+
errorCompleter.complete(error);
1003+
}));
1004+
unawaited(controller.setPlatformNavigationDelegate(delegate));
1005+
unawaited(controller.loadRequest(
1006+
LoadRequestParams(uri: Uri.parse('$prefixUrl/favicon.ico')),
1007+
));
1008+
1009+
await tester.pumpWidget(Builder(
1010+
builder: (BuildContext context) {
1011+
return PlatformWebViewWidget(
1012+
PlatformWebViewWidgetCreationParams(controller: controller),
1013+
).build(context);
1014+
},
1015+
));
1016+
1017+
final HttpResponseError error = await errorCompleter.future;
1018+
1019+
expect(error, isNotNull);
1020+
expect(error.response?.statusCode, 404);
1021+
});
1022+
1023+
testWidgets('onHttpError is not called when no HTTP error is received',
1024+
(WidgetTester tester) async {
1025+
const String testPage = '''
1026+
<!DOCTYPE html><html>
1027+
</head>
1028+
<body>
1029+
</body>
1030+
</html>
1031+
''';
1032+
1033+
final Completer<HttpResponseError> errorCompleter =
1034+
Completer<HttpResponseError>();
1035+
final Completer<void> pageFinishCompleter = Completer<void>();
1036+
1037+
final PlatformWebViewController controller = PlatformWebViewController(
1038+
const PlatformWebViewControllerCreationParams(),
1039+
);
1040+
unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted));
1041+
final PlatformNavigationDelegate delegate = PlatformNavigationDelegate(
1042+
const PlatformNavigationDelegateCreationParams(),
1043+
);
1044+
unawaited(delegate.setOnHttpError((HttpResponseError error) {
1045+
errorCompleter.complete(error);
1046+
}));
1047+
unawaited(delegate.setOnPageFinished(
1048+
(_) => pageFinishCompleter.complete(),
1049+
));
1050+
unawaited(controller.setPlatformNavigationDelegate(delegate));
1051+
unawaited(controller.loadHtmlString(testPage));
1052+
1053+
await tester.pumpWidget(Builder(
1054+
builder: (BuildContext context) {
1055+
return PlatformWebViewWidget(
1056+
PlatformWebViewWidgetCreationParams(controller: controller),
1057+
).build(context);
1058+
},
1059+
));
1060+
1061+
expect(errorCompleter.future, doesNotComplete);
1062+
await pageFinishCompleter.future;
1063+
});
1064+
9901065
testWidgets('can block requests', (WidgetTester tester) async {
9911066
Completer<void> pageLoaded = Completer<void>();
9921067

example/ios/RunnerTests/FWFDataConvertersTests.m

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,34 @@ - (void)testNSKeyValueChangeKeyConversionReturnsUnknownIfUnrecognized {
171171
- (void)testWKNavigationTypeConversionReturnsUnknownIfUnrecognized {
172172
XCTAssertEqual(FWFWKNavigationTypeFromNativeWKNavigationType(-15), FWFWKNavigationTypeUnknown);
173173
}
174+
175+
- (void)testFWFWKNavigationResponseDataFromNativeNavigationResponse {
176+
WKNavigationResponse *mockResponse = OCMClassMock([WKNavigationResponse class]);
177+
OCMStub([mockResponse isForMainFrame]).andReturn(YES);
178+
179+
NSHTTPURLResponse *mockURLResponse = OCMClassMock([NSHTTPURLResponse class]);
180+
OCMStub([mockURLResponse statusCode]).andReturn(1);
181+
OCMStub([mockResponse response]).andReturn(mockURLResponse);
182+
183+
FWFWKNavigationResponseData *data =
184+
FWFWKNavigationResponseDataFromNativeNavigationResponse(mockResponse);
185+
XCTAssertEqual(data.forMainFrame, YES);
186+
}
187+
188+
- (void)testFWFNSHttpUrlResponseDataFromNativeNSURLResponse {
189+
NSHTTPURLResponse *mockResponse = OCMClassMock([NSHTTPURLResponse class]);
190+
OCMStub([mockResponse statusCode]).andReturn(1);
191+
192+
FWFNSHttpUrlResponseData *data = FWFNSHttpUrlResponseDataFromNativeNSURLResponse(mockResponse);
193+
XCTAssertEqual(data.statusCode, 1);
194+
}
195+
196+
- (void)testFWFNativeWKNavigationResponsePolicyFromEnum {
197+
XCTAssertEqual(
198+
FWFNativeWKNavigationResponsePolicyFromEnum(FWFWKNavigationResponsePolicyEnumAllow),
199+
WKNavigationResponsePolicyAllow);
200+
XCTAssertEqual(
201+
FWFNativeWKNavigationResponsePolicyFromEnum(FWFWKNavigationResponsePolicyEnumCancel),
202+
WKNavigationResponsePolicyCancel);
203+
}
174204
@end

example/ios/RunnerTests/FWFNavigationDelegateHostApiTests.m

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,4 +267,46 @@ - (void)testDidReceiveAuthenticationChallenge {
267267
XCTAssertEqual(callbackDisposition, NSURLSessionAuthChallengeCancelAuthenticationChallenge);
268268
XCTAssertEqualObjects(callbackCredential, credential);
269269
}
270+
271+
- (void)testDecidePolicyForNavigationResponse {
272+
FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init];
273+
274+
FWFNavigationDelegate *mockDelegate = [self mockNavigationDelegateWithManager:instanceManager
275+
identifier:0];
276+
FWFNavigationDelegateFlutterApiImpl *mockFlutterAPI =
277+
[self mockFlutterApiWithManager:instanceManager];
278+
279+
OCMStub([mockDelegate navigationDelegateAPI]).andReturn(mockFlutterAPI);
280+
281+
WKWebView *mockWebView = OCMClassMock([WKWebView class]);
282+
[instanceManager addDartCreatedInstance:mockWebView withIdentifier:1];
283+
284+
WKNavigationResponse *mockNavigationResponse = OCMClassMock([WKNavigationResponse class]);
285+
OCMStub([mockNavigationResponse isForMainFrame]).andReturn(YES);
286+
287+
NSHTTPURLResponse *mockURLResponse = OCMClassMock([NSHTTPURLResponse class]);
288+
OCMStub([mockURLResponse statusCode]).andReturn(1);
289+
OCMStub([mockNavigationResponse response]).andReturn(mockURLResponse);
290+
291+
OCMStub([mockFlutterAPI
292+
decidePolicyForNavigationResponseForDelegateWithIdentifier:0
293+
webViewIdentifier:1
294+
navigationResponse:OCMOCK_ANY
295+
completion:
296+
([OCMArg
297+
invokeBlockWithArgs:
298+
[[FWFWKNavigationResponsePolicyEnumBox
299+
alloc]
300+
initWithValue:
301+
FWFWKNavigationResponsePolicyEnumAllow],
302+
[NSNull null], nil])]);
303+
304+
WKNavigationResponsePolicy __block callbackPolicy = -1;
305+
[mockDelegate webView:mockWebView
306+
decidePolicyForNavigationResponse:mockNavigationResponse
307+
decisionHandler:^(WKNavigationResponsePolicy policy) {
308+
callbackPolicy = policy;
309+
}];
310+
XCTAssertEqual(callbackPolicy, WKNavigationResponsePolicyAllow);
311+
}
270312
@end

example/lib/main.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ class _WebViewExampleState extends State<WebViewExample> {
174174
..setOnPageFinished((String url) {
175175
debugPrint('Page finished loading: $url');
176176
})
177+
..setOnHttpError((HttpResponseError error) {
178+
debugPrint('Error occurred on page: ${error.response?.statusCode}');
179+
})
177180
..setOnWebResourceError((WebResourceError error) {
178181
debugPrint('''
179182
Page resource error:

ios/Classes/FWFDataConverters.h

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,32 @@ extern FWFWKNavigationActionData *FWFWKNavigationActionDataFromNativeWKNavigatio
8484
/// @return A FWFNSUrlRequestData.
8585
extern FWFNSUrlRequestData *FWFNSUrlRequestDataFromNativeNSURLRequest(NSURLRequest *request);
8686

87-
/// Converts a WKFrameInfo to an FWFWKFrameInfoData.
88-
///
89-
/// @param info The object containing information to create a FWFWKFrameInfoData.
90-
///
91-
/// @return A FWFWKFrameInfoData.
87+
/**
88+
* Converts a WKNavigationResponse to an FWFWKNavigationResponseData.
89+
*
90+
* @param response The object containing information to create a WKNavigationResponseData.
91+
*
92+
* @return A FWFWKNavigationResponseData.
93+
*/
94+
extern FWFWKNavigationResponseData *FWFWKNavigationResponseDataFromNativeNavigationResponse(
95+
WKNavigationResponse *response);
96+
/**
97+
* Converts a NSURLResponse to an FWFNSHttpUrlResponseData.
98+
*
99+
* @param response The object containing information to create a WKNavigationActionData.
100+
*
101+
* @return A FWFNSHttpUrlResponseData.
102+
*/
103+
extern FWFNSHttpUrlResponseData *FWFNSHttpUrlResponseDataFromNativeNSURLResponse(
104+
NSURLResponse *response);
105+
106+
/**
107+
* Converts a WKFrameInfo to an FWFWKFrameInfoData.
108+
*
109+
* @param info The object containing information to create a FWFWKFrameInfoData.
110+
*
111+
* @return A FWFWKFrameInfoData.
112+
*/
92113
extern FWFWKFrameInfoData *FWFWKFrameInfoDataFromNativeWKFrameInfo(WKFrameInfo *info);
93114

94115
/// Converts an FWFWKNavigationActionPolicyEnumData to a WKNavigationActionPolicy.
@@ -99,11 +120,23 @@ extern FWFWKFrameInfoData *FWFWKFrameInfoDataFromNativeWKFrameInfo(WKFrameInfo *
99120
extern WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData(
100121
FWFWKNavigationActionPolicyEnumData *data);
101122

102-
/// Converts a NSError to an FWFNSErrorData.
103-
///
104-
/// @param error The object containing information to create a FWFNSErrorData.
105-
///
106-
/// @return A FWFNSErrorData.
123+
/**
124+
* Converts an FWFWKNavigationResponsePolicyEnumData to a WKNavigationResponsePolicy.
125+
*
126+
* @param policy The data object containing information to create a WKNavigationResponsePolicy.
127+
*
128+
* @return A WKNavigationResponsePolicy or -1 if data could not be converted.
129+
*/
130+
extern WKNavigationResponsePolicy FWFNativeWKNavigationResponsePolicyFromEnum(
131+
FWFWKNavigationResponsePolicyEnum policy);
132+
133+
/**
134+
* Converts a NSError to an FWFNSErrorData.
135+
*
136+
* @param error The object containing information to create a FWFNSErrorData.
137+
*
138+
* @return A FWFNSErrorData.
139+
*/
107140
extern FWFNSErrorData *FWFNSErrorDataFromNativeNSError(NSError *error);
108141

109142
/// Converts an NSKeyValueChangeKey to a FWFNSKeyValueChangeKeyEnumData.

ios/Classes/FWFDataConverters.m

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,24 @@ WKAudiovisualMediaTypes FWFNativeWKAudiovisualMediaTypeFromEnumData(
181181
request:FWFNSUrlRequestDataFromNativeNSURLRequest(info.request)];
182182
}
183183

184+
FWFWKNavigationResponseData *FWFWKNavigationResponseDataFromNativeNavigationResponse(
185+
WKNavigationResponse *response) {
186+
return [FWFWKNavigationResponseData
187+
makeWithResponse:FWFNSHttpUrlResponseDataFromNativeNSURLResponse(response.response)
188+
forMainFrame:response.forMainFrame];
189+
}
190+
191+
/// Cast the NSURLResponse object to NSHTTPURLResponse.
192+
///
193+
/// NSURLResponse doesn't contain the status code so it must be cast to NSHTTPURLResponse.
194+
/// This cast will always succeed because the NSURLResponse object actually is an instance of
195+
/// NSHTTPURLResponse. See:
196+
/// https://developer.apple.com/documentation/foundation/nsurlresponse#overview
197+
FWFNSHttpUrlResponseData *FWFNSHttpUrlResponseDataFromNativeNSURLResponse(NSURLResponse *response) {
198+
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
199+
return [FWFNSHttpUrlResponseData makeWithStatusCode:httpResponse.statusCode];
200+
}
201+
184202
WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData(
185203
FWFWKNavigationActionPolicyEnumData *data) {
186204
switch (data.value) {
@@ -209,6 +227,18 @@ WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData(
209227
return [FWFNSErrorData makeWithCode:error.code domain:error.domain userInfo:userInfo];
210228
}
211229

230+
WKNavigationResponsePolicy FWFNativeWKNavigationResponsePolicyFromEnum(
231+
FWFWKNavigationResponsePolicyEnum policy) {
232+
switch (policy) {
233+
case FWFWKNavigationResponsePolicyEnumAllow:
234+
return WKNavigationResponsePolicyAllow;
235+
case FWFWKNavigationResponsePolicyEnumCancel:
236+
return WKNavigationResponsePolicyCancel;
237+
}
238+
239+
return -1;
240+
}
241+
212242
FWFNSKeyValueChangeKeyEnumData *FWFNSKeyValueChangeKeyEnumDataFromNativeNSKeyValueChangeKey(
213243
NSKeyValueChangeKey key) {
214244
if ([key isEqualToString:NSKeyValueChangeIndexesKey]) {

ios/Classes/FWFGeneratedWebKitApis.h

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2013 The Flutter Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
4-
// Autogenerated from Pigeon (v13.1.2), do not edit directly.
4+
// Autogenerated from Pigeon (v13.0.0), do not edit directly.
55
// See also: https://pub.dev/packages/pigeon
66

77
#import <Foundation/Foundation.h>
@@ -130,6 +130,20 @@ typedef NS_ENUM(NSUInteger, FWFWKNavigationActionPolicyEnum) {
130130
- (instancetype)initWithValue:(FWFWKNavigationActionPolicyEnum)value;
131131
@end
132132

133+
/// Mirror of WKNavigationResponsePolicy.
134+
///
135+
/// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc.
136+
typedef NS_ENUM(NSUInteger, FWFWKNavigationResponsePolicyEnum) {
137+
FWFWKNavigationResponsePolicyEnumAllow = 0,
138+
FWFWKNavigationResponsePolicyEnumCancel = 1,
139+
};
140+
141+
/// Wrapper for FWFWKNavigationResponsePolicyEnum to allow for nullability.
142+
@interface FWFWKNavigationResponsePolicyEnumBox : NSObject
143+
@property(nonatomic, assign) FWFWKNavigationResponsePolicyEnum value;
144+
- (instancetype)initWithValue:(FWFWKNavigationResponsePolicyEnum)value;
145+
@end
146+
133147
/// Mirror of NSHTTPCookiePropertyKey.
134148
///
135149
/// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey.
@@ -341,8 +355,10 @@ typedef NS_ENUM(NSUInteger, FWFNSUrlCredentialPersistence) {
341355
@class FWFWKPermissionDecisionData;
342356
@class FWFWKMediaCaptureTypeData;
343357
@class FWFNSUrlRequestData;
358+
@class FWFNSHttpUrlResponseData;
344359
@class FWFWKUserScriptData;
345360
@class FWFWKNavigationActionData;
361+
@class FWFWKNavigationResponseData;
346362
@class FWFWKFrameInfoData;
347363
@class FWFNSErrorData;
348364
@class FWFWKScriptMessageData;
@@ -430,6 +446,16 @@ typedef NS_ENUM(NSUInteger, FWFNSUrlCredentialPersistence) {
430446
@property(nonatomic, copy) NSDictionary<NSString *, NSString *> *allHttpHeaderFields;
431447
@end
432448

449+
/// Mirror of NSURLResponse.
450+
///
451+
/// See https://developer.apple.com/documentation/foundation/nshttpurlresponse?language=objc.
452+
@interface FWFNSHttpUrlResponseData : NSObject
453+
/// `init` unavailable to enforce nonnull fields, see the `make` class method.
454+
- (instancetype)init NS_UNAVAILABLE;
455+
+ (instancetype)makeWithStatusCode:(NSInteger)statusCode;
456+
@property(nonatomic, assign) NSInteger statusCode;
457+
@end
458+
433459
/// Mirror of WKUserScript.
434460
///
435461
/// See https://developer.apple.com/documentation/webkit/wkuserscript?language=objc.
@@ -458,6 +484,18 @@ typedef NS_ENUM(NSUInteger, FWFNSUrlCredentialPersistence) {
458484
@property(nonatomic, assign) FWFWKNavigationType navigationType;
459485
@end
460486

487+
/// Mirror of WKNavigationResponse.
488+
///
489+
/// See https://developer.apple.com/documentation/webkit/wknavigationresponse.
490+
@interface FWFWKNavigationResponseData : NSObject
491+
/// `init` unavailable to enforce nonnull fields, see the `make` class method.
492+
- (instancetype)init NS_UNAVAILABLE;
493+
+ (instancetype)makeWithResponse:(FWFNSHttpUrlResponseData *)response
494+
forMainFrame:(BOOL)forMainFrame;
495+
@property(nonatomic, strong) FWFNSHttpUrlResponseData *response;
496+
@property(nonatomic, assign) BOOL forMainFrame;
497+
@end
498+
461499
/// Mirror of WKFrameInfo.
462500
///
463501
/// See https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc.
@@ -778,6 +816,15 @@ NSObject<FlutterMessageCodec> *FWFWKNavigationDelegateFlutterApiGetCodec(void);
778816
(void (^)(FWFWKNavigationActionPolicyEnumData
779817
*_Nullable,
780818
FlutterError *_Nullable))completion;
819+
- (void)decidePolicyForNavigationResponseForDelegateWithIdentifier:(NSInteger)identifier
820+
webViewIdentifier:(NSInteger)webViewIdentifier
821+
navigationResponse:(FWFWKNavigationResponseData *)
822+
navigationResponse
823+
completion:
824+
(void (^)(
825+
FWFWKNavigationResponsePolicyEnumBox
826+
*_Nullable,
827+
FlutterError *_Nullable))completion;
781828
- (void)didFailNavigationForDelegateWithIdentifier:(NSInteger)identifier
782829
webViewIdentifier:(NSInteger)webViewIdentifier
783830
error:(FWFNSErrorData *)error

0 commit comments

Comments
 (0)