Source code

Revision control

Copy as Markdown

Other Tools

# Message Channels
```eval_rst
.. contents:: Table of Contents
:depth: 3
:local:
:backlinks: none
```
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 ##
```html
<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 receive 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.
```eval_rst
.. js:autofunction:: global_channel
:short-name:
.. js:autofunction:: start_global_channel
:short-name:
.. js:autoclass:: RemoteGlobalCommandRecvChannel
:members:
```
Contexts wanting to communicate with the remote context do so using a
`RemoteGlobal` object.
```eval_rst
.. js:autoclass:: RemoteGlobal
:members:
```
#### 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.
```eval_rst
.. js:autoclass:: RemoteObject
:members:
```
#### Example ####
test.html
```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
```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.
```eval_rst
.. js:autofunction:: channel
:members:
.. js:autoclass:: Channel
:members:
.. js:autoclass:: SendChannel
:members:
.. js:autoclass:: RecvChannel
:members:
```
### Navigation and bfcache
For specific use cases around bfcache, it's important to be able to
ensure that no network connections (including websockets) remain open
at the time of navigation, otherwise the page will be excluded from
bfcache. This is handled as follows:
* A `disconnectReader` method on `SendChannel`. This causes a
server-initiated disconnect of the corresponding `RecvChannel`
websocket. The idea is to allow a page to send a command that will
initiate a navigation, then without knowing when the navigation is
done, send further commands that will be processed when the
`RecvChannel` reconnects. If the commands are sent before the
navigation, but not processed, they can be buffered by the remote
and then lost during navigation.
* A `close_all_channel_sockets()` function. This just closes all the open
websockets associated with channels in the global in which it's
called. Any channel then has to be reconnected to be used
again. Calling `closeAllChannelSockets()` right before navigating
will leave you in a state with no open websocket connections (unless
something happens to reopen one before the navigation starts).
```eval_rst
.. js:autofunction:: close_all_channel_sockets
:members:
```