8055 Commits

Author SHA1 Message Date
Johan Dahlin
b27c6d68e8 LibWeb: Dedup @font-face fetches by source URL 2026-04-25 17:06:28 +02:00
Johan Dahlin
d90c5b295a LibWeb: Honor text argument of FontFaceSet.load() / .check() 2026-04-25 17:06:28 +02:00
Johan Dahlin
8c49029bb7 LibGfx+LibWeb: Only trigger @font-face loads from text shaping 2026-04-25 17:06:28 +02:00
Johan Dahlin
acabf765c1 LibWeb+LibGfx: Defer @font-face fetches until a codepoint renders 2026-04-25 17:06:28 +02:00
Johan Dahlin
0de26af387 Tests/LibWeb: Add tests for @font-face load behavior 2026-04-25 17:06:28 +02:00
Aliaksandr Kalenik
4f54b16315 LibWeb: Preserve non-ASCII characters in canvas text preparation
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.
2026-04-25 14:54:18 +02:00
Andreas Kling
0105d56e6c Tests: Add CSS image-set resolution reftest
Cover image-set() candidate selection by resolution for background
images. The tests cover both source-order-independent selection and
calculated resolution descriptors that require layout context.
2026-04-25 14:54:10 +02:00
Andreas Kling
c1fcb7dc3e Tests: Add CSS image-set background reftest
Cover image-set() painting through background-image with URL-backed
SVG candidates. The test verifies that an unsupported type() candidate
is skipped and the supported image/svg+xml candidate is loaded and
painted.
2026-04-25 14:54:10 +02:00
Andreas Kling
61d79a1e47 LibWeb: Parse CSS image-set()
Add an abstract image style value for image-set() and parse both the
standard and -webkit-prefixed spellings through the existing <image>
value path. The parser accepts URL and string image candidates,
optional resolution descriptors, and type() filters.

Track attr-taint through substituted component values so image-set()
candidates using attr()-derived URL-producing tokens are rejected when
resolved for URL-using properties.

Update the relevant WPT baselines now that image-set() parsing is
supported in additional value contexts.
2026-04-25 14:54:10 +02:00
Andreas Kling
9a275155c9 Tests: Import CSS image-set parsing WPT
Import the CSS Images image-set parsing WPT with the current
expectations so parser support can be landed against a focused baseline.
2026-04-25 14:54:10 +02:00
Tim Ledbetter
30f8e7f80c LibWeb: Include non-auto cross margins in auto-margin resolution
When resolving cross-axis auto margins on a flex item, the outer cross
size calculation omitted all cross-axis margins. We now include
non-auto margins as part of the outer cross size treating auto margins
as zero.
2026-04-25 14:46:12 +02:00
Andreas Kling
b629914428 LibWeb: Make TransferArrayBuffer zero-copy
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. :^)
2026-04-25 10:53:51 +02:00
Tim Ledbetter
59bf30f17f LibWeb: Add AVIF and WebP to the Accept header for images
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.
2026-04-25 08:49:04 +02:00
Tim Ledbetter
49834ef782 Tests: Add test server endpoint that records the last request's headers 2026-04-25 08:49:04 +02:00
Andreas Kling
c66cab7e6b AK: Hide tentative HashTable bucket from iterators across ensure()
HashMap<_, GC::Ref<_>>::ensure() crashed under UBSan whenever the
initialization callback triggered a GC: lookup_for_writing() stamped
the target bucket as used and added it to the ordered list before the
callback ran, so the marking visitor walked the map, read the
uninitialized slot, and failed the returns_nonnull check in GC::Ref.

