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.
Source-dated flag state. The milestone=153 listing files this as “Enabled by default”. As of 2026-10-08, EventTimingMatchingHTML is status: "stable" on trunk, but remains status: "experimental" on the M153 branch (7978). Neither record marks it public: true; the Blink command-line switch, not a documented chrome://flags entry, is the reproducible way to compare behaviors in a local build: --enable-blink-features=EventTimingMatchingHTML versus --disable-blink-features=EventTimingMatchingHTML. The later trunk flip alone does not establish the M153 build default.
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: stable on trunk, experimental on M153 branch (2026-10-08); neither publicly exposed as a flags-UI toggle |
| 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 | Both paths observe the media-query step; page_animator.cc calls CallMediaQueryListListeners() before dispatching queued MediaQueryListEvent listeners when the flag is enabled. This is Blink source order, not a cross-engine ordering promise |
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.”
The M153 page_animator.cc predicate defines this flush by exclusion, not by a finite allowlist: it excludes scroll, scrollsnapchange, scrollsnapchanging, scrollend, resize, pagereveal, and events whose interface is MediaQueryListEvent. Remaining queued events, including animation events and the source-comment examples of <dialog> close and text-control select/change, are dispatched here. This is an implementation filter, not an exhaustive author-facing event list or a new timing guarantee for every unlisted event.
observing the new order (examples)
Nothing new to feature-detect, but the order can be measured. This probe was run in local headless Chrome 154.0.8037.97 (2026-10-08), with a CDP viewport override from 800 to 500 CSS pixels immediately after the opacity change. Run it on a page initially wider than 600px; let #timing-probe render before changing its opacity. CDP’s Emulation.setDeviceMetricsOverride (width 500, height 800, deviceScaleFactor 1, mobile false) supplies the resize — window.resizeTo() did not resize this headless build.
<div id="timing-probe" style="width:40px;height:40px;opacity:1;transition:opacity 1s"></div>
<script>
const trace = [];
let frame = 0;
function tick() { frame++; requestAnimationFrame(tick); }
requestAnimationFrame(tick);
const mql = matchMedia('(max-width:600px)');
mql.addEventListener('change', () => trace.push(['mq-change', frame]));
const box = document.querySelector('#timing-probe');
box.addEventListener('transitionrun', () => trace.push(['transitionrun', frame]));
// After two rAFs allow initial opacity to render, then run these two CDP steps:
box.style.opacity = '0';
trace.push(['set', frame]);
// CDP: Emulation.setDeviceMetricsOverride({width:500,height:800,
// deviceScaleFactor:1,mobile:false}); then inspect trace after 1 second.
</script>
Observed flag comparison: with --enable-blink-features=EventTimingMatchingHTML, set, mq-change, and transitionrun were all recorded in frame 17, in that order. With --disable-blink-features=EventTimingMatchingHTML, set and mq-change were in frame 22, while transitionrun arrived in frame 23. The absolute frame numbers are incidental; the on/off difference reproduces the frame-late transition regression only with the flag disabled in this Chrome 154 probe (the flag-on run dispatched all three entries in one frame), and shows the media-query-before-transition ordering for this probe, not a universal assertion about every viewport/transition combination or an M153 binary.
The Chrome Platform Showcase feature route returned HTTP 200 (2026-10-08), but its runtime conformance is UNVERIFIED: both the frames-to-run and event-order concepts reported transitionrun one frame late in local Chrome 154.0.8037.97, even with --enable-blink-features=EventTimingMatchingHTML. The separate gendn flag comparison above resized the top-level CDP viewport from 800 to 500 CSS pixels immediately after changing opacity and saw all three entries in one frame with the flag on (the event a frame later with it off). The CPS concepts use their own embedded stage and breakpoint triggers. These different measurements do not establish a universal contradiction or prove the CPS example conforms: no live iframe is embedded until the setups are reconciled.
web 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) | 153 per listing; verify build behavior | milestone=153 listing; trunk stable versus M153 branch experimental; local Chrome 154 flag-on/off timing observation below |
| 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