v153 · enabled by default · css · events
Interoperable dispatch timing for transitionrun and media query events
A conformance fix that moves two event dispatches to their spec-defined points in the rendering steps: media-query change events now fire at the media-query evaluation step (CSSOM View — evaluate media queries and report changes), before any pending animation events; and transitionrun events fire at the animation-events step (Web Animations — update animations and send events) in the same rendering iteration even for transitions created earlier in that iteration — instead of a frame late.
Scripts that implicitly depended on the old Blink timing — for example asserting that transitionrun arrives one frame after a style change that creates a transition, or observing media-query change listeners interleaved with transitionrun/animationstart listeners in a single callback batch — will observe a different, now spec-conformant order from Chrome 153. Review tests that snapshot event order. There is no new API surface to feature-detect; the events, their interfaces, and their targets are unchanged.
Flag state. The milestone=153 listing files this feature under “Enabled by default” (verified 2026-07-29; the listing is authoritative for identity). The Blink runtime feature that gates the change is EventTimingMatchingHTML, recorded with status experimental in runtime_enabled_features.json5 at trunk (fetched 2026-07-29) and not marked public: true — so there is no chrome://flags entry. For local testing of the aligned behavior outside the rollout, launch Chrome with --enable-blink-features=EventTimingMatchingHTML. The two implementation CLs (8044688, 8045604) both landed behind this flag.
at a glance
| What it is | A Blink conformance change to when two existing events dispatch during the event loop's rendering steps — media-query change (MDN) and transitionrun (MDN) — aligning Chrome with the HTML event loop specification and with Gecko/WebKit |
|---|---|
| Milestone listing | Chrome 153 — Enabled by default (listing, verified 2026-07-29) |
| New API surface | None — no new interfaces, members, values, or flags for authors |
| What changes | (1) transitionrun fires in the same rendering iteration that creates the transition (previously delayed to a later iteration); (2) media-query change fires at the media-query step, before pending animation events (previously intermixed with them) |
| Runtime feature | EventTimingMatchingHTML (status experimental, not public) in runtime_enabled_features.json5 |
| Implementation | CL 8044688 (merged 2026-07-08) + CL 8045604 (merged 2026-07-14), both in page_animator.cc; tracking bug crbug.com/397737222 |
| ChromeStatus | 6312504658624512 — Interoperable dispatch timing for transitionrun and media query events (Blink component Blink>Animation) |
why it exists
The ChromeStatus record states the motivation verbatim:
“Aligns Blink's dispatch timing for animation “transitionrun” event and media query “change” event with the spec, making the timing interoperable with Gecko and WebKit. More precisely, as per HTML window event loop specification, “transitionrun” events will be fired at Step 3.11 even for animations created earlier in the same iteration (instead of delaying them for a later iteration), and the media query “change” event will be fired at Step 3.10 before firing any pending animation events (instead of intermixing them with animation events at Step 3.11).”
Two long-standing Blink deviations are being removed:
- Frame-late
transitionrun. When a style change created a transition during a rendering iteration, Blink queued itstransitionrunfor a later iteration. The spec model queues the event as part of that same iteration's animation-event dispatch, so a listener can observe (and mutate) the transition one frame earlier. - Interleaved media-query events. Blink dispatched media-query
changeevents intermixed with animation events in the animation-events step. The spec gives media-query evaluation its own, earlier step — so achangelistener that starts or cancels an animation now reliably runs before that iteration's animation events, and the animation events it causes are still picked up in the same iteration.
Both deviations made cross-browser test assertions and framework scheduling code order-dependent in ways that differed per engine; the listing frames the fix as an interoperability change. (The listing’s “Step 3.10/3.11” numbering refers to the update the rendering sub-steps of the event loop processing model; the step names are quoted below because numbering shifts as steps are added.)
Source: ChromeStatus API record (summary quoted verbatim); HTML — event loop processing modelthe spec order: where each event belongs
The update the rendering steps of the HTML event loop run, in order (relevant steps quoted verbatim from the processing model, fetched 2026-07-29):
- … run the resize steps for
doc[CSSOM View] - … run the scroll steps for
doc[CSSOM View] - … evaluate media queries and report changes for
doc[CSSOM View] — the media-query step (listing “3.10”) - … update animations and send events for
doc[Web Animations] — the animation-events step (listing “3.11”) - … run the fullscreen steps, canvas context-lost steps, and then the animation frame callbacks (
requestAnimationFrame)
Two consequences follow from the spec text itself:
- Media-query events belong to an earlier step than animation events — CSSOM View's algorithm fires a
changeevent (a MediaQueryListEvent) at each MediaQueryList “in the order they were created, oldest first” whenever itsmatchesstate changed since the last run. - Web Animations' algorithm updates timeline current times and then dispatches the resulting animation events in the same step — nothing in the spec defers events for animations created earlier in the same iteration to a later one.
behavior contract 1: transitionrun in the creating iteration
CSS Transitions 2 §5.1 dispatches transition events by comparing the transition’s phase “in the previous animation frame to its current state”; the phase table maps idle → pending or before to transitionrun (elapsed time = interval start), with the target being “the transition’s owning element”. The event marks the moment a transition first leaves the idle phase — before any transition-delay has begun.
| Situation | Before Chrome 153 (Blink) | From Chrome 153 (spec-aligned) |
|---|---|---|
| Transition created by a style change during iteration N | transitionrun dispatched in a later rendering iteration (frame late) | transitionrun dispatched in iteration N’s animation-events step (“update animations and send events”) |
| Listener mutates the animation it just observed | Mutation lands one frame later than the event queue implies | Listener runs in the same frame; its mutations apply to the current iteration’s rendering |
| Animations removed during the update | Remove-generated events could miss the frame | Events generated during the update (e.g. animation remove events) “fire within the same frame” (CL 8045604) |
Implementation: CL 8045604 (merged 2026-07-14) moved document animation updates and scroll-animation servicing into the Step 11 position for all non-throttled documents, and dispatches each non-throttled controller’s animation events immediately after updating its animations — so events produced by the update are delivered in the same frame. Throttled (e.g. hidden) documents are not serviced on this cadence, as before.
Source: CL 8045604 commit message; CSS Transitions 2 §5.1 event dispatch; Web Animations — update animations and send eventsbehavior contract 2: media-query change before animation events
CSSOM View’s algorithm: for each MediaQueryList whose document is being rendered, “in the order they were created, oldest first”, if the list’s matches state changed since the last run, “fire an event named change at target using MediaQueryListEvent, with its isTrusted attribute initialized to true, its media attribute initialized to target’s media, and its matches attribute initialized to target’s matches state.”
| Situation | Before Chrome 153 (Blink) | From Chrome 153 (spec-aligned) |
|---|---|---|
| Viewport/class change flips a media query during iteration N | change dispatched intermixed with animation events at the animation-events step | change dispatched at the earlier media-query step, before pending animation events |
change listener starts/cancels an animation | Resulting animation events raced in the same mixed batch | Listener runs first; animation events it causes are still collected and dispatched in the same iteration’s animation step |
Legacy mql.addListener(cb) callbacks | Separate dispatch path from standard change events | Standard MediaQueryListEvent “dispatched immediately in Step 10 alongside legacy mql.addListener() callbacks” (CL 8044688) — both paths observe the same step |
behavior contract 3: the compat flush after animation events
Blink historically used the animation-events step as an unfiltered flush for queued events beyond animation events. CL 8044688 splits this in two, in its own words:
“While HTML spec for Step 11 is only about animation updates, Blink has been dispatching all events without any filtering here. … We are splitting this dispatch in two: first dispatch the animation events as per Step 11, then dispatch all events not covered to maintain backward compatibility. We are avoiding unfiltered dispatch in the last step to prevent premature dispatch of any events covered by the previous steps in case any such event is queued after their dispatch before in the same loop.”
Net effect for authors: events that belong to the named rendering steps (resize, scroll, media-query, animation) are no longer at risk of being flushed early by the catch-all in page_animator.cc; events the spec doesn’t assign to a step keep their historical delivery point. This is the piece that keeps the alignment web-compatible — the CL frames the second dispatch explicitly as backward compatibility, not a new contract.
observing the new order (examples)
Nothing to feature-detect — the events and interfaces are unchanged — but the ordering is observable. These gendn-derived snippets (not from a spec; labelled as such) log the relative order so you can compare engines or pre/post-153 Chrome:
// Order probe: media-query change vs animation events in one iteration
const log = [];
const mql = matchMedia("(max-width: 600px)");
mql.addEventListener("change", () => log.push("mq-change"));
document.documentElement.style.transition = "margin-left 100s";
document.documentElement.addEventListener("transitionrun",
() => log.push("transitionrun"), { once: true });
// Force both in one iteration: shrink the viewport and flip the property.
// Spec order (Chrome 153+): "mq-change" before "transitionrun".
// Old Blink: the two could appear in either relative order.
// Same-iteration transitionrun: listener observes the transition
// in the frame it was created (Chrome 153+), not a frame later.
el.addEventListener("transitionrun", (e) => {
// e.elapsedTime === 0 at interval start (negative delay = -delay)
console.log("running:", e.propertyName, e.elapsedTime);
});
el.style.transition = "opacity 1s";
el.style.opacity = "0"; // creates + queues in one style change
The Chrome Platform Showcase has no demo route for this feature (route HEAD-checked 404, 2026-07-29), so no live iframe is embedded; the snippets above are the canonical probes.
Source: ordering asserted by the ChromeStatus summary and CL 8044688; snippets are gendn-derived illustrationsweb platform tests
The timing change rides on existing WPT suites — neither implementation CL adds new WPT files (their file lists touch only page_animator.cc, the runtime-flag record, and Chromium unit tests — verified in the CL file lists, 2026-07-29):
css/css-transitions/event-dispatch.tentative.html— 30promise_tests over the transition-event phase machine; its harness treats events as late only if two animation frames pass without them, and asserts per-phaseelapsedTimevalues.css/cssom-view/MediaQueryList-change-event-matches-value.htmlandMediaQueryListEvent.html— the media-query change-event value/interface contracts.- Chromium unit tests updated by the CLs themselves:
core/input/event_handler_test.cc,core/annotation/annotation_agent_impl_test.cc(CL 8045604 file list).
The ChromeStatus record links a sibling event-loop alignment test — new-scroll-event-dispatched-at-next-updating-rendering-time.html (scroll events at the scroll step) — viewed with the interop filter; it documents the same rendering-steps program rather than these two events directly.
browser compatibility
Interim tables. BCD records support for the events but not for dispatch timing (no BCD key exists for the alignment behavior; a webstatus.dev query returns zero matches, 2026-07-29). The alignment rows below come from the authoritative milestone listing, not BCD.
| Engine | Aligned timing | Evidence |
|---|---|---|
| Chrome / Edge (Chromium) | From Chrome 153 | milestone=153 listing (“Enabled by default”); gated by EventTimingMatchingHTML (flag record) |
| Firefox (Gecko) | Already conformant (per listing) | ChromeStatus record: the change makes Blink “interoperable with Gecko and WebKit”; vendor-position field records no objection (no notes) |
| Safari (WebKit) | Already conformant (per listing) | Same record; no WebKit standards-position link on record |
| Event | Chrome | Firefox | Safari |
|---|---|---|---|
transitionrun (api.Element.transitionrun_event) | 74 | 53 | 13.1 |
Media-query change (api.MediaQueryList.change_event) | 39 | 55 | 14 |
security and privacy
- No new API surface — no new interfaces, members, permissions, or storage; the change moves existing events between spec-named steps.
- No new information exposure — the same events carry the same payloads to the same targets; only their relative dispatch order and frame alignment change.
- Timing-observability unchanged in kind — scripts could always observe event order; the aligned order is now cross-engine consistent, which reduces engine-fingerprinting variance rather than adding a signal.
- No security or privacy section of the cited specs introduces additional considerations for these dispatch steps beyond the event definitions themselves.
specifications
| Document | Relevance |
|---|---|
| HTML Standard — event loop processing model | The normative rendering-steps order (media-query evaluation before animation events); the listing’s “Step 3.10/3.11” refers to these sub-steps |
| CSSOM View — evaluate media queries and report changes | The change-event dispatch algorithm (oldest-first, on matches state change, MediaQueryListEvent payload) |
| Web Animations — update animations and send events | The animation-update-then-dispatch algorithm run at the animation-events step |
| CSS Transitions Level 2 §5.1 — event dispatch | The transition phase machine and the transitionrun mapping (idle → pending-or-before; target = owning element) |
see also
- Chrome Platform Status — this feature (API record)
- CL 8044688 — Align PageAnimator with HTML spec for MQ and animation events (merged 2026-07-08) · CL 8045604 — Align PageAnimator with HTML spec for Step 11 animation updates (merged 2026-07-14)
- Tracking bug — crbug.com/397737222 (requires Google sign-in to view details)
- MDN — transitionrun event · MDN — MediaQueryList: change event (both cover the events’ syntax/type, not the dispatch-timing contract — verified 2026-07-29)
- wpt.fyi — transition event-dispatch results · wpt.fyi — cssom-view results