Added ProcessGlobalConfig for Android WebViews, Added disableWebView static method on InAppWebViewController for Android, Added support for Android WebViewFeature.isStartupFeatureSupported, WebViewFeature.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS, WebViewFeature.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX, WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER, Added WebMessage.type property, WebMessage.data property is of type dynamic, JavaScriptReplyProxy.postMessage is of type WebMessage, changed WebMessageListener.onPostMessage and WebMessagePort.setWebMessageCallback methods signature

This commit is contained in:
Lorenzo Pichilli 2023-11-14 18:39:25 +01:00
parent a0bbbba7f1
commit 8dfddc2e12
54 changed files with 1560 additions and 266 deletions

View File

@ -1,3 +1,16 @@
## 6.0.0-beta.28
- Added `ProcessGlobalConfig` for Android WebViews
- Added `disableWebView` static method on `InAppWebViewController` for Android
- Added support for Android `WebViewFeature.isStartupFeatureSupported`, `WebViewFeature.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS`, `WebViewFeature.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX`, `WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER`
- Added `WebMessage.type` property
### BREAKING CHANGES
- `WebMessage.data` property is of type `dynamic`
- `JavaScriptReplyProxy.postMessage` is of type `WebMessage`
- `WebMessageListener.onPostMessage` and `WebMessagePort.setWebMessageCallback` methods signature
## 6.0.0-beta.27 ## 6.0.0-beta.27
- Added `requestPostMessageChannel`, `postMessage`, `isEngagementSignalsApiAvailable` methods on `ChromeSafariBrowser` for Android - Added `requestPostMessageChannel`, `postMessage`, `isEngagementSignalsApiAvailable` methods on `ChromeSafariBrowser` for Android

View File

@ -2,29 +2,28 @@ package com.pichillilorenzo.flutter_inappwebview;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.webkit.ValueCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.ChromeSafariBrowserManager; import com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.ChromeSafariBrowserManager;
import com.pichillilorenzo.flutter_inappwebview.credential_database.CredentialDatabaseHandler; import com.pichillilorenzo.flutter_inappwebview.credential_database.CredentialDatabaseHandler;
import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserManager;
import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebViewManager; import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebViewManager;
import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserManager;
import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobManager; import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobManager;
import com.pichillilorenzo.flutter_inappwebview.process_global_config.ProcessGlobalConfigManager;
import com.pichillilorenzo.flutter_inappwebview.proxy.ProxyManager; import com.pichillilorenzo.flutter_inappwebview.proxy.ProxyManager;
import com.pichillilorenzo.flutter_inappwebview.service_worker.ServiceWorkerManager; import com.pichillilorenzo.flutter_inappwebview.service_worker.ServiceWorkerManager;
import com.pichillilorenzo.flutter_inappwebview.tracing.TracingControllerManager; import com.pichillilorenzo.flutter_inappwebview.tracing.TracingControllerManager;
import com.pichillilorenzo.flutter_inappwebview.webview.FlutterWebViewFactory; import com.pichillilorenzo.flutter_inappwebview.webview.FlutterWebViewFactory;
import com.pichillilorenzo.flutter_inappwebview.webview.InAppWebViewManager; import com.pichillilorenzo.flutter_inappwebview.webview.InAppWebViewManager;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.platform.PlatformViewRegistry; import io.flutter.plugin.platform.PlatformViewRegistry;
import io.flutter.view.FlutterView; import io.flutter.view.FlutterView;
@ -58,6 +57,8 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
public PrintJobManager printJobManager; public PrintJobManager printJobManager;
@Nullable @Nullable
public TracingControllerManager tracingControllerManager; public TracingControllerManager tracingControllerManager;
@Nullable
public ProcessGlobalConfigManager processGlobalConfigManager;
public FlutterWebViewFactory flutterWebViewFactory; public FlutterWebViewFactory flutterWebViewFactory;
public Context applicationContext; public Context applicationContext;
public PluginRegistry.Registrar registrar; public PluginRegistry.Registrar registrar;
@ -122,6 +123,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
printJobManager = new PrintJobManager(this); printJobManager = new PrintJobManager(this);
} }
tracingControllerManager = new TracingControllerManager(this); tracingControllerManager = new TracingControllerManager(this);
processGlobalConfigManager = new ProcessGlobalConfigManager(this);
} }
@Override @Override
@ -178,6 +180,10 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
tracingControllerManager.dispose(); tracingControllerManager.dispose();
tracingControllerManager = null; tracingControllerManager = null;
} }
if (processGlobalConfigManager != null) {
processGlobalConfigManager.dispose();
processGlobalConfigManager = null;
}
} }
@Override @Override

View File

@ -28,6 +28,12 @@ public class WebViewFeatureManager extends ChannelDelegateImpl {
String feature = (String) call.argument("feature"); String feature = (String) call.argument("feature");
result.success(WebViewFeature.isFeatureSupported(feature)); result.success(WebViewFeature.isFeatureSupported(feature));
break; break;
case "isStartupFeatureSupported":
if (plugin != null && plugin.activity != null) {
String startupFeature = (String) call.argument("startupFeature");
result.success(WebViewFeature.isStartupFeatureSupported(plugin.activity, startupFeature));
}
break;
default: default:
result.notImplemented(); result.notImplemented();
} }

View File

@ -0,0 +1,50 @@
package com.pichillilorenzo.flutter_inappwebview.process_global_config;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.ProcessGlobalConfig;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.pull_to_refresh.PullToRefreshSettings;
import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class ProcessGlobalConfigManager extends ChannelDelegateImpl {
protected static final String LOG_TAG = "ProcessGlobalConfigManager";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_processglobalconfig";
@Nullable
public InAppWebViewFlutterPlugin plugin;
public ProcessGlobalConfigManager(@NonNull final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
this.plugin = plugin;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) {
case "apply":
if (plugin != null && plugin.activity != null) {
ProcessGlobalConfigSettings settings = (new ProcessGlobalConfigSettings())
.parse((Map<String, Object>) call.argument("settings"));
ProcessGlobalConfig.apply(settings.toProcessGlobalConfig(plugin.activity));
}
result.success(true);
break;
default:
result.notImplemented();
}
}
@Override
public void dispose() {
super.dispose();
plugin = null;
}
}

View File

@ -0,0 +1,119 @@
package com.pichillilorenzo.flutter_inappwebview.process_global_config;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.ProcessGlobalConfig;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.ISettings;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
public class ProcessGlobalConfigSettings implements ISettings<ProcessGlobalConfig> {
public static final String LOG_TAG = "ProcessGlobalConfigSettings";
@Nullable
public String dataDirectorySuffix;
@Nullable
public DirectoryBasePaths directoryBasePaths;
@NonNull
@Override
public ProcessGlobalConfigSettings parse(@NonNull Map<String, Object> settings) {
for (Map.Entry<String, Object> pair : settings.entrySet()) {
String key = pair.getKey();
Object value = pair.getValue();
if (value == null) {
continue;
}
switch (key) {
case "dataDirectorySuffix":
dataDirectorySuffix = (String) value;
break;
case "directoryBasePaths":
directoryBasePaths = (new DirectoryBasePaths()).parse((Map<String, Object>) value);
break;
}
}
return this;
}
public ProcessGlobalConfig toProcessGlobalConfig(@NonNull Context context) {
ProcessGlobalConfig config = new ProcessGlobalConfig();
if (dataDirectorySuffix != null &&
WebViewFeature.isStartupFeatureSupported(context, WebViewFeature.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX)) {
config.setDataDirectorySuffix(context, dataDirectorySuffix);
}
if (directoryBasePaths != null &&
WebViewFeature.isStartupFeatureSupported(context, WebViewFeature.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS)) {
config.setDirectoryBasePaths(context,
new File(directoryBasePaths.dataDirectoryBasePath),
new File(directoryBasePaths.cacheDirectoryBasePath));
}
return config;
}
@NonNull
public Map<String, Object> toMap() {
Map<String, Object> settings = new HashMap<>();
settings.put("dataDirectorySuffix", dataDirectorySuffix);
return settings;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull ProcessGlobalConfig processGlobalConfig) {
Map<String, Object> realSettings = toMap();
return realSettings;
}
static class DirectoryBasePaths implements ISettings<Object> {
public static final String LOG_TAG = "ProcessGlobalConfigSettings";
public String cacheDirectoryBasePath;
public String dataDirectoryBasePath;
@NonNull
@Override
public DirectoryBasePaths parse(@NonNull Map<String, Object> settings) {
for (Map.Entry<String, Object> pair : settings.entrySet()) {
String key = pair.getKey();
Object value = pair.getValue();
if (value == null) {
continue;
}
switch (key) {
case "cacheDirectoryBasePath":
cacheDirectoryBasePath = (String) value;
break;
case "dataDirectoryBasePath":
dataDirectoryBasePath = (String) value;
break;
}
}
return this;
}
@NonNull
public Map<String, Object> toMap() {
Map<String, Object> settings = new HashMap<>();
settings.put("cacheDirectoryBasePath", cacheDirectoryBasePath);
settings.put("dataDirectoryBasePath", dataDirectoryBasePath);
return settings;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull Object obj) {
Map<String, Object> realSettings = toMap();
return realSettings;
}
}
}

View File

