Added mediaType, pageZoom, limitsNavigationsToAppBoundDomains iOS-specific webview options, Added handlesURLScheme iOS-specific webview method, Added ContentWorld class, minor bug fixes

This commit is contained in:
Lorenzo Pichilli 2021-02-04 01:43:55 +01:00
parent 37546a1dc0
commit a023d34fd9
14 changed files with 431 additions and 114 deletions

View File

@ -8,7 +8,9 @@
- Added `UserScript` and `UserScriptInjectionTime` classes
- Added `initialUserScripts` WebView option
- Added `addUserScript`, `addUserScripts`, `removeUserScript`, `removeUserScripts`, `removeAllUserScripts` WebView methods
- Added `isDirectionalLockEnabled` iOS-specific webview option
- Added `isDirectionalLockEnabled`, `mediaType`, `pageZoom`, `limitsNavigationsToAppBoundDomains` iOS-specific webview options
- Added `handlesURLScheme` iOS-specific webview method
- Added `ContentWorld` class
- Updated integration tests
- Merge "Upgraded appcompat to 1.2.0-rc-02" [#465](https://github.com/pichillilorenzo/flutter_inappwebview/pull/465) (thanks to [andreidiaconu](https://github.com/andreidiaconu))
- Merge "Added missing field 'headers' which returned by WebResourceResponse.toMap()" [#490](https://github.com/pichillilorenzo/flutter_inappwebview/pull/490) (thanks to [Doflatango](https://github.com/Doflatango))
@ -32,6 +34,7 @@
### BREAKING CHANGES
- Minimum Flutter version required is `1.22.0` and Dart SDK `>=2.12.0-0 <3.0.0`
- iOS Xcode version `>= 12`
- Removed `debuggingEnabled` WebView option; on Android you should use now the `AndroidInAppWebViewController.setWebContentsDebuggingEnabled(bool debuggingEnabled)` static method; on iOS, debugging is always enabled
- `allowUniversalAccessFromFileURLs` and `allowFileAccessFromFileURLs` WebView options moved from Android-specific options to cross-platform options.

View File

@ -33,7 +33,7 @@ If you need a specific version, please change the **GitHub branch** of this repo
- Dart sdk: ">=2.12.0-0 <3.0.0"
- Flutter: ">=1.22.0"
- Android: `minSdkVersion 17` and add support for `androidx` (see [AndroidX Migration](https://flutter.dev/docs/development/androidx-migration) to migrate an existing app)
- iOS: `--ios-language swift`, Xcode version `>= 11`
- iOS: `--ios-language swift`, Xcode version `>= 12`
### IMPORTANT Note for Android and iOS
@ -494,6 +494,7 @@ iOS-specific methods can be called using the `InAppWebViewController.ios` attrib
* `hasOnlySecureContent`: A Boolean value indicating whether all resources on the page have been loaded over securely encrypted connections.
* `reloadFromOrigin`: Reloads the current page, performing end-to-end revalidation using cache-validating conditionals if possible.
* `static handlesURLScheme(String urlScheme)`: Returns a Boolean value that indicates whether WebKit natively supports resources with the specified URL scheme.
##### About the JavaScript handler
@ -643,9 +644,12 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
* `ignoresViewportScaleLimits`: Set to `true` if you want that the WebView should always allow scaling of the webpage, regardless of the author's intent.
* `isFraudulentWebsiteWarningEnabled`: A Boolean value indicating whether warnings should be shown for suspected fraudulent content such as phishing or malware.
* `isPagingEnabled`: A Boolean value that determines whether paging is enabled for the scroll view. The default value is `false`.
* `isDirectionalLockEnabled`: A Boolean value that determines whether scrolling is disabled in a particular direction.
* `isDirectionalLockEnabled`: A Boolean value that determines whether scrolling is disabled in a particular direction. The default value is `false`.
* `limitsNavigationsToAppBoundDomains`: A Boolean value that indicates whether the web view limits navigation to pages within the apps domain. 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`.
* `mediaType`: The media type for the contents of the web view. The default value is `null`.
* `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`.
* `pageZoom`: The scale factor by which the web view scales content relative to its bounds. The default value is `1.0`.
* `scrollsToTop`: A Boolean value that controls whether the scroll-to-top gesture is enabled. The default value is `true`.
* `selectionGranularity`: The level of granularity with which the user can interactively select content in the web view.
* `sharedCookiesEnabled`: Set `true` if shared cookies from `HTTPCookieStorage.shared` should used for every load request in the WebView.

View File

@ -113,13 +113,45 @@ final public class InAppWebView extends InputAwareWebView {
public Runnable checkContextMenuShouldBeClosedTask;
public int newCheckContextMenuShouldBeClosedTaskTask = 100; // ms
static final String scriptsWrapperJS = "(function(){" +
" if (window." + JavaScriptBridgeInterface.name + "._scriptsLoaded == null) {" +
static final String pluginScriptsWrapperJS = "(function(){" +
" if (window." + JavaScriptBridgeInterface.name + "._pluginScriptsLoaded == null || !window." + JavaScriptBridgeInterface.name + "._pluginScriptsLoaded) {" +
" $PLACEHOLDER_VALUE" +
" window." + JavaScriptBridgeInterface.name + "._scriptsLoaded = true;" +
" window." + JavaScriptBridgeInterface.name + "._pluginScriptsLoaded = true;" +
" }" +
"})();";
static final String userScriptsAtDocumentStartWrapperJS = "if (window." + JavaScriptBridgeInterface.name + "._userScriptsAtDocumentStartLoaded == null || !window." + JavaScriptBridgeInterface.name + "._userScriptsAtDocumentStartLoaded) {" +
" $PLACEHOLDER_VALUE" +
" window." + JavaScriptBridgeInterface.name + "._userScriptsAtDocumentStartLoaded = true;" +
"}";
static final String userScriptsAtDocumentEndWrapperJS = "if (window." + JavaScriptBridgeInterface.name + "._userScriptsAtDocumentEndLoaded == null || !window." + JavaScriptBridgeInterface.name + "._userScriptsAtDocumentEndLoaded) {" +
" $PLACEHOLDER_VALUE" +
" window." + JavaScriptBridgeInterface.name + "._userScriptsAtDocumentEndLoaded = true;" +
"}";
static final String contentWorldWrapperJS = "(function() {" +
" var iframe = document.getElementById('$CONTENT_WORLD_NAME');" +
" if (iframe == null) {" +
" iframe = document.createElement('iframe');" +
" iframe.id = '$CONTENT_WORLD_NAME';" +
" iframe.style = 'display: none; z-index: 0; position: absolute; width: 0px; height: 0px';" +
" document.body.append(iframe);" +
" }" +
" var script = iframe.contentWindow.document.createElement('script');" +
" var sourceEncoded = $JSON_SOURCE_ENCODED;" +
" script.innerHTML = sourceEncoded.source;" +
" iframe.contentWindow.document.body.append(script);" +
"})();";
static final String documentReadyWrapperJS = "if (document.readyState === 'interactive' || document.readyState === 'complete') { " +
" $PLACEHOLDER_VALUE" +
"} else {" +
" document.addEventListener('DOMContentLoaded', function() {" +
" $PLACEHOLDER_VALUE" +
" });" +
"}";
static final String consoleLogJS = "(function(console) {" +
" var oldLogs = {" +
" 'log': console.log," +
@ -147,11 +179,15 @@ final public class InAppWebView extends InputAwareWebView {
"})(window.console);";
static final String printJS = "window.print = function() {" +
" window." + JavaScriptBridgeInterface.name + ".callHandler('onPrint', window.location.href);" +
" if (window.top == null || window.top === window) {" +
" window." + JavaScriptBridgeInterface.name + ".callHandler('onPrint', window.location.href);" +
" } else {" +
" window.top.print();" +
" }" +
"};";
static final String platformReadyJS = "(function() {" +
" if (window." + JavaScriptBridgeInterface.name + "._platformReady == null) {" +
" if ((window.top == null || window.top === window) && window." + JavaScriptBridgeInterface.name + "._platformReady == null) {" +
" window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));" +
" window." + JavaScriptBridgeInterface.name + "._platformReady = true;" +
" }" +
@ -171,8 +207,8 @@ final public class InAppWebView extends InputAwareWebView {
" observer.observe({entryTypes: ['resource']});" +
"})();";
static final String variableForShouldInterceptAjaxRequestJS = "window._flutter_inappwebview_useShouldInterceptAjaxRequest";
static final String enableVariableForShouldInterceptAjaxRequestJS = variableForShouldInterceptAjaxRequestJS + " = $PLACEHOLDER_VALUE;";
static final String variableForShouldInterceptAjaxRequestJS = "_flutter_inappwebview_useShouldInterceptAjaxRequest";
static final String enableVariableForShouldInterceptAjaxRequestJS = "window." + variableForShouldInterceptAjaxRequestJS + " = $PLACEHOLDER_VALUE;";
static final String interceptAjaxRequestsJS = "(function(ajax) {" +
" var send = ajax.prototype.send;" +
@ -225,7 +261,8 @@ final public class InAppWebView extends InputAwareWebView {
" };" +
" function handleEvent(e) {" +
" var self = this;" +
" if (" + variableForShouldInterceptAjaxRequestJS + " == null || " + variableForShouldInterceptAjaxRequestJS + " == true) {" +
" var w = (window.top == null || window.top === window) ? window : window.top;" +
" if (w." + variableForShouldInterceptAjaxRequestJS + " == null || w." + variableForShouldInterceptAjaxRequestJS + " == true) {" +
" var headers = this.getAllResponseHeaders();" +
" var responseHeaders = {};" +
" if (headers != null) {" +
@ -276,12 +313,14 @@ final public class InAppWebView extends InputAwareWebView {
" };" +
" ajax.prototype.send = function(data) {" +
" var self = this;" +
" if (" + variableForShouldInterceptAjaxRequestJS + " == null || " + variableForShouldInterceptAjaxRequestJS + " == true) {" +
" var w = (window.top == null || window.top === window) ? window : window.top;" +
" if (w." + variableForShouldInterceptAjaxRequestJS + " == null || w." + variableForShouldInterceptAjaxRequestJS + " == true) {" +
" if (!this._flutter_inappwebview_already_onreadystatechange_wrapped) {" +
" this._flutter_inappwebview_already_onreadystatechange_wrapped = true;" +
" var onreadystatechange = this.onreadystatechange;" +
" this.onreadystatechange = function() {" +
" if (" + variableForShouldInterceptAjaxRequestJS + " == null || " + variableForShouldInterceptAjaxRequestJS + " == true) {" +
" var w = (window.top == null || window.top === window) ? window : window.top;" +
" if (w." + variableForShouldInterceptAjaxRequestJS + " == null || w." + variableForShouldInterceptAjaxRequestJS + " == true) {" +
" var headers = this.getAllResponseHeaders();" +
" var responseHeaders = {};" +
" if (headers != null) {" +
@ -384,8 +423,8 @@ final public class InAppWebView extends InputAwareWebView {
" };" +
"})(window.XMLHttpRequest);";
static final String variableForShouldInterceptFetchRequestsJS = "window._flutter_inappwebview_useShouldInterceptFetchRequest";
static final String enableVariableForShouldInterceptFetchRequestsJS = variableForShouldInterceptFetchRequestsJS + " = $PLACEHOLDER_VALUE;";
static final String variableForShouldInterceptFetchRequestsJS = "_flutter_inappwebview_useShouldInterceptFetchRequest";
static final String enableVariableForShouldInterceptFetchRequestsJS = "window." + variableForShouldInterceptFetchRequestsJS + " = $PLACEHOLDER_VALUE;";
static final String interceptFetchRequestsJS = "(function(fetch) {" +
" if (fetch == null) {" +
@ -454,7 +493,8 @@ final public class InAppWebView extends InputAwareWebView {
" return credentials;" +
" }" +
" window.fetch = async function(resource, init) {" +
" if (window." + variableForShouldInterceptFetchRequestsJS + " == null || window." + variableForShouldInterceptFetchRequestsJS + " == true) {" +
" var w = (window.top == null || window.top === window) ? window : window.top;" +
" if (w." + variableForShouldInterceptFetchRequestsJS + " == null || w." + variableForShouldInterceptFetchRequestsJS + " == true) {" +
" var fetchRequest = {" +
" url: null," +
" method: null," +

View File

@ -29,6 +29,9 @@ import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivit
import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappwebview.Util;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayInputStream;
import java.net.MalformedURLException;
import java.net.URI;
@ -163,11 +166,10 @@ public class InAppWebViewClient extends WebViewClient {
private void loadCustomJavaScriptOnPageStarted(WebView view) {
InAppWebView webView = (InAppWebView) view;
String js = preparePluginUserScripts(webView);
js += prepareUserScriptsAtDocumentStart(webView);
String jsPluginScripts = preparePluginUserScripts(webView);
String jsUserScriptsAtDocumentStart = prepareUserScriptsAtDocumentStart(webView);
js = InAppWebView.scriptsWrapperJS
.replace("$PLACEHOLDER_VALUE", js);
String js = wrapPluginAndUserScripts(jsPluginScripts, jsUserScriptsAtDocumentStart, null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript(js, (ValueCallback<String>) null);
@ -180,12 +182,11 @@ public class InAppWebViewClient extends WebViewClient {
InAppWebView webView = (InAppWebView) view;
// try to reload also custom scripts if they were not loaded during the onPageStarted event
String js = preparePluginUserScripts(webView);
js += prepareUserScriptsAtDocumentStart(webView);
js += prepareUserScriptsAtDocumentEnd(webView);
String jsPluginScripts = preparePluginUserScripts(webView);
String jsUserScriptsAtDocumentStart = prepareUserScriptsAtDocumentStart(webView);
String jsUserScriptsAtDocumentEnd = prepareUserScriptsAtDocumentEnd(webView);
js = InAppWebView.scriptsWrapperJS
.replace("$PLACEHOLDER_VALUE", js);
String js = wrapPluginAndUserScripts(jsPluginScripts, jsUserScriptsAtDocumentStart, jsUserScriptsAtDocumentEnd);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript(js, (ValueCallback<String>) null);
@ -196,7 +197,7 @@ public class InAppWebViewClient extends WebViewClient {
private String preparePluginUserScripts(InAppWebView webView) {
String js = InAppWebView.consoleLogJS;
js += JavaScriptBridgeInterface.flutterInAppBroserJSClass;
js += JavaScriptBridgeInterface.callHandlerScriptJS;
if (webView.options.useShouldInterceptAjaxRequest) {
js += InAppWebView.interceptAjaxRequestsJS;
}
@ -223,8 +224,33 @@ public class InAppWebViewClient extends WebViewClient {
Integer injectionTime = (Integer) userScript.get("injectionTime");
if (injectionTime == null || injectionTime == 0) {
String source = (String) userScript.get("source");
String contentWorldName = (String) userScript.get("contentWorld");
if (source != null) {
js.append("(function(){").append(source).append("})();");
if (contentWorldName != null && !contentWorldName.equals("page")) {
String jsPluginScripts = preparePluginUserScripts(webView);
source = jsPluginScripts + "\n" + source;
}
JSONObject sourceEncoded = new JSONObject();
try {
// encode the javascript source in order to escape special chars and quotes
sourceEncoded.put("source", source);
} catch (JSONException e) {
e.printStackTrace();
}
String sourceWrapped = contentWorldName == null || contentWorldName.equals("page") ? source :
InAppWebView.contentWorldWrapperJS.replace("$CONTENT_WORLD_NAME", contentWorldName)
.replace("$CONTENT_WORLD_NAME", contentWorldName)
.replace("$JSON_SOURCE_ENCODED", sourceEncoded.toString());
if (contentWorldName != null && !contentWorldName.equals("page")) {
// adds another wrapper because sometimes document.body is not ready and it is undefined, causing an error and not adding the iframe element.
sourceWrapped = InAppWebView.documentReadyWrapperJS.replace("$PLACEHOLDER_VALUE", sourceWrapped)
.replace("$PLACEHOLDER_VALUE", sourceWrapped);
}
js.append(sourceWrapped);
}
}
}
@ -237,16 +263,44 @@ public class InAppWebViewClient extends WebViewClient {
for (Map<String, Object> userScript : webView.userScripts) {
Integer injectionTime = (Integer) userScript.get("injectionTime");
if (injectionTime == 1) {
if (injectionTime != null && injectionTime == 1) {
String source = (String) userScript.get("source");
String contentWorldName = (String) userScript.get("contentWorld");
if (source != null) {
js.append("(function(){").append(source).append("})();");
if (contentWorldName != null && !contentWorldName.equals("page")) {
String jsPluginScripts = preparePluginUserScripts(webView);
source = jsPluginScripts + "\n" + source;
}
JSONObject sourceEncoded = new JSONObject();
try {
// encode the javascript source in order to escape special chars and quotes
sourceEncoded.put("source", source);
} catch (JSONException e) {
e.printStackTrace();
}
String sourceWrapped = contentWorldName == null || contentWorldName.equals("page") ? source :
InAppWebView.contentWorldWrapperJS.replace("$CONTENT_WORLD_NAME", contentWorldName)
.replace("$CONTENT_WORLD_NAME", contentWorldName)
.replace("$JSON_SOURCE_ENCODED", sourceEncoded.toString());
js.append(sourceWrapped);
}
}
}
return js.toString();
}
private String wrapPluginAndUserScripts(String jsPluginScripts, @Nullable String jsUserScriptsAtDocumentStart, @Nullable String jsUserScriptsAtDocumentEnd) {
String jsPluginScriptsWrapped = InAppWebView.pluginScriptsWrapperJS
.replace("$PLACEHOLDER_VALUE", jsPluginScripts);
String jsUserScriptsAtDocumentStartWrapped = jsUserScriptsAtDocumentStart == null || jsUserScriptsAtDocumentStart.isEmpty() ? "" :
InAppWebView.userScriptsAtDocumentStartWrapperJS.replace("$PLACEHOLDER_VALUE", jsUserScriptsAtDocumentStart);
String jsUserScriptsAtDocumentEndWrapped = jsUserScriptsAtDocumentEnd == null || jsUserScriptsAtDocumentEnd.isEmpty() ? "" :
InAppWebView.userScriptsAtDocumentEndWrapperJS.replace("$PLACEHOLDER_VALUE", jsUserScriptsAtDocumentEnd);
return jsPluginScriptsWrapped + "\n" + jsUserScriptsAtDocumentStartWrapped + "\n" + jsUserScriptsAtDocumentEndWrapped;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {

View File

@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"android":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+8/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.0.4+3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"device_info","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":["device_info"]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-02-03 15:14:14.166914","version":"1.26.0-18.0.pre.90"}
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"android":[{"name":"device_info","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-2.0.0-nullsafety.2/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":["device_info"]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/.pub-cache/git/plugins-16f3281b04b0db12e609352b1c9544901392e428/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.27/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1+1/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.4/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+8/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.0.4+3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"device_info","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":["device_info"]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-02-04 01:10:32.198308","version":"1.26.0-18.0.pre.90"}

View File

@ -84,6 +84,9 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
initialUrl: "https://flutter.dev/",
// initialFile: "assets/index.html",
initialHeaders: {},
initialUserScripts: UnmodifiableListView<UserScript>([
]),
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: false,
@ -143,7 +146,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
});
},
onConsoleMessage: (controller, consoleMessage) {
// print(consoleMessage);
print(consoleMessage);
},
),
),

View File

@ -8,14 +8,14 @@ import 'package:flutter_inappwebview_example/chrome_safari_browser_example.scree
import 'package:flutter_inappwebview_example/headless_in_app_webview.screen.dart';
import 'package:flutter_inappwebview_example/in_app_webiew_example.screen.dart';
import 'package:flutter_inappwebview_example/in_app_browser_example.screen.dart';
import 'package:permission_handler/permission_handler.dart';
// import 'package:permission_handler/permission_handler.dart';
// InAppLocalhostServer localhostServer = new InAppLocalhostServer();
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await Permission.camera.request();
await Permission.microphone.request();
// await Permission.camera.request();
// await Permission.microphone.request();
if (Platform.isAndroid) {
await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
}

View File

@ -285,8 +285,8 @@ let resourceObserverJS = """
})();
"""
let variableForShouldInterceptAjaxRequestJS = "window._flutter_inappwebview_useShouldInterceptAjaxRequest"
let enableVariableForShouldInterceptAjaxRequestJS = "\(variableForShouldInterceptAjaxRequestJS) = $PLACEHOLDER_VALUE;"
let variableForShouldInterceptAjaxRequestJS = "_flutter_inappwebview_useShouldInterceptAjaxRequest"
let enableVariableForShouldInterceptAjaxRequestJS = "window.\(variableForShouldInterceptAjaxRequestJS) = $PLACEHOLDER_VALUE;"
let interceptAjaxRequestsJS = """
(function(ajax) {
@ -340,7 +340,7 @@ let interceptAjaxRequestsJS = """
};
function handleEvent(e) {
var self = this;
if (\(variableForShouldInterceptAjaxRequestJS) == null || \(variableForShouldInterceptAjaxRequestJS) == true) {
if (window.\(variableForShouldInterceptAjaxRequestJS) == null || window.\(variableForShouldInterceptAjaxRequestJS) == true) {
var headers = this.getAllResponseHeaders();
var responseHeaders = {};
if (headers != null) {
@ -391,12 +391,12 @@ let interceptAjaxRequestsJS = """
};
ajax.prototype.send = function(data) {
var self = this;
if (\(variableForShouldInterceptAjaxRequestJS) == null || \(variableForShouldInterceptAjaxRequestJS) == true) {
if (window.\(variableForShouldInterceptAjaxRequestJS) == null || window.\(variableForShouldInterceptAjaxRequestJS) == true) {
if (!this._flutter_inappwebview_already_onreadystatechange_wrapped) {
this._flutter_inappwebview_already_onreadystatechange_wrapped = true;
var onreadystatechange = this.onreadystatechange;
this.onreadystatechange = function() {
if (\(variableForShouldInterceptAjaxRequestJS) == null || \(variableForShouldInterceptAjaxRequestJS) == true) {
if (window.\(variableForShouldInterceptAjaxRequestJS) == null || window.\(variableForShouldInterceptAjaxRequestJS) == true) {
var headers = this.getAllResponseHeaders();
var responseHeaders = {};
if (headers != null) {
@ -500,8 +500,8 @@ let interceptAjaxRequestsJS = """
})(window.XMLHttpRequest);
"""
let variableForShouldInterceptFetchRequestsJS = "window._flutter_inappwebview_useShouldInterceptFetchRequest"
let enableVariableForShouldInterceptFetchRequestsJS = "\(variableForShouldInterceptFetchRequestsJS) = $PLACEHOLDER_VALUE;"
let variableForShouldInterceptFetchRequestsJS = "_flutter_inappwebview_useShouldInterceptFetchRequest"
let enableVariableForShouldInterceptFetchRequestsJS = "window.\(variableForShouldInterceptFetchRequestsJS) = $PLACEHOLDER_VALUE;"
let interceptFetchRequestsJS = """
(function(fetch) {
@ -763,13 +763,13 @@ window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = null;
while (target) {
if (target.tagName === 'IMG') {
var img = target;
window.flutter_inappwebview._lastImageTouched = {
window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = {
src: img.src
};
var parent = img.parentNode;
while (parent) {
if (parent.tagName === 'A') {
window.flutter_inappwebview._lastAnchorOrImageTouched = {
window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = {
title: parent.textContent,
url: parent.href,
src: img.src
@ -784,8 +784,8 @@ window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = null;
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 = {
window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = (img != null) ? {src: img.src} : window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched;
window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = {
title: link.textContent,
url: link.href,
src: imgSrc
@ -877,6 +877,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
static var windowWebViews: [Int64:WebViewTransport] = [:]
static var windowAutoincrementId: Int64 = 0;
var userScriptsContentWorlds: [String] = ["defaultClient", "page"]
init(frame: CGRect, configuration: WKWebViewConfiguration, IABController: InAppBrowserWebViewController?, contextMenu: [String: Any]?, channel: FlutterMethodChannel?) {
super.init(frame: frame, configuration: configuration)
self.channel = channel
@ -1059,7 +1061,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
public func prepare() {
self.scrollView.addGestureRecognizer(self.longPressRecognizer!)
addObserver(self,
@ -1156,6 +1157,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
scrollView.maximumZoomScale = CGFloat(options.maximumZoomScale)
scrollView.minimumZoomScale = CGFloat(options.minimumZoomScale)
if #available(iOS 14.0, *) {
mediaType = options.mediaType
pageZoom = CGFloat(options.pageZoom)
}
// debugging is always enabled for iOS,
// there isn't any option to set about it such as on Android.
@ -1182,13 +1188,17 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
configuration.preferences.javaScriptCanOpenWindowsAutomatically = options.javaScriptCanOpenWindowsAutomatically
configuration.preferences.javaScriptEnabled = options.javaScriptEnabled
configuration.preferences.minimumFontSize = CGFloat(options.minimumFontSize)
if #available(iOS 13.0, *) {
configuration.preferences.isFraudulentWebsiteWarningEnabled = options.isFraudulentWebsiteWarningEnabled
configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: options.preferredContentMode)!
}
configuration.preferences.javaScriptEnabled = options.javaScriptEnabled
if #available(iOS 14.0, *) {
configuration.defaultWebpagePreferences.allowsContentJavaScript = options.javaScriptEnabled
}
}
}
@ -1209,22 +1219,51 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
configuration.userContentController.addUserScript(userScriptWindowId)
}
func addPluginUserScripts() -> Void {
if let options = options {
let originalViewPortMetaTagContentJSScript = WKUserScript(source: originalViewPortMetaTagContentJS, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
configuration.userContentController.addUserScript(originalViewPortMetaTagContentJSScript)
@available(iOS 14.0, *)
func addSharedPluginUserScriptsBetweenContentWorlds(contentWorlds: [WKContentWorld]) -> Void {
for contentWorld in contentWorlds {
let promisePolyfillJSScript = WKUserScript(source: promisePolyfillJS, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: contentWorld)
configuration.userContentController.addUserScript(promisePolyfillJSScript)
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)
let javaScriptBridgeJSScript = WKUserScript(source: javaScriptBridgeJS, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: contentWorld)
configuration.userContentController.addUserScript(javaScriptBridgeJSScript)
configuration.userContentController.removeScriptMessageHandler(forName: "callHandler", contentWorld: contentWorld)
configuration.userContentController.add(self, contentWorld: contentWorld, name: "callHandler")
let consoleLogJSScript = WKUserScript(source: consoleLogJS, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: contentWorld)
configuration.userContentController.addUserScript(consoleLogJSScript)
configuration.userContentController.removeScriptMessageHandler(forName: "consoleLog", contentWorld: contentWorld)
configuration.userContentController.add(self, contentWorld: contentWorld, name: "consoleLog")
configuration.userContentController.removeScriptMessageHandler(forName: "consoleDebug", contentWorld: contentWorld)
configuration.userContentController.add(self, contentWorld: contentWorld, name: "consoleDebug")
configuration.userContentController.removeScriptMessageHandler(forName: "consoleError", contentWorld: contentWorld)
configuration.userContentController.add(self, contentWorld: contentWorld, name: "consoleError")
configuration.userContentController.removeScriptMessageHandler(forName: "consoleInfo", contentWorld: contentWorld)
configuration.userContentController.add(self, contentWorld: contentWorld, name: "consoleInfo")
configuration.userContentController.removeScriptMessageHandler(forName: "consoleWarn", contentWorld: contentWorld)
configuration.userContentController.add(self, contentWorld: contentWorld, name: "consoleWarn")
if let options = options {
if options.useShouldInterceptAjaxRequest {
let interceptAjaxRequestsJSScript = WKUserScript(source: interceptAjaxRequestsJS, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: contentWorld)
configuration.userContentController.addUserScript(interceptAjaxRequestsJSScript)
}
if options.useShouldInterceptFetchRequest {
let interceptFetchRequestsJSScript = WKUserScript(source: interceptFetchRequestsJS, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: contentWorld)
configuration.userContentController.addUserScript(interceptFetchRequestsJSScript)
}
}
}
}
func addPluginUserScripts() -> Void {
if #available(iOS 14.0, *) {
let contentWorlds = userScriptsContentWorlds.map { (contentWorldName) -> WKContentWorld in
return getContentWorld(name: contentWorldName)
}
addSharedPluginUserScriptsBetweenContentWorlds(contentWorlds: contentWorlds)
} else {
let promisePolyfillJSScript = WKUserScript(source: promisePolyfillJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(promisePolyfillJSScript)
@ -1246,40 +1285,57 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
configuration.userContentController.removeScriptMessageHandler(forName: "consoleWarn")
configuration.userContentController.add(self, name: "consoleWarn")
let findElementsAtPointJSScript = WKUserScript(source: findElementsAtPointJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(findElementsAtPointJSScript)
if let options = options {
if options.useShouldInterceptAjaxRequest {
let interceptAjaxRequestsJSScript = WKUserScript(source: interceptAjaxRequestsJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(interceptAjaxRequestsJSScript)
}
if options.useShouldInterceptFetchRequest {
let interceptFetchRequestsJSScript = WKUserScript(source: interceptFetchRequestsJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(interceptFetchRequestsJSScript)
}
}
}
let findElementsAtPointJSScript = WKUserScript(source: findElementsAtPointJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(findElementsAtPointJSScript)
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)
let findTextHighlightJSScript = WKUserScript(source: findTextHighlightJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(findTextHighlightJSScript)
configuration.userContentController.removeScriptMessageHandler(forName: "onFindResultReceived")
configuration.userContentController.add(self, name: "onFindResultReceived")
let onWindowFocusEventJSScript = WKUserScript(source: onWindowFocusEventJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(onWindowFocusEventJSScript)
let onWindowBlurEventJSScript = WKUserScript(source: onWindowBlurEventJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(onWindowBlurEventJSScript)
if let options = options {
let originalViewPortMetaTagContentJSScript = WKUserScript(source: originalViewPortMetaTagContentJS, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
configuration.userContentController.addUserScript(originalViewPortMetaTagContentJSScript)
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.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)
}
if options.useOnLoadResource {
let resourceObserverJSScript = WKUserScript(source: resourceObserverJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(resourceObserverJSScript)
}
let findTextHighlightJSScript = WKUserScript(source: findTextHighlightJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(findTextHighlightJSScript)
configuration.userContentController.removeScriptMessageHandler(forName: "onFindResultReceived")
configuration.userContentController.add(self, name: "onFindResultReceived")
let onWindowFocusEventJSScript = WKUserScript(source: onWindowFocusEventJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(onWindowFocusEventJSScript)
let onWindowBlurEventJSScript = WKUserScript(source: onWindowBlurEventJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(onWindowBlurEventJSScript)
if options.useShouldInterceptAjaxRequest {
let interceptAjaxRequestsJSScript = WKUserScript(source: interceptAjaxRequestsJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(interceptAjaxRequestsJSScript)
}
if options.useShouldInterceptFetchRequest {
let interceptFetchRequestsJSScript = WKUserScript(source: interceptFetchRequestsJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(interceptFetchRequestsJSScript)
}
}
}
@ -1295,10 +1351,21 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
public func appendUserScript(userScript: [String: Any]) -> Void {
let wkUserScript = WKUserScript(source: userScript["source"] as! String,
injectionTime: WKUserScriptInjectionTime.init(rawValue: userScript["injectionTime"] as! Int) ?? .atDocumentStart,
forMainFrameOnly: userScript["iosForMainFrameOnly"] as! Bool)
userScripts.append(wkUserScript)
var wkUserScript: WKUserScript?
if #available(iOS 14.0, *), let contentWorldName = userScript["contentWorld"] as? String {
if !userScriptsContentWorlds.contains(contentWorldName) {
userScriptsContentWorlds.append(contentWorldName)
}
wkUserScript = WKUserScript(source: userScript["source"] as! String,
injectionTime: WKUserScriptInjectionTime.init(rawValue: userScript["injectionTime"] as! Int) ?? .atDocumentStart,
forMainFrameOnly: userScript["iosForMainFrameOnly"] as! Bool,
in: getContentWorld(name: contentWorldName))
} else {
wkUserScript = WKUserScript(source: userScript["source"] as! String,
injectionTime: WKUserScriptInjectionTime.init(rawValue: userScript["injectionTime"] as! Int) ?? .atDocumentStart,
forMainFrameOnly: userScript["iosForMainFrameOnly"] as! Bool)
}
userScripts.append(wkUserScript!)
}
public func appendUserScripts(wkUserScripts: [WKUserScript]) -> Void {
@ -1331,6 +1398,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
addPluginUserScripts()
}
@available(iOS 14.0, *)
func getContentWorld(name: String) -> WKContentWorld {
switch name {
case "defaultClient":
return WKContentWorld.defaultClient
case "page":
return WKContentWorld.page
default:
return WKContentWorld.world(name: name)
}
}
@available(iOS 10.0, *)
static public func getDataDetectorType(type: String) -> WKDataDetectorTypes {
switch type {
@ -1478,6 +1557,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
}
if #available(iOS 14.0, *) {
configuration.limitsNavigationsToAppBoundDomains = options.limitsNavigationsToAppBoundDomains
}
}
return configuration
@ -1739,10 +1822,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
configuration.preferences.javaScriptCanOpenWindowsAutomatically = newOptions.javaScriptCanOpenWindowsAutomatically
}
if newOptionsMap["javaScriptEnabled"] != nil && options?.javaScriptEnabled != newOptions.javaScriptEnabled {
configuration.preferences.javaScriptEnabled = newOptions.javaScriptEnabled
}
if newOptionsMap["minimumFontSize"] != nil && options?.minimumFontSize != newOptions.minimumFontSize {
configuration.preferences.minimumFontSize = CGFloat(newOptions.minimumFontSize)
}
@ -1848,6 +1927,27 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
clearCache()
}
if newOptionsMap["javaScriptEnabled"] != nil && options?.javaScriptEnabled != newOptions.javaScriptEnabled {
configuration.preferences.javaScriptEnabled = newOptions.javaScriptEnabled
}
if #available(iOS 14.0, *) {
if options?.mediaType != newOptions.mediaType {
mediaType = newOptions.mediaType
}
if newOptionsMap["pageZoom"] != nil && options?.pageZoom != newOptions.pageZoom {
pageZoom = CGFloat(newOptions.pageZoom)
}
if newOptionsMap["limitsNavigationsToAppBoundDomains"] != nil && options?.limitsNavigationsToAppBoundDomains != newOptions.limitsNavigationsToAppBoundDomains {
configuration.limitsNavigationsToAppBoundDomains = newOptions.limitsNavigationsToAppBoundDomains
}
if newOptionsMap["javaScriptEnabled"] != nil && options?.javaScriptEnabled != newOptions.javaScriptEnabled {
configuration.defaultWebpagePreferences.allowsContentJavaScript = newOptions.javaScriptEnabled
}
}
if #available(iOS 11.0, *), newOptionsMap["contentBlockers"] != nil {
configuration.userContentController.removeAllContentRuleLists()
let contentBlockers = newOptions.contentBlockers
@ -1870,11 +1970,9 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
self.options = newOptions
scrollView.isScrollEnabled = !(newOptions.disableVerticalScroll && newOptions.disableHorizontalScroll)
if let options = self.options {
scrollView.isScrollEnabled = !(options.disableVerticalScroll && options.disableHorizontalScroll)
}
self.options = newOptions
}
func getOptions() -> [String: Any?]? {
@ -3253,6 +3351,11 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
configuration.userContentController.removeScriptMessageHandler(forName: "onFindResultReceived")
if #available(iOS 14.0, *) {
configuration.userContentController.removeAllScriptMessageHandlers()
for contentWorldName in userScriptsContentWorlds {
let contentWorld = getContentWorld(name: contentWorldName)
configuration.userContentController.removeAllScriptMessageHandlers(from: contentWorld)
configuration.userContentController.removeAllScriptMessageHandlers(from: contentWorld)
}
}
configuration.userContentController.removeAllUserScripts()
if #available(iOS 11.0, *) {

View File

@ -62,6 +62,9 @@ public class InAppWebViewOptions: Options<InAppWebView> {
var minimumZoomScale = 1.0
var contentInsetAdjustmentBehavior = 2 // UIScrollView.ContentInsetAdjustmentBehavior.never
var isDirectionalLockEnabled = false
var mediaType: String? = nil
var pageZoom = 1.0
var limitsNavigationsToAppBoundDomains = false
override init(){
super.init()
@ -78,7 +81,6 @@ public class InAppWebViewOptions: Options<InAppWebView> {
realOptions["allowsLinkPreview"] = webView.allowsLinkPreview
realOptions["allowsPictureInPictureMediaPlayback"] = configuration.allowsPictureInPictureMediaPlayback
}
realOptions["javaScriptEnabled"] = configuration.preferences.javaScriptEnabled
realOptions["javaScriptCanOpenWindowsAutomatically"] = configuration.preferences.javaScriptCanOpenWindowsAutomatically
if #available(iOS 10.0, *) {
realOptions["mediaPlaybackRequiresUserGesture"] = configuration.mediaTypesRequiringUserActionForPlayback == .all
@ -111,6 +113,13 @@ public class InAppWebViewOptions: Options<InAppWebView> {
realOptions["allowUniversalAccessFromFileURLs"] = configuration.value(forKey: "allowUniversalAccessFromFileURLs")
realOptions["allowFileAccessFromFileURLs"] = configuration.preferences.value(forKey: "allowFileAccessFromFileURLs")
realOptions["isDirectionalLockEnabled"] = webView.scrollView.isDirectionalLockEnabled
realOptions["javaScriptEnabled"] = configuration.preferences.javaScriptEnabled
if #available(iOS 14.0, *) {
realOptions["mediaType"] = webView.mediaType
realOptions["pageZoom"] = Float(webView.pageZoom)
realOptions["limitsNavigationsToAppBoundDomains"] = configuration.limitsNavigationsToAppBoundDomains
realOptions["javaScriptEnabled"] = configuration.defaultWebpagePreferences.allowsContentJavaScript
}
}
return realOptions
}

View File

@ -26,13 +26,22 @@ class InAppWebViewStatic: NSObject, FlutterPlugin {
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
//let arguments = call.arguments as? NSDictionary
let arguments = call.arguments as? NSDictionary
switch call.method {
case "getDefaultUserAgent":
InAppWebViewStatic.getDefaultUserAgent(completionHandler: { (value) in
result(value)
})
break
case "handlesURLScheme":
let urlScheme = arguments!["urlScheme"] as! String
if #available(iOS 11.0, *) {
result(WKWebView.handlesURLScheme(urlScheme))
} else {
result(false)
}
break
default:
result(FlutterMethodNotImplemented)
break

View File

@ -2275,4 +2275,18 @@ class IOSInAppWebViewController {
return await _controller._channel
.invokeMethod('hasOnlySecureContent', args);
}
///Returns a Boolean value that indicates whether WebKit natively supports resources with the specified URL scheme.
///
///[urlScheme] represents the URL scheme associated with the resource.
///
///**NOTE**: available only on iOS 11.0+.
///
///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/2875370-handlesurlscheme
static Future<bool> handlesURLScheme(String urlScheme) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('urlScheme', () => urlScheme);
return await InAppWebViewController._staticChannel
.invokeMethod('handlesURLScheme', args);
}
}

View File

@ -4571,10 +4571,20 @@ class UserScript {
///The default value is `true`. Available only on iOS.
bool iosForMainFrameOnly;
UserScript({required this.source, required this.injectionTime, this.iosForMainFrameOnly = true});
///**NOTE for iOS 14.0+**: The namespace in which to evaluate the script.
///This parameter doesnt apply to changes your script makes to the underlying web content, such as the documents DOM structure.
///Those changes remain visible to all scripts, regardless of which content world you specify.
///For more information about content worlds, see [WKContentWorld](https://developer.apple.com/documentation/webkit/wkcontentworld).
ContentWorld? contentWorld;
UserScript({required this.source, required this.injectionTime, this.iosForMainFrameOnly = true, this.contentWorld});
Map<String, dynamic> toMap() {
return {"source": source, "injectionTime": injectionTime.toValue(), "iosForMainFrameOnly": iosForMainFrameOnly};
return {"source": source,
"injectionTime": injectionTime.toValue(),
"iosForMainFrameOnly": iosForMainFrameOnly,
"contentWorld": contentWorld?.name
};
}
Map<String, dynamic> toJson() {
@ -4585,4 +4595,29 @@ class UserScript {
String toString() {
return toMap().toString();
}
}
}
///Class that represents an object that defines a scope of execution for JavaScript code, and which you use to prevent conflicts between different scripts.
///
///**NOTE for iOS 14.0+**: this class represents the native [WKContentWorld](https://developer.apple.com/documentation/webkit/wkcontentworld) class.
///
///**NOTE for Android**: it will create and append an `<iframe>` HTML element with `id` equals to [name] to the webpage's content that contains only the scripts
///in order to define a new scope of execution for JavaScript code. Unfortunately, there isn't any other way to do it.
///For any [ContentWorld], except [ContentWorld.page], if you need to access to the `window` or `document` global Object,
///you need to use `window.top` and `window.top.document` because the code runs inside an `<iframe>`.
class ContentWorld {
///The name of a custom content world.
final String name;
///Returns the custom content world with the specified name.
ContentWorld.world({required this.name});
///The default world for clients.
static ContentWorld defaultClient = ContentWorld.world(name: "defaultClient");
///The content world for the current webpages content.
///This property contains the content world for scripts that the current webpage executes.
///Be careful when manipulating variables in this content world.
///If you modify a variable with the same name as one the webpage uses, you may unintentionally disrupt the normal operation of that page.
static ContentWorld page = ContentWorld.world(name: "page");
}

View File

@ -844,6 +844,28 @@ class IOSInAppWebViewOptions
///The default value is `false`.
bool isDirectionalLockEnabled;
///The media type for the contents of the web view.
///When the value of this property is `null`, the web view derives the current media type from the CSS media property of its content.
///If you assign a value other than `null` to this property, the web view uses the value you provide instead.
///The default value of this property is `null`.
///
///**NOTE**: available on iOS 14.0+.
String? mediaType;
///The scale factor by which the web view scales content relative to its bounds.
///The default value of this property is `1.0`, which displays the content without any scaling.
///Changing the value of this property is equivalent to setting the CSS `zoom` property on all page content.
///
///**NOTE**: available on iOS 14.0+.
double pageZoom;
///A Boolean value that indicates whether the web view limits navigation to pages within the apps domain.
///Check [App-Bound Domains](https://webkit.org/blog/10882/app-bound-domains/) for more details.
///The default value is `false`.
///
///**NOTE**: available on iOS 14.0+.
bool limitsNavigationsToAppBoundDomains;
IOSInAppWebViewOptions(
{this.disallowOverScroll = false,
this.enableViewportScale = false,
@ -869,7 +891,10 @@ class IOSInAppWebViewOptions
this.minimumZoomScale = 1.0,
this.contentInsetAdjustmentBehavior =
IOSUIScrollViewContentInsetAdjustmentBehavior.NEVER,
this.isDirectionalLockEnabled = false});
this.isDirectionalLockEnabled = false,
this.mediaType,
this.pageZoom = 1.0,
this.limitsNavigationsToAppBoundDomains = false});
@override
Map<String, dynamic> toMap() {
@ -906,6 +931,9 @@ class IOSInAppWebViewOptions
"minimumZoomScale": minimumZoomScale,
"contentInsetAdjustmentBehavior": contentInsetAdjustmentBehavior.toValue(),
"isDirectionalLockEnabled": isDirectionalLockEnabled,
"mediaType": mediaType,
"pageZoom": pageZoom,
"limitsNavigationsToAppBoundDomains": limitsNavigationsToAppBoundDomains,
};
}
@ -956,6 +984,9 @@ class IOSInAppWebViewOptions
IOSUIScrollViewContentInsetAdjustmentBehavior.fromValue(
map["contentInsetAdjustmentBehavior"])!;
options.isDirectionalLockEnabled = map["isDirectionalLockEnabled"];
options.mediaType = map["mediaType"];
options.pageZoom = map["pageZoom"];
options.limitsNavigationsToAppBoundDomains = map["limitsNavigationsToAppBoundDomains"];
return options;
}