Skip to content

enable Javascript in iOS, support abort loading specific URLs #292

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 28, 2019
Merged
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
@@ -1,21 +1,42 @@
package com.flutter_webview_plugin;

import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.os.Build;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Created by lejard_h on 20/12/2017.
*/

public class BrowserClient extends WebViewClient {
private Pattern invalidUrlPattern = null;

public BrowserClient() {
this(null);
}

public BrowserClient(String invalidUrlRegex) {
super();
if (invalidUrlRegex != null) {
invalidUrlPattern = Pattern.compile(invalidUrlRegex);
}
}

public void updateInvalidUrlRegex(String invalidUrlRegex) {
if (invalidUrlRegex != null) {
invalidUrlPattern = Pattern.compile(invalidUrlRegex);
} else {
invalidUrlPattern = null;
}
}

@Override
Expand All @@ -40,6 +61,35 @@ public void onPageFinished(WebView view, String url) {

}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
// returning true causes the current WebView to abort loading the URL,
// while returning false causes the WebView to continue loading the URL as usual.
String url = request.getUrl().toString();
boolean isInvalid = checkInvalidUrl(url);
Map<String, Object> data = new HashMap<>();
data.put("url", url);
data.put("type", isInvalid ? "abortLoad" : "shouldStart");

FlutterWebviewPlugin.channel.invokeMethod("onState", data);
return isInvalid;
}

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// returning true causes the current WebView to abort loading the URL,
// while returning false causes the WebView to continue loading the URL as usual.
boolean isInvalid = checkInvalidUrl(url);
Map<String, Object> data = new HashMap<>();
data.put("url", url);
data.put("type", isInvalid ? "abortLoad" : "shouldStart");

FlutterWebviewPlugin.channel.invokeMethod("onState", data);
return isInvalid;
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
super.onReceivedHttpError(view, request, errorResponse);
Expand All @@ -48,4 +98,22 @@ public void onReceivedHttpError(WebView view, WebResourceRequest request, WebRes
data.put("code", Integer.toString(errorResponse.getStatusCode()));
FlutterWebviewPlugin.channel.invokeMethod("onHttpError", data);
}

@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
Map<String, Object> data = new HashMap<>();
data.put("url", failingUrl);
data.put("code", errorCode);
FlutterWebviewPlugin.channel.invokeMethod("onHttpError", data);
}

