From 54a8d8bb685c18fdd5dfa26ee311bb16a0eec400 Mon Sep 17 00:00:00 2001
From: Andreu Botella User agents that support JavaScript must also implement the AsyncContext proposal.
+ The following terms are defined there, and used in this specification: JSASYNCCONTEXT[Global][PropagatesAsyncContext][LegacyFactoryFunction][LegacyLenientThis][LegacyNullToEmptyString]
+
template element
section.
To run an + algorithm steps with an Async Context Mapping mapping: + JSASYNCCONTEXT
+ +Let previousMapping be + AsyncContextSwap(mapping).
Let returnValue be the result of running steps. If this throws an + exception e, then:
+ +Perform AsyncContextSwap(previousMapping).
Throw e.
Perform AsyncContextSwap(previousMapping).
Return returnValue.
Specifications should not use the AsyncContextSwap abstract operation defined in + JSASYNCCONTEXT, or otherwise set or mutate an agent record's [[AsyncContextMapping]] + field. Instead they should run an algorithm with + an Async Context Mapping, which properly cleans up after itself.
+ @@ -66678,6 +66723,10 @@ o............A....eA script element has a delaying the
load event boolean, initially false.
A script element has an execution Async Context Mapping, which is
+ either null or an Async Context Mapping, initially null. It is used so async-local
+ state from the script preparation is available when the script is executed.
A script element has a type, which is
either null, "classic", "module", "importmap", or "speculationrules", initially null. It is
@@ -67184,6 +67233,9 @@ document.body.append(script1, script2);
+
Set el's execution Async Context Mapping to + AsyncContextSnapshot().
If el does not have a src content
attribute:
script element el:
Let document be el's node document.
Assert: el's execution Async Context Mapping is not + null.
If el's preparation-time document is not equal to - document, then return.
Run the following steps with + el's execution Async Context Mapping:
-Unblock rendering on el.
In the case where prepare the script element + immediately invoked this algorithm, this does not change the current Async + Context Mapping.
-If el's result is null, then fire an event named error
- at el, and return.
Set el's execution Async Context Mapping to null.
If el's from an external file is
- true, or el's type is "module", then increment document's ignore-destructive-writes
- counter.
Let document be el's node document.
Switch on el's type:
+If el's preparation-time document is not equal to + document, then return.
classic"Let oldCurrentScript be the value to which document's currentScript object was most recently
- set.
Unblock rendering on el.
If el's root is not a shadow root, then
- set document's currentScript
- attribute to el. Otherwise, set it to null.
This does not use the in a document tree check, as
- el could have been removed from the document prior to execution, and in that
- scenario currentScript still needs to
- point to it.
If el's result is null, then fire an event named error
+ at el, and return.
Run the classic script given by - el's result.
If el's from an external file is
+ true, or el's type is "module", then increment document's ignore-destructive-writes
+ counter.
Set document's currentScript attribute to
- oldCurrentScript.
Switch on el's type:
-module"Assert: document's currentScript attribute is null.
classic"Let oldCurrentScript be the value to which document's currentScript object was most recently
+ set.
Run the module script given by - el's result.
If el's root is not a shadow root, then
+ set document's currentScript
+ attribute to el. Otherwise, set it to null.
This does not use the in a document tree check, as
+ el could have been removed from the document prior to execution, and in that
+ scenario currentScript still needs to
+ point to it.
importmap"Register an import map given el's relevant global - object and el's result.
Run the classic script given by + el's result.
speculationrules"Register speculation rules given el's relevant global - object and el's result.
Set document's currentScript attribute to
+ oldCurrentScript.
Decrement the ignore-destructive-writes counter of document, if - it was incremented in the earlier step.
module"Assert: document's currentScript attribute is null.
If el's from an external file is
- true, then fire an event named load at el.
Run the module script given by + el's result.
importmap"Register an import map given el's relevant global + object and el's result.
speculationrules"Register speculation rules given el's relevant global + object and el's result.
Decrement the ignore-destructive-writes counter of document, if + it was incremented in the earlier step.
If el's from an external file is
+ true, then fire an event named load at el.
If blur event target is not null, fire a focus event
- named blur at blur event target, with
- related blur target as the related target.
If blur event target is not null:
+ +If blur event target's relevant agent is not the + surrounding agent, or if blur event target's relevant settings + object's origin is not + same origin with the current settings object's origin, then run the following steps with Async Context Mapping « ». + Otherwise, run the following steps:
+ +Fire a focus event named blur at
+ blur event target, with related blur target as the related
+ target.
Issue #3506: If + focus event target's relevant agent is not the surrounding agent, then the event + should be fired in a task in the relevant agent's event loop.
+ +In some cases, e.g., if entry is
an area element's shape, a scrollable region, or a viewport, no
@@ -85428,9 +85517,30 @@ dictionary CommandEventInit : EventInit {
focus target be null.
If focus event target is not null, fire a focus event
- named focus at focus event target, with
- related focus target as the related target.
If focus event target is not null:
+ +If focus event target's relevant agent is not the + surrounding agent, or if focus event target's relevant + settings object's origin is not + same origin with the current settings object's origin, then run the following steps with Async Context Mapping « ». + Otherwise, run the following steps:
+ +Fire a focus event named focus at
+ focus event target, with related focus target as the related
+ target.
Issue #3506: If + focus event target's relevant agent is not the surrounding agent, then the event + should be fired in a task in the relevant agent's event loop.
+ +In some cases, e.g. if entry is an area
element's shape, a scrollable region, or a viewport, no event is fired.
While the event loop's microtask queue is not empty:
+Run the following steps with Async + Context Mapping « »:
Let oldestMicrotask be the result of dequeuing - from the event loop's microtask queue.
While the event loop's microtask queue is not empty:
-Set the event loop's currently running task to - oldestMicrotask.
Let oldestMicrotask be the result of dequeuing + from the event loop's microtask queue.
Run oldestMicrotask.
+Set the event loop's currently running task to + oldestMicrotask.
Run oldestMicrotask.
+ +This might involve invoking scripted callbacks, which eventually calls the + clean up after running script steps, which call this perform a microtask + checkpoint algorithm again, which is why we use the performing a microtask + checkpoint flag to avoid reentrancy.
+This might involve invoking scripted callbacks, which eventually calls the - clean up after running script steps, which call this perform a microtask - checkpoint algorithm again, which is why we use the performing a microtask - checkpoint flag to avoid reentrancy.
+Set the event loop's currently running task back to + null.
Set the event loop's currently running task back to - null.
For each environment settings object settingsObject whose + responsible event loop is this event loop, notify about rejected + promises given settingsObject's global object.
For each environment settings object settingsObject whose - responsible event loop is this event loop, notify about rejected - promises given settingsObject's global object.
Cleanup Indexed Database transactions.
Let uniqueHandle be null.
Let savedAsyncContextMapping be AsyncContextSnapshot().
Let task be a task that runs the following substeps:
Assert: uniqueHandle is a unique internal value, - not null.
Run the following steps with the + Async Context Mapping savedAsyncContextMapping: -
If id does not exist in global's - map of setTimeout and setInterval IDs, then abort these steps.
Assert: uniqueHandle is a unique internal value, + not null.
If global's map of setTimeout and setInterval IDs[id] - does not equal uniqueHandle, then abort these steps.
- -This accommodates for the ID having been cleared by a clearTimeout() or clearInterval() call, and being reused by a subsequent
- setTimeout() or setInterval() call.
If id does not exist in global's + map of setTimeout and setInterval IDs, then abort these steps.
Let settings object be global's relevant settings - object.
If global's map of setTimeout and setInterval IDs[id] + does not equal uniqueHandle, then abort these steps.
+ +This accommodates for the ID having been cleared by a clearTimeout() or clearInterval() call, and being reused by a subsequent
+ setTimeout() or setInterval() call.
If scripting is disabled for - settings object, then abort these steps.
Let settings object be global's relevant settings + object.
Record timing info for timer handler given handler, - settings object, and repeat.
If scripting is disabled for + settings object, then abort these steps.
If handler is a Function, then invoke handler given
- arguments and "report", and with callback this value set to thisArg.
Record timing info for timer handler given handler, + settings object, and repeat.
Otherwise:
+If handler is a Function, then invoke handler given
+ arguments and "report", and with callback this value set to thisArg.
If previousId was not given:
+Otherwise:
Let globalName be "Window" if global is
- a Window object; "WorkerGlobalScope"
- otherwise.
If previousId was not given:
-Let methodName be "setInterval" if
- repeat is true; "setTimeout" otherwise.
Let globalName be "Window" if global is
+ a Window object; "WorkerGlobalScope"
+ otherwise.
Let sink be a concatenation of globalName, U+0020 SPACE, and - methodName.
Let methodName be "setInterval" if
+ repeat is true; "setTimeout" otherwise.
Set handler to the result of invoking the get trusted type compliant string algorithm with
- TrustedScript, global,
- handler, sink, and "script".
Let sink be a concatenation of globalName, U+0020 SPACE, and + methodName.
Set handler to the result of invoking the get trusted type compliant string algorithm with
+ TrustedScript, global,
+ handler, sink, and "script".
Assert: handler is a string.
Assert: handler is a string.
Perform EnsureCSPDoesNotBlockStringCompilation(realm, - « », handler, handler, timer, « », handler). If this throws - an exception, catch it, report it for - global, and abort these steps.
Perform EnsureCSPDoesNotBlockStringCompilation(realm, + « », handler, handler, timer, « », handler). If this throws + an exception, catch it, report it for + global, and abort these steps.
Let fetch options be the default script fetch - options.
Let fetch options be the default script fetch + options.
Let base URL be settings object's API base - URL.
Let base URL be settings object's API base + URL.
If initiating script is not null, then:
+If initiating script is not null, then:
-Set fetch options to a script fetch options whose cryptographic nonce is initiating
- script's fetch options's
- cryptographic nonce, integrity metadata is the empty
- string, parser metadata is
- "not-parser-inserted", credentials mode is
- initiating script's fetch
- options's credentials
- mode, referrer
- policy is initiating script's fetch options's referrer policy, and fetch priority is "auto".
Set base URL to initiating script's base URL.
Set fetch options to a script fetch options whose cryptographic nonce is initiating
+ script's fetch options's
+ cryptographic nonce, integrity metadata is the empty
+ string, parser metadata is
+ "not-parser-inserted", credentials mode is
+ initiating script's fetch
+ options's credentials
+ mode, referrer
+ policy is initiating script's fetch options's referrer policy, and fetch priority is "auto".
Set base URL to initiating script's base URL.
The effect of these steps ensures that the string compilation done by setTimeout() and setInterval() behaves equivalently to that done by
- eval(). That is, module script fetches via import()
- will behave the same in both contexts.
The effect of these steps ensures that the string compilation done by setTimeout() and setInterval() behaves equivalently to that done by
+ eval(). That is, module script fetches via import()
+ will behave the same in both contexts.
Let script be the result of creating a classic script given - handler, settings object, base URL, and fetch - options.
Let script be the result of creating a classic script given + handler, settings object, base URL, and fetch + options.
Run the classic script - script.
Run the classic script + script.
If id does not exist in global's - map of setTimeout and setInterval IDs, then abort these steps.
If id does not exist in global's + map of setTimeout and setInterval IDs, then abort these steps.
If global's map of setTimeout and setInterval IDs[id] - does not equal uniqueHandle, then abort these steps.
- -The ID might have been removed via the author code in handler
- calling clearTimeout() or clearInterval(). Checking that uniqueHandle isn't different
- accounts for the possibility of the ID, after having been cleared, being reused by a
- subsequent setTimeout() or setInterval() call.
If global's map of setTimeout and setInterval IDs[id] + does not equal uniqueHandle, then abort these steps.
+ +The ID might have been removed via the author code in handler
+ calling clearTimeout() or clearInterval(). Checking that uniqueHandle isn't different
+ accounts for the possibility of the ID, after having been cleared, being reused by a
+ subsequent setTimeout() or setInterval() call.
If repeat is true, then perform the timer initialization - steps again, given global, handler, timeout, - arguments, true, and id.
If repeat is true, then perform the timer initialization + steps again, given global, handler, timeout, + arguments, true, and id.
Otherwise, remove global's map of - setTimeout and setInterval IDs[id].
Otherwise, remove global's map of + setTimeout and setInterval IDs[id].
callback FrameRequestCallback = undefined (DOMHighResTimeStamp time);
interface mixin AnimationFrameProvider {
- unsigned long requestAnimationFrame(FrameRequestCallback callback);
+ unsigned long requestAnimationFrame([PropagatesAsyncContext] FrameRequestCallback callback);
undefined cancelAnimationFrame(unsigned long handle);
};
Window includes AnimationFrameProvider;
@@ -155976,6 +156102,9 @@ INSERT INTERFACES HERE
[JPEG]
JPEG File Interchange Format, E. Hamilton.
+ [JSASYNCCONTEXT]
+ AsyncContext. Ecma International.
+
[JSERRORSTACKS]
(Non-normative) Error Stacks. Ecma International.
From 015923ac2b1e1b032cb367af1688a820a2b7ca1f Mon Sep 17 00:00:00 2001
From: Andreu Botella
Date: Thu, 12 Feb 2026 16:06:51 +0100
Subject: [PATCH 2/3] Inline run inside the timer task
---
source | 224 ++++++++++++++++++++++++++++-----------------------------
1 file changed, 109 insertions(+), 115 deletions(-)
diff --git a/source b/source
index ad8c1b70682..5e79d3f0b0b 100644
--- a/source
+++ b/source
@@ -125034,150 +125034,144 @@ interface XMLSerializer {
Let savedAsyncContextMapping be AsyncContextSnapshot().
- Let task be a task that runs the following
- substeps:
+ Let task be a task that runs the following steps with the Async Context Mapping
+ savedAsyncContextMapping:
- -
-
Run the following steps with the
- Async Context Mapping savedAsyncContextMapping:
+
Assert: uniqueHandle is a unique internal value,
+ not null.
-
- Assert: uniqueHandle is a unique internal value,
- not null.
+ If id does not exist in global's
+ map of setTimeout and setInterval IDs, then abort these steps.
- If id does not exist in global's
- map of setTimeout and setInterval IDs, then abort these steps.
+ -
+
If global's map of setTimeout and setInterval IDs[id]
+ does not equal uniqueHandle, then abort these steps.
+
+ This accommodates for the ID having been cleared by a clearTimeout() or clearInterval() call, and being reused by a subsequent
+ setTimeout() or setInterval() call.
+
- -
-
If global's map of setTimeout and setInterval IDs[id]
- does not equal uniqueHandle, then abort these steps.
-
- This accommodates for the ID having been cleared by a clearTimeout() or clearInterval() call, and being reused by a subsequent
- setTimeout() or setInterval() call.
-
+ Let settings object be global's relevant settings
+ object.
- Let settings object be global's relevant settings
- object.
+ If scripting is disabled for
+ settings object, then abort these steps.
- If scripting is disabled for
- settings object, then abort these steps.
+ Record timing info for timer handler given handler,
+ settings object, and repeat.
- Record timing info for timer handler given handler,
- settings object, and repeat.
+ If handler is a Function, then invoke handler given
+ arguments and "report", and with callback this value set to thisArg.
- If handler is a Function, then invoke handler given
- arguments and "report", and with callback this value set to thisArg.
+ -
+
Otherwise:
+
-
-
Otherwise:
+ If previousId was not given:
- -
-
If previousId was not given:
-
-
- Let globalName be "Window" if global is
- a Window object; "WorkerGlobalScope"
- otherwise.
-
- Let methodName be "setInterval" if
- repeat is true; "setTimeout" otherwise.
-
- Let sink be a concatenation of globalName, U+0020 SPACE, and
- methodName.
-
- Set handler to the result of invoking the get trusted type compliant string algorithm with
- TrustedScript, global,
- handler, sink, and "script".
-
-
+ Let globalName be "Window" if global is
+ a Window object; "WorkerGlobalScope"
+ otherwise.
- Assert: handler is a string.
+ Let methodName be "setInterval" if
+ repeat is true; "setTimeout" otherwise.
- Perform EnsureCSPDoesNotBlockStringCompilation(realm,
- « », handler, handler, timer, « », handler). If this throws
- an exception, catch it, report it for
- global, and abort these steps.
-
+ Let sink be a concatenation of globalName, U+0020 SPACE, and
+ methodName.
- Let fetch options be the default script fetch
- options.
+ Set handler to the result of invoking the get trusted type compliant string algorithm with
+ TrustedScript, global,
+ handler, sink, and "script".
+
+
- Let base URL be settings object's API base
- URL.
+ Assert: handler is a string.
- -
-
If initiating script is not null, then:
+ Perform EnsureCSPDoesNotBlockStringCompilation(realm,
+ « », handler, handler, timer, « », handler). If this throws
+ an exception, catch it, report it for
+ global, and abort these steps.
+
-
- Set fetch options to a script fetch options whose cryptographic nonce is initiating
- script's fetch options's
- cryptographic nonce, integrity metadata is the empty
- string, parser metadata is
- "not-parser-inserted", credentials mode is
- initiating script's fetch
- options's credentials
- mode, referrer
- policy is initiating script's fetch options's referrer policy, and fetch priority is "auto".
-
- Set base URL to initiating script's base URL.
-
+ Let fetch options be the default script fetch
+ options.
- The effect of these steps ensures that the string compilation done by setTimeout() and setInterval() behaves equivalently to that done by
- eval(). That is, module script fetches via import()
- will behave the same in both contexts.
-
+ Let base URL be settings object's API base
+ URL.
- Let script be the result of creating a classic script given
- handler, settings object, base URL, and fetch
- options.
+ -
+
If initiating script is not null, then:
- Run the classic script
- script.
+
+ Set fetch options to a script fetch options whose cryptographic nonce is initiating
+ script's fetch options's
+ cryptographic nonce, integrity metadata is the empty
+ string, parser metadata is
+ "not-parser-inserted", credentials mode is
+ initiating script's fetch
+ options's credentials
+ mode, referrer
+ policy is initiating script's fetch options's referrer policy, and fetch priority is "auto".
+
+ Set base URL to initiating script's base URL.
-
-
- If id does not exist in global's
- map of setTimeout and setInterval IDs, then abort these steps.
-
- If global's map of setTimeout and setInterval IDs[id]
- does not equal uniqueHandle, then abort these steps.
-
- The ID might have been removed via the author code in handler
- calling clearTimeout() or clearInterval(). Checking that uniqueHandle isn't different
- accounts for the possibility of the ID, after having been cleared, being reused by a
- subsequent setTimeout() or setInterval() call.
+ The effect of these steps ensures that the string compilation done by setTimeout() and setInterval() behaves equivalently to that done by
+ eval(). That is, module script fetches via import()
+ will behave the same in both contexts.
- If repeat is true, then perform the timer initialization
- steps again, given global, handler, timeout,
- arguments, true, and id.
+ Let script be the result of creating a classic script given
+ handler, settings object, base URL, and fetch
+ options.
- Otherwise, remove global's map of
- setTimeout and setInterval IDs[id].
+ Run the classic script
+ script.
+
+ If id does not exist in global's
+ map of setTimeout and setInterval IDs, then abort these steps.
+
+
+ If global's map of setTimeout and setInterval IDs[id]
+ does not equal uniqueHandle, then abort these steps.
+
+ The ID might have been removed via the author code in handler
+ calling clearTimeout() or clearInterval(). Checking that uniqueHandle isn't different
+ accounts for the possibility of the ID, after having been cleared, being reused by a
+ subsequent setTimeout() or setInterval() call.
+
+
+ If repeat is true, then perform the timer initialization
+ steps again, given global, handler, timeout,
+ arguments, true, and id.
+
+ Otherwise, remove global's map of
+ setTimeout and setInterval IDs[id].
From 7949f3ec07be772d5a3f4c5e42a0952da415ccec Mon Sep 17 00:00:00 2001
From: Andreu Botella
Date: Mon, 13 Apr 2026 18:40:21 +0200
Subject: [PATCH 3/3] AsyncContext error events
This patch adds AsyncContext support for error and promise rejection
events. It builds on top of #12152.
This PR is part of #104432.
---
source | 107 +++++++++++++++++++++++++++++++++++----------------------
1 file changed, 65 insertions(+), 42 deletions(-)
diff --git a/source b/source
index 5e79d3f0b0b..660d56e4e04 100644
--- a/source
+++ b/source
@@ -2316,8 +2316,9 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
getting the keys,
getting the values,
sorting in descending order,
- size, and
- iterate
+ size,
+ iterate, and
+ clone
The list data structure and the associated definitions for
append,
extend,
@@ -115723,14 +115724,16 @@ new PaymentRequest(…); // Allowed to use
A global object has an in error reporting mode
boolean, which is initially false.
- A global object has an outstanding rejected promises weak set, a
- set of Promise objects, initially empty. This set
- must not create strong references to any of its members, and implementations are free to limit
- its size in an implementation-defined manner, e.g., by removing old entries from it
- when new ones are added.
+ A global object has an outstanding rejected promises weak map, a
+ map from Promise objects to
+ Async Context Mappings, initially empty. This map must
+ not create strong references to any of its keys, and implementations are free to limit its size
+ in an implementation-defined manner, e.g., by removing old entries from it when new
+ ones are added.
- A global object has an about-to-be-notified rejected promises list, a
- list of Promise objects, initially empty.
+ A global object has an about-to-be-notified rejected promises map, a
+ map from Promise objects to
+ Async Context Mappings, initially empty.
A global object has an import map,
initially an empty import map.
@@ -118463,39 +118466,48 @@ dictionary ErrorEventInit : EventInit {
global:
- Let list be a clone of global's
- about-to-be-notified rejected promises list.
+ Let map be a clone of global's
+ about-to-be-notified rejected promises map.
- If list is empty, then return.
+ If map is empty, then return.
- Empty global's about-to-be-notified
- rejected promises list.
+ Clear global's about-to-be-notified
+ rejected promises map.
-
Queue a global task on the DOM manipulation task source given
global to run the following step:
- For each promise p of list:
+ For each entry p →
+ asyncContextMapping of map:
If p.[[PromiseIsHandled]] is true, then continue.
- Let notCanceled be the result of firing
- an event named unhandledrejection at
- global, using PromiseRejectionEvent, with the cancelable attribute initialized to true, the promise attribute initialized to
- p, and the reason
- attribute initialized to p.[[PromiseResult]].
+ -
+
Let notCanceled be the result of running the following steps with Async Context Mapping
+ asyncContextMapping:
+
+
+ Return the result of firing an event named
+ unhandledrejection at global,
+ using PromiseRejectionEvent, with the cancelable attribute initialized to true, the promise attribute initialized to
+ p, and the reason
+ attribute initialized to p.[[PromiseResult]].
+
+
If
notCanceled is true, then the user agent may report
p.[[PromiseResult]] to a developer console.
- If p.[[PromiseIsHandled]] is false, then append p to global's outstanding rejected
- promises weak set.
+ If p.[[PromiseIsHandled]] is false, then set
+ (global's outstanding rejected promises weak map)[p] to
+ asyncContextMapping.
@@ -120094,8 +120106,9 @@ dictionary PromiseRejectionEventInit : EventInitIf operation is "reject", then:
- Append promise to global's
- about-to-be-notified rejected promises list.
+ Set (global's about-to-be-notified
+ rejected promises map)[promise] to
+ AsyncContextSnapshot().
@@ -120103,23 +120116,31 @@ dictionary PromiseRejectionEventInit : EventInitIf operation is "handle", then:
- If global's about-to-be-notified rejected promises list contains promise, then remove promise from that list and return.
+ If global's about-to-be-notified rejected promises map contains an entry with the key promise, then remove that entry and return.
+
+ If global's outstanding rejected promises weak map does not
+ contain an entry with the key promise, then
+ return.
- If global's outstanding rejected promises weak set does not
- contain promise, then return.
+ Remove an entry with the key promise from
+ global's outstanding rejected promises weak map.
- Remove promise from global's
- outstanding rejected promises weak set.
+ -
+
Queue a global task on the DOM manipulation task source given
+ global to run the following steps
+ with Async Context Mapping AsyncContextSnapshot():
- Queue a global task on the DOM manipulation task source given
- global to fire an event named rejectionhandled at global, using
- PromiseRejectionEvent, with the promise attribute initialized to
- promise, and the reason
- attribute initialized to promise.[[PromiseResult]].
+
+ Fire an event named rejectionhandled at global, using
+ PromiseRejectionEvent, with the promise attribute initialized to
+ promise, and the reason
+ attribute initialized to promise.[[PromiseResult]].
+
+
@@ -120235,7 +120256,9 @@ dictionary PromiseRejectionEventInit : EventInit
Queue a global task on the JavaScript engine task source given
- global to perform the following steps:
+ global to run the following steps
+ with Async Context Mapping
+ finalizationRegistry.[[FinalizationRegistryAsyncContextMapping]]:
Let entry be