@ -0,0 +1,117 @@
package com.pichillilorenzo.flutter_inappwebview.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.WebMessageCompat;
import androidx.webkit.WebViewFeature;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class WebMessageCompatExt {
@Nullable
private Object data;
private @WebMessageCompat.Type int type;
@Nullable
private List<WebMessagePortCompatExt> ports;
public WebMessageCompatExt(@Nullable Object data, int type, @Nullable List<WebMessagePortCompatExt> ports) {
this.data = data;
this.type = type;
this.ports = ports;
}
public static WebMessageCompatExt fromMapWebMessageCompat(@NonNull WebMessageCompat message) {
Object data;
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER) && message.getType() == WebMessageCompat.TYPE_ARRAY_BUFFER) {
data = message.getArrayBuffer();
} else {
data = message.getData();
}
return new WebMessageCompatExt(data, message.getType(), null);
}
@Nullable
public static WebMessageCompatExt fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
Object data = map.get("data");
Integer type = (Integer) map.get("type");
List<Map<String, Object>> portMapList = (List<Map<String, Object>>) map.get("ports");
List<WebMessagePortCompatExt> ports = null;
if (portMapList != null && !portMapList.isEmpty()) {
ports = new ArrayList<>();
for (Map<String, Object> portMap : portMapList) {
ports.add(WebMessagePortCompatExt.fromMap(portMap));
}
}
return new WebMessageCompatExt(data, type, ports);
}
public Map<String, Object> toMap() {
Map<String, Object> proxyRuleMap = new HashMap<>();
proxyRuleMap.put("data", data);
proxyRuleMap.put("type", type);
return proxyRuleMap;
}
@Nullable
public Object getData() {
return data;
}
public void setData(@Nullable Object data) {
this.data = data;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
@Nullable
public List<WebMessagePortCompatExt> getPorts() {
return ports;
}
public void setPorts(@Nullable List<WebMessagePortCompatExt> ports) {
this.ports = ports;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WebMessageCompatExt that = (WebMessageCompatExt) o;
if (type != that.type) return false;
if (!Objects.equals(data, that.data)) return false;
return Objects.equals(ports, that.ports);
}
@Override
public int hashCode() {
int result = data != null ? data.hashCode() : 0;
result = 31 * result + type;
result = 31 * result + (ports != null ? ports.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "WebMessageCompatExt{" +
"data=" + data +
", type=" + type +
", ports=" + ports +
'}';
}
}

View File

@ -0,0 +1,78 @@
package com.pichillilorenzo.flutter_inappwebview.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class WebMessagePortCompatExt {
private int index;
@NonNull
private String webMessageChannelId;
public WebMessagePortCompatExt(int index, @NonNull String webMessageChannelId) {
this.index = index;
this.webMessageChannelId = webMessageChannelId;
}
@Nullable
public static WebMessagePortCompatExt fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
Integer index = (Integer) map.get("index");
String webMessageChannelId = (String) map.get("webMessageChannelId");
return new WebMessagePortCompatExt(index, webMessageChannelId);
}
public Map<String, Object> toMap() {
Map<String, Object> proxyRuleMap = new HashMap<>();
proxyRuleMap.put("index", index);
proxyRuleMap.put("webMessageChannelId", webMessageChannelId);
return proxyRuleMap;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
@NonNull
public String getWebMessageChannelId() {
return webMessageChannelId;
}
public void setWebMessageChannelId(@NonNull String webMessageChannelId) {
this.webMessageChannelId = webMessageChannelId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WebMessagePortCompatExt that = (WebMessagePortCompatExt) o;
if (index != that.index) return false;
return webMessageChannelId.equals(that.webMessageChannelId);
}
@Override
public int hashCode() {
int result = index;
result = 31 * result + webMessageChannelId.hashCode();
return result;
}
@Override
public String toString() {
return "WebMessagePortCompatExt{" +
"index=" + index +
", webMessageChannelId='" + webMessageChannelId + '\'' +
'}';
}
}

View File

@ -133,6 +133,12 @@ public class InAppWebViewManager extends ChannelDelegateImpl {
result.success(false); result.success(false);
} }
break; break;
case "disableWebView":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WebView.disableWebView();
}
result.success(true);
break;
case "disposeKeepAlive": case "disposeKeepAlive":
final String keepAliveId = (String) call.argument("keepAliveId"); final String keepAliveId = (String) call.argument("keepAliveId");
if (keepAliveId != null) { if (keepAliveId != null) {

View File

@ -43,7 +43,9 @@ import com.pichillilorenzo.flutter_inappwebview.types.SslCertificateExt;
import com.pichillilorenzo.flutter_inappwebview.types.SyncBaseCallbackResultImpl; import com.pichillilorenzo.flutter_inappwebview.types.SyncBaseCallbackResultImpl;
import com.pichillilorenzo.flutter_inappwebview.types.URLRequest; import com.pichillilorenzo.flutter_inappwebview.types.URLRequest;
import com.pichillilorenzo.flutter_inappwebview.types.UserScript; import com.pichillilorenzo.flutter_inappwebview.types.UserScript;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessageCompatExt;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessagePort; import com.pichillilorenzo.flutter_inappwebview.types.WebMessagePort;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessagePortCompatExt;
import com.pichillilorenzo.flutter_inappwebview.types.WebResourceErrorExt; import com.pichillilorenzo.flutter_inappwebview.types.WebResourceErrorExt;
import com.pichillilorenzo.flutter_inappwebview.types.WebResourceRequestExt; import com.pichillilorenzo.flutter_inappwebview.types.WebResourceRequestExt;
import com.pichillilorenzo.flutter_inappwebview.types.WebResourceResponseExt; import com.pichillilorenzo.flutter_inappwebview.types.WebResourceResponseExt;
@ -141,8 +143,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(value); result.success(value);
} }
}); });
} } else {
else {
result.success(null); result.success(null);
} }
break; break;
@ -210,8 +211,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
if (webView != null) { if (webView != null) {
Map<String, Object> screenshotConfiguration = (Map<String, Object>) call.argument("screenshotConfiguration"); Map<String, Object> screenshotConfiguration = (Map<String, Object>) call.argument("screenshotConfiguration");
webView.takeScreenshot(screenshotConfiguration, result); webView.takeScreenshot(screenshotConfiguration, result);
} } else
else
result.success(null); result.success(null);
break; break;
case setSettings: case setSettings:
@ -282,8 +282,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(success); result.success(success);
} }
}); });
} } else {
else {
result.success(false); result.success(false);
} }
break; break;
@ -568,8 +567,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(value); result.success(value);
} }
}); });
} } else {
else {
result.success(null); result.success(null);
} }
break; break;
@ -598,27 +596,33 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
break; break;
case postWebMessage: case postWebMessage:
if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.POST_WEB_MESSAGE)) { if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.POST_WEB_MESSAGE)) {
Map<String, Object> message = (Map<String, Object>) call.argument("message"); WebMessageCompatExt message = WebMessageCompatExt.fromMap((Map<String, Object>) call.argument("message"));
String targetOrigin = (String) call.argument("targetOrigin"); String targetOrigin = (String) call.argument("targetOrigin");
List<WebMessagePortCompat> compatPorts = new ArrayList<>(); List<WebMessagePortCompat> compatPorts = new ArrayList<>();
List<WebMessagePort> ports = new ArrayList<>(); List<WebMessagePortCompatExt> portsExt = message.getPorts();
List<Map<String, Object>> portsMap = (List<Map<String, Object>>) message.get("ports"); if (portsExt != null) {
if (portsMap != null) { for (WebMessagePortCompatExt portExt : portsExt) {
for (Map<String, Object> portMap : portsMap) { WebMessageChannel webMessageChannel = webView.getWebMessageChannels().get(portExt.getWebMessageChannelId());
String webMessageChannelId = (String) portMap.get("webMessageChannelId");
Integer index = (Integer) portMap.get("index");
WebMessageChannel webMessageChannel = webView.getWebMessageChannels().get(webMessageChannelId);
if (webMessageChannel != null) { if (webMessageChannel != null) {
if (webView instanceof InAppWebView) { if (webView instanceof InAppWebView) {
compatPorts.add(webMessageChannel.compatPorts.get(index)); compatPorts.add(webMessageChannel.compatPorts.get(portExt.getIndex()));
} }
} }
} }
} }
Object data = message.getData();
if (webView instanceof InAppWebView) { if (webView instanceof InAppWebView) {
WebMessageCompat webMessage = new WebMessageCompat((String) message.get("data"), compatPorts.toArray(new WebMessagePortCompat[0]));
try { try {
WebViewCompat.postWebMessage((WebView) webView, webMessage, Uri.parse(targetOrigin)); if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER) && data != null &&
message.getType() == WebMessageCompat.TYPE_ARRAY_BUFFER) {
WebViewCompat.postWebMessage((WebView) webView,
new WebMessageCompat((byte[]) data, compatPorts.toArray(new WebMessagePortCompat[0])),
Uri.parse(targetOrigin));
} else {
WebViewCompat.postWebMessage((WebView) webView,
new WebMessageCompat(data != null ? data.toString() : null, compatPorts.toArray(new WebMessagePortCompat[0])),
Uri.parse(targetOrigin));
}
result.success(true); result.success(true);
} catch (Exception e) { } catch (Exception e) {
result.error(LOG_TAG, e.getMessage(), null); result.error(LOG_TAG, e.getMessage(), null);
@ -671,8 +675,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
} }
/** /**
* @deprecated * @deprecated Use {@link FindInteractionChannelDelegate#onFindResultReceived} instead.
* Use {@link FindInteractionChannelDelegate#onFindResultReceived} instead.
*/ */
@Deprecated @Deprecated
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) { public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {

View File

@ -11,6 +11,8 @@ import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.JavaScriptBridgeJS; import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.JavaScriptBridgeJS;
import com.pichillilorenzo.flutter_inappwebview.types.Disposable; import com.pichillilorenzo.flutter_inappwebview.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessageCompatExt;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessagePortCompatExt;
import com.pichillilorenzo.flutter_inappwebview.webview.InAppWebViewInterface; import com.pichillilorenzo.flutter_inappwebview.webview.InAppWebViewInterface;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessagePort; import com.pichillilorenzo.flutter_inappwebview.types.WebMessagePort;
import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.InAppWebView; import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.InAppWebView;
@ -77,7 +79,7 @@ public class WebMessageChannel implements Disposable {
@Override @Override
public void onMessage(@NonNull WebMessagePortCompat port, @Nullable WebMessageCompat message) { public void onMessage(@NonNull WebMessagePortCompat port, @Nullable WebMessageCompat message) {
super.onMessage(port, message); super.onMessage(port, message);
webMessageChannel.onMessage(index, message != null ? message.getData() : null); webMessageChannel.onMessage(index, message != null ? WebMessageCompatExt.fromMapWebMessageCompat(message) : null);
} }
}); });
result.success(true); result.success(true);
@ -89,25 +91,28 @@ public class WebMessageChannel implements Disposable {
} }
} }
public void postMessageForInAppWebView(final Integer index, Map<String, Object> message, @NonNull MethodChannel.Result result) { public void postMessageForInAppWebView(final Integer index, @NonNull WebMessageCompatExt message, @NonNull MethodChannel.Result result) {
if (webView != null && compatPorts.size() > 0 && if (webView != null && compatPorts.size() > 0 &&
WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_PORT_POST_MESSAGE)) { WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_PORT_POST_MESSAGE)) {
WebMessagePortCompat port = compatPorts.get(index); WebMessagePortCompat port = compatPorts.get(index);
List<WebMessagePortCompat> webMessagePorts = new ArrayList<>(); List<WebMessagePortCompat> webMessagePorts = new ArrayList<>();
List<Map<String, Object>> portsMap = (List<Map<String, Object>>) message.get("ports"); List<WebMessagePortCompatExt> portsExt = message.getPorts();
if (portsMap != null) { if (portsExt != null) {
for (Map<String, Object> portMap : portsMap) { for (WebMessagePortCompatExt portExt : portsExt) {
String webMessageChannelId = (String) portMap.get("webMessageChannelId"); WebMessageChannel webMessageChannel = webView.getWebMessageChannels().get(portExt.getWebMessageChannelId());
Integer portIndex = (Integer) portMap.get("index");
WebMessageChannel webMessageChannel = webView.getWebMessageChannels().get(webMessageChannelId);
if (webMessageChannel != null) { if (webMessageChannel != null) {
webMessagePorts.add(webMessageChannel.compatPorts.get(portIndex)); webMessagePorts.add(webMessageChannel.compatPorts.get(portExt.getIndex()));
} }
} }
} }
WebMessageCompat webMessage = new WebMessageCompat((String) message.get("data"), webMessagePorts.toArray(new WebMessagePortCompat[0])); Object data = message.getData();
try { try {
port.postMessage(webMessage); if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER) && data != null &&
message.getType() == WebMessageCompat.TYPE_ARRAY_BUFFER) {
port.postMessage(new WebMessageCompat((byte[]) data, webMessagePorts.toArray(new WebMessagePortCompat[0])));
} else {
port.postMessage(new WebMessageCompat(data != null ? data.toString() : null, webMessagePorts.toArray(new WebMessagePortCompat[0])));
}
result.success(true); result.success(true);
} catch (Exception e) { } catch (Exception e) {
result.error(LOG_TAG, e.getMessage(), null); result.error(LOG_TAG, e.getMessage(), null);
@ -132,7 +137,7 @@ public class WebMessageChannel implements Disposable {
} }
} }
public void onMessage(int index, String message) { public void onMessage(int index, @Nullable WebMessageCompatExt message) {
if (channelDelegate != null) { if (channelDelegate != null) {
channelDelegate.onMessage(index, message); channelDelegate.onMessage(index, message);
} }

View File

@ -5,6 +5,7 @@ import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl; import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview.types.Disposable; import com.pichillilorenzo.flutter_inappwebview.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessageCompatExt;
import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.InAppWebView; import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.InAppWebView;
import java.util.HashMap; import java.util.HashMap;
@ -36,7 +37,7 @@ public class WebMessageChannelChannelDelegate extends ChannelDelegateImpl {
case "postMessage": case "postMessage":
if (webMessageChannel != null && webMessageChannel.webView instanceof InAppWebView) { if (webMessageChannel != null && webMessageChannel.webView instanceof InAppWebView) {
final Integer index = (Integer) call.argument("index"); final Integer index = (Integer) call.argument("index");
Map<String, Object> message = (Map<String, Object>) call.argument("message"); WebMessageCompatExt message = WebMessageCompatExt.fromMap((Map<String, Object>) call.argument("message"));
webMessageChannel.postMessageForInAppWebView(index, message, result); webMessageChannel.postMessageForInAppWebView(index, message, result);
} else { } else {
result.success(false); result.success(false);
@ -55,12 +56,12 @@ public class WebMessageChannelChannelDelegate extends ChannelDelegateImpl {
} }
} }
public void onMessage(int index, String message) { public void onMessage(int index, @Nullable WebMessageCompatExt message) {
MethodChannel channel = getChannel(); MethodChannel channel = getChannel();
if (channel == null) return; if (channel == null) return;
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
obj.put("index", index); obj.put("index", index);
obj.put("message", message ); obj.put("message", message != null ? message.toMap() : null);
channel.invokeMethod("onMessage", obj); channel.invokeMethod("onMessage", obj);
} }

View File

@ -14,6 +14,7 @@ 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;
import com.pichillilorenzo.flutter_inappwebview.types.Disposable; import com.pichillilorenzo.flutter_inappwebview.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessageCompatExt;
import com.pichillilorenzo.flutter_inappwebview.webview.InAppWebViewInterface; import com.pichillilorenzo.flutter_inappwebview.webview.InAppWebViewInterface;
import com.pichillilorenzo.flutter_inappwebview.types.PluginScript; import com.pichillilorenzo.flutter_inappwebview.types.PluginScript;
import com.pichillilorenzo.flutter_inappwebview.types.UserScriptInjectionTime; import com.pichillilorenzo.flutter_inappwebview.types.UserScriptInjectionTime;
@ -61,7 +62,7 @@ public class WebMessageListener implements Disposable {
boolean isMainFrame, @NonNull JavaScriptReplyProxy javaScriptReplyProxy) { boolean isMainFrame, @NonNull JavaScriptReplyProxy javaScriptReplyProxy) {
replyProxy = javaScriptReplyProxy; replyProxy = javaScriptReplyProxy;
if (channelDelegate != null) { if (channelDelegate != null) {
channelDelegate.onPostMessage(message.getData(), channelDelegate.onPostMessage(WebMessageCompatExt.fromMapWebMessageCompat(message),
sourceOrigin.toString().equals("null") ? null : sourceOrigin.toString(), sourceOrigin.toString().equals("null") ? null : sourceOrigin.toString(),
isMainFrame); isMainFrame);
} }
@ -171,9 +172,16 @@ public class WebMessageListener implements Disposable {
} }
} }
public void postMessageForInAppWebView(String message, @NonNull MethodChannel.Result result) { public void postMessageForInAppWebView(WebMessageCompatExt message, @NonNull MethodChannel.Result result) {
if (replyProxy != null && WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) { if (replyProxy != null && WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
replyProxy.postMessage(message); Object data = message.getData();
if (data != null) {
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER) && message.getType() == WebMessageCompat.TYPE_ARRAY_BUFFER) {
replyProxy.postMessage((byte[]) data);
} else {
replyProxy.postMessage(data.toString());
}
}
} }
result.success(true); result.success(true);
} }
@ -196,12 +204,14 @@ public class WebMessageListener implements Disposable {
if (rule.getHost() != null && rule.getHost().startsWith("[")) { if (rule.getHost() != null && rule.getHost().startsWith("[")) {
try { try {
IPv6 = Util.normalizeIPv6(rule.getHost().substring(1, rule.getHost().length() - 1)); IPv6 = Util.normalizeIPv6(rule.getHost().substring(1, rule.getHost().length() - 1));
} catch (Exception ignored) {} } catch (Exception ignored) {
}
} }
String hostIPv6 = null; String hostIPv6 = null;
try { try {
hostIPv6 = Util.normalizeIPv6(host); hostIPv6 = Util.normalizeIPv6(host);
} catch (Exception ignored) {} } catch (Exception ignored) {
}
boolean schemeAllowed = rule.getScheme().equals(scheme); boolean schemeAllowed = rule.getScheme().equals(scheme);

View File

@ -2,10 +2,12 @@ package com.pichillilorenzo.flutter_inappwebview.webview.web_message;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.webkit.WebMessageCompat;
import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebView; import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebView;
import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl; import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview.types.Disposable; import com.pichillilorenzo.flutter_inappwebview.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview.types.WebMessageCompatExt;
import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.InAppWebView; import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.InAppWebView;
import java.util.HashMap; import java.util.HashMap;
@ -28,7 +30,7 @@ public class WebMessageListenerChannelDelegate extends ChannelDelegateImpl {
switch (call.method) { switch (call.method) {
case "postMessage": case "postMessage":
if (webMessageListener != null && webMessageListener.webView instanceof InAppWebView) { if (webMessageListener != null && webMessageListener.webView instanceof InAppWebView) {
String message = (String) call.argument("message"); WebMessageCompatExt message = WebMessageCompatExt.fromMap((Map<String, Object>) call.argument("message"));
webMessageListener.postMessageForInAppWebView(message, result); webMessageListener.postMessageForInAppWebView(message, result);
} else { } else {
result.success(false); result.success(false);
@ -39,11 +41,11 @@ public class WebMessageListenerChannelDelegate extends ChannelDelegateImpl {
} }
} }
public void onPostMessage(String message, String sourceOrigin, boolean isMainFrame) { public void onPostMessage(@Nullable WebMessageCompatExt message, String sourceOrigin, boolean isMainFrame) {
MethodChannel channel = getChannel(); MethodChannel channel = getChannel();
if (channel == null) return; if (channel == null) return;
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
obj.put("message", message); obj.put("message", message != null ? message.toMap() : null);
obj.put("sourceOrigin", sourceOrigin); obj.put("sourceOrigin", sourceOrigin);
obj.put("isMainFrame", isMainFrame); obj.put("isMainFrame", isMainFrame);
channel.invokeMethod("onPostMessage", obj); channel.invokeMethod("onPostMessage", obj);

View File

@ -1,3 +1,7 @@
## 1.1.1
- Added `ExchangeableObject.fromMapForceAllInline`.
## 1.1.0 ## 1.1.0
- Added `ExchangeableObject.copyMethod`. - Added `ExchangeableObject.copyMethod`.

View File

@ -2,6 +2,7 @@ class ExchangeableObject {
final bool toMapMethod; final bool toMapMethod;
final bool toJsonMethod; final bool toJsonMethod;
final bool fromMapFactory; final bool fromMapFactory;
final bool fromMapForceAllInline;
final bool nullableFromMapFactory; final bool nullableFromMapFactory;
final bool toStringMethod; final bool toStringMethod;
final bool copyMethod; final bool copyMethod;
@ -10,6 +11,7 @@ class ExchangeableObject {
this.toMapMethod = true, this.toMapMethod = true,
this.toJsonMethod = true, this.toJsonMethod = true,
this.fromMapFactory = true, this.fromMapFactory = true,
this.fromMapForceAllInline = false,
this.nullableFromMapFactory = true, this.nullableFromMapFactory = true,
this.toStringMethod = true, this.toStringMethod = true,
this.copyMethod = false this.copyMethod = false

View File

@ -1,10 +1,10 @@
name: flutter_inappwebview_internal_annotations name: flutter_inappwebview_internal_annotations
description: Internal annotations used by the generator of flutter_inappwebview plugin description: Internal annotations used by the generator of flutter_inappwebview plugin
version: 1.1.0 version: 1.1.1
homepage: https://github.com/pichillilorenzo/flutter_inappwebview homepage: https://github.com/pichillilorenzo/flutter_inappwebview
environment: environment:
sdk: ">=2.14.0 <3.0.0" sdk: ">=2.15.0 <4.0.0"
dev_dependencies: dev_dependencies:
test: ^1.21.6 test: ^1.21.6

View File

@ -358,7 +358,7 @@ class ExchangeableObjectGenerator
constructorParameter.isFinal || fieldElement.isFinal || constructorParameter.isFinal || fieldElement.isFinal ||
!Util.typeIsNullable(constructorParameter.type)) && !Util.typeIsNullable(constructorParameter.type)) &&
!constructorParameter.hasDefaultValue; !constructorParameter.hasDefaultValue;
if (isRequiredParameter || fieldElement.isFinal) { if (isRequiredParameter || fieldElement.isFinal || annotation.read("fromMapForceAllInline").boolValue) {
requiredFields.add('$fieldName: $value,'); requiredFields.add('$fieldName: $value,');
} else { } else {
nonRequiredFields.add("instance.$fieldName = $value;"); nonRequiredFields.add("instance.$fieldName = $value;");

View File

@ -209,11 +209,10 @@ packages:
flutter_inappwebview_internal_annotations: flutter_inappwebview_internal_annotations:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_inappwebview_internal_annotations path: "../flutter_inappwebview_internal_annotations"
sha256: "064a8ccbc76217dcd3b0fd6c6ea6f549e69b2849a0233b5bb46af9632c3ce2ff" relative: true
url: "https://pub.dev" source: path
source: hosted version: "1.1.1"
version: "1.1.0"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:

View File

@ -12,7 +12,7 @@ dependencies:
sdk: flutter sdk: flutter
build: ^2.4.0 build: ^2.4.0
source_gen: ^1.3.1 source_gen: ^1.3.1
flutter_inappwebview_internal_annotations: ^1.1.0 flutter_inappwebview_internal_annotations: ^1.1.1
dev_dependencies: dev_dependencies:
build_runner: ^2.4.2 build_runner: ^2.4.2

View File

@ -10,7 +10,8 @@ void webMessage() {
].contains(defaultTargetPlatform); ].contains(defaultTargetPlatform);
skippableGroup('WebMessage', () { skippableGroup('WebMessage', () {
skippableTestWidgets('WebMessageChannel', (WidgetTester tester) async { skippableTestWidgets('WebMessageChannel post String',
(WidgetTester tester) async {
final Completer<InAppWebViewController> controllerCompleter = final Completer<InAppWebViewController> controllerCompleter =
Completer<InAppWebViewController>(); Completer<InAppWebViewController>();
final Completer webMessageCompleter = Completer<String>(); final Completer webMessageCompleter = Completer<String>();
@ -61,7 +62,7 @@ void webMessage() {
await port1.setWebMessageCallback((message) async { await port1.setWebMessageCallback((message) async {
await port1 await port1
.postMessage(WebMessage(data: message! + " and back")); .postMessage(WebMessage(data: message!.data + " and back"));
}); });
await controller.postWebMessage( await controller.postWebMessage(
message: WebMessage(data: "capturePort", ports: [port2]), message: WebMessage(data: "capturePort", ports: [port2]),
@ -78,7 +79,94 @@ void webMessage() {
expect(message, 'JavaScript To Native and back'); expect(message, 'JavaScript To Native and back');
}); });
skippableTestWidgets('WebMessageListener', (WidgetTester tester) async { skippableTestWidgets('WebMessageChannel post ArrayBuffer',
(WidgetTester tester) async {
final Completer<InAppWebViewController> controllerCompleter =
Completer<InAppWebViewController>();
final Completer webMessageCompleter = Completer<String>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: InAppWebView(
key: GlobalKey(),
initialData: InAppWebViewInitialData(data: """
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebMessageChannel Test</title>
</head>
<body>
<button id="button" onclick="port.postMessage(stringToBuffer(input.value));" />Send</button>
<br />
<input id="input" type="text" value="JavaScript To Native" />
<script>
function bufferToString(buffer) {
return String.fromCharCode.apply(null, Array.from(new Uint8Array(buffer)));
}
function stringToBuffer(value) {
var buffer = new ArrayBuffer(value.length);
var view = new Uint8Array(buffer);
for (var i = 0, length = value.length; i < length; i++) {
view[i] = value.charCodeAt(i);
}
return buffer;
}
var port;
window.addEventListener('message', function(event) {
if (bufferToString(event.data) == 'capturePort') {
if (event.ports[0] != null) {
port = event.ports[0];
port.onmessage = function (event) {
console.log(event.data);
};
}
}
}, false);
</script>
</body>
</html>
"""),
onWebViewCreated: (controller) {
controllerCompleter.complete(controller);
},
onConsoleMessage: (controller, consoleMessage) {
webMessageCompleter.complete(consoleMessage.message);
},
onLoadStop: (controller, url) async {
var webMessageChannel =
await controller.createWebMessageChannel();
var port1 = webMessageChannel!.port1;
var port2 = webMessageChannel.port2;
await port1.setWebMessageCallback((message) async {
await port1.postMessage(WebMessage(
data: utf8.encode(utf8.decode(message!.data) + " and back"),
type: WebMessageType.ARRAY_BUFFER));
});
await controller.postWebMessage(
message: WebMessage(
data: utf8.encode("capturePort"),
type: WebMessageType.ARRAY_BUFFER,
ports: [port2]),
targetOrigin: WebUri("*"));
await controller.evaluateJavascript(
source: "document.getElementById('button').click();");
},
),
),
);
await controllerCompleter.future;
final String message = await webMessageCompleter.future;
expect(message, 'JavaScript To Native and back');
});
skippableTestWidgets('WebMessageListener post String',
(WidgetTester tester) async {
final Completer<InAppWebViewController> controllerCompleter = final Completer<InAppWebViewController> controllerCompleter =
Completer<InAppWebViewController>(); Completer<InAppWebViewController>();
final Completer<void> pageLoaded = Completer<void>(); final Completer<void> pageLoaded = Completer<void>();
@ -97,9 +185,10 @@ void webMessage() {
if (isMainFrame && if (isMainFrame &&
(sourceOrigin.toString() + '/') == (sourceOrigin.toString() + '/') ==
TEST_URL_EXAMPLE.toString()) { TEST_URL_EXAMPLE.toString()) {
replyProxy.postMessage(message! + " and back"); replyProxy.postMessage(
WebMessage(data: message!.data + " and back"));
} else { } else {
replyProxy.postMessage("Nope"); replyProxy.postMessage(WebMessage(data: "Nope"));
} }
}, },
)); ));
@ -130,5 +219,77 @@ void webMessage() {
final String message = await webMessageCompleter.future; final String message = await webMessageCompleter.future;
expect(message, 'JavaScript To Native and back'); expect(message, 'JavaScript To Native and back');
}); });
skippableTestWidgets('WebMessageListener post ArrayBuffer',
(WidgetTester tester) async {
final Completer<InAppWebViewController> controllerCompleter =
Completer<InAppWebViewController>();
final Completer<void> pageLoaded = Completer<void>();
final Completer webMessageCompleter = Completer<String>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: InAppWebView(
key: GlobalKey(),
onWebViewCreated: (controller) async {
await controller.addWebMessageListener(WebMessageListener(
jsObjectName: "myTestObj",
allowedOriginRules: Set.from(["https://*.example.com"]),
onPostMessage:
(message, sourceOrigin, isMainFrame, replyProxy) {
if (isMainFrame &&
(sourceOrigin.toString() + '/') ==
TEST_URL_EXAMPLE.toString()) {
replyProxy.postMessage(WebMessage(
data: utf8
.encode(utf8.decode(message!.data) + " and back"),
type: WebMessageType.ARRAY_BUFFER));
} else {
replyProxy.postMessage(WebMessage(
data: utf8.encode("Nope"),
type: WebMessageType.ARRAY_BUFFER));
}
},
));
controllerCompleter.complete(controller);
},
onConsoleMessage: (controller, consoleMessage) {
webMessageCompleter.complete(consoleMessage.message);
},
onLoadStop: (controller, url) async {
if (url.toString() == TEST_URL_EXAMPLE.toString()) {
pageLoaded.complete();
}
},
),
),
);
final controller = await controllerCompleter.future;
await controller.loadUrl(urlRequest: URLRequest(url: TEST_URL_EXAMPLE));
await pageLoaded.future;
await controller.evaluateJavascript(source: """
function bufferToString(buffer) {
return String.fromCharCode.apply(null, Array.from(new Uint8Array(buffer)));
}
function stringToBuffer(value) {
var buffer = new ArrayBuffer(value.length);
var view = new Uint8Array(buffer);
for (var i = 0, length = value.length; i < length; i++) {
view[i] = value.charCodeAt(i);
}
return buffer;
}
myTestObj.addEventListener('message', function(event) {
console.log(bufferToString(event.data));
});
myTestObj.postMessage(stringToBuffer('JavaScript To Native'));
""");
final String message = await webMessageCompleter.future;
expect(message, 'JavaScript To Native and back');
});
}, skip: shouldSkip); }, skip: shouldSkip);
} }

