Commit graph

77691 commits

Author SHA1 Message Date
Undefine
b9fec5edbf Meta: Rename and merge some lagom_* and ladybird_*
We had both {lagom,ladybird}_test and {lagom,ladybird}_lib, now both
are just one helper. Also rename all other lagom_* helpers to be
ladybird_*.
2026-05-05 22:08:24 +02:00
Undefine
77966f66cb Meta: Remove add_ladybird_subdirectory
It went unused.
2026-05-05 22:08:24 +02:00
Undefine
8e90ca50c4 Meta: Allow ladybird.py to install all targets for host tools builds
In cases when we build only host tools we almost always want to install
all the targets that are there. Lock it down to only host tools as
installing when all targets are build will install a bunch of things
that are probably not intented to be installed.
2026-05-05 22:08:24 +02:00
Undefine
a87f853009 Meta: Remove a legacy check from check-style.py
That was a purely SerenityOS check that doesn't make sense for Ladybird
2026-05-05 22:08:24 +02:00
Undefine
ffe5a5a6cf Meta: Add a vcpkg.json linter
This script checks for two things:
- that all the dependencies have their version locked properly
- that all versions are set as version#port-version
2026-05-05 22:08:24 +02:00
Undefine
1d83e8c896 Meta: Move all remaining dependency checks to check_for_dependencies
This also allows us to get rid of a couple of files that were meant
for dependency specific checks.
2026-05-05 22:08:24 +02:00
Undefine
b41dbf60f3 Meta: Ensure version overrides in vcpkg.json are consistent
Remove the missing overrides for wayland and wayland-protocols and
make sure all overrides follow the scheme of version#port_verison.
This will be verified after a later commit that adds a linter script.
2026-05-05 22:08:24 +02:00
antoniospg
428fa59167 LibMedia+LibWeb: Implement HTMLMediaElement.getStartDate() 2026-05-05 14:45:00 -05:00
Aliaksandr Kalenik
76c79ee522 LibGfx: Remove ImmutableBitmap
DecodedImageFrame now owns decoded bitmap pixels directly, so the
separate ImmutableBitmap wrapper no longer carries useful semantics.
Remove the class and pass decoded image frames or bitmaps at the
boundaries where pixels are actually required.

The Skia image cache now keys off DecodedImageFrame, matching the
display-list commands that paint decoded images. Video frames stay
owned by LibMedia, with the explicit YUV-to-bitmap conversion living
at HTMLVideoElement's decoded-frame entry point for canvas and WebGL
callers.
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
40f2abb7fe LibGfx+LibWeb: Add DecodedImageFrame
Decoded image data should not continue to traffic in ImmutableBitmap now
that the bitmap wrapper is being retired. Introduce DecodedImageFrame as
the paintable decoded-image unit and store a Bitmap plus ColorSpace in
it directly.

Thread the new frame type through decoded image data, display-list
image commands, filters, canvas drawImage, patterns, WebGL texture
upload, and CSS/SVG image consumers. ImmutableBitmap remains only at
the legacy boundaries that still need it, such as HTML video snapshots
and callers that explicitly ask for a bitmap snapshot.

This keeps color-space ownership with the decoded frame while making
the expensive or legacy ImmutableBitmap path explicit at the few call
sites that still need it.
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
3c25d080b1 LibGfx: Move painting surface snapshots to PaintingSurface
ImmutableBitmap still owned the helper that read pixels from a
PaintingSurface and wrapped the result as an ImmutableBitmap. That kept
a surface readback operation attached to the type we are trying to
remove, even though the snapshot is really a property of the painting
surface.

Add PaintingSurface::snapshot_bitmap() as the explicit readback path.
The remaining callers now wrap that bitmap in ImmutableBitmap only at
the places that still need the old abstraction. Canvas serialization
also uses the same helper, so the BGRA8888 premultiplied snapshot
policy has a single owner.
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
00181dcf50 LibGfx: Move bitmap export out of ImmutableBitmap
Bitmap export is pixel conversion, not an ImmutableBitmap-specific
operation. It needs a source bitmap and color space, and callers should
provide those explicitly instead of routing through the immutable
snapshot abstraction.