Split bucket reservation into two phases. lookup_for_writing() now
hands back the target in the Free state (not in the ordered list,
m_size unchanged); callers placement-new the value and then commit via
commit_inserted_bucket(). The Robin Hood displacement loop still
stamps the slot internally and un-stamps before returning, so probing
is unchanged and the whole operation remains a single hash and a
single probe.
2026-04-25 06:21:36 +02:00
Yayoi-cs
0b9636fadf LibJS: Only cache TypedArray data pointers for owned buffers
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.
2026-04-25 06:11:18 +02:00
Tim Ledbetter
5d69c6d2b7 LibWeb: Filter by font width before weight in font matching
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.
2026-04-24 20:19:38 +02:00
Aliaksandr Kalenik
1193409f64 LibWeb: Wait for CompletelyAvailable state before resolving img.decode()
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.
2026-04-24 19:27:26 +02:00
Aliaksandr Kalenik
cd6672eee0 LibWeb: Fix min-content collapsing to 0 with min-width: 0 descendant
During intrinsic sizing, compute_width() ran on block descendants with
an intrinsic-sizing available space. For a non-FC-establishing block
with auto width, used_width stayed auto, and the min-width clamp then
compared AvailableSize::min-content against min-width via operator<,
which always returns true when the left side is min-content. The clamp
fired with min-width: 0 and set content_width to 0 permanently.

Skip the min-width clamp when used_width is still auto, mirroring the
max-width clamp a few lines above which already no-ops via
to_px_or_zero. The real width is then set by the IntrinsicSizing branch
in layout_block_level_children.
2026-04-24 19:08:28 +02:00
Timothy Flynn
12d9aaebb3 LibJS: Remove gc from the global object
No other engine defines this function, so it is an observable difference
of our engine. This traces back to the earliest days of LibJS.

