Skip to content

Commit

Permalink
Merge pull request #292 from hnvn/master
Browse files Browse the repository at this point in the history
enable Javascript in iOS, support abort loading specific URLs
  • Loading branch information
charafau authored Mar 28, 2019
2 parents 271e68b + e80cfd2 commit 8f1ad40
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 9 deletions.
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

0 comments on commit 8f1ad40

Please sign in to comment.