Added WebViewFeature.DOCUMENT_START_SCRIPT Android feature support

This commit is contained in:
Lorenzo Pichilli 2022-10-06 19:17:11 +02:00
parent 1791b8202b
commit 80b8bde10a
22 changed files with 207 additions and 49 deletions

View File

@ -10,6 +10,7 @@
- Added `onCameraCaptureStateChanged`, `onMicrophoneCaptureStateChanged` WebView events - Added `onCameraCaptureStateChanged`, `onMicrophoneCaptureStateChanged` WebView events
- Added support for `onPermissionRequest` event on iOS 15.0+ - Added support for `onPermissionRequest` event on iOS 15.0+
- Added `debugLoggingSettings` static property for WebView and ChromeSafariBrowser - Added `debugLoggingSettings` static property for WebView and ChromeSafariBrowser
- Added `WebViewFeature.DOCUMENT_START_SCRIPT` Android feature support
- Updated `getMetaThemeColor` on iOS 15.0+ - Updated `getMetaThemeColor` on iOS 15.0+
- Deprecated `onLoadError` for `onReceivedError`. `onReceivedError` will be called also for subframes - Deprecated `onLoadError` for `onReceivedError`. `onReceivedError` will be called also for subframes
- Deprecated `onLoadHttpError` for `onReceivedError`. `onReceivedHttpError` will be called also for subframes - Deprecated `onLoadHttpError` for `onReceivedError`. `onReceivedHttpError` will be called also for subframes

View File

@ -10,7 +10,8 @@ public class ConsoleLogJS {
ConsoleLogJS.CONSOLE_LOG_JS_SOURCE, ConsoleLogJS.CONSOLE_LOG_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START, UserScriptInjectionTime.AT_DOCUMENT_START,
null, null,
true true,
null
); );
public static final String CONSOLE_LOG_JS_SOURCE = "(function(console) {" + public static final String CONSOLE_LOG_JS_SOURCE = "(function(console) {" +

View File

@ -12,7 +12,8 @@ public class InterceptAjaxRequestJS {
InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_SOURCE, InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START, UserScriptInjectionTime.AT_DOCUMENT_START,
null, null,
true true,
null
); );
public static final String INTERCEPT_AJAX_REQUEST_JS_SOURCE = "(function(ajax) {" + public static final String INTERCEPT_AJAX_REQUEST_JS_SOURCE = "(function(ajax) {" +

View File

@ -12,7 +12,8 @@ public class InterceptFetchRequestJS {
InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_SOURCE, InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START, UserScriptInjectionTime.AT_DOCUMENT_START,
null, null,
true true,
null
); );
public static final String INTERCEPT_FETCH_REQUEST_JS_SOURCE = "(function(fetch) {" + public static final String INTERCEPT_FETCH_REQUEST_JS_SOURCE = "(function(fetch) {" +

View File

@ -11,7 +11,8 @@ public class JavaScriptBridgeJS {
JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_SOURCE, JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START, UserScriptInjectionTime.AT_DOCUMENT_START,
null, null,
true true,
null
); );
public static final String JAVASCRIPT_UTIL_VAR_NAME = "window." + JAVASCRIPT_BRIDGE_NAME + "._Util"; public static final String JAVASCRIPT_UTIL_VAR_NAME = "window." + JAVASCRIPT_BRIDGE_NAME + "._Util";

View File

@ -11,7 +11,8 @@ public class OnLoadResourceJS {
OnLoadResourceJS.ON_LOAD_RESOURCE_JS_SOURCE, OnLoadResourceJS.ON_LOAD_RESOURCE_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START, UserScriptInjectionTime.AT_DOCUMENT_START,
null, null,
false false,
null
); );
public static final String ON_LOAD_RESOURCE_JS_SOURCE = "window." + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE + " = true;" + public static final String ON_LOAD_RESOURCE_JS_SOURCE = "window." + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE + " = true;" +

View File

