From 80b8bde10a8e1d72f20ae1fde98dacfae387c195 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Thu, 6 Oct 2022 19:17:11 +0200 Subject: [PATCH] Added WebViewFeature.DOCUMENT_START_SCRIPT Android feature support --- CHANGELOG.md | 1 + .../plugin_scripts_js/ConsoleLogJS.java | 3 +- .../InterceptAjaxRequestJS.java | 3 +- .../InterceptFetchRequestJS.java | 3 +- .../plugin_scripts_js/JavaScriptBridgeJS.java | 3 +- .../plugin_scripts_js/OnLoadResourceJS.java | 3 +- .../OnWindowBlurEventJS.java | 3 +- .../OnWindowFocusEventJS.java | 3 +- .../plugin_scripts_js/PluginScriptsUtil.java | 3 +- .../plugin_scripts_js/PrintJS.java | 3 +- .../plugin_scripts_js/PromisePolyfillJS.java | 3 +- .../types/PluginScript.java | 6 +- .../types/UserContentController.java | 76 ++++++++++++++++++- .../types/UserScript.java | 32 +++++++- .../webview/in_app_webview/InAppWebView.java | 51 ++++++++----- .../in_app_webview/InAppWebViewClient.java | 15 ++-- .../web_message/WebMessageListener.java | 3 +- .../src/exchangeable_object_generator.dart | 2 +- lib/src/android/webview_feature.dart | 9 +++ lib/src/android/webview_feature.g.dart | 10 +++ lib/src/types/user_script.dart | 9 +++ lib/src/types/user_script.g.dart | 12 ++- 22 files changed, 207 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97602b91..5e1aff56 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Added `onCameraCaptureStateChanged`, `onMicrophoneCaptureStateChanged` WebView events - Added support for `onPermissionRequest` event on iOS 15.0+ - Added `debugLoggingSettings` static property for WebView and ChromeSafariBrowser +- Added `WebViewFeature.DOCUMENT_START_SCRIPT` Android feature support - Updated `getMetaThemeColor` on iOS 15.0+ - Deprecated `onLoadError` for `onReceivedError`. `onReceivedError` will be called also for subframes - Deprecated `onLoadHttpError` for `onReceivedError`. `onReceivedHttpError` will be called also for subframes diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/ConsoleLogJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/ConsoleLogJS.java index 1f00c825..69323cb9 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/ConsoleLogJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/ConsoleLogJS.java @@ -10,7 +10,8 @@ public class ConsoleLogJS { ConsoleLogJS.CONSOLE_LOG_JS_SOURCE, UserScriptInjectionTime.AT_DOCUMENT_START, null, - true + true, + null ); public static final String CONSOLE_LOG_JS_SOURCE = "(function(console) {" + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptAjaxRequestJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptAjaxRequestJS.java index 3a750c78..f4a0dfa2 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptAjaxRequestJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptAjaxRequestJS.java @@ -12,7 +12,8 @@ public class InterceptAjaxRequestJS { InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_SOURCE, UserScriptInjectionTime.AT_DOCUMENT_START, null, - true + true, + null ); public static final String INTERCEPT_AJAX_REQUEST_JS_SOURCE = "(function(ajax) {" + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptFetchRequestJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptFetchRequestJS.java index f5d833eb..3555228c 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptFetchRequestJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptFetchRequestJS.java @@ -12,7 +12,8 @@ public class InterceptFetchRequestJS { InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_SOURCE, UserScriptInjectionTime.AT_DOCUMENT_START, null, - true + true, + null ); public static final String INTERCEPT_FETCH_REQUEST_JS_SOURCE = "(function(fetch) {" + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/JavaScriptBridgeJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/JavaScriptBridgeJS.java index 565a6120..31fc58b5 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/JavaScriptBridgeJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/JavaScriptBridgeJS.java @@ -11,7 +11,8 @@ public class JavaScriptBridgeJS { JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_SOURCE, UserScriptInjectionTime.AT_DOCUMENT_START, null, - true + true, + null ); public static final String JAVASCRIPT_UTIL_VAR_NAME = "window." + JAVASCRIPT_BRIDGE_NAME + "._Util"; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/OnLoadResourceJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/OnLoadResourceJS.java index e7b3d0bc..1b93e0cd 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/OnLoadResourceJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/OnLoadResourceJS.java @@ -11,7 +11,8 @@ public class OnLoadResourceJS { OnLoadResourceJS.ON_LOAD_RESOURCE_JS_SOURCE, UserScriptInjectionTime.AT_DOCUMENT_START, null, - false + false, + null ); public static final String ON_LOAD_RESOURCE_JS_SOURCE = "window." + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE + " = true;" + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/OnWindowBlurEventJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/OnWindowBlurEventJS.java index 4854a26d..f5e095b4 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/OnWindowBlurEventJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/OnWindowBlurEventJS.java @@ -10,7 +10,8 @@ public class OnWindowBlurEventJS { OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_SOURCE, UserScriptInjectionTime.AT_DOCUMENT_START, null, - false + false, + null ); public static final String ON_WINDOW_BLUR_EVENT_JS_SOURCE = "(function(){" + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/OnWindowFocusEventJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/OnWindowFocusEventJS.java index 04e9bc09..f1aa8feb 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/OnWindowFocusEventJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/OnWindowFocusEventJS.java @@ -10,7 +10,8 @@ public class OnWindowFocusEventJS { OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_SOURCE, UserScriptInjectionTime.AT_DOCUMENT_START, null, - false + false, + null ); public static final String ON_WINDOW_FOCUS_EVENT_JS_SOURCE = "(function(){" + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PluginScriptsUtil.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PluginScriptsUtil.java index c3d05233..c3358ea5 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PluginScriptsUtil.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PluginScriptsUtil.java @@ -77,7 +77,8 @@ public class PluginScriptsUtil { PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE, UserScriptInjectionTime.AT_DOCUMENT_START, null, - false + false, + null ); // android Workaround to hide context menu when user emit a keydown event diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PrintJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PrintJS.java index 0af901d4..c35a258e 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PrintJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PrintJS.java @@ -10,7 +10,8 @@ public class PrintJS { PrintJS.PRINT_JS_SOURCE, UserScriptInjectionTime.AT_DOCUMENT_START, null, - false + false, + null ); public static final String PRINT_JS_SOURCE = "window.print = function() {" + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PromisePolyfillJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PromisePolyfillJS.java index 7303d032..bc3618f7 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PromisePolyfillJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/PromisePolyfillJS.java @@ -10,7 +10,8 @@ public class PromisePolyfillJS { PromisePolyfillJS.PROMISE_POLYFILL_JS_SOURCE, UserScriptInjectionTime.AT_DOCUMENT_START, null, - true + true, + null ); // https://github.com/tildeio/rsvp.js diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/PluginScript.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/PluginScript.java index e7fe563c..b0ee99ff 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/PluginScript.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/PluginScript.java @@ -3,11 +3,13 @@ package com.pichillilorenzo.flutter_inappwebview.types; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.Set; + public class PluginScript extends UserScript { private boolean requiredInAllContentWorlds; - public PluginScript(@Nullable String groupName, @NonNull String source, @NonNull UserScriptInjectionTime injectionTime, @Nullable ContentWorld contentWorld, boolean requiredInAllContentWorlds) { - super(groupName, source, injectionTime, contentWorld); + public PluginScript(@Nullable String groupName, @NonNull String source, @NonNull UserScriptInjectionTime injectionTime, @Nullable ContentWorld contentWorld, boolean requiredInAllContentWorlds, @Nullable Set allowedOriginRules) { + super(groupName, source, injectionTime, contentWorld, allowedOriginRules); this.requiredInAllContentWorlds = requiredInAllContentWorlds; } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/UserContentController.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/UserContentController.java index ee1a3467..21c6847e 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/UserContentController.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/UserContentController.java @@ -1,10 +1,15 @@ package com.pichillilorenzo.flutter_inappwebview.types; +import android.annotation.SuppressLint; import android.text.TextUtils; import android.util.Log; +import android.webkit.WebView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.webkit.ScriptHandler; +import androidx.webkit.WebViewCompat; +import androidx.webkit.WebViewFeature; import com.pichillilorenzo.flutter_inappwebview.Util; import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.JavaScriptBridgeJS; @@ -21,7 +26,8 @@ import java.util.List; import java.util.Map; import java.util.Set; -public class UserContentController { +@SuppressLint("RestrictedApi") +public class UserContentController implements Disposable { protected static final String LOG_TAG = "UserContentController"; @NonNull @@ -29,6 +35,8 @@ public class UserContentController { add(ContentWorld.PAGE); }}; + private final Map scriptHandlerMap = new HashMap<>(); + @NonNull private final Map> userOnlyScripts = new HashMap>() {{ put(UserScriptInjectionTime.AT_DOCUMENT_START, new LinkedHashSet()); @@ -40,7 +48,11 @@ public class UserContentController { put(UserScriptInjectionTime.AT_DOCUMENT_END, new LinkedHashSet()); }}; - public UserContentController() { + @Nullable + public WebView webView; + + public UserContentController(WebView webView) { + this.webView = webView; } public String generateWrappedCodeForDocumentStart() { @@ -52,8 +64,11 @@ public class UserContentController { public String generateWrappedCodeForDocumentEnd() { UserScriptInjectionTime injectionTime = UserScriptInjectionTime.AT_DOCUMENT_END; - // try to reload scripts if they were not loaded during the AT_DOCUMENT_START event - String js = generateCodeForDocumentStart(); + String js = ""; + if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + // try to reload scripts if they were not loaded during the AT_DOCUMENT_START event + js += generateCodeForDocumentStart(); + } js += generatePluginScriptsCodeAt(injectionTime); js += generateUserOnlyScriptsCodeAt(injectionTime); js = USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE.replace(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, js); @@ -161,6 +176,14 @@ public class UserContentController { if (contentWorld != null) { contentWorlds.add(contentWorld); } + if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + ScriptHandler scriptHandler = WebViewCompat.addDocumentStartJavaScript( + webView, + userOnlyScript.getSource(), + userOnlyScript.getAllowedOriginRules() + ); + this.scriptHandlerMap.put(userOnlyScript, scriptHandler); + } return this.userOnlyScripts.get(userOnlyScript.getInjectionTime()).add(userOnlyScript); } @@ -171,6 +194,13 @@ public class UserContentController { } public boolean removeUserOnlyScript(UserScript userOnlyScript) { + if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + ScriptHandler scriptHandler = this.scriptHandlerMap.get(userOnlyScript); + if (scriptHandler != null) { + scriptHandler.remove(); + this.scriptHandlerMap.remove(userOnlyScript); + } + } return this.userOnlyScripts.get(userOnlyScript.getInjectionTime()).remove(userOnlyScript); } @@ -180,6 +210,15 @@ public class UserContentController { } public void removeAllUserOnlyScripts() { + if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + for (UserScript userOnlyScript : this.userOnlyScripts.get(UserScriptInjectionTime.AT_DOCUMENT_START)) { + ScriptHandler scriptHandler = this.scriptHandlerMap.get(userOnlyScript); + if (scriptHandler != null) { + scriptHandler.remove(); + this.scriptHandlerMap.remove(userOnlyScript); + } + } + } this.userOnlyScripts.get(UserScriptInjectionTime.AT_DOCUMENT_START).clear(); this.userOnlyScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END).clear(); } @@ -204,6 +243,14 @@ public class UserContentController { if (contentWorld != null) { contentWorlds.add(contentWorld); } + if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + ScriptHandler scriptHandler = WebViewCompat.addDocumentStartJavaScript( + webView, + pluginScript.getSource(), + pluginScript.getAllowedOriginRules() + ); + this.scriptHandlerMap.put(pluginScript, scriptHandler); + } return this.pluginScripts.get(pluginScript.getInjectionTime()).add(pluginScript); } @@ -214,10 +261,26 @@ public class UserContentController { } public boolean removePluginScript(PluginScript pluginScript) { + if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + ScriptHandler scriptHandler = this.scriptHandlerMap.get(pluginScript); + if (scriptHandler != null) { + scriptHandler.remove(); + this.scriptHandlerMap.remove(pluginScript); + } + } return this.pluginScripts.get(pluginScript.getInjectionTime()).remove(pluginScript); } public void removeAllPluginScripts() { + if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + for (PluginScript pluginScript : this.pluginScripts.get(UserScriptInjectionTime.AT_DOCUMENT_START)) { + ScriptHandler scriptHandler = this.scriptHandlerMap.get(pluginScript); + if (scriptHandler != null) { + scriptHandler.remove(); + this.scriptHandlerMap.remove(pluginScript); + } + } + } this.pluginScripts.get(UserScriptInjectionTime.AT_DOCUMENT_START).clear(); this.pluginScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END).clear(); } @@ -353,4 +416,9 @@ public class UserContentController { private static final String DOCUMENT_READY_WRAPPER_JS_SOURCE = "if (document.readyState === 'interactive' || document.readyState === 'complete') { " + " " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE + "}"; + + @Override + public void dispose() { + webView = null; + } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/UserScript.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/UserScript.java index c4bdc991..2b03effa 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/UserScript.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/UserScript.java @@ -3,7 +3,10 @@ package com.pichillilorenzo.flutter_inappwebview.types; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; public class UserScript { @Nullable @@ -14,12 +17,19 @@ public class UserScript { private UserScriptInjectionTime injectionTime; @NonNull private ContentWorld contentWorld; + @NonNull + private Set allowedOriginRules = new HashSet<>(); - public UserScript(@Nullable String groupName, @NonNull String source, @NonNull UserScriptInjectionTime injectionTime, @Nullable ContentWorld contentWorld) { + public UserScript(@Nullable String groupName, @NonNull String source, + @NonNull UserScriptInjectionTime injectionTime, @Nullable ContentWorld contentWorld, + @Nullable Set allowedOriginRules) { this.groupName = groupName; this.source = source; this.injectionTime = injectionTime; this.contentWorld = contentWorld == null ? ContentWorld.PAGE : contentWorld; + this.allowedOriginRules = allowedOriginRules == null ? new HashSet() {{ + add("*"); + }} : allowedOriginRules; } @Nullable @@ -31,8 +41,9 @@ public class UserScript { String source = (String) map.get("source"); UserScriptInjectionTime injectionTime = UserScriptInjectionTime.fromValue((int) map.get("injectionTime")); ContentWorld contentWorld = ContentWorld.fromMap((Map) map.get("contentWorld")); + Set allowedOriginRules = new HashSet<>((List) map.get("allowedOriginRules")); assert source != null; - return new UserScript(groupName, source, injectionTime, contentWorld); + return new UserScript(groupName, source, injectionTime, contentWorld, allowedOriginRules); } @Nullable @@ -71,6 +82,15 @@ public class UserScript { this.contentWorld = contentWorld == null ? ContentWorld.PAGE : contentWorld; } + @NonNull + public Set getAllowedOriginRules() { + return allowedOriginRules; + } + + public void setAllowedOriginRules(@NonNull Set allowedOriginRules) { + this.allowedOriginRules = allowedOriginRules; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -78,10 +98,12 @@ public class UserScript { UserScript that = (UserScript) o; - if (groupName != null ? !groupName.equals(that.groupName) : that.groupName != null) return false; + if (groupName != null ? !groupName.equals(that.groupName) : that.groupName != null) + return false; if (!source.equals(that.source)) return false; if (injectionTime != that.injectionTime) return false; - return contentWorld.equals(that.contentWorld); + if (!contentWorld.equals(that.contentWorld)) return false; + return allowedOriginRules.equals(that.allowedOriginRules); } @Override @@ -90,6 +112,7 @@ public class UserScript { result = 31 * result + source.hashCode(); result = 31 * result + injectionTime.hashCode(); result = 31 * result + contentWorld.hashCode(); + result = 31 * result + allowedOriginRules.hashCode(); return result; } @@ -100,6 +123,7 @@ public class UserScript { ", source='" + source + '\'' + ", injectionTime=" + injectionTime + ", contentWorld=" + contentWorld + + ", allowedOriginRules=" + allowedOriginRules + '}'; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebView.java index 3848e1ee..33846277 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebView.java @@ -2,6 +2,7 @@ package com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; @@ -96,9 +97,11 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.regex.Pattern; @@ -153,7 +156,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie public Runnable checkContextMenuShouldBeClosedTask; public int newCheckContextMenuShouldBeClosedTaskTask = 100; // ms - public UserContentController userContentController = new UserContentController(); + public UserContentController userContentController = new UserContentController(this); public Map> callAsyncJavaScriptCallbacks = new HashMap<>(); public Map> evaluateJavaScriptContentWorldCallbacks = new HashMap<>(); @@ -161,6 +164,8 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie public Map webMessageChannels = new HashMap<>(); public List webMessageListeners = new ArrayList<>(); + private List initialUserOnlyScript = new ArrayList<>(); + public InAppWebView(Context context) { super(context); } @@ -185,7 +190,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie this.windowId = windowId; this.customSettings = customSettings; this.contextMenu = contextMenu; - this.userContentController.addUserOnlyScripts(userScripts); + this.initialUserOnlyScript = userScripts; if (plugin != null && plugin.activity != null) { plugin.activity.registerForContextMenu(this); } @@ -208,24 +213,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie WebViewCompat.setWebViewRenderProcessClient(this, inAppWebViewRenderProcessClient); } - userContentController.addPluginScript(PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(ConsoleLogJS.CONSOLE_LOG_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(PrintJS.PRINT_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT); - userContentController.addPluginScript(OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT); - if (customSettings.useShouldInterceptAjaxRequest) { - userContentController.addPluginScript(InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT); - } - if (customSettings.useShouldInterceptFetchRequest) { - userContentController.addPluginScript(InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT); - } - if (customSettings.useOnLoadResource) { - userContentController.addPluginScript(OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT); - } - if (!customSettings.useHybridComposition) { - userContentController.addPluginScript(PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT); - } + prepareAndAddUserScripts(); if (customSettings.useOnDownloadStart) setDownloadListener(new DownloadStartListener()); @@ -504,6 +492,28 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie }); } + private void prepareAndAddUserScripts() { + userContentController.addPluginScript(PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT); + userContentController.addPluginScript(JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT); + userContentController.addPluginScript(ConsoleLogJS.CONSOLE_LOG_JS_PLUGIN_SCRIPT); + userContentController.addPluginScript(PrintJS.PRINT_JS_PLUGIN_SCRIPT); + userContentController.addPluginScript(OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT); + userContentController.addPluginScript(OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT); + if (customSettings.useShouldInterceptAjaxRequest) { + userContentController.addPluginScript(InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT); + } + if (customSettings.useShouldInterceptFetchRequest) { + userContentController.addPluginScript(InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT); + } + if (customSettings.useOnLoadResource) { + userContentController.addPluginScript(OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT); + } + if (!customSettings.useHybridComposition) { + userContentController.addPluginScript(PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT); + } + this.userContentController.addUserOnlyScripts(this.initialUserOnlyScript); + } + public void setIncognito(boolean enabled) { WebSettings settings = getSettings(); if (enabled) { @@ -1879,6 +1889,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie @Override public void dispose() { + userContentController.dispose(); if (windowId != null) { InAppWebViewChromeClient.windowWebViewMessages.remove(windowId); } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebViewClient.java index 62bf88ed..a2eca550 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebViewClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebViewClient.java @@ -1,5 +1,6 @@ package com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.graphics.Bitmap; import android.net.Uri; @@ -166,15 +167,17 @@ public class InAppWebViewClient extends WebViewClient { } } + @SuppressLint("RestrictedApi") public void loadCustomJavaScriptOnPageStarted(WebView view) { InAppWebView webView = (InAppWebView) view; - String source = webView.userContentController.generateWrappedCodeForDocumentStart(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - webView.evaluateJavascript(source, (ValueCallback) null); - } else { - webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", "")); + if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + String source = webView.userContentController.generateWrappedCodeForDocumentStart(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webView.evaluateJavascript(source, (ValueCallback) null); + } else { + webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", "")); + } } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageListener.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageListener.java index 48291605..1d7dbf80 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageListener.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/web_message/WebMessageListener.java @@ -96,7 +96,8 @@ public class WebMessageListener implements Disposable { source, UserScriptInjectionTime.AT_DOCUMENT_START, null, - false + false, + null )); } } diff --git a/dev_packages/generators/lib/src/exchangeable_object_generator.dart b/dev_packages/generators/lib/src/exchangeable_object_generator.dart index 3311b426..acbf0a6a 100644 --- a/dev_packages/generators/lib/src/exchangeable_object_generator.dart +++ b/dev_packages/generators/lib/src/exchangeable_object_generator.dart @@ -586,7 +586,7 @@ class ExchangeableObjectGenerator getToMapValue('$genericTypeFieldName', genericType) + ').toList()'; } else { - return fieldName; + return elementType.isDartCoreSet ? "$fieldName${(isNullable ? '?' : '')}.toList()" : fieldName; } } else if (fieldTypeElement != null && hasToMapMethod(fieldTypeElement)) { return fieldName + diff --git a/lib/src/android/webview_feature.dart b/lib/src/android/webview_feature.dart index c41cf788..73a2cb01 100644 --- a/lib/src/android/webview_feature.dart +++ b/lib/src/android/webview_feature.dart @@ -6,6 +6,7 @@ import '../in_app_webview/in_app_webview_settings.dart'; import 'proxy_controller.dart'; import 'service_worker_controller.dart'; import '../web_message/main.dart'; +import '../types/user_script_injection_time.dart'; part 'webview_feature.g.dart'; @@ -187,6 +188,10 @@ class WebViewFeature_ { static const WEB_VIEW_RENDERER_TERMINATE = const WebViewFeature_._internal("WEB_VIEW_RENDERER_TERMINATE"); + ///This feature covers [UserScriptInjectionTime.AT_DOCUMENT_START]. + static const DOCUMENT_START_SCRIPT = + const AndroidWebViewFeature_._internal("DOCUMENT_START_SCRIPT"); + ///Return whether a feature is supported at run-time. On devices running Android version `Build.VERSION_CODES.LOLLIPOP` and higher, ///this will check whether a feature is supported, depending on the combination of the desired feature, the Android version of device, ///and the WebView APK on the device. If running on a device with a lower API level, this will always return `false`. @@ -386,6 +391,10 @@ class AndroidWebViewFeature_ { static const WEB_VIEW_RENDERER_TERMINATE = const AndroidWebViewFeature_._internal("WEB_VIEW_RENDERER_TERMINATE"); + ///This feature covers [UserScriptInjectionTime.AT_DOCUMENT_START]. + static const DOCUMENT_START_SCRIPT = + const AndroidWebViewFeature_._internal("DOCUMENT_START_SCRIPT"); + ///Return whether a feature is supported at run-time. On devices running Android version `Build.VERSION_CODES.LOLLIPOP` and higher, ///this will check whether a feature is supported, depending on the combination of the desired feature, the Android version of device, ///and the WebView APK on the device. If running on a device with a lower API level, this will always return `false`. diff --git a/lib/src/android/webview_feature.g.dart b/lib/src/android/webview_feature.g.dart index 6562f26d..91520b07 100644 --- a/lib/src/android/webview_feature.g.dart +++ b/lib/src/android/webview_feature.g.dart @@ -189,6 +189,10 @@ class WebViewFeature { static const WEB_VIEW_RENDERER_TERMINATE = WebViewFeature._internal( 'WEB_VIEW_RENDERER_TERMINATE', 'WEB_VIEW_RENDERER_TERMINATE'); + ///This feature covers [UserScriptInjectionTime.AT_DOCUMENT_START]. + static const DOCUMENT_START_SCRIPT = WebViewFeature._internal( + 'DOCUMENT_START_SCRIPT', 'DOCUMENT_START_SCRIPT'); + ///Set of all values of [WebViewFeature]. static final Set values = [ WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, @@ -232,6 +236,7 @@ class WebViewFeature { WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT, WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, WebViewFeature.WEB_VIEW_RENDERER_TERMINATE, + WebViewFeature.DOCUMENT_START_SCRIPT, ].toSet(); ///Gets a possible [WebViewFeature] instance from [String] value. @@ -479,6 +484,10 @@ class AndroidWebViewFeature { static const WEB_VIEW_RENDERER_TERMINATE = AndroidWebViewFeature._internal( 'WEB_VIEW_RENDERER_TERMINATE', 'WEB_VIEW_RENDERER_TERMINATE'); + ///This feature covers [UserScriptInjectionTime.AT_DOCUMENT_START]. + static const DOCUMENT_START_SCRIPT = AndroidWebViewFeature._internal( + 'DOCUMENT_START_SCRIPT', 'DOCUMENT_START_SCRIPT'); + ///Set of all values of [AndroidWebViewFeature]. static final Set values = [ AndroidWebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, @@ -522,6 +531,7 @@ class AndroidWebViewFeature { AndroidWebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT, AndroidWebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, AndroidWebViewFeature.WEB_VIEW_RENDERER_TERMINATE, + AndroidWebViewFeature.DOCUMENT_START_SCRIPT, ].toSet(); ///Gets a possible [AndroidWebViewFeature] instance from [String] value. diff --git a/lib/src/types/user_script.dart b/lib/src/types/user_script.dart index 7e176e64..b01ecaab 100644 --- a/lib/src/types/user_script.dart +++ b/lib/src/types/user_script.dart @@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../in_app_webview/webview.dart'; import 'user_script_injection_time.dart'; import 'content_world.dart'; +import '../android/webview_feature.dart'; part 'user_script.g.dart'; @@ -29,6 +30,11 @@ class UserScript_ { ///**NOTE**: available only on iOS. bool forMainFrameOnly; + ///A set of matching rules for the allowed origins. + /// + ///**NOTE**: available only on Android and only if [WebViewFeature.DOCUMENT_START_SCRIPT] feature is supported. + late Set allowedOriginRules; + ///A scope of execution in which to evaluate the script to prevent conflicts between different scripts. ///For more information about content worlds, see [ContentWorld]. late ContentWorld contentWorld; @@ -40,7 +46,10 @@ class UserScript_ { required this.injectionTime, @Deprecated("Use forMainFrameOnly instead") this.iosForMainFrameOnly, this.forMainFrameOnly = true, + Set? allowedOriginRules, ContentWorld? contentWorld}) { + this.allowedOriginRules = allowedOriginRules != null ? + allowedOriginRules : Set.from(["*"]); this.contentWorld = contentWorld ?? ContentWorld.PAGE; // ignore: deprecated_member_use_from_same_package this.forMainFrameOnly = this.iosForMainFrameOnly != null diff --git a/lib/src/types/user_script.g.dart b/lib/src/types/user_script.g.dart index e782fcf7..aafa9dc4 100644 --- a/lib/src/types/user_script.g.dart +++ b/lib/src/types/user_script.g.dart @@ -28,6 +28,11 @@ class UserScript { ///**NOTE**: available only on iOS. bool forMainFrameOnly; + ///A set of matching rules for the allowed origins. + /// + ///**NOTE**: available only on Android and only if [WebViewFeature.DOCUMENT_START_SCRIPT] feature is supported. + late Set allowedOriginRules; + ///A scope of execution in which to evaluate the script to prevent conflicts between different scripts. ///For more information about content worlds, see [ContentWorld]. late ContentWorld contentWorld; @@ -37,7 +42,10 @@ class UserScript { required this.injectionTime, @Deprecated("Use forMainFrameOnly instead") this.iosForMainFrameOnly, this.forMainFrameOnly = true, + Set? allowedOriginRules, ContentWorld? contentWorld}) { + this.allowedOriginRules = + allowedOriginRules != null ? allowedOriginRules : Set.from(["*"]); this.contentWorld = contentWorld ?? ContentWorld.PAGE; this.forMainFrameOnly = this.iosForMainFrameOnly != null ? this.iosForMainFrameOnly! @@ -57,6 +65,7 @@ class UserScript { iosForMainFrameOnly: map['forMainFrameOnly'], ); instance.forMainFrameOnly = map['forMainFrameOnly']; + instance.allowedOriginRules = map['allowedOriginRules'].cast(); instance.contentWorld = map['contentWorld']; return instance; } @@ -68,6 +77,7 @@ class UserScript { "source": source, "injectionTime": injectionTime.toNativeValue(), "forMainFrameOnly": forMainFrameOnly, + "allowedOriginRules": allowedOriginRules.toList(), "contentWorld": contentWorld.toMap(), }; } @@ -79,6 +89,6 @@ class UserScript { @override String toString() { - return 'UserScript{groupName: $groupName, source: $source, injectionTime: $injectionTime, forMainFrameOnly: $forMainFrameOnly, contentWorld: $contentWorld}'; + return 'UserScript{groupName: $groupName, source: $source, injectionTime: $injectionTime, forMainFrameOnly: $forMainFrameOnly, allowedOriginRules: $allowedOriginRules, contentWorld: $contentWorld}'; } }