Add exact Chromium coordinate checks for effect, containment, will-change,
and content-visibility triggers, with container-type as a pinned negative
control.
Track transform, filter, backdrop-filter, perspective, containment,
will-change, and content-visibility triggers independently. Use the combined
state for absolute and fixed containing-block ancestry.
Add an exact Chromium fixture for absolute and fixed descendants of
transformed boxes, an unsupported identity transform, and the viewport-fixed
control case.
Track every non-none transform as a containing-block trigger, including
unsupported visual transforms. Keep separate nearest-ancestor maps so
positioned boxes capture absolute descendants while only transforms capture
fixed descendants.
Add a portable containing-block fixture covering start, end, percentage, and
fixed insets. Extend solid-color component checks to validate coordinates as
well as dimensions.
Reparent inset-positioned absolute boxes to the nearest non-static ancestor
and fixed boxes to the initial containing block. Keep auto-axis boxes on the
existing static-position path, distinguish fixed from absolute, and blockify
positioned inline boxes and floats.
Add portable checks for live attribute maps, parser script moves, element
scrolling, and streamed codec transforms. Capture Chromium references through
Playwright's pinned browser and record the exact version and lifecycle.
Make attribute maps live, preserve parser script execution state, and expose
element scrolling used by React commit callbacks. Connect fallback web streams
so React Router and Remix hydration payloads flow through codec transforms.
Adds docs/render with a handover covering what shipped on this branch,
the verification methodology, build gotchas, and the prioritized list of
remaining work, alongside the phase plan and the index of the Gecko
layout study that informed it.
Adds render-repros: one HTML fixture per bug fixed here (absolute fills,
mixed inline+block layout, carousel clipping, z-index order, clear,
tables, image density and alt fallback) plus run.sh to render them all
in obscura and Chromium for side-by-side checking. Run before and after
any layout or paint change.
Paint order was pure tree order, so a dropdown/overlay declared before a
later sibling lost to it and a z:-1 decorative backdrop covered its own
content. A positioned element with a non-zero z-index now lifts its
whole subtree into a separate paint layer: negative layers under the
normal flow, positive above it, each sorted ascending with tree order
preserved within equal z. Nested z roots paint inside their ancestor
root's subtree in tree order, the pragmatic core of CSS stacking
contexts.
clear (left/right/both, previously unparsed) now ends the float zone,
so a clearfix element drops below the float at full width instead of
joining the flow column beside it.
Repro-verified: an earlier z:10 overlay paints over a later sibling, a
later z:-1 backdrop paints under earlier content, and the clear repro
matches Chromium within 3px on every measure. Spot set unchanged or
slightly improved; obstacle course 33/33.
Cells now vertical-align the way tables actually do: middle by default
(the effective UA behavior), with author top/middle/bottom parsed and
applied to whichever shape the cell took: main-axis alignment on the
flex-column stand-in, line alignment on a promoted inline context, or a
pinned-origin offset for the pure-text leaf path where the shaped text
height is known at finalize.
Specified column widths finally apply. <col>/<colgroup> widths (style
or attribute, with span) and colspan-1 cell widths are recorded per
column at build time and pinned inside the column-balancing pass: px
directly, percent against the table's content width, with each column's
content min-content still flooring it. The cell's own width is
neutralized once it has fed the column, so the cell box fills its grid
area instead of shrinking to a fraction of its own already-sized track
(a width:50% cell previously ended up at 25 percent). Unspecified
columns keep content sizing but stretch to fill a definite table width
instead of leaving a dead strip of table background. Row and cell
heights act as row minimums.
Verified against Chromium on a 5-case repro (column from cell width,
row backgrounds, vertical alignment, col element width, percent cell in
a fixed table): all match. Wikipedia and the prose spot set unchanged;
obstacle course 33/33.
paint_image clipped an object-fit:cover overflow to the element box but
ignored the ancestor overflow:hidden clip entirely, so a partially
visible image (a half-scrolled carousel slide, a hero cropped by its
section) painted its full raster past the viewport edge. Pass the
already clip-intersected visible rect down and mask against it whenever
the destination exceeds it on any side.
Broken images now fall back the way browsers do: a non-empty alt renders
as text in place of the image with no placeholder box, alt="" renders
nothing (declared decorative), and only a missing alt keeps the neutral
grey placeholder.
Verified with a repro covering the half-clipped cover slide and all
three alt variants; no movement on the image-heavy spot set; obstacle
course 33/33.
The srcset picker already normalized every candidate to a pixel density
(x descriptor, or w / source-size) to choose one, then threw the density
away, so layout sized the image from the file's raw pixels. Every 2x
responsive asset occupied twice its design size.
Return the picked candidate's density alongside its URL (img srcset,
data-srcset, and picture source paths alike) and divide the fetched
intrinsic dimensions by it, so layout sees CSS px.
Verified: srcset "x.png 2x" of a 648px file lays out at 324 CSS px and
"648w" with sizes=300px at 300, while a bare src stays at raw size; no
regressions on the image-heavy spot set (a bbc delta traced to homepage
content drift against a days-old baseline, 8.0 vs 7.8 percent when
recaptured same-time); obstacle course 33/33.
Two structural fixes to the layout and paint model.
Mixed inline+block content: a block holding both was promoted wholesale
to a flex-row-wrap container to approximate the inline formatting
context. That collapsed contentless block children to width 0 (a
position:relative hero wrapper whose only child is an absolute fill
image vanished with it) and let text share lines with sibling blocks.
Now the parent stays a real block, block-level children stay direct
children (auto width fills the containing block), and each maximal run
of consecutive inline-level siblings becomes one anonymous block-level
box carrying the IFC. A run of pure text and foldable inline wrappers
collapses to a single shaped cosmic-text leaf (one taffy node per run
instead of one per word); runs with atomic inline boxes fall back to a
flex-wrap wrapper. Absolutely positioned children neither join nor
break runs and no longer count as inline content, so their containing
block keeps its real used width and taffy resolves width:100%/inset:0
fills against it.
Overflow clips: an inherited overflow:hidden clip was shifted by the
painted node's own accumulated translate, so a carousel track's
translateX dragged the viewport's clip along with every slide and all
slides stayed visible stacked. Clips are now stored in the clip owner's
coordinate space (owner translate baked in at resolve time) and no
longer move with descendants, for boxes, per-word text, and shaped
inline items alike. The same walk records per-node accumulated
translates once, replacing the per-painted-node ancestor walks. Also
parse translate3d (Swiper-style carousels position their track
exclusively through it) and make transform:none reset a lower-priority
transform.
Verified: abs-fill repros that rendered 0x0 now fill across
block/flex/grid parents; carousel repro shows exactly one slide;
danluu.com mismatch vs Chromium 13.0 to 5.7 percent; no regressions
across the prose and framework spot sets; obstacle course 33/33; 36
percent faster on a layout-bound synthetic page with neutral real-page
memory.
Skip <link rel=stylesheet> sheets that are disabled or gated to
print/dark, and resolve @import chains inside fetched sheets so the
imported CSS reaches the cascade.
- disabled links (JS-toggled alternate themes) no longer paint pages
dark under a light Chromium
- media="(prefers-color-scheme: dark)" link sheets are skipped
- @import url(...) is fetched recursively; Sphinx docs pull their layout
from basic.css via classic.css and it was being dropped
Pixel fidelity vs headless Chromium: docs.python 95.5 to 10.2 percent,
web.dev 91.1 to 10.5, developer.chrome 88.0 to 18.9. No regressions on
the good-site set; obstacle course still 33/33.
A direct child of a grid container is blockified per CSS Display 3, so an
inline <a>/<time>/<span> becomes its own block-level grid item instead of
being flattened into loose words in the container (which shattered grid
lists like jvns.ca's .article-list of alternating <time>/<a> into one word
per cell). Grid only, not flex, since flex-column is also our block
stand-in for <td>.
An <img src="//host/path"> (ubiquitous on Wikipedia and CDN-hosted media)
now inherits the document scheme, defaulting to https for non-network bases
so a file:// page does not turn it into file://host. Previously url::join
against a file base produced an unfetchable URL.
Chrome's default sans-serif on this class of host is Liberation Sans, not
DejaVu (measured: sans-serif == Arial == Helvetica == Liberation width,
DejaVu ~12% wider). Forcing DejaVu drifted all text wider and shifted
every line. Bundle Liberation Sans/Serif/Mono (DejaVu kept as glyph
fallback), default to Liberation Sans, and resolve font-family to the
right generic (monospace/code -> Mono, serif -> Serif, else -> Sans).
A controlled text page drops from ~15-20% to 3.5% pixel mismatch and
every line lands within 1px of Chrome.
percentage padding/margin: top/bottom percentages resolve against the
containing-block width (the responsive aspect-ratio-box trick, e.g.
padding-top:56.25%), resolved to px in the top-down layout pass so card
media boxes reserve space instead of collapsing.
box-shadow: parse and paint an outset shadow (rounded, offset, spread,
soft-edge approximation) so cards/buttons/menus have depth.
external-sprite svg: resolve <use href="url#id"> to an external sprite
file, inject the referenced symbol so inline-svg icon systems rasterize.
background-clip:text: paint transparent-with-gradient text (the common
gradient hero/button label) instead of leaving it invisible.