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.
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
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.
Thread UTF-16 string input through JSON, script parsing, Date parsing,
Intl option parsing, Temporal parsing, and the helper library boundaries
that feed those parsers. Preserve ASCII fast paths where the source data
is known to be ASCII.
Move the remaining LibJS primitive string users to UTF-16 views and
strings. Remove the primitive string UTF-8 accessors and byte-string
coercion paths so new callers cannot rely on the old storage model.
Keep primitive string storage in Utf16String and remove the UTF-8
storage path from PrimitiveString. ASCII strings still use compact
Utf16String ASCII storage, while UTF-16 becomes the only owned
representation.
Replace the remaining UTF-16-mode StringBuilder instances in LibWeb
with Utf16StringBuilder when they assemble Utf16String values or UTF-16
text views. This keeps those paths in UTF-16 throughout and uses
explicit ASCII append helpers for ASCII-only pieces.
Keep StringBuilder in place for JSON, markup, CSS serialization, and
other byte-oriented string construction paths.
Accept Utf16View patterns at the LibRegex compile boundary and pass
UTF-16 or ASCII storage directly into the Rust regex parser. This keeps
JavaScript regular expression construction from converting patterns
through UTF-8 when LibRegex can consume the same UTF-16 representation
used by LibJS.
Update RegExp construction, HTML pattern validation, the regex fuzzer,
and LibRegex tests to use the UTF-16 compile API.
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.
Pass an encoding label to StreamingDecoder instead of requiring
callers to pre-resolve a Decoder reference.
This removes redundant decoder_for() plumbing from callers and lets
StreamingDecoder hide how chunked decoder state is represented.
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.
2D canvas contexts started publishing partial frames after canvas
rasterization moved into the Compositor. WebContent still splits large
recorded command lists after 64 commands, but every split batch was sent
through the same compositor path as the end-of-frame flush. The
compositor replayed each batch into the DrawCanvas source surface,
so a pending present could sample a canvas after clear and before the
rest of the next frame had been drawn. Canvas-heavy pages such as
slither.com then flickered between partial and complete frames.
Carry an explicit commit bit with 2D canvas command updates. Non-commit
batches now update a hidden working canvas in the Compositor, while the
display-list-visible surface keeps the last committed canvas contents.
The end-of-frame canvas preparation sends the commit boundary, including
the empty-commit case needed when the auto-flush consumed all recorded
commands before prepare_for_compositing() runs.
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.
To `AnimatedBitmapDecodedImageData`. This better reflects what it is and
frees up `AnimatedDecodedImageData` to be used as an abstract class in a
later commit.
In a future commit, ownership of animation will be transferred from
these clients to `AnimatedDecodedImageData` and we will need a way to
invalidate them for new frames.
This also revealed some `ImageProvider`s which don't yet support
animated images (e.g. `<input type="file">`, `<object>`, etc) but that
is left as a FIXME for now.
Previously image animations driven by `ImageStyleValueResource`
invalidated clients using `on_animate`, this was only implemented by the
`background` presentational attribute of `HTMLBodyElement`.
It now uses `notify_clients_did_update` which is implemented by all
clients.
All other callers of `SharedResourceRequest::fetch_resource` guard based
on `needs_fetching` so let's do that here as well (and add a `VERIFY` so
that future callers don't make the same mistake).
This allows us to remove the `frame_rect` accessor.
This also fixes a bug where we computed the scaling mode based on the
clipping rect rather than the draw rect for `ImagePaintable`
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.
Compositor surfaces are only used for nested navigables now. The
display list command already carries the CompositorSurfaceId, but
playback still resolved that id through DisplayListResourceStorage and
WebContent exposed IPC for direct surface updates and clears.
Keep published child surfaces as PaintingSurface entries on the
compositor ContextState and pass that map into Skia display list
playback. Publishing and detaching nested contexts now update the parent
cache entirely inside the compositor, so WebContent no longer needs
update_compositor_surface or clear_compositor_surface messages.
2D canvas rendering now lives in the compositor, but drawing one canvas
into another still converted the source HTMLCanvasElement into a
DecodedImageFrame in WebContent. That forced a compositor readback for
every drawImage(canvas, ...) call before sending the destination canvas
commands back to the compositor.
Teach the canvas command stream to carry a DrawCanvas command that names
the source canvas surface. The destination 2D context now flushes the
source canvas, records that command, and immediately flushes the
destination command list so the compositor copies the source surface at
the drawImage call boundary. Bitmap sources continue to use DrawBitmap,
and true readback APIs still read pixels explicitly.
Compositor-backed canvas contexts keep their transports tied to a single
Compositor connection. When that connection dies, the 2D backing
storage and WebGL GL objects disappear with it, but WebContent does not
surface the loss to canvas contexts or create fresh host contexts after
reconnect.
Track compositor loss through the WebContent connection, mark WebGL
contexts lost, dispatch the standard context events, and rebuild the
remote proxy when the page opts into restoration. For 2D canvas, queue
the canvas context loss steps, discard the dead backing storage, and
create new storage before firing contextrestored.
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.
The display list can now refer to canvas ids, but WebContent still had
no channel for creating or updating those canvas resources in the
Compositor. Both 2D and WebGL canvases would have had to grow the IPC
plumbing in the same commit that changes the rendering contexts.
This adds the Compositor-side CanvasHost, WebContent transport objects,
and the IPC/CMake pieces needed to allocate, update, read back, and
destroy remote canvas contexts. The rendering contexts are not switched
over yet, keeping this as plumbing for later commits.
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.
Keep UI process session history authoritative across overlapping
fallback loads and traversals. WebContent can finish a superseded
history load with a live document matching the UI seed URL while its
local step, document state id, and Navigation API keys still describe
a temporary partial list.
Reconstruct the current entry around the UI-owned list in that case.
This avoids making the UI process adopt WebContent's incomplete
snapshot.
Track UI-started fallback loads by URL so unrelated navigations cannot
consume the pending seed state. Resolve deferred WebDriver completions
through the view registry so callbacks queued before a process swap do
not touch a destroyed view.
Add WebDriver coverage that waits for explicit UI/WebContent history
convergence after the relevant document events. The waits poll
observable history state instead of depending on timing.
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.
clobberNextNavigationWithATraversal() arms a one-shot that, on the next
call to Navigable::begin_navigation, re-stamps the navigable’s ongoing
navigation with a synthetic session-history traversal during the unload
check, then clears it on a later turn — draining deferred navigations.
This lets a single-process test deterministically reproduce a race
between a cross-document navigation and a concurrent traversal, which
otherwise only surfaces under scheduling jitter in a multi-process run.
Merge `current_image_frame` and `current_image_frame_sized` into a
single method which takes an `Optional<Gfx::IntSize>`.
Rename `default_image_frame_sized` to `default_image_frame` and make
it's `Gfx::IntSize` argument `Optional`.
Apply pinch zoom deltas to the compositor's visual viewport transform
so the currently presented display list can respond without waiting for
the WebContent main thread. Keep the normal WebContent pinch event path
so the real VisualViewport state and DOM-visible events catch up after.
Only take the compositor path when async scrolling is enabled and there
are no blocking wheel listeners, since pinch zoom dispatches a synthetic
wheel event that script may cancel. Coalesce queued pinch events in
WebContent so main-thread catch-up can adopt multiple gesture deltas
together.
Use the compositor visual viewport transform for wheel hit testing and
consume wheel deltas as visual viewport pan while zoomed. Scale the
handoff to layout viewport scrolling by the inverse visual viewport
scale, so touchpad momentum does not jump when the visual viewport hits
an edge.
The decoded-frame Skia image cache is useful for display-list
rasterization because decoded image resources can be replayed over many
frames. The cache lets that path reuse SkImage wrappers and GPU-backed
copies instead of rebuilding them whenever the same resource is painted.
For canvas, commands are consumed into one backing surface and decoded
frames are already held by the command or paint style for the draw. A
per-painter cache does not match that usage model, and can keep decoded
frames and Skia images alive after the draw has consumed them.
This removes the cache from PainterSkia and drops the now-unused pruning
hook from CanvasCommandPlayer. With the cache gone, PainterSkia can hold
its painting surface directly instead of allocating a private Impl.
DisplayListPlayerSkia keeps owning the cache, so display-list
rasterization keeps the SkImage reuse behavior.
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.
Child navigables can lose document-associated apply-history tasks when
a parent page replaces or destroys the child document. Queue child tasks
with no document association so they remain runnable, and share that
choice for both activation and update-only continuations. Keep top-level
work associated with the active document to preserve initial about:blank
Window reuse.
Also abandon a queued child fetch if its parent document is already gone
before reading the parent's relevant settings object. This matches
browser behavior for detached frame navigations and avoids resuming
stale work against a discarded parent.
The Twinings menu to Black Tea to Earl Grey product flow now reaches the
product main content under Ladybird WebDriver. Existing navigation
coverage and the full LibWeb text suite cover the local history cases.
Same-document navigations now commit synchronously in WebContent, while
the UI process mirror learns about them over asynchronous IPC. A stale
UI seed could be accepted back into a live non-initial document and
overwrite its latest entry, making queued traversals target unreachable
entries.
Share descriptor comparison helpers between LibWeb and LibWebView.
Reject stale top-level seeds against the active document latest entry,
and let the UI process adopt WebContent current snapshots when a seed is
rejected. Test-only session history dumps now first send WebContent
current state synchronously, so dumps observe the converged state.
Allow post-load UI seeds to carry UI-owned nested histories that the
freshly loaded top-level document has not reconstructed yet. Add unit
coverage for matching those seeds while still checking top-level state.
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.
Normalize the target step again at the end of applying a history
step, since iframe removal can leave the originally computed target
step unused before the asynchronous application finishes. Let the UI
history snapshot use the same used-step mapping when it serializes a
current item for the UI process.
Handle late child-frame navigation commits whose original nested
history entry disappeared before finalization. Removed iframes have no
live nested history list to update, and initial about:blank still needs
its first real navigation to replace the remaining initial child entry.
Add coverage for iframe pushState before nested history readiness and
for removing and recreating an iframe after an initial same-document
history update.
Use the LibWebView history mirror to preserve traversable session
history across WebContent process swaps. WebContent reports snapshots to
the UI process, and new renderers can be seeded from the mirror.
Browser back and forward now resolve through the UI-owned used history
steps. WebContent still runs the spec traversal path when the current
renderer has enough matching state to do so.
Handle canceled and no-op UI navigations without leaving speculative
history entries or pending WebDriver waits behind. Preserve traversal
precheck state across synchronous IPC shutdown, and avoid overwriting a
restored target entry's persisted scroll state before the document has
adopted that entry.
Add structured helpers for the history data mirrored by LibWebView.
Cover POST resources, history state, navigation API state, scroll
positions, and entry metadata.
Keep this below the UI model so browser-side history code can move data
through typed objects instead of ad-hoc strings.
Move CanvasRenderingContext2D onto the same record-and-replay model that
the compositor-process path will use, but keep playback local for now.
Draw calls append CanvasCommandList entries and flush/readback paths
replay the commands into a local CanvasCommandPlayer-owned surface.
Mach transport already sends payloads as out-of-line virtual-copy
regions, but the receive path immediately copied each payload into a new
Vector and deallocated the kernel mapping. That made the IPC IO thread
touch every byte before the main thread could decode the message.
Add ReceivedMessageBytes as the raw-message byte storage and let the
Mach transport adopt the OOL region directly. The mapping now lives
until the raw message storage is destroyed, so invalid descriptor paths
and normal queue teardown both release it through the same destructor.
Socket transports keep their existing receive copy path by wrapping
vectors in the same storage type, and the direct raw-message consumers
now decode from its ReadonlyBytes view.
The pragma directives specification steps should only run when a meta
element is inserted into the document, meaning it is in a document tree
after the insertion steps have run. We previously ran the pragma
algorithms for any insertion, so inserting a meta element with
`http-equiv=content-language` into a detached subtree dereferenced a
null document element.