View File

@ -0,0 +1,38 @@
part of 'main.dart';
void apply() {
final shouldSkip = kIsWeb
? true
: ![
TargetPlatform.android,
].contains(defaultTargetPlatform);
skippableTestWidgets('apply', (WidgetTester tester) async {
await expectLater(
ProcessGlobalConfig.instance().apply(
settings: ProcessGlobalConfigSettings(
dataDirectorySuffix:
(await WebViewFeature.isStartupFeatureSupported(
WebViewFeature
.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX))
? 'suffix_inappwebviewexample'
: null,
directoryBasePaths:
(await WebViewFeature.isStartupFeatureSupported(
WebViewFeature
.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS))
? ProcessGlobalConfigDirectoryBasePaths(
cacheDirectoryBasePath:
(await getApplicationDocumentsDirectory())
.absolute
.path +
'/inappwebviewexample/cache',
dataDirectoryBasePath:
(await getApplicationDocumentsDirectory())
.absolute
.path +
'/inappwebviewexample/data')
: null)),
completes);
}, skip: shouldSkip);
}

View File

@ -0,0 +1,20 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:path_provider/path_provider.dart';
import '../constants.dart';
import '../env.dart';
import '../util.dart';
part 'apply.dart';
void main() {
final shouldSkip = kIsWeb;
skippableGroup('Process Global Config', () {
apply();
}, skip: shouldSkip);
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'process_global_config/main.dart' as process_global_config_tests;
import 'in_app_webview/main.dart' as in_app_webview_tests; import 'in_app_webview/main.dart' as in_app_webview_tests;
import 'find_interaction_controller/main.dart' import 'find_interaction_controller/main.dart'
as find_interaction_controller_tests; as find_interaction_controller_tests;
@ -34,6 +35,7 @@ void main() {
FindInteractionController.debugLoggingSettings.usePrint = true; FindInteractionController.debugLoggingSettings.usePrint = true;
FindInteractionController.debugLoggingSettings.maxLogMessageLength = 7000; FindInteractionController.debugLoggingSettings.maxLogMessageLength = 7000;
process_global_config_tests.main();
in_app_webview_tests.main(); in_app_webview_tests.main();
find_interaction_controller_tests.main(); find_interaction_controller_tests.main();
service_worker_controller_tests.main(); service_worker_controller_tests.main();

View File

@ -2887,14 +2887,22 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
let body = message.body as! [String: Any?] let body = message.body as! [String: Any?]
let webMessageChannelId = body["webMessageChannelId"] as! String let webMessageChannelId = body["webMessageChannelId"] as! String
let index = body["index"] as! Int64 let index = body["index"] as! Int64
let webMessage = body["message"] as? String var webMessage: WebMessage? = nil
if let webMessageMap = body["message"] as? [String : Any?] {
webMessage = WebMessage.fromMap(map: webMessageMap)
}
if let webMessageChannel = webMessageChannels[webMessageChannelId] { if let webMessageChannel = webMessageChannels[webMessageChannelId] {
webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage) webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage)
} }
} else if message.name == "onWebMessageListenerPostMessageReceived" { } else if message.name == "onWebMessageListenerPostMessageReceived" {
let body = message.body as! [String: Any?] let body = message.body as! [String: Any?]
let jsObjectName = body["jsObjectName"] as! String let jsObjectName = body["jsObjectName"] as! String
let messageData = body["message"] as? String var webMessage: WebMessage? = nil
if let webMessageMap = body["message"] as? [String : Any?] {
webMessage = WebMessage.fromMap(map: webMessageMap)
}
if let webMessageListener = webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) { if let webMessageListener = webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) {
let isMainFrame = message.frameInfo.isMainFrame let isMainFrame = message.frameInfo.isMainFrame
@ -2920,7 +2928,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
if let scheme = scheme, !scheme.isEmpty, let host = host, !host.isEmpty { if let scheme = scheme, !scheme.isEmpty, let host = host, !host.isEmpty {
sourceOrigin = URL(string: "\(scheme)://\(host)\(port != nil && port != 0 ? ":" + String(port!) : "")") sourceOrigin = URL(string: "\(scheme)://\(host)\(port != nil && port != 0 ? ":" + String(port!) : "")")
} }
webMessageListener.channelDelegate?.onPostMessage(message: messageData, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame) webMessageListener.channelDelegate?.onPostMessage(message: webMessage, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame)
} }
} }
} }
@ -3190,11 +3198,11 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
} }
portsString = "[" + portArrayString.joined(separator: ", ") + "]" portsString = "[" + portArrayString.joined(separator: ", ") + "]"
} }
let data = message.data?.replacingOccurrences(of: "\'", with: "\\'") ?? "null"
let url = URL(string: targetOrigin)?.absoluteString ?? "*" let url = URL(string: targetOrigin)?.absoluteString ?? "*"
let source = """ let source = """
(function() { (function() {
window.postMessage('\(data)', '\(url)', \(portsString)); window.postMessage(\(message.jsData), '\(url)', \(portsString));
})(); })();
""" """
evaluateJavascript(source: source, completionHandler: completionHandler) evaluateJavascript(source: source, completionHandler: completionHandler)

View File

@ -25,8 +25,8 @@ public class WebMessageChannel : FlutterMethodCallDelegate {
self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel)
} }
self.ports = [ self.ports = [
WebMessagePort(name: "port1", webMessageChannel: self), WebMessagePort(name: "port1", index: 0, webMessageChannelId: self.id, webMessageChannel: self),
WebMessagePort(name: "port2", webMessageChannel: self) WebMessagePort(name: "port2", index: 1, webMessageChannelId: self.id, webMessageChannel: self)
] ]
} }

View File

