summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/docs/writing-tests/channels.md
blob: 9296247fca4fd3bee7115be3526e2c90d87fb95a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# 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 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.

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