Commit 3ffa1c0a authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

Updated onLoadResource event, Updated WebResourceResponse class, Deleted...

Updated onLoadResource event, Updated WebResourceResponse class, Deleted WebResourceRequest class, added useOnDownloadStart option, created webview options classes, added initial content blocking support
parent 49983cfd
This diff is collapsed.
......@@ -9,9 +9,17 @@
- 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))
- 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: 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 `onTargetBlank` event and `useOnTargetBlank` option to manage links with `target="_blank"`
- Added `ContentBlocker`, `ContentBlockerTrigger` and `ContentBlockerAction` classes and the `contentBlockers` option that allows to define a set of rules to use to block content in the WebView
### BREAKING CHANGES
- Deleted `WebResourceRequest` class
- Updated `WebResourceResponse` class
- Updated `onLoadResource` event
- WebView options are now available with the new corresponding classes: `InAppWebViewOptions`, `AndroidInAppWebViewOptions`, `iOSInAppWebViewOptions`, `InAppBrowserOptions`, `AndroidInAppBrowserOptions`, `iOSInAppBrowserOptions`, `AndroidChromeCustomTabsOptions` and `iOSChromeCustomTabsOptions`
## 1.2.1
......
package com.pichillilorenzo.flutter_inappbrowser.ContentBlocker;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.webkit.WebResourceResponse;
import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebView;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.Request;
import okhttp3.Response;
public class ContentBlocker {
protected static final String LOG_TAG = "ContentBlocker";
public static WebResourceResponse checkUrl(final InAppWebView webView, String url, ContentBlockerTriggerResourceType responseResourceType) {
if (webView.options.contentBlockers == null)
return null;
for (Map<String, Map<String, Object>> contentBlocker : webView.options.contentBlockers) {
ContentBlockerTrigger trigger = ContentBlockerTrigger.fromMap(contentBlocker.get("trigger"));
List<ContentBlockerTriggerResourceType> resourceTypes = trigger.resourceType;
ContentBlockerAction action = ContentBlockerAction.fromMap(contentBlocker.get("action"));
Pattern mPattern = Pattern.compile(trigger.urlFilter);
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)) {
return null;
}
switch (action.type) {
case BLOCK:
return new WebResourceResponse("", "", null);
case CSS_DISPLAY_NONE:
final String jsScript = "function hide () { document.querySelectorAll('" + action.selector + "').forEach(function (item, index) { item.style.display = \"none\"; }); }; hide(); document.addEventListener(\"DOMContentLoaded\", function(event) { hide(); });";
final Handler handler = new Handler(Looper.getMainLooper());
Log.d(LOG_TAG, jsScript);
handler.post(new Runnable() {
@Override
public void run() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript(jsScript, null);
} else {
webView.loadUrl("javascript:" + jsScript);
}
}
});
break;
case MAKE_HTTPS:
if (url.startsWith("http://")) {
String urlHttps = url.replace("http://", "https://");
Request mRequest = new Request.Builder().url(urlHttps).build();
Response response = null;
try {
response = webView.httpClient.newCall(mRequest).execute();
byte[] dataBytes = response.body().bytes();
InputStream dataStream = new ByteArrayInputStream(dataBytes);
String[] contentTypeSplitted = response.header("content-type", "text/plain").split(";");
String contentType = contentTypeSplitted[0].trim();
String encoding = (contentTypeSplitted.length > 1 && contentTypeSplitted[1].contains("charset="))
? contentTypeSplitted[1].replace("charset=", "").trim()
: "utf-8";
response.close();
return new WebResourceResponse(contentType, encoding, dataStream);
} catch (IOException e) {
e.printStackTrace();
if (response != null) {
response.close();
}
Log.e(LOG_TAG, e.getMessage());
}
}
break;
}
}
}
return null;
}
public static WebResourceResponse checkUrl(final InAppWebView webView, String url) {
ContentBlockerTriggerResourceType responseResourceType = getResourceTypeFromUrl(webView, url);
return checkUrl(webView, url, responseResourceType);
}
public static WebResourceResponse checkUrl(final InAppWebView webView, String url, String contentType) {
ContentBlockerTriggerResourceType responseResourceType = getResourceTypeFromContentType(contentType);
return checkUrl(webView, url, responseResourceType);
}
public static ContentBlockerTriggerResourceType getResourceTypeFromUrl(InAppWebView webView, String url) {
ContentBlockerTriggerResourceType responseResourceType = ContentBlockerTriggerResourceType.RAW;
// make an HTTP "HEAD" request to the server for that URL. This will not return the full content of the URL.
if (url.startsWith("http://") || url.startsWith("https://")) {
Request mRequest = new Request.Builder().url(url).head().build();
Response response = null;
try {
response = webView.httpClient.newCall(mRequest).execute();
if (response.header("content-type") != null) {
String[] contentTypeSplitted = response.header("content-type").split(";");
String contentType = contentTypeSplitted[0].trim();
String encoding = (contentTypeSplitted.length > 1 && contentTypeSplitted[1].contains("charset="))
? contentTypeSplitted[1].replace("charset=", "").trim()
: "utf-8";
response.close();
responseResourceType = getResourceTypeFromContentType(contentType);
}
} catch (IOException e) {
if (response != null) {
response.close();
}
e.printStackTrace();
}
}
return responseResourceType;
}
public static ContentBlockerTriggerResourceType getResourceTypeFromContentType(String contentType) {
ContentBlockerTriggerResourceType responseResourceType = ContentBlockerTriggerResourceType.RAW;
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
if (contentType.equals("text/css")) {
responseResourceType = ContentBlockerTriggerResourceType.STYLE_SHEET;
} else if (contentType.equals("image/svg+xml")) {
responseResourceType = ContentBlockerTriggerResourceType.SVG_DOCUMENT;
} else if (contentType.startsWith("image/")) {
responseResourceType = ContentBlockerTriggerResourceType.IMAGE;
} else if (contentType.startsWith("font/")) {
responseResourceType = ContentBlockerTriggerResourceType.FONT;
} else if (contentType.startsWith("audio/") || contentType.startsWith("video/") || contentType.equals("application/ogg")) {
responseResourceType = ContentBlockerTriggerResourceType.MEDIA;
} else if (contentType.endsWith("javascript")) {
responseResourceType = ContentBlockerTriggerResourceType.SCRIPT;
} else if (contentType.startsWith("text/")) {
responseResourceType = ContentBlockerTriggerResourceType.DOCUMENT;
}
return responseResourceType;
}
}
package com.pichillilorenzo.flutter_inappbrowser.ContentBlocker;
import java.util.Map;
public class ContentBlockerAction {
ContentBlockerActionType type;
String selector;
ContentBlockerAction(ContentBlockerActionType type, String selector) {
this.type = type;
if (this.type.equals(ContentBlockerActionType.CSS_DISPLAY_NONE)) {
assert(selector != null);
}
this.selector = selector;
}
public static ContentBlockerAction fromMap(Map<String, Object> map) {
ContentBlockerActionType type = ContentBlockerActionType.fromValue((String) map.get("type"));
String selector = (String) map.get("selector");
return new ContentBlockerAction(type, selector);
}
}
package com.pichillilorenzo.flutter_inappbrowser.ContentBlocker;
public enum ContentBlockerActionType {
BLOCK ("block"),
CSS_DISPLAY_NONE ("css-display-none"),
MAKE_HTTPS ("make-https");
private final String value;
private ContentBlockerActionType(String value) {
this.value = value;
}
public boolean equalsValue(String otherValue) {
return value.equals(otherValue);
}
public static ContentBlockerActionType fromValue(String value) {
for( ContentBlockerActionType type : ContentBlockerActionType.values()) {
if(value.equals(type.value))
return type;
}
throw new IllegalArgumentException("No enum constant: " + value);
}
public String toString() {
return this.value;
}
}
package com.pichillilorenzo.flutter_inappbrowser.ContentBlocker;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ContentBlockerTrigger {
public String urlFilter;
List<ContentBlockerTriggerResourceType> resourceType = new ArrayList<>();
public ContentBlockerTrigger(String urlFilter, List<ContentBlockerTriggerResourceType> resourceType) {
this.urlFilter = urlFilter;
this.resourceType = resourceType != null ? resourceType : this.resourceType;
}
public static ContentBlockerTrigger fromMap(Map<String, Object> map) {
String urlFilter = (String) map.get("url-filter");
List<String> resourceTypeStringList = (List<String>) map.get("resource-type");
List<ContentBlockerTriggerResourceType> resourceType = new ArrayList<>();
for (String type : resourceTypeStringList) {
resourceType.add(ContentBlockerTriggerResourceType.fromValue(type));
}
return new ContentBlockerTrigger(urlFilter, resourceType);
}
}
package com.pichillilorenzo.flutter_inappbrowser.ContentBlocker;
public enum ContentBlockerTriggerResourceType {
DOCUMENT ("document"),
IMAGE ("image"),
STYLE_SHEET ("style-sheet"),
SCRIPT ("script"),
FONT ("font"),
SVG_DOCUMENT ("svg-document"),
MEDIA ("media"),
RAW ("raw");
private final String value;
private ContentBlockerTriggerResourceType(String value) {
this.value = value;
}
public boolean equalsValue(String otherValue) {
return value.equals(otherValue);
}
public static ContentBlockerTriggerResourceType fromValue(String value) {
for( ContentBlockerTriggerResourceType type : ContentBlockerTriggerResourceType.values()) {
if(value.equals(type.value))
return type;
}
throw new IllegalArgumentException("No enum constant: " + value);
}
public String toString() {
return this.value;
}
}
package com.pichillilorenzo.flutter_inappbrowser.InAppWebView;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
......@@ -23,7 +22,6 @@ import com.pichillilorenzo.flutter_inappbrowser.FlutterWebView;
import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserFlutterPlugin;
import com.pichillilorenzo.flutter_inappbrowser.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappbrowser.RequestPermissionHandler;
import com.pichillilorenzo.flutter_inappbrowser.Util;
import java.io.ByteArrayOutputStream;
......@@ -47,11 +45,11 @@ public class InAppWebView extends WebView {
public InAppBrowserActivity inAppBrowserActivity;
public FlutterWebView flutterWebView;
public int id;
InAppWebViewClient inAppWebViewClient;
InAppWebChromeClient inAppWebChromeClient;
public InAppWebViewClient inAppWebViewClient;
public InAppWebChromeClient inAppWebChromeClient;
public InAppWebViewOptions options;
public boolean isLoading = false;
OkHttpClient httpClient;
public OkHttpClient httpClient;
int okHttpClientCacheSize = 10 * 1024 * 1024; // 10MB
static final String consoleLogJS = "(function() {" +
......@@ -80,6 +78,15 @@ public class InAppWebView extends WebView {
" }" +
"})();";
static final String resourceObserverJS = "(function() {" +
" var observer = new PerformanceObserver(function(list) {" +
" list.getEntries().forEach(function(entry) {" +
" window." + JavaScriptBridgeInterface.name + "._resourceLoaded(JSON.stringify(entry));" +
" });" +
" });" +
" observer.observe({entryTypes: ['resource']});" +
"})();";
static final String platformReadyJS = "window.dispatchEvent(new Event('flutterInAppBrowserPlatformReady'));";
public InAppWebView(Context context) {
......@@ -117,7 +124,8 @@ public class InAppWebView extends WebView {
boolean isFromInAppBrowserActivity = inAppBrowserActivity != null;
httpClient = new OkHttpClient().newBuilder().cache(new Cache(getContext().getCacheDir(), okHttpClientCacheSize)).build();
//httpClient = new OkHttpClient().newBuilder().cache(new Cache(getContext().getCacheDir(), okHttpClientCacheSize)).build();
httpClient = new OkHttpClient().newBuilder().build();
addJavascriptInterface(new JavaScriptBridgeInterface((isFromInAppBrowserActivity) ? inAppBrowserActivity : flutterWebView), JavaScriptBridgeInterface.name);
......@@ -127,23 +135,8 @@ public class InAppWebView extends WebView {
inAppWebViewClient = new InAppWebViewClient((isFromInAppBrowserActivity) ? inAppBrowserActivity : flutterWebView);
setWebViewClient(inAppWebViewClient);
setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(final String url, final String userAgent,
final String contentDisposition, final String mimetype,
final long contentLength) {
RequestPermissionHandler.checkAndRun(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, RequestPermissionHandler.REQUEST_CODE_WRITE_EXTERNAL_STORAGE, new Runnable(){
@Override
public void run(){
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
obj.put("url", url);
getChannel().invokeMethod("onDownloadStart", obj);
}
});
}
});
if (options.useOnDownloadStart)
setDownloadListener(new DownloadStartListener());
WebSettings settings = getSettings();
......@@ -396,6 +389,14 @@ public class InAppWebView extends WebView {
if (newOptionsMap.get("useOnTargetBlank") != null && options.useOnTargetBlank != newOptions.useOnTargetBlank)
settings.setSupportMultipleWindows(newOptions.useOnTargetBlank);
if (newOptionsMap.get("useOnDownloadStart") != null && options.useOnDownloadStart != newOptions.useOnDownloadStart) {
if (newOptions.useOnDownloadStart) {
setDownloadListener(new DownloadStartListener());
} else {
setDownloadListener(null);
}
}
options = newOptions;
}
......@@ -534,6 +535,17 @@ public class InAppWebView extends WebView {
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel;
}
class DownloadStartListener implements DownloadListener {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
obj.put("url", url);
getChannel().invokeMethod("onDownloadStart", obj);
}
}
@Override
public void destroy() {
super.destroy();
......
......@@ -5,10 +5,6 @@ import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Build;
import androidx.annotation.RequiresApi;
import android.os.Handler;
import android.os.Looper;
import android.util.Base64;
import android.util.Log;
import android.webkit.CookieManager;
......@@ -21,6 +17,9 @@ import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.RequiresApi;
import com.pichillilorenzo.flutter_inappbrowser.ContentBlocker.ContentBlocker;
import com.pichillilorenzo.flutter_inappbrowser.FlutterWebView;
import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserFlutterPlugin;
......@@ -28,15 +27,10 @@ import com.pichillilorenzo.flutter_inappbrowser.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappbrowser.Util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.MethodChannel;
import okhttp3.Request;
import okhttp3.Response;
public class InAppWebViewClient extends WebViewClient {
......@@ -185,12 +179,16 @@ public class InAppWebViewClient extends WebViewClient {
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
startPageTime = System.currentTimeMillis();
InAppWebView webView = (InAppWebView) view;
((inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView).isLoading = true;
if (webView.options.useOnLoadResource)
webView.loadUrl("javascript:" + webView.resourceObserverJS.replaceAll("[\r\n]+", ""));
super.onPageStarted(view, url, favicon);
startPageTime = System.currentTimeMillis();
webView.isLoading = true;
if (inAppBrowserActivity != null && inAppBrowserActivity.searchView != null && !url.equals(inAppBrowserActivity.searchView.getQuery().toString())) {
inAppBrowserActivity.searchView.setQuery(url, false);
}
......@@ -204,9 +202,11 @@ public class InAppWebViewClient extends WebViewClient {
public void onPageFinished(final WebView view, String url) {
InAppWebView webView = (InAppWebView) view;
super.onPageFinished(view, url);
((inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView).isLoading = false;
webView.isLoading = false;
// CB-10395 InAppBrowserFlutterPlugin's WebView not storing cookies reliable to local device storage
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
......@@ -305,7 +305,7 @@ public class InAppWebViewClient extends WebViewClient {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
InAppWebView webView = (InAppWebView) view;
final InAppWebView webView = (InAppWebView) view;
final String url = request.getUrl().toString();
String scheme = request.getUrl().getScheme();
......@@ -324,105 +324,16 @@ public class InAppWebViewClient extends WebViewClient {
}
else if (flutterResult.result != null) {
Map<String, String> res = (Map<String, String>) flutterResult.result;
WebResourceResponse response = ContentBlocker.checkUrl(webView, url, res.get("content-type"));
if (response != null)
return response;
byte[] data = Base64.decode(res.get("base64data"), Base64.DEFAULT);
return new WebResourceResponse(res.get("content-type"), res.get("content-encoding"), new ByteArrayInputStream(data));
}
}
if (!request.getMethod().toLowerCase().equals("get") ||
!(((inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView).options.useOnLoadResource)) {
return null;
}
try {
Request mRequest = new Request.Builder().url(url).build();
long startResourceTime = System.currentTimeMillis();
Response response = ((inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView).httpClient.newCall(mRequest).execute();
long startTime = startResourceTime - startPageTime;
startTime = (startTime < 0) ? 0 : startTime;
long duration = System.currentTimeMillis() - startResourceTime;
if (response.cacheResponse() != null) {
duration = 0;
}
String reasonPhrase = response.message();
if (reasonPhrase.equals("")) {
reasonPhrase = statusCodeMapping.get(response.code());
}
reasonPhrase = (reasonPhrase.equals("") || reasonPhrase == null) ? "OK" : reasonPhrase;
Map<String, String> headersResponse = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
StringBuilder value = new StringBuilder();
for (String val : entry.getValue()) {
value.append((value.toString().isEmpty()) ? val : "; " + val);
}
headersResponse.put(entry.getKey().toLowerCase(), value.toString());
}
Map<String, String> headersRequest = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : mRequest.headers().toMultimap().entrySet()) {
StringBuilder value = new StringBuilder();
for (String val : entry.getValue()) {
value.append((value.toString().isEmpty()) ? val : "; " + val);
}
headersRequest.put(entry.getKey().toLowerCase(), value.toString());
}
final Map<String, Object> obj = new HashMap<>();
Map<String, Object> res = new HashMap<>();
Map<String, Object> req = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
byte[] dataBytes = response.body().bytes();
InputStream dataStream = new ByteArrayInputStream(dataBytes);
res.put("url", url);
res.put("statusCode", response.code());
res.put("headers", headersResponse);
res.put("startTime", startTime);
res.put("duration", duration);
res.put("data", dataBytes);
req.put("url", url);
req.put("headers", headersRequest);
req.put("method", mRequest.method());
obj.put("response", res);
obj.put("request", req);
// java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread.
// https://github.com/pichillilorenzo/flutter_inappbrowser/issues/98
final Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
getChannel().invokeMethod("onLoadResource", obj);
}
});
// this return is not working (it blocks some resources), so return null
// return new WebResourceResponse(
// response.header("content-type", "text/plain").split(";")[0].trim(),
// response.header("content-encoding", "utf-8"),
// response.code(),
// reasonPhrase,
// headersResponse,
// dataStream
// );
} catch (IOException e) {
e.printStackTrace();
Log.d(LOG_TAG, e.getMessage());
} catch (Exception e) {
e.printStackTrace();
Log.d(LOG_TAG, e.getMessage());
}
return null;
WebResourceResponse response = ContentBlocker.checkUrl(webView, url);
return response;
}
private MethodChannel getChannel() {
......
......@@ -4,6 +4,7 @@ import com.pichillilorenzo.flutter_inappbrowser.Options;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class InAppWebViewOptions extends Options {
......@@ -11,6 +12,7 @@ public class InAppWebViewOptions extends Options {
public boolean useShouldOverrideUrlLoading = false;
public boolean useOnLoadResource = false;
public boolean useOnDownloadStart = false;
public boolean useOnTargetBlank = false;
public boolean clearCache = false;
public String userAgent = "";
......@@ -21,6 +23,7 @@ public class InAppWebViewOptions extends Options {
public boolean verticalScrollBarEnabled = true;
public boolean horizontalScrollBarEnabled = true;
public List<String> resourceCustomSchemes = new ArrayList<>();
public List<Map<String, Map<String, Object>>> contentBlockers = new ArrayList<>();
public boolean clearSessionCache = false;
public boolean builtInZoomControls = false;
......
......@@ -8,6 +8,11 @@ import android.webkit.JavascriptInterface;
import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebView;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
......@@ -77,7 +82,42 @@ public class JavaScriptBridgeInterface {
});
}
});
}
@JavascriptInterface
public void _resourceLoaded(String json) {
try {
JSONObject jsonObject = new JSONObject(json);
final Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
String initiatorType = jsonObject.getString("initiatorType");
String url = jsonObject.getString("name");
Double startTime = jsonObject.getDouble("startTime");
Double duration = jsonObject.getDouble("duration");
obj.put("initiatorType", initiatorType);
obj.put("url", url);
obj.put("startTime", startTime);
obj.put("duration", duration);
// java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread.
// https://github.com/pichillilorenzo/flutter_inappbrowser/issues/98
final Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
getChannel().invokeMethod("onLoadResource", obj);
}
});
} catch (final JSONException e) {
Log.e(LOG_TAG, "Json parsing error: " + e.getMessage());
}
}
private MethodChannel getChannel() {
......
......@@ -17,6 +17,7 @@
<application
android:name="com.pichillilorenzo.flutterwebviewexample.MyApplication"
android:label="flutter_inappbrowser_example"
android:usesCleartextTraffic="true"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
......
......@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Flutter InAppBrowser</title>
<link rel="stylesheet" href="https://getbootstrap.com/docs/4.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="http://getbootstrap.com/docs/4.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="css/style.css">
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
......
......@@ -65,14 +65,21 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
//initialUrl: "https://flutter.dev/",
initialFile: "assets/index.html",
initialHeaders: {},
initialOptions: {
//"mediaPlaybackRequiresUserGesture": false,
//"allowsInlineMediaPlayback": true,
"useShouldOverrideUrlLoading": true,
"useOnTargetBlank": true,
"resourceCustomSchemes": ["my-special-custom-scheme"],
//"useOnLoadResource": true
},
initialOptions: [
InAppWebViewOptions(
useShouldOverrideUrlLoading: true,
useOnTargetBlank: true,
//useOnLoadResource: true,
useOnDownloadStart: true,
resourceCustomSchemes: ["my-special-custom-scheme"],
contentBlockers: [
ContentBlocker(
ContentBlockerTrigger(".*", resourceType: [ContentBlockerTriggerResourceType.IMAGE]),
ContentBlockerAction(ContentBlockerActionType.BLOCK)
)
]
)
],
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
......@@ -104,8 +111,8 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
print("override $url");
controller.loadUrl(url);
},
onLoadResource: (InAppWebViewController controller, WebResourceResponse response, WebResourceRequest request) {
print("Started at: " +
onLoadResource: (InAppWebViewController controller, WebResourceResponse response) {
print("Resource type: '"+response.initiatorType + "' started at: " +
response.startTime.toString() +
"ms ---> duration: " +
response.duration.toString() +
......@@ -113,13 +120,13 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
response.url);
},
onConsoleMessage: (InAppWebViewController controller, ConsoleMessage consoleMessage) {
// print("""
// console output:
// sourceURL: ${consoleMessage.sourceURL}
// lineNumber: ${consoleMessage.lineNumber}
// message: ${consoleMessage.message}
// messageLevel: ${consoleMessage.messageLevel}
// """);
print("""
console output:
sourceURL: ${consoleMessage.sourceURL}
lineNumber: ${consoleMessage.lineNumber}
message: ${consoleMessage.message}
messageLevel: ${consoleMessage.messageLevel}
""");
},
onDownloadStart: (InAppWebViewController controller, String url) async {
final taskId = await FlutterDownloader.enqueue(
......@@ -133,7 +140,7 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
if (scheme == "my-special-custom-scheme") {
var bytes = await rootBundle.load("assets/" + url.replaceFirst("my-special-custom-scheme://", "", 0));
var asBase64 = base64.encode(bytes.buffer.asUint8List());
var response = new CustomSchemeResponse(asBase64, "image/svg+xml", "utf-8");
var response = new CustomSchemeResponse(asBase64, "image/svg+xml", contentEnconding: "utf-8");
return response;
}
return null;
......
......@@ -400,9 +400,7 @@ class _MyAppState extends State<MyApp> {
initialHeaders: {
},
initialOptions: {
},
initialOptions: [],
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
......
......@@ -47,7 +47,7 @@ class MyInappBrowser extends InAppBrowser {
}
@override
void onLoadResource(WebResourceResponse response, WebResourceRequest request) {
void onLoadResource(WebResourceResponse response) {
print("Started at: " +
response.startTime.toString() +
"ms ---> duration: " +
......
......@@ -63,7 +63,7 @@ let resourceObserverJS = """
window.webkit.messageHandlers['resourceLoaded'].postMessage(JSON.stringify(entry));
});
});
observer.observe({entryTypes: ['resource', 'mark', 'measure']});
observer.observe({entryTypes: ['resource']});
})();
"""
......@@ -586,26 +586,28 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
decidePolicyFor navigationResponse: WKNavigationResponse,
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
if (options?.useOnLoadResource)! {
// if (options?.useOnLoadResource)! {
// if let url = navigationResponse.response.url {
// if WKNavigationMap[url.absoluteString] != nil {
// let startResourceTime: Int64 = (WKNavigationMap[url.absoluteString]!["startTime"] as! Int64)
// let startTime: Int64 = startResourceTime - startPageTime;
// let duration: Int64 = currentTimeInMilliSeconds() - startResourceTime;
// onLoadResource(response: navigationResponse.response, fromRequest: WKNavigationMap[url.absoluteString]!["request"] as? URLRequest, withData: Data(), startTime: startTime, duration: duration)
// }
// }
// }
if (options?.useOnDownloadStart)! {
let mimeType = navigationResponse.response.mimeType
if let url = navigationResponse.response.url {
if WKNavigationMap[url.absoluteString] != nil {
let startResourceTime: Int64 = (WKNavigationMap[url.absoluteString]!["startTime"] as! Int64)
let startTime: Int64 = startResourceTime - startPageTime;
let duration: Int64 = currentTimeInMilliSeconds() - startResourceTime;
onLoadResource(response: navigationResponse.response, fromRequest: WKNavigationMap[url.absoluteString]!["request"] as? URLRequest, withData: Data(), startTime: startTime, duration: duration)
if mimeType != nil && !mimeType!.starts(with: "text/") {
onDownloadStart(url: url.absoluteString)
decisionHandler(.cancel)
return
}
}
}
let mimeType = navigationResponse.response.mimeType
if let url = navigationResponse.response.url {
if mimeType != nil && !mimeType!.starts(with: "text/") {
onDownloadStart(url: url.absoluteString)
decisionHandler(.cancel)
return
}
}
decisionHandler(.allow)
}
......@@ -703,27 +705,12 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
public func onLoadResource(response: URLResponse, fromRequest request: URLRequest?, withData data: Data, startTime: Int64, duration: Int64) {
var headersResponse = (response as! HTTPURLResponse).allHeaderFields as! [String: String]
headersResponse.lowercaseKeys()
var headersRequest = request!.allHTTPHeaderFields! as [String: String]
headersRequest.lowercaseKeys()
public func onLoadResource(initiatorType: String, url: String, startTime: Double, duration: Double) {
var arguments: [String : Any] = [
"response": [
"url": response.url!.absoluteString,
"statusCode": (response as! HTTPURLResponse).statusCode,
"headers": headersResponse,
"startTime": startTime,
"duration": duration,
"data": data
],
"request": [
"url": request!.url!.absoluteString,
"headers": headersRequest,
"method": request!.httpMethod!
]
"initiatorType": initiatorType,
"url": url,
"startTime": startTime,
"duration": duration
]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
......@@ -852,24 +839,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
if !UIApplication.shared.canOpenURL(url) {
return
}
let startTime: Int64 = Int64(resource["startTime"] as! Double)
let duration: Int64 = Int64(resource["duration"] as! Double)
var urlRequest = URLRequest(url: url)
urlRequest.allHTTPHeaderFields = [:]
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: urlRequest) { (data, response, error) in
if error != nil {
print(error)
return
}
var withData = data
if withData == nil {
withData = Data()
}
self.onLoadResource(response: response!, fromRequest: urlRequest, withData: withData!, startTime: startTime, duration: duration)
}
task.resume()
let initiatorType = resource["initiatorType"] as! String
let startTime = resource["startTime"] as! Double
let duration = resource["duration"] as! Double
self.onLoadResource(initiatorType: initiatorType, url: url.absoluteString, startTime: startTime, duration: duration)
}
}
else if message.name == "callHandler" {
......
......@@ -12,6 +12,7 @@ public class InAppWebViewOptions: Options {
var useShouldOverrideUrlLoading = false
var useOnLoadResource = false
var useOnDownloadStart = false
var useOnTargetBlank = false
var clearCache = false
var userAgent = ""
......@@ -21,6 +22,7 @@ public class InAppWebViewOptions: Options {
var verticalScrollBarEnabled = true
var horizontalScrollBarEnabled = true
var resourceCustomSchemes: [String] = []
var contentBlockers: [[String: [String : Any]]] = []
var disallowOverScroll = false
var enableViewportScale = false
......
......@@ -28,4 +28,5 @@ export 'src/channel_manager.dart';
export 'src/chrome_safari_browser.dart';
export 'src/chrome_safari_browser.dart';
export 'src/in_app_localhost_server.dart';
export 'src/web_history.dart';
export 'src/webview_options.dart';
export 'src/content_blocker.dart';
class ContentBlocker {
ContentBlockerTrigger trigger;
ContentBlockerAction action;
ContentBlocker(this.trigger, this.action);
Map<String, Map<String, dynamic>> toMap() {
return {
"trigger": trigger.toMap(),
"action": action.toMap()
};
}
}
class ContentBlockerTriggerResourceType {
final String _value;
const ContentBlockerTriggerResourceType._internal(this._value);
toString() => _value;
static const DOCUMENT = const ContentBlockerTriggerResourceType._internal('document');
static const IMAGE = const ContentBlockerTriggerResourceType._internal('image');
static const STYLE_SHEET = const ContentBlockerTriggerResourceType._internal('style-sheet');
static const SCRIPT = const ContentBlockerTriggerResourceType._internal('script');
static const FONT = const ContentBlockerTriggerResourceType._internal('font');
static const MEDIA = const ContentBlockerTriggerResourceType._internal('media');
static const SVG_DOCUMENT = const ContentBlockerTriggerResourceType._internal('svg-document');
static const RAW = const ContentBlockerTriggerResourceType._internal('raw');
}
class ContentBlockerTrigger {
String urlFilter;
List<ContentBlockerTriggerResourceType> resourceType;
ContentBlockerTrigger(this.urlFilter, {this.resourceType = const []});
Map<String, dynamic> toMap() {
List<String> resourceTypeStringList = [];
resourceType.forEach((type) {
resourceTypeStringList.add(type.toString());
});
return {
"url-filter": urlFilter,
"resource-type": resourceTypeStringList
};
}
}
class ContentBlockerActionType {
final String _value;
const ContentBlockerActionType._internal(this._value);
toString() => _value;
static const BLOCK = const ContentBlockerActionType._internal('block');
static const CSS_DISPLAY_NONE = const ContentBlockerActionType._internal('css-display-none');
static const MAKE_HTTPS = const ContentBlockerActionType._internal('make-https');
}
class ContentBlockerAction {
ContentBlockerActionType type;
String selector;
ContentBlockerAction(ContentBlockerActionType type, {String selector}) {
this.type = type;
if (this.type == ContentBlockerActionType.CSS_DISPLAY_NONE) {
assert(selector != null);
}
this.selector = selector;
}
Map<String, dynamic> toMap() {
return {
"type": type.toString(),
"selector": selector
};
}
}
\ No newline at end of file
......@@ -52,6 +52,7 @@ class InAppBrowser {
/// - All platforms support:
/// - __useShouldOverrideUrlLoading__: Set to `true` to be able to listen at the [shouldOverrideUrlLoading()] event. The default value is `false`.
/// - __useOnLoadResource__: Set to `true` to be able to listen at the [onLoadResource()] event. The default value is `false`.
/// - __useOnDownloadStart__: Set to `true` to be able to listen at the [onDownloadStart()] event. The default value is `false`.
/// - __useOnTargetBlank__: Set to `true` to be able to listen at the [onTargetBlank()] event. The default value is `false`.
/// - __clearCache__: Set to `true` to have all the browser's cache cleared before the new window is opened. The default value is `false`.
/// - __userAgent__: Set the custom WebView's user-agent.
......@@ -296,9 +297,7 @@ class InAppBrowser {
///Event fires when the [InAppBrowser] webview loads a resource.
///
///**NOTE**: In order to be able to listen this event, you need to set `useOnLoadResource` option to `true`.
///
///**NOTE only for iOS**: In some cases, the [response.data] of a [response] with `text/assets` encoding could be empty.
void onLoadResource(WebResourceResponse response, WebResourceRequest request) {
void onLoadResource(WebResourceResponse response) {
}
......
......@@ -12,8 +12,8 @@ import 'package:flutter/gestures.dart';
import 'types.dart';
import 'in_app_browser.dart';
import 'web_history.dart';
import 'channel_manager.dart';
import 'webview_options.dart';
///Initial [data] as a content for an [InAppWebView] instance, using [baseUrl] as the base URL for it.
......@@ -44,6 +44,7 @@ class InAppWebViewInitialData {
///All platforms support these options:
/// - __useShouldOverrideUrlLoading__: Set to `true` to be able to listen at the [InAppWebView.shouldOverrideUrlLoading()] event. The default value is `false`.
/// - __useOnLoadResource__: Set to `true` to be able to listen at the [InAppWebView.onLoadResource()] event. The default value is `false`.
/// - __useOnDownloadStart__: Set to `true` to be able to listen at the [InAppWebView.onDownloadStart()] event. The default value is `false`.
/// - __useOnTargetBlank__: Set to `true` to be able to listen at the [InAppWebView.onTargetBlank()] event. The default value is `false`.
/// - __clearCache__: Set to `true` to have all the browser's cache cleared before the new window is opened. The default value is `false`.
/// - __userAgent___: Set the custom WebView's user-agent.
......@@ -136,7 +137,7 @@ class InAppWebView extends StatefulWidget {
///Initial headers that will be used.
final Map<String, String> initialHeaders;
///Initial options that will be used.
final Map<String, dynamic> initialOptions;
final List<WebViewOptions> initialOptions;
/// `gestureRecognizers` specifies which gestures should be consumed by the web view.
/// It is possible for other gesture recognizers to be competing with the web view on pointer
/// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle
......@@ -152,7 +153,7 @@ class InAppWebView extends StatefulWidget {
this.initialFile,
this.initialData,
this.initialHeaders = const {},
this.initialOptions = const {},
this.initialOptions = const [],
this.onWebViewCreated,
this.onLoadStart,
this.onLoadStop,
......@@ -187,6 +188,11 @@ class _InAppWebViewState extends State<InAppWebView> {
@override
Widget build(BuildContext context) {
Map<String, dynamic> initialOptions = {};
widget.initialOptions.forEach((webViewOption) {
initialOptions.addAll(webViewOption.toMap());
});
if (defaultTargetPlatform == TargetPlatform.android) {
return GestureDetector(
onLongPress: () {},
......@@ -201,7 +207,7 @@ class _InAppWebViewState extends State<InAppWebView> {
'initialFile': widget.initialFile,
'initialData': widget.initialData?.toMap(),
'initialHeaders': widget.initialHeaders,
'initialOptions': widget.initialOptions
'initialOptions': initialOptions
},
creationParamsCodec: const StandardMessageCodec(),
),
......@@ -216,7 +222,7 @@ class _InAppWebViewState extends State<InAppWebView> {
'initialFile': widget.initialFile,
'initialData': widget.initialData?.toMap(),
'initialHeaders': widget.initialHeaders,
'initialOptions': widget.initialOptions
'initialOptions': initialOptions
},
creationParamsCodec: const StandardMessageCodec(),
);
......@@ -307,31 +313,17 @@ class InAppWebViewController {
_inAppBrowser.shouldOverrideUrlLoading(url);
break;
case "onLoadResource":
Map<dynamic, dynamic> rawResponse = call.arguments["response"];
rawResponse = rawResponse.cast<String, dynamic>();
Map<dynamic, dynamic> rawRequest = call.arguments["request"];
rawRequest = rawRequest.cast<String, dynamic>();
String urlResponse = rawResponse["url"];
Map<dynamic, dynamic> headersResponse = rawResponse["headers"];
headersResponse = headersResponse.cast<String, String>();
int statusCode = rawResponse["statusCode"];
int startTime = rawResponse["startTime"];
int duration = rawResponse["duration"];
Uint8List data = rawResponse["data"];
String urlRequest = rawRequest["url"];
Map<dynamic, dynamic> headersRequest = rawRequest["headers"];
headersRequest = headersResponse.cast<String, String>();
String method = rawRequest["method"];
var response = new WebResourceResponse(urlResponse, headersResponse, statusCode, startTime, duration, data);
var request = new WebResourceRequest(urlRequest, headersRequest, method);
String initiatorType = call.arguments["initiatorType"];
String url = call.arguments["url"];
double startTime = call.arguments["startTime"];
double duration = call.arguments["duration"];
var response = new WebResourceResponse(initiatorType, url, startTime, duration);
if (_widget != null && _widget.onLoadResource != null)
_widget.onLoadResource(this, response, request);
_widget.onLoadResource(this, response);
else if (_inAppBrowser != null)
_inAppBrowser.onLoadResource(response, request);
_inAppBrowser.onLoadResource(response);
break;
case "onConsoleMessage":
String sourceURL = call.arguments["sourceURL"];
......
......@@ -25,41 +25,34 @@ enum ConsoleMessageLevel {
DEBUG, ERROR, LOG, TIP, WARNING
}
///Public class representing a resource request of the [InAppBrowser] WebView.
///It is used by the method [InAppBrowser.onLoadResource()].
class WebResourceRequest {
String url;
Map<String, String> headers;
String method;
WebResourceRequest(this.url, this.headers, this.method);
}
///Public class representing a resource response of the [InAppBrowser] WebView.
///It is used by the method [InAppBrowser.onLoadResource()].
class WebResourceResponse {
///A string representing the type of resource.
String initiatorType;
///Resource URL.
String url;
Map<String, String> headers;
int statusCode;
int startTime;
int duration;
Uint8List data;
///Returns the [DOMHighResTimeStamp](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp) for the time a resource fetch started.
double startTime;
///Returns the [DOMHighResTimeStamp](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp) duration to fetch a resource.
double duration;
WebResourceResponse(this.url, this.headers, this.statusCode, this.startTime, this.duration, this.data);
WebResourceResponse(this.initiatorType, this.url, this.startTime, this.duration);
}
///Public class representing the response returned by the [onLoadResourceCustomScheme()] event of [InAppWebView].
///It allows to load a specific resource. The resource data must be encoded to `base64`.
class CustomSchemeResponse {
///Data enconded to 'base64'.
String base64data;
///Content-Type of the data, such as `image/png`.
String contentType;
///Content-Enconding of the data, such as `utf-8`.
String contentEnconding;
CustomSchemeResponse(this.base64data, this.contentType, this.contentEnconding);
CustomSchemeResponse(this.base64data, this.contentType, {this.contentEnconding = 'utf-8'});
Map<String, dynamic> toJson() {
return {
......@@ -84,6 +77,37 @@ class ConsoleMessage {
ConsoleMessage(this.sourceURL, this.lineNumber, this.message, this.messageLevel);
}
///WebHistory class.
///
///This class contains a snapshot of the current back/forward list for a WebView.
class WebHistory {
List<WebHistoryItem> _list;
///List of all [WebHistoryItem]s.
List<WebHistoryItem> get list => _list;
///Index of the current [WebHistoryItem].
int currentIndex;
WebHistory(this._list, this.currentIndex);
}
///WebHistoryItem class.
///
///A convenience class for accessing fields in an entry in the back/forward list of a WebView. Each WebHistoryItem is a snapshot of the requested history item.
class WebHistoryItem {
///Original url of this history item.
String originalUrl;
///Document title of this history item.
String title;
///Url of this history item.
String url;
///0-based position index in the back-forward [WebHistory.list].
int index;
///Position offset respect to the currentIndex of the back-forward [WebHistory.list].
int offset;
WebHistoryItem(this.originalUrl, this.title, this.url, this.index, this.offset);
}
typedef onWebViewCreatedCallback = void Function(InAppWebViewController controller);
typedef onWebViewLoadStartCallback = void Function(InAppWebViewController controller, String url);
typedef onWebViewLoadStopCallback = void Function(InAppWebViewController controller, String url);
......@@ -91,7 +115,7 @@ typedef onWebViewLoadErrorCallback = void Function(InAppWebViewController contro
typedef onWebViewProgressChangedCallback = void Function(InAppWebViewController controller, int progress);
typedef onWebViewConsoleMessageCallback = void Function(InAppWebViewController controller, ConsoleMessage consoleMessage);
typedef shouldOverrideUrlLoadingCallback = void Function(InAppWebViewController controller, String url);
typedef onWebViewLoadResourceCallback = void Function(InAppWebViewController controller, WebResourceResponse response, WebResourceRequest request);
typedef onWebViewLoadResourceCallback = void Function(InAppWebViewController controller, WebResourceResponse response);
typedef onWebViewScrollChangedCallback = void Function(InAppWebViewController controller, int x, int y);
typedef onDownloadStartCallback = void Function(InAppWebViewController controller, String url);
typedef onLoadResourceCustomSchemeCallback = Future<CustomSchemeResponse> Function(InAppWebViewController controller, String scheme, String url);
......
///WebHistory class.
///
///This class contains a snapshot of the current back/forward list for a WebView.
class WebHistory {
List<WebHistoryItem> _list;
///List of all [WebHistoryItem]s.
List<WebHistoryItem> get list => _list;
///Index of the current [WebHistoryItem].
int currentIndex;
WebHistory(this._list, this.currentIndex);
}
///WebHistoryItem class.
///
///A convenience class for accessing fields in an entry in the back/forward list of a WebView. Each WebHistoryItem is a snapshot of the requested history item.
class WebHistoryItem {
///Original url of this history item.
String originalUrl;
///Document title of this history item.
String title;
///Url of this history item.
String url;
///0-based position index in the back-forward [WebHistory.list].
int index;
///Position offset respect to the currentIndex of the back-forward [WebHistory.list].
int offset;
WebHistoryItem(this.originalUrl, this.title, this.url, this.index, this.offset);
}
\ No newline at end of file
import 'package:flutter_inappbrowser/src/content_blocker.dart';
class WebViewOptions {
Map<String, dynamic> toMap() {
return {};
}
}
class InAppWebViewOptions implements WebViewOptions {
bool useShouldOverrideUrlLoading;
bool useOnLoadResource;
bool useOnDownloadStart;
bool useOnTargetBlank;
bool clearCache;
String userAgent;
bool javaScriptEnabled;
bool javaScriptCanOpenWindowsAutomatically;
bool mediaPlaybackRequiresUserGesture;
int textZoom;
bool verticalScrollBarEnabled;
bool horizontalScrollBarEnabled;
List<String> resourceCustomSchemes;
List<ContentBlocker> contentBlockers;
InAppWebViewOptions({this.useShouldOverrideUrlLoading = false, this.useOnLoadResource = false, this.useOnDownloadStart = false, this.useOnTargetBlank = false,
this.clearCache = false, this.userAgent = "", this.javaScriptEnabled = true, this.javaScriptCanOpenWindowsAutomatically = false,
this.mediaPlaybackRequiresUserGesture = true, this.textZoom = 100, this.verticalScrollBarEnabled = true, this.horizontalScrollBarEnabled = true, this.resourceCustomSchemes = const [],
this.contentBlockers = const []});
@override
Map<String, dynamic> toMap() {
List<Map<String, Map<String, dynamic>>> contentBlockersMapList = [];
contentBlockers.forEach((contentBlocker) {
contentBlockersMapList.add(contentBlocker.toMap());
});
return {
"useShouldOverrideUrlLoading": useShouldOverrideUrlLoading,
"useOnLoadResource": useOnLoadResource,
"useOnDownloadStart": useOnDownloadStart,
"useOnTargetBlank": useOnTargetBlank,
"clearCache": clearCache,
"userAgent": userAgent,
"javaScriptEnabled": javaScriptEnabled,
"javaScriptCanOpenWindowsAutomatically": javaScriptCanOpenWindowsAutomatically,
"mediaPlaybackRequiresUserGesture": mediaPlaybackRequiresUserGesture,
"textZoom": textZoom,
"verticalScrollBarEnabled": verticalScrollBarEnabled,
"horizontalScrollBarEnabled": horizontalScrollBarEnabled,
"resourceCustomSchemes": resourceCustomSchemes,
"contentBlockers": contentBlockersMapList,
};
}
}
class AndroidInAppWebViewOptions implements WebViewOptions {
bool clearSessionCache;
bool builtInZoomControls;
bool displayZoomControls;
bool supportZoom;
bool databaseEnabled;
bool domStorageEnabled;
bool useWideViewPort;
bool safeBrowsingEnabled;
bool transparentBackground;
String mixedContentMode;
AndroidInAppWebViewOptions({this.clearSessionCache = false, this.builtInZoomControls = false, this.displayZoomControls = false, this.supportZoom = true, this.databaseEnabled = false,
this.domStorageEnabled = false, this.useWideViewPort = true, this.safeBrowsingEnabled = true, this.transparentBackground = false, this.mixedContentMode = ""});
@override
Map<String, dynamic> toMap() {
return {
"clearSessionCache": clearSessionCache,
"builtInZoomControls": builtInZoomControls,
"displayZoomControls": displayZoomControls,
"supportZoom": supportZoom,
"databaseEnabled": databaseEnabled,
"domStorageEnabled": domStorageEnabled,
"useWideViewPort": useWideViewPort,
"safeBrowsingEnabled": safeBrowsingEnabled,
"transparentBackground": transparentBackground,
"mixedContentMode": mixedContentMode,
};
}
}
class iOSInAppWebViewOptions implements WebViewOptions {
bool disallowOverScroll;
bool enableViewportScale;
bool suppressesIncrementalRendering;
bool allowsAirPlayForMediaPlayback;
bool allowsBackForwardNavigationGestures;
bool allowsLinkPreview;
bool ignoresViewportScaleLimits;
bool allowsInlineMediaPlayback;
bool allowsPictureInPictureMediaPlayback;
bool transparentBackground;
iOSInAppWebViewOptions({this.disallowOverScroll = false, this.enableViewportScale = false, this.suppressesIncrementalRendering = false, this.allowsAirPlayForMediaPlayback = true,
this.allowsBackForwardNavigationGestures = true, this.allowsLinkPreview = true, this.ignoresViewportScaleLimits = false, this.allowsInlineMediaPlayback = false,
this.allowsPictureInPictureMediaPlayback = true, this.transparentBackground = false});
@override
Map<String, dynamic> toMap() {
return {
"disallowOverScroll": disallowOverScroll,
"enableViewportScale": enableViewportScale,
"suppressesIncrementalRendering": suppressesIncrementalRendering,
"allowsAirPlayForMediaPlayback": allowsAirPlayForMediaPlayback,
"allowsBackForwardNavigationGestures": allowsBackForwardNavigationGestures,
"allowsLinkPreview": allowsLinkPreview,
"ignoresViewportScaleLimits": ignoresViewportScaleLimits,
"allowsInlineMediaPlayback": allowsInlineMediaPlayback,
"allowsPictureInPictureMediaPlayback": allowsPictureInPictureMediaPlayback,
"transparentBackground": transparentBackground,
};
}
}
class InAppBrowserOptions implements WebViewOptions {
bool hidden;
bool toolbarTop;
String toolbarTopBackgroundColor;
String toolbarTopFixedTitle;
bool hideUrlBar;
InAppBrowserOptions({this.hidden = false, this.toolbarTop = true, this.toolbarTopBackgroundColor = "", this.toolbarTopFixedTitle = "", this.hideUrlBar = false});
@override
Map<String, dynamic> toMap() {
return {
"hidden": hidden,
"toolbarTop": toolbarTop,
"toolbarTopBackgroundColor": toolbarTopBackgroundColor,
"toolbarTopFixedTitle": toolbarTopFixedTitle,
"hideUrlBar": hideUrlBar,
};
}
}
class AndroidInAppBrowserOptions implements WebViewOptions {
bool hideTitleBar;
bool closeOnCannotGoBack;
bool progressBar;
AndroidInAppBrowserOptions({this.hideTitleBar = true, this.closeOnCannotGoBack = true, this.progressBar = true});
@override
Map<String, dynamic> toMap() {
return {
"hideTitleBar": hideTitleBar,
"closeOnCannotGoBack": closeOnCannotGoBack,
"progressBar": progressBar,
};
}
}
class iOSInAppBrowserOptions implements WebViewOptions {
bool toolbarBottom;
String toolbarBottomBackgroundColor;
bool toolbarBottomTranslucent;
String closeButtonCaption;
String closeButtonColor;
int presentationStyle; //default fullscreen
int transitionStyle; //default crossDissolve
bool spinner;
iOSInAppBrowserOptions({this.toolbarBottom = true, this.toolbarBottomBackgroundColor = "", this.toolbarBottomTranslucent = true, this.closeButtonCaption = "",
this.closeButtonColor = "", this.presentationStyle = 0, this.transitionStyle = 0, this.spinner = true});
@override
Map<String, dynamic> toMap() {
return {
"toolbarBottom": toolbarBottom,
"toolbarBottomBackgroundColor": toolbarBottomBackgroundColor,
"toolbarBottomTranslucent": toolbarBottomTranslucent,
"closeButtonCaption": closeButtonCaption,
"closeButtonColor": closeButtonColor,
"presentationStyle": presentationStyle,
"transitionStyle": transitionStyle,
"spinner": spinner,
};
}
}
class AndroidChromeCustomTabsOptions implements WebViewOptions {
bool addShareButton;
bool showTitle;
String toolbarBackgroundColor;
bool enableUrlBarHiding;
bool instantAppsEnabled;
AndroidChromeCustomTabsOptions({this.addShareButton = true, this.showTitle = true, this.toolbarBackgroundColor = "", this.enableUrlBarHiding = false, this.instantAppsEnabled = false});
@override
Map<String, dynamic> toMap() {
return {
"addShareButton": addShareButton,
"showTitle": showTitle,
"toolbarBackgroundColor": toolbarBackgroundColor,
"enableUrlBarHiding": enableUrlBarHiding,
"instantAppsEnabled": instantAppsEnabled,
};
}
}
class iOSChromeCustomTabsOptions implements WebViewOptions {
bool entersReaderIfAvailable;
bool barCollapsingEnabled;
int dismissButtonStyle; //default done
String preferredBarTintColor;
String preferredControlTintColor;
int presentationStyle; //default fullscreen
int transitionStyle; //default crossDissolve
iOSChromeCustomTabsOptions({this.entersReaderIfAvailable = false, this.barCollapsingEnabled = false, this.dismissButtonStyle = 0, this.preferredBarTintColor = "",
this.preferredControlTintColor = "", this.presentationStyle = 0, this.transitionStyle = 0});
@override
Map<String, dynamic> toMap() {
return {
"entersReaderIfAvailable": entersReaderIfAvailable,
"barCollapsingEnabled": barCollapsingEnabled,
"dismissButtonStyle": dismissButtonStyle,
"preferredBarTintColor": preferredBarTintColor,
"preferredControlTintColor": preferredControlTintColor,
"presentationStyle": presentationStyle,
"transitionStyle": transitionStyle,
};
}
}
\ No newline at end of file
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