@ -10,7 +10,8 @@ public class OnWindowBlurEventJS {
OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_SOURCE, OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START, UserScriptInjectionTime.AT_DOCUMENT_START,
null, null,
false false,
null
); );
public static final String ON_WINDOW_BLUR_EVENT_JS_SOURCE = "(function(){" + public static final String ON_WINDOW_BLUR_EVENT_JS_SOURCE = "(function(){" +

View File

@ -10,7 +10,8 @@ public class OnWindowFocusEventJS {
OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_SOURCE, OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START, UserScriptInjectionTime.AT_DOCUMENT_START,
null, null,
false false,
null
); );
public static final String ON_WINDOW_FOCUS_EVENT_JS_SOURCE = "(function(){" + public static final String ON_WINDOW_FOCUS_EVENT_JS_SOURCE = "(function(){" +

View File

@ -77,7 +77,8 @@ public class PluginScriptsUtil {
PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE, PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START, UserScriptInjectionTime.AT_DOCUMENT_START,
null, null,
false false,
null
); );
// android Workaround to hide context menu when user emit a keydown event // android Workaround to hide context menu when user emit a keydown event

View File

@ -10,7 +10,8 @@ public class PrintJS {
PrintJS.PRINT_JS_SOURCE, PrintJS.PRINT_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START, UserScriptInjectionTime.AT_DOCUMENT_START,
null, null,
false false,
null
); );
public static final String PRINT_JS_SOURCE = "window.print = function() {" + public static final String PRINT_JS_SOURCE = "window.print = function() {" +

View File

@ -10,7 +10,8 @@ public class PromisePolyfillJS {
PromisePolyfillJS.PROMISE_POLYFILL_JS_SOURCE, PromisePolyfillJS.PROMISE_POLYFILL_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START, UserScriptInjectionTime.AT_DOCUMENT_START,
null, null,
true true,
null
); );
// https://github.com/tildeio/rsvp.js // https://github.com/tildeio/rsvp.js

View File

@ -3,11 +3,13 @@ package com.pichillilorenzo.flutter_inappwebview.types;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.Set;
public class PluginScript extends UserScript { public class PluginScript extends UserScript {
private boolean requiredInAllContentWorlds; private boolean requiredInAllContentWorlds;
public PluginScript(@Nullable String groupName, @NonNull String source, @NonNull UserScriptInjectionTime injectionTime, @Nullable ContentWorld contentWorld, boolean requiredInAllContentWorlds) { public PluginScript(@Nullable String groupName, @NonNull String source, @NonNull UserScriptInjectionTime injectionTime, @Nullable ContentWorld contentWorld, boolean requiredInAllContentWorlds, @Nullable Set<String> allowedOriginRules) {
super(groupName, source, injectionTime, contentWorld); super(groupName, source, injectionTime, contentWorld, allowedOriginRules);
this.requiredInAllContentWorlds = requiredInAllContentWorlds; this.requiredInAllContentWorlds = requiredInAllContentWorlds;
} }

View File

@ -1,10 +1,15 @@
package com.pichillilorenzo.flutter_inappwebview.types; package com.pichillilorenzo.flutter_inappwebview.types;
import android.annotation.SuppressLint;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.webkit.WebView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; 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.Util;
import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.JavaScriptBridgeJS; import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.JavaScriptBridgeJS;
@ -21,7 +26,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
public class UserContentController { @SuppressLint("RestrictedApi")
public class UserContentController implements Disposable {
protected static final String LOG_TAG = "UserContentController"; protected static final String LOG_TAG = "UserContentController";
@NonNull @NonNull
@ -29,6 +35,8 @@ public class UserContentController {
add(ContentWorld.PAGE); add(ContentWorld.PAGE);
}}; }};
private final Map<UserScript, ScriptHandler> scriptHandlerMap = new HashMap<>();
@NonNull @NonNull
private final Map<UserScriptInjectionTime, LinkedHashSet<UserScript>> userOnlyScripts = new HashMap<UserScriptInjectionTime, LinkedHashSet<UserScript>>() {{ private final Map<UserScriptInjectionTime, LinkedHashSet<UserScript>> userOnlyScripts = new HashMap<UserScriptInjectionTime, LinkedHashSet<UserScript>>() {{
put(UserScriptInjectionTime.AT_DOCUMENT_START, new LinkedHashSet<UserScript>()); put(UserScriptInjectionTime.AT_DOCUMENT_START, new LinkedHashSet<UserScript>());
@ -40,7 +48,11 @@ public class UserContentController {
put(UserScriptInjectionTime.AT_DOCUMENT_END, new LinkedHashSet<PluginScript>()); put(UserScriptInjectionTime.AT_DOCUMENT_END, new LinkedHashSet<PluginScript>());
}}; }};
public UserContentController() { @Nullable
public WebView webView;
public UserContentController(WebView webView) {
this.webView = webView;
} }
public String generateWrappedCodeForDocumentStart() { public String generateWrappedCodeForDocumentStart() {
@ -52,8 +64,11 @@ public class UserContentController {
public String generateWrappedCodeForDocumentEnd() { public String generateWrappedCodeForDocumentEnd() {
UserScriptInjectionTime injectionTime = UserScriptInjectionTime.AT_DOCUMENT_END; UserScriptInjectionTime injectionTime = UserScriptInjectionTime.AT_DOCUMENT_END;
String js = "";
if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
// try to reload scripts if they were not loaded during the AT_DOCUMENT_START event // try to reload scripts if they were not loaded during the AT_DOCUMENT_START event
String js = generateCodeForDocumentStart(); js += generateCodeForDocumentStart();
}
js += generatePluginScriptsCodeAt(injectionTime); js += generatePluginScriptsCodeAt(injectionTime);
js += generateUserOnlyScriptsCodeAt(injectionTime); js += generateUserOnlyScriptsCodeAt(injectionTime);
js = USER_SCRIPTS_AT_DOCUMENT_END_WRAPPER_JS_SOURCE.replace(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE, js); 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) { if (contentWorld != null) {
contentWorlds.add(contentWorld); 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); return this.userOnlyScripts.get(userOnlyScript.getInjectionTime()).add(userOnlyScript);
} }
@ -171,6 +194,13 @@ public class UserContentController {
} }
public boolean removeUserOnlyScript(UserScript userOnlyScript) { 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); return this.userOnlyScripts.get(userOnlyScript.getInjectionTime()).remove(userOnlyScript);
} }
@ -180,6 +210,15 @@ public class UserContentController {
} }
public void removeAllUserOnlyScripts() { 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_START).clear();
this.userOnlyScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END).clear(); this.userOnlyScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END).clear();
} }
@ -204,6 +243,14 @@ public class UserContentController {
if (contentWorld != null) { if (contentWorld != null) {
contentWorlds.add(contentWorld); 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); return this.pluginScripts.get(pluginScript.getInjectionTime()).add(pluginScript);
} }
@ -214,10 +261,26 @@ public class UserContentController {
} }
public boolean removePluginScript(PluginScript pluginScript) { 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); return this.pluginScripts.get(pluginScript.getInjectionTime()).remove(pluginScript);
} }
public void removeAllPluginScripts() { 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_START).clear();
this.pluginScripts.get(UserScriptInjectionTime.AT_DOCUMENT_END).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') { " + private static final String DOCUMENT_READY_WRAPPER_JS_SOURCE = "if (document.readyState === 'interactive' || document.readyState === 'complete') { " +
" " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE + " " + PluginScriptsUtil.VAR_PLACEHOLDER_VALUE +
"}"; "}";
@Override
public void dispose() {
webView = null;
}
} }

View File

@ -3,7 +3,10 @@ package com.pichillilorenzo.flutter_inappwebview.types;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
public class UserScript { public class UserScript {
@Nullable @Nullable
@ -14,12 +17,19 @@ public class UserScript {
private UserScriptInjectionTime injectionTime; private UserScriptInjectionTime injectionTime;
@NonNull @NonNull
private ContentWorld contentWorld; private ContentWorld contentWorld;
@NonNull
private Set<String> 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<String> allowedOriginRules) {
this.groupName = groupName; this.groupName = groupName;
this.source = source; this.source = source;
this.injectionTime = injectionTime; this.injectionTime = injectionTime;
this.contentWorld = contentWorld == null ? ContentWorld.PAGE : contentWorld; this.contentWorld = contentWorld == null ? ContentWorld.PAGE : contentWorld;
this.allowedOriginRules = allowedOriginRules == null ? new HashSet<String>() {{
add("*");
}} : allowedOriginRules;
} }
@Nullable @Nullable
@ -31,8 +41,9 @@ public class UserScript {
String source = (String) map.get("source"); String source = (String) map.get("source");
UserScriptInjectionTime injectionTime = UserScriptInjectionTime.fromValue((int) map.get("injectionTime")); UserScriptInjectionTime injectionTime = UserScriptInjectionTime.fromValue((int) map.get("injectionTime"));
ContentWorld contentWorld = ContentWorld.fromMap((Map<String, Object>) map.get("contentWorld")); ContentWorld contentWorld = ContentWorld.fromMap((Map<String, Object>) map.get("contentWorld"));
Set<String> allowedOriginRules = new HashSet<>((List<String>) map.get("allowedOriginRules"));
assert source != null; assert source != null;
return new UserScript(groupName, source, injectionTime, contentWorld); return new UserScript(groupName, source, injectionTime, contentWorld, allowedOriginRules);
} }
@Nullable @Nullable
@ -71,6 +82,15 @@ public class UserScript {
this.contentWorld = contentWorld == null ? ContentWorld.PAGE : contentWorld; this.contentWorld = contentWorld == null ? ContentWorld.PAGE : contentWorld;
} }
@NonNull
public Set<String> getAllowedOriginRules() {
return allowedOriginRules;
}
public void setAllowedOriginRules(@NonNull Set<String> allowedOriginRules) {
this.allowedOriginRules = allowedOriginRules;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -78,10 +98,12 @@ public class UserScript {
UserScript that = (UserScript) o; 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 (!source.equals(that.source)) return false;
if (injectionTime != that.injectionTime) 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 @Override
@ -90,6 +112,7 @@ public class UserScript {
result = 31 * result + source.hashCode(); result = 31 * result + source.hashCode();
result = 31 * result + injectionTime.hashCode(); result = 31 * result + injectionTime.hashCode();
result = 31 * result + contentWorld.hashCode(); result = 31 * result + contentWorld.hashCode();
result = 31 * result + allowedOriginRules.hashCode();
return result; return result;
} }
@ -100,6 +123,7 @@ public class UserScript {
", source='" + source + '\'' + ", source='" + source + '\'' +
", injectionTime=" + injectionTime + ", injectionTime=" + injectionTime +
", contentWorld=" + contentWorld + ", contentWorld=" + contentWorld +
", allowedOriginRules=" + allowedOriginRules +
'}'; '}';
} }
} }

View File

@ -2,6 +2,7 @@ package com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder; import android.animation.PropertyValuesHolder;
import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -96,9 +97,11 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -153,7 +156,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie
public Runnable checkContextMenuShouldBeClosedTask; public Runnable checkContextMenuShouldBeClosedTask;
public int newCheckContextMenuShouldBeClosedTaskTask = 100; // ms public int newCheckContextMenuShouldBeClosedTaskTask = 100; // ms
public UserContentController userContentController = new UserContentController(); public UserContentController userContentController = new UserContentController(this);
public Map<String, ValueCallback<String>> callAsyncJavaScriptCallbacks = new HashMap<>(); public Map<String, ValueCallback<String>> callAsyncJavaScriptCallbacks = new HashMap<>();
public Map<String, ValueCallback<String>> evaluateJavaScriptContentWorldCallbacks = new HashMap<>(); public Map<String, ValueCallback<String>> evaluateJavaScriptContentWorldCallbacks = new HashMap<>();
@ -161,6 +164,8 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie
public Map<String, WebMessageChannel> webMessageChannels = new HashMap<>(); public Map<String, WebMessageChannel> webMessageChannels = new HashMap<>();
public List<WebMessageListener> webMessageListeners = new ArrayList<>(); public List<WebMessageListener> webMessageListeners = new ArrayList<>();
private List<UserScript> initialUserOnlyScript = new ArrayList<>();
public InAppWebView(Context context) { public InAppWebView(Context context) {
super(context); super(context);
} }
@ -185,7 +190,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie
this.windowId = windowId; this.windowId = windowId;
this.customSettings = customSettings; this.customSettings = customSettings;
this.contextMenu = contextMenu; this.contextMenu = contextMenu;
this.userContentController.addUserOnlyScripts(userScripts); this.initialUserOnlyScript = userScripts;
if (plugin != null && plugin.activity != null) { if (plugin != null && plugin.activity != null) {
plugin.activity.registerForContextMenu(this); plugin.activity.registerForContextMenu(this);
} }
@ -208,24 +213,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie
WebViewCompat.setWebViewRenderProcessClient(this, inAppWebViewRenderProcessClient); WebViewCompat.setWebViewRenderProcessClient(this, inAppWebViewRenderProcessClient);
} }
userContentController.addPluginScript(PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT); prepareAndAddUserScripts();
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);
}
if (customSettings.useOnDownloadStart) if (customSettings.useOnDownloadStart)
setDownloadListener(new DownloadStartListener()); 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) { public void setIncognito(boolean enabled) {
WebSettings settings = getSettings(); WebSettings settings = getSettings();
if (enabled) { if (enabled) {
@ -1879,6 +1889,7 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie
@Override @Override
public void dispose() { public void dispose() {
userContentController.dispose();
if (windowId != null) { if (windowId != null) {
InAppWebViewChromeClient.windowWebViewMessages.remove(windowId); InAppWebViewChromeClient.windowWebViewMessages.remove(windowId);
} }

View File

@ -1,5 +1,6 @@
package com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview; package com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview;
import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
@ -166,17 +167,19 @@ public class InAppWebViewClient extends WebViewClient {
} }
} }
@SuppressLint("RestrictedApi")
public void loadCustomJavaScriptOnPageStarted(WebView view) { public void loadCustomJavaScriptOnPageStarted(WebView view) {
InAppWebView webView = (InAppWebView) view; InAppWebView webView = (InAppWebView) view;
if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
String source = webView.userContentController.generateWrappedCodeForDocumentStart(); String source = webView.userContentController.generateWrappedCodeForDocumentStart();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript(source, (ValueCallback<String>) null); webView.evaluateJavascript(source, (ValueCallback<String>) null);
} else { } else {
webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", "")); webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", ""));
} }
} }
}
public void loadCustomJavaScriptOnPageFinished(WebView view) { public void loadCustomJavaScriptOnPageFinished(WebView view) {
InAppWebView webView = (InAppWebView) view; InAppWebView webView = (InAppWebView) view;

View File

@ -96,7 +96,8 @@ public class WebMessageListener implements Disposable {
source, source,
UserScriptInjectionTime.AT_DOCUMENT_START, UserScriptInjectionTime.AT_DOCUMENT_START,
null, null,
false false,
null
)); ));
} }
} }

View File