private boolean checkInvalidUrl(String url) {
if (invalidUrlPattern == null) {
return false;
} else {
Matcher matcher = invalidUrlPattern.matcher(url);
return matcher.lookingAt();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ private void openUrl(MethodCall call, MethodChannel.Result result) {
Map<String, String> headers = call.argument("headers");
boolean scrollBar = call.argument("scrollBar");
boolean allowFileURLs = call.argument("allowFileURLs");
boolean useWideViewPort = call.argument("useWideViewPort");
String invalidUrlRegex = call.argument("invalidUrlRegex");
boolean geolocationEnabled = call.argument("geolocationEnabled");

if (webViewManager == null || webViewManager.closed == true) {
Expand All @@ -120,6 +122,8 @@ private void openUrl(MethodCall call, MethodChannel.Result result) {
supportMultipleWindows,
appCacheEnabled,
allowFileURLs,
useWideViewPort,
invalidUrlRegex,
geolocationEnabled
);
result.success(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
Expand All @@ -15,7 +14,6 @@
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;

import java.util.HashMap;
Expand Down Expand Up @@ -75,13 +73,14 @@ public boolean handleResult(int requestCode, int resultCode, Intent intent){
boolean closed = false;
WebView webView;
Activity activity;
BrowserClient webViewClient;
ResultHandler resultHandler;

WebviewManager(final Activity activity) {
this.webView = new ObservableWebView(activity);
this.activity = activity;
this.resultHandler = new ResultHandler();
WebViewClient webViewClient = new BrowserClient();
webViewClient = new BrowserClient();
webView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Expand Down Expand Up @@ -206,6 +205,8 @@ void openUrl(
boolean supportMultipleWindows,
boolean appCacheEnabled,
boolean allowFileURLs,
boolean useWideViewPort,
String invalidUrlRegex,
boolean geolocationEnabled
) {
webView.getSettings().setJavaScriptEnabled(withJavascript);
Expand All @@ -221,6 +222,10 @@ void openUrl(
webView.getSettings().setAllowFileAccessFromFileURLs(allowFileURLs);
webView.getSettings().setAllowUniversalAccessFromFileURLs(allowFileURLs);

webView.getSettings().setUseWideViewPort(useWideViewPort);

webViewClient.updateInvalidUrlRegex(invalidUrlRegex);

if (geolocationEnabled) {
webView.getSettings().setGeolocationEnabled(true);
webView.setWebChromeClient(new WebChromeClient() {
Expand Down
1 change: 1 addition & 0 deletions example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ android {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.yourcompany.flutter_webview_plugin_example"
minSdkVersion 16
}

buildTypes {
Expand Down
1 change: 1 addition & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class _MyHomePageState extends State<MyHomePage> {
selectedUrl,
rect: Rect.fromLTWH(0.0, 0.0, MediaQuery.of(context).size.width, 300.0),
userAgent: kAndroidUserAgent,
invalidUrlRegex: r'^(https).+(twitter)', // prevent redirecting to twitter when user click on its icon in flutter website
);
},
child: const Text('Open Webview (rect)'),
Expand Down
43 changes: 39 additions & 4 deletions ios/Classes/FlutterWebviewPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@interface FlutterWebviewPlugin() <WKNavigationDelegate, UIScrollViewDelegate, WKUIDelegate> {
BOOL _enableAppScheme;
BOOL _enableZoom;
NSString* _invalidUrlRegex;
}
@end

Expand Down Expand Up @@ -83,6 +84,8 @@ - (void)initWebview:(FlutterMethodCall*)call {
NSString *userAgent = call.arguments[@"userAgent"];
NSNumber *withZoom = call.arguments[@"withZoom"];
NSNumber *scrollBar = call.arguments[@"scrollBar"];
NSNumber *withJavascript = call.arguments[@"withJavascript"];
_invalidUrlRegex = call.arguments[@"invalidUrlRegex"];

if (clearCache != (id)[NSNull null] && [clearCache boolValue]) {
[[NSURLCache sharedURLCache] removeAllCachedResponses];
Expand Down Expand Up @@ -112,9 +115,18 @@ - (void)initWebview:(FlutterMethodCall*)call {
self.webview.scrollView.showsHorizontalScrollIndicator = [scrollBar boolValue];
self.webview.scrollView.showsVerticalScrollIndicator = [scrollBar boolValue];

WKPreferences* preferences = [[self.webview configuration] preferences];
if ([withJavascript boolValue]) {
[preferences setJavaScriptEnabled:YES];
} else {
[preferences setJavaScriptEnabled:NO];
}

_enableZoom = [withZoom boolValue];

[self.viewController.view addSubview:self.webview];
UIViewController* presentedViewController = self.viewController.presentedViewController;
UIViewController* currentViewController = presentedViewController != nil ? presentedViewController : self.viewController;
[currentViewController.view addSubview:self.webview];

[self navigate:call];
}
Expand Down Expand Up @@ -235,18 +247,37 @@ - (void)cleanCookies {
}];
}

- (bool)checkInvalidUrl:(NSURL*)url {
NSString* urlString = url != nil ? [url absoluteString] : nil;
if (_invalidUrlRegex != [NSNull null] && urlString != nil) {
NSError* error = NULL;
NSRegularExpression* regex =
[NSRegularExpression regularExpressionWithPattern:_invalidUrlRegex
options:NSRegularExpressionCaseInsensitive
error:&error];
NSTextCheckingResult* match = [regex firstMatchInString:urlString
options:0
range:NSMakeRange(0, [urlString length])];
return match != nil;
} else {
return false;
}
}

#pragma mark -- WkWebView Delegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {

BOOL isInvalid = [self checkInvalidUrl: navigationAction.request.URL];

id data = @{@"url": navigationAction.request.URL.absoluteString,
@"type": @"shouldStart",
@"type": isInvalid ? @"abortLoad" : @"shouldStart",
@"navigationType": [NSNumber numberWithInt:navigationAction.navigationType]};
[channel invokeMethod:@"onState" arguments:data];

if (navigationAction.navigationType == WKNavigationTypeBackForward) {
[channel invokeMethod:@"onBackPressed" arguments:nil];
} else {
} else if (!isInvalid) {
id data = @{@"url": navigationAction.request.URL.absoluteString};
[channel invokeMethod:@"onUrlChanged" arguments:data];
}
Expand All @@ -255,7 +286,11 @@ - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigati
([webView.URL.scheme isEqualToString:@"http"] ||
[webView.URL.scheme isEqualToString:@"https"] ||
[webView.URL.scheme isEqualToString:@"about"])) {
decisionHandler(WKNavigationActionPolicyAllow);
if (isInvalid) {
decisionHandler(WKNavigationActionPolicyCancel);
} else {
decisionHandler(WKNavigationActionPolicyAllow);
}
} else {
decisionHandler(WKNavigationActionPolicyCancel);
}
Expand Down
14 changes: 12 additions & 2 deletions lib/src/base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'package:flutter/services.dart';
const _kChannel = 'flutter_webview_plugin';

// TODO: more general state for iOS/android
enum WebViewState { shouldStart, startLoad, finishLoad }
enum WebViewState { shouldStart, startLoad, finishLoad, abortLoad }

// TODO: use an id by webview to be able to manage multiple webview

Expand Down Expand Up @@ -79,7 +79,6 @@ class FlutterWebviewPlugin {
/// Start the Webview with [url]
/// - [headers] specify additional HTTP headers
/// - [withJavascript] enable Javascript or not for the Webview
/// iOS WebView: Not implemented yet
/// - [clearCache] clear the cache of the Webview
/// - [clearCookies] clear all cookies of the Webview
/// - [hidden] not show
Expand All @@ -94,6 +93,10 @@ class FlutterWebviewPlugin {
/// - [withLocalUrl]: allow url as a local path
/// Allow local files on iOs > 9.0
/// - [scrollBar]: enable or disable scrollbar
/// - [supportMultipleWindows] enable multiple windows support in Android
/// - [invalidUrlRegex] is the regular expression of URLs that web view shouldn't load.
/// For example, when webview is redirected to a specific URL, you want to intercept
/// this process by stopping loading this URL and replacing webview by another screen.
Future<Null> launch(String url, {
Map<String, String> headers,
bool withJavascript,
Expand All @@ -110,6 +113,8 @@ class FlutterWebviewPlugin {
bool supportMultipleWindows,
bool appCacheEnabled,
bool allowFileURLs,
bool useWideViewPort,
String invalidUrlRegex,
bool geolocationEnabled,
}) async {
final args = <String, dynamic>{
Expand All @@ -127,6 +132,8 @@ class FlutterWebviewPlugin {
'supportMultipleWindows': supportMultipleWindows ?? false,
'appCacheEnabled': appCacheEnabled ?? false,
'allowFileURLs': allowFileURLs ?? false,
'useWideViewPort': useWideViewPort ?? false,
'invalidUrlRegex': invalidUrlRegex,
'geolocationEnabled': geolocationEnabled ?? false,
};

Expand Down Expand Up @@ -235,6 +242,9 @@ class WebViewStateChanged {
case 'finishLoad':
t = WebViewState.finishLoad;
break;
case 'abortLoad':
t = WebViewState.abortLoad;
break;
}
return WebViewStateChanged(t, map['url'], map['navigationType']);
}
Expand Down
3 changes: 3 additions & 0 deletions lib/src/webview_scaffold.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class WebviewScaffold extends StatefulWidget {
this.hidden = false,
this.initialChild,
this.allowFileURLs,
this.invalidUrlRegex,
this.geolocationEnabled
}) : super(key: key);

Expand All @@ -53,6 +54,7 @@ class WebviewScaffold extends StatefulWidget {
final bool hidden;
final Widget initialChild;
final bool allowFileURLs;
final String invalidUrlRegex;
final bool geolocationEnabled;

@override
Expand Down Expand Up @@ -127,6 +129,7 @@ class _WebviewScaffoldState extends State<WebviewScaffold> {
supportMultipleWindows: widget.supportMultipleWindows,
appCacheEnabled: widget.appCacheEnabled,
allowFileURLs: widget.allowFileURLs,
invalidUrlRegex: widget.invalidUrlRegex,
geolocationEnabled: widget.geolocationEnabled
);
} else {
Expand Down