The LZW data for both GIF and TIFF images is sometimes intentionally
missing an end-of-information (EOI) code, which technically is a
decoding error, but in practive is handled gracefully by Firefox, Safari
and Chrome for GIFs and Safari for TIFFs. Let's mirror their behavior.
The included WPT test exposes the fact that trailing garbage bytes can
also result in decoding errors. We handle this in the LZW logic rather
than in the image decoding since our LZW implementation is currently
only used by GIF and TIFF decoding. The error is logged behind the
LZW_DEBUG flag.
Add broader style-invalidation regression coverage for selector shapes
that are easy to mis-model when invalidation data is flattened into
feature sets. Cover selector-list alternatives, nested pseudo-class
arguments, pseudo-element arguments, nth-child selector lists,
quirks-mode class matching, mixed-case ancestor filters, duplicate
invalidation rule merging, and concrete :has() feature filtering.
Keep the expectations on the unoptimized baseline so follow-up
optimization commits can show the counter progressions separately.
These tests dump the normal invalidation counters instead of asserting
specific counter values in script.
An `inset: 0` abspos child of an inline-relative with block-level
descendants should size to the inline's full extent across the
"before"/"middle"/"after" wrappers. The expected output records the
current buggy state (most cases collapse to the viewport); the next
commit fixes it.
::slotted() rules from an outer shadow currently leak onto intermediate
<slot> elements re-slotted into that shadow. The expected output records
the buggy state; the next commit fixes it.
Pin the recompute counters (and shadow-DOM correctness) when a
structural mutation flips a sibling's match for :first-child,
:last-child, :nth-child, :nth-of-type, or a + / ~ combinator. The
affected sibling's entire subtree is marked dirty today, so an 8-leaf
sibling produces elementStyleRecomputations=11. Shadow-host cases pin
that inherited-style propagation reaches light DOM, light-DOM-hosted
shadow roots, and nested shadow DOM under the affected sibling.
The next commit replaces the entire-subtree mark with a root-only mark
when no descendant selector ties matching to the sibling's position,
and rebaselines the counter expectations.
Introduce IncrementalDocumentParser, which streams the response body
through a TextCodec::StreamingDecoder into the HTMLTokenizer one chunk
at a time. The tokenizer pauses when it runs out of input and resumes
once the next chunk is appended; when the body closes we close the
tokenizer's input stream so it can finish the parse.
DocumentLoading routes HTML responses through the new parser instead of
buffering the full body before handing it to HTMLParser.
Add focused coverage for the counters produced when a stylesheet's media
queries are evaluated for the first time. Two paths are exercised:
- document.adoptedStyleSheets, where the sheet is added without an
eager evaluate_media_queries call. The next update_style currently
flips m_did_match from "no recorded result" to a recorded one and
fires MediaQueryChangedMatchState on top of the AdoptedStyleSheetsList
invalidation, so fullStyleInvalidations bumps twice.
- <style> element add via StyleSheetList::add_sheet, which evaluates
media queries eagerly and absorbs the first-eval flip. These tests
pin that behavior so future changes don't regress it.
Cases vary the outer MediaList (empty, all, screen, print, width-based
matching and not), the rule selector (matching, non-matching), inner
@media rules (matching, non-matching, mixed, nested), and @supports
wrapping inner @media. The expectations record behavior before the
optimization, so subsequent commits can show counter progressions.
Add focused coverage for style invalidation matrix behavior.
Cover concrete invalidation metadata collection, :defined triggers,
same-parent moves, :has() mutation roots, and feature filtering.
Also cover dynamic and unprobeable pseudo-classes inside :has().
These include mixed-metadata cases where another :has() selector in the
same scope records concrete metadata.
Add SVG and MathML case-sensitive name coverage for :has() filters.
The expectations record behavior before the optimization, so the next
commit can show counter progressions clearly.
Share the style cache for shadow roots whose only active author sheet is
the same constructed stylesheet. Matching already carries the effective
shadow root separately, so the cache can be reused while selectors such
as :host and ::slotted() still evaluate against each consuming shadow
root.
Keep the optimization conservative by falling back to the existing
per-scope cache whenever the shadow root has multiple active sheets, a
non-constructed sheet, or a page user stylesheet. Drop the shared cache
when the stylesheet rules or media query match state change.
Add coverage for two shadow roots adopting the same constructed sheet,
including :host, ::slotted(), and replaceSync() invalidation.
CSSOM declaration mutations on style rules and nested declarations
do not change selector buckets, layer ordering, or keyframe sets stored
in rule caches. Keep those caches valid and only invalidate affected
styles, while leaving keyframe declaration mutations on the existing
cache-invalidating path.
Add coverage showing a CSSOM style declaration mutation is observed
through an already-built rule cache.
Invalidate only the style scope whose media rules changed instead
of throwing away every shadow root rule cache whenever any active
stylesheet changes media query match state. Shadow-root stylesheet
changes still dirty the host side because :host and ::slotted
selectors can affect nodes outside the shadow tree.
When scoped invalidation leaves dirty descendants in a shadow root,
preserve the host ancestor chain so the document style update walk
reaches them before forced layout.
Add coverage that a matching media rule introduced in one shadow tree
does not broadly invalidate a page full of unrelated shadow roots,
and that a dirty shadow root is updated before layout is forced.
CSSFontFaceRule inherited from CSSStyleSheet::Subresource, which made
each face a critical subresource of its parent stylesheet. New
subresources start in the Unloaded state, and the stylesheet's
loading_state() treats Unloaded as Loading.
When a @font-face declares a unicode-range and no codepoint in that
range is ever rendered, FontComputer only registers the face for
matching and never calls FontFace::load() on it. The face stays
Unloaded, so the parent stylesheet stays stuck reporting Loading,
which keeps HTMLLinkElement's load-event delayer alive and prevents
the document load event from firing. HTMLParserEndState then times
out in phase 2 (WaitingForLoadEventDelay) after 15 seconds.
Decouple @font-face from the stylesheet's loading state. Font loading
remains tracked by FontFaceSet, which is the correct place.
Fixes flakiness in worker tests that create a Worker or SharedWorker
with a missing script URL and only attach an error handler to it.
Once the test callback returns, nothing keeps the worker rooted from
JavaScript. If GC ran before the WebWorker process reported the
script fetch failure, the Worker/WorkerAgentParent cycle could be
collected and the error event never delivered, leaving the test hung
until timeout.
Hold startup-pending WorkerAgentParents from the outside
EnvironmentSettingsObject and release that edge once the script load
succeeds, fails, or the worker closes. The worker now survives long
enough to deliver its first script-load result.
When a click handler calls history.replaceState and the link's
cross-document activation behavior runs in the same task, the queued
sync step runs apply-the-history-step on the navigable mid-navigation,
transitioning its ongoing navigation through "traversal" and back to
null. That aborts the link's navigate event and bails out its deferred
work, leaving the link nav abandoned.
Skip this transient when the navigable already has a fresh ongoing
navigation. No major engine reproduces the race; long term, sync
same-document nav should bypass the traversal queue entirely (matching
Chromium).
This solves the long-standing issue where clicking on a box of tea
on https://twinings.co.uk/ would freeze the browser. :^)
Top-level navigation requests use the document fetch destination. CSP's
effective directive algorithm does not list document as a handled fetch
request destination, but our fallback path treated it like an unknown
fetch destination and applied connect-src.
Return no effective fetch directive for document destinations. This lets
top-level navigation use the CSP navigation checks instead. Keep nested
navigation on the existing frame and iframe path, since HTML rewrites
such requests to the container local name when a navigable has a
container.
This makes https://reddit.com/ load instead of redirecting to a blocked
challenge reponse URL.
When the HTML parser blocks on a synchronous external script, run a
separate tokenizer over the unparsed input and issue speculative fetches
for the resources it finds (script src, link rel=stylesheet|preload, img
src), with <base href> tracking and template/foreign-content skipping.
Also fills in the previously-stubbed "consume a preloaded resource"
algorithm and the document's "map of preloaded resources", so that
<link rel="preload"> followed by a matching consumer deduplicates to
a single fetch.
The img inside a <picture> has to re-run "update the image data" when
nearby <source> elements change, so script-driven swaps of srcset (and
the other dimension/media attributes) actually take effect.
Per the HTML spec, the relevant mutations for an img element include:
"The element's parent is a picture element and a source element that
is a previous sibling has its srcset, sizes, media, type, width or
height attributes set, changed, or removed."
The same applies to source insertion, moving, and removal.
Fixes image loading on https://www.apple.com/mac/
We now track when a parent has a child affected by a backward structural
pseudo-class. These are selectors whose match result for an element can
depend on siblings after that element, such as `:last-child`,
`:only-child`, `:last-of-type`, `:only-of-type`, `:nth-last-child`, and
`:nth-last-of-type`.
When inserting or removing a node, previous siblings only need style
invalidation if one of them was matched against such a selector. Use the
parent-level flag to skip the previous-sibling walk when no child under
that parent can be affected.
This saves a lot of invalidation work on sites that insert a lot of
nodes into the DOM via JS.
Several structural-* stress tests iterated over enough scopes,
mutation cases, and modes to time out on the slower CI runners.
Split each timing-out test along mode boundaries so no single file
runs more than a quarter of the original work, mirroring the
warm/cold/pseudo/detached split shape across the suite:
* structural-descendant-stress: split scope-wise into doc/shadow,
slot, and part files (already there from the earlier commit),
then mode-wise into attached/detached and warm/pseudo halves.
* structural-descendant-state-stress: split mode-wise into the
same four-quadrant attached/detached x warm/pseudo layout.
* structural-descendant-direction-language-stress: ditto.
* structural-child-part-shadow-stress and structural-child-slot-
stress: same four-quadrant split (these already had no -cold
detached modes so the detached half is smaller).
Every resulting file is well under 700 lines of expected output,
keeping the per-test runtime comfortably below the CI timeout.
The two no-op-invalidation-counters tests asserted an exact target
count and printed FAIL when the live count didn't match. The numbers
they record document how much invalidation work happens for a given
mutation, and we expect those numbers to drop as the invalidation
logic improves rather than to be a fixed target right now. Keep the
tests as a moving record of current counts instead of as pass/fail
gates, so churn shows up in expected file diffs without making the
tests look broken.
When the text content under a dir=auto ancestor changes, the
ancestor's effective directionality can flip and inherits down to
every descendant. The previous commit only marked the ancestor
itself dirty, so descendants whose :dir()-dependent style depended
on the ancestor's direction were left rendered against the old
value.
Walk the ancestor's shadow-including inclusive subtree and mark
every element for style update so the new direction propagates
through the cascade.
A large suite of structural style-invalidation tests built around a
single shared driver (structural-matrix.js) that runs each scenario
under a matrix of subject/parent/ancestor topologies: light DOM,
shadow tree, ::slotted, ::part, exportparts, and SVG <use> shadow.
Covers:
* child mutations under parents observable via :empty,
:placeholder-shown, :nth-child, :has(), and ::part.
* descendant mutations affecting :has(), :dir/:lang inheritance,
slot reassignment, and dir=auto text changes.
* detach/reconnect of subtrees, including reconnecting under a
different shadow scope.
* topology changes (slot name, exportparts, SVG <use> rebuild)
that change which scope's rules apply to a subject.
The driver records baseline counter snapshots and asserts both the
visible style result and that the right invalidation work happens
(or doesn't) for each topology variant, so that future changes to
the invalidation pipeline can't silently regress any of them.
Sets the style attribute to the same string twice, and to a
different value once, then uses the styleInvalidations counter to
assert that an identical re-set is a no-op while a real change
invalidates exactly once.
When a subtree is inserted into a connected parent, every newly-
connected element needs to pick up matching style without falling
back to Node::inserted() unconditionally dirtying the world. Build
a small subtree off-document, insert its root, and assert that a
deeply-nested leaf and a freshly-inserted direct child both have
their cascade applied.
Exercises invalidation for child-state pseudo-classes
(:focus-within, :hover, :has(), :empty, :placeholder-shown,
:dir(), :valid/:invalid, :checked, :defined) across DOM mutations:
appendChild, removeChild, moveBefore (same-parent and
cross-parent), DocumentFragment inserts, and shadow-tree variants.
For each combination it verifies the parent's color (or pseudo-
element content) before and after the mutation, locking down the
expected invalidation behavior so future cleanup of the
invalidation pipeline doesn't silently regress these cases.
Tracks the styleInvalidations counter to make sure inserting and
removing children (and document fragments) under a parent that has
no rules keying off child-state pseudo-classes doesn't dirty any
element's style.
The whitespace-normalization loop in prepare_text() called
StringBuilder::append() on each code point, which resolves to the
`char` overload and truncates non-ASCII characters. measureText("ó")
therefore returned a width of 0, despite fillText painting the glyph.
Use append_code_point() instead, and add a regression test for both
precomposed and decomposed accented text.
Move owned ArrayBuffer storage directly when transferring stream
buffers instead of copying the bytes before detaching the source.
WebAssembly memory continues to copy because its ArrayBuffer wraps
externally-owned storage.
Preserve the abrupt completion from DetachArrayBuffer before moving
storage so non-transferable buffers, such as WebAssembly.Memory-backed
views, still surface TypeError through stream operations instead of
aborting.
This saves ~130ms of main thread time when loading a YouTube video
on my Linux computer. :^)
This matches the behavior of other engines. Some CDNs that do content
negotiation will fall back to non alpha-preserving formats if these
values are not present.
WebAssembly.Memory-backed ArrayBuffers wrap external
ByteBuffer storage. When that memory grows,
ByteBuffer::try_resize() may realloc the backing storage while
old fixed-length buffer objects remain reachable from JS.
TypedArrayBase cached m_data for all fixed-length buffers, and
the asm interpreter fast path dereferenced that cached pointer
directly. For wasm memory views this could leave a stale
pointer behind across grow().
Restrict cached typed-array data pointers to fixed-length
ArrayBuffers that own stable ByteBuffer storage.
External/unowned buffers, including WebAssembly.Memory
buffers, now keep m_data == nullptr and fall back to code that
re-derives buffer().data() on each access.
Add regressions for both the original shared-memory grow case
and the second-grow stale-view case.
Implement the width filtering step of the font matching algorithm.
Without it, system font providers that group all widths under one
family could return a condensed variant for font-width: normal,
producing visibly narrower text.
HTMLImageElement's update-the-image-data step 16 queues its state
transition and load event dispatch via a 1 ms BatchingDispatcher, so
the current request does not become CompletelyAvailable synchronously
when the fetch finishes. decode()'s on_finish callback, however, was
queuing its resolve task directly on the event loop, bypassing the
batch. That race meant decode() could resolve while the image request
was still in Unavailable state, so any .then() handler inspecting
img.width / img.height (or anything derived from the bitmap) would see
zeros.
Google Maps hits this on its .9.png road shield icons: after awaiting
img.decode() it reads a.width / a.height and calls
ctx.getImageData(0, 0, 0, 0), which throws IndexSizeError and aborts
the tile rendering pipeline.
Route decode()'s on_finish through the same BatchingDispatcher so both
are processed in the same batch, with the decode resolution queued
after step 16's element task.
Previously it used `realm.[[GlobalObject]]` instead of
`realm.[[GlobalEnv]].[[GlobalThisValue]]`.
In LibWeb, that corresponds to Window and WindowProxy respectively.
Avoid broad document invalidation when adding or removing ordinary
document-owned or shadow-owned stylesheets. Reuse the targeted
StyleSheetInvalidation path for style rules, including shadow-host
escapes, pseudo-element-only selectors, and trailing-universal cases.
Keep the broad path for sheet contents whose effects are not captured
by selector invalidation alone, including @property, @font-face,
@font-feature-values, @keyframes, imported sheets, and top-level @layer
blocks. Broad-path shadow-root sheets still reach host-side consumers
through their active-scope effects.
Toggling CSSStyleSheet::disabled previously cleared the cached media
match bits and reloaded fonts, but never informed the owning documents
or shadow roots that style resolution was now stale. Worse, the IDL
binding for the disabled attribute dispatches through a non-virtual
setter on StyleSheet, so any override on CSSStyleSheet was bypassed
entirely.
Make set_disabled() virtual so the CSSStyleSheet override actually runs,
snapshot the pre-mutation shadow-root stylesheet effects before flipping
the flag, and hand them to invalidate_owners() so a disable that strips
the last host-reaching rule still tears down host-side style correctly.
When invalidate_owners() runs on a stylesheet scoped to a shadow root,
we previously dirtied the host and its light-DOM side too broadly. That
forced restyles on nodes the shadow-scoped stylesheet cannot match.
Inspect the sheet's effective selectors and dependent features up front.
Only dirty assigned nodes, the host, the host root, or host-side
animation consumers when the sheet can actually reach them, while
keeping purely shadow-local mutations inside the shadow tree.
Handle inline stylesheet @keyframes insertions without falling back to
broad owner invalidation. Recompute only elements whose computed
animation-name already references the inserted keyframes name.
Document-scoped insertions still walk the shadow-including tree so
existing shadow trees pick up inherited animations, and shadow-root
stylesheets fan out through the host root so :host combinators can
refresh host-side consumers as well. Also introduce the shared
ShadowRootStylesheetEffects analysis so later stylesheet mutation paths
can reuse the same per-scope escape classification.
Avoid forcing a full style update when a connected inline <style> sheet
inserts an ordinary style rule. Build a targeted invalidation set from
the inserted rule and walk only the affected roots instead.
Introduce the shared StyleSheetInvalidation helper so later stylesheet
mutation paths can reuse the same selector analysis and root application
logic. It handles trailing-universal selectors, pseudo-element-only
rightmost compounds, and shadow-host escapes through ::slotted(...) and
:host combinators.
Keep the broad invalidate_owners() path for constructed stylesheets and
other sheet kinds whose TreeScope interactions still require it.
Adopting a node into another document preserves the node's dirty style
flags, but the destination ancestor chain never sees them propagate. If
a style update is already pending in the new document, it can skip the
adopted subtree entirely.
Snapshot the subtree and child dirty bits before set_document() updates
m_document, then walk the new ancestor chain and re-mark
child_needs_style_update so the pending restyle still descends into the
adopted subtree.
When inheriting custom-property data from a parent element, we were
copying the parent's full CustomPropertyData regardless of whether
each property was registered with `inherits: false`. That caused
non-inheriting registered properties to leak from the parent,
contrary to the @property spec.
Wrap the parent-side lookup so we strip any custom property whose
registration says it should not inherit, and only build a fresh
CustomPropertyData when at least one property was actually filtered.
Key the filtered view's cache on both the destination document's
identity and its custom-property registration generation. The
generation counter is local to each document, so a subtree adopted
into another document (or queried via getComputedStyle from another
window) could otherwise pick up a cached view computed under an
unrelated registration set and silently skip non-inheriting filtering
in the new document.
A @keyframes rule scoped to a shadow root was not reliably reached
from an animated slotted light-DOM element: the keyframes lookup
walked the element's own root first, then fell back to the document,
but slotted elements can pick up animation-name from a ::slotted(...)
rule that lives in an ancestor shadow root rather than in the
element's own tree.
Track the shadow-root scope that supplied each winning cascaded
declaration, and use that scope to resolve the matching @keyframes
when processing animation definitions. A shared constructable
stylesheet can be adopted into several scopes at once, so the
declaration object alone is too weak as a key; the per-entry
shadow-root pointer disambiguates which adoption actually contributed.
Also refresh running CSS animations' keyframe sets when style is
recomputed. Previously only the first animation creation path set a
keyframe set, so an existing animation never picked up newly inserted
@keyframes rules.
The @keyframes parser was storing the keyframes name via
Token::to_string(), which keeps a string token in its quoted,
serialized form. That meant @keyframes "foo" was stored as
"\"foo\"" while animation-name: "foo" resolved to "foo",
and the two never matched.
Store the unquoted string or identifier value so the @keyframes name
and the animation-name reference compare on the same string.
When inline layout emits a whitespace chunk, it previously selected the
surrounding text's font without checking whether that font actually
contains a glyph for the whitespace codepoint. On pages that use
`@font-face` rules sharded by `unicode-range` (e.g. a Roboto webfont
split across one file for Cyrillic letters and another for basic Latin),
the shard covering the letters is picked for an adjacent space even
though the space codepoint lives in a different shard. HarfBuzz then
shapes the space with a font that has no glyph for it and emits
`.notdef`, rendering spaces as tofu boxes.
Check `contains_glyph(space_code_point)` on each candidate in
`font_for_space()` and fall through to
`FontCascadeList::font_for_code_point()` for the whitespace codepoint
when no surrounding font has the glyph.
Fixes whitespace rendering on web.telegram.org/a.
This brings a couple of advantages:
- Previously we relied on the caller validating the parsed value was in
bounds after the fact - this was usually fine but there are a couple
of places that it was forgotten (see the tests added in this commit),
requiring the bounds to be passed as arguments makes us consider the
desired range more explicitly.
- In a future commit we will use the passed bounds as the clamping
bounds for computed values, removing the need for the existing
`ValueParsingContext` based method we have at the moment.
- Generating code is easier with this approach
Also, explicitly prevent drag events from firing when the context menu
opens. This will only be the case on macOS, since its context menu is
opened by Ctrl+mousedown. This replaces the prior exception preventing
drag events when Ctrl is held during mousedown.
Fixes#9018 and #9019