@ -39,22 +39,20 @@ public class WebMessageChannelChannelDelegate : ChannelDelegate {
if let webView = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 { if let webView = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 {
let index = arguments!["index"] as! Int let index = arguments!["index"] as! Int
let port = ports[index] let port = ports[index]
let message = arguments!["message"] as! [String: Any?] var message = WebMessage.fromMap(map: arguments!["message"] as! [String: Any?])
var webMessagePorts: [WebMessagePort] = [] var ports: [WebMessagePort] = []
let portsMap = message["ports"] as? [[String: Any?]] if let notConnectedPorts = message.ports {
if let portsMap = portsMap { for notConnectedPort in notConnectedPorts {
for portMap in portsMap { if let webMessageChannel = webView.webMessageChannels[notConnectedPort.webMessageChannelId] {
let webMessageChannelId = portMap["webMessageChannelId"] as! String ports.append(webMessageChannel.ports[Int(notConnectedPort.index)])
let index = portMap["index"] as! Int
if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] {
webMessagePorts.append(webMessageChannel.ports[index])
} }
} }
} }
let webMessage = WebMessage(data: message["data"] as? String, ports: webMessagePorts) message.ports = ports
do { do {
try port.postMessage(message: webMessage) { (_) in try port.postMessage(message: message) { (_) in
result(true) result(true)
} }
} catch let error as NSError { } catch let error as NSError {
@ -85,10 +83,10 @@ public class WebMessageChannelChannelDelegate : ChannelDelegate {
} }
} }
public func onMessage(index: Int64, message: String?) { public func onMessage(index: Int64, message: WebMessage?) {
let arguments: [String:Any?] = [ let arguments: [String:Any?] = [
"index": index, "index": index,
"message": message "message": message?.toMap()
] ]
channel?.invokeMethod("onMessage", arguments: arguments) channel?.invokeMethod("onMessage", arguments: arguments)
} }

View File

@ -22,12 +22,13 @@ public class WebMessageListenerChannelDelegate : ChannelDelegate {
case "postMessage": case "postMessage":
if let webView = webMessageListener?.webView, let jsObjectName = webMessageListener?.jsObjectName { if let webView = webMessageListener?.webView, let jsObjectName = webMessageListener?.jsObjectName {
let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'") let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
let messageEscaped = (arguments!["message"] as! String).replacingOccurrences(of: "\'", with: "\\'") let message = WebMessage.fromMap(map: arguments!["message"] as! [String: Any?])
let source = """ let source = """
(function() { (function() {
var webMessageListener = window['\(jsObjectNameEscaped)']; var webMessageListener = window['\(jsObjectNameEscaped)'];
if (webMessageListener != null) { if (webMessageListener != null) {
var event = {data: '\(messageEscaped)'}; var event = {data: \(message.jsData)};
if (webMessageListener.onmessage != null) { if (webMessageListener.onmessage != null) {
webMessageListener.onmessage(event); webMessageListener.onmessage(event);
} }
@ -50,9 +51,9 @@ public class WebMessageListenerChannelDelegate : ChannelDelegate {
} }
} }
public func onPostMessage(message: String?, sourceOrigin: URL?, isMainFrame: Bool) { public func onPostMessage(message: WebMessage?, sourceOrigin: URL?, isMainFrame: Bool) {
let arguments: [String:Any?] = [ let arguments: [String:Any?] = [
"message": message, "message": message?.toMap(),
"sourceOrigin": sourceOrigin?.absoluteString, "sourceOrigin": sourceOrigin?.absoluteString,
"isMainFrame": isMainFrame "isMainFrame": isMainFrame
] ]

View File

@ -509,23 +509,21 @@ public class WebViewChannelDelegate : ChannelDelegate {
break break
case .postWebMessage: case .postWebMessage:
if let webView = webView { if let webView = webView {
let message = arguments!["message"] as! [String: Any?] var message = WebMessage.fromMap(map: arguments!["message"] as! [String: Any?])
let targetOrigin = arguments!["targetOrigin"] as! String let targetOrigin = arguments!["targetOrigin"] as! String
var ports: [WebMessagePort] = [] var ports: [WebMessagePort] = []
let portsMap = message["ports"] as? [[String: Any?]] if let notConnectedPorts = message.ports {
if let portsMap = portsMap { for notConnectedPort in notConnectedPorts {
for portMap in portsMap { if let webMessageChannel = webView.webMessageChannels[notConnectedPort.webMessageChannelId] {
let webMessageChannelId = portMap["webMessageChannelId"] as! String ports.append(webMessageChannel.ports[Int(notConnectedPort.index)])
let index = portMap["index"] as! Int
if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] {
ports.append(webMessageChannel.ports[index])
} }
} }
} }
let webMessage = WebMessage(data: message["data"] as? String, ports: ports) message.ports = ports
do { do {
try webView.postWebMessage(message: webMessage, targetOrigin: targetOrigin) { (_) in try webView.postWebMessage(message: message, targetOrigin: targetOrigin) { (_) in
result(true) result(true)
} }
} catch let error as NSError { } catch let error as NSError {

View File

@ -13,7 +13,11 @@ function FlutterInAppWebViewWebMessageListener(jsObjectName) {
this.listeners = []; this.listeners = [];
this.onmessage = null; this.onmessage = null;
} }
FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(message) { FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(data) {
var message = {
"data": window.ArrayBuffer != null && data instanceof ArrayBuffer ? Array.from(new Uint8Array(data)) : (data != null ? data.toString() : null),
"type": window.ArrayBuffer != null && data instanceof ArrayBuffer ? 1 : 0
};
window.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message}); window.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message});
}; };
FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) { FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) {

View File

@ -7,16 +7,53 @@
import Foundation import Foundation
public class WebMessage : NSObject { public class WebMessage : NSObject, Disposable {
var data: String? var data: Any?
var type: WebMessageType
var ports: [WebMessagePort]? var ports: [WebMessagePort]?
public init(data: String?, ports: [WebMessagePort]?) { var jsData: String {
var jsData: String = "null"
if let messageData = data {
if type == .arrayBuffer, let messageDataArrayBuffer = messageData as? FlutterStandardTypedData {
jsData = "new Uint8Array(\(Array(messageDataArrayBuffer.data))).buffer"
} else if let messageDataString = messageData as? String {
jsData = "'\(messageDataString.replacingOccurrences(of: "\'", with: "\\'"))'"
}
}
return jsData
}
public init(data: Any?, type: WebMessageType, ports: [WebMessagePort]?) {
self.type = type
super.init() super.init()
self.data = data self.data = data
self.ports = ports self.ports = ports
} }
public static func fromMap(map: [String: Any?]) -> WebMessage {
let portMapList = map["ports"] as? [[String: Any?]]
var ports: [WebMessagePort]? = nil
if let portMapList = portMapList, !portMapList.isEmpty {
ports = []
portMapList.forEach { (portMap) in
ports?.append(WebMessagePort.fromMap(map: portMap))
}
}
return WebMessage(
data: map["data"] as? Any,
type: WebMessageType.init(rawValue: map["type"] as! Int)!,
ports: ports)
}
public func toMap () -> [String: Any?] {
return [
"data": type == .arrayBuffer && data is [UInt8] ? Data(data as! [UInt8]) : data,
"type": type.rawValue
]
}
public func dispose() { public func dispose() {
ports?.removeAll() ports?.removeAll()
} }
@ -26,3 +63,8 @@ public class WebMessage : NSObject {
dispose() dispose()
} }
} }
public enum WebMessageType: Int {
case string = 0
case arrayBuffer = 1
}

View File

@ -9,13 +9,17 @@ import Foundation
public class WebMessagePort : NSObject { public class WebMessagePort : NSObject {
var name: String var name: String
var index: Int64
var webMessageChannelId: String
var webMessageChannel: WebMessageChannel? var webMessageChannel: WebMessageChannel?
var isClosed = false var isClosed = false
var isTransferred = false var isTransferred = false
var isStarted = false var isStarted = false
public init(name: String, webMessageChannel: WebMessageChannel) { public init(name: String, index: Int64, webMessageChannelId: String, webMessageChannel: WebMessageChannel?) {
self.name = name self.name = name
self.index = index
self.webMessageChannelId = webMessageChannelId
super.init() super.init()
self.webMessageChannel = webMessageChannel self.webMessageChannel = webMessageChannel
} }
@ -35,7 +39,10 @@ public class WebMessagePort : NSObject {
window.webkit.messageHandlers["onWebMessagePortMessageReceived"].postMessage({ window.webkit.messageHandlers["onWebMessagePortMessageReceived"].postMessage({
"webMessageChannelId": "\(webMessageChannel.id)", "webMessageChannelId": "\(webMessageChannel.id)",
"index": \(String(index)), "index": \(String(index)),
"message": event.data "message": {
"data": window.ArrayBuffer != null && event.data instanceof ArrayBuffer ? Array.from(new Uint8Array(event.data)) : (event.data != null ? event.data.toString() : null),
"type": window.ArrayBuffer != null && event.data instanceof ArrayBuffer ? 1 : 0
}
}); });
} }
} }
@ -71,12 +78,12 @@ public class WebMessagePort : NSObject {
} }
portsString = "[" + portArrayString.joined(separator: ", ") + "]" portsString = "[" + portArrayString.joined(separator: ", ") + "]"
} }
let data = message.data?.replacingOccurrences(of: "\'", with: "\\'") ?? "null"
let source = """ let source = """
(function() { (function() {
var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"];
if (webMessageChannel != null) { if (webMessageChannel != null) {
webMessageChannel.\(self.name).postMessage('\(data)', \(portsString)); webMessageChannel.\(self.name).postMessage(\(message.jsData), \(portsString));
} }
})(); })();
""" """
@ -111,6 +118,23 @@ public class WebMessagePort : NSObject {
} }
} }
public static func fromMap(map: [String: Any?]) -> WebMessagePort {
let index = map["index"] as! Int64
return WebMessagePort(
name: "port\(String(index + 1))",
index: index,
webMessageChannelId: map["webMessageChannelId"] as! String,
webMessageChannel: nil)
}
public func toMap () -> [String: Any?] {
return [
"name": name,
"index": index,
"webMessageChannelId": webMessageChannelId
]
}
public func dispose() { public func dispose() {
isClosed = true isClosed = true
webMessageChannel = nil webMessageChannel = nil

View File

@ -9,3 +9,8 @@ export 'webview_asset_loader.dart'
ResourcesPathHandler, ResourcesPathHandler,
InternalStoragePathHandler; InternalStoragePathHandler;
export 'tracing_controller.dart' show TracingController, TracingSettings; export 'tracing_controller.dart' show TracingController, TracingSettings;
export 'process_global_config.dart'
show
ProcessGlobalConfig,
ProcessGlobalConfigSettings,
ProcessGlobalConfigDirectoryBasePaths;

View File

@ -0,0 +1,150 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
import 'webview_feature.dart';
import '../in_app_webview/webview.dart';
import '../in_app_webview/in_app_webview_controller.dart';
import '../cookie_manager.dart';
part 'process_global_config.g.dart';
///Process Global Configuration for [WebView].
///WebView has some process-global configuration parameters
///that cannot be changed once WebView has been loaded.
///This class allows apps to set these parameters.
///
///If it is used, the configuration should be set and apply should
///be called prior to loading WebView into the calling process.
///Most of the methods in `android.webkit` and `androidx.webkit` packages load WebView,
///so the configuration should be applied before calling any of these methods.
///
///The following code configures the data directory suffix that WebView uses and
///then applies the configuration. WebView uses this configuration when it is loaded.
///
///[apply] can only be called once.
///
///Only a single thread should access this class at a given time.
///
///The configuration should be set up as early as possible during application startup,
///to ensure that it happens before any other thread can call a method that loads [WebView].
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - ProcessGlobalConfig](https://developer.android.com/reference/androidx/webkit/ProcessGlobalConfig))
class ProcessGlobalConfig {
static ProcessGlobalConfig? _instance;
static const MethodChannel _channel = const MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_processglobalconfig');
ProcessGlobalConfig._();
///Gets the [ProcessGlobalConfig] shared instance.
static ProcessGlobalConfig instance() {
return (_instance != null) ? _instance! : _init();
}
static ProcessGlobalConfig _init() {
_channel.setMethodCallHandler((call) async {
try {
return await _handleMethod(call);
} on Error catch (e) {
print(e);
print(e.stackTrace);
}
});
_instance = ProcessGlobalConfig._();
return _instance!;
}
static Future<dynamic> _handleMethod(MethodCall call) async {
// ProcessGlobalConfig controller = ProcessGlobalConfig.instance();
switch (call.method) {
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
// return null;
}
///Applies the configuration to be used by [WebView] on loading.
///This method can only be called once.
///
///Calling this method will not cause [WebView] to be loaded and will not block the calling thread.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - ProcessGlobalConfig.apply](https://developer.android.com/reference/androidx/webkit/ProcessGlobalConfig#apply(androidx.webkit.ProcessGlobalConfig)))
Future<void> apply({required ProcessGlobalConfigSettings settings}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("settings", () => settings.toMap());
return await _channel.invokeMethod('apply', args);
}
}
///Class that represents the settings used to configure the [ProcessGlobalConfig].
///
///**Supported Platforms/Implementations**:
///- Android native WebView
@ExchangeableObject(copyMethod: true)
class ProcessGlobalConfigSettings_ {
///The directory name suffix to be used for the current process.
///Must not contain a path separator and should not be empty.
///
///Define the directory used to store WebView data for the current process.
///The provided suffix will be used when constructing data and cache directory paths.
///If this API is not called, no suffix will be used.
///Each directory can be used by only one process in the application.
///If more than one process in an app wishes to use WebView,
///only one process can use the default directory,
///and other processes must call this API to define a unique suffix.
///
///This means that different processes in the same application cannot directly share [WebView]-related data,
///since the data directories must be distinct.
///Applications that use this API may have to explicitly pass data between processes.
///For example, login cookies may have to be copied from one process's cookie jar to the other using [CookieManager] if both processes' WebViews are intended to be logged in.
///
///Most applications should simply ensure that all components of the app that rely
///on WebView are in the same process, to avoid needing multiple data directories.
///The [InAppWebViewController.disableWebView] method can be used to ensure that the other processes do not use WebView by accident in this case.
///
///**NOTE**: available only if [WebViewFeature.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX] feature is supported.
String? dataDirectorySuffix;
///Set the base directories that [WebView] will use for the current process.
///If this method is not used, [WebView] uses the default base paths defined by the Android framework.
///
///WebView will create and use a subdirectory under each of the base paths supplied to this method.
///
///This method can be used in conjunction with setDataDirectorySuffix.
///A different subdirectory is created for each suffix.
///
///The base paths must be absolute paths.
///
///The data directory must not be under the Android cache directory,
///as Android may delete cache files when disk space is low and WebView may not function properly if this occurs.
///Refer to [this link](https://developer.android.com/training/data-storage/app-specific#internal-remove-cache).
///
///If the specified directories already exist then they must be readable and writable by the current process.
///If they do not already exist, [WebView] will attempt to create them during initialization, along with any missing parent directories.
///In such a case, the directory in which [WebView] creates missing directories must be readable and writable by the current process.
///
///**NOTE**: available only if [WebViewFeature.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS] feature is supported.
ProcessGlobalConfigDirectoryBasePaths_? directoryBasePaths;
ProcessGlobalConfigSettings_(
{this.dataDirectorySuffix, this.directoryBasePaths});
}
///Class that represents the settings used to configure the [ProcessGlobalConfigSettings.directoryBasePaths].
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - ProxyConfig](https://developer.android.com/reference/androidx/webkit/ProxyConfig))
@ExchangeableObject()
class ProcessGlobalConfigDirectoryBasePaths_ {
///The absolute base path for the WebView data directory.
String dataDirectoryBasePath;
///The absolute base path for the WebView cache directory.
String cacheDirectoryBasePath;
ProcessGlobalConfigDirectoryBasePaths_(
{required this.dataDirectoryBasePath,
required this.cacheDirectoryBasePath});
}

View File

@ -0,0 +1,142 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'process_global_config.dart';
// **************************************************************************
// ExchangeableObjectGenerator
// **************************************************************************
///Class that represents the settings used to configure the [ProcessGlobalConfig].
///
///**Supported Platforms/Implementations**:
///- Android native WebView
class ProcessGlobalConfigSettings {
///The directory name suffix to be used for the current process.
///Must not contain a path separator and should not be empty.
///
///Define the directory used to store WebView data for the current process.
///The provided suffix will be used when constructing data and cache directory paths.
///If this API is not called, no suffix will be used.
///Each directory can be used by only one process in the application.
///If more than one process in an app wishes to use WebView,
///only one process can use the default directory,
///and other processes must call this API to define a unique suffix.
///
///This means that different processes in the same application cannot directly share [WebView]-related data,
///since the data directories must be distinct.
///Applications that use this API may have to explicitly pass data between processes.
///For example, login cookies may have to be copied from one process's cookie jar to the other using [CookieManager] if both processes' WebViews are intended to be logged in.
///
///Most applications should simply ensure that all components of the app that rely
///on WebView are in the same process, to avoid needing multiple data directories.
///The [InAppWebViewController.disableWebView] method can be used to ensure that the other processes do not use WebView by accident in this case.
///
///**NOTE**: available only if [WebViewFeature.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX] feature is supported.
String? dataDirectorySuffix;
///Set the base directories that [WebView] will use for the current process.
///If this method is not used, [WebView] uses the default base paths defined by the Android framework.
///
///WebView will create and use a subdirectory under each of the base paths supplied to this method.
///
///This method can be used in conjunction with setDataDirectorySuffix.
///A different subdirectory is created for each suffix.
///
///The base paths must be absolute paths.
///
///The data directory must not be under the Android cache directory,
///as Android may delete cache files when disk space is low and WebView may not function properly if this occurs.
///Refer to [this link](https://developer.android.com/training/data-storage/app-specific#internal-remove-cache).
///
///If the specified directories already exist then they must be readable and writable by the current process.
///If they do not already exist, [WebView] will attempt to create them during initialization, along with any missing parent directories.
///In such a case, the directory in which [WebView] creates missing directories must be readable and writable by the current process.
///
///**NOTE**: available only if [WebViewFeature.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS] feature is supported.
ProcessGlobalConfigDirectoryBasePaths? directoryBasePaths;
ProcessGlobalConfigSettings(
{this.dataDirectorySuffix, this.directoryBasePaths});
///Gets a possible [ProcessGlobalConfigSettings] instance from a [Map] value.
static ProcessGlobalConfigSettings? fromMap(Map<String, dynamic>? map) {
if (map == null) {
return null;
}
final instance = ProcessGlobalConfigSettings(
dataDirectorySuffix: map['dataDirectorySuffix'],
directoryBasePaths: ProcessGlobalConfigDirectoryBasePaths.fromMap(
map['directoryBasePaths']?.cast<String, dynamic>()),
);
return instance;
}
///Converts instance to a map.
Map<String, dynamic> toMap() {
return {
"dataDirectorySuffix": dataDirectorySuffix,
"directoryBasePaths": directoryBasePaths?.toMap(),
};
}
///Converts instance to a map.
Map<String, dynamic> toJson() {
return toMap();
}
///Returns a copy of ProcessGlobalConfigSettings.
ProcessGlobalConfigSettings copy() {
return ProcessGlobalConfigSettings.fromMap(toMap()) ??
ProcessGlobalConfigSettings();
}
@override
String toString() {
return 'ProcessGlobalConfigSettings{dataDirectorySuffix: $dataDirectorySuffix, directoryBasePaths: $directoryBasePaths}';
}
}
///Class that represents the settings used to configure the [ProcessGlobalConfigSettings.directoryBasePaths].
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - ProxyConfig](https://developer.android.com/reference/androidx/webkit/ProxyConfig))
class ProcessGlobalConfigDirectoryBasePaths {
///The absolute base path for the WebView cache directory.
String cacheDirectoryBasePath;
///The absolute base path for the WebView data directory.
String dataDirectoryBasePath;
ProcessGlobalConfigDirectoryBasePaths(
{required this.cacheDirectoryBasePath,
required this.dataDirectoryBasePath});
///Gets a possible [ProcessGlobalConfigDirectoryBasePaths] instance from a [Map] value.
static ProcessGlobalConfigDirectoryBasePaths? fromMap(
Map<String, dynamic>? map) {
if (map == null) {
return null;
}
final instance = ProcessGlobalConfigDirectoryBasePaths(
cacheDirectoryBasePath: map['cacheDirectoryBasePath'],
dataDirectoryBasePath: map['dataDirectoryBasePath'],
);
return instance;
}
///Converts instance to a map.
Map<String, dynamic> toMap() {
return {
"cacheDirectoryBasePath": cacheDirectoryBasePath,
"dataDirectoryBasePath": dataDirectoryBasePath,
};
}
///Converts instance to a map.
Map<String, dynamic> toJson() {
return toMap();
}
@override
String toString() {
return 'ProcessGlobalConfigDirectoryBasePaths{cacheDirectoryBasePath: $cacheDirectoryBasePath, dataDirectoryBasePath: $dataDirectoryBasePath}';
}
}

View File

@ -139,41 +139,4 @@ class ProxySettings_ {
this.bypassSimpleHostnames, this.bypassSimpleHostnames,
this.removeImplicitRules, this.removeImplicitRules,
this.reverseBypassEnabled = false}); this.reverseBypassEnabled = false});
// Map<String, dynamic> toMap() {
// return {
// "bypassRules": bypassRules,
// "directs": directs,
// "proxyRules": proxyRules.map((e) => e.toMap()).toList(),
// "bypassSimpleHostnames": bypassSimpleHostnames,
// "removeImplicitRules": removeImplicitRules,
// "reverseBypassEnabled": reverseBypassEnabled
// };
// }
//
// static ProxySettings fromMap(Map<String, dynamic> map) {
// var settings = ProxySettings();
// settings.bypassRules = map["bypassRules"];
// settings.directs = map["directs"];
// settings.proxyRules = (map["proxyRules"].cast<Map<String, dynamic>>()
// as List<Map<String, dynamic>>)
// .map((e) => ProxyRule.fromMap(e)) as List<ProxyRule>;
// settings.bypassSimpleHostnames = map["bypassSimpleHostnames"];
// settings.removeImplicitRules = map["removeImplicitRules"];
// settings.reverseBypassEnabled = map["reverseBypassEnabled"];
// return settings;
// }
//
// Map<String, dynamic> toJson() {
// return this.toMap();
// }
//
// @override
// String toString() {
// return 'ProxySettings{bypassRules: $bypassRules, directs: $directs, proxyRules: $proxyRules, bypassSimpleHostnames: $bypassSimpleHostnames, removeImplicitRules: $removeImplicitRules, reverseBypassEnabled: $reverseBypassEnabled}';
// }
//
// ProxySettings copy() {
// return ProxySettings.fromMap(this.toMap());
// }
} }

View File

