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.
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
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
Instead of comparing the current time to the duration, the playback
manager now has an explicit Ended state that jumps to the duration. The
element simply reacts to that to trigger the ended event and attribute,
along with all the other steps involved.
This moves the ended event to fire after the seeked event, which
matches other browsers' behavior. The spec doesn't explicitly say which
order they should fire in.
Previously, clearing a DataTransfer's data removed entries from the
drag data store without updating the associated `DataTransferItem`
objects. An item obtained beforehand kept an index that no longer
referenced a valid entry, so reading its kind or type accessed an
out of bounds element of the now-empty list and crashed. We now keep
the item objects in sync when clearing data, placing any stale ones
into the disabled mode.
Let SVGStyleElement delegate style updates through StyleElementBase so
parser-created inline SVG `<style>` elements update when they are popped
from the parser stack, just like HTML style elements.
This lets SVG style imports participate in script-blocking stylesheet
checks while keeping dynamic text, type, and media changes from
re-blocking parser scripts.
Evaluate the associated stylesheet's media queries when a parser-created
`<style>` sheet is created, and re-evaluate them when the media
attribute changes.
This lets HTMLStyleElement::contributes_a_script_blocking_style_sheet()
honor the media clause from the HTML script-blocking rules. A
still-loading parser-created style is removed from the script-blocking
set as soon as its media no longer matches.
Create parser-blocking style sheets when parser-created `<style>`
elements are popped from the stack of open elements, and ignore dynamic
style updates while those elements are still open in the parser.
Make the shared style-element script-blocking predicate describe the
active style sheet instance. Stale script-blocking entries are removed
when that style sheet is replaced or removed.
Deeply nested structures passed to JSON.stringify or structuredClone
would cause a lot of recursion, eventually causing a crash.
We now throw an InternalError instead, same as other browser engines.
Previously, the text searched by find-in-page was gathered from each
text node's rendered string, which still contains uncollapsed
whitespace. We now collapse runs of whitespace to a single space and
trim leading and trailing whitespace per block when building the
searchable text, while keeping each character mapped back to its DOM
position so matches still resolve to correct ranges.
...and avoid telling the media element about it unless the track is
actually associated. The media element should set its state later when
it queries the status upon metadata parsing.
An HTMLImageElement can be adopted across documents while an image
update is still pending. In that case the element's wrapper realm can
still be the old iframe realm, but the image request must be created and
fetched using the element's current node document.
Using the old iframe realm associates the request with the removed
iframe's document-owned image request cache, so the adopted image can
fail to complete in the parent document. Use document().realm() when
creating/updating/fetching image requests so adopted images continue
loading in their new document.
Add a regression for an image created in an iframe, then adopted into
the parent after the iframe is removed.
Chromium and Webkit do not fire a load event for the test, but Firefox
does. All load the image, where we previously did not.
See: https://github.com/whatwg/html/issues/8492
Treat a missing animation frame callback driver as an empty map when
canceling a frame. This keeps media controls finalization from
allocating while GC is collecting unreachable media elements.
Cover this with a media controls GC text test.
Preserve leading BOMs when parsing already-decoded HTML strings, since
those strings do not go through the encoded byte decoder path.
Decoded markup from JS strings can also contain WTF-8 for lone surrogate
code units. Keep the common scalar UTF-8 path to a single validation and
copy, but replace surrogates before handing bytes to the Rust tokenizer.
Add text coverage for DOMParser and innerHTML string parsing, including
leading BOMs, text and attributes, lone high and low surrogates, and a
valid surrogate pair.
Do not skip fully transparent canvas fill and stroke styles when the
current compositing operator can still affect the destination bitmap.
TradingView uses transparent copy fills while repainting its chart
canvases, and skipping those draws left old translucent chart pixels in
place so later source-over draws accumulated on top.
Add text coverage for transparent copy replacing an existing translucent
pixel before another translucent draw.
A second root module in a failed cycle can call Evaluate after the
cycle root has already cached an evaluation error. In that case
InnerModuleEvaluation returns the cached error immediately and Evaluate
must reject the new top-level capability with that same error.
Remove the extra assertion that the module evaluation error is empty in
the abrupt completion path. Add text coverage for evaluating both roots
of a module cycle whose dependency throws during evaluation.
When an image source changes before a queued decode() job can attach
fetch callbacks, the current request can still be unavailable and have
no shared resource request. Treat that as the spec's current request
changed case instead of asserting while adding callbacks.
Add text coverage for changing an image source immediately after
calling decode(), matching the WPT image-decode-path-changes crash.
Add a targeted update_style_for_element() mode that resolves one
flat-tree inheritance chain. Normal callers still get computed style in
display:none subtrees. Focusability stops when that chain resolves to
display:none.
The targeted path falls back to normal document style traversal for
document invalidation, full rebuilds, and selector work. That keeps
global dirty state consumed atomically. Local recomputation preserves
layout, display-list, slot, visual-context, and stacking-context
invalidation. It marks children dirty when a recomputed element can
affect descendant style.
Use StopAtDisplayNone for focusable-area rendering checks so incidental
is_focusable() queries do not repeatedly force unrelated document style
work. Rebaseline style invalidation counters for the reduced
getComputedStyle() recomputation scope.
Add coverage for focus and getComputedStyle() queries that run while
style work is pending for the target element, its ancestors, slotted
nodes, media queries, animations, and :has() invalidation.
The expected output records the current stale results so the targeted
style update change can show the behavior fix directly. Also make the
style invalidation counter tests avoid recomputation counters in cases
where the exact recomputation count is incidental to the invalidation
behavior under test.
Button layout wraps non-flex/grid button contents in an anonymous flex
item. That flex item kept the default automatic minimum height, so
a descendant with height: 100% and width: auto could make a fixed-height
button grow to the descendant's intrinsic height. This made image-only
controls, such as site header logos, render much taller than the height
specified by author styles.
Set min-height: 0 on the anonymous button content box so
percentage-height descendants can resolve against and shrink to the
button's used height.
Handle the delegatesFocus branch of get-the-focusable-area before
rejecting non-focusable focus targets. This lets host.focus() move focus
to the first focusable delegate in the shadow tree, or preserve an
already-focused descendant.
Treat delegatesFocus shadow hosts as focus delegates even when tabindex
would otherwise make the host focusable. Skip inert delegate candidates,
and reject inert shadow hosts before looking for a delegate. Check focus
inertness through shadow-host ancestry so direct focus cannot enter
inert shadow subtrees.
Rebaseline the disabled delegatesFocus WPT now that this behavior
passes. Cover delegated focus, hidden and inert delegate candidates,
tabindex hosts, inert hosts and direct delegates inside inert hosts, and
hosts without any focusable delegate.
Apply the focusing steps' get-the-focusable-area mapping before
rejecting a non-focusable target. This preserves documentElement.focus()
by mapping the non-focusable document element to the Document viewport.
Also map rendered navigable containers with content navigables to their
active document, while leaving hidden containers unfocused. Preserve
Window focus events for child document viewports reached through iframe
focus, while still suppressing the top-level viewport surrogate events.
Treat rendered object elements as focusable through their default
non-null tabindex, even when they show fallback or image content instead
of a child navigable.
Keep the spec focus-chain common-tail handling intact for viewport
focus. The Document object is only our surrogate for the viewport, so
designate viewport focus from the new focus target without dispatching
Window focus/focusin events for that top-level surrogate.
Pass that viewport surrogate as the fallback target for fragment
scrolling and NavigateEvent focus reset, so unfocusable body or fragment
targets still clear stale element focus.
Cover documentElement.focus() in both the activeElement and focus-chain
tests, including a tabindex document element that remains focused as an
element. Cover object focus with and without a child navigable, hidden
object focus attempts, iframe focus events, hidden iframe focus, and
blurring a focused iframe after it becomes hidden. Also cover viewport
fallback for intercepted navigation focus reset and fragment scrolling
to an unfocusable target.
Do not let elements inside display:none subtrees become focus targets.
The HTML focusable-area model only allows elements to be focusable when
they are rendered, delegate rendering to their children, or are relevant
canvas fallback content.
Preserve blur for the current focused area after script hides it, since
the unfocusing steps operate on the old focus target. Check display:none
through the flat-tree style parent chain, so slotted controls inside
hidden slot subtrees cannot become focused.
Cover hidden ancestors with materialized computed style, display:none
controls, display:contents, hidden focused controls, and slotted cases
inside hidden and visible shadow-tree subtrees.
Previously, if the find in page query was updated and no matches were
found we would keep the old selection instead of clearing it.
`window.find` behaves differently from the find in page UI and
preserves the old behavior.
Keep the temporary document used by HTML fragment parsing from
running post-connection work while the parser is staging nodes there.
This lets scripts from Range.createContextualFragment() remain
unstarted until the returned fragment is inserted into the real
document, and removes the script-specific preparation guard.
Strengthen parser coverage so contextual fragment scripts must wait
until the fragment is applied before running.
Finish the Rust implementation of the spec tree-construction algorithms
needed by the LibWeb test suite. Add the remaining table modes, foster
parenting, scope helpers, adoption agency handling, ruby/list/form and
select cases, frameset state, foreign-content edge cases, and parser
host callbacks.
Preserve behavior that depends on the C++ DOM integration, including
parser-created custom element reactions, fragment quirks mode, arbitrary
fragment namespaces, template fragment mode, fragment form ownership,
MathML annotation-xml boundaries, contextual fragment scripts, parser
script source positions, document.close() parser state, void-element
insertion, and duplicate attribute tracking.
Add focused tests for the parser edge cases that are easy to regress at
the boundary between the Rust tree builder and the C++ DOM host.
When a link element's href changes, the spec expects the callback
to check if "el contributes a script-blocking style sheet"
(step 6). This check examines current fetch state, which fails
when old fetch callbacks run after a new fetch has started.
Use FetchController::stop_fetch() instead of abort() to prevent
old fetch callbacks from executing entirely. This ensures only the
current fetch's callback runs, allowing it to correctly check
current state per spec without race conditions.
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.
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 a ScriptCreatedParser flag plumbed through HTMLParser's constructor
and create_for_scripting(). Only document.open()'s parser sets it to
Yes. Document::close() step 3 now checks is_script_created() so it
correctly skips parsers that weren't created via document.open(),
matching the spec.
Previously the check was just `if (!m_parser)`, which incorrectly let
document.close() insert an EOF into a network-driven parser. The bug
was mostly latent because the network parser used to finish quickly,
but it matters once the network parser stays alive for the duration of
a streamed parse.
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/
Previously it used `realm.[[GlobalObject]]` instead of
`realm.[[GlobalEnv]].[[GlobalThisValue]]`.
In LibWeb, that corresponds to Window and WindowProxy respectively.
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).
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
This tightens the implementation of video element sizing to the spec by
implementing two spec concepts:
- The media resource's natural width and height, and
- The video element's natural width and height.
The element's natural dimensions change based on the representation,
which has many inputs, so update checks are triggered from many
locations.
The resize event is fired when the media resource's natural dimensions
change, and the layout is invalidated if the element's natural
dimensions change.
Tests for a few important resize triggers have been added.
This state will indicate to the media element that it's not guaranteed
to have a frame yet, for the purposes of determining the ready state.
JavaScript should be sure that video elements with a ready state of
HAVE_CURRENT_DATA or greater represent the current video frame already.
To allow the state to be exited if audio is disabled, audio tracks are
now only added to the buffering set on enable if the audio sink exists,
since without the sink starting the data provider, it will never be
removed.
This is a step towards making video ref tests.
SharedResourceRequest was treating any URL ending in .svg as SVG, even
when the response Content-Type was some other format (like image/webp).
This could result in transformed CDN image URLs to fail decoding.
Only use the .svg URL suffix fallback when no MIME type was provided.
Having PlaybackManager start in Buffering was causing us to report
a media element readyState of HAVE_CURRENT_DATA. HAVE_CURRENT_DATA
doesn't make a whole lot of sense for local files, since we should have
all the data immediately when we process the metadata. This is
reflected in the buffered attribute, so let's not limit the ready state
unecessarily.
Use promises to await the expected sequence of events. Also, don't
assume that canplaythrough will fire after error. That depends on the
implementation.
When setting to a non-string value (i.e. a `CanvasGradient` or
`CanvasPattern`) we would accidentally update the fill style instead of
the stroke style.
This allows dragging elements on the page and dropping them onto other
elements. This does not yet support dragging text.
The test added here is manual; the WPT tests rely heavily on WebDriver
actions.