Skip to content

Commit fc3fa31

Browse files
Clarify a bit web integration document
1 parent dee2804 commit fc3fa31

File tree

1 file changed

+41
-14
lines changed

1 file changed

+41
-14
lines changed

WEB-INTEGRATION.md

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ group them into two categories:
161161
into a web API so it can be run. For events, this would be the context in
162162
which `addEventListener` is called or an event handler attribute
163163
(e.g. `onclick`) is set.
164-
165164
- The **causal context** (also called the dispatch context, especially in
166165
reference to events) is the context in which some web API is called that
167166
ultimately causes the callback to be called. This is usually an API that
@@ -172,6 +171,18 @@ group them into two categories:
172171
userland JS code in the same agent (e.g. a user-originated `click` event),
173172
there is no causal context.
174173

174+
We propose that, in general, APIs should call callbacks using the _causal context_.
175+
Some APIs can be used in multiple different ways (e.g. events), such that the
176+
causal context is not always available. In that case the **empty context** (where
177+
every `AsyncContext.Variable` is set to its default value) is used instead, as
178+
if the callback is invoked as a new top-level operation (like JavaScript code that
179+
runs when a page is just loaded).
180+
181+
Propagating the causal context matches the behavior you'd get if APIs were implemented
182+
in JavaScript, internally using only promises and continuation callbacks. This will
183+
thus match how most userland libraries behave, unless they modify how `AsyncContext`
184+
flows by manually snapshotting and restoring it.
185+
175186
We propose that, in general, if there is a causal context, that should be the
176187
context that the callback should be called with; otherwise, the registration
177188
context should be used. However, if an API is used in multiple different ways
@@ -180,6 +191,11 @@ there are cases where the causal context should be used even though it does not
180191
exist. In such cases, the **empty context** (where every `AsyncContext.Variable`
181192
is set to its default value) is used instead.
182193

194+
Note that for some APIs there is no difference between the causal context and
195+
the registration context: for example, `setTimeout` receives the callback
196+
(registration context) at the same time as the user is requesting (causal context)
197+
to schedule it to be run later.
198+
183199
In the rest of this document, we look at various kinds of web platform APIs
184200
which accept callbacks or otherwise need integration with AsyncContext, and
185201
examine which context should be used.
@@ -198,8 +214,8 @@ These are web APIs whose sole purpose is to take a callback and schedule it in
198214
the event loop in some way. The callback will run asynchronously at some point,
199215
when there is no other JS code in the call stack.
200216

201-
For these APIs, the causal context is the same as the registration context the
202-
context in which the API is called. After all, that API call starts a background
217+
For these APIs, the causal context is the same as the context in which the API
218+
is called. After all, that API call starts a background
203219
user-agent-internal operation that results in the callback being called.
204220
Therefore, this is the context the callback should be called with.
205221

@@ -285,7 +301,7 @@ These APIs register a callback or constructor to be invoked when some action
285301
runs. They’re also commonly used as a way to associate a newly created class
286302
instance with some action, such as in worklets or with custom elements.
287303

288-
In cases where the action originates due to something happening outside of the web page (such as some user action), there is no dispatch context. Therefore, the only available context is the
304+
In cases where the action originates due to something happening outside of the web page (such as some user action), there is never a dispatch context. Therefore, the only available context is the
289305
registration context, the one active when the web API is called.
290306