@ -1,7 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/src/cookie_manager.dart';
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
import 'process_global_config.dart';
import '../cookie_manager.dart';
import '../in_app_webview/in_app_webview_controller.dart'; import '../in_app_webview/in_app_webview_controller.dart';
import '../in_app_webview/in_app_webview_settings.dart'; import '../in_app_webview/in_app_webview_settings.dart';
import 'proxy_controller.dart'; import 'proxy_controller.dart';
@ -29,18 +30,18 @@ class WebViewFeature_ {
@ExchangeableObjectMethod(ignore: true) @ExchangeableObjectMethod(ignore: true)
String toNativeValue() => _value; String toNativeValue() => _value;
///This feature covers [InAppWebViewController.createWebMessageChannel]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewController.createWebMessageChannel].
static const CREATE_WEB_MESSAGE_CHANNEL = static const CREATE_WEB_MESSAGE_CHANNEL =
const WebViewFeature_._internal("CREATE_WEB_MESSAGE_CHANNEL"); const WebViewFeature_._internal("CREATE_WEB_MESSAGE_CHANNEL");
///This feature covers [InAppWebViewSettings.disabledActionModeMenuItems]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.disabledActionModeMenuItems].
static const DISABLED_ACTION_MODE_MENU_ITEMS = static const DISABLED_ACTION_MODE_MENU_ITEMS =
const WebViewFeature_._internal("DISABLED_ACTION_MODE_MENU_ITEMS"); const WebViewFeature_._internal("DISABLED_ACTION_MODE_MENU_ITEMS");
///This feature covers [InAppWebViewSettings.forceDark]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.forceDark].
static const FORCE_DARK = const WebViewFeature_._internal("FORCE_DARK"); static const FORCE_DARK = const WebViewFeature_._internal("FORCE_DARK");
///This feature covers [InAppWebViewSettings.forceDarkStrategy]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.forceDarkStrategy].
static const FORCE_DARK_STRATEGY = static const FORCE_DARK_STRATEGY =
const WebViewFeature_._internal("FORCE_DARK_STRATEGY"); const WebViewFeature_._internal("FORCE_DARK_STRATEGY");
@ -59,19 +60,19 @@ class WebViewFeature_ {
/// ///
static const MULTI_PROCESS = const WebViewFeature_._internal("MULTI_PROCESS"); static const MULTI_PROCESS = const WebViewFeature_._internal("MULTI_PROCESS");
///This feature covers [InAppWebViewSettings.offscreenPreRaster]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.offscreenPreRaster].
static const OFF_SCREEN_PRERASTER = static const OFF_SCREEN_PRERASTER =
const WebViewFeature_._internal("OFF_SCREEN_PRERASTER"); const WebViewFeature_._internal("OFF_SCREEN_PRERASTER");
///This feature covers [InAppWebViewController.postWebMessage]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewController.postWebMessage].
static const POST_WEB_MESSAGE = static const POST_WEB_MESSAGE =
const WebViewFeature_._internal("POST_WEB_MESSAGE"); const WebViewFeature_._internal("POST_WEB_MESSAGE");
///This feature covers [ProxyController.setProxyOverride] and [ProxyController.clearProxyOverride]. ///Feature for [isFeatureSupported]. This feature covers [ProxyController.setProxyOverride] and [ProxyController.clearProxyOverride].
static const PROXY_OVERRIDE = static const PROXY_OVERRIDE =
const WebViewFeature_._internal("PROXY_OVERRIDE"); const WebViewFeature_._internal("PROXY_OVERRIDE");
///This feature covers [ProxySettings.reverseBypassEnabled]. ///Feature for [isFeatureSupported]. This feature covers [ProxySettings.reverseBypassEnabled].
static const PROXY_OVERRIDE_REVERSE_BYPASS = static const PROXY_OVERRIDE_REVERSE_BYPASS =
const WebViewFeature_._internal("PROXY_OVERRIDE_REVERSE_BYPASS"); const WebViewFeature_._internal("PROXY_OVERRIDE_REVERSE_BYPASS");
@ -83,11 +84,11 @@ class WebViewFeature_ {
static const RECEIVE_WEB_RESOURCE_ERROR = static const RECEIVE_WEB_RESOURCE_ERROR =
const WebViewFeature_._internal("RECEIVE_WEB_RESOURCE_ERROR"); const WebViewFeature_._internal("RECEIVE_WEB_RESOURCE_ERROR");
///This feature covers [InAppWebViewController.setSafeBrowsingAllowlist]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewController.setSafeBrowsingAllowlist].
static const SAFE_BROWSING_ALLOWLIST = static const SAFE_BROWSING_ALLOWLIST =
const WebViewFeature_._internal("SAFE_BROWSING_ALLOWLIST"); const WebViewFeature_._internal("SAFE_BROWSING_ALLOWLIST");
///This feature covers [InAppWebViewSettings.safeBrowsingEnabled]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.safeBrowsingEnabled].
static const SAFE_BROWSING_ENABLE = static const SAFE_BROWSING_ENABLE =
const WebViewFeature_._internal("SAFE_BROWSING_ENABLE"); const WebViewFeature_._internal("SAFE_BROWSING_ENABLE");
@ -95,7 +96,7 @@ class WebViewFeature_ {
static const SAFE_BROWSING_HIT = static const SAFE_BROWSING_HIT =
const WebViewFeature_._internal("SAFE_BROWSING_HIT"); const WebViewFeature_._internal("SAFE_BROWSING_HIT");
///This feature covers [InAppWebViewController.getSafeBrowsingPrivacyPolicyUrl]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewController.getSafeBrowsingPrivacyPolicyUrl].
static const SAFE_BROWSING_PRIVACY_POLICY_URL = static const SAFE_BROWSING_PRIVACY_POLICY_URL =
const WebViewFeature_._internal("SAFE_BROWSING_PRIVACY_POLICY_URL"); const WebViewFeature_._internal("SAFE_BROWSING_PRIVACY_POLICY_URL");
@ -117,27 +118,27 @@ class WebViewFeature_ {
static const SAFE_BROWSING_WHITELIST = static const SAFE_BROWSING_WHITELIST =
const WebViewFeature_._internal("SAFE_BROWSING_WHITELIST"); const WebViewFeature_._internal("SAFE_BROWSING_WHITELIST");
///This feature covers [ServiceWorkerController]. ///Feature for [isFeatureSupported]. This feature covers [ServiceWorkerController].
static const SERVICE_WORKER_BASIC_USAGE = static const SERVICE_WORKER_BASIC_USAGE =
const WebViewFeature_._internal("SERVICE_WORKER_BASIC_USAGE"); const WebViewFeature_._internal("SERVICE_WORKER_BASIC_USAGE");
///This feature covers [ServiceWorkerController.setBlockNetworkLoads] and [ServiceWorkerController.getBlockNetworkLoads]. ///Feature for [isFeatureSupported]. This feature covers [ServiceWorkerController.setBlockNetworkLoads] and [ServiceWorkerController.getBlockNetworkLoads].
static const SERVICE_WORKER_BLOCK_NETWORK_LOADS = static const SERVICE_WORKER_BLOCK_NETWORK_LOADS =
const WebViewFeature_._internal("SERVICE_WORKER_BLOCK_NETWORK_LOADS"); const WebViewFeature_._internal("SERVICE_WORKER_BLOCK_NETWORK_LOADS");
///This feature covers [ServiceWorkerController.setCacheMode] and [ServiceWorkerController.getCacheMode]. ///Feature for [isFeatureSupported]. This feature covers [ServiceWorkerController.setCacheMode] and [ServiceWorkerController.getCacheMode].
static const SERVICE_WORKER_CACHE_MODE = static const SERVICE_WORKER_CACHE_MODE =
const WebViewFeature_._internal("SERVICE_WORKER_CACHE_MODE"); const WebViewFeature_._internal("SERVICE_WORKER_CACHE_MODE");
///This feature covers [ServiceWorkerController.setAllowContentAccess] and [ServiceWorkerController.getAllowContentAccess]. ///Feature for [isFeatureSupported]. This feature covers [ServiceWorkerController.setAllowContentAccess] and [ServiceWorkerController.getAllowContentAccess].
static const SERVICE_WORKER_CONTENT_ACCESS = static const SERVICE_WORKER_CONTENT_ACCESS =
const WebViewFeature_._internal("SERVICE_WORKER_CONTENT_ACCESS"); const WebViewFeature_._internal("SERVICE_WORKER_CONTENT_ACCESS");
///This feature covers [ServiceWorkerController.setAllowFileAccess] and [ServiceWorkerController.getAllowFileAccess]. ///Feature for [isFeatureSupported]. This feature covers [ServiceWorkerController.setAllowFileAccess] and [ServiceWorkerController.getAllowFileAccess].
static const SERVICE_WORKER_FILE_ACCESS = static const SERVICE_WORKER_FILE_ACCESS =
const WebViewFeature_._internal("SERVICE_WORKER_FILE_ACCESS"); const WebViewFeature_._internal("SERVICE_WORKER_FILE_ACCESS");
///This feature covers [ServiceWorkerClient.shouldInterceptRequest]. ///Feature for [isFeatureSupported]. This feature covers [ServiceWorkerClient.shouldInterceptRequest].
static const SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST = static const SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST =
const WebViewFeature_._internal( const WebViewFeature_._internal(
"SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST"); "SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST");
@ -146,7 +147,7 @@ class WebViewFeature_ {
static const SHOULD_OVERRIDE_WITH_REDIRECTS = static const SHOULD_OVERRIDE_WITH_REDIRECTS =
const WebViewFeature_._internal("SHOULD_OVERRIDE_WITH_REDIRECTS"); const WebViewFeature_._internal("SHOULD_OVERRIDE_WITH_REDIRECTS");
///This feature covers [InAppWebViewController.startSafeBrowsing]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewController.startSafeBrowsing].
static const START_SAFE_BROWSING = static const START_SAFE_BROWSING =
const WebViewFeature_._internal("START_SAFE_BROWSING"); const WebViewFeature_._internal("START_SAFE_BROWSING");
@ -162,7 +163,7 @@ class WebViewFeature_ {
static const WEB_MESSAGE_CALLBACK_ON_MESSAGE = static const WEB_MESSAGE_CALLBACK_ON_MESSAGE =
const WebViewFeature_._internal("WEB_MESSAGE_CALLBACK_ON_MESSAGE"); const WebViewFeature_._internal("WEB_MESSAGE_CALLBACK_ON_MESSAGE");
///This feature covers [WebMessageListener]. ///Feature for [isFeatureSupported]. This feature covers [WebMessageListener].
static const WEB_MESSAGE_LISTENER = static const WEB_MESSAGE_LISTENER =
const WebViewFeature_._internal("WEB_MESSAGE_LISTENER"); const WebViewFeature_._internal("WEB_MESSAGE_LISTENER");
@ -198,45 +199,90 @@ 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]. ///Feature for [isFeatureSupported]. This feature covers [UserScriptInjectionTime.AT_DOCUMENT_START].
static const DOCUMENT_START_SCRIPT = static const DOCUMENT_START_SCRIPT =
const WebViewFeature_._internal("DOCUMENT_START_SCRIPT"); const WebViewFeature_._internal("DOCUMENT_START_SCRIPT");
///This feature covers [InAppWebViewSettings.willSuppressErrorPage]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.willSuppressErrorPage].
static const SUPPRESS_ERROR_PAGE = static const SUPPRESS_ERROR_PAGE =
const WebViewFeature_._internal("SUPPRESS_ERROR_PAGE"); const WebViewFeature_._internal("SUPPRESS_ERROR_PAGE");
///This feature covers [InAppWebViewSettings.algorithmicDarkeningAllowed]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.algorithmicDarkeningAllowed].
static const ALGORITHMIC_DARKENING = static const ALGORITHMIC_DARKENING =
const WebViewFeature_._internal("ALGORITHMIC_DARKENING"); const WebViewFeature_._internal("ALGORITHMIC_DARKENING");
///This feature covers [InAppWebViewSettings.enterpriseAuthenticationAppLinkPolicyEnabled]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.enterpriseAuthenticationAppLinkPolicyEnabled].
static const ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY = static const ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY =
const WebViewFeature_._internal( const WebViewFeature_._internal(
"ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY"); "ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY");
///This feature covers [InAppWebViewController.getVariationsHeader]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewController.getVariationsHeader].
static const GET_VARIATIONS_HEADER = static const GET_VARIATIONS_HEADER =
const WebViewFeature_._internal("GET_VARIATIONS_HEADER"); const WebViewFeature_._internal("GET_VARIATIONS_HEADER");
///This feature covers cookie attributes of [CookieManager.getCookie] and [CookieManager.getCookies] methods. ///Feature for [isFeatureSupported]. This feature covers cookie attributes of [CookieManager.getCookie] and [CookieManager.getCookies] methods.
static const GET_COOKIE_INFO = static const GET_COOKIE_INFO =
const WebViewFeature_._internal("GET_COOKIE_INFO"); const WebViewFeature_._internal("GET_COOKIE_INFO");
///This feature covers cookie attributes of [CookieManager.getCookie] and [CookieManager.getCookies] methods. ///Feature for [isFeatureSupported]. This feature covers cookie attributes of [CookieManager.getCookie] and [CookieManager.getCookies] methods.
static const REQUESTED_WITH_HEADER_ALLOW_LIST = static const REQUESTED_WITH_HEADER_ALLOW_LIST =
const WebViewFeature_._internal("REQUESTED_WITH_HEADER_ALLOW_LIST"); const WebViewFeature_._internal("REQUESTED_WITH_HEADER_ALLOW_LIST");
///Feature for [isFeatureSupported]. This feature covers [WebMessagePort.postMessage] with `ArrayBuffer` type,
///[InAppWebViewController.postWebMessage] with `ArrayBuffer` type, and [JavaScriptReplyProxy.postMessage] with `ArrayBuffer` type.
static const WEB_MESSAGE_ARRAY_BUFFER =
const WebViewFeature_._internal("WEB_MESSAGE_ARRAY_BUFFER");
///Feature for [isStartupFeatureSupported]. This feature covers [ProcessGlobalConfigSettings.dataDirectorySuffix].
static const STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX =
const WebViewFeature_._internal(
"STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX");
///Feature for [isStartupFeatureSupported]. This feature covers [ProcessGlobalConfigSettings.directoryBasePaths].
static const STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS =
const WebViewFeature_._internal(
"STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS");
///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`.
/// ///
///**Note**: This method is different from [isStartupFeatureSupported] and this
///method only accepts certain features.
///Please verify that the correct feature checking method is used for a particular feature.
///
///**Note**: If this method returns `false`, it is not safe to invoke the methods
///requiring the desired feature.
///Furthermore, if this method returns `false` for a particular feature, any callback guarded by that feature will not be invoked.
///
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewFeature#isFeatureSupported(java.lang.String) ///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewFeature#isFeatureSupported(java.lang.String)
static Future<bool> isFeatureSupported(WebViewFeature_ feature) async { static Future<bool> isFeatureSupported(WebViewFeature_ feature) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("feature", () => feature.toNativeValue()); args.putIfAbsent("feature", () => feature.toNativeValue());
return await _channel.invokeMethod('isFeatureSupported', args); return await _channel.invokeMethod('isFeatureSupported', args);
} }
///Return whether a startup feature is supported at run-time.
///On devices running Android version `Build.VERSION_CODES.LOLLIPOP` and higher,
///this will check whether a startup 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`.
///
///**Note**: This method is different from [isFeatureSupported] and this method only accepts startup features.
///Please verify that the correct feature checking method is used for a particular feature.
///
///**Note**: If this method returns `false`, it is not safe to invoke the methods requiring the desired feature.
///Furthermore, if this method returns `false` for a particular feature,
///any callback guarded by that feature will not be invoked.
///
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewFeature#isFeatureSupported(java.lang.String)
static Future<bool> isStartupFeatureSupported(
WebViewFeature_ startupFeature) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("startupFeature", () => startupFeature.toNativeValue());
return await _channel.invokeMethod('isStartupFeatureSupported', args);
}
} }
///Class that represents an Android-specific utility class for checking which WebView Support Library features are supported on the device. ///Class that represents an Android-specific utility class for checking which WebView Support Library features are supported on the device.
@ -430,23 +476,23 @@ 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]. ///Feature for [isFeatureSupported]. This feature covers [UserScriptInjectionTime.AT_DOCUMENT_START].
static const DOCUMENT_START_SCRIPT = static const DOCUMENT_START_SCRIPT =
const AndroidWebViewFeature_._internal("DOCUMENT_START_SCRIPT"); const AndroidWebViewFeature_._internal("DOCUMENT_START_SCRIPT");
///This feature covers [InAppWebViewSettings.willSuppressErrorPage]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.willSuppressErrorPage].
static const SUPPRESS_ERROR_PAGE = static const SUPPRESS_ERROR_PAGE =
const AndroidWebViewFeature_._internal("SUPPRESS_ERROR_PAGE"); const AndroidWebViewFeature_._internal("SUPPRESS_ERROR_PAGE");
///This feature covers [InAppWebViewSettings.algorithmicDarkeningAllowed]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.algorithmicDarkeningAllowed].
static const ALGORITHMIC_DARKENING = static const ALGORITHMIC_DARKENING =
const AndroidWebViewFeature_._internal("ALGORITHMIC_DARKENING"); const AndroidWebViewFeature_._internal("ALGORITHMIC_DARKENING");
///This feature covers [InAppWebViewSettings.requestedWithHeaderMode]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.requestedWithHeaderMode].
static const REQUESTED_WITH_HEADER_CONTROL = static const REQUESTED_WITH_HEADER_CONTROL =
const AndroidWebViewFeature_._internal("REQUESTED_WITH_HEADER_CONTROL"); const AndroidWebViewFeature_._internal("REQUESTED_WITH_HEADER_CONTROL");
///This feature covers [InAppWebViewSettings.enterpriseAuthenticationAppLinkPolicyEnabled]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.enterpriseAuthenticationAppLinkPolicyEnabled].
static const ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY = static const ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY =
const AndroidWebViewFeature_._internal( const AndroidWebViewFeature_._internal(
"ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY"); "ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY");

View File

@ -19,40 +19,40 @@ class WebViewFeature {
String value, Function nativeValue) => String value, Function nativeValue) =>
WebViewFeature._internal(value, nativeValue()); WebViewFeature._internal(value, nativeValue());
///This feature covers [InAppWebViewSettings.algorithmicDarkeningAllowed]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.algorithmicDarkeningAllowed].
static const ALGORITHMIC_DARKENING = WebViewFeature._internal( static const ALGORITHMIC_DARKENING = WebViewFeature._internal(
'ALGORITHMIC_DARKENING', 'ALGORITHMIC_DARKENING'); 'ALGORITHMIC_DARKENING', 'ALGORITHMIC_DARKENING');
///This feature covers [InAppWebViewController.createWebMessageChannel]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewController.createWebMessageChannel].
static const CREATE_WEB_MESSAGE_CHANNEL = WebViewFeature._internal( static const CREATE_WEB_MESSAGE_CHANNEL = WebViewFeature._internal(
'CREATE_WEB_MESSAGE_CHANNEL', 'CREATE_WEB_MESSAGE_CHANNEL'); 'CREATE_WEB_MESSAGE_CHANNEL', 'CREATE_WEB_MESSAGE_CHANNEL');
///This feature covers [InAppWebViewSettings.disabledActionModeMenuItems]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.disabledActionModeMenuItems].
static const DISABLED_ACTION_MODE_MENU_ITEMS = WebViewFeature._internal( static const DISABLED_ACTION_MODE_MENU_ITEMS = WebViewFeature._internal(
'DISABLED_ACTION_MODE_MENU_ITEMS', 'DISABLED_ACTION_MODE_MENU_ITEMS'); 'DISABLED_ACTION_MODE_MENU_ITEMS', 'DISABLED_ACTION_MODE_MENU_ITEMS');
///This feature covers [UserScriptInjectionTime.AT_DOCUMENT_START]. ///Feature for [isFeatureSupported]. This feature covers [UserScriptInjectionTime.AT_DOCUMENT_START].
static const DOCUMENT_START_SCRIPT = WebViewFeature._internal( static const DOCUMENT_START_SCRIPT = WebViewFeature._internal(
'DOCUMENT_START_SCRIPT', 'DOCUMENT_START_SCRIPT'); 'DOCUMENT_START_SCRIPT', 'DOCUMENT_START_SCRIPT');
///This feature covers [InAppWebViewSettings.enterpriseAuthenticationAppLinkPolicyEnabled]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.enterpriseAuthenticationAppLinkPolicyEnabled].
static const ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY = static const ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY =
WebViewFeature._internal('ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY', WebViewFeature._internal('ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY',
'ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY'); 'ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY');
///This feature covers [InAppWebViewSettings.forceDark]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.forceDark].
static const FORCE_DARK = static const FORCE_DARK =
WebViewFeature._internal('FORCE_DARK', 'FORCE_DARK'); WebViewFeature._internal('FORCE_DARK', 'FORCE_DARK');
///This feature covers [InAppWebViewSettings.forceDarkStrategy]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.forceDarkStrategy].
static const FORCE_DARK_STRATEGY = static const FORCE_DARK_STRATEGY =
WebViewFeature._internal('FORCE_DARK_STRATEGY', 'FORCE_DARK_STRATEGY'); WebViewFeature._internal('FORCE_DARK_STRATEGY', 'FORCE_DARK_STRATEGY');
///This feature covers cookie attributes of [CookieManager.getCookie] and [CookieManager.getCookies] methods. ///Feature for [isFeatureSupported]. This feature covers cookie attributes of [CookieManager.getCookie] and [CookieManager.getCookies] methods.
static const GET_COOKIE_INFO = static const GET_COOKIE_INFO =
WebViewFeature._internal('GET_COOKIE_INFO', 'GET_COOKIE_INFO'); WebViewFeature._internal('GET_COOKIE_INFO', 'GET_COOKIE_INFO');
///This feature covers [InAppWebViewController.getVariationsHeader]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewController.getVariationsHeader].
static const GET_VARIATIONS_HEADER = WebViewFeature._internal( static const GET_VARIATIONS_HEADER = WebViewFeature._internal(
'GET_VARIATIONS_HEADER', 'GET_VARIATIONS_HEADER'); 'GET_VARIATIONS_HEADER', 'GET_VARIATIONS_HEADER');
@ -72,19 +72,19 @@ class WebViewFeature {
static const MULTI_PROCESS = static const MULTI_PROCESS =
WebViewFeature._internal('MULTI_PROCESS', 'MULTI_PROCESS'); WebViewFeature._internal('MULTI_PROCESS', 'MULTI_PROCESS');
///This feature covers [InAppWebViewSettings.offscreenPreRaster]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.offscreenPreRaster].
static const OFF_SCREEN_PRERASTER = static const OFF_SCREEN_PRERASTER =
WebViewFeature._internal('OFF_SCREEN_PRERASTER', 'OFF_SCREEN_PRERASTER'); WebViewFeature._internal('OFF_SCREEN_PRERASTER', 'OFF_SCREEN_PRERASTER');
///This feature covers [InAppWebViewController.postWebMessage]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewController.postWebMessage].
static const POST_WEB_MESSAGE = static const POST_WEB_MESSAGE =
WebViewFeature._internal('POST_WEB_MESSAGE', 'POST_WEB_MESSAGE'); WebViewFeature._internal('POST_WEB_MESSAGE', 'POST_WEB_MESSAGE');
///This feature covers [ProxyController.setProxyOverride] and [ProxyController.clearProxyOverride]. ///Feature for [isFeatureSupported]. This feature covers [ProxyController.setProxyOverride] and [ProxyController.clearProxyOverride].
static const PROXY_OVERRIDE = static const PROXY_OVERRIDE =
WebViewFeature._internal('PROXY_OVERRIDE', 'PROXY_OVERRIDE'); WebViewFeature._internal('PROXY_OVERRIDE', 'PROXY_OVERRIDE');
///This feature covers [ProxySettings.reverseBypassEnabled]. ///Feature for [isFeatureSupported]. This feature covers [ProxySettings.reverseBypassEnabled].
static const PROXY_OVERRIDE_REVERSE_BYPASS = WebViewFeature._internal( static const PROXY_OVERRIDE_REVERSE_BYPASS = WebViewFeature._internal(
'PROXY_OVERRIDE_REVERSE_BYPASS', 'PROXY_OVERRIDE_REVERSE_BYPASS'); 'PROXY_OVERRIDE_REVERSE_BYPASS', 'PROXY_OVERRIDE_REVERSE_BYPASS');
@ -96,15 +96,15 @@ class WebViewFeature {
static const RECEIVE_WEB_RESOURCE_ERROR = WebViewFeature._internal( static const RECEIVE_WEB_RESOURCE_ERROR = WebViewFeature._internal(
'RECEIVE_WEB_RESOURCE_ERROR', 'RECEIVE_WEB_RESOURCE_ERROR'); 'RECEIVE_WEB_RESOURCE_ERROR', 'RECEIVE_WEB_RESOURCE_ERROR');
///This feature covers cookie attributes of [CookieManager.getCookie] and [CookieManager.getCookies] methods. ///Feature for [isFeatureSupported]. This feature covers cookie attributes of [CookieManager.getCookie] and [CookieManager.getCookies] methods.
static const REQUESTED_WITH_HEADER_ALLOW_LIST = WebViewFeature._internal( static const REQUESTED_WITH_HEADER_ALLOW_LIST = WebViewFeature._internal(
'REQUESTED_WITH_HEADER_ALLOW_LIST', 'REQUESTED_WITH_HEADER_ALLOW_LIST'); 'REQUESTED_WITH_HEADER_ALLOW_LIST', 'REQUESTED_WITH_HEADER_ALLOW_LIST');
///This feature covers [InAppWebViewController.setSafeBrowsingAllowlist]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewController.setSafeBrowsingAllowlist].
static const SAFE_BROWSING_ALLOWLIST = WebViewFeature._internal( static const SAFE_BROWSING_ALLOWLIST = WebViewFeature._internal(
'SAFE_BROWSING_ALLOWLIST', 'SAFE_BROWSING_ALLOWLIST'); 'SAFE_BROWSING_ALLOWLIST', 'SAFE_BROWSING_ALLOWLIST');
///This feature covers [InAppWebViewSettings.safeBrowsingEnabled]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.safeBrowsingEnabled].
static const SAFE_BROWSING_ENABLE = static const SAFE_BROWSING_ENABLE =
WebViewFeature._internal('SAFE_BROWSING_ENABLE', 'SAFE_BROWSING_ENABLE'); WebViewFeature._internal('SAFE_BROWSING_ENABLE', 'SAFE_BROWSING_ENABLE');
@ -112,7 +112,7 @@ class WebViewFeature {
static const SAFE_BROWSING_HIT = static const SAFE_BROWSING_HIT =
WebViewFeature._internal('SAFE_BROWSING_HIT', 'SAFE_BROWSING_HIT'); WebViewFeature._internal('SAFE_BROWSING_HIT', 'SAFE_BROWSING_HIT');
///This feature covers [InAppWebViewController.getSafeBrowsingPrivacyPolicyUrl]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewController.getSafeBrowsingPrivacyPolicyUrl].
static const SAFE_BROWSING_PRIVACY_POLICY_URL = WebViewFeature._internal( static const SAFE_BROWSING_PRIVACY_POLICY_URL = WebViewFeature._internal(
'SAFE_BROWSING_PRIVACY_POLICY_URL', 'SAFE_BROWSING_PRIVACY_POLICY_URL'); 'SAFE_BROWSING_PRIVACY_POLICY_URL', 'SAFE_BROWSING_PRIVACY_POLICY_URL');
@ -134,28 +134,28 @@ class WebViewFeature {
static const SAFE_BROWSING_WHITELIST = WebViewFeature._internal( static const SAFE_BROWSING_WHITELIST = WebViewFeature._internal(
'SAFE_BROWSING_WHITELIST', 'SAFE_BROWSING_WHITELIST'); 'SAFE_BROWSING_WHITELIST', 'SAFE_BROWSING_WHITELIST');
///This feature covers [ServiceWorkerController]. ///Feature for [isFeatureSupported]. This feature covers [ServiceWorkerController].
static const SERVICE_WORKER_BASIC_USAGE = WebViewFeature._internal( static const SERVICE_WORKER_BASIC_USAGE = WebViewFeature._internal(
'SERVICE_WORKER_BASIC_USAGE', 'SERVICE_WORKER_BASIC_USAGE'); 'SERVICE_WORKER_BASIC_USAGE', 'SERVICE_WORKER_BASIC_USAGE');
///This feature covers [ServiceWorkerController.setBlockNetworkLoads] and [ServiceWorkerController.getBlockNetworkLoads]. ///Feature for [isFeatureSupported]. This feature covers [ServiceWorkerController.setBlockNetworkLoads] and [ServiceWorkerController.getBlockNetworkLoads].
static const SERVICE_WORKER_BLOCK_NETWORK_LOADS = WebViewFeature._internal( static const SERVICE_WORKER_BLOCK_NETWORK_LOADS = WebViewFeature._internal(
'SERVICE_WORKER_BLOCK_NETWORK_LOADS', 'SERVICE_WORKER_BLOCK_NETWORK_LOADS',
'SERVICE_WORKER_BLOCK_NETWORK_LOADS'); 'SERVICE_WORKER_BLOCK_NETWORK_LOADS');
///This feature covers [ServiceWorkerController.setCacheMode] and [ServiceWorkerController.getCacheMode]. ///Feature for [isFeatureSupported]. This feature covers [ServiceWorkerController.setCacheMode] and [ServiceWorkerController.getCacheMode].
static const SERVICE_WORKER_CACHE_MODE = WebViewFeature._internal( static const SERVICE_WORKER_CACHE_MODE = WebViewFeature._internal(
'SERVICE_WORKER_CACHE_MODE', 'SERVICE_WORKER_CACHE_MODE'); 'SERVICE_WORKER_CACHE_MODE', 'SERVICE_WORKER_CACHE_MODE');
///This feature covers [ServiceWorkerController.setAllowContentAccess] and [ServiceWorkerController.getAllowContentAccess]. ///Feature for [isFeatureSupported]. This feature covers [ServiceWorkerController.setAllowContentAccess] and [ServiceWorkerController.getAllowContentAccess].
static const SERVICE_WORKER_CONTENT_ACCESS = WebViewFeature._internal( static const SERVICE_WORKER_CONTENT_ACCESS = WebViewFeature._internal(
'SERVICE_WORKER_CONTENT_ACCESS', 'SERVICE_WORKER_CONTENT_ACCESS'); 'SERVICE_WORKER_CONTENT_ACCESS', 'SERVICE_WORKER_CONTENT_ACCESS');
///This feature covers [ServiceWorkerController.setAllowFileAccess] and [ServiceWorkerController.getAllowFileAccess]. ///Feature for [isFeatureSupported]. This feature covers [ServiceWorkerController.setAllowFileAccess] and [ServiceWorkerController.getAllowFileAccess].
static const SERVICE_WORKER_FILE_ACCESS = WebViewFeature._internal( static const SERVICE_WORKER_FILE_ACCESS = WebViewFeature._internal(
'SERVICE_WORKER_FILE_ACCESS', 'SERVICE_WORKER_FILE_ACCESS'); 'SERVICE_WORKER_FILE_ACCESS', 'SERVICE_WORKER_FILE_ACCESS');
///This feature covers [ServiceWorkerClient.shouldInterceptRequest]. ///Feature for [isFeatureSupported]. This feature covers [ServiceWorkerClient.shouldInterceptRequest].
static const SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST = static const SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST =
WebViewFeature._internal('SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST', WebViewFeature._internal('SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST',
'SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST'); 'SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST');
@ -164,11 +164,21 @@ class WebViewFeature {
static const SHOULD_OVERRIDE_WITH_REDIRECTS = WebViewFeature._internal( static const SHOULD_OVERRIDE_WITH_REDIRECTS = WebViewFeature._internal(
'SHOULD_OVERRIDE_WITH_REDIRECTS', 'SHOULD_OVERRIDE_WITH_REDIRECTS'); 'SHOULD_OVERRIDE_WITH_REDIRECTS', 'SHOULD_OVERRIDE_WITH_REDIRECTS');
///This feature covers [InAppWebViewController.startSafeBrowsing]. ///Feature for [isStartupFeatureSupported]. This feature covers [ProcessGlobalConfigSettings.dataDirectorySuffix].
static const STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX =
WebViewFeature._internal('STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX',
'STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX');
///Feature for [isStartupFeatureSupported]. This feature covers [ProcessGlobalConfigSettings.directoryBasePaths].
static const STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS =
WebViewFeature._internal('STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS',
'STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS');
///Feature for [isFeatureSupported]. This feature covers [InAppWebViewController.startSafeBrowsing].
static const START_SAFE_BROWSING = static const START_SAFE_BROWSING =
WebViewFeature._internal('START_SAFE_BROWSING', 'START_SAFE_BROWSING'); WebViewFeature._internal('START_SAFE_BROWSING', 'START_SAFE_BROWSING');
///This feature covers [InAppWebViewSettings.willSuppressErrorPage]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.willSuppressErrorPage].
static const SUPPRESS_ERROR_PAGE = static const SUPPRESS_ERROR_PAGE =
WebViewFeature._internal('SUPPRESS_ERROR_PAGE', 'SUPPRESS_ERROR_PAGE'); WebViewFeature._internal('SUPPRESS_ERROR_PAGE', 'SUPPRESS_ERROR_PAGE');
@ -180,11 +190,16 @@ class WebViewFeature {
static const VISUAL_STATE_CALLBACK = WebViewFeature._internal( static const VISUAL_STATE_CALLBACK = WebViewFeature._internal(
'VISUAL_STATE_CALLBACK', 'VISUAL_STATE_CALLBACK'); 'VISUAL_STATE_CALLBACK', 'VISUAL_STATE_CALLBACK');
///Feature for [isFeatureSupported]. This feature covers [WebMessagePort.postMessage] with `ArrayBuffer` type,
///[InAppWebViewController.postWebMessage] with `ArrayBuffer` type, and [JavaScriptReplyProxy.postMessage] with `ArrayBuffer` type.
static const WEB_MESSAGE_ARRAY_BUFFER = WebViewFeature._internal(
'WEB_MESSAGE_ARRAY_BUFFER', 'WEB_MESSAGE_ARRAY_BUFFER');
/// ///
static const WEB_MESSAGE_CALLBACK_ON_MESSAGE = WebViewFeature._internal( static const WEB_MESSAGE_CALLBACK_ON_MESSAGE = WebViewFeature._internal(
'WEB_MESSAGE_CALLBACK_ON_MESSAGE', 'WEB_MESSAGE_CALLBACK_ON_MESSAGE'); 'WEB_MESSAGE_CALLBACK_ON_MESSAGE', 'WEB_MESSAGE_CALLBACK_ON_MESSAGE');
///This feature covers [WebMessageListener]. ///Feature for [isFeatureSupported]. This feature covers [WebMessageListener].
static const WEB_MESSAGE_LISTENER = static const WEB_MESSAGE_LISTENER =
WebViewFeature._internal('WEB_MESSAGE_LISTENER', 'WEB_MESSAGE_LISTENER'); WebViewFeature._internal('WEB_MESSAGE_LISTENER', 'WEB_MESSAGE_LISTENER');
@ -262,10 +277,13 @@ class WebViewFeature {
WebViewFeature.SERVICE_WORKER_FILE_ACCESS, WebViewFeature.SERVICE_WORKER_FILE_ACCESS,
WebViewFeature.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST, WebViewFeature.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST,
WebViewFeature.SHOULD_OVERRIDE_WITH_REDIRECTS, WebViewFeature.SHOULD_OVERRIDE_WITH_REDIRECTS,
WebViewFeature.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX,
WebViewFeature.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS,
WebViewFeature.START_SAFE_BROWSING, WebViewFeature.START_SAFE_BROWSING,
WebViewFeature.SUPPRESS_ERROR_PAGE, WebViewFeature.SUPPRESS_ERROR_PAGE,
WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE, WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE,
WebViewFeature.VISUAL_STATE_CALLBACK, WebViewFeature.VISUAL_STATE_CALLBACK,
WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER,
WebViewFeature.WEB_MESSAGE_CALLBACK_ON_MESSAGE, WebViewFeature.WEB_MESSAGE_CALLBACK_ON_MESSAGE,
WebViewFeature.WEB_MESSAGE_LISTENER, WebViewFeature.WEB_MESSAGE_LISTENER,
WebViewFeature.WEB_MESSAGE_PORT_CLOSE, WebViewFeature.WEB_MESSAGE_PORT_CLOSE,
@ -308,6 +326,14 @@ class WebViewFeature {
///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`.
/// ///
///**Note**: This method is different from [isStartupFeatureSupported] and this
///method only accepts certain features.
///Please verify that the correct feature checking method is used for a particular feature.
///
///**Note**: If this method returns `false`, it is not safe to invoke the methods
///requiring the desired feature.
///Furthermore, if this method returns `false` for a particular feature, any callback guarded by that feature will not be invoked.
///
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewFeature#isFeatureSupported(java.lang.String) ///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewFeature#isFeatureSupported(java.lang.String)
static Future<bool> isFeatureSupported(WebViewFeature feature) async { static Future<bool> isFeatureSupported(WebViewFeature feature) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
@ -315,6 +341,28 @@ class WebViewFeature {
return await _channel.invokeMethod('isFeatureSupported', args); return await _channel.invokeMethod('isFeatureSupported', args);
} }
///Return whether a startup feature is supported at run-time.
///On devices running Android version `Build.VERSION_CODES.LOLLIPOP` and higher,
///this will check whether a startup 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`.
///
///**Note**: This method is different from [isFeatureSupported] and this method only accepts startup features.
///Please verify that the correct feature checking method is used for a particular feature.
///
///**Note**: If this method returns `false`, it is not safe to invoke the methods requiring the desired feature.
///Furthermore, if this method returns `false` for a particular feature,
///any callback guarded by that feature will not be invoked.
///
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewFeature#isFeatureSupported(java.lang.String)
static Future<bool> isStartupFeatureSupported(
WebViewFeature startupFeature) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("startupFeature", () => startupFeature.toNativeValue());
return await _channel.invokeMethod('isStartupFeatureSupported', args);
}
///Gets [String] value. ///Gets [String] value.
String toValue() => _value; String toValue() => _value;
@ -345,7 +393,7 @@ class AndroidWebViewFeature {
String value, Function nativeValue) => String value, Function nativeValue) =>
AndroidWebViewFeature._internal(value, nativeValue()); AndroidWebViewFeature._internal(value, nativeValue());
///This feature covers [InAppWebViewSettings.algorithmicDarkeningAllowed]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.algorithmicDarkeningAllowed].
static const ALGORITHMIC_DARKENING = AndroidWebViewFeature._internal( static const ALGORITHMIC_DARKENING = AndroidWebViewFeature._internal(
'ALGORITHMIC_DARKENING', 'ALGORITHMIC_DARKENING'); 'ALGORITHMIC_DARKENING', 'ALGORITHMIC_DARKENING');
@ -358,11 +406,11 @@ class AndroidWebViewFeature {
AndroidWebViewFeature._internal( AndroidWebViewFeature._internal(
'DISABLED_ACTION_MODE_MENU_ITEMS', 'DISABLED_ACTION_MODE_MENU_ITEMS'); 'DISABLED_ACTION_MODE_MENU_ITEMS', 'DISABLED_ACTION_MODE_MENU_ITEMS');
///This feature covers [UserScriptInjectionTime.AT_DOCUMENT_START]. ///Feature for [isFeatureSupported]. This feature covers [UserScriptInjectionTime.AT_DOCUMENT_START].
static const DOCUMENT_START_SCRIPT = AndroidWebViewFeature._internal( static const DOCUMENT_START_SCRIPT = AndroidWebViewFeature._internal(
'DOCUMENT_START_SCRIPT', 'DOCUMENT_START_SCRIPT'); 'DOCUMENT_START_SCRIPT', 'DOCUMENT_START_SCRIPT');
///This feature covers [InAppWebViewSettings.enterpriseAuthenticationAppLinkPolicyEnabled]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.enterpriseAuthenticationAppLinkPolicyEnabled].
static const ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY = static const ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY =
AndroidWebViewFeature._internal( AndroidWebViewFeature._internal(
'ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY', 'ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY',
@ -412,7 +460,7 @@ class AndroidWebViewFeature {
static const RECEIVE_WEB_RESOURCE_ERROR = AndroidWebViewFeature._internal( static const RECEIVE_WEB_RESOURCE_ERROR = AndroidWebViewFeature._internal(
'RECEIVE_WEB_RESOURCE_ERROR', 'RECEIVE_WEB_RESOURCE_ERROR'); 'RECEIVE_WEB_RESOURCE_ERROR', 'RECEIVE_WEB_RESOURCE_ERROR');
///This feature covers [InAppWebViewSettings.requestedWithHeaderMode]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.requestedWithHeaderMode].
static const REQUESTED_WITH_HEADER_CONTROL = AndroidWebViewFeature._internal( static const REQUESTED_WITH_HEADER_CONTROL = AndroidWebViewFeature._internal(
'REQUESTED_WITH_HEADER_CONTROL', 'REQUESTED_WITH_HEADER_CONTROL'); 'REQUESTED_WITH_HEADER_CONTROL', 'REQUESTED_WITH_HEADER_CONTROL');
@ -486,7 +534,7 @@ class AndroidWebViewFeature {
static const START_SAFE_BROWSING = AndroidWebViewFeature._internal( static const START_SAFE_BROWSING = AndroidWebViewFeature._internal(
'START_SAFE_BROWSING', 'START_SAFE_BROWSING'); 'START_SAFE_BROWSING', 'START_SAFE_BROWSING');
///This feature covers [InAppWebViewSettings.willSuppressErrorPage]. ///Feature for [isFeatureSupported]. This feature covers [InAppWebViewSettings.willSuppressErrorPage].
static const SUPPRESS_ERROR_PAGE = AndroidWebViewFeature._internal( static const SUPPRESS_ERROR_PAGE = AndroidWebViewFeature._internal(
'SUPPRESS_ERROR_PAGE', 'SUPPRESS_ERROR_PAGE'); 'SUPPRESS_ERROR_PAGE', 'SUPPRESS_ERROR_PAGE');

View File

@ -30,6 +30,7 @@ import 'in_app_webview_keep_alive.dart';
import '../print_job/main.dart'; import '../print_job/main.dart';
import '../find_interaction/main.dart'; import '../find_interaction/main.dart';
import '../android/process_global_config.dart';
///List of forbidden names for JavaScript handlers. ///List of forbidden names for JavaScript handlers.
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@ -3870,6 +3871,24 @@ class InAppWebViewController extends ChannelController {
false; false;
} }
///Indicate that the current process does not intend to use WebView,
///and that an exception should be thrown if a WebView is created or any other
///methods in the `android.webkit` package are used.
///
///Applications with multiple processes may wish to call this in processes that
///are not intended to use WebView to avoid accidentally incurring the memory usage
///of initializing WebView in long-lived processes that have no need for it,
///and to prevent potential data directory conflicts (see [ProcessGlobalConfigSettings.dataDirectorySuffix]).
///
///**NOTE for Android**: available only on Android 28+.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.disableWebView](https://developer.android.com/reference/android/webkit/WebView.html#disableWebView()))
static Future<void> disableWebView() async {
Map<String, dynamic> args = <String, dynamic>{};
await _staticChannel.invokeMethod('disableWebView', args);
}
///Returns a Boolean value that indicates whether WebKit natively supports resources with the specified URL scheme. ///Returns a Boolean value that indicates whether WebKit natively supports resources with the specified URL scheme.
/// ///
///[urlScheme] represents the URL scheme associated with the resource. ///[urlScheme] represents the URL scheme associated with the resource.

View File

@ -2,5 +2,5 @@ import '../web_message/main.dart';
import '../web_uri.dart'; import '../web_uri.dart';
///The listener for handling [WebMessageListener] events sent by a `postMessage()` on the injected JavaScript object. ///The listener for handling [WebMessageListener] events sent by a `postMessage()` on the injected JavaScript object.
typedef void OnPostMessageCallback(String? message, WebUri? sourceOrigin, typedef void OnPostMessageCallback(WebMessage? message, WebUri? sourceOrigin,
bool isMainFrame, JavaScriptReplyProxy replyProxy); bool isMainFrame, JavaScriptReplyProxy replyProxy);

View File

@ -2,4 +2,4 @@ import '../web_message/main.dart';
///The listener for handling [WebMessagePort] events. ///The listener for handling [WebMessagePort] events.
///The message callback methods are called on the main thread. ///The message callback methods are called on the main thread.
typedef void WebMessageCallback(String? message); typedef void WebMessageCallback(WebMessage? message);

View File

@ -1,4 +1,4 @@
export 'web_message.dart' show WebMessage; export 'web_message.dart' show WebMessage, WebMessageType;
export 'web_message_port.dart' show WebMessagePort; export 'web_message_port.dart' show WebMessagePort;
export 'web_message_channel.dart' show WebMessageChannel; export 'web_message_channel.dart' show WebMessageChannel;
export 'web_message_listener.dart'; export 'web_message_listener.dart';

View File

@ -1,18 +1,47 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
import '../android/webview_feature.dart';
import 'web_message_port.dart'; import 'web_message_port.dart';
part 'web_message.g.dart'; part 'web_message.g.dart';
///The Dart representation of the HTML5 PostMessage event. ///The Dart representation of the HTML5 PostMessage event.
///See https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interfaces for definition of a MessageEvent in HTML5. ///See https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interfaces for definition of a MessageEvent in HTML5.
@ExchangeableObject() @ExchangeableObject(fromMapForceAllInline: true)
class WebMessage_ { class WebMessage_ {
///The data of the message. ///The data of the message.
String? data; dynamic data;
///The payload type of the message.
WebMessageType_ type;
///The ports that are sent with the message. ///The ports that are sent with the message.
List<WebMessagePort>? ports; List<WebMessagePort>? ports;
WebMessage_({this.data, this.ports}); @ExchangeableObjectConstructor()
WebMessage_({this.data, this.type = WebMessageType_.STRING, this.ports}) {
assert(((this.data == null || this.data is String) &&
this.type == WebMessageType_.STRING) ||
(this.data != null &&
this.data is Uint8List &&
this.type == WebMessageType_.ARRAY_BUFFER));
}
}
///The type corresponding to the [WebMessage].
@ExchangeableEnum()
class WebMessageType_ {
// ignore: unused_field
final int _value;
const WebMessageType_._internal(this._value);
///Indicates the payload of WebMessageCompat is String.
static const STRING = const WebMessageType_._internal(0);
///Indicates the payload of WebMessageCompat is JavaScript ArrayBuffer.
///
///**NOTE**: available only if [WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER] feature is supported.
static const ARRAY_BUFFER = const WebMessageType_._internal(1);
} }

View File

@ -2,6 +2,84 @@
part of 'web_message.dart'; part of 'web_message.dart';
// **************************************************************************
// ExchangeableEnumGenerator
// **************************************************************************
///The type corresponding to the [WebMessage].
class WebMessageType {
final int _value;
final int _nativeValue;
const WebMessageType._internal(this._value, this._nativeValue);
// ignore: unused_element
factory WebMessageType._internalMultiPlatform(
int value, Function nativeValue) =>
WebMessageType._internal(value, nativeValue());
///Indicates the payload of WebMessageCompat is JavaScript ArrayBuffer.
///
///**NOTE**: available only if [WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER] feature is supported.
static const ARRAY_BUFFER = WebMessageType._internal(1, 1);
///Indicates the payload of WebMessageCompat is String.
static const STRING = WebMessageType._internal(0, 0);
///Set of all values of [WebMessageType].
static final Set<WebMessageType> values = [
WebMessageType.ARRAY_BUFFER,
WebMessageType.STRING,
].toSet();
///Gets a possible [WebMessageType] instance from [int] value.
static WebMessageType? fromValue(int? value) {
if (value != null) {
try {
return WebMessageType.values
.firstWhere((element) => element.toValue() == value);
} catch (e) {
return null;
}
}
return null;
}
///Gets a possible [WebMessageType] instance from a native value.
static WebMessageType? fromNativeValue(int? value) {
if (value != null) {
try {
return WebMessageType.values
.firstWhere((element) => element.toNativeValue() == value);
} catch (e) {
return null;
}
}
return null;
}
///Gets [int] value.
int toValue() => _value;
///Gets [int] native value.
int toNativeValue() => _nativeValue;
@override
int get hashCode => _value.hashCode;
@override
bool operator ==(value) => value == _value;
@override
String toString() {
switch (_value) {
case 1:
return 'ARRAY_BUFFER';
case 0:
return 'STRING';
}
return _value.toString();
}
}
// ************************************************************************** // **************************************************************************
// ExchangeableObjectGenerator // ExchangeableObjectGenerator
// ************************************************************************** // **************************************************************************
@ -10,11 +88,20 @@ part of 'web_message.dart';
///See https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interfaces for definition of a MessageEvent in HTML5. ///See https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interfaces for definition of a MessageEvent in HTML5.
class WebMessage { class WebMessage {
///The data of the message. ///The data of the message.
String? data; dynamic data;
///The ports that are sent with the message. ///The ports that are sent with the message.
List<WebMessagePort>? ports; List<WebMessagePort>? ports;
WebMessage({this.data, this.ports});
///The payload type of the message.
WebMessageType type;
WebMessage({this.data, this.type = WebMessageType.STRING, this.ports}) {
assert(((this.data == null || this.data is String) &&
this.type == WebMessageType.STRING) ||
(this.data != null &&
this.data is Uint8List &&
this.type == WebMessageType.ARRAY_BUFFER));
}
///Gets a possible [WebMessage] instance from a [Map] value. ///Gets a possible [WebMessage] instance from a [Map] value.
static WebMessage? fromMap(Map<String, dynamic>? map) { static WebMessage? fromMap(Map<String, dynamic>? map) {
@ -26,6 +113,7 @@ class WebMessage {
ports: map['ports'] != null ports: map['ports'] != null
? List<WebMessagePort>.from(map['ports'].map((e) => e)) ? List<WebMessagePort>.from(map['ports'].map((e) => e))
: null, : null,
type: WebMessageType.fromNativeValue(map['type'])!,
); );
return instance; return instance;
} }
@ -35,6 +123,7 @@ class WebMessage {
return { return {
"data": data, "data": data,
"ports": ports?.map((e) => e.toMap()).toList(), "ports": ports?.map((e) => e.toMap()).toList(),
"type": type.toNativeValue(),
}; };
} }
@ -45,6 +134,6 @@ class WebMessage {
@override @override
String toString() { String toString() {
return 'WebMessage{data: $data, ports: $ports}'; return 'WebMessage{data: $data, ports: $ports, type: $type}';
} }
} }

View File

@ -1,6 +1,7 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/src/util.dart'; import 'package:flutter_inappwebview/src/util.dart';
import 'web_message_port.dart'; import 'web_message_port.dart';
import 'web_message.dart';
///The representation of the [HTML5 message channels](https://html.spec.whatwg.org/multipage/web-messaging.html#message-channels). ///The representation of the [HTML5 message channels](https://html.spec.whatwg.org/multipage/web-messaging.html#message-channels).
/// ///
@ -45,7 +46,10 @@ class WebMessageChannel extends ChannelController {
int index = call.arguments["index"]; int index = call.arguments["index"];
var port = index == 0 ? this.port1 : this.port2; var port = index == 0 ? this.port1 : this.port2;
if (port.onMessage != null) { if (port.onMessage != null) {
String? message = call.arguments["message"]; WebMessage? message = call.arguments["message"] != null
? WebMessage.fromMap(
call.arguments["message"].cast<String, dynamic>())
: null;
port.onMessage!(message); port.onMessage!(message);
} }
break; break;

View File

@ -3,6 +3,7 @@ import '../in_app_webview/in_app_webview_controller.dart';
import '../types/main.dart'; import '../types/main.dart';
import '../util.dart'; import '../util.dart';
import '../web_uri.dart'; import '../web_uri.dart';
import 'web_message.dart';
///This listener receives messages sent on the JavaScript object which was injected by [InAppWebViewController.addWebMessageListener]. ///This listener receives messages sent on the JavaScript object which was injected by [InAppWebViewController.addWebMessageListener].
/// ///
@ -56,7 +57,10 @@ class WebMessageListener extends ChannelController {
_replyProxy = new JavaScriptReplyProxy(this); _replyProxy = new JavaScriptReplyProxy(this);
} }
if (onPostMessage != null) { if (onPostMessage != null) {
String? message = call.arguments["message"]; WebMessage? message = call.arguments["message"] != null
? WebMessage.fromMap(
call.arguments["message"].cast<String, dynamic>())
: null;
WebUri? sourceOrigin = call.arguments["sourceOrigin"] != null WebUri? sourceOrigin = call.arguments["sourceOrigin"] != null
? WebUri(call.arguments["sourceOrigin"]) ? WebUri(call.arguments["sourceOrigin"])
: null; : null;
@ -107,10 +111,12 @@ class JavaScriptReplyProxy {
///Post a [message] to the injected JavaScript object which sent this [JavaScriptReplyProxy]. ///Post a [message] to the injected JavaScript object which sent this [JavaScriptReplyProxy].
/// ///
///If [message] is of type [WebMessageType.ARRAY_BUFFER], be aware that large byte buffers can lead to out-of-memory crashes on low-end devices.
///
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/JavaScriptReplyProxy#postMessage(java.lang.String) ///**Official Android API**: https://developer.android.com/reference/androidx/webkit/JavaScriptReplyProxy#postMessage(java.lang.String)
Future<void> postMessage(String message) async { Future<void> postMessage(WebMessage message) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('message', () => message); args.putIfAbsent('message', () => message.toMap());
await _webMessageListener.channel?.invokeMethod('postMessage', args); await _webMessageListener.channel?.invokeMethod('postMessage', args);
} }

View File

@ -2201,14 +2201,22 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
let body = message.body as! [String: Any?] let body = message.body as! [String: Any?]
let webMessageChannelId = body["webMessageChannelId"] as! String let webMessageChannelId = body["webMessageChannelId"] as! String
let index = body["index"] as! Int64 let index = body["index"] as! Int64
let webMessage = body["message"] as? String var webMessage: WebMessage? = nil
if let webMessageMap = body["message"] as? [String : Any?] {
webMessage = WebMessage.fromMap(map: webMessageMap)
}
if let webMessageChannel = webMessageChannels[webMessageChannelId] { if let webMessageChannel = webMessageChannels[webMessageChannelId] {
webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage) webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage)
} }
} else if message.name == "onWebMessageListenerPostMessageReceived" { } else if message.name == "onWebMessageListenerPostMessageReceived" {
let body = message.body as! [String: Any?] let body = message.body as! [String: Any?]
let jsObjectName = body["jsObjectName"] as! String let jsObjectName = body["jsObjectName"] as! String
let messageData = body["message"] as? String var webMessage: WebMessage? = nil
if let webMessageMap = body["message"] as? [String : Any?] {
webMessage = WebMessage.fromMap(map: webMessageMap)
}
if let webMessageListener = webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) { if let webMessageListener = webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) {
let isMainFrame = message.frameInfo.isMainFrame let isMainFrame = message.frameInfo.isMainFrame
@ -2225,7 +2233,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
if !scheme.isEmpty, !host.isEmpty { if !scheme.isEmpty, !host.isEmpty {
sourceOrigin = URL(string: "\(scheme)://\(host)\(port != 0 ? ":" + String(port) : "")") sourceOrigin = URL(string: "\(scheme)://\(host)\(port != 0 ? ":" + String(port) : "")")
} }
webMessageListener.channelDelegate?.onPostMessage(message: messageData, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame) webMessageListener.channelDelegate?.onPostMessage(message: webMessage, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame)
} }
} }
} }
@ -2514,11 +2522,11 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
} }
portsString = "[" + portArrayString.joined(separator: ", ") + "]" portsString = "[" + portArrayString.joined(separator: ", ") + "]"
} }
let data = message.data?.replacingOccurrences(of: "\'", with: "\\'") ?? "null"
let url = URL(string: targetOrigin)?.absoluteString ?? "*" let url = URL(string: targetOrigin)?.absoluteString ?? "*"
let source = """ let source = """
(function() { (function() {
window.postMessage('\(data)', '\(url)', \(portsString)); window.postMessage(\(message.jsData), '\(url)', \(portsString));
})(); })();
""" """
evaluateJavascript(source: source, completionHandler: completionHandler) evaluateJavascript(source: source, completionHandler: completionHandler)

