The old parameter-default lexer stopped at any whitespace, making it
impossible to write defaults like `optional CSSOMString text = " "`.
Fall back to `consume_quoted_string()` (capturing quotes) when the
default starts with a quote, so the spec default for
FontFaceSet.load()/.check() can be expressed in IDL.
Only run update_layout() during mousemove handling when the result is
actually consumed, i.e. when the middle-button autoscroll handler or
mouse selection is active. This avoids forcing a synchronous layout
on every mousemove event in the common case.
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.
The list previously omitted AVIF even though we ship a working
AVIFImageDecoderPlugin, which meant <picture><source type="image/avif">
candidates and image-set(... type("image/avif")) candidates were
unconditionally skipped.
List-style images can now be abstract image values such as image-set(),
not just plain URL images. Visit the stored abstract image directly so
GC-backed resources held by those image values stay reachable.
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.
Extract the file-local is_supported_image_type() helper from
HTMLImageElement into a small standalone translation unit so other
parts of the engine can ask the same question. The next commit reuses
it for the image-set() type() filter.
The list is still hard-coded; deriving it from the registered image
decoders remains a FIXME.
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.
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.
FontFace.load() set the face's status to "loading" but never switched
the containing FontFaceSets to "loading" or appended to their
`[[LoadingFonts]]` lists. The load-completion handler then found
`[[LoadingFonts]]` already empty and fired switch-to-loaded after the
first face finished, resolving `document.fonts.ready` while faces in
the same set were potentially still loading.
Previously, if search was disabled, entering non-URL text would just
silently drop the search query (and on Qt, we would reload the current
URL). We now detect that the query did not result in a navigation and
load an error page instead, which directs the user to enable search.
Move partial interface, partial namespace, mixin, typedef, and overload
finalization into a context-wide post-parse resolve step.
This lets BindingsGenerator parse all declared IDL files first and then
finalize the shared IDL context before generating bindings.
Teach the bindings build to pass support IDL files alongside the regular
binding IDLs so BindingsGenerator parses the full declared IDL set into
a shared context.
Keep idl_files.cmake as the source of truth for parsed support IDLs, and
let Interface::will_generate_code() decide which parsed interfaces emit
generated bindings.
Semantic IDL lookups no longer rely on imported modules, so stop storing
that state on Interface and Module.
import directives are still resolved during parsing for now, but the
parsed imported-module lists are no longer needed.
Stop using imported modules to decide which binding headers to include.
Instead, collect dependencies from referenced interfaces, dictionaries,
enumerations, callbacks, and nested IDL types in the parsed context.
Derive C++ namespaces from each IDL module's location and use those
qualified names when generating binding code.
Also Teach dictionaries their owning IDL module path so dictionary C++
types can be qualified the same way as interfaces. This removes the need
for the generated `using namespace Web::*` hack and the hard-coded
namespace list.
Also fix DOMURL.idl to refer to the IDL interface name `URL`, not the
C++ implementation name `DOMURL`.
No other third layer folder in LibWeb has its own namespace which
makes this a special case for the IDLGenerator when determining
namespaces. Instead of adding a special case, simply remove the
namespace.
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.
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.
invalidate_all_prototype_chains_leading_to_this used to scan every
prototype shape in the realm and walk each one's chain looking for
the mutated shape. That was O(N_prototype_shapes x chain_depth) per
mutation and showed up hot in real profiles when a page churned a
lot of prototype state during startup.
Each prototype shape now keeps a weak list of the prototype shapes
whose immediate [[Prototype]] points at the object that owns this
shape. The list is registered on prototype-shape creation
(clone_for_prototype, set_prototype_shape) and migrated to the new
prototype shape when the owning prototype object transitions to a
new shape. Invalidation is then a recursive walk over this direct-
child registry, costing O(transitive descendants).
Saves ~300 ms of main thread time when loading https://youtube.com/
on my Linux machine. :^)
notify_about_rejected_promises() is called for every related environment
settings object at the end of every microtask checkpoint. It was
unconditionally reading the realm up front, which showed up at 3.0% self
time in a YouTube playback profile.
This patch moves the realm lookup into the queued task callback, which
happens way less often.
Document::is_decoded_svg() was reached through two pointer hops and a
virtual call into PageClient on every invocation. It showed up at 1.9%
self time in a YouTube playback profile, and it's also called for every
document in the hot documents_in_this_event_loop_matching() loop that
runs on every rendering update.
The page's client is fixed for the lifetime of a document, so we can
cache the answer at construction time and serve future calls from a
plain member load.
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.
Previously, we were accidentally creating temporary copies of custom
property maps on both sides of a ternary in `compute_style_impl()`. We
now bind to a static empty sentinel instead so the reference binds
directly to `own_values()` without copying.
font_for_code_point() was the heaviest function in layout profiles
of a YouTube page (216ms CPU out of 2900ms total). Every call walked
the full cascade and ran a virtual contains_glyph() against each
entry, even though the result is the same for most ASCII code points
across a document.
Add a 128-entry direct-mapped cache keyed by code point that stores
the resolved Font pointer on first lookup. Subsequent ASCII lookups
become a null check plus a load.
No invalidation is needed: m_fonts is append-only, and the cascade
returns the first matching font, so once an entry claims a code
point, later appends cannot change the answer.
Currently there are multiple style values which are essentially the same
thing, a function holding a value, just with different names. This
commit adds a generic style value to replace them with and the following
commits will do so.
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.
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/.
Use the shared IDL context when checking whether a type is a JSON type
instead of walking imported modules from the current interface. This
removes another dependency on explicit #import directives when resolving
interface inheritance for toJSON.
Define MediaKeySystemConfiguration and MediaKeySystemMediaCapability in
Web::EncryptedMediaExtensions instead of Web::Bindings. This matches
other dictionary definitions in the codebase.
Keep the JsonWebKey dictionary types in line with other dictionary
types in the codebase by putting them in the Crypto namespace
rather than under Web::Bindings.
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!
The element-by-element loop compiled to scalar 8-byte moves that the
compiler could not vectorize: source and destination alias, and strict
aliasing prevented hoisting the m_indexed_elements pointer load out of
the loop body. memmove collapses the shift into a single vectorized
copy.