Commit fead4fb2 authored by 1/2's avatar 1/2 Committed by GitHub

Merge pull request #1 from pichillilorenzo/master

upgrade origin
parents 971ac889 f89610ae
This diff is collapsed.
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
- Merge "Fix abstract method error && swift version error" [#155](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/155) (thanks to [AlexVincent525](https://github.com/AlexVincent525)) - Merge "Fix abstract method error && swift version error" [#155](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/155) (thanks to [AlexVincent525](https://github.com/AlexVincent525))
- Merge "migrating to swift 5.0" [#162](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/162) (thanks to [fattiger00](https://github.com/fattiger00)) - Merge "migrating to swift 5.0" [#162](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/162) (thanks to [fattiger00](https://github.com/fattiger00))
- Merge "Update readme example" [#178](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/178) (thanks to [SebastienBtr](https://github.com/SebastienBtr)) - Merge "Update readme example" [#178](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/178) (thanks to [SebastienBtr](https://github.com/SebastienBtr))
- Merge "handle choose file callback in android" [#183](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/183) (thanks to [crazecoder](https://github.com/crazecoder))
- Merge "add initialScale in android" [#186](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/186) (thanks to [crazecoder](https://github.com/crazecoder))
- Added `horizontalScrollBarEnabled` and `verticalScrollBarEnabled` options to enable/disable the corresponding scrollbar of the WebView [#165](https://github.com/pichillilorenzo/flutter_inappbrowser/issues/165) - Added `horizontalScrollBarEnabled` and `verticalScrollBarEnabled` options to enable/disable the corresponding scrollbar of the WebView [#165](https://github.com/pichillilorenzo/flutter_inappbrowser/issues/165)
- Added `onDownloadStart` event and `useOnDownloadStart` option: event fires when the WebView recognizes and starts a downloadable file. - Added `onDownloadStart` event and `useOnDownloadStart` option: event fires when the WebView recognizes and starts a downloadable file.
- Added `onLoadResourceCustomScheme` event and `resourceCustomSchemes` option to set custom schemes that WebView must handle to load resources - Added `onLoadResourceCustomScheme` event and `resourceCustomSchemes` option to set custom schemes that WebView must handle to load resources
...@@ -43,6 +45,7 @@ ...@@ -43,6 +45,7 @@
- Renamed `injectScriptCode` to `evaluateJavascript` - Renamed `injectScriptCode` to `evaluateJavascript`
- Renamed `injectStyleCode` to `injectCSSCode` - Renamed `injectStyleCode` to `injectCSSCode`
- Renamed `injectStyleFile` to `injectCSSFileFromUrl` - Renamed `injectStyleFile` to `injectCSSFileFromUrl`
- No need to listen to `window.addEventListener("flutterInAppBrowserPlatformReady", fuction(){ })` javascript event anymore to call `window.flutter_inappbrowser.callHandler(handlerName <String>, ...args)` to use the JavaScript message handlers
## 1.2.1 ## 1.2.1
......
...@@ -155,7 +155,7 @@ public class ContentBlockerHandler { ...@@ -155,7 +155,7 @@ public class ContentBlockerHandler {
break; break;
case MAKE_HTTPS: case MAKE_HTTPS:
if (url.startsWith("http://")) { if (scheme.equals("http") && (port == -1 || port == 80)) {
String urlHttps = url.replace("http://", "https://"); String urlHttps = url.replace("http://", "https://");
Request mRequest = new Request.Builder().url(urlHttps).build(); Request mRequest = new Request.Builder().url(urlHttps).build();
......
...@@ -117,10 +117,10 @@ public class InAppBrowserActivity extends AppCompatActivity { ...@@ -117,10 +117,10 @@ public class InAppBrowserActivity extends AppCompatActivity {
if (!options.toolbarTop) if (!options.toolbarTop)
actionBar.hide(); actionBar.hide();
if (!options.toolbarTopBackgroundColor.isEmpty()) if (options.toolbarTopBackgroundColor != null && !options.toolbarTopBackgroundColor.isEmpty())
actionBar.setBackgroundDrawable(new ColorDrawable(Color.parseColor(options.toolbarTopBackgroundColor))); actionBar.setBackgroundDrawable(new ColorDrawable(Color.parseColor(options.toolbarTopBackgroundColor)));
if (!options.toolbarTopFixedTitle.isEmpty()) if (options.toolbarTopFixedTitle != null && !options.toolbarTopFixedTitle.isEmpty())
actionBar.setTitle(options.toolbarTopFixedTitle); actionBar.setTitle(options.toolbarTopFixedTitle);
} }
......
...@@ -32,6 +32,8 @@ import com.pichillilorenzo.flutter_inappbrowser.Util; ...@@ -32,6 +32,8 @@ import com.pichillilorenzo.flutter_inappbrowser.Util;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
...@@ -182,12 +184,6 @@ public class InAppWebViewClient extends WebViewClient { ...@@ -182,12 +184,6 @@ public class InAppWebViewClient extends WebViewClient {
view.clearFocus(); view.clearFocus();
view.requestFocus(); view.requestFocus();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript(InAppWebView.platformReadyJS, (ValueCallback<String>) null);
} else {
webView.loadUrl("javascript:" + InAppWebView.platformReadyJS.replaceAll("[\r\n]+", ""));
}
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null) if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid); obj.put("uuid", inAppBrowserActivity.uuid);
...@@ -563,14 +559,21 @@ public class InAppWebViewClient extends WebViewClient { ...@@ -563,14 +559,21 @@ public class InAppWebViewClient extends WebViewClient {
}); });
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override @Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { public WebResourceResponse shouldInterceptRequest(WebView view, final String url) {
final InAppWebView webView = (InAppWebView) view; final InAppWebView webView = (InAppWebView) view;
final String url = request.getUrl().toString(); URI uri;
String scheme = request.getUrl().getScheme(); try {
uri = new URI(url);
} catch (URISyntaxException e) {
e.printStackTrace();
Log.e(LOG_TAG, e.getMessage());
return null;
}
String scheme = uri.getScheme();
if (webView.options.resourceCustomSchemes != null && webView.options.resourceCustomSchemes.contains(scheme)) { if (webView.options.resourceCustomSchemes != null && webView.options.resourceCustomSchemes.contains(scheme)) {
final Map<String, Object> obj = new HashMap<>(); final Map<String, Object> obj = new HashMap<>();
...@@ -619,6 +622,13 @@ public class InAppWebViewClient extends WebViewClient { ...@@ -619,6 +622,13 @@ public class InAppWebViewClient extends WebViewClient {
return response; return response;
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
return shouldInterceptRequest(view, url);
}
private MethodChannel getChannel() { private MethodChannel getChannel() {
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.inAppBrowser.channel : flutterWebView.channel; return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.inAppBrowser.channel : flutterWebView.channel;
} }
......
package com.pichillilorenzo.flutter_inappbrowser.InAppWebView; package com.pichillilorenzo.flutter_inappbrowser.InAppWebView;
import android.os.Build;
import android.util.Log;
import android.webkit.WebSettings; import android.webkit.WebSettings;
import com.pichillilorenzo.flutter_inappbrowser.Options; import com.pichillilorenzo.flutter_inappbrowser.Options;
import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static android.webkit.WebSettings.LayoutAlgorithm.NORMAL;
public class InAppWebViewOptions extends Options { public class InAppWebViewOptions extends Options {
public static final String LOG_TAG = "InAppWebViewOptions"; public static final String LOG_TAG = "InAppWebViewOptions";
...@@ -23,7 +28,6 @@ public class InAppWebViewOptions extends Options { ...@@ -23,7 +28,6 @@ public class InAppWebViewOptions extends Options {
public Boolean debuggingEnabled = false; public Boolean debuggingEnabled = false;
public Boolean javaScriptCanOpenWindowsAutomatically = false; public Boolean javaScriptCanOpenWindowsAutomatically = false;
public Boolean mediaPlaybackRequiresUserGesture = true; public Boolean mediaPlaybackRequiresUserGesture = true;
public Integer textZoom = 100;
public Integer minimumFontSize = 8; public Integer minimumFontSize = 8;
public Boolean verticalScrollBarEnabled = true; public Boolean verticalScrollBarEnabled = true;
public Boolean horizontalScrollBarEnabled = true; public Boolean horizontalScrollBarEnabled = true;
...@@ -36,6 +40,7 @@ public class InAppWebViewOptions extends Options { ...@@ -36,6 +40,7 @@ public class InAppWebViewOptions extends Options {
public Boolean cacheEnabled = true; public Boolean cacheEnabled = true;
public Boolean transparentBackground = false; public Boolean transparentBackground = false;
public Integer textZoom = 100;
public Boolean clearSessionCache = false; public Boolean clearSessionCache = false;
public Boolean builtInZoomControls = false; public Boolean builtInZoomControls = false;
public Boolean displayZoomControls = false; public Boolean displayZoomControls = false;
...@@ -66,7 +71,7 @@ public class InAppWebViewOptions extends Options { ...@@ -66,7 +71,7 @@ public class InAppWebViewOptions extends Options {
public Boolean loadWithOverviewMode = true; public Boolean loadWithOverviewMode = true;
public Boolean loadsImagesAutomatically = true; public Boolean loadsImagesAutomatically = true;
public Integer minimumLogicalFontSize = 8; public Integer minimumLogicalFontSize = 8;
public Integer initialScale; public Integer initialScale = 0;
public Boolean needInitialFocus = true; public Boolean needInitialFocus = true;
public Boolean offscreenPreRaster = false; public Boolean offscreenPreRaster = false;
public String sansSerifFontFamily = "sans-serif"; public String sansSerifFontFamily = "sans-serif";
...@@ -75,4 +80,49 @@ public class InAppWebViewOptions extends Options { ...@@ -75,4 +80,49 @@ public class InAppWebViewOptions extends Options {
public Boolean saveFormData = true; public Boolean saveFormData = true;
public Boolean thirdPartyCookiesEnabled = true; public Boolean thirdPartyCookiesEnabled = true;
public Boolean hardwareAcceleration = true; public Boolean hardwareAcceleration = true;
@Override
public Object onParse(Map.Entry<String, Object> pair) {
if (pair.getKey().equals("layoutAlgorithm")) {
String value = (String) pair.getValue();
if (value != null) {
switch (value) {
case "NORMAL":
pair.setValue(NORMAL);
return pair;
case "TEXT_AUTOSIZING":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return pair.setValue(WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING);
} else {
pair.setValue(NORMAL);
}
return pair;
}
}
}
return super.onParse(pair);
}
@Override
public Object onGetHashMap(Field field) {
if (field.getName().equals("layoutAlgorithm")) {
try {
WebSettings.LayoutAlgorithm value = (WebSettings.LayoutAlgorithm) field.get(this);
if (value != null) {
switch (value) {
case NORMAL:
return "NORMAL";
default:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && value.equals(WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING)) {
return "TEXT_AUTOSIZING";
}
return "NORMAL";
}
}
} catch (IllegalAccessException e) {
Log.d(LOG_TAG, e.getMessage());
}
}
return super.onGetHashMap(field);
}
} }
...@@ -15,8 +15,9 @@ public class Options { ...@@ -15,8 +15,9 @@ public class Options {
Iterator it = options.entrySet().iterator(); Iterator it = options.entrySet().iterator();
while (it.hasNext()) { while (it.hasNext()) {
Map.Entry<String, Object> pair = (Map.Entry<String, Object>) it.next(); Map.Entry<String, Object> pair = (Map.Entry<String, Object>) it.next();
Object value = this.onParse(pair);
try { try {
this.getClass().getDeclaredField(pair.getKey()).set(this, pair.getValue()); this.getClass().getDeclaredField(pair.getKey()).set(this, value);
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
Log.d(LOG_TAG, e.getMessage()); Log.d(LOG_TAG, e.getMessage());
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
...@@ -26,16 +27,25 @@ public class Options { ...@@ -26,16 +27,25 @@ public class Options {
return this; return this;
} }
public Object onParse(Map.Entry<String, Object> pair) {
return pair.getValue();
}
public HashMap<String, Object> getHashMap() { public HashMap<String, Object> getHashMap() {
HashMap<String, Object> options = new HashMap<>(); HashMap<String, Object> options = new HashMap<>();
for (Field f : this.getClass().getDeclaredFields()) { for (Field field : this.getClass().getDeclaredFields()) {
options.put(field.getName(), onGetHashMap(field));
}
return options;
}
public Object onGetHashMap(Field field) {
try { try {
options.put(f.getName(), f.get(this)); return field.get(this);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
Log.d(LOG_TAG, e.getMessage()); Log.d(LOG_TAG, e.getMessage());
} }
} return null;
return options;
} }
} }
...@@ -77,26 +77,23 @@ ...@@ -77,26 +77,23 @@
window.location = "#foo-" + randomNumber; window.location = "#foo-" + randomNumber;
} }
window.addEventListener("flutterInAppBrowserPlatformReady", function(event) {
console.log("ready");
window.flutter_inappbrowser.callHandler('handlerFoo').then(function(result) { window.flutter_inappbrowser.callHandler('handlerFoo').then(function(result) {
//console.log(result, typeof result); console.log(result, typeof result);
//console.log(JSON.stringify(result), result.bar); console.log(JSON.stringify(result), result.bar);
}); });
window.flutter_inappbrowser.callHandler('handlerFooWithArgs', 1, true, ['bar', 5], {foo: 'baz'}).then(function(result) { window.flutter_inappbrowser.callHandler('handlerFooWithArgs', 1, true, ['bar', 5], {foo: 'baz'}).then(function(result) {
//console.log(result, typeof result); console.log(result, typeof result);
//console.log(JSON.stringify(result)); console.log(JSON.stringify(result));
});
}); });
$(document).ready(function() { $(document).ready(function() {
console.log("jQuery ready"); console.log("jQuery ready");
var xhttp = new XMLHttpRequest(); var xhttp = new XMLHttpRequest();
xhttp.addEventListener("load", function() { xhttp.addEventListener("load", function() {
console.log(this.responseText); console.log(this.response);
}); });
xhttp.open("POST", "http://192.168.1.20:8082/test-ajax-post"); xhttp.open("POST", "http://192.168.1.20:8082/test-ajax-post");
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
......
...@@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; ...@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart'; import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
class MyChromeSafariBrowser extends ChromeSafariBrowser { class MyChromeSafariBrowser extends ChromeSafariBrowser {
MyChromeSafariBrowser(browserFallback) : super(browserFallback); MyChromeSafariBrowser(browserFallback) : super(bFallback: browserFallback);
@override @override
void onOpened() { void onOpened() {
print("ChromeSafari browser opened"); print("ChromeSafari browser opened");
...@@ -36,7 +36,7 @@ class _ChromeSafariExampleScreenState extends State<ChromeSafariExampleScreen> { ...@@ -36,7 +36,7 @@ class _ChromeSafariExampleScreenState extends State<ChromeSafariExampleScreen> {
return new Center( return new Center(
child: new RaisedButton( child: new RaisedButton(
onPressed: () async { onPressed: () async {
await widget.browser.open("https://flutter.dev/", await widget.browser.open(url: "https://flutter.dev/",
options: ChromeSafariBrowserClassOptions( options: ChromeSafariBrowserClassOptions(
androidChromeCustomTabsOptions: AndroidChromeCustomTabsOptions(addShareButton: false), androidChromeCustomTabsOptions: AndroidChromeCustomTabsOptions(addShareButton: false),
iosSafariOptions: IosSafariOptions(barCollapsingEnabled: true) iosSafariOptions: IosSafariOptions(barCollapsingEnabled: true)
......
...@@ -104,10 +104,11 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -104,10 +104,11 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
resourceCustomSchemes: ["my-special-custom-scheme"], resourceCustomSchemes: ["my-special-custom-scheme"],
contentBlockers: [ contentBlockers: [
ContentBlocker( ContentBlocker(
ContentBlockerTrigger(".*", trigger: ContentBlockerTrigger(
urlFilter: ".*",
resourceType: [ContentBlockerTriggerResourceType.IMAGE, ContentBlockerTriggerResourceType.STYLE_SHEET], resourceType: [ContentBlockerTriggerResourceType.IMAGE, ContentBlockerTriggerResourceType.STYLE_SHEET],
ifTopUrl: ["https://getbootstrap.com/"]), ifTopUrl: ["https://getbootstrap.com/"]),
ContentBlockerAction(ContentBlockerActionType.BLOCK) action: ContentBlockerAction(type: ContentBlockerActionType.BLOCK)
) )
] ]
), ),
...@@ -125,11 +126,11 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -125,11 +126,11 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
if (Platform.isAndroid) if (Platform.isAndroid)
webView.startSafeBrowsing(); webView.startSafeBrowsing();
webView.addJavaScriptHandler('handlerFoo', (args) { webView.addJavaScriptHandler(handlerName:'handlerFoo', callback: (args) {
return new Foo(bar: 'bar_value', baz: 'baz_value'); return new Foo(bar: 'bar_value', baz: 'baz_value');
}); });
webView.addJavaScriptHandler('handlerFooWithArgs', (args) { webView.addJavaScriptHandler(handlerName: 'handlerFooWithArgs', callback: (args) {
print(args); print(args);
return [args[0] + 5, !args[1], args[2][0], args[3]['foo']]; return [args[0] + 5, !args[1], args[2][0], args[3]['foo']];
}); });
...@@ -155,20 +156,20 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -155,20 +156,20 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
var tRexHtml = await controller.getTRexRunnerHtml(); var tRexHtml = await controller.getTRexRunnerHtml();
var tRexCss = await controller.getTRexRunnerCss(); var tRexCss = await controller.getTRexRunnerCss();
controller.loadData(""" controller.loadData(data: """
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no">
<style>${tRexCss}</style> <style>$tRexCss</style>
</head> </head>
<body> <body>
${tRexHtml} $tRexHtml
<p> <p>
URL ${url} failed to load. URL $url failed to load.
</p> </p>
<p> <p>
Error: ${code}, ${message} Error: $code, $message
</p> </p>
</body> </body>
</html> </html>
...@@ -182,7 +183,7 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -182,7 +183,7 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
}, },
shouldOverrideUrlLoading: (InAppWebViewController controller, String url) { shouldOverrideUrlLoading: (InAppWebViewController controller, String url) {
print("override $url"); print("override $url");
controller.loadUrl(url); controller.loadUrl(url: url);
}, },
onLoadResource: (InAppWebViewController controller, LoadedResource response) { onLoadResource: (InAppWebViewController controller, LoadedResource response) {
print("Resource type: '"+response.initiatorType + "' started at: " + print("Resource type: '"+response.initiatorType + "' started at: " +
...@@ -212,14 +213,14 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -212,14 +213,14 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
onLoadResourceCustomScheme: (InAppWebViewController controller, String scheme, String url) async { onLoadResourceCustomScheme: (InAppWebViewController controller, String scheme, String url) async {
if (scheme == "my-special-custom-scheme") { if (scheme == "my-special-custom-scheme") {
var bytes = await rootBundle.load("assets/" + url.replaceFirst("my-special-custom-scheme://", "", 0)); var bytes = await rootBundle.load("assets/" + url.replaceFirst("my-special-custom-scheme://", "", 0));
var response = new CustomSchemeResponse(bytes.buffer.asUint8List(), "image/svg+xml", contentEnconding: "utf-8"); var response = new CustomSchemeResponse(data: bytes.buffer.asUint8List(), contentType: "image/svg+xml", contentEnconding: "utf-8");
return response; return response;
} }
return null; return null;
}, },
onTargetBlank: (InAppWebViewController controller, String url) { onTargetBlank: (InAppWebViewController controller, String url) {
print("target _blank: " + url); print("target _blank: " + url);
controller.loadUrl(url); controller.loadUrl(url: url);
}, },
onGeolocationPermissionsShowPrompt: (InAppWebViewController controller, String origin) async { onGeolocationPermissionsShowPrompt: (InAppWebViewController controller, String origin) async {
GeolocationPermissionShowPromptResponse response; GeolocationPermissionShowPromptResponse response;
...@@ -234,14 +235,14 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -234,14 +235,14 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
FlatButton( FlatButton(
child: Text("Close"), child: Text("Close"),
onPressed: () { onPressed: () {
response = new GeolocationPermissionShowPromptResponse(origin, false, false); response = new GeolocationPermissionShowPromptResponse(origin: origin, allow: false, retain: false);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
FlatButton( FlatButton(
child: Text("Accept"), child: Text("Accept"),
onPressed: () { onPressed: () {
response = new GeolocationPermissionShowPromptResponse(origin, true, true); response = new GeolocationPermissionShowPromptResponse(origin: origin, allow: true, retain: true);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
...@@ -288,21 +289,24 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -288,21 +289,24 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
print("Current highlighted: $activeMatchOrdinal, Number of matches found: $numberOfMatches, find operation completed: $isDoneCounting"); print("Current highlighted: $activeMatchOrdinal, Number of matches found: $numberOfMatches, find operation completed: $isDoneCounting");
}, },
shouldInterceptAjaxRequest: (InAppWebViewController controller, AjaxRequest ajaxRequest) async { shouldInterceptAjaxRequest: (InAppWebViewController controller, AjaxRequest ajaxRequest) async {
//print("AJAX REQUEST: ${ajaxRequest.method} - ${ajaxRequest.url}, DATA: ${ajaxRequest.data}"); print("AJAX REQUEST: ${ajaxRequest.method} - ${ajaxRequest.url}, DATA: ${ajaxRequest.data}");
if (ajaxRequest.url == "http://192.168.1.20:8082/test-ajax-post") {
ajaxRequest.responseType = 'json';
}
// ajaxRequest.method = "GET"; // ajaxRequest.method = "GET";
// ajaxRequest.url = "http://192.168.1.20:8082/test-download-file"; // ajaxRequest.url = "http://192.168.1.20:8082/test-download-file";
// ajaxRequest.headers = { // ajaxRequest.headers = {
// "Custom-Header": "Custom-Value" // "Custom-Header": "Custom-Value"
// }; // };
// return ajaxRequest; // return ajaxRequest;
return null; return ajaxRequest;
}, },
onAjaxReadyStateChange: (InAppWebViewController controller, AjaxRequest ajaxRequest) async { onAjaxReadyStateChange: (InAppWebViewController controller, AjaxRequest ajaxRequest) async {
//print("AJAX READY STATE CHANGE: ${ajaxRequest.method} - ${ajaxRequest.url}, ${ajaxRequest.status}, ${ajaxRequest.readyState}, ${ajaxRequest.responseType}, ${ajaxRequest.responseText}, ${ajaxRequest.responseHeaders}"); print("AJAX READY STATE CHANGE: ${ajaxRequest.method} - ${ajaxRequest.url}, ${ajaxRequest.status}, ${ajaxRequest.readyState}, ${ajaxRequest.responseType}, ${ajaxRequest.responseText}, ${ajaxRequest.response}, ${ajaxRequest.responseHeaders}");
return AjaxRequestAction.PROCEED; return AjaxRequestAction.PROCEED;
}, },
onAjaxProgress: (InAppWebViewController controller, AjaxRequest ajaxRequest) async { onAjaxProgress: (InAppWebViewController controller, AjaxRequest ajaxRequest) async {
//print("AJAX EVENT: ${ajaxRequest.method} - ${ajaxRequest.url}, ${ajaxRequest.event.type}, LOADED: ${ajaxRequest.event.loaded}, ${ajaxRequest.responseHeaders}"); print("AJAX EVENT: ${ajaxRequest.method} - ${ajaxRequest.url}, ${ajaxRequest.event.type}, LOADED: ${ajaxRequest.event.loaded}, ${ajaxRequest.responseHeaders}");
return AjaxRequestAction.PROCEED; return AjaxRequestAction.PROCEED;
}, },
shouldInterceptFetchRequest: (InAppWebViewController controller, FetchRequest fetchRequest) async { shouldInterceptFetchRequest: (InAppWebViewController controller, FetchRequest fetchRequest) async {
...@@ -312,7 +316,7 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -312,7 +316,7 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
return fetchRequest; return fetchRequest;
}, },
onNavigationStateChange: (InAppWebViewController controller, String url) async { onNavigationStateChange: (InAppWebViewController controller, String url) async {
print("NAVIGATION STATE CHANGE: ${url}"); print("NAVIGATION STATE CHANGE: $url");
setState(() { setState(() {
this.url = url; this.url = url;
}); });
......
...@@ -3,7 +3,7 @@ import 'dart:async'; ...@@ -3,7 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart'; import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
class MyInappBrowser extends InAppBrowser { class MyInAppBrowser extends InAppBrowser {
@override @override
Future onBrowserCreated() async { Future onBrowserCreated() async {
...@@ -43,7 +43,7 @@ class MyInappBrowser extends InAppBrowser { ...@@ -43,7 +43,7 @@ class MyInappBrowser extends InAppBrowser {
@override @override
void shouldOverrideUrlLoading(String url) { void shouldOverrideUrlLoading(String url) {
print("\n\n override $url\n\n"); print("\n\n override $url\n\n");
this.webViewController.loadUrl(url); this.webViewController.loadUrl(url: url);
} }
@override @override
...@@ -75,11 +75,13 @@ class MyInappBrowser extends InAppBrowser { ...@@ -75,11 +75,13 @@ class MyInappBrowser extends InAppBrowser {
@override @override
Future<CustomSchemeResponse> onLoadResourceCustomScheme(String scheme, String url) async { Future<CustomSchemeResponse> onLoadResourceCustomScheme(String scheme, String url) async {
print("custom scheme: " + scheme); print("custom scheme: " + scheme);
return null;
} }
@override @override
Future<GeolocationPermissionShowPromptResponse> onGeolocationPermissionsShowPrompt(String origin) async { Future<GeolocationPermissionShowPromptResponse> onGeolocationPermissionsShowPrompt(String origin) async {
print("request Geolocation permission API"); print("request Geolocation permission API");
return null;
} }
@override @override
...@@ -89,18 +91,18 @@ class MyInappBrowser extends InAppBrowser { ...@@ -89,18 +91,18 @@ class MyInappBrowser extends InAppBrowser {
@override @override
Future<JsConfirmResponse> onJsConfirm(String message) { Future<JsConfirmResponse> onJsConfirm(String message) {
return null;
} }
@override @override
Future<JsPromptResponse> onJsPrompt(String message, String defaultValue) { Future<JsPromptResponse> onJsPrompt(String message, String defaultValue) {
return null;
} }
} }
class WebviewExampleScreen extends StatefulWidget { class WebviewExampleScreen extends StatefulWidget {
final MyInappBrowser browser = new MyInappBrowser(); final MyInAppBrowser browser = new MyInAppBrowser();
static BuildContext context = null; static BuildContext context;
@override @override
_WebviewExampleScreenState createState() => new _WebviewExampleScreenState(); _WebviewExampleScreenState createState() => new _WebviewExampleScreenState();
...@@ -119,7 +121,7 @@ class _WebviewExampleScreenState extends State<WebviewExampleScreen> { ...@@ -119,7 +121,7 @@ class _WebviewExampleScreenState extends State<WebviewExampleScreen> {
child: new RaisedButton( child: new RaisedButton(
onPressed: () { onPressed: () {
widget.browser.openFile( widget.browser.openFile(
"assets/index.html", assetFilePath: "assets/index.html",
//url: "https://www.google.com/", //url: "https://www.google.com/",
options: InAppBrowserClassOptions( options: InAppBrowserClassOptions(
inAppWebViewWidgetOptions: InAppWebViewWidgetOptions( inAppWebViewWidgetOptions: InAppWebViewWidgetOptions(
......
...@@ -82,8 +82,6 @@ window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() { ...@@ -82,8 +82,6 @@ window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() {
} }
""" """
let platformReadyJS = "window.dispatchEvent(new Event('flutterInAppBrowserPlatformReady'));";
let findTextHighlightJS = """ let findTextHighlightJS = """
var wkwebview_SearchResultCount = 0; var wkwebview_SearchResultCount = 0;
var wkwebview_CurrentHighlight = 0; var wkwebview_CurrentHighlight = 0;
...@@ -266,6 +264,29 @@ let interceptAjaxRequestsJS = """ ...@@ -266,6 +264,29 @@ let interceptAjaxRequestsJS = """
ajax.prototype._flutter_inappbrowser_password = null; ajax.prototype._flutter_inappbrowser_password = null;
ajax.prototype._flutter_inappbrowser_already_onreadystatechange_wrapped = false; ajax.prototype._flutter_inappbrowser_already_onreadystatechange_wrapped = false;
ajax.prototype._flutter_inappbrowser_request_headers = {}; ajax.prototype._flutter_inappbrowser_request_headers = {};
function convertRequestResponse(request, callback) {
if (request.response != null && request.responseType != null) {
switch (request.responseType) {
case 'arraybuffer':
callback(new Uint8Array(request.response));
return;
case 'blob':
const reader = new FileReader();
reader.addEventListener('loadend', function() {
callback(new Uint8Array(reader.result));
});
reader.readAsArrayBuffer(blob);
return;
case 'document':
callback(request.response.documentElement.outerHTML);
return;
case 'json':
callback(request.response);
return;
};
}
callback(null);
};
ajax.prototype.open = function(method, url, isAsync, user, password) { ajax.prototype.open = function(method, url, isAsync, user, password) {
isAsync = (isAsync != null) ? isAsync : true; isAsync = (isAsync != null) ? isAsync : true;
this._flutter_inappbrowser_url = url; this._flutter_inappbrowser_url = url;
...@@ -293,25 +314,29 @@ let interceptAjaxRequestsJS = """ ...@@ -293,25 +314,29 @@ let interceptAjaxRequestsJS = """
responseHeaders[header] = value; responseHeaders[header] = value;
}); });
} }
convertRequestResponse(this, function(response) {
var ajaxRequest = { var ajaxRequest = {
method: this._flutter_inappbrowser_method, method: self._flutter_inappbrowser_method,
url: this._flutter_inappbrowser_url, url: self._flutter_inappbrowser_url,
isAsync: this._flutter_inappbrowser_isAsync, isAsync: self._flutter_inappbrowser_isAsync,
user: this._flutter_inappbrowser_user, user: self._flutter_inappbrowser_user,
password: this._flutter_inappbrowser_password, password: self._flutter_inappbrowser_password,
withCredentials: this.withCredentials, withCredentials: self.withCredentials,
headers: this._flutter_inappbrowser_request_headers, headers: self._flutter_inappbrowser_request_headers,
readyState: this.readyState, readyState: self.readyState,
status: this.status, status: self.status,
responseURL: this.responseURL, responseURL: self.responseURL,
responseType: this.responseType, responseType: self.responseType,
responseText: this.responseText, response: response,
statusText: this.statusText, responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null,
responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null,
statusText: self.statusText,
responseHeaders, responseHeaders, responseHeaders, responseHeaders,
event: { event: {
type: e.type, type: e.type,
loaded: e.loaded, loaded: e.loaded,
lengthComputable: e.lengthComputable lengthComputable: e.lengthComputable,
total: e.total
} }
}; };
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxProgress', ajaxRequest).then(function(result) { window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxProgress', ajaxRequest).then(function(result) {
...@@ -323,6 +348,7 @@ let interceptAjaxRequestsJS = """ ...@@ -323,6 +348,7 @@ let interceptAjaxRequestsJS = """
}; };
} }
}); });
});
} }
}; };
ajax.prototype.send = function(data) { ajax.prototype.send = function(data) {
...@@ -344,20 +370,23 @@ let interceptAjaxRequestsJS = """ ...@@ -344,20 +370,23 @@ let interceptAjaxRequestsJS = """
responseHeaders[header] = value; responseHeaders[header] = value;
}); });
} }
convertRequestResponse(this, function(response) {
var ajaxRequest = { var ajaxRequest = {
method: this._flutter_inappbrowser_method, method: self._flutter_inappbrowser_method,
url: this._flutter_inappbrowser_url, url: self._flutter_inappbrowser_url,
isAsync: this._flutter_inappbrowser_isAsync, isAsync: self._flutter_inappbrowser_isAsync,
user: this._flutter_inappbrowser_user, user: self._flutter_inappbrowser_user,
password: this._flutter_inappbrowser_password, password: self._flutter_inappbrowser_password,
withCredentials: this.withCredentials, withCredentials: self.withCredentials,
headers: this._flutter_inappbrowser_request_headers, headers: self._flutter_inappbrowser_request_headers,
readyState: this.readyState, readyState: self.readyState,
status: this.status, status: self.status,
responseURL: this.responseURL, responseURL: self.responseURL,
responseType: this.responseType, responseType: self.responseType,
responseText: this.responseText, response: response,
statusText: this.statusText, responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null,
responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null,
statusText: self.statusText,
responseHeaders: responseHeaders responseHeaders: responseHeaders
}; };
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) { window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) {
...@@ -372,6 +401,7 @@ let interceptAjaxRequestsJS = """ ...@@ -372,6 +401,7 @@ let interceptAjaxRequestsJS = """
onreadystatechange(); onreadystatechange();
} }
}); });
});
} else if (onreadystatechange != null) { } else if (onreadystatechange != null) {
onreadystatechange(); onreadystatechange();
} }
...@@ -383,6 +413,7 @@ let interceptAjaxRequestsJS = """ ...@@ -383,6 +413,7 @@ let interceptAjaxRequestsJS = """
this.addEventListener('progress', handleEvent); this.addEventListener('progress', handleEvent);
this.addEventListener('error', handleEvent); this.addEventListener('error', handleEvent);
this.addEventListener('abort', handleEvent); this.addEventListener('abort', handleEvent);
this.addEventListener('timeout', handleEvent);
var ajaxRequest = { var ajaxRequest = {
data: data, data: data,
method: this._flutter_inappbrowser_method, method: this._flutter_inappbrowser_method,
...@@ -391,7 +422,8 @@ let interceptAjaxRequestsJS = """ ...@@ -391,7 +422,8 @@ let interceptAjaxRequestsJS = """
user: this._flutter_inappbrowser_user, user: this._flutter_inappbrowser_user,
password: this._flutter_inappbrowser_password, password: this._flutter_inappbrowser_password,
withCredentials: this.withCredentials, withCredentials: this.withCredentials,
headers: this._flutter_inappbrowser_request_headers headers: this._flutter_inappbrowser_request_headers,
responseType: this.responseType
}; };
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) { window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) {
if (result != null) { if (result != null) {
...@@ -402,6 +434,9 @@ let interceptAjaxRequestsJS = """ ...@@ -402,6 +434,9 @@ let interceptAjaxRequestsJS = """
}; };
data = result.data; data = result.data;
self.withCredentials = result.withCredentials; self.withCredentials = result.withCredentials;
if (result.responseType != null) {
self.responseType = result.responseType;
};
for (var header in result.headers) { for (var header in result.headers) {
var value = result.headers[header]; var value = result.headers[header];
self.setRequestHeader(header, value); self.setRequestHeader(header, value);
...@@ -557,7 +592,7 @@ let interceptFetchRequestsJS = """ ...@@ -557,7 +592,7 @@ let interceptFetchRequestsJS = """
controller.abort(); controller.abort();
break; break;
} }
var resultResource = (result.resource != null) ? result.resource : resource; var resultResource = (result.url != null) ? result.url : resource;
var resultInit = init; var resultInit = init;
if (result.init != null) { if (result.init != null) {
resultInit.method = result.method; resultInit.method = result.method;
...@@ -1308,7 +1343,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -1308,7 +1343,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
currentURL = url currentURL = url
InAppWebView.credentialsProposed = [] InAppWebView.credentialsProposed = []
onLoadStop(url: (currentURL?.absoluteString)!) onLoadStop(url: (currentURL?.absoluteString)!)
evaluateJavaScript(platformReadyJS, completionHandler: nil)
if IABController != nil { if IABController != nil {
IABController!.updateUrlTextField(url: (currentURL?.absoluteString)!) IABController!.updateUrlTextField(url: (currentURL?.absoluteString)!)
......
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'types.dart'; import 'types.dart';
...@@ -12,16 +13,16 @@ import 'in_app_browser.dart'; ...@@ -12,16 +13,16 @@ import 'in_app_browser.dart';
///This class uses native [Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android ///This class uses native [Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android
///and [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS. ///and [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS.
/// ///
///[browserFallback] represents the [InAppBrowser] instance fallback in case [Chrome Custom Tabs]/[SFSafariViewController] is not available. ///[browserFallback] represents the [InAppBrowser] instance fallback in case `Chrome Custom Tabs`/`SFSafariViewController` is not available.
class ChromeSafariBrowser { class ChromeSafariBrowser {
String uuid; String uuid;
InAppBrowser browserFallback; InAppBrowser browserFallback;
bool _isOpened = false; bool _isOpened = false;
///Initialize the [ChromeSafariBrowser] instance with an [InAppBrowser] fallback instance or `null`. ///Initialize the [ChromeSafariBrowser] instance with an [InAppBrowser] fallback instance or `null`.
ChromeSafariBrowser (bf) { ChromeSafariBrowser ({bFallback}) {
uuid = uuidGenerator.v4(); uuid = uuidGenerator.v4();
browserFallback = bf; browserFallback = bFallback;
ChannelManager.addListener(uuid, handleMethod); ChannelManager.addListener(uuid, handleMethod);
_isOpened = false; _isOpened = false;
} }
...@@ -45,32 +46,14 @@ class ChromeSafariBrowser { ...@@ -45,32 +46,14 @@ class ChromeSafariBrowser {
///Opens an [url] in a new [ChromeSafariBrowser] instance. ///Opens an [url] in a new [ChromeSafariBrowser] instance.
/// ///
///- [url]: The [url] to load. Call [encodeUriComponent()] on this if the [url] contains Unicode characters. ///[url]: The [url] to load. Call [encodeUriComponent()] on this if the [url] contains Unicode characters.
/// ///
///- [options]: Options for the [ChromeSafariBrowser]. ///[options]: Options for the [ChromeSafariBrowser].
/// ///
///- [headersFallback]: The additional header of the [InAppBrowser] instance fallback to be used in the HTTP request for this URL, specified as a map from name to value. ///[headersFallback]: The additional header of the [InAppBrowser] instance fallback to be used in the HTTP request for this URL, specified as a map from name to value.
/// ///
///- [optionsFallback]: Options used by the [InAppBrowser] instance fallback. ///[optionsFallback]: Options used by the [InAppBrowser] instance fallback.
/// Future<void> open({@required String url, ChromeSafariBrowserClassOptions options, Map<String, String> headersFallback = const {}, InAppBrowserClassOptions optionsFallback}) async {
///**Android** supports these options:
///
///- __addShareButton__: Set to `false` if you don't want the default share button. The default value is `true`.
///- __showTitle__: Set to `false` if the title shouldn't be shown in the custom tab. The default value is `true`.
///- __toolbarBackgroundColor__: Set the custom background color of the toolbar.
///- __enableUrlBarHiding__: Set to `true` to enable the url bar to hide as the user scrolls down on the page. The default value is `false`.
///- __instantAppsEnabled__: Set to `true` to enable Instant Apps. The default value is `false`.
///
///**iOS** supports these options:
///
///- __entersReaderIfAvailable__: Set to `true` if Reader mode should be entered automatically when it is available for the webpage. The default value is `false`.
///- __barCollapsingEnabled__: Set to `true` to enable bar collapsing. The default value is `false`.
///- __dismissButtonStyle__: Set the custom style for the dismiss button. The default value is `0 //done`. See [SFSafariViewController.DismissButtonStyle](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller/dismissbuttonstyle) for all the available styles.
///- __preferredBarTintColor__: Set the custom background color of the navigation bar and the toolbar.
///- __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<void> open(String url, {ChromeSafariBrowserClassOptions options, Map<String, String> headersFallback = const {}, InAppBrowserClassOptions optionsFallback}) async {
assert(url != null && url.isNotEmpty); assert(url != null && url.isNotEmpty);
this.throwIsAlreadyOpened(message: 'Cannot open $url!'); this.throwIsAlreadyOpened(message: 'Cannot open $url!');
......
import 'package:flutter/foundation.dart';
///ContentBlocker class represents a set of rules to use block content in the browser window.
///
///On iOS, it uses [WKContentRuleListStore](https://developer.apple.com/documentation/webkit/wkcontentruleliststore).
///On Android, it uses a custom implementation because such functionality doesn't exist.
///
///In general, this [article](https://developer.apple.com/documentation/safariservices/creating_a_content_blocker) can be used to get an overview about this functionality
///but on Android there are two types of [action] that are unavailable: `block-cookies` and `ignore-previous-rules`.
class ContentBlocker { class ContentBlocker {
///Trigger of the content blocker. The trigger tells to the WebView when to perform the corresponding action.
ContentBlockerTrigger trigger; ContentBlockerTrigger trigger;
///Action associated to the trigger. The action tells to the WebView what to do when the trigger is matched.
ContentBlockerAction action; ContentBlockerAction action;
ContentBlocker(this.trigger, this.action); ContentBlocker({@required this.trigger,@required this.action});
Map<String, Map<String, dynamic>> toMap() { Map<String, Map<String, dynamic>> toMap() {
return { return {
...@@ -13,21 +24,22 @@ class ContentBlocker { ...@@ -13,21 +24,22 @@ class ContentBlocker {
static ContentBlocker fromMap(Map<dynamic, Map<dynamic, dynamic>> map) { static ContentBlocker fromMap(Map<dynamic, Map<dynamic, dynamic>> map) {
return ContentBlocker( return ContentBlocker(
ContentBlockerTrigger.fromMap( trigger: ContentBlockerTrigger.fromMap(
Map<String, dynamic>.from(map["trigger"]) Map<String, dynamic>.from(map["trigger"])
), ),
ContentBlockerAction.fromMap( action: ContentBlockerAction.fromMap(
Map<String, dynamic>.from(map["action"]) Map<String, dynamic>.from(map["action"])
) )
); );
} }
} }
///ContentBlockerTriggerResourceType class represents the possible resource type defined for a [ContentBlockerTrigger].
class ContentBlockerTriggerResourceType { class ContentBlockerTriggerResourceType {
final String _value; final String _value;
const ContentBlockerTriggerResourceType._internal(this._value); const ContentBlockerTriggerResourceType._internal(this._value);
static ContentBlockerTriggerResourceType fromValue(String value) { static ContentBlockerTriggerResourceType fromValue(String value) {
return (["document", "image", "LINK", "style-sheet", "script", "font", return (["document", "image", "style-sheet", "script", "font",
"media", "svg-document", "raw"].contains(value)) ? ContentBlockerTriggerResourceType._internal(value) : null; "media", "svg-document", "raw"].contains(value)) ? ContentBlockerTriggerResourceType._internal(value) : null;
} }
toValue() => _value; toValue() => _value;
...@@ -39,9 +51,11 @@ class ContentBlockerTriggerResourceType { ...@@ -39,9 +51,11 @@ class ContentBlockerTriggerResourceType {
static const FONT = const ContentBlockerTriggerResourceType._internal('font'); static const FONT = const ContentBlockerTriggerResourceType._internal('font');
static const MEDIA = const ContentBlockerTriggerResourceType._internal('media'); static const MEDIA = const ContentBlockerTriggerResourceType._internal('media');
static const SVG_DOCUMENT = const ContentBlockerTriggerResourceType._internal('svg-document'); static const SVG_DOCUMENT = const ContentBlockerTriggerResourceType._internal('svg-document');
///Any untyped load
static const RAW = const ContentBlockerTriggerResourceType._internal('raw'); static const RAW = const ContentBlockerTriggerResourceType._internal('raw');
} }
///ContentBlockerTriggerLoadType class represents the possible load type for a [ContentBlockerTrigger].
class ContentBlockerTriggerLoadType { class ContentBlockerTriggerLoadType {
final String _value; final String _value;
const ContentBlockerTriggerLoadType._internal(this._value); const ContentBlockerTriggerLoadType._internal(this._value);
...@@ -50,24 +64,44 @@ class ContentBlockerTriggerLoadType { ...@@ -50,24 +64,44 @@ class ContentBlockerTriggerLoadType {
} }
toValue() => _value; toValue() => _value;
///FIRST_PARTY is triggered only if the resource has the same scheme, domain, and port as the main page resource.
static const FIRST_PARTY = const ContentBlockerTriggerLoadType._internal('first-party'); static const FIRST_PARTY = const ContentBlockerTriggerLoadType._internal('first-party');
///THIRD_PARTY is triggered if the resource is not from the same domain as the main page resource.
static const THIRD_PARTY = const ContentBlockerTriggerLoadType._internal('third-party'); static const THIRD_PARTY = const ContentBlockerTriggerLoadType._internal('third-party');
} }
///Trigger of the content blocker. The trigger tells to the WebView when to perform the corresponding action.
///A trigger dictionary must include an [ContentBlockerTrigger.urlFilter], which specifies a pattern to match the URL against.
///The remaining properties are optional and modify the behavior of the trigger.
///For example, you can limit the trigger to specific domains or have it not apply when a match is found on a specific domain.
class ContentBlockerTrigger { class ContentBlockerTrigger {
///A regular expression pattern to match the URL against.
String urlFilter; String urlFilter;
///Used only by iOS. A Boolean value. The default value is false.
bool urlFilterIsCaseSensitive; bool urlFilterIsCaseSensitive;
///A list of [ContentBlockerTriggerResourceType] representing the resource types (how the browser intends to use the resource) that the rule should match.
///If not specified, the rule matches all resource types.
List<ContentBlockerTriggerResourceType> resourceType; List<ContentBlockerTriggerResourceType> resourceType;
///A list of strings matched to a URL's domain; limits action to a list of specific domains.
///Values must be lowercase ASCII, or punycode for non-ASCII. Add * in front to match domain and subdomains. Can't be used with [ContentBlockerTrigger.unlessDomain].
List<String> ifDomain; List<String> ifDomain;
///A list of strings matched to a URL's domain; acts on any site except domains in a provided list.
///Values must be lowercase ASCII, or punycode for non-ASCII. Add * in front to match domain and subdomains. Can't be used with [ContentBlockerTrigger.ifDomain].
List<String> unlessDomain; List<String> unlessDomain;
///A list of [ContentBlockerTriggerLoadType] that can include one of two mutually exclusive values. If not specified, the rule matches all load types.
List<ContentBlockerTriggerLoadType> loadType; List<ContentBlockerTriggerLoadType> loadType;
///A list of strings matched to the entire main document URL; limits the action to a specific list of URL patterns.
///Values must be lowercase ASCII, or punycode for non-ASCII. Can't be used with [ContentBlockerTrigger.unlessTopUrl].
List<String> ifTopUrl; List<String> ifTopUrl;
///An array of strings matched to the entire main document URL; acts on any site except URL patterns in provided list.
///Values must be lowercase ASCII, or punycode for non-ASCII. Can't be used with [ContentBlockerTrigger.ifTopUrl].
List<String> unlessTopUrl; List<String> unlessTopUrl;
ContentBlockerTrigger(String urlFilter, {bool urlFilterIsCaseSensitive = false, List<ContentBlockerTriggerResourceType> resourceType = const [], ContentBlockerTrigger({@required String urlFilter, bool urlFilterIsCaseSensitive = false, List<ContentBlockerTriggerResourceType> resourceType = const [],
List<String> ifDomain = const [], List<String> unlessDomain = const [], List<ContentBlockerTriggerLoadType> loadType = const [], List<String> ifDomain = const [], List<String> unlessDomain = const [], List<ContentBlockerTriggerLoadType> loadType = const [],
List<String> ifTopUrl = const [], List<String> unlessTopUrl = const []}) { List<String> ifTopUrl = const [], List<String> unlessTopUrl = const []}) {
this.urlFilter = urlFilter; this.urlFilter = urlFilter;
assert(this.urlFilter != null);
this.resourceType = resourceType; this.resourceType = resourceType;
this.urlFilterIsCaseSensitive = urlFilterIsCaseSensitive; this.urlFilterIsCaseSensitive = urlFilterIsCaseSensitive;
this.ifDomain = ifDomain; this.ifDomain = ifDomain;
...@@ -124,7 +158,7 @@ class ContentBlockerTrigger { ...@@ -124,7 +158,7 @@ class ContentBlockerTrigger {
}); });
return ContentBlockerTrigger( return ContentBlockerTrigger(
map["url-filter"], urlFilter: map["url-filter"],
urlFilterIsCaseSensitive: map["url-filter-is-case-sensitive"], urlFilterIsCaseSensitive: map["url-filter-is-case-sensitive"],
ifDomain: List<String>.from(map["if-domain"] ?? []), ifDomain: List<String>.from(map["if-domain"] ?? []),
unlessDomain: List<String>.from(map["unless-domain"] ?? []), unlessDomain: List<String>.from(map["unless-domain"] ?? []),
...@@ -136,6 +170,7 @@ class ContentBlockerTrigger { ...@@ -136,6 +170,7 @@ class ContentBlockerTrigger {
} }
} }
///ContentBlockerActionType class represents the kind of action that can be used with a [ContentBlockerTrigger].
class ContentBlockerActionType { class ContentBlockerActionType {
final String _value; final String _value;
const ContentBlockerActionType._internal(this._value); const ContentBlockerActionType._internal(this._value);
...@@ -144,17 +179,31 @@ class ContentBlockerActionType { ...@@ -144,17 +179,31 @@ class ContentBlockerActionType {
} }
toValue() => _value; toValue() => _value;
///Stops loading of the resource. If the resource was cached, the cache is ignored.
static const BLOCK = const ContentBlockerActionType._internal('block'); static const BLOCK = const ContentBlockerActionType._internal('block');
///Hides elements of the page based on a CSS selector. A selector field contains the selector list. Any matching element has its display property set to none, which hides it.
///
///**NOTE**: on Android, JavaScript must be enabled.
static const CSS_DISPLAY_NONE = const ContentBlockerActionType._internal('css-display-none'); static const CSS_DISPLAY_NONE = const ContentBlockerActionType._internal('css-display-none');
///Changes a URL from http to https. URLs with a specified (nondefault) port and links using other protocols are unaffected.
static const MAKE_HTTPS = const ContentBlockerActionType._internal('make-https'); static const MAKE_HTTPS = const ContentBlockerActionType._internal('make-https');
} }
///Action associated to the trigger. The action tells to the WebView what to do when the trigger is matched.
///When a trigger matches a resource, the browser queues the associated action for execution.
///The WebView evaluates all the triggers, it executes the actions in order.
///When a domain matches a trigger, all rules after the triggered rule that specify the same action are skipped.
///Group the rules with similar actions together to improve performance.
class ContentBlockerAction { class ContentBlockerAction {
///Type of the action.
ContentBlockerActionType type; ContentBlockerActionType type;
///If the action type is [ContentBlockerActionType.CSS_DISPLAY_NONE], then also the [selector] property is required, otherwise it is ignored.
///It specify a string that defines a selector list. Use CSS identifiers as the individual selector values, separated by commas.
String selector; String selector;
ContentBlockerAction(ContentBlockerActionType type, {String selector}) { ContentBlockerAction({@required ContentBlockerActionType type, String selector}) {
this.type = type; this.type = type;
assert(this.type != null);
if (this.type == ContentBlockerActionType.CSS_DISPLAY_NONE) { if (this.type == ContentBlockerActionType.CSS_DISPLAY_NONE) {
assert(selector != null); assert(selector != null);
} }
...@@ -177,7 +226,7 @@ class ContentBlockerAction { ...@@ -177,7 +226,7 @@ class ContentBlockerAction {
static ContentBlockerAction fromMap(Map<String, dynamic> map) { static ContentBlockerAction fromMap(Map<String, dynamic> map) {
return ContentBlockerAction( return ContentBlockerAction(
ContentBlockerActionType.fromValue(map["type"]), type: ContentBlockerActionType.fromValue(map["type"]),
selector: map["selector"] selector: map["selector"]
); );
} }
......
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
///Manages the cookies used by WebView instances. ///Manages the cookies used by WebView instances.
...@@ -26,8 +27,8 @@ class CookieManager { ...@@ -26,8 +27,8 @@ class CookieManager {
/// ///
///The default value of [path] is `"/"`. ///The default value of [path] is `"/"`.
///If [domain] is `null`, its default value will be the domain name of [url]. ///If [domain] is `null`, its default value will be the domain name of [url].
Future<void> setCookie(String url, String name, String value, Future<void> setCookie({@required String url, @required String name, @required String value,
{ String domain, String domain,
String path = "/", String path = "/",
int expiresDate, int expiresDate,
int maxAge, int maxAge,
...@@ -55,7 +56,7 @@ class CookieManager { ...@@ -55,7 +56,7 @@ class CookieManager {
} }
///Gets all the cookies for the given [url]. ///Gets all the cookies for the given [url].
Future<List<Map<String, dynamic>>> getCookies(String url) async { Future<List<Map<String, dynamic>>> getCookies({@required String url}) async {
assert(url != null && url.isNotEmpty); assert(url != null && url.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
...@@ -70,7 +71,7 @@ class CookieManager { ...@@ -70,7 +71,7 @@ class CookieManager {
} }
///Gets a cookie by its [name] for the given [url]. ///Gets a cookie by its [name] for the given [url].
Future<Map<String, dynamic>> getCookie(String url, String name) async { Future<Map<String, dynamic>> getCookie({@required String url, @required String name}) async {
assert(url != null && url.isNotEmpty); assert(url != null && url.isNotEmpty);
assert(name != null && name.isNotEmpty); assert(name != null && name.isNotEmpty);
...@@ -90,7 +91,7 @@ class CookieManager { ...@@ -90,7 +91,7 @@ class CookieManager {
/// ///
///The default value of [path] is `"/"`. ///The default value of [path] is `"/"`.
///If [domain] is `null` or empty, its default value will be the domain name of [url]. ///If [domain] is `null` or empty, its default value will be the domain name of [url].
Future<void> deleteCookie(String url, String name, {String domain = "", String path = "/"}) async { Future<void> deleteCookie({@required String url, @required String name, String domain = "", String path = "/"}) async {
if (domain == null || domain.isEmpty) if (domain == null || domain.isEmpty)
domain = _getDomainName(url); domain = _getDomainName(url);
...@@ -111,7 +112,7 @@ class CookieManager { ...@@ -111,7 +112,7 @@ class CookieManager {
/// ///
///The default value of [path] is `"/"`. ///The default value of [path] is `"/"`.
///If [domain] is `null` or empty, its default value will be the domain name of [url]. ///If [domain] is `null` or empty, its default value will be the domain name of [url].
Future<void> deleteCookies(String url, {String domain = "", String path = "/"}) async { Future<void> deleteCookies({@required String url, String domain = "", String path = "/"}) async {
if (domain == null || domain.isEmpty) if (domain == null || domain.isEmpty)
domain = _getDomainName(url); domain = _getDomainName(url);
......
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'types.dart'; import 'types.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
/// ///HttpAuthCredentialDatabase class implements a singleton object (shared instance) which manages the shared HTTP auth credentials cache.
///On iOS, this class uses the [URLCredentialStorage](https://developer.apple.com/documentation/foundation/urlcredentialstorage) class.
///On Android, this class has a custom implementation using `android.database.sqlite.SQLiteDatabase` because [WebViewDatabase](https://developer.android.com/reference/android/webkit/WebViewDatabase)
///doesn't offer the same functionalities as iOS `URLCredentialStorage`.
class HttpAuthCredentialDatabase { class HttpAuthCredentialDatabase {
static HttpAuthCredentialDatabase _instance; static HttpAuthCredentialDatabase _instance;
static const MethodChannel _channel = const MethodChannel('com.pichillilorenzo/flutter_inappbrowser_credential_database'); static const MethodChannel _channel = const MethodChannel('com.pichillilorenzo/flutter_inappbrowser_credential_database');
/// ///Gets the database shared instance.
static HttpAuthCredentialDatabase instance() { static HttpAuthCredentialDatabase instance() {
return (_instance != null) ? _instance : _init(); return (_instance != null) ? _instance : _init();
} }
...@@ -22,7 +27,9 @@ class HttpAuthCredentialDatabase { ...@@ -22,7 +27,9 @@ class HttpAuthCredentialDatabase {
static Future<dynamic> _handleMethod(MethodCall call) async { static Future<dynamic> _handleMethod(MethodCall call) async {
} }
/// ///Gets a map list of all HTTP auth credentials saved.
///Each map contains the key `protectionSpace` of type [ProtectionSpace]
///and the key `credentials` of type `List<HttpAuthCredential>` that contains all the HTTP auth credentials saved for that `protectionSpace`.
Future<List<Map<String, dynamic>>> getAllAuthCredentials() async { Future<List<Map<String, dynamic>>> getAllAuthCredentials() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
List<dynamic> allCredentials = await _channel.invokeMethod('getAllAuthCredentials', args); List<dynamic> allCredentials = await _channel.invokeMethod('getAllAuthCredentials', args);
...@@ -38,8 +45,8 @@ class HttpAuthCredentialDatabase { ...@@ -38,8 +45,8 @@ class HttpAuthCredentialDatabase {
return result; return result;
} }
/// ///Gets all the HTTP auth credentials saved for that [protectionSpace].
Future<List<HttpAuthCredential>> getHttpAuthCredentials(ProtectionSpace protectionSpace) async { Future<List<HttpAuthCredential>> getHttpAuthCredentials({@required ProtectionSpace protectionSpace}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("host", () => protectionSpace.host); args.putIfAbsent("host", () => protectionSpace.host);
args.putIfAbsent("protocol", () => protectionSpace.protocol); args.putIfAbsent("protocol", () => protectionSpace.protocol);
...@@ -53,8 +60,8 @@ class HttpAuthCredentialDatabase { ...@@ -53,8 +60,8 @@ class HttpAuthCredentialDatabase {
return credentials; return credentials;
} }
/// ///Saves an HTTP auth [credential] for that [protectionSpace].
Future<void> setHttpAuthCredential(ProtectionSpace protectionSpace, HttpAuthCredential credential) async { Future<void> setHttpAuthCredential({@required ProtectionSpace protectionSpace, @required HttpAuthCredential credential}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("host", () => protectionSpace.host); args.putIfAbsent("host", () => protectionSpace.host);
args.putIfAbsent("protocol", () => protectionSpace.protocol); args.putIfAbsent("protocol", () => protectionSpace.protocol);
...@@ -65,8 +72,8 @@ class HttpAuthCredentialDatabase { ...@@ -65,8 +72,8 @@ class HttpAuthCredentialDatabase {
await _channel.invokeMethod('setHttpAuthCredential', args); await _channel.invokeMethod('setHttpAuthCredential', args);
} }
/// ///Removes an HTTP auth [credential] for that [protectionSpace].
Future<void> removeHttpAuthCredential(ProtectionSpace protectionSpace, HttpAuthCredential credential) async { Future<void> removeHttpAuthCredential({@required ProtectionSpace protectionSpace, @required HttpAuthCredential credential}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("host", () => protectionSpace.host); args.putIfAbsent("host", () => protectionSpace.host);
args.putIfAbsent("protocol", () => protectionSpace.protocol); args.putIfAbsent("protocol", () => protectionSpace.protocol);
...@@ -77,8 +84,8 @@ class HttpAuthCredentialDatabase { ...@@ -77,8 +84,8 @@ class HttpAuthCredentialDatabase {
await _channel.invokeMethod('removeHttpAuthCredential', args); await _channel.invokeMethod('removeHttpAuthCredential', args);
} }
/// ///Removes all the HTTP auth credentials saved for that [protectionSpace].
Future<void> removeHttpAuthCredentials(ProtectionSpace protectionSpace) async { Future<void> removeHttpAuthCredentials({@required ProtectionSpace protectionSpace}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("host", () => protectionSpace.host); args.putIfAbsent("host", () => protectionSpace.host);
args.putIfAbsent("protocol", () => protectionSpace.protocol); args.putIfAbsent("protocol", () => protectionSpace.protocol);
...@@ -87,7 +94,7 @@ class HttpAuthCredentialDatabase { ...@@ -87,7 +94,7 @@ class HttpAuthCredentialDatabase {
await _channel.invokeMethod('removeHttpAuthCredentials', args); await _channel.invokeMethod('removeHttpAuthCredentials', args);
} }
/// ///Removes all the HTTP auth credentials saved in the database.
Future<void> clearAllAuthCredentials() async { Future<void> clearAllAuthCredentials() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('clearAllAuthCredentials', args); await _channel.invokeMethod('clearAllAuthCredentials', args);
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment