addWebMessageListener method Null safety

Future<void> addWebMessageListener(
  1. WebMessageListener webMessageListener
)

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:

Rule Description Example
http/https with hostname SCHEME is http or https; HOSTNAME_PATTERN is a regular hostname; PORT is optional, when not present, the rule will match port 80 for http and port 443 for https.
  • https://foobar.com:8080 - Matches https:// URL on port 8080, whose normalized host is foobar.com.
  • https://www.example.com - Matches https:// URL on port 443, whose normalized host is www.example.com.
http/https with pattern matching SCHEME is http or https; HOSTNAME_PATTERN is a sub-domain matching pattern with a leading *.; PORT is optional, when not present, the rule will match port 80 for http and port 443 for https.
  • https://*.example.com - Matches https://calendar.example.com and https://foo.bar.example.com but not https://example.com.
  • https://*.example.com:8080 - Matches https://calendar.example.com:8080
http/https with IP literal SCHEME is https or https; HOSTNAME_PATTERN is IP literal; PORT is optional, when not present, the rule will match port 80 for http and port 443 for https.
  • https://127.0.0.1 - Matches https:// URL on port 443, whose IPv4 address is 127.0.0.1
  • https://[::1] or https://[0:0::1]- Matches any URL to the IPv6 loopback address with port 443.
  • https://[::1]:99 - Matches any https:// URL to the IPv6 loopback on port 99.
Custom scheme SCHEME is a custom scheme; HOSTNAME_PATTERN and PORT must not be present.
  • my-app-scheme:// - Matches any my-app-scheme:// URL.
* Wildcard rule, matches any origin.
  • *

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:

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

// 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!");
// 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)

Implementation

Future<void> addWebMessageListener(
    WebMessageListener webMessageListener) async {
  assert(
      !_webMessageListenerObjNames.contains(webMessageListener.jsObjectName),
      "jsObjectName ${webMessageListener.jsObjectName} was already added.");
  _webMessageListenerObjNames.add(webMessageListener.jsObjectName);

  Map<String, dynamic> args = <String, dynamic>{};
  args.putIfAbsent('webMessageListener', () => webMessageListener.toMap());
  await _channel.invokeMethod('addWebMessageListener', args);
}