From 372b7712114fe9e9cad0f77f1e3a1ccbf7d0f09e Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Sun, 27 Oct 2019 04:35:05 +0100 Subject: [PATCH] updated webview options, updated content blockers --- .idea/workspace.xml | 349 +++++++++--------- .../ContentBlocker/ContentBlocker.java | 78 +++- .../ContentBlocker/ContentBlockerTrigger.java | 30 +- .../InAppWebView/InAppWebViewClient.java | 29 +- .../flutter_inappbrowser/Util.java | 16 +- example/assets/index.html | 7 +- example/lib/chrome_safari_example.screen.dart | 5 +- example/lib/inline_example.screen.dart | 4 +- example/lib/webview_example.screen.dart | 10 +- ios/Classes/FlutterWebViewController.swift | 37 +- .../InAppBrowserWebViewController.swift | 41 +- ios/Classes/InAppWebView.swift | 22 ++ lib/src/chrome_safari_browser.dart | 18 +- lib/src/content_blocker.dart | 61 ++- lib/src/in_app_browser.dart | 43 ++- lib/src/webview_options.dart | 28 +- 16 files changed, 543 insertions(+), 235 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index f2e059fd..b80552a6 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -15,31 +15,22 @@ - - - - - - - - - + + - - - + + - + + - - + + - - - + @@ -59,15 +50,6 @@ - - - - - - - - - @@ -80,8 +62,8 @@ - - + + @@ -89,20 +71,8 @@ - - - - - - - - - - - - - - + + @@ -110,8 +80,8 @@ - - + + @@ -119,11 +89,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -131,8 +125,8 @@ - - + + @@ -140,8 +134,8 @@ - - + + @@ -149,6 +143,18 @@ + + + + + + + + + + + + @@ -161,10 +167,6 @@ - test - WebResourceResponse - onCUstom - CustomSchemeResponse initialUrl InAppBrowser onCustomScheme @@ -187,10 +189,14 @@ _hand _ChannelManager onDo - options initialData assert Level + options + options = const {} + List<WebViewOptions> options = const [] + options) + WebViewOptions activity.getPreferences(0) @@ -233,7 +239,6 @@ @@ -275,7 +281,35 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -330,35 +365,6 @@ - - @@ -509,10 +515,11 @@ - + + - - + + @@ -522,7 +529,7 @@ - + @@ -530,7 +537,7 @@ - + @@ -558,13 +565,6 @@ - - - - - - - @@ -726,16 +726,6 @@ - - - - - - - - - - @@ -770,19 +760,7 @@ - - - - - - - - - - - - - + @@ -795,23 +773,6 @@ - - - - - - - - - - - - - - - - - @@ -819,10 +780,34 @@ - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -833,41 +818,34 @@ - + - - + + - - + + - + - - + + - + - - - - - - - - - + + @@ -876,17 +854,44 @@ - - + + - + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlocker.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlocker.java index 2007dac5..1f40e8dd 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlocker.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlocker.java @@ -11,8 +11,11 @@ import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebView; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.util.List; import java.util.Map; +import java.util.concurrent.CountDownLatch; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -22,10 +25,15 @@ import okhttp3.Response; public class ContentBlocker { protected static final String LOG_TAG = "ContentBlocker"; - public static WebResourceResponse checkUrl(final InAppWebView webView, String url, ContentBlockerTriggerResourceType responseResourceType) { + public static WebResourceResponse checkUrl(final InAppWebView webView, String url, ContentBlockerTriggerResourceType responseResourceType) throws URISyntaxException, InterruptedException { if (webView.options.contentBlockers == null) return null; + URI u = new URI(url); + String host = u.getHost(); + int port = u.getPort(); + String scheme = u.getScheme(); + for (Map> contentBlocker : webView.options.contentBlockers) { ContentBlockerTrigger trigger = ContentBlockerTrigger.fromMap(contentBlocker.get("trigger")); List resourceTypes = trigger.resourceType; @@ -36,13 +44,68 @@ public class ContentBlocker { Matcher m = mPattern.matcher(url); if (m.matches()) { - Log.d(LOG_TAG, url); - Log.d(LOG_TAG, responseResourceType.toString()); - Log.d(LOG_TAG, resourceTypes.toString()); - if (resourceTypes != null && resourceTypes.size() > 0 && !resourceTypes.contains(responseResourceType)) { + if (!resourceTypes.isEmpty() && !resourceTypes.contains(responseResourceType)) { return null; } + if (!trigger.ifDomain.isEmpty()) { + boolean matchFound = false; + for (String domain : trigger.ifDomain) { + if ((domain.startsWith("*") && host.endsWith(domain.replace("*", ""))) || domain.equals(host)) { + matchFound = true; + break; + } + } + if (!matchFound) + return null; + } + if (!trigger.unlessDomain.isEmpty()) { + for (String domain : trigger.unlessDomain) + if ((domain.startsWith("*") && host.endsWith(domain.replace("*", ""))) || domain.equals(host)) + return null; + } + + final String[] webViewUrl = new String[1]; + if (!trigger.loadType.isEmpty() || !trigger.ifTopUrl.isEmpty() || !trigger.unlessTopUrl.isEmpty()) { + final CountDownLatch latch = new CountDownLatch(1); + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(new Runnable() { + @Override + public void run() { + webViewUrl[0] = webView.getUrl(); + latch.countDown(); + } + }); + latch.await(); + } + + if (!trigger.loadType.isEmpty()) { + URI cUrl = new URI(webViewUrl[0]); + String cHost = cUrl.getHost(); + int cPort = cUrl.getPort(); + String cScheme = cUrl.getScheme(); + + if ( (trigger.loadType.contains("first-party") && cHost != null && !(cScheme.equals(scheme) && cHost.equals(host) && cPort == port)) || + (trigger.loadType.contains("third-party") && cHost != null && cHost.equals(host)) ) + return null; + } + if (!trigger.ifTopUrl.isEmpty()) { + boolean matchFound = false; + for (String topUrl : trigger.ifTopUrl) { + if (webViewUrl[0].equals(topUrl)) { + matchFound = true; + break; + } + } + if (!matchFound) + return null; + } + if (!trigger.unlessTopUrl.isEmpty()) { + for (String topUrl : trigger.unlessTopUrl) + if (webViewUrl[0].equals(topUrl)) + return null; + } + switch (action.type) { case BLOCK: @@ -103,12 +166,12 @@ public class ContentBlocker { return null; } - public static WebResourceResponse checkUrl(final InAppWebView webView, String url) { + public static WebResourceResponse checkUrl(final InAppWebView webView, String url) throws URISyntaxException, InterruptedException { ContentBlockerTriggerResourceType responseResourceType = getResourceTypeFromUrl(webView, url); return checkUrl(webView, url, responseResourceType); } - public static WebResourceResponse checkUrl(final InAppWebView webView, String url, String contentType) { + public static WebResourceResponse checkUrl(final InAppWebView webView, String url, String contentType) throws URISyntaxException, InterruptedException { ContentBlockerTriggerResourceType responseResourceType = getResourceTypeFromContentType(contentType); return checkUrl(webView, url, responseResourceType); } @@ -141,6 +204,7 @@ public class ContentBlocker { response.close(); } e.printStackTrace(); + Log.e(LOG_TAG, e.getMessage()); } } return responseResourceType; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerTrigger.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerTrigger.java index 99f22015..d1f94853 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerTrigger.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerTrigger.java @@ -7,21 +7,45 @@ import java.util.Map; public class ContentBlockerTrigger { public String urlFilter; - List resourceType = new ArrayList<>(); + public Boolean urlFilterIsCaseSensitive; + public List resourceType = new ArrayList<>(); + public List ifDomain = new ArrayList<>(); + public List unlessDomain = new ArrayList<>(); + public List loadType = new ArrayList<>(); + public List ifTopUrl = new ArrayList<>(); + public List unlessTopUrl = new ArrayList<>(); - public ContentBlockerTrigger(String urlFilter, List resourceType) { + public ContentBlockerTrigger(String urlFilter, Boolean urlFilterIsCaseSensitive, List resourceType, List ifDomain, + List unlessDomain, List loadType, List ifTopUrl, List unlessTopUrl) { this.urlFilter = urlFilter; this.resourceType = resourceType != null ? resourceType : this.resourceType; + this.urlFilterIsCaseSensitive = urlFilterIsCaseSensitive != null ? urlFilterIsCaseSensitive : false; + this.ifDomain = ifDomain != null ? ifDomain : this.ifDomain; + this.unlessDomain = unlessDomain != null ? unlessDomain : this.unlessDomain; + if ((!(this.ifDomain.isEmpty() || this.unlessDomain.isEmpty()) != false)) + throw new AssertionError(); + this.loadType = loadType != null ? loadType : this.loadType; + if ((this.loadType.size() > 2)) throw new AssertionError(); + this.ifTopUrl = ifTopUrl != null ? ifTopUrl : this.ifTopUrl; + this.unlessTopUrl = unlessTopUrl != null ? unlessTopUrl : this.unlessTopUrl; + if ((!(this.ifTopUrl.isEmpty() || this.unlessTopUrl.isEmpty()) != false)) + throw new AssertionError(); } public static ContentBlockerTrigger fromMap(Map map) { String urlFilter = (String) map.get("url-filter"); + Boolean urlFilterIsCaseSensitive = (Boolean) map.get("url-filter-is-case-sensitive"); List resourceTypeStringList = (List) map.get("resource-type"); List resourceType = new ArrayList<>(); for (String type : resourceTypeStringList) { resourceType.add(ContentBlockerTriggerResourceType.fromValue(type)); } - return new ContentBlockerTrigger(urlFilter, resourceType); + List ifDomain = (List) map.get("if-domain"); + List unlessDomain = (List) map.get("unless-domain"); + List loadType = (List) map.get("load-type"); + List ifTopUrl = (List) map.get("if-top-url"); + List unlessTopUrl = (List) map.get("unless-top-url"); + return new ContentBlockerTrigger(urlFilter, urlFilterIsCaseSensitive, resourceType, ifDomain, unlessDomain, loadType, ifTopUrl, unlessTopUrl); } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewClient.java index d40c6492..03589d13 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewClient.java @@ -27,6 +27,8 @@ import com.pichillilorenzo.flutter_inappbrowser.JavaScriptBridgeInterface; import com.pichillilorenzo.flutter_inappbrowser.Util; import java.io.ByteArrayInputStream; +import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; @@ -317,14 +319,27 @@ public class InAppWebViewClient extends WebViewClient { obj.put("url", url); obj.put("scheme", scheme); - Util.WaitFlutterResult flutterResult = Util.invokeMethodAndWait(getChannel(), "onLoadResourceCustomScheme", obj); + Util.WaitFlutterResult flutterResult; + try { + flutterResult = Util.invokeMethodAndWait(getChannel(), "onLoadResourceCustomScheme", obj); + } catch (InterruptedException e) { + e.printStackTrace(); + Log.e(LOG_TAG, e.getMessage()); + return null; + } if (flutterResult.error != null) { - Log.d(LOG_TAG, flutterResult.error); + Log.e(LOG_TAG, flutterResult.error); } else if (flutterResult.result != null) { Map res = (Map) flutterResult.result; - WebResourceResponse response = ContentBlocker.checkUrl(webView, url, res.get("content-type")); + WebResourceResponse response = null; + try { + response = ContentBlocker.checkUrl(webView, url, res.get("content-type")); + } catch (Exception e) { + e.printStackTrace(); + Log.e(LOG_TAG, e.getMessage()); + } if (response != null) return response; byte[] data = Base64.decode(res.get("base64data"), Base64.DEFAULT); @@ -332,7 +347,13 @@ public class InAppWebViewClient extends WebViewClient { } } - WebResourceResponse response = ContentBlocker.checkUrl(webView, url); + WebResourceResponse response = null; + try { + response = ContentBlocker.checkUrl(webView, url); + } catch (Exception e) { + e.printStackTrace(); + Log.e(LOG_TAG, e.getMessage()); + } return response; } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/Util.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/Util.java index b41992d4..4b74c85a 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/Util.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/Util.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CountDownLatch; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry; @@ -44,11 +45,12 @@ public class Util { return ANDROID_ASSET_URL + key; } - public static WaitFlutterResult invokeMethodAndWait(final MethodChannel channel, final String method, final Object arguments) { + public static WaitFlutterResult invokeMethodAndWait(final MethodChannel channel, final String method, final Object arguments) throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final Map flutterResultMap = new HashMap<>(); flutterResultMap.put("result", null); flutterResultMap.put("error", null); - flutterResultMap.put("isBlocking", true); Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @@ -58,27 +60,25 @@ public class Util { @Override public void success(Object result) { flutterResultMap.put("result", result); - flutterResultMap.put("isBlocking", false); + latch.countDown(); } @Override public void error(String s, String s1, Object o) { flutterResultMap.put("error", "ERROR: " + s + " " + s1); flutterResultMap.put("result", o); - flutterResultMap.put("isBlocking", false); + latch.countDown(); } @Override public void notImplemented() { - flutterResultMap.put("isBlocking", false); + latch.countDown(); } }); } }); - while((Boolean) flutterResultMap.get("isBlocking")) { - // block until flutter side returns - } + latch.await(); return new WaitFlutterResult(flutterResultMap.get("result"), (String) flutterResultMap.get("error")); } diff --git a/example/assets/index.html b/example/assets/index.html index fc349eed..45b9c555 100644 --- a/example/assets/index.html +++ b/example/assets/index.html @@ -26,8 +26,8 @@

Inline WebView

flutter logo

Cover is a one-page template for building simple and beautiful home pages. Download, edit the text, and add your own fullscreen background photo to make it your own.

-

- Learn more +

+ placeholder 100x50

@@ -54,6 +54,9 @@ console.log(JSON.stringify(result)); }); }); + $(document).ready(function() { + console.log("jQuery ready"); + }); \ No newline at end of file diff --git a/example/lib/chrome_safari_example.screen.dart b/example/lib/chrome_safari_example.screen.dart index 1a7255e9..15eeca46 100644 --- a/example/lib/chrome_safari_example.screen.dart +++ b/example/lib/chrome_safari_example.screen.dart @@ -38,7 +38,10 @@ class _ChromeSafariExampleScreenState extends State { return new Center( child: new RaisedButton( onPressed: () async { - await widget.browser.open("https://flutter.dev/"); + await widget.browser.open("https://flutter.dev/", options: [ + AndroidChromeCustomTabsOptions(addShareButton: false), + iOSChromeCustomTabsOptions(barCollapsingEnabled: true) + ]); }, child: Text("Open Chrome Safari Browser")), ); diff --git a/example/lib/inline_example.screen.dart b/example/lib/inline_example.screen.dart index 5e6211b0..ca0d5cc0 100644 --- a/example/lib/inline_example.screen.dart +++ b/example/lib/inline_example.screen.dart @@ -74,7 +74,9 @@ class _InlineExampleScreenState extends State { resourceCustomSchemes: ["my-special-custom-scheme"], contentBlockers: [ ContentBlocker( - ContentBlockerTrigger(".*", resourceType: [ContentBlockerTriggerResourceType.IMAGE]), + ContentBlockerTrigger(".*", + resourceType: [ContentBlockerTriggerResourceType.IMAGE, ContentBlockerTriggerResourceType.STYLE_SHEET], + ifTopUrl: ["https://getbootstrap.com/"]), ContentBlockerAction(ContentBlockerActionType.BLOCK) ) ] diff --git a/example/lib/webview_example.screen.dart b/example/lib/webview_example.screen.dart index 65beccbb..e4be9ce3 100644 --- a/example/lib/webview_example.screen.dart +++ b/example/lib/webview_example.screen.dart @@ -97,10 +97,12 @@ class _WebviewExampleScreenState extends State { onPressed: () { widget.browser.open( url: "https://www.google.com/", - options: { - "useShouldOverrideUrlLoading": true, - "useOnLoadResource": true, - } + options: [ + InAppWebViewOptions( + useShouldOverrideUrlLoading: true, + useOnLoadResource: true, + ) + ] ); }, child: Text("Open Webview Browser")), diff --git a/ios/Classes/FlutterWebViewController.swift b/ios/Classes/FlutterWebViewController.swift index c6315457..f15d971b 100755 --- a/ios/Classes/FlutterWebViewController.swift +++ b/ios/Classes/FlutterWebViewController.swift @@ -38,6 +38,39 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { webView!.options = options webView!.prepare() + if #available(iOS 11.0, *) { + if let contentBlockers = webView!.options?.contentBlockers { + do { + let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: []) + let blockRules = String(data: jsonData, encoding: String.Encoding.utf8) + WKContentRuleListStore.default().compileContentRuleList( + forIdentifier: "ContentBlockingRules", + encodedContentRuleList: blockRules) { (contentRuleList, error) in + + if let error = error { + print(error.localizedDescription) + return + } + + let configuration = self.webView!.configuration + configuration.userContentController.add(contentRuleList!) + + self.load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders) + } + return + } catch { + print(error.localizedDescription) + } + } + } + load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders) + } + + public func view() -> UIView { + return webView! + } + + public func load(initialUrl: String, initialFile: String?, initialData: [String: String]?, initialHeaders: [String: String]) { if initialFile != nil { do { try webView!.loadFile(url: initialFile!, headers: initialHeaders) @@ -60,10 +93,6 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { } } - public func view() -> UIView { - return webView! - } - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { let arguments = call.arguments as? NSDictionary switch call.method { diff --git a/ios/Classes/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowserWebViewController.swift index cb902db9..f5622ec2 100755 --- a/ios/Classes/InAppBrowserWebViewController.swift +++ b/ios/Classes/InAppBrowserWebViewController.swift @@ -119,19 +119,50 @@ class InAppBrowserWebViewController: UIViewController, UIScrollViewDelegate, WKU prepareConstraints() prepareWebView() - if self.initData == nil { - loadUrl(url: self.initURL!, headers: self.initHeaders) - } - else { - webView.loadData(data: initData!, mimeType: initMimeType!, encoding: initEncoding!, baseUrl: initBaseUrl!) + if #available(iOS 11.0, *) { + if let contentBlockers = webView!.options?.contentBlockers, contentBlockers.count > 0 { + do { + let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: []) + let blockRules = String(data: jsonData, encoding: String.Encoding.utf8) + WKContentRuleListStore.default().compileContentRuleList( + forIdentifier: "ContentBlockingRules", + encodedContentRuleList: blockRules) { (contentRuleList, error) in + + if let error = error { + print(error.localizedDescription) + return + } + + let configuration = self.webView!.configuration + configuration.userContentController.add(contentRuleList!) + + self.initLoad(initURL: self.initURL, initData: self.initData, initMimeType: self.initMimeType, initEncoding: self.initEncoding, initBaseUrl: self.initBaseUrl, initHeaders: self.initHeaders) + + self.navigationDelegate?.onBrowserCreated(uuid: self.uuid, webView: self.webView) + } + return + } catch { + print(error.localizedDescription) + } + } } + initLoad(initURL: initURL, initData: initData, initMimeType: initMimeType, initEncoding: initEncoding, initBaseUrl: initBaseUrl, initHeaders: initHeaders) + navigationDelegate?.onBrowserCreated(uuid: uuid, webView: webView) } viewPrepared = true super.viewWillAppear(animated) } + func initLoad(initURL: URL?, initData: String?, initMimeType: String?, initEncoding: String?, initBaseUrl: String?, initHeaders: [String: String]?) { + if self.initData == nil { + loadUrl(url: self.initURL!, headers: self.initHeaders) + } + else { + webView.loadData(data: initData!, mimeType: initMimeType!, encoding: initEncoding!, baseUrl: initBaseUrl!) + } + } override func viewDidLoad() { super.viewDidLoad() diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift index 81a0b0f9..42929421 100755 --- a/ios/Classes/InAppWebView.swift +++ b/ios/Classes/InAppWebView.swift @@ -418,6 +418,28 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi clearCache() } + if #available(iOS 11.0, *), newOptionsMap["contentBlockers"] != nil { + let contentBlockers = newOptions.contentBlockers + configuration.userContentController.removeAllContentRuleLists() + if contentBlockers.count > 0 { + do { + let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: []) + let blockRules = String(data: jsonData, encoding: String.Encoding.utf8) + WKContentRuleListStore.default().compileContentRuleList( + forIdentifier: "ContentBlockingRules", + encodedContentRuleList: blockRules) { (contentRuleList, error) in + if let error = error { + print(error.localizedDescription) + return + } + self.configuration.userContentController.add(contentRuleList!) + } + } catch { + print(error.localizedDescription) + } + } + } + self.options = newOptions } diff --git a/lib/src/chrome_safari_browser.dart b/lib/src/chrome_safari_browser.dart index f40f248f..9e93a5de 100644 --- a/lib/src/chrome_safari_browser.dart +++ b/lib/src/chrome_safari_browser.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/services.dart'; +import 'package:flutter_inappbrowser/src/webview_options.dart'; import 'types.dart'; import 'channel_manager.dart'; @@ -69,16 +70,27 @@ class ChromeSafariBrowser { ///- __preferredControlTintColor__: Set the custom color of the control buttons on the navigation bar and the toolbar. ///- __presentationStyle__: Set the custom modal presentation style when presenting the WebView. The default value is `0 //fullscreen`. See [UIModalPresentationStyle](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle) for all the available styles. ///- __transitionStyle__: Set to the custom transition style when presenting the WebView. The default value is `0 //crossDissolve`. See [UIModalTransitionStyle](https://developer.apple.com/documentation/uikit/uimodaltransitionStyle) for all the available styles. - Future open(String url, {Map options = const {}, Map headersFallback = const {}, Map optionsFallback = const {}}) async { + Future open(String url, {List options = const [], Map headersFallback = const {}, List optionsFallback = const []}) async { assert(url != null && url.isNotEmpty); this.throwIsAlreadyOpened(message: 'Cannot open $url!'); + + Map optionsMap = {}; + options.forEach((webViewOption) { + optionsMap.addAll(webViewOption.toMap()); + }); + + Map optionsFallbackMap = {}; + optionsFallback.forEach((webViewOption) { + optionsFallbackMap.addAll(webViewOption.toMap()); + }); + Map args = {}; args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuidFallback', () => (browserFallback != null) ? browserFallback.uuid : ''); args.putIfAbsent('url', () => url); args.putIfAbsent('headers', () => headersFallback); - args.putIfAbsent('options', () => options); - args.putIfAbsent('optionsFallback', () => optionsFallback); + args.putIfAbsent('options', () => optionsMap); + args.putIfAbsent('optionsFallback', () => optionsFallbackMap); args.putIfAbsent('isData', () => false); args.putIfAbsent('useChromeSafariBrowser', () => true); await ChannelManager.channel.invokeMethod('open', args); diff --git a/lib/src/content_blocker.dart b/lib/src/content_blocker.dart index 4413d79a..2e3c8908 100644 --- a/lib/src/content_blocker.dart +++ b/lib/src/content_blocker.dart @@ -27,22 +27,68 @@ class ContentBlockerTriggerResourceType { static const RAW = const ContentBlockerTriggerResourceType._internal('raw'); } +class ContentBlockerTriggerLoadType { + final String _value; + const ContentBlockerTriggerLoadType._internal(this._value); + toString() => _value; + + static const FIRST_PARTY = const ContentBlockerTriggerLoadType._internal('first-party'); + static const THIRD_PARTY = const ContentBlockerTriggerLoadType._internal('third-party'); +} + class ContentBlockerTrigger { String urlFilter; + bool urlFilterIsCaseSensitive; List resourceType; + List ifDomain; + List unlessDomain; + List loadType; + List ifTopUrl; + List unlessTopUrl; - ContentBlockerTrigger(this.urlFilter, {this.resourceType = const []}); + ContentBlockerTrigger(String urlFilter, {bool urlFilterIsCaseSensitive = false, List resourceType = const [], + List ifDomain = const [], List unlessDomain = const [], List loadType = const [], + List ifTopUrl = const [], List unlessTopUrl = const []}) { + this.urlFilter = urlFilter; + this.resourceType = resourceType; + this.urlFilterIsCaseSensitive = urlFilterIsCaseSensitive; + this.ifDomain = ifDomain; + this.unlessDomain = unlessDomain; + assert(!(this.ifDomain.isEmpty || this.unlessDomain.isEmpty) == false); + this.loadType = loadType; + assert(this.loadType.length <= 2); + this.ifTopUrl = ifTopUrl; + this.unlessTopUrl = unlessTopUrl; + assert(!(this.ifTopUrl.isEmpty || this.unlessTopUrl.isEmpty) == false); + } Map toMap() { List resourceTypeStringList = []; resourceType.forEach((type) { resourceTypeStringList.add(type.toString()); }); + List loadTypeStringList = []; + loadType.forEach((type) { + loadTypeStringList.add(type.toString()); + }); - return { + Map map = { "url-filter": urlFilter, - "resource-type": resourceTypeStringList + "url-filter-is-case-sensitive": urlFilterIsCaseSensitive, + "if-domain": ifDomain, + "unless-domain": unlessDomain, + "resource-type": resourceTypeStringList, + "load-type": loadTypeStringList, + "if-top-url": ifTopUrl, + "unless-top-url": unlessTopUrl }; + + map.keys + .where((key) => map[key] == null || (map[key] is List && (map[key] as List).length == 0)) // filter keys + .toList() // create a copy to avoid concurrent modifications + .forEach(map.remove); + + return map; } } @@ -69,9 +115,16 @@ class ContentBlockerAction { } Map toMap() { - return { + Map map = { "type": type.toString(), "selector": selector }; + + map.keys + .where((key) => map[key] == null || (map[key] is List && (map[key] as List).length == 0)) // filter keys + .toList() // create a copy to avoid concurrent modifications + .forEach(map.remove); + + return map; } } \ No newline at end of file diff --git a/lib/src/in_app_browser.dart b/lib/src/in_app_browser.dart index eeeee592..c90929cf 100644 --- a/lib/src/in_app_browser.dart +++ b/lib/src/in_app_browser.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:collection'; import 'package:flutter/services.dart'; +import 'package:flutter_inappbrowser/src/webview_options.dart'; import 'types.dart'; import 'channel_manager.dart'; @@ -101,14 +102,20 @@ class InAppBrowser { /// - __allowsInlineMediaPlayback__: Set to `true` to allow HTML5 media playback to appear inline within the screen layout, using browser-supplied controls rather than native controls. For this to work, add the `webkit-playsinline` attribute to any `