blitz-dom: no step between styling and layout, so an animating element's styles cannot be adjusted
Generated from repros/blitz-dom-no-step-between-style-and-layout/README.md when the site was built at 7aa23e2.
What niso does
Section titled “What niso does”niso applies the user’s text size (Dynamic Type, Android’s font scale) by editing the used font size in the
styles each restyle computes, between resolve and another layout pass (crates/niso-core/src/text_scale.rs):
stylo’s servo build has no text zoom (Device::text_zoom is fixed at 1, style/device/servo.rs). Every
resolve restyles the elements a running animation or transition is on before it lays out, so while one runs that
element, and the descendants that inherit their font size from it, are drawn at their CSS size. niso’s test for it,
text_stays_scaled_while_its_element_transitions (crates/niso-core/tests/text.rs), is ignored until blitz
has the step.
Expected vs actual
Section titled “Expected vs actual”An embedder that adjusts computed styles (here a 2x text zoom on #t) and lays them out:
- Expected:
#tlaid out at twice its width, still or while its color transitions. - Actual: right while still, but at its CSS width in every frame of the transition, since the
resolvethat lays the edit out restyles#tfirst.
zoomed 2x while its color transitions, at 0.2s: expected 279.2px, actual 139.6pxzoomed 2x while its color transitions, at 0.4s: expected 279.2px, actual 139.6pxzoomed 2x while its color transitions, at 0.6s: expected 279.2px, actual 139.6pxThe patch
Section titled “The patch”resolve-style.patch splits BaseDocument::resolve into two public halves, resolve_style(time) -> bool
(messages, scroll animation, device changes and the restyle, running animations included; false when there is
nothing to lay out) and resolve_boxes(time) (damage, box construction, layout, transforms, the paint tree,
subdocuments). resolve calls both, so nothing else changes. An embedder edits styles in between and inserts
damage on what it changed. With it (--features split):
zoomed 2x while its color transitions, at 0.2s: expected 279.2px, actual 279.2pxzoomed 2x while its color transitions, at 0.4s: expected 279.2px, actual 279.2pxzoomed 2x while its color transitions, at 0.6s: expected 279.2px, actual 279.2pxniso-text-scale.patch is niso’s side: text_scale::resolve scales between the halves wherever niso resolved.
With both patches, text_stays_scaled_while_its_element_transitions passes, its three cases under a 2x curve (the
transitioning element, a child with a font size of its own, which already passes without, and a child inheriting
its font size), as does the rest of tests/text.rs and tests/render.rs.
CARGO_TARGET_DIR=~/.capy/work/repro-target cargo runCARGO_TARGET_DIR=~/.capy/work/repro-target sh verify.shcargo run exits 1 while the bug reproduces. verify.sh applies the patch to a copy of the pinned checkout,
runs the repro both ways and niso’s test with both patches (restoring niso and the lock files afterwards), and exits
0 when the repro fails unpatched and everything passes patched.
Upstream main
Section titled “Upstream main”Not checked. Tested at the pinned be60597f67b3cece0456d26e8d7ff5a88600bbc5; main was at b6ace6a0c33e17b654e8e6d0b14a7e04e83756fe on 2026-11-09.
Existing issue or PR
Section titled “Existing issue or PR”None searched.
Issue text
Section titled “Issue text”Title: Expose a step between styling and layout (resolve in two halves)
Body:
Embedders sometimes need to adjust computed styles before layout: a text zoom (servo’s stylo has none), a control
sized to a native widget. Today the only way is to edit after resolve and call resolve again, but every
resolve restyles the elements a running animation or transition is on before laying out, so on those elements the
edit never reaches layout. The attached patch splits resolve into resolve_style and resolve_boxes (with
resolve calling both, unchanged). The repro here zooms an element 2x: with the split it stays zoomed through its
transition; without, it is at its CSS size in every frame. Tested at be60597.