Added Android pull-to-refresh setSize method and size option, Updated Android androidx.webkit:webkit to 1.4.0, androidx.browser:browser to 1.3.0, androidx.appcompat:appcompat to 1.2.0, updated docs
This commit is contained in:
parent
6f356be623
commit
97edbe158f
|
@ -2,8 +2,10 @@
|
|||
|
||||
- Added `WebMessageChannel` and `WebMessageListener` features
|
||||
- Added `canScrollVertically` and `canScrollHorizontally` webview methods
|
||||
- Added Android pull-to-refresh `setSize` method and `size` option
|
||||
- `AndroidInAppWebViewController.getCurrentWebViewPackage` is available now starting from Android API 21+
|
||||
- Updated Android Gradle distributionUrl version to `5.6.4`
|
||||
- Updated Android `androidx.webkit:webkit` to `1.4.0`, `androidx.browser:browser` to `1.3.0`, `androidx.appcompat:appcompat` to `1.2.0`
|
||||
- Attempt to fix "InAppBrowserActivity.onCreate NullPointerException - Attempt to invoke virtual method 'java.lang.String android.os.Bundle.getString(java.lang.String)' on a null object reference" [#665](https://github.com/pichillilorenzo/flutter_inappwebview/issues/665)
|
||||
- Fixed "[iOS] Application crashes when processing onCreateWindow" [#579](https://github.com/pichillilorenzo/flutter_inappwebview/issues/579)
|
||||
- Fixed wrong mapping of `NavigationAction` class on Android for `androidHasGesture` and `androidIsRedirect` properties
|
||||
|
|
|
@ -45,9 +45,9 @@ android {
|
|||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation 'androidx.webkit:webkit:1.3.0'
|
||||
implementation 'androidx.browser:browser:1.2.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0-rc02'
|
||||
implementation 'androidx.webkit:webkit:1.4.0'
|
||||
implementation 'androidx.browser:browser:1.3.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.squareup.okhttp3:mockwebserver:3.14.7'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
}
|
||||
|
|
|
@ -229,8 +229,7 @@ public class InAppWebViewMethodHandler implements MethodChannel.MethodCallHandle
|
|||
result.success((webView != null) ? webView.getCopyBackForwardList() : null);
|
||||
break;
|
||||
case "startSafeBrowsing":
|
||||
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 &&
|
||||
WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
|
||||
if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
|
||||
WebViewCompat.startSafeBrowsing(webView.getContext(), new ValueCallback<Boolean>() {
|
||||
@Override
|
||||
public void onReceiveValue(Boolean success) {
|
||||
|
@ -514,16 +513,14 @@ public class InAppWebViewMethodHandler implements MethodChannel.MethodCallHandle
|
|||
}
|
||||
break;
|
||||
case "createWebMessageChannel":
|
||||
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||
WebViewFeature.isFeatureSupported(WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL)) {
|
||||
if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL)) {
|
||||
result.success(webView.createCompatWebMessageChannel().toMap());
|
||||
} else {
|
||||
result.success(null);
|
||||
}
|
||||
break;
|
||||
case "postWebMessage":
|
||||
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||
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");
|
||||
String targetOrigin = (String) call.argument("targetOrigin");
|
||||
List<WebMessagePortCompat> ports = new ArrayList<>();
|
||||
|
|
|
@ -12,8 +12,10 @@ import androidx.webkit.WebViewFeature;
|
|||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
|
@ -48,13 +50,22 @@ public class InAppWebViewStatic implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
break;
|
||||
case "getSafeBrowsingPrivacyPolicyUrl":
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 && WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL)) {
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL)) {
|
||||
result.success(WebViewCompat.getSafeBrowsingPrivacyPolicyUrl().toString());
|
||||
} else
|
||||
result.success(null);
|
||||
break;
|
||||
case "setSafeBrowsingWhitelist":
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 && WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_WHITELIST)) {
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_ALLOWLIST)) {
|
||||
Set<String> hosts = new HashSet<>((List<String>) call.argument("hosts"));
|
||||
WebViewCompat.setSafeBrowsingAllowlist(hosts, new ValueCallback<Boolean>() {
|
||||
@Override
|
||||
public void onReceiveValue(Boolean value) {
|
||||
result.success(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_WHITELIST)) {
|
||||
List<String> hosts = (List<String>) call.argument("hosts");
|
||||
WebViewCompat.setSafeBrowsingWhitelist(hosts, new ValueCallback<Boolean>() {
|
||||
@Override
|
||||
|
@ -62,8 +73,7 @@ public class InAppWebViewStatic implements MethodChannel.MethodCallHandler {
|
|||
result.success(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
} else
|
||||
result.success(false);
|
||||
break;
|
||||
case "getCurrentWebViewPackage":
|
||||
|
|
|
@ -1619,7 +1619,6 @@ final public class InAppWebView extends InputAwareWebView {
|
|||
return computeHorizontalScrollRange() > computeHorizontalScrollExtent();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public WebMessageChannel createCompatWebMessageChannel() {
|
||||
String id = UUID.randomUUID().toString();
|
||||
WebMessageChannel webMessageChannel = new WebMessageChannel(id, this);
|
||||
|
|
|
@ -71,6 +71,8 @@ public class PullToRefreshLayout extends SwipeRefreshLayout implements MethodCha
|
|||
setDistanceToTriggerSync(options.distanceToTriggerSync);
|
||||
if (options.slingshotDistance != null)
|
||||
setSlingshotDistance(options.slingshotDistance);
|
||||
if (options.size != null)
|
||||
setSize(options.size);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -124,6 +126,13 @@ public class PullToRefreshLayout extends SwipeRefreshLayout implements MethodCha
|
|||
case "getDefaultSlingshotDistance":
|
||||
result.success(SwipeRefreshLayout.DEFAULT_SLINGSHOT_DISTANCE);
|
||||
break;
|
||||
case "setSize":
|
||||
{
|
||||
Integer size = (Integer) call.argument("size");
|
||||
setSize(size);
|
||||
}
|
||||
result.success(true);
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ public class PullToRefreshOptions implements Options<PullToRefreshLayout> {
|
|||
public Integer distanceToTriggerSync;
|
||||
@Nullable
|
||||
public Integer slingshotDistance;
|
||||
@Nullable
|
||||
public Integer size;
|
||||
|
||||
public PullToRefreshOptions parse(Map<String, Object> options) {
|
||||
for (Map.Entry<String, Object> pair : options.entrySet()) {
|
||||
|
@ -44,6 +46,9 @@ public class PullToRefreshOptions implements Options<PullToRefreshLayout> {
|
|||
case "slingshotDistance":
|
||||
slingshotDistance = (Integer) value;
|
||||
break;
|
||||
case "size":
|
||||
size = (Integer) value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,6 +62,7 @@ public class PullToRefreshOptions implements Options<PullToRefreshLayout> {
|
|||
options.put("backgroundColor", backgroundColor);
|
||||
options.put("distanceToTriggerSync", distanceToTriggerSync);
|
||||
options.put("slingshotDistance", slingshotDistance);
|
||||
options.put("size", size);
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"android":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.5-nullsafety/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.2.0-nullsafety/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.1.0-nullsafety.3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-03-13 14:56:36.936663","version":"2.1.0-10.0.pre"}
|
||||
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"android":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.5-nullsafety/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.2.0-nullsafety/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.1.0-nullsafety.3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-03-19 15:15:51.579916","version":"2.1.0-10.0.pre"}
|
|
@ -4908,6 +4908,12 @@ setTimeout(function() {
|
|||
final pullToRefreshController = PullToRefreshController(
|
||||
options: PullToRefreshOptions(
|
||||
color: Colors.blue,
|
||||
size: AndroidPullToRefreshSize.DEFAULT,
|
||||
backgroundColor: Colors.grey,
|
||||
enabled: true,
|
||||
slingshotDistance: 150,
|
||||
distanceToTriggerSync: 150,
|
||||
attributedTitle: IOSNSAttributedString(string: "test")
|
||||
),
|
||||
onRefresh: () {
|
||||
|
||||
|
@ -4938,6 +4944,119 @@ setTimeout(function() {
|
|||
expect(currentUrl, 'https://github.com/flutter');
|
||||
});
|
||||
|
||||
group('WebMessage', () {
|
||||
testWidgets('WebMessageChannel', (WidgetTester tester) async {
|
||||
final Completer 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(input.value);" />Send</button>
|
||||
<br />
|
||||
<input id="input" type="text" value="JavaScript To Native" />
|
||||
|
||||
<script>
|
||||
var port;
|
||||
window.addEventListener('message', function(event) {
|
||||
if (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: message! + " and back"));
|
||||
});
|
||||
await controller.postWebMessage(message: WebMessage(data: "capturePort", ports: [port2]), targetOrigin: Uri.parse("*"));
|
||||
await controller.evaluateJavascript(source: "document.getElementById('button').click();");
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
await controllerCompleter.future;
|
||||
|
||||
final String message = await webMessageCompleter.future;
|
||||
expect(message, 'JavaScript To Native and back');
|
||||
});
|
||||
|
||||
testWidgets('WebMessageListener', (WidgetTester tester) async {
|
||||
final Completer 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) {
|
||||
expect(sourceOrigin.toString(), "https://www.example.com");
|
||||
expect(isMainFrame, true);
|
||||
|
||||
replyProxy.postMessage(message! + " and back");
|
||||
},
|
||||
));
|
||||
controllerCompleter.complete(controller);
|
||||
},
|
||||
onConsoleMessage: (controller, consoleMessage) {
|
||||
webMessageCompleter.complete(consoleMessage.message);
|
||||
},
|
||||
onLoadStop: (controller, url) async {
|
||||
if (url.toString() == "https://www.example.com/") {
|
||||
pageLoaded.complete();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
final controller = await controllerCompleter.future;
|
||||
await controller.loadUrl(urlRequest: URLRequest(url: Uri.parse("https://www.example.com/")));
|
||||
await pageLoaded.future;
|
||||
|
||||
await controller.evaluateJavascript(source: """
|
||||
myTestObj.addEventListener('message', function(event) {
|
||||
console.log(event.data);
|
||||
});
|
||||
myTestObj.postMessage('JavaScript To Native');
|
||||
""");
|
||||
|
||||
final String message = await webMessageCompleter.future;
|
||||
expect(message, 'JavaScript To Native and back');
|
||||
});
|
||||
});
|
||||
|
||||
group('android methods', () {
|
||||
testWidgets('clearSslPreferences', (WidgetTester tester) async {
|
||||
final Completer controllerCompleter =
|
||||
|
|
|
@ -152,7 +152,8 @@ class AndroidWebViewFeature {
|
|||
const AndroidWebViewFeature._internal(
|
||||
"SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL");
|
||||
|
||||
///
|
||||
///Use [SAFE_BROWSING_ALLOWLIST] instead.
|
||||
@Deprecated('Use `SAFE_BROWSING_ALLOWLIST` instead')
|
||||
static const SAFE_BROWSING_WHITELIST =
|
||||
const AndroidWebViewFeature._internal("SAFE_BROWSING_WHITELIST");
|
||||
|
||||
|
|
|
@ -37,7 +37,9 @@ class ChromeSafariBrowserNotOpenedException implements Exception {
|
|||
///`android.support.customtabs.action.CustomTabsService` in your `AndroidManifest.xml`
|
||||
///(you can read more about it here: https://developers.google.com/web/android/custom-tabs/best-practices#applications_targeting_android_11_api_level_30_or_above).
|
||||
class ChromeSafariBrowser {
|
||||
late String id;
|
||||
///View ID used internally.
|
||||
late final String id;
|
||||
|
||||
Map<int, ChromeSafariBrowserMenuItem> _menuItems = new HashMap();
|
||||
bool _isOpened = false;
|
||||
late MethodChannel _channel;
|
||||
|
|
|
@ -41,7 +41,7 @@ class InAppBrowserNotOpenedException implements Exception {
|
|||
///This class uses the native WebView of the platform.
|
||||
///The [webViewController] field can be used to access the [InAppWebViewController] API.
|
||||
class InAppBrowser {
|
||||
///View ID.
|
||||
///View ID used internally.
|
||||
late final String id;
|
||||
|
||||
///Context menu used by the browser. It should be set before opening the browser.
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
|
|||
import '../_static_channel.dart';
|
||||
|
||||
import '../../types.dart';
|
||||
import '../../android/webview_feature.dart';
|
||||
|
||||
///Class represents the Android controller that contains only android-specific methods for the WebView.
|
||||
class AndroidInAppWebViewController {
|
||||
|
@ -21,10 +22,10 @@ class AndroidInAppWebViewController {
|
|||
///URL loads are not guaranteed to be protected by Safe Browsing until after the this method returns true.
|
||||
///Safe Browsing is not fully supported on all devices. For those devices this method will returns false.
|
||||
///
|
||||
///This should not be called if Safe Browsing has been disabled by manifest tag
|
||||
///or [AndroidInAppWebViewOptions.safeBrowsingEnabled]. This prepares resources used for Safe Browsing.
|
||||
///This should not be called if Safe Browsing has been disabled by manifest tag or [AndroidInAppWebViewOptions.safeBrowsingEnabled].
|
||||
///This prepares resources used for Safe Browsing.
|
||||
///
|
||||
///**NOTE**: available only on Android 27+.
|
||||
///This method should only be called if [AndroidWebViewFeature.isFeatureSupported] returns `true` for [AndroidWebViewFeature.START_SAFE_BROWSING].
|
||||
///
|
||||
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#startSafeBrowsing(android.content.Context,%20android.webkit.ValueCallback%3Cjava.lang.Boolean%3E)
|
||||
Future<bool> startSafeBrowsing() async {
|
||||
|
@ -134,7 +135,7 @@ class AndroidInAppWebViewController {
|
|||
|
||||
///Returns a URL pointing to the privacy policy for Safe Browsing reporting.
|
||||
///
|
||||
///**NOTE**: available only on Android 27+.
|
||||
///This method should only be called if [AndroidWebViewFeature.isFeatureSupported] returns `true` for [AndroidWebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL].
|
||||
///
|
||||
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewCompat#getSafeBrowsingPrivacyPolicyUrl()
|
||||
static Future<Uri?> getSafeBrowsingPrivacyPolicyUrl() async {
|
||||
|
@ -156,11 +157,11 @@ class AndroidInAppWebViewController {
|
|||
///
|
||||
///All other rules, including wildcards, are invalid. The correct syntax for hosts is defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2).
|
||||
///
|
||||
///This method should only be called if [AndroidWebViewFeature.isFeatureSupported] returns `true` for [AndroidWebViewFeature.SAFE_BROWSING_ALLOWLIST].
|
||||
///
|
||||
///[hosts] represents the list of hosts. This value must never be `null`.
|
||||
///
|
||||
///**NOTE**: available only on Android 27+.
|
||||
///
|
||||
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewCompat#getSafeBrowsingPrivacyPolicyUrl()
|
||||
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewCompat#setSafeBrowsingAllowlist(java.util.Set%3Cjava.lang.String%3E,%20android.webkit.ValueCallback%3Cjava.lang.Boolean%3E)
|
||||
static Future<bool> setSafeBrowsingWhitelist(
|
||||
{required List<String> hosts}) async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
|
|
|
@ -20,6 +20,7 @@ import '../web_storage/web_storage.dart';
|
|||
import '../util.dart';
|
||||
import '../web_message/web_message_channel.dart';
|
||||
import '../web_message/web_message_listener.dart';
|
||||
import '../android/webview_feature.dart';
|
||||
|
||||
import 'headless_in_app_webview.dart';
|
||||
import 'in_app_webview.dart';
|
||||
|
@ -2090,6 +2091,18 @@ class InAppWebViewController {
|
|||
return await _channel.invokeMethod('isSecureContext', args);
|
||||
}
|
||||
|
||||
///Creates a message channel to communicate with JavaScript and returns the message channel with ports that represent the endpoints of this message channel.
|
||||
///The HTML5 message channel functionality is described [here](https://html.spec.whatwg.org/multipage/comms.html#messagechannel).
|
||||
///
|
||||
///The returned message channels are entangled and already in started state.
|
||||
///
|
||||
///This method should be called when the page is loaded, for example, when the [WebView.onLoadStop] is fired, otherwise the [WebMessageChannel] won't work.
|
||||
///
|
||||
///**NOTE for Android**: This method should only be called if [AndroidWebViewFeature.isFeatureSupported] returns `true` for [AndroidWebViewFeature.CREATE_WEB_MESSAGE_CHANNEL].
|
||||
///
|
||||
///**NOTE for iOS**: This is implemented using Javascript.
|
||||
///
|
||||
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewCompat#createWebMessageChannel(android.webkit.WebView)
|
||||
Future<WebMessageChannel?> createWebMessageChannel() async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
Map<String, dynamic>? result = (await _channel.invokeMethod('createWebMessageChannel', args))
|
||||
|
@ -2097,6 +2110,16 @@ class InAppWebViewController {
|
|||
return WebMessageChannel.fromMap(result);
|
||||
}
|
||||
|
||||
///Post a message to main frame. The embedded application can restrict the messages to a certain target origin.
|
||||
///See [HTML5 spec](https://html.spec.whatwg.org/multipage/comms.html#posting-messages) for how target origin can be used.
|
||||
///
|
||||
///A target origin can be set as a wildcard ("*"). However this is not recommended.
|
||||
///
|
||||
///**NOTE for Android**: This method should only be called if [AndroidWebViewFeature.isFeatureSupported] returns `true` for [AndroidWebViewFeature.POST_WEB_MESSAGE].
|
||||
///
|
||||
///**NOTE for iOS**: This is implemented using Javascript.
|
||||
///
|
||||
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewCompat#postWebMessage(android.webkit.WebView,%20androidx.webkit.WebMessageCompat,%20android.net.Uri)
|
||||
Future<void> postWebMessage({required WebMessage message, Uri? targetOrigin}) async {
|
||||
if (targetOrigin == null) {
|
||||
targetOrigin = Uri.parse("");
|
||||
|
@ -2107,6 +2130,165 @@ class InAppWebViewController {
|
|||
await _channel.invokeMethod('postWebMessage', args);
|
||||
}
|
||||
|
||||
///Adds a [WebMessageListener] to the WebView and injects a JavaScript object into each frame that the [WebMessageListener] will listen on.
|
||||
///
|
||||
///The injected JavaScript object will be named [WebMessageListener.jsObjectName] in the global scope.
|
||||
///This will inject the JavaScript object in any frame whose origin matches [WebMessageListener.allowedOriginRules]
|
||||
///for every navigation after this call, and the JavaScript object will be available immediately when the page begins to load.
|
||||
///
|
||||
///Each [WebMessageListener.allowedOriginRules] entry must follow the format `SCHEME "://" [ HOSTNAME_PATTERN [ ":" PORT ] ]`, each part is explained in the below table:
|
||||
///
|
||||
///<table>
|
||||
/// <colgroup>
|
||||
/// <col width="25%">
|
||||
/// </colgroup>
|
||||
/// <tbody>
|
||||
/// <tr>
|
||||
/// <th>Rule</th>
|
||||
/// <th>Description</th>
|
||||
/// <th>Example</th>
|
||||
/// </tr>
|
||||
/// <tr>
|
||||
/// <td>http/https with hostname</td>
|
||||
/// <td><code translate="no" dir="ltr">SCHEME</code> is http or https; <code translate="no" dir="ltr">HOSTNAME_<wbr>PATTERN</code> is a regular hostname; <code translate="no" dir="ltr">PORT</code> is optional, when not present, the rule will match port <code translate="no" dir="ltr">80</code> for http and port
|
||||
/// <code translate="no" dir="ltr">443</code> for https.
|
||||
/// </td>
|
||||
/// <td>
|
||||
/// <ul>
|
||||
/// <li><code translate="no" dir="ltr">https://foobar.com:8080</code> - Matches https:// URL on port 8080, whose normalized
|
||||
/// host is foobar.com.
|
||||
/// </li>
|
||||
/// <li><code translate="no" dir="ltr">https://www.example.com</code> - Matches https:// URL on port 443, whose normalized host
|
||||
/// is www.example.com.
|
||||
/// </li>
|
||||
/// </ul>
|
||||
/// </td>
|
||||
/// </tr>
|
||||
/// <tr>
|
||||
/// <td>http/https with pattern matching</td>
|
||||
/// <td><code translate="no" dir="ltr">SCHEME</code> is http or https; <code translate="no" dir="ltr">HOSTNAME_<wbr>PATTERN</code> is a sub-domain matching
|
||||
/// pattern with a leading <code translate="no" dir="ltr">*.<wbr></code>; <code translate="no" dir="ltr">PORT</code> is optional, when not present, the rule will
|
||||
/// match port <code translate="no" dir="ltr">80</code> for http and port <code translate="no" dir="ltr">443</code> for https.
|
||||
/// </td>
|
||||
/// <td>
|
||||
/// <ul>
|
||||
/// <li><code translate="no" dir="ltr">https://*.example.com</code> - Matches https://calendar.example.com and
|
||||
/// https://foo.bar.example.com but not https://example.com.
|
||||
/// </li>
|
||||
/// <li><code translate="no" dir="ltr">https://*.example.com:8080</code> - Matches https://calendar.example.com:8080</li>
|
||||
/// </ul>
|
||||
/// </td>
|
||||
/// </tr>
|
||||
/// <tr>
|
||||
/// <td>http/https with IP literal</td>
|
||||
/// <td><code translate="no" dir="ltr">SCHEME</code> is https or https; <code translate="no" dir="ltr">HOSTNAME_<wbr>PATTERN</code> is IP literal; <code translate="no" dir="ltr">PORT</code> is
|
||||
/// optional, when not present, the rule will match port <code translate="no" dir="ltr">80</code> for http and port <code translate="no" dir="ltr">443</code>
|
||||
/// for https.
|
||||
/// </td>
|
||||
/// <td>
|
||||
/// <ul>
|
||||
/// <li><code translate="no" dir="ltr">https://127.0.0.1</code> - Matches https:// URL on port 443, whose IPv4 address is
|
||||
/// 127.0.0.1
|
||||
/// </li>
|
||||
/// <li><code translate="no" dir="ltr">https://[::1]</code> or <code translate="no" dir="ltr">https://[0:0::1]</code>- Matches any URL to the IPv6 loopback
|
||||
/// address with port 443.
|
||||
/// </li>
|
||||
/// <li><code translate="no" dir="ltr">https://[::1]:99</code> - Matches any https:// URL to the IPv6 loopback on port 99.</li>
|
||||
/// </ul>
|
||||
/// </td>
|
||||
/// </tr>
|
||||
/// <tr>
|
||||
/// <td>Custom scheme</td>
|
||||
/// <td><code translate="no" dir="ltr">SCHEME</code> is a custom scheme; <code translate="no" dir="ltr">HOSTNAME_<wbr>PATTERN</code> and <code translate="no" dir="ltr">PORT</code> must not be
|
||||
/// present.
|
||||
/// </td>
|
||||
/// <td>
|
||||
/// <ul>
|
||||
/// <li><code translate="no" dir="ltr">my-app-scheme://</code> - Matches any my-app-scheme:// URL.</li>
|
||||
/// </ul>
|
||||
/// </td>
|
||||
/// </tr>
|
||||
/// <tr>
|
||||
/// <td><code translate="no" dir="ltr">*</code></td>
|
||||
/// <td>Wildcard rule, matches any origin.</td>
|
||||
/// <td>
|
||||
/// <ul>
|
||||
/// <li><code translate="no" dir="ltr">*</code></li>
|
||||
/// </ul>
|
||||
/// </td>
|
||||
/// </tr>
|
||||
/// </tbody>
|
||||
///</table>
|
||||
///
|
||||
///Note that this is a powerful API, as the JavaScript object will be injected when the frame's origin matches any one of the allowed origins.
|
||||
///The HTTPS scheme is strongly recommended for security; allowing HTTP origins exposes the injected object to any potential network-based attackers.
|
||||
///If a wildcard "*" is provided, it will inject the JavaScript object to all frames.
|
||||
///A wildcard should only be used if the app wants **any** third party web page to be able to use the injected object.
|
||||
///When using a wildcard, the app must treat received messages as untrustworthy and validate any data carefully.
|
||||
///
|
||||
///This method can be called multiple times to inject multiple JavaScript objects.
|
||||
///
|
||||
///Let's say the injected JavaScript object is named `myObject`. We will have following methods on that object once it is available to use:
|
||||
///
|
||||
///```javascript
|
||||
/// // Web page (in JavaScript)
|
||||
/// // message needs to be a JavaScript String, MessagePorts is an optional parameter.
|
||||
/// myObject.postMessage(message[, MessagePorts]) // on Android
|
||||
/// myObject.postMessage(message) // on iOS
|
||||
///
|
||||
/// // To receive messages posted from the app side, assign a function to the "onmessage"
|
||||
/// // property. This function should accept a single "event" argument. "event" has a "data"
|
||||
/// // property, which is the message string from the app side.
|
||||
/// myObject.onmessage = function(event) { ... }
|
||||
///
|
||||
/// // To be compatible with DOM EventTarget's addEventListener, it accepts type and listener
|
||||
/// // parameters, where type can be only "message" type and listener can only be a JavaScript
|
||||
/// // function for myObject. An event object will be passed to listener with a "data" property,
|
||||
/// // which is the message string from the app side.
|
||||
/// myObject.addEventListener(type, listener)
|
||||
///
|
||||
/// // To be compatible with DOM EventTarget's removeEventListener, it accepts type and listener
|
||||
/// // parameters, where type can be only "message" type and listener can only be a JavaScript
|
||||
/// // function for myObject.
|
||||
/// myObject.removeEventListener(type, listener)
|
||||
///```
|
||||
///
|
||||
///We start the communication between JavaScript and the app from the JavaScript side.
|
||||
///In order to send message from the app to JavaScript, it needs to post a message from JavaScript first,
|
||||
///so the app will have a [JavaScriptReplyProxy] object to respond. Example:
|
||||
///
|
||||
///```javascript
|
||||
/// // Web page (in JavaScript)
|
||||
/// myObject.onmessage = function(event) {
|
||||
/// // prints "Got it!" when we receive the app's response.
|
||||
/// console.log(event.data);
|
||||
/// }
|
||||
/// myObject.postMessage("I'm ready!");
|
||||
///```
|
||||
///
|
||||
///```dart
|
||||
/// // Flutter App
|
||||
/// child: InAppWebView(
|
||||
/// onWebViewCreated: (controller) async {
|
||||
/// if (!Platform.isAndroid || await AndroidWebViewFeature.isFeatureSupported(AndroidWebViewFeature.WEB_MESSAGE_LISTENER)) {
|
||||
/// await controller.addWebMessageListener(WebMessageListener(
|
||||
/// jsObjectName: "myObject",
|
||||
/// onPostMessage: (message, sourceOrigin, isMainFrame, replyProxy) {
|
||||
/// // do something about message, sourceOrigin and isMainFrame.
|
||||
/// replyProxy.postMessage("Got it!");
|
||||
/// },
|
||||
/// ));
|
||||
/// }
|
||||
/// await controller.loadUrl(urlRequest: URLRequest(url: Uri.parse("https://www.example.com")));
|
||||
/// },
|
||||
/// ),
|
||||
///```
|
||||
///
|
||||
///**NOTE for Android**: This method should only be called if [AndroidWebViewFeature.isFeatureSupported] returns `true` for [AndroidWebViewFeature.WEB_MESSAGE_LISTENER].
|
||||
///
|
||||
///**NOTE for iOS**: This is implemented using Javascript.
|
||||
///
|
||||
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addWebMessageListener(android.webkit.WebView,%20java.lang.String,%20java.util.Set%3Cjava.lang.String%3E,%20androidx.webkit.WebViewCompat.WebMessageListener)
|
||||
Future<void> addWebMessageListener(WebMessageListener webMessageListener) async {
|
||||
assert(!_webMessageListenerObjNames.contains(webMessageListener.jsObjectName), "jsObjectName ${webMessageListener.jsObjectName} was already added.");
|
||||
_webMessageListenerObjNames.add(webMessageListener.jsObjectName);
|
||||
|
@ -2116,11 +2298,13 @@ class InAppWebViewController {
|
|||
await _channel.invokeMethod('addWebMessageListener', args);
|
||||
}
|
||||
|
||||
///Returns `true` if the webpage can scroll vertically, otherwise `false`.
|
||||
Future<bool> canScrollVertically() async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
return await _channel.invokeMethod('canScrollVertically', args);
|
||||
}
|
||||
|
||||
///Returns `true` if the webpage can scroll horizontally, otherwise `false`.
|
||||
Future<bool> canScrollHorizontally() async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
return await _channel.invokeMethod('canScrollHorizontally', args);
|
||||
|
|
|
@ -115,6 +115,15 @@ class PullToRefreshController {
|
|||
return await _channel?.invokeMethod('getDefaultSlingshotDistance', args);
|
||||
}
|
||||
|
||||
///Sets the size of the refresh indicator. One of [AndroidPullToRefreshSize.DEFAULT], or [AndroidPullToRefreshSize.LARGE].
|
||||
///
|
||||
///**NOTE**: Available only on Android.
|
||||
Future<void> setSize(AndroidPullToRefreshSize size) async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
args.putIfAbsent('size', () => size.toValue());
|
||||
await _channel?.invokeMethod('setSize', args);
|
||||
}
|
||||
|
||||
///Sets the styled title text to display in the refresh control.
|
||||
///
|
||||
///**NOTE**: Available only on iOS.
|
||||
|
|
|
@ -22,6 +22,11 @@ class PullToRefreshOptions {
|
|||
///**NOTE**: Available only on Android.
|
||||
int? slingshotDistance;
|
||||
|
||||
///The size of the refresh indicator.
|
||||
///
|
||||
///**NOTE**: Available only on Android.
|
||||
AndroidPullToRefreshSize? size;
|
||||
|
||||
///The title text to display in the refresh control.
|
||||
///
|
||||
///**NOTE**: Available only on iOS.
|
||||
|
@ -33,6 +38,7 @@ class PullToRefreshOptions {
|
|||
this.backgroundColor,
|
||||
this.distanceToTriggerSync,
|
||||
this.slingshotDistance,
|
||||
this.size,
|
||||
this.attributedTitle});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
|
@ -42,6 +48,7 @@ class PullToRefreshOptions {
|
|||
"backgroundColor": backgroundColor?.toHex(),
|
||||
"distanceToTriggerSync": distanceToTriggerSync,
|
||||
"slingshotDistance": slingshotDistance,
|
||||
"size": size?.toValue(),
|
||||
"attributedTitle": attributedTitle?.toMap() ?? {}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ import 'web_storage/web_storage.dart';
|
|||
import 'pull_to_refresh/pull_to_refresh_controller.dart';
|
||||
import 'pull_to_refresh/pull_to_refresh_options.dart';
|
||||
import 'util.dart';
|
||||
import 'web_message/web_message_listener.dart';
|
||||
import 'web_message/web_message_channel.dart';
|
||||
|
||||
///This type represents a callback, added with [InAppWebViewController.addJavaScriptHandler], that listens to post messages sent from JavaScript.
|
||||
///
|
||||
|
@ -30,6 +32,13 @@ import 'util.dart';
|
|||
///In this case, simply return data that you want to send and it will be automatically json encoded using [jsonEncode] from the `dart:convert` library.
|
||||
typedef dynamic JavaScriptHandlerCallback(List<dynamic> arguments);
|
||||
|
||||
///The listener for handling [WebMessageListener] events sent by a `postMessage()` on the injected JavaScript object.
|
||||
typedef void OnPostMessageCallback(String? message, Uri? sourceOrigin, bool isMainFrame, JavaScriptReplyProxy replyProxy);
|
||||
|
||||
///The listener for handling [WebMessagePort] events.
|
||||
///The message callback methods are called on the main thread.
|
||||
typedef void WebMessageCallback(String? message);
|
||||
|
||||
///Class representing the level of a console message.
|
||||
class ConsoleMessageLevel {
|
||||
final int _value;
|
||||
|
@ -6775,3 +6784,51 @@ class IOSNSAttributedStringTextEffectStyle {
|
|||
@override
|
||||
int get hashCode => _value.hashCode;
|
||||
}
|
||||
|
||||
///Android-specific class representing the size of the refresh indicator.
|
||||
class AndroidPullToRefreshSize {
|
||||
final int _value;
|
||||
|
||||
const AndroidPullToRefreshSize._internal(this._value);
|
||||
|
||||
static final Set<AndroidPullToRefreshSize> values = [
|
||||
AndroidPullToRefreshSize.DEFAULT,
|
||||
AndroidPullToRefreshSize.LARGE,
|
||||
].toSet();
|
||||
|
||||
static AndroidPullToRefreshSize? fromValue(int? value) {
|
||||
if (value != null) {
|
||||
try {
|
||||
return AndroidPullToRefreshSize.values
|
||||
.firstWhere((element) => element.toValue() == value);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int toValue() => _value;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
switch (_value) {
|
||||
case 0:
|
||||
return "LARGE";
|
||||
case 1:
|
||||
default:
|
||||
return "DEFAULT";
|
||||
}
|
||||
}
|
||||
|
||||
///Default size.
|
||||
static const DEFAULT = const AndroidPullToRefreshSize._internal(1);
|
||||
|
||||
///Large size.
|
||||
static const LARGE = const AndroidPullToRefreshSize._internal(0);
|
||||
|
||||
bool operator ==(value) => value == _value;
|
||||
|
||||
@override
|
||||
int get hashCode => _value.hashCode;
|
||||
}
|
|
@ -1,9 +1,17 @@
|
|||
import 'package:flutter/services.dart';
|
||||
import '../types.dart';
|
||||
import '../in_app_webview/in_app_webview_controller.dart';
|
||||
|
||||
///The representation of the [HTML5 message channels](https://html.spec.whatwg.org/multipage/web-messaging.html#message-channels).
|
||||
class WebMessageChannel {
|
||||
String id;
|
||||
WebMessagePort port1;
|
||||
WebMessagePort port2;
|
||||
///Message Channel ID used internally.
|
||||
final String id;
|
||||
|
||||
///The first [WebMessagePort] object of the channel.
|
||||
final WebMessagePort port1;
|
||||
|
||||
///The second [WebMessagePort] object of the channel.
|
||||
final WebMessagePort port2;
|
||||
|
||||
late MethodChannel _channel;
|
||||
|
||||
|
@ -44,23 +52,43 @@ class WebMessageChannel {
|
|||
}
|
||||
}
|
||||
|
||||
///The representation of the [HTML5 message ports](https://html.spec.whatwg.org/multipage/comms.html#messageport).
|
||||
///
|
||||
///A Message port represents one endpoint of a Message Channel. In Android webview, there is no separate Message Channel object.
|
||||
///When a message channel is created, both ports are tangled to each other and started.
|
||||
///See [InAppWebViewController.createWebMessageChannel] for creating a message channel.
|
||||
///
|
||||
///When a message port is first created or received via transfer, it does not have a [WebMessageCallback] to receive web messages.
|
||||
///On Android, the messages are queued until a [WebMessageCallback] is set.
|
||||
///
|
||||
///A message port should be closed when it is not used by the embedder application anymore.
|
||||
///A closed port cannot be transferred or cannot be reopened to send messages.
|
||||
///Close can be called multiple times.
|
||||
///
|
||||
///When a port is transferred to JavaScript, it cannot be used to send or receive messages at the Dart side anymore.
|
||||
///Different from HTML5 Spec, a port cannot be transferred if one of these has ever happened: i. a message callback was set, ii. a message was posted on it.
|
||||
///A transferred port cannot be closed by the application, since the ownership is also transferred.
|
||||
///
|
||||
///It is possible to transfer both ports of a channel to JavaScript, for example for communication between subframes.
|
||||
class WebMessagePort {
|
||||
late final int _index;
|
||||
|
||||
Function(String? message)? _onMessage;
|
||||
WebMessageCallback? _onMessage;
|
||||
late WebMessageChannel _webMessageChannel;
|
||||
|
||||
WebMessagePort({required int index}) {
|
||||
this._index = index;
|
||||
}
|
||||
|
||||
Future<void> setWebMessageCallback(Function(String? message)? onMessage) async {
|
||||
///Sets a callback to receive message events on the main thread.
|
||||
Future<void> setWebMessageCallback(WebMessageCallback? onMessage) async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
args.putIfAbsent('index', () => this._index);
|
||||
await _webMessageChannel._channel.invokeMethod('setWebMessageCallback', args);
|
||||
this._onMessage = onMessage;
|
||||
}
|
||||
|
||||
///Post a WebMessage to the entangled port.
|
||||
Future<void> postMessage(WebMessage message) async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
args.putIfAbsent('index', () => this._index);
|
||||
|
@ -68,6 +96,7 @@ class WebMessagePort {
|
|||
await _webMessageChannel._channel.invokeMethod('postMessage', args);
|
||||
}
|
||||
|
||||
///Close the message port and free any resources associated with it.
|
||||
Future<void> close() async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
args.putIfAbsent('index', () => this._index);
|
||||
|
@ -91,8 +120,13 @@ class WebMessagePort {
|
|||
}
|
||||
}
|
||||
|
||||
///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.
|
||||
class WebMessage {
|
||||
///The data of the message.
|
||||
String? data;
|
||||
|
||||
///The ports that are sent with the message.
|
||||
List<WebMessagePort>? ports;
|
||||
|
||||
WebMessage({this.data, this.ports});
|
||||
|
|
|
@ -1,10 +1,28 @@
|
|||
import 'package:flutter/services.dart';
|
||||
import '../in_app_webview/in_app_webview_controller.dart';
|
||||
import '../types.dart';
|
||||
|
||||
///This listener receives messages sent on the JavaScript object which was injected by [InAppWebViewController.addWebMessageListener].
|
||||
class WebMessageListener {
|
||||
String jsObjectName;
|
||||
///The name for the injected JavaScript object.
|
||||
final String jsObjectName;
|
||||
|
||||
///A set of matching rules for the allowed origins.
|
||||
late Set<String> allowedOriginRules;
|
||||
|
||||
JavaScriptReplyProxy? _replyProxy;
|
||||
Function(String? message, Uri? sourceOrigin, bool isMainFrame, JavaScriptReplyProxy replyProxy)? onPostMessage;
|
||||
|
||||
///Event that receives a message sent by a `postMessage()` on the injected JavaScript object.
|
||||
///
|
||||
///Note that when the frame is `file:` or `content:` origin, the value of [sourceOrigin] is `null`.
|
||||
///
|
||||
///- [message] represents the message from JavaScript.
|
||||
///- [sourceOrigin] represents the origin of the frame that the message is from.
|
||||
///- [isMainFrame] is `true` if the message is from the main frame.
|
||||
///- [replyProxy] is used to reply back to the JavaScript object.
|
||||
///
|
||||
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewCompat.WebMessageListener#onPostMessage(android.webkit.WebView,%20androidx.webkit.WebMessageCompat,%20android.net.Uri,%20boolean,%20androidx.webkit.JavaScriptReplyProxy)
|
||||
OnPostMessageCallback? onPostMessage;
|
||||
|
||||
late MethodChannel _channel;
|
||||
|
||||
|
@ -52,6 +70,11 @@ class WebMessageListener {
|
|||
}
|
||||
}
|
||||
|
||||
///This class represents the JavaScript object injected by [InAppWebViewController.addWebMessageListener].
|
||||
///An instance will be given by [WebMessageListener.onPostMessage].
|
||||
///The app can use `postMessage(String)` to talk to the JavaScript context.
|
||||
///
|
||||
///There is a 1:1 relationship between this object and the JavaScript object in a frame.
|
||||
class JavaScriptReplyProxy {
|
||||
late WebMessageListener _webMessageListener;
|
||||
|
||||
|
@ -59,6 +82,9 @@ class JavaScriptReplyProxy {
|
|||
this._webMessageListener = webMessageListener;
|
||||
}
|
||||
|
||||
///Post a [message] to the injected JavaScript object which sent this [JavaScriptReplyProxy].
|
||||
///
|
||||
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/JavaScriptReplyProxy#postMessage(java.lang.String)
|
||||
Future<void> postMessage(String message) async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
args.putIfAbsent('message', () => message);
|
||||
|
|
Loading…
Reference in New Issue