diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c72d341..10ea592b 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index 0fca9379..4ac1fc2e 100755 --- a/README.md +++ b/README.md @@ -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 app’s 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. diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java index 005503c2..383bfdb2 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java @@ -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," + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java index bd0841cc..3859a175 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java @@ -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) 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) 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 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) { diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java index 560e7bd1..3453e152 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java @@ -28,13 +28,25 @@ public class JavaScriptBridgeInterface { " !function(t,e){\"object\"==typeof exports&&\"undefined\"!=typeof module?e(exports):\"function\"==typeof define&&define.amd?define([\"exports\"],e):e(t.RSVP={})}(this,function(t){\"use strict\";function e(t){var e=t._promiseCallbacks;return e||(e=t._promiseCallbacks={}),e}var r={mixin:function(t){return t.on=this.on,t.off=this.off,t.trigger=this.trigger,t._promiseCallbacks=void 0,t},on:function(t,r){if(\"function\"!=typeof r)throw new TypeError(\"Callback must be a function\");var n=e(this),o=n[t];o||(o=n[t]=[]),-1===o.indexOf(r)&&o.push(r)},off:function(t,r){var n=e(this);if(r){var o=n[t],i=o.indexOf(r);-1!==i&&o.splice(i,1)}else n[t]=[]},trigger:function(t,r,n){var o=e(this)[t];if(o)for(var i=0;i2&&void 0!==arguments[2])||arguments[2],o=arguments[3];return function(t,e){if(!t)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return!e||\"object\"!=typeof e&&\"function\"!=typeof e?t:e}(this,t.call(this,e,r,n,o))}return function(t,e){if(\"function\"!=typeof e&&null!==e)throw new TypeError(\"Super expression must either be null or a function, not \"+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype._init=function(t,e){this._result={},this._enumerate(e)},e.prototype._enumerate=function(t){var e=Object.keys(t),r=e.length,n=this.promise;this._remaining=r;for(var o=void 0,i=void 0,s=0;n._state===a&&s { initialUrl: "https://flutter.dev/", // initialFile: "assets/index.html", initialHeaders: {}, + initialUserScripts: UnmodifiableListView([ + + ]), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( useShouldOverrideUrlLoading: false, @@ -143,7 +146,7 @@ class _InAppWebViewExampleScreenState extends State { }); }, onConsoleMessage: (controller, consoleMessage) { - // print(consoleMessage); + print(consoleMessage); }, ), ), diff --git a/example/lib/main.dart b/example/lib/main.dart index 38682433..9e6a49a9 100755 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -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); } diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift index 4b18c027..a96abc3f 100755 --- a/ios/Classes/InAppWebView.swift +++ b/ios/Classes/InAppWebView.swift @@ -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, *) { diff --git a/ios/Classes/InAppWebViewOptions.swift b/ios/Classes/InAppWebViewOptions.swift index 340405ee..239123c9 100755 --- a/ios/Classes/InAppWebViewOptions.swift +++ b/ios/Classes/InAppWebViewOptions.swift @@ -62,6 +62,9 @@ public class InAppWebViewOptions: Options { 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 { 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 { 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 } diff --git a/ios/Classes/InAppWebViewStatic.swift b/ios/Classes/InAppWebViewStatic.swift index 87efe4c3..aa36a906 100755 --- a/ios/Classes/InAppWebViewStatic.swift +++ b/ios/Classes/InAppWebViewStatic.swift @@ -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 diff --git a/lib/src/in_app_webview_controller.dart b/lib/src/in_app_webview_controller.dart index f488324d..9441e618 100644 --- a/lib/src/in_app_webview_controller.dart +++ b/lib/src/in_app_webview_controller.dart @@ -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 handlesURLScheme(String urlScheme) async { + Map args = {}; + args.putIfAbsent('urlScheme', () => urlScheme); + return await InAppWebViewController._staticChannel + .invokeMethod('handlesURLScheme', args); + } } diff --git a/lib/src/types.dart b/lib/src/types.dart index de0221af..1555f30c 100755 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -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 doesn’t apply to changes your script makes to the underlying web content, such as the document’s 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 toMap() { - return {"source": source, "injectionTime": injectionTime.toValue(), "iosForMainFrameOnly": iosForMainFrameOnly}; + return {"source": source, + "injectionTime": injectionTime.toValue(), + "iosForMainFrameOnly": iosForMainFrameOnly, + "contentWorld": contentWorld?.name + }; } Map toJson() { @@ -4585,4 +4595,29 @@ class UserScript { String toString() { return toMap().toString(); } -} \ No newline at end of file +} + +///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 `