From 2ccdf2e52c85567d5dd6ba958ad3cc23bdc7a723 Mon Sep 17 00:00:00 2001 From: hunghd Date: Tue, 8 Jan 2019 14:39:07 +0700 Subject: [PATCH 1/5] enable Javascript on iOS, support abort loading specific URL(s) by using regex --- .../flutter_webview_plugin/BrowserClient.java | 68 +++++++++++++++++++ .../FlutterWebviewPlugin.java | 4 +- .../WebviewManager.java | 10 +-- example/android/app/build.gradle | 1 + example/lib/main.dart | 1 + ios/Classes/FlutterWebviewPlugin.m | 39 ++++++++++- lib/src/base.dart | 12 +++- lib/src/webview_scaffold.dart | 3 + 8 files changed, 128 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/flutter_webview_plugin/BrowserClient.java b/android/src/main/java/com/flutter_webview_plugin/BrowserClient.java index 55583883..a052a12b 100644 --- a/android/src/main/java/com/flutter_webview_plugin/BrowserClient.java +++ b/android/src/main/java/com/flutter_webview_plugin/BrowserClient.java @@ -1,6 +1,8 @@ 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; @@ -8,14 +10,33 @@ 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 @@ -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 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 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); @@ -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 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(); + } + } } \ No newline at end of file diff --git a/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java b/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java index a2da4fda..d0c1186f 100644 --- a/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java +++ b/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java @@ -97,6 +97,7 @@ private void openUrl(MethodCall call, MethodChannel.Result result) { Map headers = call.argument("headers"); boolean scrollBar = call.argument("scrollBar"); boolean allowFileURLs = call.argument("allowFileURLs"); + String invalidUrlRegex = call.argument("invalidUrlRegex"); if (webViewManager == null || webViewManager.closed == true) { webViewManager = new WebviewManager(activity); @@ -118,7 +119,8 @@ private void openUrl(MethodCall call, MethodChannel.Result result) { scrollBar, supportMultipleWindows, appCacheEnabled, - allowFileURLs + allowFileURLs, + invalidUrlRegex ); result.success(null); } diff --git a/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java b/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java index 1dd8491f..b3b84ef5 100644 --- a/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java +++ b/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java @@ -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; @@ -14,7 +13,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; @@ -74,13 +72,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) { @@ -204,7 +203,8 @@ void openUrl( boolean scrollBar, boolean supportMultipleWindows, boolean appCacheEnabled, - boolean allowFileURLs + boolean allowFileURLs, + String invalidUrlRegex ) { webView.getSettings().setJavaScriptEnabled(withJavascript); webView.getSettings().setBuiltInZoomControls(withZoom); @@ -219,6 +219,8 @@ void openUrl( webView.getSettings().setAllowFileAccessFromFileURLs(allowFileURLs); webView.getSettings().setAllowUniversalAccessFromFileURLs(allowFileURLs); + webViewClient.updateInvalidUrlRegex(invalidUrlRegex); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); } diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 8d163b0d..3a963017 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -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 { diff --git a/example/lib/main.dart b/example/lib/main.dart index 91cc2dbd..28b5fffc 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -201,6 +201,7 @@ class _MyHomePageState extends State { selectedUrl, rect: Rect.fromLTWH(0.0, 0.0, MediaQuery.of(context).size.width, 300.0), userAgent: kAndroidUserAgent, + invalidUrlRegex: r'^(https).+(twitter)', ); }, child: const Text('Open Webview (rect)'), diff --git a/ios/Classes/FlutterWebviewPlugin.m b/ios/Classes/FlutterWebviewPlugin.m index 8fcb17b7..c1636f52 100644 --- a/ios/Classes/FlutterWebviewPlugin.m +++ b/ios/Classes/FlutterWebviewPlugin.m @@ -6,6 +6,7 @@ @interface FlutterWebviewPlugin() { BOOL _enableAppScheme; BOOL _enableZoom; + NSString* _invalidUrlRegex; } @end @@ -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]; @@ -111,6 +114,13 @@ - (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]; @@ -234,18 +244,37 @@ - (void)cleanCookies { }]; } +- (bool)checkInvalidUrl:(NSURL*)url { + NSString* urlString = url != nil ? [url absoluteString] : nil; + if (_invalidUrlRegex && urlString) { + 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]; } @@ -254,7 +283,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); } diff --git a/lib/src/base.dart b/lib/src/base.dart index 91101810..d917f2e3 100644 --- a/lib/src/base.dart +++ b/lib/src/base.dart @@ -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 @@ -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 @@ -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 web view is redirected to a specific URL, you want to intercept + /// this process by stopping loading this URL and replacing web view by another screen. Future launch(String url, { Map headers, bool withJavascript, @@ -110,6 +113,7 @@ class FlutterWebviewPlugin { bool supportMultipleWindows, bool appCacheEnabled, bool allowFileURLs, + String invalidUrlRegex, }) async { final args = { 'url': url, @@ -126,6 +130,7 @@ class FlutterWebviewPlugin { 'supportMultipleWindows': supportMultipleWindows ?? false, 'appCacheEnabled': appCacheEnabled ?? false, 'allowFileURLs': allowFileURLs ?? false, + 'invalidUrlRegex': invalidUrlRegex, }; if (headers != null) { @@ -233,6 +238,9 @@ class WebViewStateChanged { case 'finishLoad': t = WebViewState.finishLoad; break; + case 'abortLoad': + t = WebViewState.abortLoad; + break; } return WebViewStateChanged(t, map['url'], map['navigationType']); } diff --git a/lib/src/webview_scaffold.dart b/lib/src/webview_scaffold.dart index 8df7b05f..55859f44 100644 --- a/lib/src/webview_scaffold.dart +++ b/lib/src/webview_scaffold.dart @@ -30,6 +30,7 @@ class WebviewScaffold extends StatefulWidget { this.hidden = false, this.initialChild, this.allowFileURLs, + this.invalidUrlRegex, }) : super(key: key); final PreferredSizeWidget appBar; @@ -52,6 +53,7 @@ class WebviewScaffold extends StatefulWidget { final bool hidden; final Widget initialChild; final bool allowFileURLs; + final String invalidUrlRegex; @override _WebviewScaffoldState createState() => _WebviewScaffoldState(); @@ -115,6 +117,7 @@ class _WebviewScaffoldState extends State { supportMultipleWindows: widget.supportMultipleWindows, appCacheEnabled: widget.appCacheEnabled, allowFileURLs: widget.allowFileURLs, + invalidUrlRegex: widget.invalidUrlRegex, ); } else { if (_rect != value) { From 2c2360158792a721257e90860d6f4497b7c93071 Mon Sep 17 00:00:00 2001 From: Ha Duy Hung Date: Tue, 8 Jan 2019 23:10:58 +0700 Subject: [PATCH 2/5] update comment --- example/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 28b5fffc..f3ea78d3 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -201,7 +201,7 @@ class _MyHomePageState extends State { selectedUrl, rect: Rect.fromLTWH(0.0, 0.0, MediaQuery.of(context).size.width, 300.0), userAgent: kAndroidUserAgent, - invalidUrlRegex: r'^(https).+(twitter)', + invalidUrlRegex: r'^(https).+(twitter)', // prevent redirecting to twitter when user click on its icon in flutter website ); }, child: const Text('Open Webview (rect)'), From 1750c17b0be2087ba02b72e1fe018e362bea7949 Mon Sep 17 00:00:00 2001 From: hunghd Date: Thu, 10 Jan 2019 16:39:30 +0700 Subject: [PATCH 3/5] fix bug crashing on iOS when regex is null --- ios/Classes/FlutterWebviewPlugin.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Classes/FlutterWebviewPlugin.m b/ios/Classes/FlutterWebviewPlugin.m index c1636f52..8b1fd40f 100644 --- a/ios/Classes/FlutterWebviewPlugin.m +++ b/ios/Classes/FlutterWebviewPlugin.m @@ -246,7 +246,7 @@ - (void)cleanCookies { - (bool)checkInvalidUrl:(NSURL*)url { NSString* urlString = url != nil ? [url absoluteString] : nil; - if (_invalidUrlRegex && urlString) { + if (_invalidUrlRegex != [NSNull null] && urlString != nil) { NSError* error = NULL; NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:_invalidUrlRegex From b277e8a5127ae4217980c842430d9b96f08a9e0b Mon Sep 17 00:00:00 2001 From: hunghd Date: Thu, 10 Jan 2019 17:19:23 +0700 Subject: [PATCH 4/5] add support wide view port in Android --- .../java/com/flutter_webview_plugin/FlutterWebviewPlugin.java | 2 ++ .../main/java/com/flutter_webview_plugin/WebviewManager.java | 3 +++ lib/src/base.dart | 2 ++ 3 files changed, 7 insertions(+) diff --git a/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java b/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java index d0c1186f..f53a21fb 100644 --- a/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java +++ b/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java @@ -97,6 +97,7 @@ private void openUrl(MethodCall call, MethodChannel.Result result) { Map headers = call.argument("headers"); boolean scrollBar = call.argument("scrollBar"); boolean allowFileURLs = call.argument("allowFileURLs"); + boolean useWideViewPort = call.argument("useWideViewPort"); String invalidUrlRegex = call.argument("invalidUrlRegex"); if (webViewManager == null || webViewManager.closed == true) { @@ -120,6 +121,7 @@ private void openUrl(MethodCall call, MethodChannel.Result result) { supportMultipleWindows, appCacheEnabled, allowFileURLs, + useWideViewPort, invalidUrlRegex ); result.success(null); diff --git a/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java b/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java index b3b84ef5..318a7a55 100644 --- a/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java +++ b/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java @@ -204,6 +204,7 @@ void openUrl( boolean supportMultipleWindows, boolean appCacheEnabled, boolean allowFileURLs, + boolean useWideViewPort, String invalidUrlRegex ) { webView.getSettings().setJavaScriptEnabled(withJavascript); @@ -219,6 +220,8 @@ void openUrl( webView.getSettings().setAllowFileAccessFromFileURLs(allowFileURLs); webView.getSettings().setAllowUniversalAccessFromFileURLs(allowFileURLs); + webView.getSettings().setUseWideViewPort(useWideViewPort); + webViewClient.updateInvalidUrlRegex(invalidUrlRegex); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { diff --git a/lib/src/base.dart b/lib/src/base.dart index d917f2e3..9cd77dbd 100644 --- a/lib/src/base.dart +++ b/lib/src/base.dart @@ -113,6 +113,7 @@ class FlutterWebviewPlugin { bool supportMultipleWindows, bool appCacheEnabled, bool allowFileURLs, + bool useWideViewPort, String invalidUrlRegex, }) async { final args = { @@ -130,6 +131,7 @@ class FlutterWebviewPlugin { 'supportMultipleWindows': supportMultipleWindows ?? false, 'appCacheEnabled': appCacheEnabled ?? false, 'allowFileURLs': allowFileURLs ?? false, + 'useWideViewPort': useWideViewPort ?? false, 'invalidUrlRegex': invalidUrlRegex, }; From 4a2389a5f109d89c15dbc7067b0061b9cc035566 Mon Sep 17 00:00:00 2001 From: Ha Duy Hung Date: Sun, 24 Mar 2019 12:09:18 +0700 Subject: [PATCH 5/5] fix bugs WebView doesn't show in iOS (Add2App) --- ios/Classes/FlutterWebviewPlugin.m | 4 +++- lib/src/base.dart | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ios/Classes/FlutterWebviewPlugin.m b/ios/Classes/FlutterWebviewPlugin.m index d21f1932..6cbc7769 100644 --- a/ios/Classes/FlutterWebviewPlugin.m +++ b/ios/Classes/FlutterWebviewPlugin.m @@ -124,7 +124,9 @@ - (void)initWebview:(FlutterMethodCall*)call { _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]; } diff --git a/lib/src/base.dart b/lib/src/base.dart index 39999beb..fe18c7e7 100644 --- a/lib/src/base.dart +++ b/lib/src/base.dart @@ -95,8 +95,8 @@ class FlutterWebviewPlugin { /// - [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 web view is redirected to a specific URL, you want to intercept - /// this process by stopping loading this URL and replacing web view by another screen. + /// 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 launch(String url, { Map headers, bool withJavascript,