Skip to content

Commit 7c5c4f8

Browse files
authored
[worklets] Multiple fixes to the {{Worklet/import()}} algorithm. (w3c#375)
* [worklets] Multiple fixes to the {{Worklet/import()}} algorithm. Instead of relying on "in parallel" this change makes all the thread hopping explicit via. message passing and queue tasks on the appropriate event loop. - Fixes w3c#372, changes arguments to fetch a module script graph. - Fixes w3c#370, running scripts in parallel. Now the script explicitly are run within a task queued on the worklet global scope's event loop. - Fixes w3c#318, actually runs the event loop. - Fixes w3c#230, treats "fetch a module worker script graph" as asynchronous. - Probably fixes w3c#225 ? I think I'm grabbing the correct state at each thread hop.
1 parent 3e8ca27 commit 7c5c4f8

File tree

1 file changed

+133
-70
lines changed

1 file changed

+133
-70
lines changed

worklets/Overview.bs

+133-70
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ urlPrefix: https://html.spec.whatwg.org/multipage/webappapis.html; type: dfn;
4040
text: environment settings object
4141
text: event loop
4242
text: event loop processing model
43-
text: fetch a module script tree
4443
text: global object
4544
text: https state
4645
text: incumbent settings object
@@ -58,8 +57,6 @@ urlPrefix: https://html.spec.whatwg.org/multipage/webappapis.html; type: dfn;
5857
urlPrefix: https://html.spec.whatwg.org/multipage/infrastructure.html; type: dfn;
5958
text: cors setting attribute
6059
text: in parallel
61-
urlPrefix: #js-;
62-
text: syntaxerror;
6360
url: resolve-a-url; text: resolve;
6461
urlPrefix: https://www.w3.org/2001/tag/doc/promises-guide; type: dfn;
6562
text: a new promise
@@ -147,7 +144,12 @@ interface WorkletGlobalScope {
147144
};
148145
</pre>
149146

150-
A {{WorkletGlobalScope}} has an associated <a>environment settings object</a>.
147+
Each {{WorkletGlobalScope}} has an associated <a>environment settings object</a>.
148+
149+
Each {{WorkletGlobalScope}} has a <dfn>worklet global scope execution environment</dfn>. This
150+
execution environment may be parallel (i.e. it may be on a separate thread, process, or other
151+
equivalent construct), or it may live on the same thread or process as the {{Worklet}} object it
152+
belongs to. Which thread or process it lives on is decided by the user agent.
151153

152154
Note:
153155
The {{WorkletGlobalScope}} has a limited global scope when compared to a
@@ -158,21 +160,26 @@ Note:
158160
### The event loop ### {#the-event-loop}
159161

160162
Each {{WorkletGlobalScope}} object has a distinct <a>event loop</a>. This <a>event loop</a> has no
161-
associated <a>browsing context</a>, and only its <a>microtask queue</a> is used (all other <a>task
162-
queues</a> are not used). The <a>event loop</a> is created by the <a>create a
163+
associated <a>browsing context</a>. The <a>event loop</a> is created by the <a>create a
163164
WorkletGlobalScope</a> algorithm.
164165

166+
The <a>event loop</a> is run on the <a>worklet global scope execution environment</a> defined above.
167+
168+
It is expected that only tasks associated {{Worklet/import()}}, the user agent invoking author
169+
defined callbacks, and <a>microtasks</a> will use this <a>event loop</a>.
170+
165171
Note:
166172
Even through the <a>event loop processing model</a> specifies that it loops continually,
167-
practically implementations aren't expected to do this. The <a>microtask queue</a> is the only
168-
queue used and is emptied while <a>invoking callback functions</a> provided by the author.
173+
practically implementations aren't expected to do this. The <a>microtask queue</a> is emptied
174+
while <a>invoking callback functions</a> provided by the author.
169175

170176
### Creating a WorkletGlobalScope ### {#creating-a-workletglobalscope}
171177

172-
When a user agent is to <dfn>create a WorkletGlobalScope</dfn>, for a given |worklet|, it
173-
<em>must</em> run the following steps:
178+
When a user agent is to <dfn>create a WorkletGlobalScope</dfn>, given |workletGlobalScopeType|,
179+
|moduleResponsesMap|, and |outsideSettings|, it <em>must</em> run the following steps:
174180

175-
1. Let |workletGlobalScopeType| be the |worklet|'s <a>worklet global scope type</a>.
181+
1. Create the <a>worklet global scope execution environment</a> and run the rest of these steps
182+
in that context.
176183

177184
2. Call the JavaScript <a>InitializeHostDefinedRealm</a> abstract operation with the following
178185
customizations:
@@ -184,25 +191,29 @@ When a user agent is to <dfn>create a WorkletGlobalScope</dfn>, for a given |wor
184191

185192
- Do not obtain any source texts for scripts or modules.
186193

187-
3. Let |settingsObject| be the result of <a>set up a worklet environment settings object</a>
194+
3. Let |insideSettings| be the result of <a>set up a worklet environment settings object</a>
188195
with |realmExecutionContext|.
189196

190-
4. Associate the |settingsObject| with |workletGlobalScope|.
191-
192-
5. For each |entry| in the given |worklet|'s <a>module responses map</a> (in insertion order),
193-
run the following substeps:
197+
4. Associate the |insideSettings| with |workletGlobalScope|.
194198

195-
1. Let |resolvedModuleURL| be |entry|'s key.
199+
5. For each |entry| in the given |moduleResponsesMap| (in insertion order), run the following
200+
substeps:
196201

197-
2. Let |script| be the result of <a>fetch a module script tree</a> given
198-
|resolvedModuleURL|, "anonymous" for the <a>CORS setting attribute</a>, and
199-
|settingsObject|.
202+
1. Let |moduleURLRecord| be |entry|'s key.
200203

201-
Note: Worklets follow <a>web workers</a> here in not allowing "use-credentials" for
202-
fetching resources.
204+
2. Let |script| be the result of <a>fetch a worklet script</a> given |moduleURLRecord|,
205+
|moduleResponsesMap|, |outsideSettings|, and |insideSettings| when
206+
it asynchronously completes.
203207

204208
3. <a>Run a module script</a> given |script|.
205209

210+
Note: <a>Fetch a worklet script</a> won't actually perform a network request as it will hit
211+
the worklet's <a>module responses map</a>. It also won't have a parsing error as at this
212+
point it should have successfully been parsed by another worklet global scope. I.e.
213+
|script| should never be <em>null</em> here.
214+
215+
6. Run the <a>responsible event loop</a> specified by |insideSettings|.
216+
206217
### Script settings for worklets ### {#script-settings-for-worklets}
207218

208219
When a user agent is to <dfn>set up a worklet environment settings object</dfn>, given a
@@ -285,85 +296,137 @@ The <a>module responses map</a> exists to ensure that {{WorkletGlobalScope}}s cr
285296
times contain the same set of script source text and have the same behaviour. The creation of
286297
additional {{WorkletGlobalScope}}s should be transparent to the author.
287298

288-
Note:
289-
Practically user-agents aren't expected to implement the following algorithm using a
290-
thread-safe map. Instead when {{Worklet/import}} is called user-agents can fetch the module
299+
<div class='note'>
300+
Practically user agents aren't expected to implement the following algorithm using a
301+
thread-safe map. Instead when {{Worklet/import()}} is called user agents can fetch the module
291302
graph on the main thread, and send the fetched sources (the data contained in the <a>module
292303
responses map</a>) to each thread which has a {{WorkletGlobalScope}}.
293304

294305
If the user agent wishes to create a new {{WorkletGlobalScope}} it can simply sent the list of
295306
all fetched sources from the main thread to the thread which owns the {{WorkletGlobalScope}}.
307+
</div>
308+
309+
A <dfn>pending tasks struct</dfn> is a <a>struct</a> consiting of:
310+
- A <dfn for="pending tasks struct">counter</dfn>.
311+
This is used by the algorithms below.
312+
313+
<hr>
296314

297315
When the <dfn method for=Worklet>import(|moduleURL|)</dfn> method is called on a {{Worklet}} object,
298316
the user agent <em>must</em> run the following steps:
299317
1. Let |promise| be <a>a new promise</a>.
300318

301-
2. Let |resolvedModuleURL| be the result of <a>parsing</a> the |moduleURL| argument relative to
319+
2. Let |worklet| be the current {{Worklet}}.
320+
321+
3. Let |moduleURLRecord| be the result of <a>parsing</a> the |moduleURL| argument relative to
302322
the <a>relevant settings object</a> of <b>this</b>.
303323

304-
3. If this fails, reject |promise| with a "<a>SyntaxError</a>" <a>DOMException</a> and abort
305-
these steps.
324+
4. If |moduleURLRecord| is failure, then reject promise with a "<a>SyntaxError</a>"
325+
<a>DOMException</a> and return |promise|.
326+
327+
5. Return |promise|, and then continue running this algorithm <a>in parallel</a>.
328+
329+
6. Let |outsideSettings| be the <a>relevant settings object</a> of <b>this</b>.
330+
331+
7. Let |moduleResponsesMap| be |worklet|'s <a>module responses map</a>.
332+
333+
8. Let |workletGlobalScopeType| be |worklet|'s <a>worklet global scope type</a>.
334+
335+
9. If the <a>worklet's WorkletGlobalScopes</a> is empty, run the following steps:
336+
337+
1. <a>Create a WorkletGlobalScope</a> given |workletGlobalScopeType|, |moduleResponsesMap|,
338+
and |outsideSettings|.
339+
340+
2. Add the {{WorkletGlobalScope}} to <a>worklet's WorkletGlobalScopes</a>.
341+
342+
The user agent may also create additional {{WorkletGlobalScope}}s at this time.
343+
344+
Wait for this step to complete before continuing.
345+
346+
10. Let |pendingTaskStruct| be a new <a>pending tasks struct</a> with <a
347+
for="pending tasks struct">counter</a> initialized to the length of <a>worklet's
348+
WorkletGlobalScopes</a>.
349+
350+
11. For each |workletGlobalScope| in the <a>worklet's WorkletGlobalScopes</a>, <a>queue a
351+
task</a> on the |workletGlobalScope| to <a>fetch and invoke a worklet script</a> given
352+
|workletGlobalScope|, |moduleURLRecord|, |moduleResponsesMap|, |outsideSettings|,
353+
|pendingTaskStruct|, and |promise|.
354+
355+
Note: The rejecting and resolving of the |promise| occurs within the <a>fetch and invoke a
356+
worklet script</a> algorithm.
357+
358+
<hr>
359+
360+
When the user agent is to <dfn>fetch and invoke a worklet script</dfn> given |workletGlobalScope|,
361+
|moduleURLRecord|, |moduleResponsesMap|, |outsideSettings|, |pendingTaskStruct|, and |promise|, the
362+
user agent <em>must</em> run the following steps:
363+
364+
Note: This algorithm is to be run within the <a>worklet global scope execution environment</a>.
365+
366+
1. Let |insideSettings| be the |workletGlobalScope|'s associated <a>environment settings object</a>.
367+
368+
2. Let |script| by the result of <a>fetch a worklet script</a> given |moduleURLRecord|,
369+
|moduleResponsesMap|, |outsideSettings|, and |insideSettings| when it asynchronously completes.
370+
371+
3. If |script| is <em>null</em>, then <a>queue a task</a> on |outsideSettings|'s <a>responsible
372+
event loop</a> to run these steps:
373+
374+
1. If |pendingTaskStruct|'s <a for="pending tasks struct">counter</a> is not <b>-1</b>, then
375+
run these steps:
306376

307-
4. Ensure that there is at least one {{WorkletGlobalScope}} in the <a>worklet's
308-
WorkletGlobalScopes</a>. If not <a>create a WorkletGlobalScope</a> given the current
309-
{{Worklet}}.
377+
1. Set |pendingTaskStruct|'s <a for="pending tasks struct">counter</a> to <b>-1</b>.
310378

311-
The user-agent may also create additional {{WorkletGlobalScope}}s at this time.
379+
2. Reject |promise| with an "<a>AbortError</a>" <a>DOMException</a>.
312380

313-
5. Let |outsideSettings| be the <a>relevant settings object</a> of <b>this</b>.
381+
4. <a>Run a module script</a> given |script|.
314382

315-
6. Run the following steps <a>in parallel</a>:
383+
5. <a>Queue a task</a> on |outsideSettings|'s <a>responsible event loop</a> to run these steps:
316384

317-
1. For each {{WorkletGlobalScope}} in the <a>worklet's WorkletGlobalScopes</a>, run these
318-
substeps:
385+
1. If |pendingTaskStruct|'s <a for="pending tasks struct">counter</a> is not <b>-1</b>, then run
386+
these steps:
319387

320-
1. Let |insideSettings| be the {{WorkletGlobalScope}}'s associated <a>environment
321-
settings object</a>.
388+
1. Decrement |pendingTaskStruct|'s <a for="pending tasks struct">counter</a> by <b>1</b>.
322389

323-
2. <a>Fetch a module script tree</a> given |resolvedModuleURL|, "omit", the empty string
324-
(as no cryptographic nonce is present for worklets), "not parser-inserted",
325-
"script", |outsideSettings|, and |insideSettings|.
390+
1. If |pendingTaskStruct|'s <a for="pending tasks struct">counter</a> is <b>0</b>, then
391+
resolve |promise|.
326392

327-
To <a>perform the request</a> given |request|, perform the following steps:
393+
<hr>
328394

329-
1. Let |cache| be the current {{Worklet}}'s <a>module responses map</a>.
395+
When the user agent is to <dfn>fetch a worklet script</dfn> given |moduleURLRecord|,
396+
|moduleResponsesMap|, |outsideSettings|, and |insideSettings|, the user agent <em>must</em>
397+
run the following steps:
330398

331-
2. Let |url| be |request|'s <a >url</a>.
399+
Note: This algorithm is to be run within the <a>worklet global scope execution environment</a>.
332400

333-
3. If |cache| contains an entry with key |url| whose value is "fetching", wait
334-
(<a>in parallel</a>) until that entry's value changes, then proceed to the
335-
next step.
401+
1. <a>Fetch a module worker script graph</a> given |moduleURLRecord|, |outsideSettings|, "script",
402+
"omit", and |insideSettings|.
336403

337-
4. If |cache| contains an entry with key |url|, asynchronously complete this
338-
algorithm with that entry's value, and abort these steps.
404+
To <a>perform the request</a> given |request|, perform the following steps:
339405

340-
5. Create an entry in |cache| with key |url| and value "fetching".
406+
1. Let |cache| be the |moduleResponsesMap|.
341407

342-
6. <a>Fetch</a> |request|.
408+
2. Let |url| be |request|'s <a for=request>url</a>.
343409

344-
7. Let |response| be the result of <a>fetch</a> when it asynchronously
345-
completes.
410+
3. If |cache| contains an entry with key |url| whose value is "fetching", wait until that
411+
entry's value changes, then proceed to the next step.
346412

347-
8. Set the value of the entry in |cache| whose key is |url| to |response|, and
348-
asynchronously complete this algorithm with |response|.
413+
4. If |cache| contains an entry with key |url|, asynchronously complete this algorithm with
414+
that entry's value, and abort these steps.
349415

350-
3. Let |script| be the result of <a>fetch a module script tree</a> when it
351-
asynchronously completes.
416+
5. Create an entry in |cache| with key |url| and value "fetching".
352417

353-
If |script| is <em>null</em> reject |promise| with a "<a>AbortError</a>"
354-
<a>DOMException</a> and abort all these steps.
418+
6. <a>Fetch</a> |request|.
355419

356-
4. <a>Run a module script</a> given |script|.
420+
7. Let |response| be the result of <a>fetch</a> when it asynchronously completes.
357421

358-
2. If <em>all</em> the steps above succeeded (in particular, if all of the scripts parsed
359-
and loaded into the global scopes), resolve |promise|.
360-
<br>Otherwise, reject |promise|.
422+
8. Set the value of the entry in |cache| whose key is |url| to |response|, and
423+
asynchronously complete this algorithm with |response|.
361424

362-
Note: Specifically, if a script fails to parse or fails to load over the network, it will
363-
reject the promise; if the script throws an error while first evaluating the promise
364-
should resolve as a classes may have been registered correctly.
425+
2. Return the result of <a>fetch a module worker script graph</a> when it asynchronously completes.
365426

366-
7. Return |promise|.
427+
Note: Specifically, if a script fails to parse or fails to load over the network, it will reject the
428+
promise. If the script throws an error while first evaluating the promise it will resolve as a
429+
classes may have been registered correctly.
367430

368431
<div class=example>
369432
When an author imports code into a {{Worklet}} the code may run against multiple
@@ -378,16 +441,16 @@ the user agent <em>must</em> run the following steps:
378441
await CSS.paintWorklet.import('script.js');
379442
</pre>
380443

381-
Behind the scenes the user-agent may load the <code class='lang-javascript'>script.js</code>
382-
into 4 global scopes, in which case the debugging tools for the user-agent would print:
444+
Behind the scenes the user agent may load the <code class='lang-javascript'>script.js</code>
445+
into 4 global scopes, in which case the debugging tools for the user agent would print:
383446
<pre class='lang-javascript'>
384447
[paintWorklet#1] Hello from a WorkletGlobalScope!
385448
[paintWorklet#4] Hello from a WorkletGlobalScope!
386449
[paintWorklet#2] Hello from a WorkletGlobalScope!
387450
[paintWorklet#3] Hello from a WorkletGlobalScope!
388451
</pre>
389452

390-
If the user-agent decided to kill and restart a {{WorkletGlobalScope}} number 3 in this example,
453+
If the user agent decided to kill and restart a {{WorkletGlobalScope}} number 3 in this example,
391454
it would print <code class='lang-javascript'>[paintWorklet#3] Hello from a
392455
WorkletGlobalScope!</code> again in the debugging tools when this occurs.
393456
</div>

0 commit comments

Comments
 (0)