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 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

View File

@ -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) {" +

View File

@ -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) {" +

View File

@ -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) {" +

View File

@ -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";

View File

@ -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;" +

View File

@ -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(){" +

View File

@ -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(){" +

View File

@ -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

View File

@ -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() {" +

View File

@ -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

View File

@ -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<String> allowedOriginRules) {
super(groupName, source, injectionTime, contentWorld, allowedOriginRules);
this.requiredInAllContentWorlds = requiredInAllContentWorlds;
}

View File

@ -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<UserScript, ScriptHandler> scriptHandlerMap = new HashMap<>();
@NonNull
private final Map<UserScriptInjectionTime, LinkedHashSet<UserScript>> userOnlyScripts = new HashMap<UserScriptInjectionTime, 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>());
}};
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;
}
}

View File

@ -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<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.source = source;
this.injectionTime = injectionTime;
this.contentWorld = contentWorld == null ? ContentWorld.PAGE : contentWorld;
this.allowedOriginRules = allowedOriginRules == null ? new HashSet<String>() {{
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<String, Object>) map.get("contentWorld"));
Set<String> allowedOriginRules = new HashSet<>((List<String>) 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<String> getAllowedOriginRules() {
return allowedOriginRules;
}
public void setAllowedOriginRules(@NonNull Set<String> 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 +
'}';
}
}

View File

@ -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<String, ValueCallback<String>> callAsyncJavaScriptCallbacks = 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 List<WebMessageListener> webMessageListeners = new ArrayList<>();
private List<UserScript> 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);
}

View File

@ -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<String>) 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<String>) null);
} else {
webView.loadUrl("javascript:" + source.replaceAll("[\r\n]+", ""));
}
}
}

View File

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

View File

@ -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 +

View File

@ -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`.

View File

@ -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<WebViewFeature> 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<AndroidWebViewFeature> 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.

View File

@ -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<String> 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<String>? 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

View File

@ -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<String> 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<String>? 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<String>();
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}';
}
}