291307
- [`navigator.mediaSession.setActionHandler()`](https://w3c.github.io/mediasession/#dom-mediasession-setactionhandler)
@@ -311,13 +327,12 @@ code, the causal context should be used instead. The main case for this is
311327
custom elements, where the lifecycle callbacks are almost always triggered
312328
synchronously by a call from userland JS to an API annotated with
313329
[`[CEReactions]`](https://html.spec.whatwg.org/multipage/custom-elements.html#cereactions).
314-
However, there are cases where this is not the case:
330+
However, there are cases where this is not the case and falls back to the empty
331+
context where each `AsyncContext.Variable` would be set to its initial value:
315332

316333
- If a custom element is contained inside a `<div contenteditable>`, the user
317334
could remove the element from the tree as part of editing, which would queue a
318-
microtask to call its `disconnectedCallback` hook. In this case, there would
319-
be no causal context, and each `AsyncContext.Variable` would be set to its
320-
initial value.
335+
microtask to call its `disconnectedCallback` hook.
321336
- A user clicking a form reset when a form-associated custom element is in the
322337
form would queue a microtask to call its `formResetCallback` lifecycle hook,
323338
and there would not be a casual context. However, if the `click()` method is
@@ -326,6 +341,8 @@ However, there are cases where this is not the case:
326341
synchronously. In that case, the causal context would be the one active when
327342
`.click()` was called.
328343

344+
<!-- Is this still true? -->
345+
329346
In the cases where the registration web API takes a constructor (such as
330347
worklets) and the registration context should be used, any getters or methods of
331348
the constructed object that are called as a result of the registered action
@@ -357,6 +374,10 @@ In general, the context that should be used is the one that matches the data
357374
flow through the algorithms ([see the section on implicit propagation
358375
below](#implicit-context-propagation)).
359376

377+
<!-- Streams are laregely defined on top of promises, and can be easily
378+
reimplemented in userland by copying the spec step-by-step. Likely this
379+
described propagation already properly comes out of that? -->
380+
360381
> TODO: Piping is largely implementation-defined. We should figure out some
361382
> context propagation constraints.
362383
@@ -390,14 +411,19 @@ registration context; that is, the context in which the class is constructed.
390411
- [`ReportingObserver`](https://w3c.github.io/reporting/#reportingobserver)
391412
[\[REPORTING\]](https://w3c.github.io/reporting/)
392413

393-
> TODO: Due to concerns about observers leading to memory leaks, an alternative
414+
> [!IMPORTANT]
415+
> Due to concerns about observers leading to memory leaks, an alternative
394416
> option is to not use the registration context, and instead call the observer's
395-
> callback with the empty context. This is still under discussion.
417+
> callback with the empty context. This is coherent with the general design direction
418+
> of using the dispatch context, except that we would say that these observers are always
419+
> triggered as consequences of browser-internal code.
396420
397421
In some cases it might be useful to expose the causal context for individual
398422
observations, by exposing an `AsyncContext.Snapshot` property on the observation
399423
record. This should be the case for `PerformanceObserver`, where
400-
`PerformanceEntry` would expose the snapshot as a `resourceContext` property.
424+
`PerformanceEntry` would expose the snapshot as a `resourceContext` property. This
425+
is not included as part of this initial proposed version, as new properties can
426+
easily be added as follow-ups in the future.
401427

402428
## Events
403429

@@ -649,7 +675,7 @@ answered:
649675
which can run when triggered from outside of JavaScript? (e.g. observers)
650676
- should it be a global, or a static method of `EventTarget`?
651677

652-
## Script errors and unhandled rejections
678+
### Script errors and unhandled rejections
653679

654680
The `error` event on a window or worker global object is fired whenever a script
655681
execution throws an uncaught exception. The context in which this exception was
@@ -680,15 +706,16 @@ window.onerror = window.onunhandledrejection = () => {
680706
};
681707
```
682708

683-
### Unhandled rejection details
709+
#### Unhandled rejection details
684710

685711
The `unhandledrejection` causal context could be unexpected in some cases. For
686712
example, in the following code sample, developers might expect `asyncVar` to map
687713
to `"bar"` in that context, since the throw that causes the promise rejection
688714
takes place inside `a()`. However, the promise that rejects *without having a
689715
registered rejection handled* is the promise returned by `b()`, which only
690716
outside of the `asyncVar.run("bar", ...)` returns. Therefore, `asyncVar` would
691-
map to `"foo"`.
717+
map to `"foo"`. Effectively this context does not propagate from where the
718+
first rejection happens, but from where the developer forgot to handle it.
692719

693720

694721
```js

0 commit comments

Comments
 (0)