Forráskód Böngészése

Merge pull request #292 from hnvn/master

enable Javascript in iOS, support abort loading specific URLs
Rafal Wachol 6 éve
szülő
commit
e0b83740eb

+ 68 - 0
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 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
@@ -40,6 +61,35 @@ public class BrowserClient extends WebViewClient {
 
     }
 
+    @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);
@@ -48,4 +98,22 @@ public class BrowserClient extends WebViewClient {
         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();
+        }
+    }
 }

+ 4 - 0
android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java

@@ -97,6 +97,8 @@ public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.A
         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) {
@@ -120,6 +122,8 @@ public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.A
                 supportMultipleWindows,
                 appCacheEnabled,
                 allowFileURLs,
+                useWideViewPort,
+                invalidUrlRegex,
                 geolocationEnabled
         );
         result.success(null);

+ 8 - 3
android/src/main/java/com/flutter_webview_plugin/WebviewManager.java

@@ -2,7 +2,6 @@ package com.flutter_webview_plugin;
 
 import android.content.Intent;
 import android.net.Uri;
-import android.util.Log;
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.os.Build;
@@ -15,7 +14,6 @@ import android.webkit.ValueCallback;
 import android.webkit.WebChromeClient;
 import android.webkit.WebSettings;
 import android.webkit.WebView;
-import android.webkit.WebViewClient;
 import android.widget.FrameLayout;
 
 import java.util.HashMap;
@@ -75,13 +73,14 @@ class WebviewManager {
     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) {
@@ -206,6 +205,8 @@ class WebviewManager {
             boolean supportMultipleWindows,
             boolean appCacheEnabled,
             boolean allowFileURLs,
+            boolean useWideViewPort,
+            String invalidUrlRegex,
             boolean geolocationEnabled
     ) {
         webView.getSettings().setJavaScriptEnabled(withJavascript);
@@ -221,6 +222,10 @@ class WebviewManager {
         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() {

+ 1 - 0
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 {

+ 1 - 0
example/lib/main.dart

@@ -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)'),

+ 39 - 4
ios/Classes/FlutterWebviewPlugin.m

@@ -6,6 +6,7 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
 @interface FlutterWebviewPlugin() <WKNavigationDelegate, UIScrollViewDelegate, WKUIDelegate> {
     BOOL _enableAppScheme;
     BOOL _enableZoom;
+    NSString* _invalidUrlRegex;
 }
 @end
 
@@ -83,6 +84,8 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
     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];
@@ -112,9 +115,18 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
     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];
 }
@@ -235,18 +247,37 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
         }];
 }
 
+- (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];
     }
@@ -255,7 +286,11 @@ static NSString *const CHANNEL_NAME = @"flutter_webview_plugin";
         ([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);
     }

+ 12 - 2
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 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,
@@ -110,6 +113,8 @@ class FlutterWebviewPlugin {
     bool supportMultipleWindows,
     bool appCacheEnabled,
     bool allowFileURLs,
+    bool useWideViewPort,
+    String invalidUrlRegex,
     bool geolocationEnabled,
   }) async {
     final args = <String, dynamic>{
@@ -127,6 +132,8 @@ class FlutterWebviewPlugin {
       'supportMultipleWindows': supportMultipleWindows ?? false,
       'appCacheEnabled': appCacheEnabled ?? false,
       'allowFileURLs': allowFileURLs ?? false,
+      'useWideViewPort': useWideViewPort ?? false,
+      'invalidUrlRegex': invalidUrlRegex,
       'geolocationEnabled': geolocationEnabled ?? false,
     };
 
@@ -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']);
   }

+ 3 - 0
lib/src/webview_scaffold.dart

@@ -30,6 +30,7 @@ class WebviewScaffold extends StatefulWidget {
     this.hidden = false,
     this.initialChild,
     this.allowFileURLs,
+    this.invalidUrlRegex,
     this.geolocationEnabled
   }) : super(key: key);
 
@@ -53,6 +54,7 @@ class WebviewScaffold extends StatefulWidget {
   final bool hidden;
   final Widget initialChild;
   final bool allowFileURLs;
+  final String invalidUrlRegex;
   final bool geolocationEnabled;
 
   @override
@@ -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 {