View File

@ -26,8 +26,8 @@ public class WebMessageChannel : FlutterMethodCallDelegate {
self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel)
} }
self.ports = [ self.ports = [
WebMessagePort(name: "port1", webMessageChannel: self), WebMessagePort(name: "port1", index: 0, webMessageChannelId: self.id, webMessageChannel: self),
WebMessagePort(name: "port2", webMessageChannel: self) WebMessagePort(name: "port2", index: 1, webMessageChannelId: self.id, webMessageChannel: self)
] ]
} }

View File

@ -40,22 +40,20 @@ public class WebMessageChannelChannelDelegate : ChannelDelegate {
if let webView = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 { if let webView = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 {
let index = arguments!["index"] as! Int let index = arguments!["index"] as! Int
let port = ports[index] let port = ports[index]
let message = arguments!["message"] as! [String: Any?] var message = WebMessage.fromMap(map: arguments!["message"] as! [String: Any?])
var webMessagePorts: [WebMessagePort] = [] var ports: [WebMessagePort] = []
let portsMap = message["ports"] as? [[String: Any?]] if let notConnectedPorts = message.ports {
if let portsMap = portsMap { for notConnectedPort in notConnectedPorts {
for portMap in portsMap { if let webMessageChannel = webView.webMessageChannels[notConnectedPort.webMessageChannelId] {
let webMessageChannelId = portMap["webMessageChannelId"] as! String ports.append(webMessageChannel.ports[Int(notConnectedPort.index)])
let index = portMap["index"] as! Int
if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] {
webMessagePorts.append(webMessageChannel.ports[index])
} }
} }
} }
let webMessage = WebMessage(data: message["data"] as? String, ports: webMessagePorts) message.ports = ports
do { do {
try port.postMessage(message: webMessage) { (_) in try port.postMessage(message: message) { (_) in
result(true) result(true)
} }
} catch let error as NSError { } catch let error as NSError {
@ -86,10 +84,10 @@ public class WebMessageChannelChannelDelegate : ChannelDelegate {
} }
} }
public func onMessage(index: Int64, message: String?) { public func onMessage(index: Int64, message: WebMessage?) {
let arguments: [String:Any?] = [ let arguments: [String:Any?] = [
"index": index, "index": index,
"message": message "message": message?.toMap()
] ]
channel?.invokeMethod("onMessage", arguments: arguments) channel?.invokeMethod("onMessage", arguments: arguments)
} }

View File

@ -23,12 +23,13 @@ public class WebMessageListenerChannelDelegate : ChannelDelegate {
case "postMessage": case "postMessage":
if let webView = webMessageListener?.webView, let jsObjectName = webMessageListener?.jsObjectName { if let webView = webMessageListener?.webView, let jsObjectName = webMessageListener?.jsObjectName {
let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'") let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
let messageEscaped = (arguments!["message"] as! String).replacingOccurrences(of: "\'", with: "\\'") let message = WebMessage.fromMap(map: arguments!["message"] as! [String: Any?])
let source = """ let source = """
(function() { (function() {
var webMessageListener = window['\(jsObjectNameEscaped)']; var webMessageListener = window['\(jsObjectNameEscaped)'];
if (webMessageListener != null) { if (webMessageListener != null) {
var event = {data: '\(messageEscaped)'}; var event = {data: \(message.jsData)};
if (webMessageListener.onmessage != null) { if (webMessageListener.onmessage != null) {
webMessageListener.onmessage(event); webMessageListener.onmessage(event);
} }
@ -51,9 +52,9 @@ public class WebMessageListenerChannelDelegate : ChannelDelegate {
} }
} }
public func onPostMessage(message: String?, sourceOrigin: URL?, isMainFrame: Bool) { public func onPostMessage(message: WebMessage?, sourceOrigin: URL?, isMainFrame: Bool) {
let arguments: [String:Any?] = [ let arguments: [String:Any?] = [
"message": message, "message": message?.toMap(),
"sourceOrigin": sourceOrigin?.absoluteString, "sourceOrigin": sourceOrigin?.absoluteString,
"isMainFrame": isMainFrame "isMainFrame": isMainFrame
] ]

View File

@ -480,23 +480,21 @@ public class WebViewChannelDelegate : ChannelDelegate {
break break
case .postWebMessage: case .postWebMessage:
if let webView = webView { if let webView = webView {
let message = arguments!["message"] as! [String: Any?] var message = WebMessage.fromMap(map: arguments!["message"] as! [String: Any?])
let targetOrigin = arguments!["targetOrigin"] as! String let targetOrigin = arguments!["targetOrigin"] as! String
var ports: [WebMessagePort] = [] var ports: [WebMessagePort] = []
let portsMap = message["ports"] as? [[String: Any?]] if let notConnectedPorts = message.ports {
if let portsMap = portsMap { for notConnectedPort in notConnectedPorts {
for portMap in portsMap { if let webMessageChannel = webView.webMessageChannels[notConnectedPort.webMessageChannelId] {
let webMessageChannelId = portMap["webMessageChannelId"] as! String ports.append(webMessageChannel.ports[Int(notConnectedPort.index)])
let index = portMap["index"] as! Int
if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] {
ports.append(webMessageChannel.ports[index])
} }
} }
} }
let webMessage = WebMessage(data: message["data"] as? String, ports: ports) message.ports = ports
do { do {
try webView.postWebMessage(message: webMessage, targetOrigin: targetOrigin) { (_) in try webView.postWebMessage(message: message, targetOrigin: targetOrigin) { (_) in
result(true) result(true)
} }
} catch let error as NSError { } catch let error as NSError {

View File

@ -13,7 +13,11 @@ function FlutterInAppWebViewWebMessageListener(jsObjectName) {
this.listeners = []; this.listeners = [];
this.onmessage = null; this.onmessage = null;
} }
FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(message) { FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(data) {
var message = {
"data": window.ArrayBuffer != null && data instanceof ArrayBuffer ? Array.from(new Uint8Array(data)) : (data != null ? data.toString() : null),
"type": window.ArrayBuffer != null && data instanceof ArrayBuffer ? 1 : 0
};
window.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message}); window.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message});
}; };
FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) { FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) {

View File

@ -6,17 +6,55 @@
// //
import Foundation import Foundation
import FlutterMacOS
public class WebMessage : NSObject { public class WebMessage : NSObject, Disposable {
var data: String? var data: Any?
var type: WebMessageType
var ports: [WebMessagePort]? var ports: [WebMessagePort]?
public init(data: String?, ports: [WebMessagePort]?) { var jsData: String {
var jsData: String = "null"
if let messageData = data {
if type == .arrayBuffer, let messageDataArrayBuffer = messageData as? FlutterStandardTypedData {
jsData = "new Uint8Array(\(Array(messageDataArrayBuffer.data))).buffer"
} else if let messageDataString = messageData as? String {
jsData = "'\(messageDataString.replacingOccurrences(of: "\'", with: "\\'"))'"
}
}
return jsData
}
public init(data: Any?, type: WebMessageType, ports: [WebMessagePort]?) {
self.type = type
super.init() super.init()
self.data = data self.data = data
self.ports = ports self.ports = ports
} }
public static func fromMap(map: [String: Any?]) -> WebMessage {
let portMapList = map["ports"] as? [[String: Any?]]
var ports: [WebMessagePort]? = nil
if let portMapList = portMapList, !portMapList.isEmpty {
ports = []
portMapList.forEach { (portMap) in
ports?.append(WebMessagePort.fromMap(map: portMap))
}
}
return WebMessage(
data: map["data"] as? Any,
type: WebMessageType.init(rawValue: map["type"] as! Int)!,
ports: ports)
}
public func toMap () -> [String: Any?] {
return [
"data": type == .arrayBuffer && data is [UInt8] ? Data(data as! [UInt8]) : data,
"type": type.rawValue
]
}
public func dispose() { public func dispose() {
ports?.removeAll() ports?.removeAll()
} }
@ -26,3 +64,8 @@ public class WebMessage : NSObject {
dispose() dispose()
} }
} }
public enum WebMessageType: Int {
case string = 0
case arrayBuffer = 1
}

View File

@ -9,13 +9,17 @@ import Foundation
public class WebMessagePort : NSObject { public class WebMessagePort : NSObject {
var name: String var name: String
var index: Int64
var webMessageChannelId: String
var webMessageChannel: WebMessageChannel? var webMessageChannel: WebMessageChannel?
var isClosed = false var isClosed = false
var isTransferred = false var isTransferred = false
var isStarted = false var isStarted = false
public init(name: String, webMessageChannel: WebMessageChannel) { public init(name: String, index: Int64, webMessageChannelId: String, webMessageChannel: WebMessageChannel?) {
self.name = name self.name = name
self.index = index
self.webMessageChannelId = webMessageChannelId
super.init() super.init()
self.webMessageChannel = webMessageChannel self.webMessageChannel = webMessageChannel
} }
@ -35,7 +39,10 @@ public class WebMessagePort : NSObject {
window.webkit.messageHandlers["onWebMessagePortMessageReceived"].postMessage({ window.webkit.messageHandlers["onWebMessagePortMessageReceived"].postMessage({
"webMessageChannelId": "\(webMessageChannel.id)", "webMessageChannelId": "\(webMessageChannel.id)",
"index": \(String(index)), "index": \(String(index)),
"message": event.data "message": {
"data": window.ArrayBuffer != null && event.data instanceof ArrayBuffer ? Array.from(new Uint8Array(event.data)) : (event.data != null ? event.data.toString() : null),
"type": window.ArrayBuffer != null && event.data instanceof ArrayBuffer ? 1 : 0
}
}); });
} }
} }
@ -71,12 +78,12 @@ public class WebMessagePort : NSObject {
} }
portsString = "[" + portArrayString.joined(separator: ", ") + "]" portsString = "[" + portArrayString.joined(separator: ", ") + "]"
} }
let data = message.data?.replacingOccurrences(of: "\'", with: "\\'") ?? "null"
let source = """ let source = """
(function() { (function() {
var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"]; var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"];
if (webMessageChannel != null) { if (webMessageChannel != null) {
webMessageChannel.\(self.name).postMessage('\(data)', \(portsString)); webMessageChannel.\(self.name).postMessage(\(message.jsData), \(portsString));
} }
})(); })();
""" """
@ -111,6 +118,23 @@ public class WebMessagePort : NSObject {
} }
} }
public static func fromMap(map: [String: Any?]) -> WebMessagePort {
let index = map["index"] as! Int64
return WebMessagePort(
name: "port\(String(index + 1))",
index: index,
webMessageChannelId: map["webMessageChannelId"] as! String,
webMessageChannel: nil)
}
public func toMap () -> [String: Any?] {
return [
"name": name,
"index": index,
"webMessageChannelId": webMessageChannelId
]
}
public func dispose() { public func dispose() {
isClosed = true isClosed = true
webMessageChannel = nil webMessageChannel = nil

View File

@ -1,6 +1,6 @@
name: flutter_inappwebview name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
version: 6.0.0-beta.27 version: 6.0.0-beta.28
homepage: https://inappwebview.dev/ homepage: https://inappwebview.dev/
repository: https://github.com/pichillilorenzo/flutter_inappwebview repository: https://github.com/pichillilorenzo/flutter_inappwebview
issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues
@ -21,7 +21,7 @@ dependencies:
flutter_web_plugins: flutter_web_plugins:
sdk: flutter sdk: flutter
js: ^0.6.4 js: ^0.6.4
flutter_inappwebview_internal_annotations: ^1.1.0 flutter_inappwebview_internal_annotations: ^1.1.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: