Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
import android.os.Build;
import android.util.Log;
import android.view.KeyEvent;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.webkit.WebResourceErrorCompat;
import androidx.webkit.WebViewClientCompat;
import io.flutter.plugin.common.MethodChannel;
import java.util.HashMap;
Expand Down Expand Up @@ -92,6 +96,14 @@ private void notifyOnNavigationRequest(
}
}

private void onReceiveError(WebView view, int code, String description, String url) {
Map<String, Object> args = new HashMap<>();
args.put("url", url);
args.put("code", code);
args.put("description", description);
methodChannel.invokeMethod("onPageReceiveError", args);
}

// This method attempts to avoid using WebViewClientCompat due to bug
// https://bugs.chromium.org/p/chromium/issues/detail?id=925887. Also, see
// https://github.com/flutter/flutter/issues/29446.
Expand Down Expand Up @@ -129,6 +141,32 @@ public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
// handled even though they were handled. We don't want to propagate those as they're not
// truly lost.
}

@TargetApi(Build.VERSION_CODES.M)
@Override
public void onReceivedError(
WebView view, WebResourceRequest request, WebResourceError error) {
FlutterWebViewClient.this.onReceiveError(
view,
error.getErrorCode(),
error.getDescription().toString(),
request.getUrl().toString());
}

@TargetApi(Build.VERSION_CODES.M)
@Override
public void onReceivedHttpError(
WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
FlutterWebViewClient.this.onReceiveError(
view, errorResponse.getStatusCode(), null, request.getUrl().toString());
}

@SuppressWarnings("deprecation")
@Override
public void onReceivedError(
WebView view, int errorCode, String description, String failingUrl) {
FlutterWebViewClient.this.onReceiveError(view, errorCode, description, failingUrl);
}
};
}

Expand Down Expand Up @@ -160,6 +198,37 @@ public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
// handled even though they were handled. We don't want to propagate those as they're not
// truly lost.
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onReceivedHttpError(
@NonNull WebView view,
@NonNull WebResourceRequest request,
@NonNull WebResourceResponse errorResponse) {
FlutterWebViewClient.this.onReceiveError(
view, errorResponse.getStatusCode(), null, request.getUrl().toString());
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onReceivedError(
@NonNull WebView view,
@NonNull WebResourceRequest request,
@NonNull WebResourceErrorCompat error) {
//TODO: is really need to check WebViewFeature.isFeatureSupported() and api version.
FlutterWebViewClient.this.onReceiveError(
view,
error.getErrorCode(),
error.getDescription().toString(),
request.getUrl().toString());
}

@SuppressWarnings("deprecation")
@Override
public void onReceivedError(
WebView view, int errorCode, String description, String failingUrl) {
FlutterWebViewClient.this.onReceiveError(view, errorCode, description, failingUrl);
}
};
}

Expand Down
9 changes: 8 additions & 1 deletion packages/webview_flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class _WebViewExampleState extends State<WebViewExample> {
// to allow calling Scaffold.of(context) so we can show a snackbar.
body: Builder(builder: (BuildContext context) {
return WebView(
initialUrl: 'https://flutter.dev',
initialUrl: 'https://google.com',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
Expand All @@ -74,6 +74,13 @@ class _WebViewExampleState extends State<WebViewExample> {
onPageFinished: (String url) {
print('Page finished loading: $url');
},
onPageReceiveError: (String url, int code, String message) {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
'Code: $code, Url: $url, Message: $message',
),
));
},
gestureNavigationEnabled: true,
);
}),
Expand Down
66 changes: 56 additions & 10 deletions packages/webview_flutter/ios/Classes/FLTWKNavigationDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
#import "FLTWKNavigationDelegate.h"

@implementation FLTWKNavigationDelegate {
FlutterMethodChannel* _methodChannel;
FlutterMethodChannel *_methodChannel;
}

- (instancetype)initWithChannel:(FlutterMethodChannel*)channel {
- (instancetype)initWithChannel:(FlutterMethodChannel *)channel {
self = [super init];
if (self) {
_methodChannel = channel;
Expand All @@ -18,26 +18,27 @@ - (instancetype)initWithChannel:(FlutterMethodChannel*)channel {

#pragma mark - WKNavigationDelegate conformance

- (void)webView:(WKWebView*)webView didStartProvisionalNavigation:(WKNavigation*)navigation {
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
[_methodChannel invokeMethod:@"onPageStarted" arguments:@{@"url" : webView.URL.absoluteString}];
}

- (void)webView:(WKWebView*)webView
decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (!self.hasDartNavigationDelegate) {
decisionHandler(WKNavigationActionPolicyAllow);
return;
}
NSDictionary* arguments = @{
NSDictionary *arguments = @{
@"url" : navigationAction.request.URL.absoluteString,
@"isForMainFrame" : @(navigationAction.targetFrame.isMainFrame)
};
[_methodChannel invokeMethod:@"navigationRequest"
arguments:arguments
result:^(id _Nullable result) {
if ([result isKindOfClass:[FlutterError class]]) {
NSLog(@"navigationRequest has unexpectedly completed with an error, "
NSLog(@"navigationRequest has unexpectedly completed with an "
@"error, "
@"allowing navigation.");
decisionHandler(WKNavigationActionPolicyAllow);
return;
Expand All @@ -50,19 +51,64 @@ - (void)webView:(WKWebView*)webView
return;
}
if (![result isKindOfClass:[NSNumber class]]) {
NSLog(@"navigationRequest unexpectedly returned a non boolean value: "
NSLog(@"navigationRequest unexpectedly returned a non boolean "
@"value: "
@"%@, allowing navigation.",
result);
decisionHandler(WKNavigationActionPolicyAllow);
return;
}
NSNumber* typedResult = result;
NSNumber *typedResult = result;
decisionHandler([typedResult boolValue] ? WKNavigationActionPolicyAllow
: WKNavigationActionPolicyCancel);
}];
}

- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation {
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[_methodChannel invokeMethod:@"onPageFinished" arguments:@{@"url" : webView.URL.absoluteString}];
}

- (void)webView:(WKWebView *)webView
didFailNavigation:(WKNavigation *)navigation
withError:(NSError *)error {
NSDictionary *arguments = @{
@"url" : webView.URL.absoluteString ?: [NSNull null],
@"code" : [NSNumber numberWithLong:error.code],
@"description" : [error localizedDescription],
};

[_methodChannel invokeMethod:@"onPageReceiveError" arguments:arguments];
}

- (void)webView:(WKWebView *)webView
didFailProvisionalNavigation:(WKNavigation *)navigation
withError:(NSError *)error {
NSDictionary *arguments = @{
@"url" : error.userInfo[NSURLErrorFailingURLStringErrorKey],
@"code" : [NSNumber numberWithLong:error.code],
@"description" : [error localizedDescription],
};

[_methodChannel invokeMethod:@"onPageReceiveError" arguments:arguments];
}

- (void)webView:(WKWebView *)webView
decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
if (response.statusCode >= 400 && response.statusCode < 600) {
NSDictionary *arguments = @{
@"url" : response.URL.absoluteString ?: [NSNull null],
@"code" : [NSNumber numberWithLong:response.statusCode],
@"description" : [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode],
};

[_methodChannel invokeMethod:@"onPageReceiveError" arguments:arguments];
}
}

decisionHandler(WKNavigationResponsePolicyAllow);
}

@end
3 changes: 3 additions & 0 deletions packages/webview_flutter/lib/platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ abstract class WebViewPlatformCallbacksHandler {

/// Invoked by [WebViewPlatformController] when a page has finished loading.
void onPageFinished(String url);

/// Invoked by [WebViewPlatformController] when platform's webview return error.
void onPageReceiveError({String url, int code, String description});
}

/// Interface for talking to the webview's platform implementation.
Expand Down
6 changes: 6 additions & 0 deletions packages/webview_flutter/lib/src/webview_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
case 'onPageStarted':
_platformCallbacksHandler.onPageStarted(call.arguments['url']);
return null;
case 'onPageReceiveError':
_platformCallbacksHandler.onPageReceiveError(
url: call.arguments['url'],
code: call.arguments['code'],
description: call.arguments['description']);
return null;
}
throw MissingPluginException(
'${call.method} was invoked but has no handler');
Expand Down
20 changes: 18 additions & 2 deletions packages/webview_flutter/lib/webview_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ typedef void PageStartedCallback(String url);
/// Signature for when a [WebView] has finished loading a page.
typedef void PageFinishedCallback(String url);

/// Signature for when a [WebView] receive a error.
/// Code may be NSURLErrorDomain code or const from Android WebViewClient or http status code.
/// Description is optional
typedef void PageReceiveErrorCallback(String url, int code, String description);

final RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9_]*\$');

/// Specifies possible restrictions on automatic media playback.
///
/// This is typically used in [WebView.initialMediaPlaybackPolicy].
Expand All @@ -98,8 +105,6 @@ enum AutoMediaPlaybackPolicy {
always_allow,
}

final RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9_]*\$');

/// A named channel for receiving messaged from JavaScript code running inside a web view.
class JavascriptChannel {
/// Constructs a Javascript channel.
Expand Down Expand Up @@ -152,6 +157,7 @@ class WebView extends StatefulWidget {
this.userAgent,
this.initialMediaPlaybackPolicy =
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
this.onPageReceiveError,
}) : assert(javascriptMode != null),
assert(initialMediaPlaybackPolicy != null),
super(key: key);
Expand Down Expand Up @@ -290,6 +296,9 @@ class WebView extends StatefulWidget {
/// By default `debuggingEnabled` is false.
final bool debuggingEnabled;

/// Invoked when a webview return error.
final PageReceiveErrorCallback onPageReceiveError;

/// The value used for the HTTP User-Agent: request header.
/// A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations.
///
Expand Down Expand Up @@ -482,6 +491,13 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler {
}
}

@override
void onPageReceiveError({String url, int code, String description}) {
if (_widget.onPageReceiveError != null) {
_widget.onPageReceiveError(url, code, description);
}
}

void _updateJavascriptChannelsFromSet(Set<JavascriptChannel> channels) {
_javascriptChannels.clear();
if (channels == null) {
Expand Down
Loading