@ -586,7 +586,7 @@ class ExchangeableObjectGenerator
getToMapValue('$genericTypeFieldName', genericType) + getToMapValue('$genericTypeFieldName', genericType) +
').toList()'; ').toList()';
} else { } else {
return fieldName; return elementType.isDartCoreSet ? "$fieldName${(isNullable ? '?' : '')}.toList()" : fieldName;
} }
} else if (fieldTypeElement != null && hasToMapMethod(fieldTypeElement)) { } else if (fieldTypeElement != null && hasToMapMethod(fieldTypeElement)) {
return fieldName + return fieldName +

View File

@ -6,6 +6,7 @@ import '../in_app_webview/in_app_webview_settings.dart';
import 'proxy_controller.dart'; import 'proxy_controller.dart';
import 'service_worker_controller.dart'; import 'service_worker_controller.dart';
import '../web_message/main.dart'; import '../web_message/main.dart';
import '../types/user_script_injection_time.dart';
part 'webview_feature.g.dart'; part 'webview_feature.g.dart';
@ -187,6 +188,10 @@ class WebViewFeature_ {
static const WEB_VIEW_RENDERER_TERMINATE = static const WEB_VIEW_RENDERER_TERMINATE =
const WebViewFeature_._internal("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, ///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, ///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`. ///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 = static const WEB_VIEW_RENDERER_TERMINATE =
const AndroidWebViewFeature_._internal("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, ///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, ///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`. ///and the WebView APK on the device. If running on a device with a lower API level, this will always return `false`.

View File

@ -189,6 +189,10 @@ class WebViewFeature {
static const WEB_VIEW_RENDERER_TERMINATE = WebViewFeature._internal( static const WEB_VIEW_RENDERER_TERMINATE = WebViewFeature._internal(
'WEB_VIEW_RENDERER_TERMINATE', 'WEB_VIEW_RENDERER_TERMINATE'); '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]. ///Set of all values of [WebViewFeature].
static final Set<WebViewFeature> values = [ static final Set<WebViewFeature> values = [
WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL,
@ -232,6 +236,7 @@ class WebViewFeature {
WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT, WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT,
WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE,
WebViewFeature.WEB_VIEW_RENDERER_TERMINATE, WebViewFeature.WEB_VIEW_RENDERER_TERMINATE,
WebViewFeature.DOCUMENT_START_SCRIPT,
].toSet(); ].toSet();
///Gets a possible [WebViewFeature] instance from [String] value. ///Gets a possible [WebViewFeature] instance from [String] value.
@ -479,6 +484,10 @@ class AndroidWebViewFeature {
static const WEB_VIEW_RENDERER_TERMINATE = AndroidWebViewFeature._internal( static const WEB_VIEW_RENDERER_TERMINATE = AndroidWebViewFeature._internal(
'WEB_VIEW_RENDERER_TERMINATE', 'WEB_VIEW_RENDERER_TERMINATE'); '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]. ///Set of all values of [AndroidWebViewFeature].
static final Set<AndroidWebViewFeature> values = [ static final Set<AndroidWebViewFeature> values = [
AndroidWebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, AndroidWebViewFeature.CREATE_WEB_MESSAGE_CHANNEL,
@ -522,6 +531,7 @@ class AndroidWebViewFeature {
AndroidWebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT, AndroidWebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT,
AndroidWebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, AndroidWebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE,
AndroidWebViewFeature.WEB_VIEW_RENDERER_TERMINATE, AndroidWebViewFeature.WEB_VIEW_RENDERER_TERMINATE,
AndroidWebViewFeature.DOCUMENT_START_SCRIPT,
].toSet(); ].toSet();
///Gets a possible [AndroidWebViewFeature] instance from [String] value. ///Gets a possible [AndroidWebViewFeature] instance from [String] value.

View File

@ -3,6 +3,7 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i
import '../in_app_webview/webview.dart'; import '../in_app_webview/webview.dart';
import 'user_script_injection_time.dart'; import 'user_script_injection_time.dart';
import 'content_world.dart'; import 'content_world.dart';
import '../android/webview_feature.dart';
part 'user_script.g.dart'; part 'user_script.g.dart';
@ -29,6 +30,11 @@ class UserScript_ {
///**NOTE**: available only on iOS. ///**NOTE**: available only on iOS.
bool forMainFrameOnly; 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<String> allowedOriginRules;
///A scope of execution in which to evaluate the script to prevent conflicts between different scripts. ///A scope of execution in which to evaluate the script to prevent conflicts between different scripts.
///For more information about content worlds, see [ContentWorld]. ///For more information about content worlds, see [ContentWorld].
late ContentWorld contentWorld; late ContentWorld contentWorld;
@ -40,7 +46,10 @@ class UserScript_ {
required this.injectionTime, required this.injectionTime,
@Deprecated("Use forMainFrameOnly instead") this.iosForMainFrameOnly, @Deprecated("Use forMainFrameOnly instead") this.iosForMainFrameOnly,
this.forMainFrameOnly = true, this.forMainFrameOnly = true,
Set<String>? allowedOriginRules,
ContentWorld? contentWorld}) { ContentWorld? contentWorld}) {
this.allowedOriginRules = allowedOriginRules != null ?
allowedOriginRules : Set.from(["*"]);
this.contentWorld = contentWorld ?? ContentWorld.PAGE; this.contentWorld = contentWorld ?? ContentWorld.PAGE;
// ignore: deprecated_member_use_from_same_package // ignore: deprecated_member_use_from_same_package
this.forMainFrameOnly = this.iosForMainFrameOnly != null this.forMainFrameOnly = this.iosForMainFrameOnly != null

View File

@ -28,6 +28,11 @@ class UserScript {
///**NOTE**: available only on iOS. ///**NOTE**: available only on iOS.
bool forMainFrameOnly; 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<String> allowedOriginRules;
///A scope of execution in which to evaluate the script to prevent conflicts between different scripts. ///A scope of execution in which to evaluate the script to prevent conflicts between different scripts.
///For more information about content worlds, see [ContentWorld]. ///For more information about content worlds, see [ContentWorld].
late ContentWorld contentWorld; late ContentWorld contentWorld;
@ -37,7 +42,10 @@ class UserScript {
required this.injectionTime, required this.injectionTime,
@Deprecated("Use forMainFrameOnly instead") this.iosForMainFrameOnly, @Deprecated("Use forMainFrameOnly instead") this.iosForMainFrameOnly,
this.forMainFrameOnly = true, this.forMainFrameOnly = true,
Set<String>? allowedOriginRules,
ContentWorld? contentWorld}) { ContentWorld? contentWorld}) {
this.allowedOriginRules =
allowedOriginRules != null ? allowedOriginRules : Set.from(["*"]);
this.contentWorld = contentWorld ?? ContentWorld.PAGE; this.contentWorld = contentWorld ?? ContentWorld.PAGE;
this.forMainFrameOnly = this.iosForMainFrameOnly != null this.forMainFrameOnly = this.iosForMainFrameOnly != null
? this.iosForMainFrameOnly! ? this.iosForMainFrameOnly!
@ -57,6 +65,7 @@ class UserScript {
iosForMainFrameOnly: map['forMainFrameOnly'], iosForMainFrameOnly: map['forMainFrameOnly'],
); );
instance.forMainFrameOnly = map['forMainFrameOnly']; instance.forMainFrameOnly = map['forMainFrameOnly'];
instance.allowedOriginRules = map['allowedOriginRules'].cast<String>();
instance.contentWorld = map['contentWorld']; instance.contentWorld = map['contentWorld'];
return instance; return instance;
} }
@ -68,6 +77,7 @@ class UserScript {
"source": source, "source": source,
"injectionTime": injectionTime.toNativeValue(), "injectionTime": injectionTime.toNativeValue(),
"forMainFrameOnly": forMainFrameOnly, "forMainFrameOnly": forMainFrameOnly,
"allowedOriginRules": allowedOriginRules.toList(),
"contentWorld": contentWorld.toMap(), "contentWorld": contentWorld.toMap(),
}; };
} }
@ -79,6 +89,6 @@ class UserScript {
@override @override
String toString() { 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}';
} }
} }