Skip to content

Engine gaps, scrollbar passthrough, and the taffy track-sizing hang fix - #42

Merged
pathscale merged 5 commits into
masterfrom
stack/engine-gaps-scrollbars-taffy
Aug 16, 2026
Merged

Engine gaps, scrollbar passthrough, and the taffy track-sizing hang fix#42
pathscale merged 5 commits into
masterfrom
stack/engine-gaps-scrollbars-taffy

Conversation

@pathscale

Copy link
Copy Markdown
Owner

Stacks the outstanding engine work onto master and takes the ps-taffy fix that ends a layout hang.

The hang fix (the reason this is urgent)

ps-taffy 4355471d fixes an infinite loop in distribute_space_up_to_limits. A track stays counted as growable while property + increase < limit, but the iteration is sized from limit - property - increase, and f32 rounding lets the first be true while the second is exactly 0.0. The computed increase is then zero, the increase > 0.0 guard rejects every track, nothing changes, and the next iteration recomputes identical values forever.

Blitz reaches this through table column sizing. A document containing a table whose column contributions land a rounding step apart wedges resolve_layout and pins the calling thread at 100% CPU. For an embedder running layout on the UI thread that is an unrecoverable application hang: it survives SIGTERM, because the shutdown path needs the thread the loop is holding.

Upstream detail and the trace are in pathscale/ps-taffy#4.

Also stacked

  • feat(shell): expose the scrollbars feature to embedders
  • feat(shell): pass through shadow-dom, floats and font embolden too
  • feat(shell): pass through complex-scripts as well
  • feat(net): a fetch that keeps the response metadata

These were sitting unlanded on feat/scrollbars-passthrough and fix/engine-gaps. They cherry-picked onto master with no conflicts.

Note on Cargo.lock

The diff is one line, the ps-taffy rev. Running any cargo command here rewrites the ps-anyrender entries from registry to git sources, because master's lock and Cargo.toml already disagree about them. That churn is deliberately excluded; this PR does not attempt to fix the pre-existing rot.

meh added 5 commits August 16, 2026 16:17
`blitz-paint/scrollbars` paints the thumb and `blitz-dom/scrollbars` tracks
drag and hover, but nothing above them could ask for either: `blitz-shell`
passed through `svg` and not this, so an embedder had no route to it.

The cost of that is quiet. An app whose CSS sets `scrollbar-width` or
`scrollbar-color` - both of which `blitz-dom` implements - gets neither the
styling nor a thumb, and nothing reports that the properties went nowhere.

Still off by default: a shell that draws no scrollbars should not pay for the
code.
Same gap as scrollbars: the features exist in `blitz-dom` and `blitz-paint`
and nothing above them could ask.

  shadow-dom  attachment and slot distribution. chuzz needs it - a page using
              custom elements with a shadow root gets no distribution without
              it.
  floats      CSS `float`, which taffy leaves off by default, so a floated
              element lays out as a block and nothing says why.
  font-embolden / apple-font-embolden
              synthetic bold for faces with no bold cut. The apple variant
              matches CoreText's own weighting and is the one for macOS.

`data-uri` was already exposed. `complex-scripts` is deliberately left off:
it is Arabic and Indic shaping, and no consumer here needs it.
I described this as Arabic and Indic shaping and left it off. The flag's own
comment says what it is: dictionary-based line breaking, which is Thai, Khmer,
Lao and Burmese. Those scripts put no spaces between words, so finding a break
point takes a dictionary rather than a space scan, and without it the text does
not wrap or wraps mid-word.

An app that already ships a Chinese locale is one that has readers in that part
of the world, so this is not a hypothetical.
`fetch_async` returns `(String, Bytes)`, the final URL and the body, and
throws away what the server said the bytes were. An embedder loading a
WebAssembly module cannot check `Content-Type` before handing the bytes to
a parser that will report an offset into a file that is not a module at
all. The headers already existed: `fetch_http` read them off the response
and dropped them on the way out.

`fetch_response_async` returns the `FetchResponse` the fetch path already
has: url, status, headers, body.

Additive rather than a widening of `fetch_async`, on purpose. Changing that
return type touches every caller in every embedder for a need only some of
them have, and `HeaderMap` is a heap-allocated multimap the hot path would
then build and clone for every subresource on the page. Existing callers
keep the cheap shape and allocate nothing new.

`fetch_http_response` is a sibling of `fetch_http` rather than a wrapper:
that one consumes the response to reach the body and so cannot hand back
what it read on the way. The per-host permit, the user agent and the
non-2xx handling are duplicated between them and have to move together.

`data:` states its own mime type, so it reports a real `Content-Type`.
`file:` has no server and reports none, which a caller must read as
unknown rather than as a mismatch.

Tested for all three schemes without a network. Checked by mutation:
dropping the data-URL header insert fails the first test.
ps-taffy 4355471d ends an infinite loop in `distribute_space_up_to_limits`,
where f32 rounding could leave a track counted as growable while its remaining
headroom computed as exactly zero. Every subsequent iteration then recomputed
the same values and the loop never returned.

Blitz reaches it through table column sizing, so a document containing a table
whose column contributions land a rounding step apart would wedge layout and
pin whichever thread called `resolve_layout` at 100% CPU.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant