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
2170
3171` dispatcher.js ` (and its server-side backend ` dispatcher.py ` ) provides a
4172universal queue-based message passing API.
@@ -17,17 +185,26 @@ listen, before sending the first message
17185(but still need to wait for the resolution of the promise returned by ` send() `
18186to ensure the order between ` send() ` s).
19187
20- # Executor framework
188+ ## Executors
21189
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.
24192
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)
29196
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