Commit 1b2de863 authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

added new webview methods, added supporZoom webview option on iOS, bug fixes,...

added new webview methods, added supporZoom webview option on iOS, bug fixes, prepare new version 3.4.0
parent 30102a5c
## 3.4.0
- Added `requestFocusNodeHref`, `requestImageRef`, `getMetaTags`, `getMetaThemeColor` 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
- Fixed `zoomBy`, `setOptions` webview methods on Android
- Fixed `databaseEnabled` android webview option default value to `true`
### BREAKING CHANGES
- `evaluateJavascript` webview method now returns `null` on iOS if the evaluated JavaScript source returns `null`
- `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`
## 3.3.0+3
- Updated Android build.gradle version and some androidx properties
......
......@@ -402,6 +402,10 @@ Screenshots:
* `getHitTestResult`: Gets the hit result for hitting an HTML elements.
* `clearFocus`: Clears the current focus. It will clear also, for example, the current text selection.
* `setContextMenu(ContextMenu contextMenu)`: Sets or updates the WebView context menu to be used next time it will appear.
* `requestFocusNodeHref`: Requests the anchor or image element URL at the last tapped point.
* `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`.
* `static getDefaultUserAgent`: Gets the default user agent.
##### `InAppWebViewController` Android-specific methods
......@@ -504,6 +508,7 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
* `disableVerticalScroll`: Set to `true` to disable vertical scroll. The default value is `false`.
* `disableHorizontalScroll`: Set to `true` to disable horizontal scroll. The default value is `false`.
* `disableContextMenu`: Set to `true` to disable context menu. The default value is `false`.
* `supportZoom`: Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
##### `InAppWebView` Android-specific options
......@@ -511,9 +516,8 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
* `useOnRenderProcessGone`: Set to `true` to be able to listen at the `androidOnRenderProcessGone` event. The default value is `false`.
* `textZoom`: Sets the text zoom of the page in percent. The default value is `100`.
* `clearSessionCache`: Set to `true` to have the session cookie cache cleared before the new window is opened.
* `builtInZoomControls`: Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `false`.
* `builtInZoomControls`: Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `true`.
* `displayZoomControls`: Set to `true` if the WebView should display on-screen zoom controls when using the built-in zoom mechanisms. The default value is `false`.
* `supportZoom`: Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
* `databaseEnabled`: Set to `true` if you want the database storage API is enabled. The default value is `true`.
* `domStorageEnabled`: Set to `true` if you want the DOM storage API is enabled. The default value is `true`.
* `useWideViewPort`: Set to `true` if the WebView should enable support for the "viewport" HTML meta tag or should use a wide viewport.
......@@ -622,7 +626,7 @@ Event names that starts with `android` or `ios` are events platform-specific.
* `androidOnPermissionRequest`: Event fired when the webview is requesting permission to access the specified resources and the permission currently isn't granted or denied (available only on Android).
* `androidOnGeolocationPermissionsShowPrompt`: Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin (available only on Android).
* `androidOnGeolocationPermissionsHidePrompt`: Notify the host application that a request for Geolocation permissions, made with a previous call to `androidOnGeolocationPermissionsShowPrompt` has been canceled (available only on Android).
* `androidShouldInterceptRequest`: Notify the host application of a resource request and allow the application to return the data (available only on Android).
* `androidShouldInterceptRequest`: Notify the host application of a resource request and allow the application to return the data (available only on Android). To use this event, the `useShouldInterceptRequest` option must be `true`.
* `androidOnRenderProcessGone`: Event fired when the given WebView's render process has exited (available only on Android).
* `androidOnRenderProcessResponsive`: Event called once when an unresponsive renderer currently associated with the WebView becomes responsive (available only on Android).
* `androidOnRenderProcessUnresponsive`: Event called when the renderer currently associated with the WebView becomes unresponsive as a result of a long running blocking task such as the execution of JavaScript (available only on Android).
......
......@@ -5,6 +5,7 @@ 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;
......@@ -22,7 +23,6 @@ import com.pichillilorenzo.flutter_inappwebview.Util;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.BinaryMessenger;
......@@ -365,8 +365,8 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
break;
case "zoomBy":
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float zoomFactor = (float) call.argument("zoomFactor");
webView.zoomBy(zoomFactor);
double zoomFactor = (double) call.argument("zoomFactor");
webView.zoomBy((float) zoomFactor);
result.success(true);
} else {
result.success(false);
......@@ -457,6 +457,20 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
result.success(false);
}
break;
case "requestFocusNodeHref":
if (webView != null) {
result.success(webView.requestFocusNodeHref());
} else {
result.success(false);
}
break;
case "requestImageRef":
if (webView != null) {
result.success(webView.requestImageRef());
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}
......
......@@ -6,8 +6,10 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintManager;
......@@ -84,6 +86,7 @@ final public class InAppWebView extends InputAwareWebView {
public LinearLayout floatingContextMenu = null;
public Map<String, Object> contextMenu = null;
public Handler headlessHandler = new Handler(Looper.getMainLooper());
static Handler mHandler = new Handler();
public Runnable checkScrollStoppedTask;
public int initialPositionScrollStoppedTask;
......@@ -1184,7 +1187,7 @@ final public class InAppWebView extends InputAwareWebView {
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
if (newOptionsMap.get("mixedContentMode") != null && !options.mixedContentMode.equals(newOptions.mixedContentMode))
if (newOptionsMap.get("mixedContentMode") != null && (options.mixedContentMode == null || !options.mixedContentMode.equals(newOptions.mixedContentMode)))
settings.setMixedContentMode(newOptions.mixedContentMode);
if (newOptionsMap.get("supportMultipleWindows") != null && options.supportMultipleWindows != newOptions.supportMultipleWindows)
......@@ -1213,7 +1216,7 @@ final public class InAppWebView extends InputAwareWebView {
if (newOptionsMap.get("cacheEnabled") != null && options.cacheEnabled != newOptions.cacheEnabled)
setCacheEnabled(newOptions.cacheEnabled);
if (newOptionsMap.get("appCachePath") != null && !options.appCachePath.equals(newOptions.appCachePath))
if (newOptionsMap.get("appCachePath") != null && (options.appCachePath == null || !options.appCachePath.equals(newOptions.appCachePath)))
settings.setAppCachePath(newOptions.appCachePath);
if (newOptionsMap.get("blockNetworkImage") != null && options.blockNetworkImage != newOptions.blockNetworkImage)
......@@ -1237,8 +1240,9 @@ final public class InAppWebView extends InputAwareWebView {
if (newOptionsMap.get("defaultTextEncodingName") != null && !options.defaultTextEncodingName.equals(newOptions.defaultTextEncodingName))
settings.setDefaultTextEncodingName(newOptions.defaultTextEncodingName);
if (newOptionsMap.get("disabledActionModeMenuItems") != null && !options.disabledActionModeMenuItems.equals(newOptions.disabledActionModeMenuItems))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
if (newOptionsMap.get("disabledActionModeMenuItems") != null && (options.disabledActionModeMenuItems == null ||
!options.disabledActionModeMenuItems.equals(newOptions.disabledActionModeMenuItems)))
settings.setDisabledActionModeMenuItems(newOptions.disabledActionModeMenuItems);
if (newOptionsMap.get("fantasyFontFamily") != null && !options.fantasyFontFamily.equals(newOptions.fantasyFontFamily))
......@@ -1318,7 +1322,8 @@ final public class InAppWebView extends InputAwareWebView {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
if (newOptionsMap.get("regexToCancelSubFramesLoading") != null && options.regexToCancelSubFramesLoading != newOptions.regexToCancelSubFramesLoading) {
if (newOptionsMap.get("regexToCancelSubFramesLoading") != null && (options.regexToCancelSubFramesLoading == null ||
!options.regexToCancelSubFramesLoading.equals(newOptions.regexToCancelSubFramesLoading))) {
if (newOptions.regexToCancelSubFramesLoading == null)
regexToCancelSubFramesLoadingCompiled = null;
else
......@@ -1338,13 +1343,15 @@ final public class InAppWebView extends InputAwareWebView {
if (newOptionsMap.get("scrollBarStyle") != null && !options.scrollBarStyle.equals(newOptions.scrollBarStyle))
setScrollBarStyle(newOptions.scrollBarStyle);
if (newOptionsMap.get("scrollBarDefaultDelayBeforeFade") != null && !options.scrollBarDefaultDelayBeforeFade.equals(newOptions.scrollBarDefaultDelayBeforeFade))
if (newOptionsMap.get("scrollBarDefaultDelayBeforeFade") != null && (options.scrollBarDefaultDelayBeforeFade == null ||
!options.scrollBarDefaultDelayBeforeFade.equals(newOptions.scrollBarDefaultDelayBeforeFade)))
setScrollBarDefaultDelayBeforeFade(newOptions.scrollBarDefaultDelayBeforeFade);
if (newOptionsMap.get("scrollbarFadingEnabled") != null && !options.scrollbarFadingEnabled.equals(newOptions.scrollbarFadingEnabled))
setScrollbarFadingEnabled(newOptions.scrollbarFadingEnabled);
if (newOptionsMap.get("scrollBarFadeDuration") != null && !options.scrollBarFadeDuration.equals(newOptions.scrollBarFadeDuration))
if (newOptionsMap.get("scrollBarFadeDuration") != null && (options.scrollBarFadeDuration == null ||
!options.scrollBarFadeDuration.equals(newOptions.scrollBarFadeDuration)))
setScrollBarFadeDuration(newOptions.scrollBarFadeDuration);
if (newOptionsMap.get("verticalScrollbarPosition") != null && !options.verticalScrollbarPosition.equals(newOptions.verticalScrollbarPosition))
......@@ -1802,6 +1809,30 @@ final public class InAppWebView extends InputAwareWebView {
});
}
public Map<String, Object> requestFocusNodeHref() {
Message msg = InAppWebView.mHandler.obtainMessage();
requestFocusNodeHref(msg);
Bundle bundle = msg.peekData();
Map<String, Object> obj = new HashMap<>();
obj.put("src", bundle.getString("src"));
obj.put("url", bundle.getString("url"));
obj.put("title", bundle.getString("title"));
return obj;
}
public Map<String, Object> requestImageRef() {
Message msg = InAppWebView.mHandler.obtainMessage();
requestImageRef(msg);
Bundle bundle = msg.peekData();
Map<String, Object> obj = new HashMap<>();
obj.put("url", bundle.getString("url"));
return obj;
}
@Override
public void dispose() {
super.dispose();
......
......@@ -465,8 +465,6 @@ public class InAppWebViewClient extends WebViewClient {
}
obj.put("message", message);
Log.d(LOG_TAG, obj.toString());
channel.invokeMethod("onReceivedServerTrustAuthRequest", obj, new MethodChannel.Result() {
@Override
public void success(Object response) {
......
......@@ -43,12 +43,12 @@ public class InAppWebViewOptions implements Options<InAppWebView> {
public Boolean disableVerticalScroll = false;
public Boolean disableHorizontalScroll = false;
public Boolean disableContextMenu = false;
public Boolean supportZoom = true;
public Integer textZoom = 100;
public Boolean clearSessionCache = false;
public Boolean builtInZoomControls = false;
public Boolean builtInZoomControls = true;
public Boolean displayZoomControls = false;
public Boolean supportZoom = true;
public Boolean databaseEnabled = false;
public Boolean domStorageEnabled = true;
public Boolean useWideViewPort = true;
......
{"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-03 01:35:21.255449","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-12 02:54:04.283438","version":"1.17.1"}
\ No newline at end of file
......@@ -2,10 +2,11 @@
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/lorenzopichilli/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example"
export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build/ios"
export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
export "FLUTTER_FRAMEWORK_DIR=/Users/lorenzopichilli/flutter/bin/cache/artifacts/engine/ios"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "TRACK_WIDGET_CREATION=true"
......@@ -78,8 +78,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(
......
final environment = {"NODE_SERVER_IP":"192.168.43.166"};
\ No newline at end of file
final environment = {"NODE_SERVER_IP":"192.168.1.123"};
\ No newline at end of file
......@@ -203,7 +203,7 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
webView!.evaluateJavascript(source: source, result: result)
}
else {
result("")
result(nil)
}
break
case "injectJavascriptFileFromUrl":
......@@ -405,6 +405,8 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
webView!.getSelectedText { (value, error) in
if let err = error {
print(err.localizedDescription)
result("")
return
}
result(value)
}
......@@ -418,6 +420,8 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
webView!.getHitTestResult { (value, error) in
if let err = error {
print(err.localizedDescription)
result(nil)
return
}
result(value)
}
......@@ -443,6 +447,34 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
result(false)
}
break
case "requestFocusNodeHref":
if webView != nil {
webView!.requestFocusNodeHref { (value, error) in
if let err = error {
print(err.localizedDescription)
result(nil)
return
}
result(value)
}
} else {
result(false)
}
break
case "requestImageRef":
if webView != nil {
webView!.requestImageRef { (value, error) in
if let err = error {
print(err.localizedDescription)
result(nil)
return
}
result(value)
}
} else {
result(false)
}
break
default:
result(FlutterMethodNotImplemented)
break
......
......@@ -681,7 +681,6 @@ window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint = function(x, y) {
EDIT_TEXT_TYPE: 9
};
var element = document.elementFromPoint(x, y);
console.log(element);
var data = {
type: 0,
extra: null
......@@ -735,6 +734,63 @@ let getSelectedTextJS = """
})();
"""
let lastTouchedAnchorOrImageJS = """
window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = null;
window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = null;
(function() {
document.addEventListener('touchstart', function(event) {
var target = event.target;
while (target) {
if (target.tagName === 'IMG') {
var img = target;
window.flutter_inappwebview._lastImageTouched = {
src: img.src
};
var parent = img.parentNode;
while (parent) {
if (parent.tagName === 'A') {
window.flutter_inappwebview._lastAnchorOrImageTouched = {
title: parent.textContent,
url: parent.href,
src: img.src
};
break;
}
parent = parent.parentNode;
}
return;
} else if (target.tagName === 'A') {
var link = target;
var images = link.getElementsByTagName('img');
var img = (images.length > 0) ? images[0] : null;
var imgSrc = (img != null) ? img.src : null;
window.flutter_inappwebview._lastImageTouched = (img != null) ? {src: img.src} : window.flutter_inappwebview._lastImageTouched;
window.flutter_inappwebview._lastAnchorOrImageTouched = {
title: link.textContent,
url: link.href,
src: imgSrc
};
return;
}
target = target.parentNode;
}
});
})();
"""
let originalViewPortMetaTagContentJS = """
window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = "";
(function() {
var metaTagNodes = document.head.getElementsByTagName('meta');
for (var i = 0; i < metaTagNodes.length; i++) {
var metaTagNode = metaTagNodes[i];
if (metaTagNode.name === "viewport") {
window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = metaTagNode.content;
}
}
})();
"""
var SharedLastTouchPointTimestamp: [InAppWebView: Int64] = [:]
public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate {
......@@ -991,7 +1047,14 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
if (options?.enableViewportScale)! {
let originalViewPortMetaTagContentJSScript = WKUserScript(source: originalViewPortMetaTagContentJS, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
configuration.userContentController.addUserScript(originalViewPortMetaTagContentJSScript)
if !(options?.supportZoom)! {
let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); document.getElementsByTagName('head')[0].appendChild(meta);"
let userScript = WKUserScript(source: jscript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
configuration.userContentController.addUserScript(userScript)
} else if (options?.enableViewportScale)! {
let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
let userScript = WKUserScript(source: jscript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
configuration.userContentController.addUserScript(userScript)
......@@ -1018,6 +1081,9 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
let printJSScript = WKUserScript(source: printJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(printJSScript)
let lastTouchedAnchorOrImageJSScript = WKUserScript(source: lastTouchedAnchorOrImageJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(lastTouchedAnchorOrImageJSScript)
if (options?.useOnLoadResource)! {
let resourceObserverJSScript = WKUserScript(source: resourceObserverJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(resourceObserverJSScript)
......@@ -1424,8 +1490,23 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
if newOptionsMap["enableViewportScale"] != nil && options?.enableViewportScale != newOptions.enableViewportScale && newOptions.enableViewportScale {
let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
if newOptionsMap["enableViewportScale"] != nil && options?.enableViewportScale != newOptions.enableViewportScale {
var jscript = ""
if (newOptions.enableViewportScale) {
jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
} else {
jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent); document.getElementsByTagName('head')[0].appendChild(meta);"
}
evaluateJavaScript(jscript, completionHandler: nil)
}
if newOptionsMap["supportZoom"] != nil && options?.supportZoom != newOptions.supportZoom {
var jscript = ""
if (newOptions.supportZoom) {
jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent); document.getElementsByTagName('head')[0].appendChild(meta);"
} else {
jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); document.getElementsByTagName('head')[0].appendChild(meta);"
}
evaluateJavaScript(jscript, completionHandler: nil)
}
......@@ -1635,7 +1716,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
if value == nil {
result!("")
result!(nil)
return
}
......@@ -2742,6 +2823,34 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
public func requestFocusNodeHref(completionHandler: @escaping ([String: Any?]?, Error?) -> Void) {
if configuration.preferences.javaScriptEnabled {
// add some delay to make it sure _lastAnchorOrImageTouched is updated
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched", completionHandler: {(value, error) in
let lastAnchorOrImageTouched = value as? [String: Any?]
completionHandler(lastAnchorOrImageTouched, error)
})
}
} else {
completionHandler(nil, nil)
}
}
public func requestImageRef(completionHandler: @escaping ([String: Any?]?, Error?) -> Void) {
if configuration.preferences.javaScriptEnabled {
// add some delay to make it sure _lastImageTouched is updated
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched", completionHandler: {(value, error) in
let lastImageTouched = value as? [String: Any?]
completionHandler(lastImageTouched, error)
})
}
} else {
completionHandler(nil, nil)
}
}
public func clearFocus() {
self.scrollView.subviews.first?.resignFirstResponder()
}
......
......@@ -34,6 +34,7 @@ public class InAppWebViewOptions: Options<InAppWebView> {
var disableVerticalScroll = false
var disableHorizontalScroll = false
var disableContextMenu = false
var supportZoom = true
var disallowOverScroll = false
var enableViewportScale = false
......
......@@ -36,3 +36,4 @@ export 'src/content_blocker.dart';
export 'src/http_auth_credentials_database.dart';
export 'src/web_storage_manager.dart';
export 'src/context_menu.dart';
export 'src/web_storage.dart';
......@@ -41,7 +41,7 @@ class CookieManager {
int expiresDate,
int maxAge,
bool isSecure}) async {
if (domain == null) domain = _getDomainName(url);
if (domain == null || domain.isEmpty) domain = _getDomainName(url);
assert(url != null && url.isNotEmpty);
assert(name != null && name.isNotEmpty);
......
......@@ -13,7 +13,7 @@ import 'in_app_webview_controller.dart';
///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree.
class InAppWebView extends StatefulWidget implements WebView {
/// `gestureRecognizers` specifies which gestures should be consumed by the web view.
/// `gestureRecognizers` specifies which gestures should be consumed by the WebView.
/// 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
/// vertical drags. The web view will claim gestures that are recognized by any of the
......
......@@ -3,6 +3,7 @@ import 'dart:async';
import 'dart:collection';
import 'dart:typed_data';
import 'dart:convert';
import 'dart:core';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
......@@ -18,6 +19,8 @@ import 'webview_options.dart';
import 'headless_in_app_webview.dart';
import 'webview.dart';
import 'in_app_webview.dart';
import 'web_storage.dart';
import 'util.dart';
///List of forbidden names for JavaScript handlers.
const javaScriptHandlerForbiddenNames = [
......@@ -56,6 +59,8 @@ class InAppWebViewController {
///iOS controller that contains only ios-specific methods
IOSInAppWebViewController ios;
WebStorage webStorage;
InAppWebViewController(dynamic id, WebView webview) {
this._id = id;
this._channel =
......@@ -64,6 +69,7 @@ class InAppWebViewController {
this._webview = webview;
this.android = AndroidInAppWebViewController(this);
this.ios = IOSInAppWebViewController(this);
this.webStorage = WebStorage(localStorage: LocalStorage(this), sessionStorage: SessionStorage(this));
}
InAppWebViewController.fromInAppBrowser(
......@@ -789,8 +795,11 @@ class InAppWebViewController {
///If this doesn't work, it tries to get the content reading the file:
///- checking if it is an asset (`file:///`) or
///- downloading it using an `HttpClient` through the WebView's current url.
///
///Returns `null` if it was unable to get it.
Future<String> getHtml() async {
var html = "";
String html;
InAppWebViewGroupOptions options = await getOptions();
if (options != null && options.crossPlatform.javaScriptEnabled == true) {
html = await evaluateJavascript(
......@@ -830,7 +839,7 @@ class InAppWebViewController {
String manifestUrl;
var html = await getHtml();
if (html.isEmpty) {
if (html != null && html.isEmpty) {
return favicons;
}
......@@ -1503,6 +1512,121 @@ class InAppWebViewController {
_inAppBrowser?.contextMenu = contextMenu;
}
///Requests the anchor or image element URL at the last tapped point.
///
///**NOTE**: On iOS it is implemented using JavaScript.
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#requestFocusNodeHref(android.os.Message)
Future<RequestFocusNodeHrefResult> requestFocusNodeHref() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic> result = await _channel.invokeMethod('requestFocusNodeHref', args);
return result != null ? RequestFocusNodeHrefResult(
url: result['url'],
title: result['title'],
src: result['src'],
) : null;
}
///Requests the URL of the image last touched by the user.
///
///**NOTE**: On iOS it is implemented using JavaScript.
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#requestImageRef(android.os.Message)
Future<RequestImageRefResult> requestImageRef() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic> result = await _channel.invokeMethod('requestImageRef', args);
return result != null ? RequestImageRefResult(
url: result['url'],
) : null;
}
///Returns the list of `<meta>` tags of the current WebView.
///
///**NOTE**: It is implemented using JavaScript.
Future<List<MetaTag>> getMetaTags() async {
List<MetaTag> metaTags = [];
List<Map<dynamic, dynamic>> metaTagList = (await evaluateJavascript(source: """
(function() {
var metaTags = [];
var metaTagNodes = document.head.getElementsByTagName('meta');
for (var i = 0; i < metaTagNodes.length; i++) {
var metaTagNode = metaTagNodes[i];
var otherAttributes = metaTagNode.getAttributeNames();
var nameIndex = otherAttributes.indexOf("name");
if (nameIndex !== -1) otherAttributes.splice(nameIndex, 1);
var contentIndex = otherAttributes.indexOf("content");
if (contentIndex !== -1) otherAttributes.splice(contentIndex, 1);
var attrs = [];
for (var j = 0; j < otherAttributes.length; j++) {
var otherAttribute = otherAttributes[j];
attrs.push(
{
name: otherAttribute,
value: metaTagNode.getAttribute(otherAttribute)
}
);
}
metaTags.push(
{
name: metaTagNode.name,
content: metaTagNode.content,
attrs: attrs
}
);
}
return metaTags;
})();
"""))?.cast<Map<dynamic, dynamic>>();
if (metaTagList == null) {
return metaTags;
}
for (var metaTag in metaTagList) {
var attrs = <MetaTagAttribute>[];
for (var metaTagAttr in metaTag["attrs"]) {
attrs.add(
MetaTagAttribute(name: metaTagAttr["name"], value: metaTagAttr["value"])
);
}
metaTags.add(
MetaTag(name: metaTag["name"], content: metaTag["content"], attrs: attrs)
);
}
return metaTags;
}
///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`.
///
///**NOTE**: It is implemented using JavaScript.
Future<Color> getMetaThemeColor() async {
var metaTags = await getMetaTags();
MetaTag metaTagThemeColor;
for (var metaTag in metaTags) {
if (metaTag.name == "theme-color") {
metaTagThemeColor = metaTag;
break;
}
}
if (metaTagThemeColor == null) {
return null;
}
var colorValue = metaTagThemeColor.content;
return Util.convertColorFromStringRepresentation(colorValue);
}
///Gets the default user agent.
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebSettings#getDefaultUserAgent(android.content.Context)
......@@ -1517,6 +1641,7 @@ class AndroidInAppWebViewController {
InAppWebViewController _controller;
AndroidInAppWebViewController(InAppWebViewController controller) {
assert(controller != null);
this._controller = controller;
}
......@@ -1719,6 +1844,7 @@ class IOSInAppWebViewController {
InAppWebViewController _controller;
IOSInAppWebViewController(InAppWebViewController controller) {
assert(controller != null);
this._controller = controller;
}
......
......@@ -3279,3 +3279,146 @@ class AndroidWebViewPackageInfo {
return toMap().toString();
}
}
///Class that represents the result used by the [InAppWebViewController.requestFocusNodeHref] method.
class RequestFocusNodeHrefResult {
///The anchor's href attribute.
String url;
///The anchor's text.
String title;
///The image's src attribute.
String src;
RequestFocusNodeHrefResult(
{this.url,
this.title,
this.src});
Map<String, dynamic> toMap() {
return {
"url": url,
"title": title,
"src": src
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Class that represents the result used by the [InAppWebViewController.requestImageRef] method.
class RequestImageRefResult {
///The image's url.
String url;
RequestImageRefResult({this.url});
Map<String, dynamic> toMap() {
return {
"url": url,
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Class that represents a `<meta>` HTML tag. It is used by the [InAppWebViewController.getMetaTags] method.
class MetaTag {
///The meta tag name value.
String name;
///The meta tag content value.
String content;
///The meta tag attributes list.
List<MetaTagAttribute> attrs;
MetaTag({this.name, this.content, this.attrs});
Map<String, dynamic> toMap() {
return {
"name": name,
"content": content,
"attrs": attrs
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Class that represents an attribute of a `<meta>` HTML tag. It is used by the [MetaTag] class.
class MetaTagAttribute {
///The attribute name.
String name;
///The attribute value.
String value;
MetaTagAttribute({this.name, this.value});
Map<String, dynamic> toMap() {
return {
"name": name,
"value": value,
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
class WebStorageType {
final String _value;
const WebStorageType._internal(this._value);
static WebStorageType fromValue(String value) {
return ([
"localStorage",
"sessionStorage",
].contains(value))
? WebStorageType._internal(value)
: null;
}
String toValue() => _value;
@override
String toString() => _value;
static const LOCAL_STORAGE =
const WebStorageType._internal("localStorage");
static const SESSION_STORAGE =
const WebStorageType._internal("sessionStorage");
bool operator ==(value) => value == _value;
@override
int get hashCode => _value.hashCode;
}
This diff is collapsed.
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'in_app_webview_controller.dart';
import 'types.dart';
class WebStorage {
LocalStorage localStorage;
SessionStorage sessionStorage;
WebStorage({@required this.localStorage, @required this.sessionStorage});
}
class WebStorageItem {
String key;
dynamic value;
WebStorageItem({this.key, this.value});
Map<String, dynamic> toMap() {
return {
"key": key,
"value": value,
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
class Storage {
InAppWebViewController _controller;
WebStorageType webStorageType;
Storage(InAppWebViewController controller, this.webStorageType) {
assert(controller != null && this.webStorageType != null);
this._controller = controller;
}
Future<int> length() async {
var result = await _controller.evaluateJavascript(source: """
window.$webStorageType.length;
""");
return result != null ? int.parse(json.decode(result)) : null;
}
Future<void> setItem({@required String key, @required dynamic value}) async {
var encodedValue = json.encode(value);
await _controller.evaluateJavascript(source: """
window.$webStorageType.setItem("$key", ${ value is String ? encodedValue : "JSON.stringify($encodedValue)" });
""");
}
Future<dynamic> getItem({@required String key}) async {
var itemValue = await _controller.evaluateJavascript(source: """
window.$webStorageType.getItem("$key");
""");
if (itemValue == null) {
return null;
}
try {
return json.decode(itemValue);
} catch (e) {}
return itemValue;
}
Future<void> removeItem({@required String key}) async {
await _controller.evaluateJavascript(source: """
window.$webStorageType.removeItem("$key");
""");
}
Future<List<WebStorageItem>> getItems() async {
var webStorageItems = <WebStorageItem>[];
List<Map<dynamic, dynamic>> items = (await _controller.evaluateJavascript(source: """
(function() {
var webStorageItems = [];
for(var i = 0; i < window.$webStorageType.length; i++){
var key = window.$webStorageType.key(i);
webStorageItems.push(
{
key: key,
value: window.$webStorageType.getItem(key)
}
);
}
return webStorageItems;
})();
""")).cast<Map<dynamic, dynamic>>();
if (items == null) {
return webStorageItems;
}
for (var item in items) {
webStorageItems.add(
WebStorageItem(key: item["key"], value: item["value"])
);
}
return webStorageItems;
}
Future<void> clear() async {
await _controller.evaluateJavascript(source: """
window.$webStorageType.clear();
""");
}
Future<String> key({@required int index}) async {
var result = await _controller.evaluateJavascript(source: """
window.$webStorageType.key($index);
""");
return result != null ? json.decode(result) : null;
}
}
class LocalStorage extends Storage {
LocalStorage(InAppWebViewController controller) : super(controller, WebStorageType.LOCAL_STORAGE);
}
class SessionStorage extends Storage {
SessionStorage(InAppWebViewController controller) : super(controller, WebStorageType.SESSION_STORAGE);
}
......@@ -380,7 +380,7 @@ abstract class WebView {
///
///[request] Object containing the details of the request.
///
///**NOTE**: available only on Android.
///**NOTE**: available only on Android. In order to be able to listen this event, you need to set [AndroidInAppWebViewOptions.useShouldInterceptRequest] option to `true`.
///
///**Official Android API**:
///- https://developer.android.com/reference/android/webkit/WebViewClient#shouldInterceptRequest(android.webkit.WebView,%20android.webkit.WebResourceRequest)
......
......@@ -159,6 +159,9 @@ class InAppWebViewOptions
///Set to `true` to disable context menu. The default value is `false`.
bool disableContextMenu;
///Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
bool supportZoom;
InAppWebViewOptions(
{this.useShouldOverrideUrlLoading = false,
this.useOnLoadResource = false,
......@@ -183,7 +186,8 @@ class InAppWebViewOptions
this.transparentBackground = false,
this.disableVerticalScroll = false,
this.disableHorizontalScroll = false,
this.disableContextMenu = false}) {
this.disableContextMenu = false,
this.supportZoom = true}) {
if (this.minimumFontSize == null)
this.minimumFontSize = Platform.isAndroid ? 8 : 0;
assert(!this.resourceCustomSchemes.contains("http") &&
......@@ -221,7 +225,8 @@ class InAppWebViewOptions
"transparentBackground": transparentBackground,
"disableVerticalScroll": disableVerticalScroll,
"disableHorizontalScroll": disableHorizontalScroll,
"disableContextMenu": disableContextMenu
"disableContextMenu": disableContextMenu,
"supportZoom": supportZoom
};
}
......@@ -266,6 +271,7 @@ class InAppWebViewOptions
options.disableVerticalScroll = map["disableVerticalScroll"];
options.disableHorizontalScroll = map["disableHorizontalScroll"];
options.disableContextMenu = map["disableContextMenu"];
options.supportZoom = map["supportZoom"];
return options;
}
......@@ -289,15 +295,12 @@ class AndroidInAppWebViewOptions
///Set to `true` to have the session cookie cache cleared before the new window is opened.
bool clearSessionCache;
///Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `false`.
///Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `true`.
bool builtInZoomControls;
///Set to `true` if the WebView should display on-screen zoom controls when using the built-in zoom mechanisms. The default value is `false`.
bool displayZoomControls;
///Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
bool supportZoom;
///Set to `true` if you want the database storage API is enabled. The default value is `true`.
bool databaseEnabled;
......@@ -493,10 +496,9 @@ class AndroidInAppWebViewOptions
AndroidInAppWebViewOptions({
this.textZoom = 100,
this.clearSessionCache = false,
this.builtInZoomControls = false,
this.builtInZoomControls = true,
this.displayZoomControls = false,
this.supportZoom = true,
this.databaseEnabled = false,
this.databaseEnabled = true,
this.domStorageEnabled = true,
this.useWideViewPort = true,
this.safeBrowsingEnabled = true,
......@@ -553,7 +555,6 @@ class AndroidInAppWebViewOptions
"clearSessionCache": clearSessionCache,
"builtInZoomControls": builtInZoomControls,
"displayZoomControls": displayZoomControls,
"supportZoom": supportZoom,
"databaseEnabled": databaseEnabled,
"domStorageEnabled": domStorageEnabled,
"useWideViewPort": useWideViewPort,
......@@ -610,7 +611,6 @@ class AndroidInAppWebViewOptions
options.clearSessionCache = map["clearSessionCache"];
options.builtInZoomControls = map["builtInZoomControls"];
options.displayZoomControls = map["displayZoomControls"];
options.supportZoom = map["supportZoom"];
options.databaseEnabled = map["databaseEnabled"];
options.domStorageEnabled = map["domStorageEnabled"];
options.useWideViewPort = map["useWideViewPort"];
......
name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
version: 3.3.0+3
version: 3.4.0
homepage: https://github.com/pichillilorenzo/flutter_inappwebview
environment:
......
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