Skip to content

Commit e0b8374

Browse files
authored
Merge pull request #292 from hnvn/master
enable Javascript in iOS, support abort loading specific URLs
2 parents 8cdfa02 + 4a2389a commit e0b8374

File tree

8 files changed

+136
-9
lines changed

8 files changed

+136
-9
lines changed

android/src/main/java/com/flutter_webview_plugin/BrowserClient.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,42 @@
11
package com.flutter_webview_plugin;
22

3+
import android.annotation.TargetApi;
34
import android.graphics.Bitmap;
5+
import android.os.Build;
46
import android.webkit.WebResourceRequest;
57
import android.webkit.WebResourceResponse;
68
import android.webkit.WebView;
79
import android.webkit.WebViewClient;
810

911
import java.util.HashMap;
1012
import java.util.Map;
13+
import java.util.regex.Matcher;
14+
import java.util.regex.Pattern;
1115

1216
/**
1317
* Created by lejard_h on 20/12/2017.
1418
*/
1519

1620
public class BrowserClient extends WebViewClient {
21+
private Pattern invalidUrlPattern = null;
22+
1723
public BrowserClient() {
24+
this(null);
25+
}
26+
27+
public BrowserClient(String invalidUrlRegex) {
1828
super();
29+
if (invalidUrlRegex != null) {
30+
invalidUrlPattern = Pattern.compile(invalidUrlRegex);
31+
}
32+
}
33+
34+
public void updateInvalidUrlRegex(String invalidUrlRegex) {
35+
if (invalidUrlRegex != null) {
36+
invalidUrlPattern = Pattern.compile(invalidUrlRegex);
37+
} else {
38+
invalidUrlPattern = null;
39+
}
1940
}
2041

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

4162
}
4263

64+
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
65+
@Override
66+
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
67+
// returning true causes the current WebView to abort loading the URL,
68+
// while returning false causes the WebView to continue loading the URL as usual.
69+
String url = request.getUrl().toString();
70+
boolean isInvalid = checkInvalidUrl(url);
71+
Map<String, Object> data = new HashMap<>();
72+
data.put("url", url);
73+
data.put("type", isInvalid ? "abortLoad" : "shouldStart");
74+
75+
FlutterWebviewPlugin.channel.invokeMethod("onState", data);
76+
return isInvalid;
77+
}
78+
79+
@Override
80+
public boolean shouldOverrideUrlLoading(WebView view, String url) {
81+
// returning true causes the current WebView to abort loading the URL,
82+
// while returning false causes the WebView to continue loading the URL as usual.
83+
boolean isInvalid = checkInvalidUrl(url);
84+
Map<String, Object> data = new HashMap<>();
85+
data.put("url", url);
86+
data.put("type", isInvalid ? "abortLoad" : "shouldStart");
87+
88+
FlutterWebviewPlugin.channel.invokeMethod("onState", data);
89+
return isInvalid;
90+
}
91+
92+
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
4393
@Override
4494
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
4595
super.onReceivedHttpError(view, request, errorResponse);
@@ -48,4 +98,22 @@ public void onReceivedHttpError(WebView view, WebResourceRequest request, WebRes
4898
data.put("code", Integer.toString(errorResponse.getStatusCode()));
4999
FlutterWebviewPlugin.channel.invokeMethod("onHttpError", data);
50100
}
101+
102+
@Override
103+
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
104+
super.onReceivedError(view, errorCode, description, failingUrl);
105+
Map<String, Object> data = new HashMap<>();
106+
data.put("url", failingUrl);
107+
data.put("code", errorCode);
108+
FlutterWebviewPlugin.channel.invokeMethod("onHttpError", data);
109+
}
110+
111+
private boolean checkInvalidUrl(String url) {
112+
if (invalidUrlPattern == null) {
113+
return false;
114+
} else {
115+
Matcher matcher = invalidUrlPattern.matcher(url);
116+
return matcher.lookingAt();
117+
}
118+
}
51119
}