Move the export formats, flags, result type, and conversion
implementation into a new BitmapExport helper. Keep
BitmapExportResult.h as a forwarding header for existing includes
while making BitmapExport.h the new home for the public API.

Update WebGL and the LibGfx export test to use the standalone helper
directly.
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
9f7f622362 LibGfx: Remove ImmutableBitmapImpl
After YUV data no longer lives behind ImmutableBitmap, the private Impl
wrapper only holds a bitmap and its color space. The extra allocation no
longer buys us a meaningful storage abstraction.

Store the bitmap and color space directly on ImmutableBitmap and remove
ImmutableBitmapImpl. This leaves ImmutableBitmap with one clear role
again: an immutable pixel snapshot with no hidden media-backed storage
mode.
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
9a348c8338 LibWeb: Paint video frames from YUV data
A video element should record video as video, not as generic external
bitmap content. Add VideoFrameSource and a dedicated display-list
command so the display-list player receives the current
Media::VideoFrame directly.

The Skia player can now upload YUV pixmaps from the frame when a GPU
context is available, without teaching the ordinary ImmutableBitmap
image cache about media formats. If GPU upload is unavailable, the
fallback explicitly converts the frame through YUVData::to_bitmap().

This gives video painting a clear extension point for future frame
backends, such as hardware frames or other planar formats, while
keeping bitmap drawing focused on immutable pixel snapshots.
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
febd85f417 LibMedia+LibWeb: Store decoded YUV data in video frames
Decoded video frames should own their planar YUV data and color space
directly. Keeping that storage behind ImmutableBitmap gave a
still-image abstraction media-specific behavior and made calls like
bitmap() potentially allocate and convert a whole video frame.

Move YUV ownership into Media::VideoFrame, where the lifetime naturally
follows media playback, and remove the YUV-backed mode from
ImmutableBitmap. This commit intentionally keeps the visible Web paint
path on ExternalContentSource by converting the current frame back to
an ImmutableBitmap where Web still expects one.

Callers that need pixels now ask the frame to convert explicitly. That
preserves behavior for canvas and bitmap consumers while making the
expensive YUV-to-pixel path visible at the call site instead of
hiding it behind ImmutableBitmap::bitmap().
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
7f09cdfc82 LibMedia+LibWeb: Queue video frames for display
The display queue used TimedImage even though the media pipeline is
selecting decoded video frames. That naming hid the real object being
handed from LibMedia to Web and kept the queue interface coupled to
bitmap-style painting.

Rename the wrapper to TimedVideoFrame and pass ref-counted VideoFrame
objects through the provider and display sink. Web still reads the
ImmutableBitmap from the frame for painting in this commit, so rendered
output and conversion behavior stay the same while the playback-facing
interfaces become frame-shaped.
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
a659c55504 LibMedia: Make video frames ref-counted
Video frames are about to be shared between the decoder, the data
provider, the display sink, and Web painting code. Passing them by
value keeps ownership tied to the old bitmap-shaped pipeline and makes
later lifetime changes harder to reason about.

Make VideoFrame ref-counted and return NonnullRefPtr from the decoder
and media queues. This changes ownership only: a VideoFrame still wraps
an ImmutableBitmap at this point, so playback behavior remains
unchanged while later commits can move storage and painting
independently.
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
ee4f9ef82c LibGfx: Preserve native YUV samples for CPU conversion
This is a small prerequisite for moving decoded YUV frames from
ImmutableBitmap into Media::VideoFrame. Once the frame owns the planes
directly, CPU consumers and the Skia upload path will share the same
YUVData object.

Those consumers need different representations. Skia expects high bit
depth YUVA pixmaps to be full-range 16-bit samples, while CPU conversion
paths must see the decoder-native samples and the original bit depth.

