Add explicit IgnoreBOM and ErrorMode options to LibTextCodec decoders,
and thread them through TextDecoder and TextDecoderStream.
This lets Web-facing decoder APIs preserve BOMs when requested and use
fatal error handling without post-processing decoded output.
NB: RemoveBOM was renamed to IgnoreBOM as "RemoveBOM" is the name
used by encoding_rs and was previously an implementation detail.
The new name matches what is used by the encoding standard as it
is now also used in LibWeb.
Temporarily enter the TextEncoderStream/TextDecoderStream realm while
running their transform and flush algorithms.
This ensures objects and exceptions created through those algorithms are
associated with the constructor realm, matching the encoding streams
realm WPT.
I find this behaviour _somewhat_ strange, and this is only very loosely
specified, but all browsers have aligned on this behviour, so we may
as well match it.
Produce JS-visible string results as UTF-16 at their source, including
numeric formatting, BigInt and BigFraction formatting, URI encoding,
console formatting, parser errors, regular expression errors, Intl and
Temporal records, LibUnicode locale boundaries, and LibWeb bindings.
Handle fractional radix formatting through the UTF-16 builder view.
Port straightforward JS string construction sites to Utf16StringBuilder.
Make ASCII appends explicit with append_ascii(), and leave byte-output,
formatted-output, and printer plumbing on StringBuilder where that API
still matches the caller.
Add a small checked JS string length product helper. Use it for
String.prototype.repeat() before constructing the repeated result. Keep
the maximum string length policy in LibJS while allowing the UTF-16
builder to remain purpose-built and infallible. Cover overflow from the
final repeated code-unit length in the repeat tests.
Problem: UBSan crash when computing layout for an element with a giant
negative inset.
Cause: CSSPixels::operator-() returned from_raw(-raw_value()), and
negating the i32 minimum overflows int.
Fix: Negate with saturating_sub(0, raw_value()) — matching the
saturating arithmetic already used by the other CSSPixels operators.
Fixes https://github.com/LadybirdBrowser/ladybird/issues/9997
Problem: Crash when loading an ordered list whose numbering reaches the
i32 limit; e.g. <ol start="2147483647"> with two or more items.
Cause: Element::ordinal_value() kept its numbering in a Checked<i32>
and stepped it once per list item. When the numbering already sits at
the i32 maximum (or minimum, for a reversed list whose value attribute
pins it there), the increment overflowed the Checked value.
Fix: Keep the numbering in a plain i32 instead, and step it with
AK::saturating_add and AK::saturating_sub — so it clamps at the i32
bounds, rather than overflowing.
Fixes https://github.com/LadybirdBrowser/ladybird/issues/10003
Problem: Crash when generating an RSA key — or serializing one —
whose publicExponent is a typed array whose backing ArrayBuffer has
been detached; for example, by calling transfer() on it.
Cause: Two places with big_integer_from_api_big_integer() reading the
bytes of the backing ArrayBuffer directly. But reading the bytes of a
detached buffer aborts.
Fix: Read the bytes with WebIDL get_buffer_source_copy() — which yields
an empty copy for a detached, or OOB resizable, buffer. The empty array
is already mapped to zero — so generation rejects the zero exponent with
an error, rather than crashing.
Fixes https://github.com/LadybirdBrowser/ladybird/issues/9991
Move CSS animation values into a mutable overlay on computed properties
and make base computed style data immutable after construction. Base
style mutation now goes through a builder that is consumed on publish,
so installed styles no longer expose mutation APIs.
Build new base style data for inherited style updates instead of cloning
and mutating installed computed properties. Element-specific computed
style adjustments now run before publication, while animation and
transition updates continue to mutate only the animated overlay.
Replace the generated C++ legacy codec implementations with a
small Rust wrapper around encoding_rs.
This keeps the existing LibTextCodec API while moving label lookup,
legacy decode/encode, validation, and streaming decoder state to Rust.
The generated index data and generator are no longer needed.
It also fixes several TextDecoder EOF cases due to a more correct
implementation. encoding_rs finalizes decoders according to the
Encoding Standard, so incomplete UTF-8/Big5 tails and malformed
UTF-16 surrogate tails produce the required single replacement at
end-of-queue instead of being dropped, buffered, or double-counted
by our old hand-written decoders.
Replace the Rust URL form-encoding callback bridge into C++ TextCodec
with a direct encoding_rs encoder.
This keeps percent-encode-after-encoding entirely in Rust and removes
liburl_rust's dependency on LibTextCodec.
It also happens to fix ISO-2022-JP URL encoding of literal U+FFFD.
The LibTextCodec reverse lookup treats generated 0xFFFD table holes as
real JIS0208 mappings, so literal U+FFFD skipped the encoder-error
path. encoding_rs treats U+FFFD as unmappable, so URL encoding emits
the required numeric character reference.
Build a per-anchor Bloom-style filter for :has() argument matching
after an anchor sees a second check for the same traversal scope. The
filter stores salted tag, id, class, and attribute-name hashes from the
child or descendant scope and rejects arguments whose required
identifiers are absent.
This avoids repeatedly walking the same subtree for unrelated :has()
arguments while preserving the single-check case. More complex
direct-child arguments use the descendant scope so hashes from later
descendant compounds cannot cause false rejections.
Keep the filter conservative for quirks-mode class selectors and for
sibling-combinator relative selectors during invalidation metadata
collection. Text tests cover cache-primed misses for both cases.
The autoplay setting was binary and its default blocked all media,
including muted video, leaving sites that rely on muted autoplay
visibly broken. Replace it with a tri-state user-agent autoplay
policy (allow audio and video, block audio, or block audio and video)
defaulting to allowing only inaudible media to autoplay.
This is enforced through the media element's "allowed to play" check,
so unmuting a muted autoplay or calling `play()` cannot slip audio
past the policy; audible playback is permitted once the document has
been activated by the user. The policy lives in a dedicated
AutoplaySettings consulted from HTMLMediaElement instead of the
Permissions Policy "allowed to use feature" check it was previously
conflated with.
Split pseudo-element rules whose originating compound contains :is() or
:where() across the cheap buckets from their selector-list alternatives
when the rule would otherwise land in the pseudo-element other bucket.
Use per-collection generation stamps to suppress duplicate candidates
when more than one alternative bucket applies to the same originating
element. Add text coverage for class, attribute, and complex combinator
arguments reaching generated pseudo-element style.
Selecting text without custom ::selection styling changed the
foreground color of the selected content. This was especially visible
for links, where the text changed color but the underline did not.
The default selection style supplied both a selection background and a
foreground color from the palette or HighlightText system color. That
made ordinary selections behave as if the page had explicitly styled
::selection color.
Only provide a default selection background, so selected content keeps
its own foreground color unless CSS overrides it. Remove the now-unused
SelectionText palette role.
Track the synthetic pseudo-elements that matched while computing an
originating element's normal style. Store the transient match set as a
bitfield, then copy those bits into ComputedProperties. Use them during
style invalidation to skip pseudo style recomputation when neither the
old nor new originating style matched pseudo rules and no pseudo style
already exists.
This shaves roughly 500 ms off loading the Ladybird GitHub repository.
Materialize synthetic pseudo styles on demand for CSSOM reads so
getComputedStyle(element, "::before") still computes skipped styles when
script asks for them. Add coverage for a universal pseudo selector, and
update style invalidation counter expectations for the reduced work.
Previously, math function serialization assumed the calculation tree
root was a numeric value or a calc-operator node, and otherwise emitted
the root's name followed by its comma-separated children.
A non-math function node such as `sibling-index()` or `anchor()` has no
children, so a `calc()` whose entire contents was such a function
serialized to an empty "calc()". We now serialize the function directly
instead.
Split StyleCache's rule matching data from its invalidation metadata.
Allow callers to build either payload independently. Style invalidation
queries no longer force a full rule cache rebuild, and rule matching
no longer builds invalidation metadata as a side effect.
Previously, callers often treated an absent rule cache as proof that no
style invalidation metadata existed. That made some invalidation paths
do nothing until something else had populated the rule cache first.
Build invalidation data before reading it instead, so these paths use
the metadata whenever stylesheet rules require it.
Rebaseline style invalidation counter expectations for the new lazy
build points. Flush setup style work in the structural :has() feature
filter test before measuring each mutation, so the recomputation
counters describe the mutation itself instead of leftover setup work.
Changing :active or :open used the broad style invalidation path, so
large subtrees were recomputed even when only the target element and
selector-matched relatives could be affected.
Reuse the :has() feature collector to keep conservative fallback for
observable :has() cases, then use pseudo-class property invalidation
for the common targeted path.
Store MouseEvent's relatedTarget in the inherited Event field instead of
keeping a second slot on MouseEvent.
Event dispatch retargets and updates the inherited field while building
the event path. The second slot left JS listeners observing stale or
null relatedTarget values during mouse and pointer boundary events.
Add coverage for boundary events between sibling elements.
Previously we didn't support interpolating component values of `scale`
from a number to a percentage (or vice versa). This also caused
interpolation from `none` to a percentage value to fail since the
fallback value is number based.
This makes the transition run when hovering buttons on
https://chrede88.github.io/L1nkr/ rather than being discrete.
In 11b053b154 I accidentally changed the
behaviour of CookieJar::set_cookie() to not match what the RFC
requires, particularly when dealing with too-long paths. This commit
restores the original behaviour, now that the validation required by
DevTools happens elsewhere, before set_cookie() is called.
Problem: Drawing a cross-origin image onto a 2D canvas clears its
origin-clean flag, but toDataURL() and toBlob() ignored that flag and
serialized the bitmap regardless. So, a page could read back the
cross-origin pixels it shouldn't (per spec) be allowed to access.
Cause: The origin-clean checks in to_data_url() and to_blob() were left
as FIXMEs. Only getImageData() enforced the flag.
Fix: Throw a SecurityError exception from both serialization entry
points when the canvas isn't origin-clean — matching getImageData() and
the spec. The same check also implements the previously-stubbed
origin-clean step in the WebDriver canvas-encoding algorithm.
Fixes: https://github.com/LadybirdBrowser/ladybird/issues/10009
Previously animation ownership was a messy split between
`AnimatedBitmapDecodedImageData` and the consumers (i.e.
`ImageStyleValueResource`, `HTMLImageElement`, and `SVGImageElement`)
with `AnimatedBitmapDecodedImageData` owning the frames and a current
frame index, and the consumers owning the rest of the state (e.g. loop
count, timers to drive the animation forward, their own current index).
This had a couple of main issues:
- While `AnimatedDecodedImageData` partially synchronized animations by
dropping unexpected advancement notifications, this didn't apply to
other animation state which meant, for instance, that a later started
consumer could drive the animation of an earlier one past the max
loop count (albeit without invalidating the earlier consumer).
- Multiple consumers didn't share frame timings, meaning animations
could be up to a full frame out of sync visually.
- Animations were paused depending on whether there were any consumers,
this is different to the behavior in other browsers (where they
continue regardless of whether there are any consumers).
- It was an overgeneralization of how animations need to work - only
`AnimatedBitmapDecodedImageData` works with an indexed frame model,
with animated SVGs (although not yet implemented) relying on their
internal event loop to be driven forward.
Given the above the new approach implemented in this commit is:
- The API for `DecodedImageData` is animation system agnostic, only
exposing `default_frame`, `current_frame`, and `restart_animation`
methods not reliant on providing a specific frame index.
- `AnimatedBitmapDecodedImageData` owns its own timer, loop count,
etc. The animation starts when the first consumer registers and ends
when the document is hidden or becomes inactive (or completes in the
case of finite animations).
- Consumers are invalidated by `AnimatedBitmapDecodedImageData` when
required.
Tests have been added for:
- Animations being paused when the document becomes inactive and
restarted when it becomes active again.
- Frame timings being synchronized across consumers.
- Restarts triggered by `HTMLImageElement` applying to all consumers.
- Processing ending once a non-infinite animation plays to completion.
The tests to ensure animations are cancelled when consumers are removed
(e.g. `animated-background-image-timer-stops-when-hidden.html`) have
been updated to assert the inverse since animation state is now per
resource not per consumer.
Problem: Discarding a document that contains an SVG “use” element could
abort the process with a !is_in_list() verification failure in the
IntrusiveListNode destructor. That surfaced intermittently in our style-
invalidation stress tests, depending on GC sweep order.
Cause: A “use” element connected to a document registers itself in the
document’s list of “use” elements and unregisters during its removal
steps. A GC’ed “use” element is swept without running those removal
steps — so it stays linked. When it’s destroyed before its document,
its list node is still linked — and the destructor aborts.
Fix: Override finalize() to unregister the “use” element before
destruction. The collector finalizes every dying cell before destroying
any of them. So, the node is always unlinked in time — the same approach
DocumentObserver and NavigationObserver already use.
Nested navigables were represented through compositor surface ids owned
by the parent context. That forced CompositorState and ContextState to
maintain bidirectional attach/detach bookkeeping, publish child
snapshots into a surface map, and keep presentation mode variants just
to distinguish UI presentation from parent composition.
Record the child compositor context id directly in the display list and
let the compositor resolve it against the painting parent at playback
time. Child contexts now keep their parent context id and latest
rendered surface, while parents no longer track child maps or compositor
surface ids. UI presentation is represented separately from parent
composition, so closing a page only stops client presentation and nested
contexts keep using set_parent_context.
Let style changes that only rebuild compatible accumulated visual
contexts avoid marking the display list dirty. This lets transform
and nonzero opacity updates send visual context tree updates without
recording a new display list.
Keep repainting changes that affect display-list contents or can change
visual context tree compatibility, including zero-crossing opacity,
transform invertibility crossings, background-attachment, clipping,
mix-blend-mode, and perspective. Schedule accumulated visual context
updates for animations independently of repaint so animated
transform/effect updates keep reaching the document.
Cover compatible visual context reuse, incompatible tree shapes, and the
display-list invalidation cases with focused LibWeb tests.
Canvas rendering is a major remaining path where WebContent directly
owns GPU-facing drawing state. Back 2D and WebGL canvas contexts with
remote Compositor transports, so WebContent talks to canvas surfaces
through IPC while the Compositor owns the rasterization resources.
This is a large step toward GPU sandboxing because canvas GPU work now
lives behind the Compositor boundary. It also gives OffscreenCanvas the
process-independent canvas plumbing that HTMLCanvasElement now uses,
making worker-owned canvases possible without another WebContent-local
rendering path.
Problem: A document could drop a load if it ran a stream of synchronous
same-document history navigations (say, a pushState flood) while it was
concurrently loaded again. The load never finished — so on sanitizer/
slow builds, this had been intermittently taking down unrelated tests in
CI — since test-web reuses one WebContent process, and the next test’s
load can arrive while the previous document’s history work is still
draining. A synchronous commit also claimed a session history step it
never retired — so claimed steps piled up without bound.
Cause: A sync same-document navigation committed immediately and could
jump the session-history-traversal queue while a queued apply-history-
step — such as a cross-document load — was still waiting behind it. The
queued run read the active session-history entry after the sync
navigation had installed it, but before its step number was assigned —
then judged itself stale against that still-pending step, and was
discarded. The shared step numbering was fragile under the same nesting:
A number computed from the current step alone could collide with an in-
flight one — and a stale run that completed later could write its own
step back over a newer one. 394312ab5a stopped the crash this used to
cause, but the races remained.
Fix: Treat a queued push whose displayed entry’s step is still pending
as live rather than stale — so the concurrent load isn’t dropped. Number
apply-history-step runs, and let a run commit its target step only if no
newer run has committed one — so a stale run can’t move the current step
backwards. Claim each new step past every claimed-but-uncommitted step —
rather than from the current step alone, and keep clearing the forward
session history from removing those entries. And retire the step a sync
commit claims, since it applies in the same task, and nothing else will.
See https://github.com/LadybirdBrowser/ladybird/issues/10028
Speedometer removes and recreates its benchmark iframe while nested
session-history bookkeeping is still queued. A live child-frame commit
could find that its nested history list had been pruned and then behave
like a stale detached frame. That dropped the real src navigation and
left the harness waiting for a load event.
Preserve the newest real child navigation until the initial session
history entry is ready. Tolerate detached child navigables while history
steps scan target entries, and recreate the missing nested history only
when the child is still the container's live content navigable. Share
the nested-history append path with initial child creation so the normal
and recovery paths keep the same step handling.
Add iframe remove/recreate coverage for pending child history, same-src
load, and repeated pushState removal.
One big assumption that our "effectively contained node traversal" made
was that the common ancestor container of the range would be all the way
at the root of the effectively contained nodes, but that's not the case
- e.g. a common text ancestor could reside inside a `<span>` whose
children are all effectively contained, causing that element to be
contained as well.
Walk up from the common ancestor container until we've found the
top-most effectively contained ancestor.
We were always checking whether the `createLink` command had a non-empty
value, which was a misinterpretation of the spec text.
WPT's reference implementation of this algorithm explicitly checks
whether a value definition was set for a command, so we do the same.
These are commands that have specific indeterminate and value behaviors.
The value behavior was implemented as a workaround and is now factored
out into a separate algorithm.
Problem: A navigation could intermittently hang forever with no load
event ever firing. In test-web, that surfaced as a 120-second
“pre-navigation timeout, WebContent process may be unresponsive”: The
about:blank load used for clearing the document between tests would
never complete — leaving WebContent idle while the harness waited.
Cause: begin_navigation claims the navigable’s ongoing navigation id and
then awaits an asynchronous unload check. While it waits, a session-
history traversal can re-stamp the navigable’s ongoing navigation to
“traversal”. When the unload check resumes, the navigation finds that
its ongoing navigation ID no longer matches — and silently aborts. But
nothing ever re-runs it — so the navigation is lost. The deferral guard
at the top of begin_navigation, which defers a navigation while a
traversal is already ongoing, runs before this window — so a traversal
that begins during the unload check slips past it.
Fix: When the post-unload-check guard finds the navigable is now running
a traversal, re-defer the navigation into the pending navigations list
instead of dropping it — mirroring the existing deferral guard. Clearing
the ongoing traversal drains the pending navigations — so the navigation
runs to completion as a fresh attempt once the traversal finishes.
Fixes https://github.com/LadybirdBrowser/ladybird/issues/10122.
box_baseline() applied CSS2's bottom margin edge rule for non-visible
overflow to every caller, so flex items with hidden overflow were
baseline-aligned by their margin edge instead of their text. CSS Align
scopes that rule to a box's last baseline set, while flex baseline
alignment and table cells use the first set, which always derives from
content. Parameterize box_baseline() on the requested baseline set and
propagate it through the recursive child lookup.
Use IncludeStarRule::Yes for cookie public-suffix checks so domains not
explicitly listed in the PSL still get treated as public suffixes via
the implicit * rule. This fixes accepting cookies for bare TLD-like
domains.
I suspect this is not an important case, but since both Firefox and
Chromium implement it, let's match their behaviour. While this does
not matter the exact letter of the spec, the relevant WPT test was
alongside this spec text as part of a spec change trying to align
to align spec behaviour with Chromium and Firefox, so I believe
what is implemented here to be the intention of the specification
authors.
Keep the visual viewport transform out of Element client rects and
IntersectionObserver geometry. Pinch zoom should change the visual
viewport, but not the layout viewport coordinates exposed through DOM
geometry APIs.
Thread an opt-out through rectangle mapping so paint and hit testing
still use the full visual transform while web-observable geometry can
stay in layout viewport coordinates. This matches the Blink and WebKit
page scale model and keeps responsive script from treating pinch zoom
like a relayout.
Add coverage for getBoundingClientRect() under pinch zoom and visual
viewport IntersectionObserver geometry.
Treat pending session history entries as absent from the used step
graph, and share that through a small step_value() helper so
snapshotting, Navigation API entry construction, target-entry lookup,
and forward clearing do not drift apart.
Keep cross-document history application tied to the navigation id that
created it. Queued changing-navigable work now finishes without
applying when a later navigation has already replaced its target, and
any traversal sentinel is cleared through the shared setter so queued
navigations can drain.
When navigation arrives while traversal is still ongoing, keep only the
newest pending navigation. This matches Chromium, WebKit, and Gecko on
sites that click through product or category links while prior loads
settle.
Revalidate queued same-document child continuations before running them
from null-document tasks, so removed frames or frames claimed by newer
navigations do not receive stale history state.
Preserve nested-history descriptors even when all child entries are
pending, keeping live child navigable identity available for later UI
process history seeds.
Add regression coverage for iframe renavigation during history commit,
for pending child history followed by a real navigation, and for removed
iframes with queued history updates.
Finalize fragment navigations and URL/history updates immediately when
no traversal state is active. Keep the queued same-document finalizer as
the fallback for reentrant traversal work and child navigables whose
nested history is not installed yet.
Share the entry-list portion of same-document navigation finalization
between the fast path and queued fallback, so append and replace
bookkeeping cannot drift.
Preserve unrelated ongoing cross-document navigations when a page starts
a load and then performs a same-document history update in the same
task. This matches Chromium, WebKit, and Gecko: the same-document
update must not cancel the pending real navigation.
The session-history mirror tests now observe synchronous UI updates. A
navigation test covers the pending-load plus pushState race.