@@ -13,7 +13,7 @@ machinery.
1313
1414Although this document focuses on the web platform, and on web APIs, it is also
1515expected to be relevant to other JavaScript environments and runtimes. This will
16- necessarily be the case for [ WinterCG ] ( https://wintercg .org ) -style runtimes,
16+ necessarily be the case for [ WinterTC ] ( https://wintertc .org ) -style runtimes,
1717since they will implement web APIs. However, the integration with the web
1818platform is also expected to serve as a model for other APIs in other JavaScript
1919environments.
@@ -310,21 +310,23 @@ synchronously by a call from userland JS to an API annotated with
310310[ ` [CEReactions] ` ] ( https://html.spec.whatwg.org/multipage/custom-elements.html#cereactions ) .
311311However, there are cases where this is not the case:
312312
313- - Userland JS could call ` .click() ` on a form reset button when a form-associated
314- custom element is in the form, which would queue a microtask that would call
315- its ` formResetCallback ` lifecycle hook. The causal context would be the one
316- active when ` .click() ` was called.
317313- If a custom element is contained inside a ` <div contenteditable> ` , the user
318314 could remove the element from the tree as part of editing, which would queue a
319315 microtask to call its ` disconnectedCallback ` hook. In this case, there would
320316 be no causal context, and each ` AsyncContext.Variable ` would be set to its
321317 initial value.
322-
323- In the cases where the registration web API takes a constructor, the methods and
324- getters of the returned object (e.g. the ` processor ` method in web audio
325- worklets, or the ` attributeChangedCallback ` hook of custom elements) also count
326- as callbacks that these APIs invoke, and which should preserve the relevant
327- context.
318+ - A user clicking a form reset when a form-associated custom element is in the
319+ form would queue a microtask to call its ` formResetCallback ` lifecycle hook,
320+ and there would not be a casual context. However, if the ` click() ` method is
321+ called from JS instead, since that method doesn't have the ` [CEReactions] `
322+ annotation, it would also call that lifecycle hook in a microtask, rather than
323+ synchronously. In that case, the causal context would be the one active when
324+ ` .click() ` was called.
325+
326+ In the cases where the registration web API takes a constructor (such as
327+ worklets) and the registration context should be used, any getters or methods of
328+ the constructed object that are called as a result of the registered action
329+ should also be called with that same registration context.
328330
329331### Stream underlying APIs
330332
@@ -334,41 +336,30 @@ The underlying [source](https://streams.spec.whatwg.org/#underlying-source-api),
334336are callbacks/methods passed during stream construction. The context in which
335337the stream is constructed is then the registration context.
336338
337- That is also the causal context for the ` start ` method, but for other methods
338- there would be a different causal context, depending on what causes the call to
339- that method. For example:
339+ That registration context is also the causal context for the ` start ` method, but
340+ for other methods there would be a different causal context, depending on what
341+ causes the call to that method. For example:
340342
341- - If ` ReadableStreamDefaultReader ` ’s ` read() ` method is called, then if the
342- ` pull ` method is called as a result , then that would be its causal context.
343- This is even if the queue is not empty and the call to ` pull ` is deferred
343+ - If ` ReadableStreamDefaultReader ` ’s ` read() ` method is called and that causes a
344+ call to the ` pull ` method, then that would be its causal context. This would
345+ be the case even if the queue is not empty and the call to ` pull ` is deferred
344346 until previous invocations resolve.
345347- If a ` Request ` is constructed from a ` ReadableStream ` body, and that is passed
346348 to ` fetch ` , the causal context for the ` pull ` method invocations should be the
347349 context active at the time that ` fetch ` was called. Similarly, if a response
348350 body ` ReadableStream ` obtained from ` fetch ` is piped to a ` WritableStream ` ,
349351 its ` write ` method’s causal context is the call to ` fetch ` .
350352
351- > TODO: Discuss the details after figuring out events.
352- >
353- > - ` controller.enqueue ` inside a ` .run() `
354- >
355- > ``` js
356- > const readable = new ReadableStream ({
357- > pull (controller ) {
358- > asyncVar .run (() => {
359- > controller .enqueue (42 );
360- > });
361- > }
362- > });
363- > const writable = new WritableStream ({
364- > write () {
365- > console .log (asyncVar .get ());
366- > }
367- > });
368- > readable .pipeTo (writable);
369- > ` ` `
370- >
371- > - Transferring streams. Would there be a causal context?
353+ In general, the context that should be used is the one that matches the data
354+ flow through the algorithms ([ see the section on implicit propagation
355+ below] ( #implicit-context-propagation ) ).
356+
357+ > TODO: Piping is largely implementation-defined. We should figure out some
358+ > context propagation constraints.
359+
360+ > TODO: If a stream gets transferred to a different agent, any cross-agent
361+ > interactions will have to use the empty context. What if you round-trip a
362+ > stream through another agent?
372363
373364### Observers
374365
@@ -397,53 +388,62 @@ registration context; that is, the context in which the class is constructed.
397388 [ \[ REPORTING\] ] ( https://w3c.github.io/reporting/ )
398389
399390In some cases it might be useful to expose the causal context for individual
400- observations; for example, the context in which a ` PerformanceObserver `
401- observation was captured. This can be done by exposing an
402- ` AsyncContext .Snapshot ` property or getter on the observation record (e.g. on
403- ` PerformanceEntry ` ).
391+ observations, by exposing an ` AsyncContext.Snapshot ` property on the observation
392+ record. This should be the case for ` PerformanceObserver ` , where
393+ ` PerformanceEntry ` would expose the snapshot as a ` resourceContext ` property.
404394
405395## Events
406396
407397Events are a single API that is used for a great number of things, including
408- cases which have a causal context (for events, also referred to as the dispatch
409- context) separate from the registration context, and cases which have no
410- dispatch context at all.
398+ cases which have a causal context (for events, also referred to as the
399+ ** dispatch context** ) separate from the registration context, and cases which
400+ have no dispatch context at all.
411401
412402For consistency, event listener callbacks should be called with the dispatch
413403context. If that does not exist, the empty context should be used, where all
414404` AsyncContext.Variable ` s are set to their initial values.
415405
416- This use of the empty context, however, clashes with the goal of allowing
417- “isolated” regions of code that share an event loop, and being able to trace
418- in which region an error originates. A solution to this would be the ability to
419- define a fallback context for a region of code. We have a proposal for this
420- being fleshed out at issue
406+ Event dispatches can be one of the following:
407+ - ** Synchronous dispatches** , where the event dispatch happens synchronously
408+ when a web API is called. Examples are ` el.click() ` which synchronously fires
409+ a ` click ` event, setting ` location.hash ` which synchronously fires a
410+ ` popstate ` event, or calling an ` EventTarget ` 's ` dispatchEvent() ` method. For
411+ these dispatches, the TC39 proposal's machinery is enough to track the
412+ dispatch context, with no help from web specs or browser engines.
413+ - ** Browser-originated dispatches** , where the event is triggered by browser or
414+ user actions, or by cross-agent JS, with no involvement from JS code in the
415+ same agent. Such dispatches can't have any dispatch context, so the listener
416+ is called with the empty context. (Though see the section on fallback context
417+ below.)
418+ - ** Asynchronous dispatches** , where the event originates from JS calling into
419+ some web API, but the dispatch happens at a later point. In these cases, the
420+ context should be tracked along the data flow of the operation, even across
421+ code running in parallel (but not through tasks enqueued on other agents'
422+ event loops). [ See below on implicit context
423+ propagation] ( #implicit-context-propagation ) for how this data flow tracking
424+ should happen.
425+
426+ This classification of event dispatches is the way it should be in theory, as
427+ well as a long-term goal. However, as we describe later in the section on
428+ implicit context propagation, for the initial rollout we propose treating the
429+ vast majority of asynchronous dispatches as if they were browser-originated.
430+ The exceptions would be:
431+
432+ - The ` popstate ` event
433+ - The ` message ` and ` messageerror ` events
434+ - All events dispatched on ` XMLHttpRequest ` or ` XMLHttpRequestUpload ` objects
435+ - The ` error ` , ` unhandledrejection ` and ` rejectionhandled ` events on the global
436+ object (see below)
437+
438+ ### Fallback context
439+
440+ This use of the empty context for browser-originated dispatches, however,
441+ clashes with the goal of allowing “isolated” regions of code that share an event
442+ loop, and being able to trace in which region an error originates. A solution to
443+ this would be the ability to define a fallback context for a region of code. We
444+ have a proposal for this being fleshed out at issue
421445[ #107 ] ( https://github.com/tc39/proposal-async-context/issues/107 ) .
422446
423- ### Design principles for dispatch snapshots
424-
425- In some cases, a single event might have multiple async dispatch contexts as its
426- possible causes, because the incoming data flow for that event might have
427- multiple branches that go back to different script executions. This is
428- particularly important when the data flow in implementations is expected to be
429- different from the spec in relevant ways.
430-
431- An example of this is HTML media playback events, where if the user clicks play,
432- and in short succession JS code calls ` videoEl .load ()` and then ` videoEl .play ()`
433- in two different contexts, all three data flow branches will merge, resulting in
434- a single ` load` event being fired.
435-
436- If some of those sources are browser-originated and some originate from JS, only
437- the latter ones should be considered. Beyond that, each such individual event
438- would have to be considered. For media playback events, this merge is caused by
439- debouncing (i.e. if a load is already in progress, calling a method that would
440- start one will reuse the existing one), and so the dispatch context should be
441- that for the earliest web API call that resulted in this event. But other cases
442- might have other needs, and their specifics need to be considered.
443-
444- > TODO: Describe automatic tracking through “queue a task” and “in parallel”
445- > algorithms. Also describe rollout for async dispatch contexts.
446-
447447## Script errors and unhandled rejections
448448
449449The ` error ` event on a window or worker global object is fired whenever a script
@@ -520,7 +520,8 @@ categories in the [“Writing Promise-Using Specifications”](https://w3ctag.gi
520520 and the loading of the font). But this is not always the case, as for the
521521 [ ` ready ` ] ( https://streams.spec.whatwg.org/#default-writer-ready ) property of a
522522 [ ` WritableStreamDefaultWriter ` ] ( https://streams.spec.whatwg.org/#writablestreamdefaultwriter ) ,
523- which could be caused to reject by a different context.
523+ which could be caused to reject by a different context. In such cases, the
524+ context should be [ propagated implicitly] ( #implicit-context-propagation ) .
524525- More general state transitions are similar to one-time “events” which can be
525526 reset, and so they should behave in the same way.
526527
@@ -537,11 +538,7 @@ empty AsyncContext snapshot, which will be an empty mapping (i.e. every
537538When you import a JS module multiple times, it will only be fetched and
538539evaluated once. Since module evaluation should not be racy (i.e. it should not
539540depend on the order of various imports), the context should be reset so that
540- module evaluation always runs with the empty AsyncContext snapshot. Inside of
541- ` ShadowRealm ` s, the AsyncContext snapshot for all module evaluations will be the
542- snapshot which was active when the ` ShadowRealm ` was created.
543-
544- > TODO: Not the empty context? How does this interact with the fallback context?
541+ module evaluation always runs with the empty AsyncContext snapshot.
545542
546543# Editorial aspects of AsyncContext integration in web specifications
547544
@@ -590,26 +587,62 @@ steps _steps_, would do the following:
590587> 1 . Throw _ e_ .
591588> 1 . [ AsyncContextSwap] ( https://tc39.es/proposal-async-context/#sec-asynccontextswap ) (_ previousContext_ ).
592589
593- In cases such as events and unhandled promise rejections, tracking the causal
594- context is not always easy, because it is not always the case that the web API
595- that ultimately ends up dispatching an event or rejecting a promise is related
596- to that event or promise.
590+ For web APIs that use the registration context and take a callback, this should
591+ be handled in WebIDL by storing the result of ` AsyncContextSnapshot() ` alongside
592+ the callback function, and swapping it when the function is called. Since this
593+ should not happen for every callback, there should be a WebIDL extended
594+ attribute applied to callback types to control this.
595+
596+ ## Implicit context propagation
597597
598- Therefore, we propose that the HTML event loop’s queueing algorithms, such as
598+ While tracking contexts along algorithm data flows is straightforward when it
599+ happens synchronously within a single event loop task, in some cases (such as
600+ for asynchronously dispatched events, or unhandled promise rejections) the
601+ context should be tracked through parallel algorithms and through tasks being
602+ queued into the event loop.
603+
604+ In a number of these cases, in particular for asynchronously dispatched events,
605+ there is no need for the browser engine to track the data flow, because the
606+ result is trivial (e.g. XHR events will have the context of the ` xhr.send() `
607+ call that caused them). But in other cases it's not that simple.
608+
609+ We propose that the HTML event loop’s queueing algorithms, such as
599610[ queue a task] ( https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-task ) ,
611+ [ queue a microtask] ( https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-microtask ) ,
600612as well as [ in parallel] ( https://html.spec.whatwg.org/multipage/infrastructure.html#in-parallel ) ,
601613should propagate the current AsyncContext mapping, even through parallel
602- algorithms, so that every event queue task has the right causal context.
603-
604- > TODO: Details of how this would be specified and implemented.
614+ algorithms, so that every event loop task has the right causal context by
615+ default.
616+
617+ The details of this implementation are still left to figure out, but each set of
618+ steps running in parallel would have a current snapshot (sort of a thread-local
619+ variable), which would be a parallel equivalent of an event loop's
620+ ` [[AsyncContextMapping]] ` agent field. And whenever the spec says to run a set
621+ of steps in parallel, or to queue a task/microtask, the current snapshot or
622+ ` [[AsyncContextMapping]] ` would be propagated to that parallel algorithm or
623+ task. Browser-originated tasks or parallel algorithms would have an empty
624+ current snapshot.
605625
606626In some cases, this automatic context propagation might not do the right thing,
607- particularly in cases where the exact data flow of certain steps is handwaved
608- (e.g. fetch’s interaction with the HTTP spec, or CSSOM View events). In those
609- cases, the context would have to be manually tracked as shown above. This would
610- also be the case for cases where the registration context must be used when
611- there is no dispatch context, although this could also be implemented via
612- WebIDL.
627+ however, particularly in cases where the exact data flow of certain steps is
628+ handwaved (e.g. fetch’s interaction with the HTTP spec, or CSSOM View events).
629+ In those cases, the context would have to manually tracked in the specs as shown
630+ in the previous section.
631+
632+ Now, it might be that the exact data flow of the browser implementation of some
633+ algorithms might not exactly match the spec’s data flow in all cases. This is
634+ especially the case in browsers that have a renderer process vs main process
635+ architecture. And in general, this implicit propagation might be hard to
636+ implement and get right in browser engines.
637+
638+ Most web APIs, in fact, although could be implemented through implicit context
639+ propagation, can also be implemented by storing the causal context and restoring
640+ it when the callback gets called. This is not generally the case for events with
641+ asynchronous dispatches, but it is for some. Therefore, in order to avoid
642+ needing browser engines to implement the whole implicit context propagation
643+ machinery in the initial AsyncContext rollout, we propose limiting the set of
644+ event dispatches that behave as if they were asynchronous dispatches, as
645+ outlined above.
613646
614647## Exposing snapshots to JS code
615648
0 commit comments