Keep YUVData in decoder-native form, and make the Skia expansion a
temporary copy owned by SkYUVAPixmaps. Add a regression test that checks
10-bit pixmaps are expanded for Skia without mutating the source planes
or changing YUVData::to_bitmap() output.
2026-05-05 14:39:17 -05:00
aplefull
7e492ff554 LibGfx: Set maximum bitmap dimensions to 65535
This matches Firefox's behavior which allows up to 65535 for both width
and height. The total area is still constrained by the pitch overflow
check. It was changed to int32_t, so now it limits the maximum area to
~23kx23k, which is still less than the 65535x65535 area that Firefox
allows, but we would eat too much memory if we allowed that.
2026-05-05 20:13:43 +02:00
Sam Atkins
7889a46724 LibWebSocket: Time out stalled close handshakes
Start a single-shot timer when a websocket enters CLOSING and fail
the connection if the peer never answers with its close frame.

Without a bound here, a dropped or non-responsive peer can leave the
websocket stuck in the closing handshake forever, which is another
path to rare websocket timeouts during repeated test runs.
2026-05-05 19:14:29 +02:00
Sam Atkins
94a98f5191 LibWeb: Surface websocket startup failures to JS
If establish_web_socket_connection() fails before a Requests::WebSocket
object exists, the DOM WebSocket used to ignore that error and remain
silently stuck with no message, error, or close events.

Dispatch error and close in that setup-failure path so callers see a
terminal websocket state instead of hanging until the harness timeout.
2026-05-05 19:14:29 +02:00
Sam Atkins
c880a34541 LibRequests: Fail websockets when RequestServer dies
Detach live websocket wrappers from a dead RequestServer connection,
notify them with error and close events, and defer the
on_request_server_died callback out of RequestClient::die().

Without this, a dead RequestServer could leave existing websocket
objects stuck forever and future websocket construction could keep
using a stale RequestClient with no terminal events.
2026-05-05 19:14:29 +02:00
Sam Atkins
48a0e9fef7 RequestServer: Queue websocket writes under curl backpressure
Add the same connect timeout used by regular requests to curl-backed
websockets, buffer unsent websocket bytes, and resume them from a write
notifier instead of retrying curl_easy_send() in a tight loop.

This prevents CPU spins on CURLE_AGAIN and keeps large websocket sends
from hanging when the peer applies backpressure.
2026-05-05 19:14:29 +02:00
Sam Atkins
1d7825f601 RequestServer: Terminate cancelled and failed websockets cleanly
Convert DNS failures and connecting-stage close requests into terminal
websocket state changes, and remove websocket entries once they close.

This keeps clients from getting stuck in CONNECTING and makes early
cancelled or failed websocket attempts terminate like the other close
paths.
2026-05-05 19:14:29 +02:00
Sam Atkins
822303540d LibWebSocket: Send empty websocket frames
Remove the client-side early return that skipped transport writes for
zero-length masked payloads.

This keeps empty text or binary messages and empty pong replies on the
wire instead of silently discarding them.

