diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ContentBlocker/ContentBlockerHandler.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ContentBlocker/ContentBlockerHandler.java index eee7b752..546dcd45 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ContentBlocker/ContentBlockerHandler.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ContentBlocker/ContentBlockerHandler.java @@ -166,7 +166,7 @@ public class ContentBlockerHandler { @Override public void run() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.evaluateJavascript(jsScript, (MethodChannel.Result) null); + webView.evaluateJavascript(jsScript, null); } else { webView.loadUrl("javascript:" + jsScript); } 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 cf1bdd96..e2724ad4 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 @@ -16,6 +16,7 @@ import android.os.Message; import android.print.PrintAttributes; import android.print.PrintDocumentAdapter; import android.print.PrintManager; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.view.ActionMode; @@ -42,6 +43,7 @@ import android.widget.HorizontalScrollView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.webkit.WebViewCompat; import androidx.webkit.WebViewFeature; @@ -56,14 +58,19 @@ import com.pichillilorenzo.flutter_inappwebview.R; import com.pichillilorenzo.flutter_inappwebview.Shared; import com.pichillilorenzo.flutter_inappwebview.Util; +import org.json.JSONException; +import org.json.JSONObject; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; import io.flutter.plugin.common.MethodChannel; @@ -106,8 +113,12 @@ final public class InAppWebView extends InputAwareWebView { public Runnable checkContextMenuShouldBeClosedTask; public int newCheckContextMenuShouldBeClosedTaskTask = 100; // ms + public Set userScriptsContentWorlds = new HashSet() {{ + add("page"); + }}; + static final String pluginScriptsWrapperJS = "(function(){" + - " if (window." + JavaScriptBridgeInterface.name + "._pluginScriptsLoaded == null || !window." + JavaScriptBridgeInterface.name + "._pluginScriptsLoaded) {" + + " if (window." + JavaScriptBridgeInterface.name + " == null || window." + JavaScriptBridgeInterface.name + "._pluginScriptsLoaded == null || !window." + JavaScriptBridgeInterface.name + "._pluginScriptsLoaded) {" + " $PLACEHOLDER_VALUE" + " window." + JavaScriptBridgeInterface.name + "._pluginScriptsLoaded = true;" + " }" + @@ -187,8 +198,8 @@ final public class InAppWebView extends InputAwareWebView { " }" + "})();"; - static final String variableForOnLoadResourceJS = "window._flutter_inappwebview_useOnLoadResource"; - static final String enableVariableForOnLoadResourceJS = variableForOnLoadResourceJS + " = $PLACEHOLDER_VALUE;"; + static final String variableForOnLoadResourceJS = "_flutter_inappwebview_useOnLoadResource"; + static final String enableVariableForOnLoadResourceJS = "window." + variableForOnLoadResourceJS + " = $PLACEHOLDER_VALUE;"; static final String resourceObserverJS = "(function() {" + " var observer = new PerformanceObserver(function(list) {" + @@ -1169,7 +1180,9 @@ final public class InAppWebView extends InputAwareWebView { String placeholderValue = newOptions.useShouldInterceptAjaxRequest ? "true" : "false"; String sourceJs = InAppWebView.enableVariableForShouldInterceptAjaxRequestJS.replace("$PLACEHOLDER_VALUE", placeholderValue); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - evaluateJavascript(sourceJs, (ValueCallback) null); + for (String contentWorldName : userScriptsContentWorlds) { + evaluateJavascript(sourceJs, contentWorldName, null); + } } else { loadUrl("javascript:" + sourceJs); } @@ -1179,7 +1192,9 @@ final public class InAppWebView extends InputAwareWebView { String placeholderValue = newOptions.useShouldInterceptFetchRequest ? "true" : "false"; String sourceJs = InAppWebView.enableVariableForShouldInterceptFetchRequestsJS.replace("$PLACEHOLDER_VALUE", placeholderValue); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - evaluateJavascript(sourceJs, (ValueCallback) null); + for (String contentWorldName : userScriptsContentWorlds) { + evaluateJavascript(sourceJs, contentWorldName, null); + } } else { loadUrl("javascript:" + sourceJs); } @@ -1457,7 +1472,7 @@ final public class InAppWebView extends InputAwareWebView { return (options != null) ? options.getRealOptions(this) : null; } - public void injectDeferredObject(String source, String jsWrapper, final MethodChannel.Result result) { + public void injectDeferredObject(String source, @Nullable final String contentWorldName, String jsWrapper, final MethodChannel.Result result) { String scriptToInject = source; if (jsWrapper != null) { org.json.JSONArray jsonEsc = new org.json.JSONArray(); @@ -1472,39 +1487,58 @@ final public class InAppWebView extends InputAwareWebView { public void run() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { // This action will have the side-effect of blurring the currently focused element - loadUrl("javascript:" + finalScriptToInject); + loadUrl("javascript:" + finalScriptToInject.replaceAll("[\r\n]+", "")); result.success(""); } else { - evaluateJavascript(finalScriptToInject, new ValueCallback() { - @Override - public void onReceiveValue(String s) { - if (result == null) - return; - result.success(s); + if (contentWorldName != null && !contentWorldName.equals("page")) { + String sourceToInject = finalScriptToInject; + if (!userScriptsContentWorlds.contains(contentWorldName)) { + userScriptsContentWorlds.add(contentWorldName); + // Add only the first time all the plugin scripts needed. + String jsPluginScripts = prepareAndWrapPluginUserScripts(); + sourceToInject = jsPluginScripts + "\n" + sourceToInject; } - }); + sourceToInject = wrapSourceCodeInContentWorld(contentWorldName, sourceToInject); + evaluateJavascript(sourceToInject, new ValueCallback() { + @Override + public void onReceiveValue(String s) { + if (result == null) + return; + result.success(s); + } + }); + } else { + evaluateJavascript(finalScriptToInject, new ValueCallback() { + @Override + public void onReceiveValue(String s) { + if (result == null) + return; + result.success(s); + } + }); + } } } }); } - public void evaluateJavascript(String source, MethodChannel.Result result) { - injectDeferredObject(source, null, result); + public void evaluateJavascript(String source, String contentWorldName, MethodChannel.Result result) { + injectDeferredObject(source, contentWorldName, null, result); } public void injectJavascriptFileFromUrl(String urlFile) { String jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document);"; - injectDeferredObject(urlFile, jsWrapper, null); + injectDeferredObject(urlFile, null, jsWrapper, null); } public void injectCSSCode(String source) { String jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document);"; - injectDeferredObject(source, jsWrapper, null); + injectDeferredObject(source, null, jsWrapper, null); } public void injectCSSFileFromUrl(String urlFile) { String jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document);"; - injectDeferredObject(urlFile, jsWrapper, null); + injectDeferredObject(urlFile, null, jsWrapper, null); } public HashMap getCopyBackForwardList() { @@ -1999,6 +2033,10 @@ final public class InAppWebView extends InputAwareWebView { } public boolean addUserScript(Map userScript) { + String contentWorldName = (String) userScript.get("contentWorld"); + if (contentWorldName != null && !userScriptsContentWorlds.contains(contentWorldName)) { + userScriptsContentWorlds.add(contentWorldName); + } return userScripts.add(userScript); } @@ -2009,6 +2047,52 @@ final public class InAppWebView extends InputAwareWebView { public void removeAllUserScripts() { userScripts.clear(); } + + public void resetUserScriptsContentWorlds() { + userScriptsContentWorlds.clear(); + userScriptsContentWorlds.add("page"); + } + + public String prepareAndWrapPluginUserScripts() { + String js = JavaScriptBridgeInterface.callHandlerScriptJS; + js += InAppWebView.consoleLogJS; + if (options.useShouldInterceptAjaxRequest) { + js += InAppWebView.interceptAjaxRequestsJS; + } + if (options.useShouldInterceptFetchRequest) { + js += InAppWebView.interceptFetchRequestsJS; + } + if (options.useOnLoadResource) { + js += InAppWebView.resourceObserverJS; + } + if (!options.useHybridComposition) { + js += InAppWebView.checkGlobalKeyDownEventToHideContextMenuJS; + } + js += InAppWebView.onWindowFocusEventJS; + js += InAppWebView.onWindowBlurEventJS; + js += InAppWebView.printJS; + + String jsWrapped = InAppWebView.pluginScriptsWrapperJS + .replace("$PLACEHOLDER_VALUE", js); + + return jsWrapped; + } + + public String wrapSourceCodeInContentWorld(@Nullable String contentWorldName, String 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("$JSON_SOURCE_ENCODED", sourceEncoded.toString()); + + return sourceWrapped; + } @Override public void dispose() { 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 3859a175..54b9340f 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 @@ -26,12 +26,8 @@ import androidx.annotation.RequiresApi; import com.pichillilorenzo.flutter_inappwebview.CredentialDatabase.Credential; import com.pichillilorenzo.flutter_inappwebview.CredentialDatabase.CredentialDatabase; import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity; -import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface; import com.pichillilorenzo.flutter_inappwebview.Util; -import org.json.JSONException; -import org.json.JSONObject; - import java.io.ByteArrayInputStream; import java.net.MalformedURLException; import java.net.URI; @@ -166,10 +162,10 @@ public class InAppWebViewClient extends WebViewClient { private void loadCustomJavaScriptOnPageStarted(WebView view) { InAppWebView webView = (InAppWebView) view; - String jsPluginScripts = preparePluginUserScripts(webView); + String jsPluginScriptsWrapped = webView.prepareAndWrapPluginUserScripts(); String jsUserScriptsAtDocumentStart = prepareUserScriptsAtDocumentStart(webView); - String js = wrapPluginAndUserScripts(jsPluginScripts, jsUserScriptsAtDocumentStart, null); + String js = wrapPluginAndUserScripts(jsPluginScriptsWrapped, jsUserScriptsAtDocumentStart, null); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { webView.evaluateJavascript(js, (ValueCallback) null); @@ -182,11 +178,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 jsPluginScripts = preparePluginUserScripts(webView); + String jsPluginScriptsWrapped = webView.prepareAndWrapPluginUserScripts(); String jsUserScriptsAtDocumentStart = prepareUserScriptsAtDocumentStart(webView); String jsUserScriptsAtDocumentEnd = prepareUserScriptsAtDocumentEnd(webView); - String js = wrapPluginAndUserScripts(jsPluginScripts, jsUserScriptsAtDocumentStart, jsUserScriptsAtDocumentEnd); + String js = wrapPluginAndUserScripts(jsPluginScriptsWrapped, jsUserScriptsAtDocumentStart, jsUserScriptsAtDocumentEnd); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { webView.evaluateJavascript(js, (ValueCallback) null); @@ -194,57 +190,25 @@ public class InAppWebViewClient extends WebViewClient { webView.loadUrl("javascript:" + js.replaceAll("[\r\n]+", "")); } } - - private String preparePluginUserScripts(InAppWebView webView) { - String js = InAppWebView.consoleLogJS; - js += JavaScriptBridgeInterface.callHandlerScriptJS; - if (webView.options.useShouldInterceptAjaxRequest) { - js += InAppWebView.interceptAjaxRequestsJS; - } - if (webView.options.useShouldInterceptFetchRequest) { - js += InAppWebView.interceptFetchRequestsJS; - } - if (webView.options.useOnLoadResource) { - js += InAppWebView.resourceObserverJS; - } - if (!webView.options.useHybridComposition) { - js += InAppWebView.checkGlobalKeyDownEventToHideContextMenuJS; - } - js += InAppWebView.onWindowFocusEventJS; - js += InAppWebView.onWindowBlurEventJS; - js += InAppWebView.printJS; - - return js; - } - - private String prepareUserScriptsAtDocumentStart(InAppWebView webView) { + + private String prepareUserScripts(InAppWebView webView, int atDocumentInjectionTime) { StringBuilder js = new StringBuilder(); for (Map userScript : webView.userScripts) { Integer injectionTime = (Integer) userScript.get("injectionTime"); - if (injectionTime == null || injectionTime == 0) { + if ((injectionTime == null && atDocumentInjectionTime == 0) || (injectionTime != null && injectionTime == atDocumentInjectionTime)) { String source = (String) userScript.get("source"); String contentWorldName = (String) userScript.get("contentWorld"); if (source != null) { if (contentWorldName != null && !contentWorldName.equals("page")) { - String jsPluginScripts = preparePluginUserScripts(webView); + String jsPluginScripts = webView.prepareAndWrapPluginUserScripts(); 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(); + if (contentWorldName != null && !webView.userScriptsContentWorlds.contains(contentWorldName)) { + webView.userScriptsContentWorlds.add(contentWorldName); } - - 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")) { + String sourceWrapped = webView.wrapSourceCodeInContentWorld(contentWorldName, source); + if (atDocumentInjectionTime == 0 && 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); @@ -258,43 +222,15 @@ public class InAppWebViewClient extends WebViewClient { return js.toString(); } + private String prepareUserScriptsAtDocumentStart(InAppWebView webView) { + return prepareUserScripts(webView, 0); + } + private String prepareUserScriptsAtDocumentEnd(InAppWebView webView) { - StringBuilder js = new StringBuilder(); - - for (Map userScript : webView.userScripts) { - Integer injectionTime = (Integer) userScript.get("injectionTime"); - if (injectionTime != null && injectionTime == 1) { - String source = (String) userScript.get("source"); - String contentWorldName = (String) userScript.get("contentWorld"); - if (source != null) { - 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(); + return prepareUserScripts(webView, 1); } - private String wrapPluginAndUserScripts(String jsPluginScripts, @Nullable String jsUserScriptsAtDocumentStart, @Nullable String jsUserScriptsAtDocumentEnd) { - String jsPluginScriptsWrapped = InAppWebView.pluginScriptsWrapperJS - .replace("$PLACEHOLDER_VALUE", jsPluginScripts); + private String wrapPluginAndUserScripts(String jsPluginScriptsWrapped, @Nullable String jsUserScriptsAtDocumentStart, @Nullable String jsUserScriptsAtDocumentEnd) { String jsUserScriptsAtDocumentStartWrapped = jsUserScriptsAtDocumentStart == null || jsUserScriptsAtDocumentStart.isEmpty() ? "" : InAppWebView.userScriptsAtDocumentStartWrapperJS.replace("$PLACEHOLDER_VALUE", jsUserScriptsAtDocumentStart); String jsUserScriptsAtDocumentEndWrapped = jsUserScriptsAtDocumentEnd == null || jsUserScriptsAtDocumentEnd.isEmpty() ? "" : @@ -305,6 +241,7 @@ public class InAppWebViewClient extends WebViewClient { @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { final InAppWebView webView = (InAppWebView) view; + webView.resetUserScriptsContentWorlds(); loadCustomJavaScriptOnPageStarted(webView); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java index 28758f7f..ae35ff70 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java @@ -74,7 +74,8 @@ public class InAppWebViewMethodHandler implements MethodChannel.MethodCallHandle case "evaluateJavascript": if (webView != null) { String source = (String) call.argument("source"); - webView.evaluateJavascript(source, result); + String contentWorldName = (String) call.argument("contentWorld"); + webView.evaluateJavascript(source, contentWorldName, result); } else { result.success(null); diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies index ff8178ef..fd3d72d9 100644 --- a/example/.flutter-plugins-dependencies +++ b/example/.flutter-plugins-dependencies @@ -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-04 22:05:52.361400","version":"1.26.0-18.0.pre.90"} \ No newline at end of file +{"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-06 02:03:14.260971","version":"1.26.0-18.0.pre.90"} \ No newline at end of file diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift index a96abc3f..c69126cc 100755 --- a/ios/Classes/InAppWebView.swift +++ b/ios/Classes/InAppWebView.swift @@ -58,7 +58,7 @@ window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() { return new Promise(function(resolve, reject) { window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = resolve; }); -} +}; """ // the message needs to be concatenated with '' in order to have the same behavior like on Android @@ -269,8 +269,8 @@ function wkwebview_FindNext(forward) { } """ -let variableForOnLoadResourceJS = "window._flutter_inappwebview_useOnLoadResource" -let enableVariableForOnLoadResourceJS = "\(variableForOnLoadResourceJS) = $PLACEHOLDER_VALUE;" +let variableForOnLoadResourceJS = "_flutter_inappwebview_useOnLoadResource" +let enableVariableForOnLoadResourceJS = "window.\(variableForOnLoadResourceJS) = $PLACEHOLDER_VALUE;" let resourceObserverJS = """ (function() { @@ -877,7 +877,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi static var windowWebViews: [Int64:WebViewTransport] = [:] static var windowAutoincrementId: Int64 = 0; - var userScriptsContentWorlds: [String] = ["defaultClient", "page"] + var userScriptsContentWorlds: [String] = ["page"] init(frame: CGRect, configuration: WKWebViewConfiguration, IABController: InAppBrowserWebViewController?, contextMenu: [String: Any]?, channel: FlutterMethodChannel?) { super.init(frame: frame, configuration: configuration) @@ -1219,60 +1219,88 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi configuration.userContentController.addUserScript(userScriptWindowId) } - @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) + func getAllPluginUserScriptMergedJS() -> String { + var allPluginUserScriptMergedJS = promisePolyfillJS + "\n" + + javaScriptBridgeJS + "\n" + + consoleLogJS + "\n" + + printJS + "\n" + if let options = options { + if options.useShouldInterceptAjaxRequest { + allPluginUserScriptMergedJS += interceptAjaxRequestsJS + "\n" + } - 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) - } + if options.useShouldInterceptFetchRequest { + allPluginUserScriptMergedJS += interceptFetchRequestsJS + "\n" } } + return allPluginUserScriptMergedJS } - func addPluginUserScripts() -> Void { - if #available(iOS 14.0, *) { - let contentWorlds = userScriptsContentWorlds.map { (contentWorldName) -> WKContentWorld in - return getContentWorld(name: contentWorldName) + @available(iOS 14.0, *) + func addSharedPluginUserScriptsInContentWorld(contentWorldName: String) -> Void { + let contentWorld = getContentWorld(name: contentWorldName) + + let promisePolyfillJSScript = WKUserScript(source: promisePolyfillJS, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: contentWorld) + configuration.userContentController.addUserScript(promisePolyfillJSScript) + + 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) + } + } + + let printJSScript = WKUserScript(source: printJS, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: contentWorld) + configuration.userContentController.addUserScript(printJSScript) + } + + func addSharedPluginUserScriptsInContentWorlds() -> Void { + if #available(iOS 14.0, *) { + for contentWorldName in userScriptsContentWorlds { + addSharedPluginUserScriptsInContentWorld(contentWorldName: contentWorldName) } - addSharedPluginUserScriptsBetweenContentWorlds(contentWorlds: contentWorlds) } else { - let promisePolyfillJSScript = WKUserScript(source: promisePolyfillJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) + let promisePolyfillJSScript = WKUserScript( + source: promisePolyfillJS, + injectionTime: .atDocumentStart, + forMainFrameOnly: false) configuration.userContentController.addUserScript(promisePolyfillJSScript) - let javaScriptBridgeJSScript = WKUserScript(source: javaScriptBridgeJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) + let javaScriptBridgeJSScript = WKUserScript( + source: javaScriptBridgeJS, + injectionTime: .atDocumentStart, + forMainFrameOnly: false) configuration.userContentController.addUserScript(javaScriptBridgeJSScript) configuration.userContentController.removeScriptMessageHandler(forName: "callHandler") configuration.userContentController.add(self, name: "callHandler") - let consoleLogJSScript = WKUserScript(source: consoleLogJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) + let consoleLogJSScript = WKUserScript( + source: consoleLogJS, + injectionTime: .atDocumentStart, + forMainFrameOnly: false) configuration.userContentController.addUserScript(consoleLogJSScript) configuration.userContentController.removeScriptMessageHandler(forName: "consoleLog") configuration.userContentController.add(self, name: "consoleLog") @@ -1287,23 +1315,33 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi if let options = options { if options.useShouldInterceptAjaxRequest { - let interceptAjaxRequestsJSScript = WKUserScript(source: interceptAjaxRequestsJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) + 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) + let interceptFetchRequestsJSScript = WKUserScript( + source: interceptFetchRequestsJS, + injectionTime: .atDocumentStart, + forMainFrameOnly: false) configuration.userContentController.addUserScript(interceptFetchRequestsJSScript) } } + + let printJSScript = WKUserScript(source: printJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) + configuration.userContentController.addUserScript(printJSScript) } + } + + func addPluginUserScripts() -> Void { + addSharedPluginUserScriptsInContentWorlds() 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) @@ -1352,18 +1390,19 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi public func appendUserScript(userScript: [String: Any]) -> Void { var wkUserScript: WKUserScript? - if #available(iOS 14.0, *), let contentWorldName = userScript["contentWorld"] as? String { - if !userScriptsContentWorlds.contains(contentWorldName) { - userScriptsContentWorlds.append(contentWorldName) - } + let contentWorldName = userScript["contentWorld"] as? String + if contentWorldName != nil, !userScriptsContentWorlds.contains(contentWorldName!) { + userScriptsContentWorlds.append(contentWorldName!) + } + if #available(iOS 14.0, *), let contentWorldName = 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) + injectionTime: WKUserScriptInjectionTime.init(rawValue: userScript["injectionTime"] as! Int) ?? .atDocumentStart, + forMainFrameOnly: userScript["iosForMainFrameOnly"] as! Bool) } userScripts.append(wkUserScript!) } @@ -1784,17 +1823,35 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi if newOptionsMap["useOnLoadResource"] != nil && options?.useOnLoadResource != newOptions.useOnLoadResource && newOptions.useOnLoadResource { let placeholderValue = newOptions.useOnLoadResource ? "true" : "false" - evaluateJavaScript(enableVariableForOnLoadResourceJS.replacingOccurrences(of: "$PLACEHOLDER_VALUE", with: placeholderValue), completionHandler: nil) + let source = enableVariableForOnLoadResourceJS.replacingOccurrences(of: "$PLACEHOLDER_VALUE", with: placeholderValue) + evaluateJavaScript(source, completionHandler: nil) } if newOptionsMap["useShouldInterceptAjaxRequest"] != nil && options?.useShouldInterceptAjaxRequest != newOptions.useShouldInterceptAjaxRequest && newOptions.useShouldInterceptAjaxRequest { let placeholderValue = newOptions.useShouldInterceptAjaxRequest ? "true" : "false" - evaluateJavaScript(enableVariableForShouldInterceptAjaxRequestJS.replacingOccurrences(of: "$PLACEHOLDER_VALUE", with: placeholderValue), completionHandler: nil) + let source = enableVariableForShouldInterceptAjaxRequestJS.replacingOccurrences(of: "$PLACEHOLDER_VALUE", with: placeholderValue) + if #available(iOS 14.0, *) { + for contentWorldName in userScriptsContentWorlds { + let contentWorld = getContentWorld(name: contentWorldName) + evaluateJavaScript(source, in: nil, in: contentWorld, completionHandler: nil) + } + } else { + evaluateJavaScript(source, completionHandler: nil) + } } if newOptionsMap["useShouldInterceptFetchRequest"] != nil && options?.useShouldInterceptFetchRequest != newOptions.useShouldInterceptFetchRequest && newOptions.useShouldInterceptFetchRequest { let placeholderValue = newOptions.useShouldInterceptFetchRequest ? "true" : "false" - evaluateJavaScript(enableVariableForShouldInterceptFetchRequestsJS.replacingOccurrences(of: "$PLACEHOLDER_VALUE", with: placeholderValue), completionHandler: nil) + + let source = enableVariableForShouldInterceptAjaxRequestJS.replacingOccurrences(of: "$PLACEHOLDER_VALUE", with: placeholderValue) + if #available(iOS 14.0, *) { + for contentWorldName in userScriptsContentWorlds { + let contentWorld = getContentWorld(name: contentWorldName) + evaluateJavaScript(source, in: nil, in: contentWorld, completionHandler: nil) + } + } else { + evaluateJavaScript(source, completionHandler: nil) + } } if newOptionsMap["mediaPlaybackRequiresUserGesture"] != nil && options?.mediaPlaybackRequiresUserGesture != newOptions.mediaPlaybackRequiresUserGesture { @@ -2000,7 +2057,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi } } - public func injectDeferredObject(source: String, withWrapper jsWrapper: String?, result: FlutterResult?) { + public func injectDeferredObject(source: String, contentWorldName: String?, withWrapper jsWrapper: String?, result: FlutterResult?) { var jsToInject = source if let wrapper = jsWrapper { let jsonData: Data? = try? JSONSerialization.data(withJSONObject: [source], options: []) @@ -2008,42 +2065,71 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi let sourceString: String? = (sourceArrayString! as NSString).substring(with: NSRange(location: 1, length: (sourceArrayString?.count ?? 0) - 2)) jsToInject = String(format: wrapper, sourceString!) } - evaluateJavaScript(jsToInject, completionHandler: {(value, error) in - if result == nil { - return + + if #available(iOS 14.0, *), let contentWorldName = contentWorldName { + if !userScriptsContentWorlds.contains(contentWorldName) { + userScriptsContentWorlds.append(contentWorldName) + addSharedPluginUserScriptsInContentWorld(contentWorldName: contentWorldName) + // Add only the first time all the plugin user scripts needed. + // In the next page load, it will use the WKUserScripts loaded + jsToInject = getAllPluginUserScriptMergedJS() + "\n" + jsToInject } - - if error != nil { - let userInfo = (error! as NSError).userInfo - self.onConsoleMessage(message: userInfo["WKJavaScriptExceptionMessage"] as? String ?? "", messageLevel: 3) + let contentWorld = getContentWorld(name: contentWorldName) + evaluateJavaScript(jsToInject, in: nil, in: contentWorld) { (evalResult) in + guard let result = result else { + return + } + + switch (evalResult) { + case .success(let value): + result(value) + return + case .failure(let error): + let userInfo = (error as NSError).userInfo + self.onConsoleMessage(message: userInfo["WKJavaScriptExceptionMessage"] as? String ?? "", messageLevel: 3) + break + } + + result(nil) } - - if value == nil { - result!(nil) - return + } else { + evaluateJavaScript(jsToInject) { (value, error) in + guard let result = result else { + return + } + + if error != nil { + let userInfo = (error! as NSError).userInfo + self.onConsoleMessage(message: userInfo["WKJavaScriptExceptionMessage"] as? String ?? "", messageLevel: 3) + } + + if value == nil { + result(nil) + return + } + + result(value) } - - result!(value) - }) + } } - public func evaluateJavascript(source: String, result: FlutterResult?) { - injectDeferredObject(source: source, withWrapper: nil, result: result) + public func evaluateJavascript(source: String, contentWorldName: String?, result: FlutterResult?) { + injectDeferredObject(source: source, contentWorldName: contentWorldName, withWrapper: nil, result: result) } public func injectJavascriptFileFromUrl(urlFile: String) { let jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document);" - injectDeferredObject(source: urlFile, withWrapper: jsWrapper, result: nil) + injectDeferredObject(source: urlFile, contentWorldName: nil, withWrapper: jsWrapper, result: nil) } public func injectCSSCode(source: String) { let jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document);" - injectDeferredObject(source: source, withWrapper: jsWrapper, result: nil) + injectDeferredObject(source: source, contentWorldName: nil, withWrapper: jsWrapper, result: nil) } public func injectCSSFileFromUrl(urlFile: String) { let jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document);" - injectDeferredObject(source: urlFile, withWrapper: jsWrapper, result: nil) + injectDeferredObject(source: urlFile, contentWorldName: nil, withWrapper: jsWrapper, result: nil) } public func getCopyBackForwardList() -> [String: Any] { @@ -2181,6 +2267,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi currentURL = url InAppWebView.credentialsProposed = [] evaluateJavaScript(platformReadyJS, completionHandler: nil) + onLoadStop(url: url?.absoluteString) if IABController != nil { diff --git a/ios/Classes/InAppWebViewMethodHandler.swift b/ios/Classes/InAppWebViewMethodHandler.swift index cf0b848f..9c36f406 100644 --- a/ios/Classes/InAppWebViewMethodHandler.swift +++ b/ios/Classes/InAppWebViewMethodHandler.swift @@ -30,15 +30,15 @@ class InAppWebViewMethodHandler: FlutterMethodCallDelegate { result( (webView != nil) ? Int(webView!.estimatedProgress * 100) : nil ) break case "loadUrl": - let url = (arguments!["url"] as? String)! - let headers = (arguments!["headers"] as? [String: String])! + let url = arguments!["url"] as! String + let headers = arguments!["headers"] as! [String: String] webView?.loadUrl(url: URL(string: url)!, headers: headers) result(true) break case "postUrl": if webView != nil { - let url = (arguments!["url"] as? String)! - let postData = (arguments!["postData"] as? FlutterStandardTypedData)! + let url = arguments!["url"] as! String + let postData = arguments!["postData"] as! FlutterStandardTypedData webView!.postUrl(url: URL(string: url)!, postData: postData.data, completionHandler: { () -> Void in result(true) }) @@ -48,16 +48,16 @@ class InAppWebViewMethodHandler: FlutterMethodCallDelegate { } break case "loadData": - let data = (arguments!["data"] as? String)! - let mimeType = (arguments!["mimeType"] as? String)! - let encoding = (arguments!["encoding"] as? String)! - let baseUrl = (arguments!["baseUrl"] as? String)! + let data = arguments!["data"] as! String + let mimeType = arguments!["mimeType"] as! String + let encoding = arguments!["encoding"] as! String + let baseUrl = arguments!["baseUrl"] as! String webView?.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl) result(true) break case "loadFile": - let url = (arguments!["url"] as? String)! - let headers = (arguments!["headers"] as? [String: String])! + let url = arguments!["url"] as! String + let headers = arguments!["headers"] as! [String: String] do { try webView?.loadFile(url: url, headers: headers) @@ -70,25 +70,26 @@ class InAppWebViewMethodHandler: FlutterMethodCallDelegate { break case "evaluateJavascript": if webView != nil { - let source = (arguments!["source"] as? String)! - webView!.evaluateJavascript(source: source, result: result) + let source = arguments!["source"] as! String + let contentWorldName = arguments!["contentWorld"] as? String + webView!.evaluateJavascript(source: source, contentWorldName: contentWorldName, result: result) } else { result(nil) } break case "injectJavascriptFileFromUrl": - let urlFile = (arguments!["urlFile"] as? String)! + let urlFile = arguments!["urlFile"] as! String webView?.injectJavascriptFileFromUrl(urlFile: urlFile) result(true) break case "injectCSSCode": - let source = (arguments!["source"] as? String)! + let source = arguments!["source"] as! String webView?.injectCSSCode(source: source) result(true) break case "injectCSSFileFromUrl": - let urlFile = (arguments!["urlFile"] as? String)! + let urlFile = arguments!["urlFile"] as! String webView?.injectCSSFileFromUrl(urlFile: urlFile) result(true) break @@ -111,12 +112,12 @@ class InAppWebViewMethodHandler: FlutterMethodCallDelegate { result(webView?.canGoForward ?? false) break case "goBackOrForward": - let steps = (arguments!["steps"] as? Int)! + let steps = arguments!["steps"] as! Int webView?.goBackOrForward(steps: steps) result(true) break case "canGoBackOrForward": - let steps = (arguments!["steps"] as? Int)! + let steps = arguments!["steps"] as! Int result(webView?.canGoBackOrForward(steps: steps) ?? false) break case "stopLoading": diff --git a/lib/src/in_app_webview_controller.dart b/lib/src/in_app_webview_controller.dart index 9aefbe24..9c406530 100644 --- a/lib/src/in_app_webview_controller.dart +++ b/lib/src/in_app_webview_controller.dart @@ -1351,7 +1351,14 @@ class InAppWebViewController { await _channel.invokeMethod('stopLoading', args); } - ///Evaluates JavaScript code into the WebView and returns the result of the evaluation. + ///Evaluates JavaScript [source] code into the WebView and returns the result of the evaluation. + /// + ///[contentWorld], on iOS, it represents the namespace in which to evaluate the JavaScript [source] code. + ///Instead, on Android, it will run the [source] code into an iframe. + ///This parameter doesn’t apply to changes you make 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 [ContentWorld]. + ///Available on iOS 14.0+. /// ///**NOTE**: This method shouldn't be called in the [WebView.onWebViewCreated] or [WebView.onLoadStart] events, ///because, in these events, the [WebView] is not ready to handle it yet. @@ -1360,10 +1367,13 @@ class InAppWebViewController { /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#evaluateJavascript(java.lang.String,%20android.webkit.ValueCallback%3Cjava.lang.String%3E) /// - ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1415017-evaluatejavascript - Future evaluateJavascript({required String source}) async { + ///**Official iOS API**: + ///- https://developer.apple.com/documentation/webkit/wkwebview/1415017-evaluatejavascript + ///- https://developer.apple.com/documentation/webkit/wkwebview/3656442-evaluatejavascript + Future evaluateJavascript({required String source, ContentWorld? contentWorld}) async { Map args = {}; args.putIfAbsent('source', () => source); + args.putIfAbsent('contentWorld', () => contentWorld?.name); var data = await _channel.invokeMethod('evaluateJavascript', args); if (data != null && defaultTargetPlatform == TargetPlatform.android) data = json.decode(data); return data; diff --git a/lib/src/types.dart b/lib/src/types.dart index b966c252..1c368c1f 100755 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -4599,12 +4599,15 @@ class UserScript { ///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 iOS**: available on 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 `