1
- # Message passing API
1
+ # ` RemoteContext ` : API for script execution in another context
2
+
3
+ ` RemoteContext ` in ` /common/dispatcher/dispatcher.js ` provides an interface to
4
+ execute JavaScript in another global object (page or worker, the "executor"),
5
+ based on:
6
+
7
+ - [ WPT RFC 88: context IDs from uuid searchParams in URL] ( https://github.com/web-platform-tests/rfcs/pull/88 ) ,
8
+ - [ WPT RFC 89: execute_script] ( https://github.com/web-platform-tests/rfcs/pull/89 ) and
9
+ - [ WPT RFC 91: RemoteContext] ( https://github.com/web-platform-tests/rfcs/pull/91 ) .
10
+
11
+ Tests can send arbitrary javascript to executors to evaluate in its global
12
+ object, like:
13
+
14
+ ```
15
+ // injector.html
16
+ const argOnLocalContext = ...;
17
+
18
+ async function execute() {
19
+ window.open('executor.html?uuid=' + uuid);
20
+ const ctx = new RemoteContext(uuid);
21
+ await ctx.execute_script(
22
+ (arg) => functionOnRemoteContext(arg),
23
+ [argOnLocalContext]);
24
+ };
25
+ ```
26
+
27
+ and on executor:
28
+
29
+ ```
30
+ // executor.html
31
+ function functionOnRemoteContext(arg) { ... }
32
+
33
+ const uuid = new URLSearchParams(window.location.search).get('uuid');
34
+ const executor = new Executor(uuid);
35
+ ```
36
+
37
+ For concrete examples, see
38
+ [ events.html] ( ../../html/browsers/browsing-the-web/back-forward-cache/events.html )
39
+ and
40
+ [ executor.html] ( ../../html/browsers/browsing-the-web/back-forward-cache/resources/executor.html )
41
+ in back-forward cache tests.
42
+ Note that executor files under ` /common/dispatcher/ ` are NOT for
43
+ ` RemoteContext.execute_script() ` .
44
+
45
+ This is universal and avoids introducing many specific ` XXX-helper.html `
46
+ resources.
47
+ Moreover, tests are easier to read, because the whole logic of the test can be
48
+ defined in a single file.
49
+
50
+ ## ` new RemoteContext(uuid) `
51
+
52
+ - ` uuid ` is a UUID string that identifies the remote context and should match
53
+ with the ` uuid ` parameter of the URL of the remote context.
54
+ - Callers should create the remote context outside this constructor (e.g.
55
+ ` window.open('executor.html?uuid=' + uuid) ` ).
56
+
57
+ ## ` RemoteContext.execute_script(fn, args) `
58
+
59
+ - ` fn ` is a JavaScript function to execute on the remote context, which is
60
+ converted to a string using ` toString() ` and sent to the remote context.
61
+ - ` args ` is null or an array of arguments to pass to the function on the
62
+ remote context. Arguments are passed as JSON.
63
+ - If the return value of ` fn ` when executed in the remote context is a promise,
64
+ the promise returned by ` execute_script ` resolves to the resolved value of
65
+ that promise. Otherwise the ` execute_script ` promise resolves to the return
66
+ value of ` fn ` .
67
+
68
+ Note that ` fn ` is evaluated on the remote context (` executor.html ` in the
69
+ example above), while ` args ` are evaluated on the caller context
70
+ (` injector.html ` ) and then passed to the remote context.
71
+
72
+ ## Return value of injected functions and ` execute_script() `
73
+
74
+ If the return value of the injected function when executed in the remote
75
+ context is a promise, the promise returned by ` execute_script ` resolves to the
76
+ resolved value of that promise. Otherwise the ` execute_script ` promise resolves
77
+ to the return value of the function.
78
+
79
+ When the return value of an injected script is a Promise, it should be resolved
80
+ before any navigation starts on the remote context. For example, it shouldn't
81
+ be resolved after navigating out and navigating back to the page again.
82
+ It's fine to create a Promise to be resolved after navigations, if it's not the
83
+ return value of the injected function.
84
+
85
+ ## Calling timing of ` execute_script() `
86
+
87
+ When ` RemoteContext.execute_script() ` is called when the remote context is not
88
+ active (for example before it is created, before navigation to the page, or
89
+ during the page is in back-forward cache), the injected script is evaluated
90
+ after the remote context becomes active.
91
+
92
+ ` RemoteContext.execute_script() ` calls should be serialized by always waiting
93
+ for the returned promise to be resolved.
94
+ So it's a good practice to always write ` await ctx.execute_script(...) ` .
95
+
96
+ ## Evaluation timing of injected functions
97
+
98
+ The script injected by ` RemoteContext.execute_script() ` can be evaluated any
99
+ time during the remote context is active.
100
+ For example, even before DOMContentLoaded events or even during navigation.
101
+ It's the responsibility of test-specific code/helpers to ensure evaluation
102
+ timing constraints (which can be also test-specific), if any needed.
103
+
104
+ ### Ensuring evaluation timing around page load
105
+
106
+ For example, to ensure that injected functions (` mainFunction ` below) are
107
+ evaluated after the first ` pageshow ` event, we can use pure JavaScript code
108
+ like below:
109
+
110
+ ```
111
+ // executor.html
112
+ window.pageShowPromise = new Promise(resolve =>
113
+ window.addEventListener('pageshow', resolve, {once: true}));
114
+
115
+
116
+ // injector.html
117
+ const waitForPageShow = async () => {
118
+ while (!window.pageShowPromise) {
119
+ await new Promise(resolve => setTimeout(resolve, 100));
120
+ }
121
+ await window.pageShowPromise;
122
+ };
123
+
124
+ await ctx.execute(waitForPageShow);
125
+ await ctx.execute(mainFunction);
126
+ ```
127
+
128
+ ### Ensuring evaluation timing around navigation out/unloading
129
+
130
+ It can be important to ensure there are no injected functions nor code behind
131
+ ` RemoteContext ` (such as Fetch APIs accessing server-side stash) running after
132
+ navigation is initiated, for example in the case of back-forward cache testing.
133
+
134
+ To ensure this,
135
+
136
+ - Do not call the next ` RemoteContext.execute() ` for the remote context after
137
+ triggering the navigation, until we are sure that the remote context is not
138
+ active (e.g. after we confirm that the new page is loaded).
139
+ - Call ` Executor.suspend(callback) ` synchronously within the injected script.
140
+ This suspends executor-related code, and calls ` callback ` when it is ready
141
+ to start navigation.
142
+
143
+ The code on the injector side would be like:
144
+
145
+ ```
146
+ // injector.html
147
+ await ctx.execute_script(() => {
148
+ executor.suspend(() => {
149
+ location.href = 'new-url.html';
150
+ });
151
+ });
152
+ ```
153
+
154
+ ## Future Work: Possible integration with ` test_driver `
155
+
156
+ Currently ` RemoteContext ` is implemented by JavaScript and WPT-server-side
157
+ stash, and not integrated with ` test_driver ` nor ` testharness ` .
158
+ There is a proposal of ` test_driver ` -integrated version (see the RFCs listed
159
+ above).
160
+
161
+ The API semantics and guidelines in this document are designed to be applicable
162
+ to both the current stash-based ` RemoteContext ` and ` test_driver ` -based
163
+ version, and thus the tests using ` RemoteContext ` will be migrated with minimum
164
+ modifications (mostly in ` /common/dispatcher/dispatcher.js ` and executors), for
165
+ example in a
166
+ [ draft CL] ( https://chromium-review.googlesource.com/c/chromium/src/+/3082215/ ) .
167
+
168
+
169
+ # ` send() ` /` receive() ` Message passing APIs
2
170
3
171
` dispatcher.js ` (and its server-side backend ` dispatcher.py ` ) provides a
4
172
universal queue-based message passing API.
@@ -17,17 +185,26 @@ listen, before sending the first message
17
185
(but still need to wait for the resolution of the promise returned by ` send() `
18
186
to ensure the order between ` send() ` s).
19
187
20
- # Executor framework
188
+ ## Executors
21
189
22
- The message passing API can be used for sending arbitrary javascript to be
23
- evaluated in another page or worker (the "executor") .
190
+ Similar to ` RemoteContext.execute_script() ` , ` send() ` / ` receive() ` can be used
191
+ for sending arbitrary javascript to be evaluated in another page or worker.
24
192
25
- ` executor.html ` (as a Document), ` executor-worker.js ` (as a Web Worker), and
26
- ` executor-service-worker.js ` (as a Service Worker) are examples of executors.
27
- Tests can send arbitrary javascript to these executors to evaluate in its
28
- execution context.
193
+ - ` executor.html ` (as a Document),
194
+ - ` executor-worker.js ` (as a Web Worker), and
195
+ - ` executor-service-worker.js ` (as a Service Worker)
29
196
30
- This is universal and avoids introducing many specific ` XXX-helper.html `
31
- resources.
32
- Moreover, tests are easier to read, because the whole logic of the test can be
33
- defined in a single file.
197
+ are examples of executors.
198
+ Note that these executors are NOT compatible with
199
+ ` RemoteContext.execute_script() ` .
200
+
201
+ ## Future Work
202
+
203
+ ` send() ` , ` receive() ` and the executors below are kept for COEP/COOP tests.
204
+
205
+ For remote script execution, new tests should use
206
+ ` RemoteContext.execute_script() ` instead.
207
+
208
+ For message passing,
209
+ [ WPT RFC 90] ( https://github.com/web-platform-tests/rfcs/pull/90 ) is still under
210
+ discussion.
0 commit comments