android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ private void openUrl(MethodCall call, MethodChannel.Result result) {
9797
Map<String, String> headers = call.argument("headers");
9898
boolean scrollBar = call.argument("scrollBar");
9999
boolean allowFileURLs = call.argument("allowFileURLs");
100+
boolean useWideViewPort = call.argument("useWideViewPort");
101+
String invalidUrlRegex = call.argument("invalidUrlRegex");
100102
boolean geolocationEnabled = call.argument("geolocationEnabled");
101103

102104
if (webViewManager == null || webViewManager.closed == true) {
@@ -120,6 +122,8 @@ private void openUrl(MethodCall call, MethodChannel.Result result) {
120122
supportMultipleWindows,
121123
appCacheEnabled,
122124
allowFileURLs,
125+
useWideViewPort,
126+
invalidUrlRegex,
123127
geolocationEnabled
124128
);
125129
result.success(null);

android/src/main/java/com/flutter_webview_plugin/WebviewManager.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import android.content.Intent;
44
import android.net.Uri;
5-
import android.util.Log;
65
import android.annotation.TargetApi;
76
import android.app.Activity;
87
import android.os.Build;
@@ -15,7 +14,6 @@
1514
import android.webkit.WebChromeClient;
1615
import android.webkit.WebSettings;
1716
import android.webkit.WebView;
18-
import android.webkit.WebViewClient;
1917
import android.widget.FrameLayout;
2018

2119
import java.util.HashMap;
@@ -75,13 +73,14 @@ public boolean handleResult(int requestCode, int resultCode, Intent intent){
7573
boolean closed = false;
7674
WebView webView;
7775
Activity activity;
76+
BrowserClient webViewClient;
7877
ResultHandler resultHandler;
7978

8079
WebviewManager(final Activity activity) {
8180
this.webView = new ObservableWebView(activity);
8281
this.activity = activity;
8382
this.resultHandler = new ResultHandler();
84-
WebViewClient webViewClient = new BrowserClient();
83+
webViewClient = new BrowserClient();
8584
webView.setOnKeyListener(new View.OnKeyListener() {
8685
@Override
8786
public boolean onKey(View v, int keyCode, KeyEvent event) {
@@ -206,6 +205,8 @@ void openUrl(
206205
boolean supportMultipleWindows,
207206
boolean appCacheEnabled,
208207
boolean allowFileURLs,
208+
boolean useWideViewPort,
209+
String invalidUrlRegex,
209210
boolean geolocationEnabled
210211
) {
211212
webView.getSettings().setJavaScriptEnabled(withJavascript);
@@ -221,6 +222,10 @@ void openUrl(
221222
webView.getSettings().setAllowFileAccessFromFileURLs(allowFileURLs);
222223
webView.getSettings().setAllowUniversalAccessFromFileURLs(allowFileURLs);
223224

225+
webView.getSettings().setUseWideViewPort(useWideViewPort);
226+
227+
webViewClient.updateInvalidUrlRegex(invalidUrlRegex);
228+
224229
if (geolocationEnabled) {
225230
webView.getSettings().setGeolocationEnabled(true);
226231
webView.setWebChromeClient(new WebChromeClient() {

example/android/app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ android {
2525
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
2626
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
2727
applicationId "com.yourcompany.flutter_webview_plugin_example"
28+
minSdkVersion 16
2829
}
2930

3031
buildTypes {

example/lib/main.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ class _MyHomePageState extends State<MyHomePage> {
201201
selectedUrl,
202202
rect: Rect.fromLTWH(0.0, 0.0, MediaQuery.of(context).size.width, 300.0),
203203
userAgent: kAndroidUserAgent,
204+
invalidUrlRegex: r'^(https).+(twitter)', // prevent redirecting to twitter when user click on its icon in flutter website
204205
);
205206
},
206207
child: const Text('Open Webview (rect)'),

ios/Classes/FlutterWebviewPlugin.m

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
@interface FlutterWebviewPlugin() <WKNavigationDelegate, UIScrollViewDelegate, WKUIDelegate> {
77
BOOL _enableAppScheme;
88
BOOL _enableZoom;
9+
NSString* _invalidUrlRegex;
910
}
1011
@end
1112

@@ -83,6 +84,8 @@ - (void)initWebview:(FlutterMethodCall*)call {
8384
NSString *userAgent = call.arguments[@"userAgent"];
8485
NSNumber *withZoom = call.arguments[@"withZoom"];
8586
NSNumber *scrollBar = call.arguments[@"scrollBar"];
87+
NSNumber *withJavascript = call.arguments[@"withJavascript"];
88+
_invalidUrlRegex = call.arguments[@"invalidUrlRegex"];
8689

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

118+
WKPreferences* preferences = [[self.webview configuration] preferences];
119+
if ([withJavascript boolValue]) {
120+
[preferences setJavaScriptEnabled:YES];
121+
} else {
122+
[preferences setJavaScriptEnabled:NO];
123+
}
124+
115125
_enableZoom = [withZoom boolValue];
116126

117-
[self.viewController.view addSubview:self.webview];
127+
UIViewController* presentedViewController = self.viewController.presentedViewController;
128+
UIViewController* currentViewController = presentedViewController != nil ? presentedViewController : self.viewController;
129+
[currentViewController.view addSubview:self.webview];
118130

119131
[self navigate:call];
120132
}
@@ -235,18 +247,37 @@ - (void)cleanCookies {
235247
}];
236248
}
237249

250+
- (bool)checkInvalidUrl:(NSURL*)url {
251+
NSString* urlString = url != nil ? [url absoluteString] : nil;
252+
if (_invalidUrlRegex != [NSNull null] && urlString != nil) {
253+
NSError* error = NULL;
254+
NSRegularExpression* regex =
255+
[NSRegularExpression regularExpressionWithPattern:_invalidUrlRegex
256+
options:NSRegularExpressionCaseInsensitive
257+
error:&error];
258+
NSTextCheckingResult* match = [regex firstMatchInString:urlString
259+
options:0
260+
range:NSMakeRange(0, [urlString length])];
261+
return match != nil;
262+
} else {
263+
return false;
264+
}
265+
}
266+
238267
#pragma mark -- WkWebView Delegate
239268
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
240269
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
241270

271+
BOOL isInvalid = [self checkInvalidUrl: navigationAction.request.URL];
272+
242273
id data = @{@"url": navigationAction.request.URL.absoluteString,
243-
@"type": @"shouldStart",
274+
@"type": isInvalid ? @"abortLoad" : @"shouldStart",
244275
@"navigationType": [NSNumber numberWithInt:navigationAction.navigationType]};
245276
[channel invokeMethod:@"onState" arguments:data];
246277

247278
if (navigationAction.navigationType == WKNavigationTypeBackForward) {
248279
[channel invokeMethod:@"onBackPressed" arguments:nil];
249-
} else {
280+
} else if (!isInvalid) {
250281
id data = @{@"url": navigationAction.request.URL.absoluteString};
251282
[channel invokeMethod:@"onUrlChanged" arguments:data];
252283
}
@@ -255,7 +286,11 @@ - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigati
255286
([webView.URL.scheme isEqualToString:@"http"] ||
256287
[webView.URL.scheme isEqualToString:@"https"] ||
257288
[webView.URL.scheme isEqualToString:@"about"])) {
258-
decisionHandler(WKNavigationActionPolicyAllow);
289+
if (isInvalid) {
290+
decisionHandler(WKNavigationActionPolicyCancel);
291+
} else {
292+
decisionHandler(WKNavigationActionPolicyAllow);
293+
}
259294
} else {
260295
decisionHandler(WKNavigationActionPolicyCancel);
261296
}

lib/src/base.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'package:flutter/services.dart';
77
const _kChannel = 'flutter_webview_plugin';
88

99
// TODO: more general state for iOS/android
10-
enum WebViewState { shouldStart, startLoad, finishLoad }
10+
enum WebViewState { shouldStart, startLoad, finishLoad, abortLoad }
1111

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

@@ -79,7 +79,6 @@ class FlutterWebviewPlugin {
7979
/// Start the Webview with [url]
8080
/// - [headers] specify additional HTTP headers
8181
/// - [withJavascript] enable Javascript or not for the Webview
82-
/// iOS WebView: Not implemented yet
8382
/// - [clearCache] clear the cache of the Webview
8483
/// - [clearCookies] clear all cookies of the Webview
8584
/// - [hidden] not show
@@ -94,6 +93,10 @@ class FlutterWebviewPlugin {
9493
/// - [withLocalUrl]: allow url as a local path
9594
/// Allow local files on iOs > 9.0
9695
/// - [scrollBar]: enable or disable scrollbar
96+
/// - [supportMultipleWindows] enable multiple windows support in Android
97+
/// - [invalidUrlRegex] is the regular expression of URLs that web view shouldn't load.
98+
/// For example, when webview is redirected to a specific URL, you want to intercept
99+
/// this process by stopping loading this URL and replacing webview by another screen.
97100
Future<Null> launch(String url, {
98101
Map<String, String> headers,
99102
bool withJavascript,
@@ -110,6 +113,8 @@ class FlutterWebviewPlugin {
110113
bool supportMultipleWindows,
111114
bool appCacheEnabled,
112115
bool allowFileURLs,
116+
bool useWideViewPort,
117+
String invalidUrlRegex,
113118
bool geolocationEnabled,
114119
}) async {
115120
final args = <String, dynamic>{
@@ -127,6 +132,8 @@ class FlutterWebviewPlugin {
127132
'supportMultipleWindows': supportMultipleWindows ?? false,
128133
'appCacheEnabled': appCacheEnabled ?? false,
129134
'allowFileURLs': allowFileURLs ?? false,
135+
'useWideViewPort': useWideViewPort ?? false,
136+
'invalidUrlRegex': invalidUrlRegex,
130137
'geolocationEnabled': geolocationEnabled ?? false,
131138
};
132139

@@ -235,6 +242,9 @@ class WebViewStateChanged {
235242
case 'finishLoad':
236243
t = WebViewState.finishLoad;
237244
break;
245+
case 'abortLoad':
246+
t = WebViewState.abortLoad;
247+
break;
238248
}
239249
return WebViewStateChanged(t, map['url'], map['navigationType']);
240250
}

lib/src/webview_scaffold.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class WebviewScaffold extends StatefulWidget {
3030
this.hidden = false,
3131
this.initialChild,
3232
this.allowFileURLs,
33+
this.invalidUrlRegex,
3334
this.geolocationEnabled
3435
}) : super(key: key);
3536

@@ -53,6 +54,7 @@ class WebviewScaffold extends StatefulWidget {
5354
final bool hidden;
5455
final Widget initialChild;
5556
final bool allowFileURLs;
57+
final String invalidUrlRegex;
5658
final bool geolocationEnabled;
5759

5860
@override
@@ -127,6 +129,7 @@ class _WebviewScaffoldState extends State<WebviewScaffold> {
127129
supportMultipleWindows: widget.supportMultipleWindows,
128130
appCacheEnabled: widget.appCacheEnabled,
129131
allowFileURLs: widget.allowFileURLs,
132+
invalidUrlRegex: widget.invalidUrlRegex,
130133
geolocationEnabled: widget.geolocationEnabled
131134
);
132135
} else {

0 commit comments

Comments
 (0)