Message Channels

Message channels provide a mechanism to communicate across globals, including in cases where there is no client-side mechanism to establish a communication channel (i.e. when the globals are in different browsing context groups).

Markup

<script src="/resources/channels.sub.js"></script>

Channels can be used in any global and are not specifically linked to testharness.js.

High Level API

The high level API provides a way to message another global, and to execute functions in that global and return the result.

Globals wanting to recieve messages using the high level API have to be loaded with a uuid query parameter in their URL, with a value that’s a UUID. This will be used to identify the channel dedicated to messages sent to that context.

The context must call either global_channel or start_global_channel when it’s ready to receive messages. This returns a RecvChannel that can be used to add message handlers.

global_channel()

Create an unconnected channel defined by a uuid in location.href for listening for RemoteGlobal messages.

Returns

RemoteGlobalCommandRecvChannel – - Disconnected channel

start_global_channel()

Start listening for RemoteGlobal messages on a channel defined by a uuid in location.href

Returns

RemoteGlobalCommandRecvChannel – - Connected channel

class RemoteGlobalCommandRecvChannel(recvChannel)

Handler for RemoteGlobal commands.

This can’t be constructed directly but must be obtained from global_channel() or start_global_channel().

RemoteGlobalCommandRecvChannel.addMessageHandler(fn)

Add a handler for postMessage messages

Arguments
  • fn (function()) – Callback function that receives the message.

RemoteGlobalCommandRecvChannel.close()

Close the channel and underlying websocket connection

RemoteGlobalCommandRecvChannel.connect()

Connect to the channel and start handling messages.

RemoteGlobalCommandRecvChannel.nextMessage()

Wait for the next postMessage message and return it (after passing it to existing handlers)

Returns

Promise – - Promise that resolves to the message.

RemoteGlobalCommandRecvChannel.removeMessageHandler(fn)

Remove a handler for postMessage messages

Arguments
  • fn (function()) – Callback function to remove

Contexts wanting to communicate with the remote context do so using a RemoteGlobal object.

class RemoteGlobal(dest)

Object representing a remote global that has a RemoteGlobalCommandRecvChannel

Create a new RemoteGlobal object.

This doesn’t actually construct the global itself; that must be done elsewhere, with a uuid query parameter in its URL set to the same as the uuid property of this object.

Arguments
  • dest (SendChannel|string()) – Either a SendChannel to the destination, or the UUID of the destination. If ommitted, a new UUID is generated, which can be used when constructing the URL for the global.

RemoteGlobal.uuid

UUID for the global

RemoteGlobal.call(fn, ...args)

Run the function fn in the remote global, passing arguments args, and return the result after awaiting any returned promise.

Arguments
  • fn (function()) – Function to run in the remote global.

  • args (Any()) – Arguments to pass to the function

Returns

Promise – - Promise resolving to the return value of the function.

RemoteGlobal.close()

Close the channel and underlying websocket connections

RemoteGlobal.connect()

Connect to the channel. Automatically called when sending the first message

RemoteGlobal.disconnectReader()

Disconnect the associated RemoteGlobalCommandRecvChannel, if any, on the server side.

Returns

Promise – - Resolved once the channel is disconnected.

RemoteGlobal.postMessage(msg)

Post a message to the remote

Arguments
  • msg (Any()) – The message to send.

Remote Objects

By default objects (e.g. script arguments) sent to the remote global are cloned. In order to support referencing objects owned by the originating global, there is a RemoteObject type which can pass a reference to an object across a channel.

class RemoteObject(type, objectId)

Representation of a non-primitive type passed through a channel

RemoteObject.delete()

Remove the object from the local cache. This means that future calls to toLocal with the same objectId will always return null.

RemoteObject.toLocal()

Return the local object referenced by the objectId of this RemoteObject, or null if there isn’t a such an object in this realm.

RemoteObject.from(obj)

Create a RemoteObject containing a handle to reference obj

Arguments
  • obj (Any()) – The object to reference.

Example

test.html

<!doctype html>
<title>call example</title>
<script src="/resources/testharness.js">
<script src="/resources/testharnessreport.js">
<script src="/resources/channel.js">

<script>
promise_test(async t => {
  let remote = new RemoteGlobal();
  window.open(`child.html?uuid=${remote.uuid}`, "_blank", "noopener");
  let result = await remote.call(id => {
    return document.getElementById(id).textContent;
  }, "test");
  assert_equals("result", "PASS");
});
</script>

child.html

<script src="/resources/channel.js">

<p id="nottest">FAIL</p>
<p id="test">PASS</p>
<script>
start_global_channel();
</script>

Low Level API

The high level API is implemented in terms of a channel abstraction. Each channel is identified by a UUID, and corresponds to a message queue hosted by the server. Channels are multiple producer, single consumer, so there’s only only entity responsible for processing messages sent to the channel. This is designed to discourage race conditions where multiple consumers try to process the same message.

On the client side, the read side of a channel is represented by a RecvChannel object, and the send side by SendChannel. An initial channel pair is created with the channel() function.

class Channel(uuid)

Abstract base class for objects that allow sending / receiving messages over a channel.

Channel.uuid

UUID for the channel

Channel.addEventListener(type, fn)

Add an event callback function. Supported message types are “connect”, “close”, and “message” (for RecvChannel).

Arguments
  • type (string()) – Message type.

  • fn (function()) – Callback function. This is called with an event-like object, with type and data properties.

Channel.close()

Close the channel and underlying websocket connection

Channel.connect(onmessage)

Connect to the channel.

Arguments
  • onmessage (function()) – Event handler function for the underlying websocket message.

Channel.removeEventListener(type, fn)

Remove an event callback function.

Arguments
  • type (string()) – Event type.

  • fn (function()) – Callback function to remove.

class SendChannel()

Send messages over a channel

SendChannel.connect()

Connect to the channel. Automatically called when sending the first message.

SendChannel.delete()

Disconnect this channel on the server side.

SendChannel.disconnectReader()

Disconnect the associated RecvChannel, if any, on the server side.

SendChannel.send(msg)

Send a message. The message object must be JSON-serializable.

Arguments
  • msg (Object()) – The message object to send.

class RecvChannel(uuid)

Receive messages over a channel

RecvChannel.nextMessage()

Wait for the next message and return it (after passing it to existing handlers)

Returns

Promise – - Promise that resolves to the message data.