Commit b9d14828 authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

added getScrollX and getScrollY webview methods, added HttpOnly and SameSite...

added getScrollX and getScrollY webview methods, added HttpOnly and SameSite set cookie options, added animated option to scrollTo and scrollBy webview methods, Added error and message to the ServerTrustChallenge class for iOS, added contentInsetAdjustmentBehavior webview iOS-specific option, added getCertificate android-specific webview method, added copy and copyWithValue methods for webview class options
parent 1b2de863
## 3.4.0
- Added `requestFocusNodeHref`, `requestImageRef`, `getMetaTags`, `getMetaThemeColor` webview methods
- Added `requestFocusNodeHref`, `requestImageRef`, `getMetaTags`, `getMetaThemeColor`, `getScrollX`, `getScrollY` webview methods
- Added `WebStorage`, `LocalStorage` and `SessionStorage` class to manage `window.localStorage` and `window.sessionStorage` JavaScript [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)
- Added `supportZoom` webview option also on iOS
- Added `HttpOnly`, `SameSite` set cookie options
- Updated `Cookie` class
- Added `animated` option to `scrollTo` and `scrollBy` webview methods
- Added error and message to the `ServerTrustChallenge` class for iOS (class used by the `onReceivedServerTrustAuthRequest` event)
- Added `contentInsetAdjustmentBehavior` webview iOS-specific option
- Added `getCertificate` android-specific webview method
- Added `copy` and `copyWithValue` methods for webview class options
- Fixed `zoomBy`, `setOptions` webview methods on Android
- Fixed `databaseEnabled` android webview option default value to `true`
......@@ -12,6 +19,8 @@
- `getHtml` webview method now could return `null` if it was unable to get it.
- Moved `supportZoom` webview option to cross-platform
- `builtInZoomControls` android webview options changed default value to `true`
- Updated `ServerTrustChallenge` class used by the `onReceivedServerTrustAuthRequest` event
- The method `getOptions`could return null now
## 3.3.0+3
......
......@@ -59,6 +59,10 @@ Because of [Flutter AndroidX compatibility](https://flutter.dev/docs/development
Also, note that to use the `InAppWebView` widget on Android, it requires **Android API 20+** (see [AndroidView](https://api.flutter.dev/flutter/widgets/AndroidView-class.html)).
**Support HTTP request**: Starting with Android 9 (API level 28), cleartext support is disabled by default:
- Check the official [Network security configuration - "Opt out of cleartext traffic"](https://developer.android.com/training/articles/security-config#CleartextTrafficPermitted) section.
- Also, check this StackOverflow issue answer: [Cleartext HTTP traffic not permitted](https://stackoverflow.com/a/50834600/4637638).
### IMPORTANT Note for iOS
If you are starting a new fresh app, you need to create the Flutter App with `flutter create --androidx -i swift`
......@@ -392,8 +396,8 @@ Screenshots:
* `clearMatches`: Clears the highlighting surrounding text matches created by `findAllAsync()`.
* `getTRexRunnerHtml`: Gets the html (with javascript) of the Chromium's t-rex runner game. Used in combination with `getTRexRunnerCss()`.
* `getTRexRunnerCss`: Gets the css of the Chromium's t-rex runner game. Used in combination with `getTRexRunnerHtml()`.
* `scrollTo({@required int x, @required int y})`: Scrolls the WebView to the position.
* `scrollBy({@required int x, @required int y})`: Moves the scrolled position of the WebView.
* `scrollTo({@required int x, @required int y, bool animated = false})`: Scrolls the WebView to the position.
* `scrollBy({@required int x, @required int y, bool animated = false})`: Moves the scrolled position of the WebView.
* `pauseTimers`: On Android, it pauses all layout, parsing, and JavaScript timers for all WebViews. This is a global requests, not restricted to just this WebView. This can be useful if the application has been paused. On iOS, it is restricted to just this WebView.
* `resumeTimers`: On Android, it resumes all layout, parsing, and JavaScript timers for all WebViews. This will resume dispatching all timers. On iOS, it resumes all layout, parsing, and JavaScript timers to just this WebView.
* `printCurrentPage`: Prints the current page.
......@@ -406,6 +410,8 @@ Screenshots:
* `requestImageRef`: Requests the URL of the image last touched by the user.
* `getMetaTags`: Returns the list of `<meta>` tags of the current WebView.
* `getMetaThemeColor`: Returns an instance of `Color` representing the `content` value of the `<meta name="theme-color" content="">` tag of the current WebView, if available, otherwise `null`.
* `getScrollX`: Returns the scrolled left position of the current WebView.
* `getScrollY`: Returns the scrolled top position of the current WebView.
* `static getDefaultUserAgent`: Gets the default user agent.
##### `InAppWebViewController` Android-specific methods
......@@ -587,6 +593,7 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
* `isPagingEnabled`: A Boolean value that determines whether paging is enabled for the scroll view. The default value is `false`.
* `maximumZoomScale`: A floating-point value that specifies the maximum scale factor that can be applied to the scroll view's content. The default value is `1.0`.
* `minimumZoomScale`: A floating-point value that specifies the minimum scale factor that can be applied to the scroll view's content. The default value is `1.0`.
* `contentInsetAdjustmentBehavior`: Configures how safe area insets are added to the adjusted content inset. The default value is `IOSUIScrollViewContentInsetAdjustmentBehavior.NEVER`.
#### `InAppWebView` Events
......
......@@ -280,7 +280,8 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
{
Integer x = (Integer) call.argument("x");
Integer y = (Integer) call.argument("y");
scrollTo(x, y);
Boolean animated = (Boolean) call.argument("animated");
scrollTo(x, y, animated);
}
result.success(true);
break;
......@@ -288,7 +289,8 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
{
Integer x = (Integer) call.argument("x");
Integer y = (Integer) call.argument("y");
scrollBy(x, y);
Boolean animated = (Boolean) call.argument("animated");
scrollBy(x, y, animated);
}
result.success(true);
break;
......@@ -376,6 +378,21 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
}
result.success(true);
break;
case "requestFocusNodeHref":
result.success(requestFocusNodeHref());
break;
case "requestImageRef":
result.success(requestImageRef());
break;
case "getScrollX":
result.success(getScrollX());
break;
case "getScrollY":
result.success(getScrollY());
break;
case "getCertificate":
result.success(getCertificate());
break;
default:
result.notImplemented();
}
......@@ -804,14 +821,14 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
result.success(false);
}
public void scrollTo(Integer x, Integer y) {
public void scrollTo(Integer x, Integer y, Boolean animated) {
if (webView != null)
webView.scrollTo(x, y);
webView.scrollTo(x, y, animated);
}
public void scrollBy(Integer x, Integer y) {
public void scrollBy(Integer x, Integer y, Boolean animated) {
if (webView != null)
webView.scrollBy(x, y);
webView.scrollBy(x, y, animated);
}
public void onPauseWebView() {
......@@ -930,6 +947,36 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
webView.contextMenu = contextMenu;
}
public Map<String, Object> requestFocusNodeHref() {
if (webView != null)
return webView.requestFocusNodeHref();
return null;
}
public Map<String, Object> requestImageRef() {
if (webView != null)
return webView.requestImageRef();
return null;
}
public Integer getScrollX() {
if (webView != null)
return webView.getScrollX();
return null;
}
public Integer getScrollY() {
if (webView != null)
return webView.getScrollY();
return null;
}
public Map<String, Object> getCertificate() {
if (webView != null)
return webView.getSslCertificate();
return null;
}
public void dispose() {
channel.setMethodCallHandler(null);
activityResultListeners.clear();
......
......@@ -5,7 +5,6 @@ import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.webkit.ValueCallback;
......@@ -308,7 +307,8 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
if (webView != null) {
Integer x = (Integer) call.argument("x");
Integer y = (Integer) call.argument("y");
webView.scrollTo(x, y);
Boolean animated = (Boolean) call.argument("animated");
webView.scrollTo(x, y, animated);
}
result.success(true);
break;
......@@ -316,7 +316,8 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
if (webView != null) {
Integer x = (Integer) call.argument("x");
Integer y = (Integer) call.argument("y");
webView.scrollBy(x, y);
Boolean animated = (Boolean) call.argument("animated");
webView.scrollBy(x, y, animated);
}
result.success(true);
break;
......@@ -461,14 +462,35 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
if (webView != null) {
result.success(webView.requestFocusNodeHref());
} else {
result.success(false);
result.success(null);
}
break;
case "requestImageRef":
if (webView != null) {
result.success(webView.requestImageRef());
} else {
result.success(false);
result.success(null);
}
break;
case "getScrollX":
if (webView != null) {
result.success(webView.getScrollX());
} else {
result.success(null);
}
break;
case "getScrollY":
if (webView != null) {
result.success(webView.getScrollY());
} else {
result.success(null);
}
break;
case "getCertificate":
if (webView != null) {
result.success(webView.getSslCertificate());
} else {
result.success(null);
}
break;
default:
......
package com.pichillilorenzo.flutter_inappwebview.InAppWebView;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.net.http.SslCertificate;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
......@@ -36,6 +39,7 @@ import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Keep;
import androidx.annotation.RequiresApi;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
......@@ -52,6 +56,11 @@ import com.pichillilorenzo.flutter_inappwebview.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
......@@ -1484,6 +1493,28 @@ final public class InAppWebView extends InputAwareWebView {
channel.invokeMethod("onScrollChanged", obj);
}
public void scrollTo(Integer x, Integer y, Boolean animated) {
if (animated) {
PropertyValuesHolder pvhX = PropertyValuesHolder.ofInt("scrollX", x);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofInt("scrollY", y);
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(this, pvhX, pvhY);
anim.setDuration(300).start();
} else {
scrollTo(x, y);
}
}
public void scrollBy(Integer x, Integer y, Boolean animated) {
if (animated) {
PropertyValuesHolder pvhX = PropertyValuesHolder.ofInt("scrollX", getScrollX() + x);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofInt("scrollY", getScrollY() + y);
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(this, pvhX, pvhY);
anim.setDuration(300).start();
} else {
scrollBy(x, y);
}
}
class DownloadStartListener implements DownloadListener {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
......@@ -1833,6 +1864,110 @@ final public class InAppWebView extends InputAwareWebView {
return obj;
}
public Map<String, Object> getSslCertificate() {
SslCertificate sslCertificate = getCertificate();
SslCertificate.DName issuedByName = sslCertificate.getIssuedBy();
Map<String, Object> issuedBy = new HashMap<>();
issuedBy.put("CName", issuedByName.getCName());
issuedBy.put("DName", issuedByName.getDName());
issuedBy.put("OName", issuedByName.getOName());
issuedBy.put("UName", issuedByName.getUName());
SslCertificate.DName issuedToName = sslCertificate.getIssuedTo();
Map<String, Object> issuedTo = new HashMap<>();
issuedTo.put("CName", issuedToName.getCName());
issuedTo.put("DName", issuedToName.getDName());
issuedTo.put("OName", issuedToName.getOName());
issuedTo.put("UName", issuedToName.getUName());
Map<String, Object> x509CertificateMap = new HashMap<>();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
X509Certificate x509Certificate = sslCertificate.getX509Certificate();
if (x509Certificate != null) {
x509CertificateMap.put("basicConstraints", x509Certificate.getBasicConstraints());
try {
x509CertificateMap.put("extendedKeyUsage", x509Certificate.getExtendedKeyUsage());
} catch (CertificateParsingException e) {
x509CertificateMap.put("extendedKeyUsage", null);
}
Map<String, Object> issuerDN = new HashMap<>();
issuerDN.put("name", x509Certificate.getIssuerDN().getName());
x509CertificateMap.put("issuerDN", issuerDN);
x509CertificateMap.put("issuerUniqueID", x509Certificate.getIssuerUniqueID());
Map<String, Object> issuerX500Principal = new HashMap<>();
issuerX500Principal.put("name", x509Certificate.getIssuerX500Principal().getName());
issuerX500Principal.put("encoded", x509Certificate.getIssuerX500Principal().getEncoded());
x509CertificateMap.put("issuerX500Principal", issuerX500Principal);
x509CertificateMap.put("keyUsage", x509Certificate.getKeyUsage());
x509CertificateMap.put("notAfter", x509Certificate.getNotAfter().getTime());
x509CertificateMap.put("notBefore", x509Certificate.getNotBefore().getTime());
x509CertificateMap.put("serialNumber", x509Certificate.getSerialNumber().longValue());
x509CertificateMap.put("sigAlgName", x509Certificate.getSigAlgName());
x509CertificateMap.put("sigAlgOID", x509Certificate.getSigAlgOID());
x509CertificateMap.put("sigAlgParams", x509Certificate.getSigAlgParams());
x509CertificateMap.put("signature", x509Certificate.getSignature());
Map<String, Object> subjectDN = new HashMap<>();
subjectDN.put("name", x509Certificate.getSubjectDN().getName());
x509CertificateMap.put("subjectDN", subjectDN);
x509CertificateMap.put("subjectUniqueID", x509Certificate.getSubjectUniqueID());
Map<String, Object> subjectX500Principal = new HashMap<>();
subjectX500Principal.put("name", x509Certificate.getSubjectX500Principal().getName());
subjectX500Principal.put("encoded", x509Certificate.getSubjectX500Principal().getEncoded());
x509CertificateMap.put("subjectX500Principal", subjectX500Principal);
try {
x509CertificateMap.put("TBSCertificate", x509Certificate.getTBSCertificate());
} catch (CertificateEncodingException e) {
x509CertificateMap.put("TBSCertificate", null);
}
x509CertificateMap.put("version", x509Certificate.getVersion());
x509CertificateMap.put("criticalExtensionOIDs", x509Certificate.getCriticalExtensionOIDs());
x509CertificateMap.put("nonCriticalExtensionOIDs", x509Certificate.getNonCriticalExtensionOIDs());
try {
x509CertificateMap.put("encoded", x509Certificate.getEncoded());
} catch (CertificateEncodingException e) {
x509CertificateMap.put("encoded", null);
}
Map<String, Object> publicKey = new HashMap<>();
publicKey.put("algorithm", x509Certificate.getPublicKey().getAlgorithm());
publicKey.put("encoded", x509Certificate.getPublicKey().getEncoded());
publicKey.put("format", x509Certificate.getPublicKey().getFormat());
x509CertificateMap.put("publicKey", publicKey);
x509CertificateMap.put("type", x509Certificate.getType());
x509CertificateMap.put("hasUnsupportedCriticalExtension", x509Certificate.hasUnsupportedCriticalExtension());
try {
x509Certificate.checkValidity();
x509CertificateMap.put("valid", true);
} catch (CertificateExpiredException e) {
x509CertificateMap.put("valid", false);
} catch (CertificateNotYetValidException e) {
x509CertificateMap.put("valid", false);
}
}
}
Map<String, Object> obj = new HashMap<>();
obj.put("issuedBy", issuedBy);
obj.put("issuedTo", issuedTo);
obj.put("validNotAfterDate", sslCertificate.getValidNotAfterDate().getTime());
obj.put("validNotBeforeDate", sslCertificate.getValidNotBeforeDate().getTime());
obj.put("x509Certificate", x509CertificateMap);
return obj;
}
@Override
public void dispose() {
super.dispose();
......
......@@ -29,9 +29,11 @@ import com.pichillilorenzo.flutter_inappwebview.CredentialDatabase.Credential;
import com.pichillilorenzo.flutter_inappwebview.CredentialDatabase.CredentialDatabase;
import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappwebview.Shared;
import com.pichillilorenzo.flutter_inappwebview.Util;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
......@@ -426,7 +428,8 @@ public class InAppWebViewClient extends WebViewClient {
obj.put("protocol", protocol);
obj.put("realm", realm);
obj.put("port", port);
obj.put("error", error.getPrimaryError());
obj.put("androidError", error.getPrimaryError());
obj.put("iosError", null);
obj.put("serverCertificate", null);
try {
X509Certificate certificate;
......
......@@ -6,6 +6,8 @@ import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.ValueCallback;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
......@@ -47,7 +49,19 @@ public class MyCookieManager implements MethodChannel.MethodCallHandler {
Long expiresDate = (expiresDateString != null ? new Long(expiresDateString) : null);
Integer maxAge = (Integer) call.argument("maxAge");
Boolean isSecure = (Boolean) call.argument("isSecure");
MyCookieManager.setCookie(url, name, value, domain, path, expiresDate, maxAge, isSecure, result);
Boolean isHttpOnly = (Boolean) call.argument("isHttpOnly");
String sameSite = (String) call.argument("sameSite");
MyCookieManager.setCookie(url,
name,
value,
domain,
path,
expiresDate,
maxAge,
isSecure,
isHttpOnly,
sameSite,
result);
}
break;
case "getCookies":
......@@ -86,6 +100,8 @@ public class MyCookieManager implements MethodChannel.MethodCallHandler {
Long expiresDate,
Integer maxAge,
Boolean isSecure,
Boolean isHttpOnly,
String sameSite,
final MethodChannel.Result result) {
String cookieValue = name + "=" + value + "; Domain=" + domain + "; Path=" + path;
......@@ -99,6 +115,12 @@ public class MyCookieManager implements MethodChannel.MethodCallHandler {
if (isSecure != null && isSecure)
cookieValue += "; Secure";
if (isHttpOnly != null && isHttpOnly)
cookieValue += "; HttpOnly";
if (sameSite != null)
cookieValue += "; SameSite=" + sameSite;
cookieValue += ";";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
......@@ -135,6 +157,14 @@ public class MyCookieManager implements MethodChannel.MethodCallHandler {
Map<String, Object> cookieMap = new HashMap<>();
cookieMap.put("name", name);
cookieMap.put("value", value);
cookieMap.put("expiresDate", null);
cookieMap.put("isSessionOnly", null);
cookieMap.put("domain", null);
cookieMap.put("sameSite", null);
cookieMap.put("isSecure", null);
cookieMap.put("isHttpOnly", null);
cookieMap.put("path", null);
cookieListMap.add(cookieMap);
}
}
......
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"android":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+3/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"e2e","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-06-12 02:54:04.283438","version":"1.17.1"}
\ No newline at end of file
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"android":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+3/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"e2e","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-06-13 01:39:11.912485","version":"1.17.1"}
\ No newline at end of file
......@@ -16,6 +16,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
ContextMenu contextMenu;
String url = "";
double progress = 0;
CookieManager _cookieManager = CookieManager.instance();
@override
void initState() {
......@@ -78,8 +79,8 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
BoxDecoration(border: Border.all(color: Colors.blueAccent)),
child: InAppWebView(
contextMenu: contextMenu,
// initialUrl: "https://github.com/flutter",
initialFile: "assets/index.html",
initialUrl: "https://github.com/flutter",
// initialFile: "assets/index.html",
initialHeaders: {},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
......@@ -101,37 +102,12 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
print("shouldOverrideUrlLoading");
return ShouldOverrideUrlLoadingAction.ALLOW;
},
onCreateWindow: (controller, onCreateWindowRequest) {
print("onCreateWindow");
},
onLoadStop: (InAppWebViewController controller, String url) async {
print("onLoadStop $url");
setState(() {
this.url = url;
});
/*var origins = await WebStorageManager.instance().android.getOrigins();
for (var origin in origins) {
print(origin);
print(await WebStorageManager.instance().android.getQuotaForOrigin(origin: origin.origin));
print(await WebStorageManager.instance().android.getUsageForOrigin(origin: origin.origin));
}
await WebStorageManager.instance().android.deleteAllData();
print("\n\nDELETED\n\n");
origins = await WebStorageManager.instance().android.getOrigins();
for (var origin in origins) {
print(origin);
await WebStorageManager.instance().android.deleteOrigin(origin: origin.origin);
}*/
/*var records = await WebStorageManager.instance().ios.fetchDataRecords(dataTypes: IOSWKWebsiteDataType.ALL);
for(var record in records) {
print(record);
}
await WebStorageManager.instance().ios.removeDataModifiedSince(dataTypes: IOSWKWebsiteDataType.ALL, date: DateTime(0));
print("\n\nDELETED\n\n");
records = await WebStorageManager.instance().ios.fetchDataRecords(dataTypes: IOSWKWebsiteDataType.ALL);
for(var record in records) {
print(record);
}*/
},
onProgressChanged: (InAppWebViewController controller, int progress) {
setState(() {
......
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
......
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
......
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
......
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
......
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
......
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
......
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
......
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
......
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
......
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
......
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
......
......@@ -347,7 +347,8 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
if webView != nil {
let x = arguments!["x"] as! Int
let y = arguments!["y"] as! Int
webView!.scrollTo(x: x, y: y)
let animated = arguments!["animated"] as! Bool
webView!.scrollTo(x: x, y: y, animated: animated)
}
result(true)
break
......@@ -355,7 +356,8 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
if webView != nil {
let x = arguments!["x"] as! Int
let y = arguments!["y"] as! Int
webView!.scrollBy(x: x, y: y)
let animated = arguments!["animated"] as! Bool
webView!.scrollBy(x: x, y: y, animated: animated)
}
result(true)
break
......@@ -475,6 +477,20 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
result(false)
}
break
case "getScrollX":
if webView != nil {
result(Int(webView!.scrollView.contentOffset.x))
} else {
result(false)
}
break
case "getScrollY":
if webView != nil {
result(Int(webView!.scrollView.contentOffset.y))
} else {
result(false)
}
break
default:
result(FlutterMethodNotImplemented)
break
......
......@@ -243,13 +243,15 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS
case "scrollTo":
let x = arguments!["x"] as! Int
let y = arguments!["y"] as! Int
webView.scrollTo(x: x, y: y)
let animated = arguments!["animated"] as! Bool
webView.scrollTo(x: x, y: y, animated: animated)
result(true)
break
case "scrollBy":
let x = arguments!["x"] as! Int
let y = arguments!["y"] as! Int
webView.scrollTo(x: x, y: y)
let animated = arguments!["animated"] as! Bool
webView.scrollTo(x: x, y: y, animated: animated)
result(true)
break
case "pauseTimers":
......
......@@ -1105,6 +1105,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
if #available(iOS 11.0, *) {
accessibilityIgnoresInvertColors = (options?.accessibilityIgnoresInvertColors)!
scrollView.contentInsetAdjustmentBehavior =
UIScrollView.ContentInsetAdjustmentBehavior.init(rawValue: (options?.contentInsetAdjustmentBehavior)!)!
}
configuration.suppressesIncrementalRendering = (options?.suppressesIncrementalRendering)!
......@@ -1488,6 +1490,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
if newOptionsMap["accessibilityIgnoresInvertColors"] != nil && options?.accessibilityIgnoresInvertColors != newOptions.accessibilityIgnoresInvertColors {
accessibilityIgnoresInvertColors = newOptions.accessibilityIgnoresInvertColors
}
if newOptionsMap["contentInsetAdjustmentBehavior"] != nil && options?.contentInsetAdjustmentBehavior != newOptions.contentInsetAdjustmentBehavior {
scrollView.contentInsetAdjustmentBehavior =
UIScrollView.ContentInsetAdjustmentBehavior.init(rawValue: newOptions.contentInsetAdjustmentBehavior)!
}
}
if newOptionsMap["enableViewportScale"] != nil && options?.enableViewportScale != newOptions.enableViewportScale {
......@@ -2542,6 +2548,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
public func onReceivedServerTrustAuthRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) {
var serverCertificateData: NSData?
let serverTrust = challenge.protectionSpace.serverTrust!
var secResult = SecTrustResultType.invalid
SecTrustEvaluate(serverTrust, &secResult);
if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
let serverCertificateCFData = SecCertificateCopyData(serverCertificate)
let data = CFDataGetBytePtr(serverCertificateCFData)
......@@ -2549,6 +2559,32 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
serverCertificateData = NSData(bytes: data, length: size)
}
let error = secResult != SecTrustResultType.proceed ? secResult.rawValue : nil
var message = ""
switch secResult {
case .deny:
message = "Indicates a user-configured deny; do not proceed."
break
case .fatalTrustFailure:
message = "Indicates a trust failure which cannot be overridden by the user."
break
case .invalid:
message = "Indicates an invalid setting or result."
break
case .otherError:
message = "Indicates a failure other than that of trust evaluation."
break
case .recoverableTrustFailure:
message = "Indicates a trust policy failure which can be overridden by the user."
break
case .unspecified:
message = "Indicates the evaluation succeeded and the certificate is implicitly trusted, but user intent was not explicitly specified."
break
default:
message = ""
}
let arguments: [String: Any?] = [
"host": challenge.protectionSpace.host,
"protocol": challenge.protectionSpace.protocol,
......@@ -2556,8 +2592,9 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
"port": challenge.protectionSpace.port,
"previousFailureCount": challenge.previousFailureCount,
"serverCertificate": serverCertificateData,
"error": -1,
"message": "",
"androidError": nil,
"iosError": error,
"message": message,
]
channel?.invokeMethod("onReceivedServerTrustAuthRequest", arguments: arguments, result: result)
}
......@@ -2746,14 +2783,14 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
evaluateJavaScript("wkwebview_ClearMatches();", completionHandler: completionHandler)
}
public func scrollTo(x: Int, y: Int) {
scrollView.setContentOffset(CGPoint(x: x, y: y), animated: false)
public func scrollTo(x: Int, y: Int, animated: Bool) {
scrollView.setContentOffset(CGPoint(x: x, y: y), animated: animated)
}
public func scrollBy(x: Int, y: Int) {
public func scrollBy(x: Int, y: Int, animated: Bool) {
let newX = CGFloat(x) + scrollView.contentOffset.x
let newY = CGFloat(y) + scrollView.contentOffset.y
scrollView.setContentOffset(CGPoint(x: newX, y: newY), animated: false)
scrollView.setContentOffset(CGPoint(x: newX, y: newY), animated: animated)
}
......
......@@ -59,6 +59,7 @@ public class InAppWebViewOptions: Options<InAppWebView> {
var isPagingEnabled = false
var maximumZoomScale = 1.0
var minimumZoomScale = 1.0
var contentInsetAdjustmentBehavior = 2 // UIScrollView.ContentInsetAdjustmentBehavior.never
override init(){
super.init()
......@@ -96,6 +97,7 @@ public class InAppWebViewOptions: Options<InAppWebView> {
realOptions["selectionGranularity"] = configuration.selectionGranularity.rawValue
if #available(iOS 11.0, *) {
realOptions["accessibilityIgnoresInvertColors"] = webView.accessibilityIgnoresInvertColors
realOptions["contentInsetAdjustmentBehavior"] = webView.scrollView.contentInsetAdjustmentBehavior.rawValue
}
realOptions["decelerationRate"] = InAppWebView.getDecelerationRateString(type: webView.scrollView.decelerationRate)
realOptions["alwaysBounceVertical"] = webView.scrollView.alwaysBounceVertical
......
......@@ -45,8 +45,20 @@ class MyCookieManager: NSObject, FlutterPlugin {
let maxAge = arguments!["maxAge"] as? Int64
let isSecure = arguments!["isSecure"] as? Bool
let isHttpOnly = arguments!["isHttpOnly"] as? Bool
let sameSite = arguments!["sameSite"] as? String
MyCookieManager.setCookie(url: url, name: name, value: value, domain: domain, path: path, expiresDate: expiresDate, maxAge: maxAge, isSecure: isSecure, result: result)
MyCookieManager.setCookie(url: url,
name: name,
value: value,
domain: domain,
path: path,
expiresDate: expiresDate,
maxAge: maxAge,
isSecure: isSecure,
isHttpOnly: isHttpOnly,
sameSite: sameSite,
result: result)
break
case "getCookies":
let url = arguments!["url"] as! String
......@@ -82,6 +94,8 @@ class MyCookieManager: NSObject, FlutterPlugin {
expiresDate: Int64?,
maxAge: Int64?,
isSecure: Bool?,
isHttpOnly: Bool?,
sameSite: String?,
result: @escaping FlutterResult) {
var properties: [HTTPCookiePropertyKey: Any] = [:]
properties[.originURL] = url
......@@ -90,6 +104,7 @@ class MyCookieManager: NSObject, FlutterPlugin {
properties[.domain] = domain
properties[.path] = path
if expiresDate != nil {
// convert from milliseconds
properties[.expires] = Date(timeIntervalSince1970: TimeInterval(Double(expiresDate!)/1000))
}
if maxAge != nil {
......@@ -98,21 +113,61 @@ class MyCookieManager: NSObject, FlutterPlugin {
if isSecure != nil && isSecure! {
properties[.secure] = "TRUE"
}
if isHttpOnly != nil && isHttpOnly! {
properties[.init("HttpOnly")] = "YES"
}
if sameSite != nil {
if #available(iOS 13.0, *) {
var sameSiteValue = HTTPCookieStringPolicy(rawValue: "None")
switch sameSite {
case "Lax":
sameSiteValue = HTTPCookieStringPolicy.sameSiteLax
case "Strict":
sameSiteValue = HTTPCookieStringPolicy.sameSiteStrict
default:
break
}
properties[.sameSitePolicy] = sameSiteValue
} else {
properties[.init("SameSite")] = sameSite
}
}
let cookie = HTTPCookie(properties: properties)!
MyCookieManager.httpCookieStore!.setCookie(cookie, completionHandler: {() in
result(true)
})
}
public static func getCookies(url: String, result: @escaping FlutterResult) {
var cookieList: [[String: Any]] = []
var cookieList: [[String: Any?]] = []
MyCookieManager.httpCookieStore!.getAllCookies { (cookies) in
for cookie in cookies {
if cookie.domain.contains(URL(string: url)!.host!) {
var sameSite: String? = nil
if #available(iOS 13.0, *) {
if let sameSiteValue = cookie.sameSitePolicy?.rawValue {
sameSite = sameSiteValue.prefix(1).capitalized + sameSiteValue.dropFirst()
}
}
var expiresDateTimestamp: Int64 = -1
if let expiresDate = cookie.expiresDate?.timeIntervalSince1970 {
// convert to milliseconds
expiresDateTimestamp = Int64(expiresDate * 1000)
}
cookieList.append([
"name": cookie.name,
"value": cookie.value
"value": cookie.value,
"expiresDate": expiresDateTimestamp != -1 ? expiresDateTimestamp : nil,
"isSessionOnly": cookie.isSessionOnly,
"domain": cookie.domain,
"sameSite": sameSite,
"isSecure": cookie.isSecure,
"isHttpOnly": cookie.isHTTPOnly,
"path": cookie.path,
])
}
}
......
......@@ -40,8 +40,10 @@ class CookieManager {
String path = "/",
int expiresDate,
int maxAge,
bool isSecure}) async {
if (domain == null || domain.isEmpty) domain = _getDomainName(url);
bool isSecure,
bool isHttpOnly,
HTTPCookieSameSitePolicy sameSite}) async {
if (domain == null) domain = _getDomainName(url);
assert(url != null && url.isNotEmpty);
assert(name != null && name.isNotEmpty);
......@@ -58,6 +60,8 @@ class CookieManager {
args.putIfAbsent('expiresDate', () => expiresDate?.toString());
args.putIfAbsent('maxAge', () => maxAge);
args.putIfAbsent('isSecure', () => isSecure);
args.putIfAbsent('isHttpOnly', () => isHttpOnly);
args.putIfAbsent('sameSite', () => sameSite?.toValue());
await _channel.invokeMethod('setCookie', args);
}
......@@ -71,10 +75,19 @@ class CookieManager {
List<dynamic> cookieListMap =
await _channel.invokeMethod('getCookies', args);
cookieListMap = cookieListMap.cast<Map<dynamic, dynamic>>();
List<Cookie> cookies = [];
for (var i = 0; i < cookieListMap.length; i++) {
cookies.add(Cookie(
name: cookieListMap[i]["name"], value: cookieListMap[i]["value"]));
name: cookieListMap[i]["name"],
value: cookieListMap[i]["value"],
expiresDate: cookieListMap[i]["expiresDate"],
isSessionOnly: cookieListMap[i]["isSessionOnly"],
domain: cookieListMap[i]["domain"],
sameSite: HTTPCookieSameSitePolicy.fromValue(cookieListMap[i]["sameSite"]),
isSecure: cookieListMap[i]["isSecure"],
isHttpOnly: cookieListMap[i]["isHttpOnly"],
path: cookieListMap[i]["path"]));
}
return cookies;
}
......@@ -92,7 +105,16 @@ class CookieManager {
for (var i = 0; i < cookies.length; i++) {
cookies[i] = cookies[i].cast<String, dynamic>();
if (cookies[i]["name"] == name)
return Cookie(name: cookies[i]["name"], value: cookies[i]["value"]);
return Cookie(
name: cookies[i]["name"],
value: cookies[i]["value"],
expiresDate: cookies[i]["expiresDate"],
isSessionOnly: cookies[i]["isSessionOnly"],
domain: cookies[i]["domain"],
sameSite: HTTPCookieSameSitePolicy.fromValue(cookies[i]["sameSite"]),
isSecure: cookies[i]["isSecure"],
isHttpOnly: cookies[i]["isHttpOnly"],
path: cookies[i]["path"]);
}
return null;
}
......
......@@ -203,36 +203,19 @@ class InAppBrowser {
await _channel.invokeMethod('setOptions', args);
}
///Gets the current [InAppBrowser] options as a `Map`. Returns `null` if the options are not setted yet.
///Gets the current [InAppBrowser] options. Returns `null` if it wasn't able to get them.
Future<InAppBrowserClassOptions> getOptions() async {
this.throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{};
InAppBrowserClassOptions inAppBrowserClassOptions =
InAppBrowserClassOptions();
Map<dynamic, dynamic> options =
await _channel.invokeMethod('getOptions', args);
if (options != null) {
options = options.cast<String, dynamic>();
inAppBrowserClassOptions.crossPlatform =
InAppBrowserOptions.fromMap(options);
inAppBrowserClassOptions.inAppWebViewGroupOptions =
InAppWebViewGroupOptions();
inAppBrowserClassOptions.inAppWebViewGroupOptions.crossPlatform =
InAppWebViewOptions.fromMap(options);
if (Platform.isAndroid) {
inAppBrowserClassOptions.android =
AndroidInAppBrowserOptions.fromMap(options);
inAppBrowserClassOptions.inAppWebViewGroupOptions.android =
AndroidInAppWebViewOptions.fromMap(options);
} else if (Platform.isIOS) {
inAppBrowserClassOptions.ios = IOSInAppBrowserOptions.fromMap(options);
inAppBrowserClassOptions.inAppWebViewGroupOptions.ios =
IOSInAppWebViewOptions.fromMap(options);
}
return InAppBrowserClassOptions.fromMap(options);
}
return inAppBrowserClassOptions;
return null;
}
///Returns `true` if the [InAppBrowser] instance is opened, otherwise `false`.
......
......@@ -366,14 +366,20 @@ class InAppWebViewController {
String protocol = call.arguments["protocol"];
String realm = call.arguments["realm"];
int port = call.arguments["port"];
int error = call.arguments["error"];
int androidError = call.arguments["androidError"];
int iosError = call.arguments["iosError"];
String message = call.arguments["message"];
Uint8List serverCertificate = call.arguments["serverCertificate"];
AndroidSslError androidSslError = androidError != null ? AndroidSslError.fromValue(androidError) : null;
IOSSslError iosSslError = iosError != null ? IOSSslError.fromValue(iosError) : null;
var protectionSpace = ProtectionSpace(
host: host, protocol: protocol, realm: realm, port: port);
var challenge = ServerTrustChallenge(
protectionSpace: protectionSpace,
error: error,
androidError: androidSslError,
iosError: iosSslError,
message: message,
serverCertificate: serverCertificate);
if (_webview != null &&
......@@ -811,8 +817,10 @@ class InAppWebViewController {
if (webviewUrl.startsWith("file:///")) {
var assetPathSplitted = webviewUrl.split("/flutter_assets/");
var assetPath = assetPathSplitted[assetPathSplitted.length - 1];
var bytes = await rootBundle.load(assetPath);
html = utf8.decode(bytes.buffer.asUint8List());
try {
var bytes = await rootBundle.load(assetPath);
html = utf8.decode(bytes.buffer.asUint8List());
} catch (e) {}
} else {
HttpClient client = new HttpClient();
var url = Uri.parse(webviewUrl);
......@@ -839,7 +847,7 @@ class InAppWebViewController {
String manifestUrl;
var html = await getHtml();
if (html != null && html.isEmpty) {
if (html == null || (html != null && html.isEmpty)) {
return favicons;
}
......@@ -880,8 +888,9 @@ class InAppWebViewController {
var faviconUrl = url.scheme + "://" + url.host + "/favicon.ico";
await client.headUrl(Uri.parse(faviconUrl));
favicons.add(Favicon(url: faviconUrl, rel: "shortcut icon"));
} catch (e) {
} catch (e, stacktrace) {
print("/favicon.ico file not found: " + e.toString());
print(stacktrace);
}
// try to get the manifest file
......@@ -896,8 +905,9 @@ class InAppWebViewController {
manifestResponse = await manifestRequest.close();
manifestFound = manifestResponse.statusCode == 200 &&
manifestResponse.headers.contentType?.mimeType == "application/json";
} catch (e) {
} catch (e, stacktrace) {
print("Manifest file not found: " + e.toString());
print(stacktrace);
}
if (manifestFound) {
......@@ -1261,26 +1271,18 @@ class InAppWebViewController {
await _channel.invokeMethod('setOptions', args);
}
///Gets the current WebView options. Returns the options with `null` value if they are not set yet.
///Gets the current WebView options. Returns `null` if it wasn't able to get them.
Future<InAppWebViewGroupOptions> getOptions() async {
Map<String, dynamic> args = <String, dynamic>{};
InAppWebViewGroupOptions inAppWebViewGroupOptions =
InAppWebViewGroupOptions();
Map<dynamic, dynamic> options =
await _channel.invokeMethod('getOptions', args);
if (options != null) {
options = options.cast<String, dynamic>();
inAppWebViewGroupOptions.crossPlatform =
InAppWebViewOptions.fromMap(options);
if (Platform.isAndroid)
inAppWebViewGroupOptions.android =
AndroidInAppWebViewOptions.fromMap(options);
else if (Platform.isIOS)
inAppWebViewGroupOptions.ios = IOSInAppWebViewOptions.fromMap(options);
return InAppWebViewGroupOptions.fromMap(options);
}
return inAppWebViewGroupOptions;
return null;
}
///Gets the WebHistory for this WebView. This contains the back/forward list for use in querying each item in the history stack.
......@@ -1378,13 +1380,16 @@ class InAppWebViewController {
///
///[y] represents the y position to scroll to.
///
///[animated] `true` to animate the scroll transition, `false` to make the scoll transition immediate.
///
///**Official Android API**: https://developer.android.com/reference/android/view/View#scrollTo(int,%20int)
///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619400-setcontentoffset
Future<void> scrollTo({@required int x, @required int y}) async {
Future<void> scrollTo({@required int x, @required int y, bool animated = false}) async {
assert(x != null && y != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('x', () => x);
args.putIfAbsent('y', () => y);
args.putIfAbsent('animated', () => animated ?? false);
await _channel.invokeMethod('scrollTo', args);
}
......@@ -1394,13 +1399,16 @@ class InAppWebViewController {
///
///[y] represents the amount of pixels to scroll by vertically.
///
///[animated] `true` to animate the scroll transition, `false` to make the scoll transition immediate.
///
///**Official Android API**: https://developer.android.com/reference/android/view/View#scrollBy(int,%20int)
///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619400-setcontentoffset
Future<void> scrollBy({@required int x, @required int y}) async {
Future<void> scrollBy({@required int x, @required int y, bool animated = false}) async {
assert(x != null && y != null);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('x', () => x);
args.putIfAbsent('y', () => y);
args.putIfAbsent('animated', () => animated ?? false);
await _channel.invokeMethod('scrollBy', args);
}
......@@ -1447,13 +1455,15 @@ class InAppWebViewController {
///Performs a zoom operation in this WebView.
///
///[zoomFactor] represents the zoom factor to apply. On Android, the zoom factor will be clamped to the Webview's zoom limits and, also, this value must be in the range 0.01 to 100.0 inclusive.
///[zoomFactor] represents the zoom factor to apply. On Android, the zoom factor will be clamped to the Webview's zoom limits and, also, this value must be in the range 0.01 (excluded) to 100.0 (included).
///
///**NOTE**: available on Android 21+.
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#zoomBy(float)
///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619412-setzoomscale
Future<void> zoomBy(double zoomFactor) async {
assert(!Platform.isAndroid || (Platform.isAndroid && zoomFactor > 0.01 && zoomFactor <= 100.0));
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('zoomFactor', () => zoomFactor);
return await _channel.invokeMethod('zoomBy', args);
......@@ -1627,6 +1637,24 @@ class InAppWebViewController {
return Util.convertColorFromStringRepresentation(colorValue);
}
///Returns the scrolled left position of the current WebView.
///
///**Official Android API**: https://developer.android.com/reference/android/view/View#getScrollX()
///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619404-contentoffset
Future<int> getScrollX() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getScrollX', args);
}
///Returns the scrolled top position of the current WebView.
///
///**Official Android API**: https://developer.android.com/reference/android/view/View#getScrollY()
///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619404-contentoffset
Future<int> getScrollY() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getScrollY', args);
}
///Gets the default user agent.
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebSettings#getDefaultUserAgent(android.content.Context)
......@@ -1766,6 +1794,16 @@ class AndroidInAppWebViewController {
return await _controller._channel.invokeMethod('clearHistory', args);
}
///Gets the SSL certificate for the main top-level page or null if there is no certificate (the site is not secure).
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#getCertificate()
Future<AndroidSslCertificate> getCertificate() async {
Map<String, dynamic> args = <String, dynamic>{};
var sslCertificateMap = (await _controller._channel.invokeMethod('getCertificate', args))?.cast<String, dynamic>();
return sslCertificateMap != null ? AndroidSslCertificate.fromMap(sslCertificateMap) : null;
}
///Clears the client certificate preferences stored in response to proceeding/cancelling client cert requests.
///Note that WebView automatically clears these preferences when the system keychain is updated.
///The preferences are shared by all the WebViews that are created by the embedder application.
......
This diff is collapsed.
......@@ -17,6 +17,10 @@ class WebViewOptions {
return null;
}
WebViewOptions copy() {
return WebViewOptions.fromMap(this.toMap());
}
Map<String, dynamic> toJson() {
return this.toMap();
}
......@@ -36,6 +40,10 @@ class BrowserOptions {
return null;
}
BrowserOptions copy() {
return BrowserOptions.fromMap(this.toMap());
}
Map<String, dynamic> toJson() {
return this.toMap();
}
......@@ -55,6 +63,10 @@ class ChromeSafariBrowserOptions {
return null;
}
ChromeSafariBrowserOptions copy() {
return ChromeSafariBrowserOptions.fromMap(this.toMap());
}
Map<String, dynamic> toJson() {
return this.toMap();
}
......@@ -284,6 +296,17 @@ class InAppWebViewOptions
String toString() {
return toMap().toString();
}
@override
InAppWebViewOptions copy() {
return InAppWebViewOptions.fromMap(this.toMap());
}
InAppWebViewOptions copyWithValue(InAppWebViewOptions webViewOptions) {
var mergedMap = this.toMap();
mergedMap.addAll(webViewOptions.toMap());
return InAppWebViewOptions.fromMap(mergedMap);
}
}
///This class represents all the Android-only WebView options available.
......@@ -681,6 +704,17 @@ class AndroidInAppWebViewOptions
String toString() {
return toMap().toString();
}
@override
AndroidInAppWebViewOptions copy() {
return AndroidInAppWebViewOptions.fromMap(this.toMap());
}
AndroidInAppWebViewOptions copyWithValue(AndroidInAppWebViewOptions webViewOptions) {
var mergedMap = this.toMap();
mergedMap.addAll(webViewOptions.toMap());
return AndroidInAppWebViewOptions.fromMap(mergedMap);
}
}
///This class represents all the iOS-only WebView options available.
......@@ -789,6 +823,10 @@ class IOSInAppWebViewOptions
///The default value is `1.0`.
double minimumZoomScale;
///Configures how safe area insets are added to the adjusted content inset.
///The default value is [IOSUIScrollViewContentInsetAdjustmentBehavior.NEVER].
IOSUIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior;
IOSInAppWebViewOptions(
{this.disallowOverScroll = false,
this.enableViewportScale = false,
......@@ -811,7 +849,8 @@ class IOSInAppWebViewOptions
this.scrollsToTop = true,
this.isPagingEnabled = false,
this.maximumZoomScale = 1.0,
this.minimumZoomScale = 1.0});
this.minimumZoomScale = 1.0,
this.contentInsetAdjustmentBehavior = IOSUIScrollViewContentInsetAdjustmentBehavior.NEVER});
@override
Map<String, dynamic> toMap() {
......@@ -845,7 +884,8 @@ class IOSInAppWebViewOptions
"scrollsToTop": scrollsToTop,
"isPagingEnabled": isPagingEnabled,
"maximumZoomScale": maximumZoomScale,
"minimumZoomScale": minimumZoomScale
"minimumZoomScale": minimumZoomScale,
"contentInsetAdjustmentBehavior": contentInsetAdjustmentBehavior.toValue()
};
}
......@@ -889,6 +929,9 @@ class IOSInAppWebViewOptions
options.isPagingEnabled = map["isPagingEnabled"];
options.maximumZoomScale = map["maximumZoomScale"];
options.minimumZoomScale = map["minimumZoomScale"];
options.contentInsetAdjustmentBehavior =
IOSUIScrollViewContentInsetAdjustmentBehavior.fromValue(
map["contentInsetAdjustmentBehavior"]);
return options;
}
......@@ -901,6 +944,17 @@ class IOSInAppWebViewOptions
String toString() {
return toMap().toString();
}
@override
IOSInAppWebViewOptions copy() {
return IOSInAppWebViewOptions.fromMap(this.toMap());
}
IOSInAppWebViewOptions copyWithValue(IOSInAppWebViewOptions webViewOptions) {
var mergedMap = this.toMap();
mergedMap.addAll(webViewOptions.toMap());
return IOSInAppWebViewOptions.fromMap(mergedMap);
}
}
///This class represents all the cross-platform [InAppBrowser] options available.
......@@ -953,6 +1007,17 @@ class InAppBrowserOptions
String toString() {
return toMap().toString();
}
@override
InAppBrowserOptions copy() {
return InAppBrowserOptions.fromMap(this.toMap());
}
InAppBrowserOptions copyWithValue(InAppBrowserOptions webViewOptions) {
var mergedMap = this.toMap();
mergedMap.addAll(webViewOptions.toMap());
return InAppBrowserOptions.fromMap(mergedMap);
}
}
///This class represents all the Android-only [InAppBrowser] options available.
......@@ -1003,6 +1068,17 @@ class AndroidInAppBrowserOptions implements BrowserOptions, AndroidOptions {
String toString() {
return toMap().toString();
}
@override
AndroidInAppBrowserOptions copy() {
return AndroidInAppBrowserOptions.fromMap(this.toMap());
}
AndroidInAppBrowserOptions copyWithValue(AndroidInAppBrowserOptions webViewOptions) {
var mergedMap = this.toMap();
mergedMap.addAll(webViewOptions.toMap());
return AndroidInAppBrowserOptions.fromMap(mergedMap);
}
}
///This class represents all the iOS-only [InAppBrowser] options available.
......@@ -1079,6 +1155,17 @@ class IOSInAppBrowserOptions implements BrowserOptions, IosOptions {
String toString() {
return toMap().toString();
}
@override
IOSInAppBrowserOptions copy() {
return IOSInAppBrowserOptions.fromMap(this.toMap());
}
IOSInAppBrowserOptions copyWithValue(IOSInAppBrowserOptions webViewOptions) {
var mergedMap = this.toMap();
mergedMap.addAll(webViewOptions.toMap());
return IOSInAppBrowserOptions.fromMap(mergedMap);
}
}
///This class represents all the Android-only [ChromeSafariBrowser] options available.
......@@ -1153,6 +1240,17 @@ class AndroidChromeCustomTabsOptions
String toString() {
return toMap().toString();
}
@override
AndroidChromeCustomTabsOptions copy() {
return AndroidChromeCustomTabsOptions.fromMap(this.toMap());
}
AndroidChromeCustomTabsOptions copyWithValue(AndroidChromeCustomTabsOptions webViewOptions) {
var mergedMap = this.toMap();
mergedMap.addAll(webViewOptions.toMap());
return AndroidChromeCustomTabsOptions.fromMap(mergedMap);
}
}
///This class represents all the iOS-only [ChromeSafariBrowser] options available.
......@@ -1230,4 +1328,15 @@ class IOSSafariOptions implements ChromeSafariBrowserOptions, IosOptions {
String toString() {
return toMap().toString();
}
@override
IOSSafariOptions copy() {
return IOSSafariOptions.fromMap(this.toMap());
}
IOSSafariOptions copyWithValue(IOSSafariOptions webViewOptions) {
var mergedMap = this.toMap();
mergedMap.addAll(webViewOptions.toMap());
return IOSSafariOptions.fromMap(mergedMap);
}
}
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