This essentially reverts 29078d4d53. The
crash that commit fixed (loading
https://www.linux.org.ru/news/opensource/16780786 and waiting) does not
occur with this change.
2026-05-05 19:14:29 +02:00
Sam Atkins
485f50287f LibWebSocket: Close failed sockets cleanly
Route fatal protocol-level websocket failures through fail_connection(),
emit both error and close once a socket exists, and drop the old
fatal_error() helper.

This gives callers a terminal close event instead of leaving transport
failures as error-only state changes.
2026-05-05 19:14:29 +02:00
Tim Ledbetter
78b47109af LibWeb: Use the ASCII line segmenter for eligible text 2026-05-05 18:59:07 +02:00
Tim Ledbetter
f161215f53 LibUnicode: Add an ASCII fast path for line break segmentation
Add a Segmenter implementation that implements the UAX#14 line breaking
rules applicable to ASCII text. This avoids the need to build an ICU
BreakIterator for the majority of text on the web.
2026-05-05 18:59:07 +02:00
Jelle Raaijmakers
2f39b2dd63 LibWeb: Split shadow stylesheet host-side invalidation reach
Broad shadow-root stylesheet changes already restyle the whole shadow
tree, but host-side fallout does not always need a document-wide
invalidation. Split the host-side reach classification so selectors
contained to the host subtree, such as `:host *` and `:host > *`,
invalidate the host, while sibling-escaping selectors such as
`:host + :has(*)` still invalidate the host's root.

Recognize sibling escapes through positive selector-list pseudos such as
`:is()` and `:where()` as well, including selectors like
`:is(:host) + :has(*)` and `:is(:host + .item)`.

This avoids turning host-contained shadow stylesheet changes into full
document style invalidations. On https://pomax.github.io/bezierinfo/,
this reduces the time to produce a layout tree from about 8.7s to 3.6s
on my machine.
2026-05-05 18:36:34 +02:00
Tim Ledbetter
1cc288730c LibWeb: Stop storing CascadedProperties on each element
`CascadedProperties` is now allocated fresh when style is recomputed.
`Element` and `PseudoElement` no longer keep hold of cascaded
properties.
2026-05-05 16:02:23 +02:00
Tim Ledbetter
c26922c898 LibWeb: Cache values needed after the cascade finishes
Previously, we consulted `cascaded_properties()` in a couple of places
after the cascade pass for the relevant element had finished, forcing
`CascadedProperties` to outlive style resolution.

We now keep the small set of values these consumers need on
`ComputedProperties`. We keep hold of resolved specified values for
properties whose computation depends on inherited info, so they can be
re-resolved when an ancestor changes. We also keep the raw winning
cascaded font-size, as this is needed by the time-traveling monospace
font quirk implemented by `recascade_font_size_if_needed()`.
2026-05-05 16:02:23 +02:00
Tim Ledbetter
6132b5fa1b LibWeb: Resolve <th> text-align using only the parent's computed value 2026-05-05 16:02:23 +02:00
Tim Ledbetter
c70e5346f5 LibWeb: Replace cascaded_properties() check in animation invalidation
Using `computed_properties()` instead achieves the same thing here.
2026-05-05 16:02:23 +02:00
Tim Ledbetter
6a64a55a41 LibWeb: Rename show_cascaded_properties flag in dump_tree()
This flag actually controls dumping of computed properties, so let's
rename it to reflect that.
2026-05-05 16:02:23 +02:00
Andreas Kling
c7e24fce2f LibWebView: Persist and restore zoom level per host
Remember the zoom level for each host so that returning to a site
restores the zoom the user previously chose, matching what other
browsers have done for years.

When the user zooms in, zooms out, or resets the zoom, the resulting
level is written to Settings keyed by the current page's host. On
navigation, when a view's URL host changes, the stored level for the
new host is applied (or the global default if there is no override).

Per-host zoom changes are broadcast through the SettingsObserver so
that two tabs viewing the same host stay in sync as soon as the user
adjusts zoom in either one. Zoom changes from within the page (such
as internals.setBrowserZoom hook) and the WebContent restart path do
not persist, only user-initiated zoom changes do.
2026-05-05 13:53:59 +02:00
Andreas Kling
d9dd412440 LibJS: Use foldhash in parser and scope-collector hash maps
The std default RandomState (SipHash) was using ~9 percentage points
of CPU on hash_one and write across the parse hot path, with the
string interner adding another ~3 pp on top. The cost was spread
across the interner, the scope collector's IndexMap<Utf16String, _>,
and several parser-side HashSet<Utf16String> declarations.

Use foldhash::quality::RandomState for the parser, scope collector,
and string interner via a new fast_hash module. Quality keeps
HashDoS resistance (keys are lexer tokens, attacker-controlled in a
browser context) while shedding SipHash's per-byte cost, and on this
workload it benchmarks slightly faster than foldhash::fast.
2026-05-05 13:53:51 +02:00
Andreas Kling
17d3a285a7 LibJS: Move ScopeData into ScopeArena and reference it by ScopeId
The Rust AST kept every scope in Rc<RefCell<ScopeData>>. The Rc made
the AST !Send (cross-thread codegen needed unsafe impl Send), and the
RefCell added a runtime borrow check on every hot-path read.

AST nodes (Block, FunctionBody, Program, SwitchStatement, SwitchCase)
now hold a ScopeId index into ScopeArena. The scope collector and
codegen take &mut/&ScopeArena, so the borrow checker enforces the
previously-implicit invariant that two phases never touch the same
scope at once.

ParsedProgram is now naturally Send. The unsafe impl Send and the
arc_with_non_send_sync allow go away. CompiledProgram keeps its
hand-rolled Send impl because it carries codegen-time state outside
the AST.

FunctionDeclarationData::is_hoisted was a Cell<bool> only because the
old &[ScopeRecord] traversal couldn't get &mut to the AST. It is now
a plain bool.
2026-05-05 13:53:51 +02:00
Andreas Kling
f2bf914874 LibJS: Intern identifier names in a per-arena string table
Identifier::name was SharedUtf16String (Rc<Utf16String>), so equality
checks against literals walked the slice and the Rc made the AST
!Send.

Replace it with a StringId (u32 index) backed by a StringInterner on
AstArena. Repeated names dedupe to the same id, so name comparisons
collapse to u32 == u32. The lexer's short/recent identifier caches
and the shared_identifier_value field on Token go away; the interner
already deduplicates everything.

Methods that previously took &mut IdentifierArena now also take
&StringInterner so they can resolve names from StringId during
analyze. Codegen helpers in bytecode/codegen.rs uniformly take
&AstArena. Generator gains intern_identifier_id, intern_property_key_id,
and intern_string_id helpers.
2026-05-05 13:53:51 +02:00
Andreas Kling
9ace32e276 LibJS: Drop Cell<> wrappers from Identifier scope-analysis fields
scope_collector now reaches Identifier through &mut IdentifierArena
indexing instead of through Rc<Identifier>'s shared reference, so the
Cell<> wrappers on local_type, local_index, is_global,
is_inside_scope_with_eval, and declaration_kind no longer earn their
keep.

Replace each Cell<T> with a plain T. The borrow checker now enforces
the existing "only scope_collector mutates these post-parse"
invariant. Shrinks Identifier and removes a layer of indirection on
hot-path field reads in codegen and ast_dump.
2026-05-05 13:53:51 +02:00
Andreas Kling
3e15e59cd1 LibJS: Move identifiers into a contiguous IdentifierArena
Replace per-AST-node Rc<Identifier> with a Copy IdentifierId index
into a Vec<Identifier> arena, plumbed through Parser, scope_collector,
codegen, ast_dump, and the FFI. The arena lives on the parser during
parse, ships out via Arc<AstArena> on ParsedProgram, and is shared by
each child Generator and FunctionPayload through Arc clones.

Eliminates the per-occurrence Rc::new in the parser: every identifier
reference, parameter binding, function name, class name, and
binding-pattern target lands in the arena's Vec instead of getting its
own malloc plus Rc control block. Identifier field reads in codegen
become direct array indexing.

Identifier still carries Cell<>-wrapped scope-analysis state, so
AstArena is not yet Send + Sync; the existing unsafe-impl-Send wrapper
on ParsedProgram covers cross-thread handoff. Removing the Cells is
the next step.
2026-05-05 13:53:51 +02:00
Andreas Kling
d9b9925914 LibJS: Make CompiledRegex thread-safe with Arc + AtomicPtr
The regex literal handle is shared between AST clones (e.g. class
field initializers reuse the same compiled regex), so shared ownership
has to stay. Switch from Rc to Arc and from Cell<*mut c_void> to
AtomicPtr<c_void> so the regex can travel with a function payload to
a worker thread without UB on the non-atomic Rc refcount.
2026-05-05 13:53:51 +02:00
Andreas Kling
5b2dd60d11 LibJS: Add AST arena types for identifiers, scopes, and interned strings
Introduce IdentifierArena, ScopeArena, and StringInterner plus their
opaque IdentifierId/ScopeId/StringId index newtypes, bundled under
AstArena.
2026-05-05 13:53:51 +02:00
Andreas Kling
9dcfe77a8c LibJS+LibWeb: Track Rust archive as input in static-lib merge
The POST_BUILD step that merges libjs_rust.a / libweb_rust.a into
liblagom-{js,web}.a has no inputs declared to ninja, so Rust-only
edits left the merged archive stale. bin/js then linked pre-change
Rust code: the stale merged archive shadows the directly-linked
fresh Rust .a on the link line.

Add an OBJECT_DEPENDS from RustIntegration.cpp / RustTokenizer.cpp
to the Rust archive so the FFI bridge recompiles, the C++ archive
re-builds, and POST_BUILD re-runs the merge. Shared-library builds
already track the Rust .a as a regular link input.
2026-05-05 13:53:51 +02:00
Jelle Raaijmakers
69f5876abb Meta: Do not require branch/commit consistency in Flatpak
Our Flatpak builds currently start failing once a commit ID no longer
matches with the commit pointed to by the configured `branch`. This is
becoming pretty annoying for Skia, since the `chrome/mXYZ` branches are
effectively moving targets.

Change the `branch` key in the Flatpak config to `x-branch`, which
removes the constraint and allows Flatpak to work with just the pinned
commit IDs. Change the linter to still use `x-branch` as the reference
to compare with our vcpkg configuration, so we make sure to still keep
things consistent between vcpkg and Flatpak.
2026-05-05 12:03:42 +02:00
Jelle Raaijmakers
3564dc1c81 Meta: Update flatpak commit for Skia
The M144 tag was updated:

https://skia.googlesource.com/skia/+/cd0c5f445516ea4e90e02b5f634cbc5ca23b5a44
2026-05-05 12:03:42 +02:00
Jelle Raaijmakers
246a6a1194 CI: Switch to build image 2026.05.4 2026-05-05 11:29:07 +02:00
Tim Ledbetter
08ece2af93 LibWeb: Skip @media rule re-evaluation when nothing has changed
The `m_needs_media_query_evaluation` tracks when we need to evaluate
media rules, but we were evaluating them unconditionally during style
updates.
2026-05-05 11:23:11 +02:00
Aliaksandr Kalenik
90b790f870 LibGfx: Drop PainterSkia::Impl::with_canvas
with_canvas() existed to wrap every canvas access with the
PaintingSurface lock/unlock pair. With the locking gone, the helper
now just hides a plain canvas() access — inline it at the call sites.
2026-05-04 20:12:21 +02:00
Aliaksandr Kalenik
40a619e286 LibGfx+LibWeb: Drop SkiaBackendContext locking
The rendering thread now uses its own SkiaBackendContext, and the main
thread no longer reaches into GPU-backed Skia objects directly. No Skia
context is shared between threads anymore, so the mutex on
SkiaBackendContext and the lock_context()/unlock_context() pair that
wrapped every PaintingSurface and ImmutableBitmap operation are all dead
weight and can go.
2026-05-04 20:12:21 +02:00
Aliaksandr Kalenik
5854bf10e6 LibWeb: Give the rendering thread its own Skia backend context
Sharing a single SkiaBackendContext between the main thread and the
rendering thread forces locking around every GPU operation. Now that
ImmutableBitmaps are context-neutral, the SkImage cache is per-painter,
and PaintingSurface accepts an explicit context, have the rendering
thread create its own GPU context on startup and use it for the
display-list player and backing store allocation.

This sets up the next commit to remove the cross-thread locking
machinery entirely.
2026-05-04 20:12:21 +02:00