We now define `gc` in just the test-js and test262 runners.
2026-04-24 18:36:23 +02:00
Andreas Kling
5da72570b8 LibWeb: Harden UA event handlers on range and number inputs
These handlers crashed on several kinds of JS-dispatched input:
zero-width range (divide by zero in the slider mouse handler),
step="any" (MUST(step_up) throws InvalidStateError), plain Event
without clientX/deltaY/key (unchecked as_foo() asserts on
undefined), min > max (trips clamp()'s VERIFY), and input.type
changes leaving the range listeners attached to dereference empty
Optionals from the range-only min()/max() accessors.

Gate each handler on its expected type_state() and on
allowed_value_step() having a value, validate event property types
before converting, and bail out on zero-width rects or min > max.
Six crash tests cover the new paths.

Hit on a Cloudflare challenge page.
2026-04-24 07:58:34 +02:00
Andreas Kling
3cf24872c4 LibWeb: Fix crash removing link stylesheet nested in a shadow tree
HTMLLinkElement::removed_from() used `old_root` to find the
StyleSheetList to remove the link's stylesheet from. That's wrong
when the link element lives inside a shadow tree that is itself
nested within a larger removed subtree: Node::remove() hands every
shadow-including descendant the outer subtree's root as `old_root`,
not the descendant's own containing root. So we'd look in the
document's list while the sheet was actually in the shadow root's
list, failing the did_remove VERIFY in StyleSheetList::remove_sheet.

Fix by using the sheet's own owning-root tracking. A link-owned sheet
always has exactly one owning document or shadow root (only constructed
stylesheets can be adopted, and link sheets are never constructed), so
we can just read that entry.

Also make owning_documents_or_shadow_roots() return by const reference
instead of copying the HashTable on every call, which benefits existing
iterating callers too.

Fixes a crash on https://nytimes.com/.
2026-04-23 22:37:06 +02:00
Aliaksandr Kalenik
bfbc3352b5 LibJS: Extend Array.prototype.shift() fast path to holey arrays
indexed_take_first() already memmoves elements down for both Packed and
Holey storage, but the caller at ArrayPrototype::shift() only entered
the fast path for Packed arrays. Holey arrays fell through to the
spec-literal per-element loop (has_property / get / set /
delete_property_or_throw), which is substantially slower.

Add a separate Holey predicate with the additional safety checks the
spec semantics require: default_prototype_chain_intact() (so
HasProperty on a hole doesn't escape to a poisoned prototype) and
extensible() (so set() on a hole slot doesn't create a new own
property on a non-extensible object). The existing Packed predicate
is left unchanged -- packed arrays don't need these checks because
every index in [0, size) is already an own data property.

Allows us to fail at Cloudflare Turnstile way much faster!
2026-04-23 21:47:21 +02:00
Luke Wilde
3d3b02b9c0 LibJS: Use the real globalThis value
Previously it used `realm.[[GlobalObject]]` instead of
`realm.[[GlobalEnv]].[[GlobalThisValue]]`.

In LibWeb, that corresponds to Window and WindowProxy respectively.
2026-04-23 20:43:01 +01:00
Ali Mohammad Pur
909013b972 LibWeb+LibTest: Move the progress bar/status printing stuff to LibTest 2026-04-23 18:48:56 +02:00
Andreas Kling
928a5247ff LibWeb: Narrow stylesheet add/remove invalidation
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.
2026-04-23 16:45:22 +02:00
Andreas Kling
cfa75e6eb4 LibWeb: Invalidate stylesheet owners when disabled state changes
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.
2026-04-23 16:45:22 +02:00
Andreas Kling
a0dc0c61f4 LibWeb: Scope broad shadow-root stylesheet invalidation
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.
2026-04-23 16:45:22 +02:00
Andreas Kling
4e92765211 LibWeb: Narrow @keyframes insertRule() invalidation
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.
2026-04-23 16:45:22 +02:00
Andreas Kling
b6559d3846 LibWeb: Narrow inline stylesheet insertRule() invalidation
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.
2026-04-23 16:45:22 +02:00
Andreas Kling
e29281893a LibWeb: Preserve pending style-update flags across document adoption
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.
2026-04-23 16:45:22 +02:00
Shannon Booth
b0acc18f9b LibWeb: Clamp limited grid item contributions by fixed max tracks
When resolving grid track sizes, limited min/max-content contributions
should be capped by fixed max track sizing functions, including the
argument to fit-content(). We were instead falling back to the grid
container maximum size, which allowed a grid item with overflowing
contents in a fit-content(0) row to inflate the intrinsic block size of
a nested grid.

That bogus intrinsic height could then be used for the grid's second row
sizing pass, causing unrelated flexible rows to absorb the extra space.
2026-04-23 09:23:02 +01:00
Andreas Kling
eb9432fcb8 LibJS: Preserve source positions in bytecode source maps
Carry full source positions through the Rust bytecode source map so
stack traces and other bytecode-backed source lookups can use them
directly.

This keeps exception-heavy paths from reconstructing line and column
information through SourceCode::range_from_offsets(), which can spend a
lot of time building SourceCode's position cache on first use.

We're trading some space for time here, but I believe it's worth it at
this tag, as this saves ~250ms of main thread time while loading
https://x.com/ on my Linux machine. :^)

Reading the stored Position out of the source map directly also exposed
two things masked by the old range_from_offsets() path: a latent
off-by-one in Lexer::new_at_offset() (its consume() bumped line_column
past the character at offset; only synthesize_binding_pattern() hit it),
and a (1,1) fallback in range_from_offsets() that fired whenever the
queried range reached EOF. Fix the lexer, then rebaseline both the
bytecode dump tests (no more spurious "1:1") and the destructuring AST
tests (binding-pattern identifiers now report their real columns).
2026-04-22 22:34:54 +02:00
Andreas Kling
a94f9aa4c7 LibWeb: Filter non-inheriting registered custom properties on inherit
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.
2026-04-22 20:59:00 +02:00
Andreas Kling
11c75a2ffb LibWeb: Fix @keyframes resolution for slotted elements
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.
2026-04-22 20:59:00 +02:00
Andreas Kling
e4800b2498 LibWeb: Parse @keyframes name as logical string, not token text
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.
2026-04-22 20:59:00 +02:00
Aliaksandr Kalenik
0bfe4677ae LibWeb: Verify whitespace font has the glyph in font_for_space()
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.
2026-04-22 20:27:41 +02:00
Aliaksandr Kalenik
eb4038fa83 AK: Fix Utf16View::operator<=> code-unit ordering on little-endian
The !has_ascii_storage() && !other.has_ascii_storage() branch did a
byte-wise __builtin_memcmp over a char16_t array, which on little-endian
does not give code-unit order: the low byte is compared first, so
0xD83D (bytes [0x3D, 0xD8]) spuriously compared less than 0x2764
(bytes [0x64, 0x27]) even though the code unit 0xD83D is greater.

No in-tree caller currently uses operator<=> for Utf16View ordering,
so this bug is dormant; the follow-up LibJS change exposes it.

Replace the memcmp branch with a per-code-unit loop, which the compiler
can auto-vectorize and which mirrors what is_code_unit_less_than already
does.
2026-04-22 19:12:54 +02:00
Tim Ledbetter
d8825c89af LibWeb: Use caption content width for inline layout in table captions
Previously, `run_caption_layout()` passed the table's border-box width
as the available space to the caption's formatting context. The BFC then
used this width directly for inline line breaking, causing text to
overflow the caption's content box by the size of the caption's own
border and padding.
2026-04-22 15:35:07 +01:00
Tim Ledbetter
6f3833e857 test-web: Inject JS wait script into layout tests
This script waits until fonts have loaded and waits for 2 animation
frames before signalling test completion. This is the same mechanism
already used for ref and crash tests.
2026-04-22 15:35:07 +01:00
Callum Law
eab7935686 LibWeb: Explicitly pass calculation context when parsing
Previously we would generate the calculation context based on the
current value parsing context. The main problem with this was that
contexts were defined per property by default and had to be overriden
per component value using "special" contexts, which was easy to forget.

We now generate the calculation context per component value in the
relevant `parse_foo_value` methods.

The new failures in `typed_arithmetic.html` are because we no longer
treat percentages as resolving to their property-level type when
computing what the resolved type of a calculation is i.e. when we are
parsing the `<number>` portion of `line-height` we treat percentages as
raw percentages, not lengths. This brings us in line with WebKit but no
longer with Chrome and WPT, I am not sure what the correct behavior is.
2026-04-22 14:24:12 +01:00
Callum Law
76250ba142 LibWeb: Validate literal numeric values at parse time
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
2026-04-22 14:24:12 +01:00
Callum Law
d6dbabdf0e LibWeb: Clamp CSS numeric token values to float range
This matches the behavior of other browsers. We did the equivalent
change for <integer> in b86377b

We continue to store these as doubles for the extra precision.
2026-04-22 14:24:12 +01:00
Tim Ledbetter
da5e002db1 LibWeb: Keep select button text in sync with the selected option
Previously, the select button's text was only refreshed inside the
two non-trivial branches of the selectedness setting algorithm.
Paths that left the select with exactly one selected option hit a
no-op branch and skipped the refresh.

Fix this by implementing the "clone selected option into select
button" algorithm and invoking it whenever the set of selected options
may have changed.
2026-04-22 09:15:29 -04:00
Tim Ledbetter
884a0140aa LibCore: Allow zero-size AnonymousBuffer creation
Previously, `AnonymousBuffer::create_with_size(0)` returned an error
because POSIX `mmap` rejects a zero length with `EINVAL`, and Windows
`CreateFileMapping` rejects a zero maximum size for an anonymous
mapping. This caused a crash when using `--headless=text` with zero
size pages like `about:blank`.
2026-04-22 09:08:39 -04:00
Tim Ledbetter
1b5f85da1e Tests/LibCore: Add some basic AnonymousBuffer tests 2026-04-22 09:08:39 -04:00
Zaggy1024
22c1b72588 LibWeb: Prevent dragstart after a prevented mousedown or dragstart
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
2026-04-22 07:34:18 -04:00
Zaggy1024
ccd9bdac56 Tests: Make each drag-and-drop-element.html subtest stateless
The event handler captures shouldn't be necessary here, we can just
print the events that are relevant to each testcase element.
2026-04-22 07:34:18 -04:00
Tim Ledbetter
06ee7bc532 LibWeb: Skip unstyled descendants in animation updates
Animation updates propagate inherited animated values by walking the
animated target's subtree and calling `recompute_inherited_style()` on
each element. Elements inserted during the same rendering update may
not have computed properties yet, which violates an existing assertion,
causing a crash.

Fix this by skipping unstyled descendants during this walk. The
subsequent recursive style update computes their style from scratch.
2026-04-22 10:14:47 +02:00
Zaggy1024
296216714a LibWeb: Handle object-fit in VideoPaintable 2026-04-21 19:11:24 -05:00