Compare commits

...

421 Commits

Author SHA1 Message Date
Lorenz A
f54793315c LibWeb: Adjust buttons computed display style
Some checks failed
CI / ${{ matrix.os_name }}, ${{ matrix.arch }}, ${{ matrix.build_preset }}, ${{ matrix.toolchain }} (arm64, Sanitizer, false, macOS, ["macos-15", "self-hosted"], Clang) (push) Has been cancelled
CI / ${{ matrix.os_name }}, ${{ matrix.arch }}, ${{ matrix.build_preset }}, ${{ matrix.toolchain }} (x86_64, All_Debug, false, Linux, ["blacksmith-16vcpu-ubuntu-2404"], Clang) (push) Has been cancelled
CI / ${{ matrix.os_name }}, ${{ matrix.arch }}, ${{ matrix.build_preset }}, ${{ matrix.toolchain }} (x86_64, Fuzzers, false, Linux, ["blacksmith-16vcpu-ubuntu-2404"], Clang) (push) Has been cancelled
CI / ${{ matrix.os_name }}, ${{ matrix.arch }}, ${{ matrix.build_preset }}, ${{ matrix.toolchain }} (x86_64, Sanitizer, false, Linux, ["blacksmith-16vcpu-ubuntu-2404"], GNU) (push) Has been cancelled
CI / ${{ matrix.os_name }}, ${{ matrix.arch }}, ${{ matrix.build_preset }}, ${{ matrix.toolchain }} (x86_64, Sanitizer, true, Linux, ["blacksmith-16vcpu-ubuntu-2404"], Clang) (push) Has been cancelled
Build Dev Container Image / build (push) Has been cancelled
Run test262 and test-wasm / run_and_update_results (push) Has been cancelled
Lint Code / lint (push) Has been cancelled
Label PRs with merge conflicts / auto-labeler (push) Has been cancelled
Push notes / build (push) Has been cancelled
Nightly Lagom / ${{ matrix.os_name }}, ${{ matrix.arch }}, ${{ matrix.build_preset }}, ${{ matrix.toolchain }} (arm64, Distribution, false, Linux, ["blacksmith-8vcpu-ubuntu-2404-arm"], Clang) (push) Has been cancelled
Nightly Lagom / ${{ matrix.os_name }}, ${{ matrix.arch }}, ${{ matrix.build_preset }}, ${{ matrix.toolchain }} (arm64, Distribution, false, macOS, ["macos-15"], Clang) (push) Has been cancelled
Nightly Lagom / ${{ matrix.os_name }}, ${{ matrix.arch }}, ${{ matrix.build_preset }}, ${{ matrix.toolchain }} (arm64, Sanitizer, false, Linux, ["blacksmith-8vcpu-ubuntu-2404-arm"], Clang) (push) Has been cancelled
Nightly Lagom / ${{ matrix.os_name }}, ${{ matrix.arch }}, ${{ matrix.build_preset }}, ${{ matrix.toolchain }} (arm64, Sanitizer, false, macOS, ["macos-15"], Swift) (push) Has been cancelled
Nightly Lagom / ${{ matrix.os_name }}, ${{ matrix.arch }}, ${{ matrix.build_preset }}, ${{ matrix.toolchain }} (x86_64, Distribution, false, Linux, ["blacksmith-8vcpu-ubuntu-2404"], GNU) (push) Has been cancelled
Nightly Lagom / ${{ matrix.os_name }}, ${{ matrix.arch }}, ${{ matrix.build_preset }}, ${{ matrix.toolchain }} (x86_64, Sanitizer, false, Linux, ["blacksmith-8vcpu-ubuntu-2404"], Swift) (push) Has been cancelled
Nightly Lagom / ${{ matrix.os_name }}, ${{ matrix.arch }}, ${{ matrix.build_preset }}, ${{ matrix.toolchain }} (x86_64, Windows_Sanitizer_CI, false, Windows, ["windows-2025"], ClangCL) (push) Has been cancelled
Nightly Lagom / Flatpak ${{ matrix.arch }} (aarch64, ["ubuntu-24.04-arm"]) (push) Has been cancelled
Nightly Lagom / Flatpak ${{ matrix.arch }} (x86_64, ["ubuntu-24.04"]) (push) Has been cancelled
Nightly Android / CI (macos-14, Android) (push) Has been cancelled
2025-11-01 13:02:44 +00:00
Andreas Kling
4bcb34d7a0 Revert "LibJS: Allocate callee execution contexts in call instruction handler"
This reverts commit cdcbbcf48b.

It made MicroBench/call-*-args.js faster, but some of the macro
benchmarks got significantly slower on macOS, so let's revert until we
understand it better.
2025-11-01 13:07:53 +01:00
ayeteadoe
f846b6f2d9 CMake: Remove unnecessary WebContent dependencies on Windows
In the initial work that got Ladybird running on Windows, there were
some DLLs that WebContent implicitly depended on which was causing
runtime errors when launching as they didn't exist in libexec.

So the workaround was to explicitly link the targets that had issues to
WebContent and use lagom_copy_runtime_dlls() to ensure they got copied
to libexec.

But given libexec is not a standard Windows convention, in a later
review we made sure Services got output to the bin folder, but those
initial workarounds were not removed even though they were now
unnecessary.
2025-11-01 07:58:12 -04:00
Andreas Kling
55636432e9 LibJS: Make ExecutionContext constructor ALWAYS_INLINE 2025-11-01 08:40:32 +01:00
Andreas Kling
8a02acbab6 LibJS: Make ExecutionContext::identifier_table a raw pointer
We were already skipping the bounds checks on this thing anyway,
so might as well shrink ExecutionContext by 8 bytes by doing this.
2025-11-01 08:40:32 +01:00
Andreas Kling
5b9469786e LibJS: Move ExecutionContext::cached_source_range to rare data 2025-11-01 08:40:32 +01:00
Andreas Kling
3ef55f8859 LibJS: Only assign initial "accumulator" value if actually provided 2025-11-01 08:40:32 +01:00
Andreas Kling
6f9d297c3c LibJS: Coerce empty completion to "undefined" early in Interpreter
Instead of always checking if we're about to return an empty completion
value in Interpreter::run_executable(), we now coerce empty completions
to the undefined value earlier instead.

This simplifies the most common path through run_executable(), giving us
a small speedup.
2025-11-01 08:40:32 +01:00
Andreas Kling
75d49c4b55 LibJS: Remove effectively unused value span from ExecutionContext
Instead of using this span, we can just use the getter that calculates
the base of the register/constant/local/argument array based on the
ExecutionContext's own address.
2025-11-01 08:40:32 +01:00
Andreas Kling
e1344afff3 LibJS: Move ExecutionContext::context_owner to rare data
This is only used by ExecutionContexts owned by an HTML::ESO.
2025-11-01 08:40:32 +01:00
Andreas Kling
d234e9ee71 LibGC: Add GC::Heap::the()
There's only ever one GC::Heap per process, so let's have a way to find
it even when you have no context.
2025-11-01 08:40:32 +01:00
Andreas Kling
8b1f2e4e24 LibJS: Remove unused ExecutionContext::local(size_t) 2025-11-01 08:40:32 +01:00
Andreas Kling
cd3ef805c4 LibJS: Avoid redundant bounds check in ExecutionContext::argument() 2025-11-01 08:40:32 +01:00
Andreas Kling
13e1881bf7 LibJS: Store length of Call instructions in a member variable
This means we don't have to do a bunch of math to find the next
instruction boundary, and gives us a small speedup.
2025-11-01 08:40:32 +01:00
Andreas Kling
1e0b56586b LibJS: Move ExecutionContext members with destructors to "rare data"
This makes ExecutionContext trivially destructible, which means less
work to do on function exit.
2025-11-01 08:40:32 +01:00
Andreas Kling
9ded35f98f LibJS: Make CachedSourceRange GC-allocated
This gets rid of a RefPtr in ExecutionContext, bringing us one step
closer to destructor-less ExecutionContext.
2025-11-01 08:40:32 +01:00
Timothy Flynn
583164e412 Meta: Upgrade prettier to version 3.6.2
This version is installed by default nowadays and has slight differences
from 3.3.3.
2025-10-31 19:55:50 -04:00
Timothy Flynn
019c529c07 Meta: Increase the line length enforced by prettier to 120
This matches our coding style recommendation in CodingStyle.md, and
matches our python formatting.
2025-10-31 19:55:50 -04:00
Timothy Flynn
b7ecdad685 CI: Use same versions of wabt on Linux and macOS 2025-10-31 18:10:45 -04:00
Luke Wilde
35c6d52d7d LibWeb/CSP: Update invalid sample assertion in violation reporting
Asserting that a sample is not provided if the resource is not Inline
is not quite valid, since Eval, TrustedTypesSink and TrustedTypesPolicy
also provide a sample.

Spec issue: https://github.com/w3c/webappsec-csp/issues/788
2025-10-31 09:08:36 +01:00
Andreas Kling
2492c07430 LibJS: Remove redundant VERIFYs in running_execution_context()
Vector::last() will already verify that the context stack is non-empty.
2025-10-31 08:56:02 +01:00
Andreas Kling
1796089f29 LibJS: Demote a VERIFY in run_executable() to ASSERT 2025-10-31 08:56:02 +01:00
Andreas Kling
62781f4818 LibJS: Use return_value register for "last completion" return values
This simplifies the epilogue in run_executable().
2025-10-31 08:56:02 +01:00
Andreas Kling
5706831328 LibJS: Make run_executable() return simple ThrowCompletionOr<Value>
We don't need to return two values; running an executable only ever
produces a throw completion, or a normal completion, i.e a Value.

This necessitated a few minor changes, such as adding a way to check
if a JS::Cell is a GeneratorResult.
2025-10-31 08:56:02 +01:00
Jelle Raaijmakers
2f7797f854 LibWeb: Implement WebGL2's getBufferSubData()
This allows copying data from a buffer to an ArrayBufferView's storage.
2025-10-30 16:20:33 -07:00
Jelle Raaijmakers
25d78f7c8e LibWeb: Implement WebGL2's readPixels with a byte offset argument
We pass it in as a `void*` since that's what this API expects when you
have bound the GL_PIXEL_PACK_BUFFER buffer.
2025-10-30 16:20:33 -07:00
Jelle Raaijmakers
7b5940d27d LibWeb: Store and return pixel (un)pack buffer bindings in WebGL2 2025-10-30 16:20:33 -07:00
ayeteadoe
30729feebb LibWeb: Prevent crash in content calculations for ListItemMarkerBox
In several content calculation methods in FormattingContext, we assumed
that if create_independent_formatting_context_if_needed() fails the
input Layout::Box is a BlockContainer; however, the web platform test
css/css-pseudo/parsing/marker-supported-properties-in-animation.html is
one scenario where the input is a Layout::ListItemMarkerBox instead and
there is no FormattingContext supported for it yet.
2025-10-30 18:21:24 +00:00
R-Goc
991ab62dd7 AK: Increase MonotonicTime precision on Windows
MonotonicTime was using 32 bit floats for operations on the values
converted from an i64 returned by the performance counter. This caused
to either precision losses or complete losses of the data for timing
even things as long as hundreds of miliseconds, which should never be a
problem with the resolution of the performance counter. This change
fixes that behavior.
2025-10-30 09:42:40 -06:00
Callum Law
c2ca712406 LibWeb: Properly simplify sum nodes containing negated sum nodes
This is ad-hoc, see https://github.com/w3c/csswg-drafts/issues/13020

Gains us 5 WPT tests
2025-10-30 12:18:24 +00:00
Sam Atkins
9c06d58b2e LibWeb/CSS: Implement and use ValueType::DashedIdent
Reduces the repeated code for parsing these, and makes them available to
the generic value parser.
2025-10-30 11:33:45 +00:00
Andreas Kling
6671cbef41 LibJS: Precompute the number of regs/constants/locals in Executable
Instead of doing it again on every call. Minor bump in call performance.
2025-10-30 08:54:45 +01:00
Andreas Kling
cdcbbcf48b LibJS: Allocate callee execution contexts in call instruction handler
By handling call instructions in an inline (C++) function, we were
breaking the alloca() optimization and adding stack overhead. We fix
this by using a macro instead. It looks awful but it works.

1.07x speedup on MicroBench/call-00-args.js
2025-10-30 08:54:45 +01:00
Andreas Kling
667354fd12 LibJS: Always assume module bindings access is in strict mode
Modules are always in strict mode anyway, no need to look at the
strictness flag here.
2025-10-30 08:54:45 +01:00
Zaggy1024
fae2888103 Tests: Add a test for WebGL framebuffers retaining data after present 2025-10-29 22:06:31 -07:00
Zaggy1024
7e20b21879 LibWeb: Bind the default frame/render buffers when resetting context
This fixes the masking and warping effects on pixijs.com.

Co-authored-by: Luke Wilde <luke@ladybird.org>
2025-10-29 22:06:31 -07:00
ayeteadoe
3708ac5599 Meta: Use non-aliased "ladybird" ninja target on Windows in ladybird.py
When attempting to use "Ladybird" as the target passed to ninja, we get
"ninja: error: unknown target 'Ladybird', did you mean 'ladybird'?".

Note that "ladybird" also works on Unix; however, given the alias was
carried over from ladybird.sh for convenience, we will keep it in place
for non-Windows builds.
2025-10-29 21:07:52 -06:00
ayeteadoe
5e529fc603 LibCore: Use WinSock2 closesocket() in PosixSocketHelper::close()
Our Core::System::is_socket() helper is not very robust, as it was
incorrectly marking m_fd as a non-socket fd. This meant we were trying
to call CLoseHandle() on a socket which fails. We can just call
closesocket() directly given we are in an explicit socket-only context.
2025-10-29 21:07:52 -06:00
ayeteadoe
7683f1285f AK: Expose helpers to invoke Windows runtime config directly in main()
When shutting down helper processes, PosixSocketHelper::close() in
SocketWindows when trying to close the socket fd with
WSANOTINITIALISED. This was due to us initiating WinSock2 during static
initialization and presumably also not terminating WinSock2 properly.

Similar to WinSock2 initiation, calling CRT during static
initialization is considered dangerous given the CRT DLL itself may not
yet be initialized.

Because of the above, we move to perform this Windows-specific
runtime setup/teardown calls into the main() functions.
2025-10-29 21:07:52 -06:00
ayeteadoe
643f0de422 RequestServer: Instruct curl to use Windows CA cert store
This is required for supporting HTTPS requests. Otherwise we fail with
CURLE_PEER_FAILED_VERIFICATION.
2025-10-29 21:07:52 -06:00
ayeteadoe
95f239a357 CMake: Add Windows executable helper function
The function currently has 2 purposes: (1) To copy dependent dlls for
executables to output binary directory. This ensures that these helper
processes can be ran after a build given not all DLLs from vcpkg libs
get implicitly copied to the bin folder. (2) Allow fully background
and/or GUI processes to use the Windows Subsystem. This prevents
unnecessarily launching a console for the process, as we either require
no user interaction or the user interaction is all handled in the GUI.
2025-10-29 21:07:52 -06:00
ayeteadoe
20f9510687 CMake: Output helper process binaries to bin instead of libexec
libexec is not a standard convention on Windows. Everything should go
into bin
2025-10-29 21:07:52 -06:00
ayeteadoe
6261c433a3 LibWebView: Implement stubbed out BrowserProcess methods on Windows 2025-10-29 21:07:52 -06:00
ayeteadoe
231336f137 LibWebView: Implement stubbed out Process methods on Windows 2025-10-29 21:07:52 -06:00
ayeteadoe
d299df24ac LibRequests: Create ReadStream abstraction for reading request data
Given the RequestServer created the request fd with socketpair() on
Windows and pipe2() on Unix, this abstraction avoids inlined ifdef soup
by hiding the details of how the AK::Stream and Core::Notifier are
created.
2025-10-29 17:47:02 -04:00
ayeteadoe
11ec7c9cea RequestServer: Create RequestPipe abstraction for request data transfer
The Win32 API equivalent to pipe2() is CreatePipe(), which creates read
and write anonymous pipe handles that we can set to non-blocking via
SetNamedPipeHandleState(); however, this initial approach caused issues
as our Windows infrastructure assumes socket-based handles/fds and that
we don't use Windows pipes at all, see Core::System::is_socket() in
SystemWindows.cpp. So we use socketpair() to keep our current
assumptions true.

Given that Windows uses socketpair() and Unix uses pipe2(), this
RequestPipe abstraction avoids ifdef soup by hiding the details about
how the read/write fds pair is created and how response data is written
to the client.
2025-10-29 17:47:02 -04:00
Andreas Kling
9dae1acc31 LibJS: Pass ExecutionContext to Interpreter::run_executable()
This avoids having to get it from the VM's context stack, since most
callers already have it on hand.
2025-10-29 21:20:10 +01:00
Andreas Kling
a7d13b107e LibJS: Move all per-frame state from Interpreter to ExecutionContext
This simplifies function entry/exit and lets us just walk away from the
used ExecutionContext instead of resetting a bunch of its state when
returning control to the caller.
2025-10-29 21:20:10 +01:00
Andreas Kling
59ce6c9b41 LibJS: Shrink two u64 fields in ExecutionContext to u32
To shrink ExecutionContext, these two fields are made 32-bit:

- skip_when_determining_incumbent_counter
- program_counter
2025-10-29 21:20:10 +01:00
Andreas Kling
4c7ffc0552 LibJS: Remove ExecutionContext::function_name field
Instead of having ExecutionContext track function names separately,
we give FunctionObject a virtual function that returns an appropriate
name string for use in call stacks.
2025-10-29 21:20:10 +01:00
Andreas Kling
e967631763 LibJS: Remove ExecutionContext::arguments_offset and just compute it 2025-10-29 21:20:10 +01:00
Andreas Kling
fdb85a330e LibJS: Stop tracking whether execution context is strict mode or not
This was only used for basic testing, and forced us to plumb this flag
flag in a bunch of places.
2025-10-29 21:20:10 +01:00
Andreas Kling
fb05063dde LibJS: Let bytecode instructions know whether they are in strict mode
This commits puts the strict mode flag in the header of every bytecode
instruction. This allows us to check for strict mode without looking at
the currently running execution context.
2025-10-29 21:20:10 +01:00
Andreas Kling
3fb678b376 LibGC: Delete operators ! and bool from GC::Ref
The GC::Ref smart pointer is always non-null, so there's no need for it
to be convertible to bool.

This exposed a small number of unnecessary null checks which we remove.
2025-10-29 21:20:10 +01:00
Tim Ledbetter
e4e18ca84b UI/AppKit: Support dead keys on MacOS 2025-10-29 12:38:59 -04:00
InvalidUsernameException
35254d17d1 LibWeb/CSS: Do not crash when parsing some multi-layer mask shorthands
This fixes a silly bug where we would crash when parsing a multi-layer
mask shorthand property that contained the no-clip keyword but no value
for mask-origin.

Fixes a crash when parsing the CSS of https://www.browserbase.com/. The
site still has other, unrelated problems though.
2025-10-28 23:50:46 -07:00
Zaggy1024
418f1575b0 LibWeb: Stop returning the value in HTMLMediaElement::set_current_time
This wasn't actually affecting the result in a script assigning a
variable to the result of an expression assigning to currentTime.
2025-10-29 06:22:48 +00:00
Lorenz A
14dba82202 LibWeb: Allow whitespace in not after a boolean-expr-group 2025-10-28 21:54:48 -07:00
dependabot[bot]
32cfb4e7d8 CI: Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 21:41:38 -07:00
stelar7
964c0ddde9 LibWeb/IDB: Import getAllRecords test 2025-10-28 19:25:26 -04:00
stelar7
8fcba173e0 LibWeb/IDB: Correctly implement IDBRecord getters 2025-10-28 19:25:26 -04:00
ayeteadoe
244f086709 LibRequests: Decode RequestTimingInfo IPC fields as i64's
On 64-bit Windows, long's are only 4 bytes. This meant that when the
client is deocding the IPC message from a async_request_finished call,
we were corrupting the RequestTimingInfo field and all the fields after
it as we were 4 bytes off.
2025-10-28 19:09:42 -04:00
Luke Wilde
d2b2d57387 LibWeb: Set attribute value directly in HTMLScriptElement::set_src
Otherwise we'll try to use the Utf16String that has already been
through Trusted Types and put it through again. That can also fail if
there's no default policy and there's a `require-trusted-types-for
'script'` directive.

Fixes Outlook failing to load.
2025-10-28 11:05:09 -07:00
Luke Wilde
498e59f71d LibWeb: Set width/height of ImageBitmap when deserializing or receiving 2025-10-28 10:05:56 -07:00
Luke Wilde
3ca4ff6037 LibWeb/WebGL: Don't attempt to do texture transforms on an empty texture
Skia will not give us a surface if the width or height is zero.
2025-10-28 10:05:56 -07:00
Tete17
b0e02af040 Tests: Add previously crashing TrustedTypes tests
Some of these fail, but we think it is due to a discrepancy between
what the spec says and what the WPT test.

Ref: https://github.com/w3c/trusted-types/issues/595
2025-10-28 13:46:54 +00:00
Tete17
676171bccd IDLGenerators: Allow set_attribute's to throw exceptions 2025-10-28 13:46:54 +00:00
Timothy Flynn
7f37889ff1 RequestServer: De-duplicate some disk cache requests
We previously had no protection against the same URL being requested
multiple times at the same time. For example, if a URL did not have any
cache entry and became requested twice, we would open two cache writers
concurrently. This would result in both writers piping the response to
disk, and we'd have a corrupt cache file.

We now hold back requests under certain scenarios until existing cache
entries have completed:

* If we are opening a cache entry for reading:
  - If there is an existing reader entry, carry on as normal. We can
    have multiple readers.
  - If there is an existing writer entry, defer the request until it is
    complete.

* If we are opening a cache entry for writing:
  - If there is an existing reader or writer entry, defer the request
    until it is complete.
2025-10-28 11:52:51 +01:00
Timothy Flynn
95d23d02f1 RequestServer: Pass the Request object to disk cache entry factories
This object will be needed in a future commit to store requests awaiting
other requests to finish. Doing this in a separate commit just to make
that commit less noisy.
2025-10-28 11:52:51 +01:00
Timothy Flynn
5384f84550 RequestServer: Create disk cache writers for new requests immediately
We previously waited until we received all response headers before we
would create the cache entry. We now create one immediately, and handle
writing the headers in its own function. This will allow us to know if
a cache entry writer already exists for a given cache key, and thus
prevent creating a second writer at the same time.
2025-10-28 11:52:51 +01:00
Timothy Flynn
d67dc23960 RequestServer: Fix typo in CacheEntry::close_and_destroy_cache_entry 2025-10-28 11:52:51 +01:00
Timothy Flynn
822fcc39de RequestServer: Manage request lifetimes as a simple state machine
We currently manage request lifetime as both an ActiveRequest structure
and a series of lambda callbacks. In an upcoming patch, we will want to
"pause" a request to de-duplicate equivalent requests, such that only
one request goes over the network and saves its response to the disk
cache.

To make that easier to reason about, this adds a Request class to manage
the lifetime of a request via a state machine. We will now be able to
add a "waiting for disk cache" state to stop the request.
2025-10-28 11:52:51 +01:00
Timothy Flynn
6cf22c424e RequestServer: Remove extra verbose disk cache log entry
This isn't particularly useful anymore, and is especially verbose for
large responses.
2025-10-28 11:52:51 +01:00
Timothy Flynn
dc10c28b57 RequestServer: Remove erroneous placeholders from dbgln statements
Apparently Services aren't compiled with ENABLE_COMPILETIME_FORMAT_CHECK
2025-10-28 11:52:51 +01:00
Timothy Flynn
1216a2f952 RequestServer: Move some cURL utilities to their own file
This will allow more easily using these from other files. This also lets
us hide the Windows.h header necessity in a single location, instead of
needing to remember to include it everywhre we would otherwise include
<curl/curl.h>.
2025-10-28 11:52:51 +01:00
Timothy Flynn
7450da5556 RequestServer: Move Resolver (and related structures) to its own file
In an upcoming commit to handle requests as a state machine, we will
need access to Resolver from outside of ConnectionFromClient..
2025-10-28 11:52:51 +01:00
ayeteadoe
997d6ee75a RequestServer: Support UDP default resolver on Windows 2025-10-28 11:52:51 +01:00
stelar7
d3568d9467 LibWeb/IDB: Handle off-by-one error in IDBIndex::getAll 2025-10-28 11:50:01 +01:00
Zaggy1024
e5d5ff952c LibMedia: Remove an unused debug variable from AudioDataProvider
Fixes the ENABLE_ALL_THE_DEBUG_MACROS build.
2025-10-27 23:03:01 -07:00
Adrian Kiezik
823deacc27 LibWeb: Implement CanvasRenderingContext2D direction property
When direction is 'rtl':
  - textAlign='start' now aligns text to the right
  - textAlign='end' now aligns text to the left

Fixes the FIXME at CanvasRenderingContext2D.cpp:283
2025-10-27 22:03:26 -07:00
Lorenz A
ffcd3a4bb2 LibWeb: Count the width of inline-blocks in InlineNodes only once
An BlockContainer inside an InlineNode is called from the
`for each in inclusive_subtree_of_type`  but is also a fragment
 of that InlineNode. Don't count the the Node twice.
2025-10-27 21:45:33 -07:00
Zaggy1024
93fde59892 LibWeb: Make the value of assignment to media currentTime the rhs value
In cases where a script assigns `x = video.currentTime = y`, we are
expected to have a result of `x === y`, even if the video's duration
is less than y.

According to the spec, this happens because the official playback
position is set to `y` in this case, but since we are following
implementations in making `currentTime` immediately return the position
on the valid media timeline, we have to specifically return the
unchanged value from the setter.

See: https://github.com/whatwg/html/issues/11773
2025-10-27 17:28:49 -07:00
Zaggy1024
9e4c87ab85 LibWeb: Ensure that media elements can seek to the duration
Due to the round trip of Duration -> double -> Duration, seeking to the
end of some media can sometimes result in the seek being resolved close
to the end but not quite there. This is a little bit of a hack to make
that work, but may be necessary depending on how the spec changes with
regard to the value returned by currentTime after a seek begins.
2025-10-27 17:28:49 -07:00
Zaggy1024
cb1719aa81 LibWeb: Don't loop the media element when paused
We should be able to seek to the end of the media without looping if
we're paused.

See: https://github.com/whatwg/html/issues/11774
2025-10-27 17:28:49 -07:00
Zaggy1024
4471e8c0ec LibWeb: Consider playback ended when loop is set after ending playback
This allows playback to restart when playing is requested after the end
of playback was reached while loop was disabled, regardless of whether
loop is then subsequently enabled.

This matches other browsers' implementations, but differs from the spec
in how the ended attribute is handled.

See: https://github.com/whatwg/html/issues/11775
2025-10-27 17:28:49 -07:00
Zaggy1024
3be6b957f8 LibWeb: Add the concept of direction of playback in HTMLMediaElement 2025-10-27 17:28:49 -07:00
Zaggy1024
45727d7a58 LibWeb: Fire the media element ended event inside a task
The spec changed in this regard, and this change ensures that once the
ended attribute is updated only during event loop step 1, ended event
handlers will see the ended attribute set to true.
2025-10-27 17:28:49 -07:00
Zaggy1024
412467cc70 LibWeb: Add a hook in EventLoop to call a task when reaching step 1 2025-10-27 17:28:49 -07:00
Zaggy1024
d9e663fc44 Tests: Add a crash test for setting HTMLMediaElement src repeatedly 2025-10-27 17:28:49 -07:00
Zaggy1024
e81fece123 LibWeb: Abort media element resource selection upon loading a source
This fixes a crash when playing video on The Cutting Room Floor.

Without aborting the resource selection algorithm, two resource
selection algorithms could be running at once, resulting in the
element requesting removal of a track from the PlaybackManager
immediately after it had been replaced with a different instance.
PlaybackManager asserts that removal of a track is valid, so this was
causing a WebContent crash.
2025-10-27 17:28:49 -07:00
Zaggy1024
122f97c68d LibWeb: Update media volume when creating a playback manager 2025-10-27 17:28:49 -07:00
Zaggy1024
7745d9209d LibMedia: Sync AudioMixingSink::set_time onto the stream thread
With the previous setup setting the time directly on the main thread,
the following could occur:

- HTMLMediaElement temporarily pauses playback to begin a seek.
- AudioMixingSink starts an audio task to drain audio and suspend.
- HTMLMediaElement starts PlaybackManager seeking to a new position.
- AudioDataProvider completes the seek to the new position.
- The PlaybackManager tells AudioMixingSink to set the media time,
  which it does so synchronously on the main thread.
- At this point, the time provider corresponds to the new position.
- The pause completes, and a deferred invocation sets media time to
  its old position again.

This would result in the timeline showing the wrong position after a
seek on rare occasions.

Instead, always queue up a drain and suspend when setting the sink's
media time. This ensures that updating the time always occurs after the
pause has completed.

Also, since setting the time is asynchronous now, we need to store the
target time until the seeking drain completes. Otherwise, we still
briefly see the previous playback position after a seek.
2025-10-27 17:28:49 -07:00
Zaggy1024
49f088275e LibWeb: Implement HTMLMediaElement looping 2025-10-27 17:28:49 -07:00
Zaggy1024
bf0219d798 LibMedia: Clamp PlaybackManager::current_time() to the duration
This allows LibWeb to detect the end of playback and pause playback or
loop to the start.
2025-10-27 17:28:49 -07:00
Zaggy1024
16b296c090 LibWeb: Update HTMLMediaElements' playback positions when invisible 2025-10-27 17:28:49 -07:00
Zaggy1024
c75f134eec LibWeb: Disconnect all sinks when forgetting HTMLMediaElement tracks
This prevents a crash when playing videos on Newgrounds.
2025-10-27 17:28:49 -07:00
Zaggy1024
9f44fcbded Everywhere: Remove AudioCodecPlugin and Qt Multimedia
These are no longer needed now that audio is played through
PlaybackManager.
2025-10-27 17:28:49 -07:00
Zaggy1024
d17e7fd921 LibMedia+Tests: Adapt TestVorbisDecode to use AudioDataProvider 2025-10-27 17:28:49 -07:00
Zaggy1024
4cf0a77d38 LibMedia: Export FFmpegDemuxer to allow direct use of data providers
We can use forward declarations of the FFmpeg types within the header
to allow it to be exported without causing errors in using code.
2025-10-27 17:28:49 -07:00
Zaggy1024
fcde0f66c8 LibMedia: Allow FFmpegDemuxer to grab samples from multiple streams
In order to do so, we create a AVFormatContext for each stream we want
to demux.
2025-10-27 17:28:49 -07:00
Zaggy1024
9c960c3307 LibMedia: Use integer math to calculate the FFmpeg seek target pts 2025-10-27 17:28:49 -07:00
Zaggy1024
e9495d0ba0 LibWeb: Implement media element seeking through PlaybackManager
Including the behavior to conditionally seek forward when fast seeking!
2025-10-27 17:28:49 -07:00
Zaggy1024
ccf4b3f6e9 LibMedia: Implement media seeking
This implementation allows:
- Accurate seeking to an exact timestamp
- Seeking to the keyframe before a timestamp
- Seeking to the keyframe after a timestamp
These three options will be used to satisfy the playback position
selection in the media element's seeking steps.
2025-10-27 17:28:49 -07:00
Zaggy1024
b1c9a872bc LibMedia: Return whether a demuxer seek moved the stream position
We no longer need to return a timestamp from the seek function, which
makes it much easier to implement backend-agnostically.
2025-10-27 17:28:49 -07:00
Zaggy1024
27ed536540 LibMedia: Give PlaybackManager a playback state getter 2025-10-27 17:28:49 -07:00
Zaggy1024
9117f661a8 LibMedia: Split some repeated code in VideoDataProvider to functions 2025-10-27 17:28:49 -07:00
Zaggy1024
e5a6b76a40 LibMedia: When reaching EOS, clear Matroska iterators' last timestamp
This ensures that if we're at EOS, we never skip a seek, so that
seeking to the end of a video always gets a frame.
2025-10-27 17:28:49 -07:00
Zaggy1024
86e236519d LibMedia: Give demuxer seeks an option to always seek to a keyframe
By default, MatroskaDemuxer chooses not to seek if the current frame
is closer to the seek target than the keyframe that precedes the seek
target. However, it can be desirable to seek to a keyframe anyway, so
let's allow that.
2025-10-27 17:28:49 -07:00
Zaggy1024
e94ab24e66 LibMedia: Don't assert when MatroskaDemuxer seeks near the start
Reader::seek_to_random_access_point() isn't actually guaranteed to
return a sample iterator that has already gotten a block timestamp.
This verify passes in almost every case, but if we happen to seek to a
timestamp before the second keyframe, we'd crash.
2025-10-27 17:28:49 -07:00
Zaggy1024
0a03cc1cf7 LibMedia: Store whether a CodedFrame is a keyframe 2025-10-27 17:28:49 -07:00
Zaggy1024
d52ceec1bf LibMedia: Take Track const& in Demuxer methods 2025-10-27 17:28:49 -07:00
Zaggy1024
aa8c28eb24 AK+Tests: Allow creating a Duration from 64-bit floating point seconds 2025-10-27 17:28:49 -07:00
Zaggy1024
d3941cd83d LibMedia: Support playing FLAC 2025-10-27 17:28:49 -07:00
Zaggy1024
6ff7e4bfac LibMedia: Implement basic playing/paused playback state handlers 2025-10-27 17:28:49 -07:00
Zaggy1024
8d9a493b1b LibMedia+LibWeb: Implement media volume/muting 2025-10-27 17:28:49 -07:00
Zaggy1024
e176249db8 LibMedia: Use AudioMixingSink as a time provider when it is available 2025-10-27 17:28:49 -07:00
Zaggy1024
ee587cfec4 LibMedia+LibWeb: Implement media pausing/resuming 2025-10-27 17:28:49 -07:00
Zaggy1024
3ebaa0cd3f LibMedia: Implement a generic MediaTimeProvider for video-only timing
This time provider can later be swapped out for the AudioMixingSink
when it implements the MediaTimeProvider interface, so that frame
timing can be driven by audio when it is present.
2025-10-27 17:28:49 -07:00
Zaggy1024
3d0b8cc30c LibWeb: Set AudioTrack and VideoTrack fields according to spec
The two classes now inherit from a common base MediaTrackBase, to
deduplicate the attributes that are shared between the two.

The integer ID from the container is used for each track's id
attribute.

The kind attribute is set to "main" or "translation" according to:
https://dev.w3.org/html5/html-sourcing-inband-tracks/

The label attribute is set to the human-readable name of the track, if
one is present.

The language attribute is set to a BCP 47 language tag, if one can be
parsed successfully.
2025-10-27 17:28:49 -07:00
Zaggy1024
29ab9c5fd5 LibMedia: Let the Matroska LanguageBCP47 element override Language
The Matroska spec indicates that this field should always override the
Language element when it is present.
2025-10-27 17:28:49 -07:00
Zaggy1024
e11da1f85f LibMedia: Store a name and language in Media::Track 2025-10-27 17:28:49 -07:00
Zaggy1024
e8238b4098 LibMedia: Replace FlyString with String for Matroska track attributes
The values stored by these are either going to be fairly unique or too
short to benefit from FlyString.
2025-10-27 17:28:49 -07:00
Zaggy1024
5456072d48 LibWeb: Implement audio media data processing through PlaybackManager 2025-10-27 17:28:49 -07:00
Zaggy1024
dfe59b8a4f LibMedia+LibWeb: Prefer MatroskaDemuxer for media playback
MatroskaDemuxer supports multiple streams already, and gives us a bit
more control over seeking.
2025-10-27 17:28:49 -07:00
Zaggy1024
0ff330c906 LibMedia: Play audio through PlaybackManager using Providers/Sinks
This commit implements the functionality to play back audio through
PlaybackManager.

To decode the audio data, AudioDataProviders are created for each track
in the provided media data. These providers will fill their audio block
queue, then sit idle until their corresponding tracks are enabled.

In order to output the audio, one AudioMixingSink is created which
manages a PlaybackStream which requests audio blocks from multiple
AudioDataProviders and mixes them into one buffer with sample-perfect
precision.
2025-10-27 17:28:49 -07:00
Zaggy1024
dd052832c1 LibMedia: Support the MP3 and AAC codecs in our demuxer 2025-10-27 17:28:49 -07:00
Zaggy1024
6b34003c2c LibMedia: Support coded audio frames in our demuxers
This adds a new variant of the metadata storage in CodedFrame for audio
frames, called CodedAudioFrameData.
2025-10-27 17:28:49 -07:00
Zaggy1024
6caa2f99aa LibMedia+LibWeb: Rewrite PlaybackManager using the provider/sink model
With this commit, all PlaybackManager can do is autoplay a file from
start to finish, with no pausing or seeking functionality.

All audio playback functionality has been removed from HTMLMediaElement
and HTMLAudioElement in anticipation of PlaybackManager taking that
over, for both audio-only and audio/video.
2025-10-27 17:28:49 -07:00
Zaggy1024
0f9fa47352 LibWeb: Update media elements' page mute state via a for_each method 2025-10-27 17:28:49 -07:00
Zaggy1024
5e645929a7 LibWeb: Stop moving a GC::Ref in media elements' create_layout_node()s
The type is trivial, so this was causing a warning.
2025-10-27 17:28:49 -07:00
Zaggy1024
31b72c4799 LibMedia: Make Track::type() const 2025-10-27 17:28:49 -07:00
Zaggy1024
7e238cd724 LibMedia: Add separate classes managing decoding and displaying video
These are unused in this commit, but will later be used to output video
via PlaybackManager, or to decode video directly to some consumer.
2025-10-27 17:28:49 -07:00
Zaggy1024
dfbad09315 LibMedia: Set the time base in FFmpegVideoDecoder
Very minor change, which doesn't actually affect our output, since we
were already inputting and outputting microseconds, but it can't hurt
to give FFmpeg's decoder this information as well.
2025-10-27 17:28:49 -07:00
Zaggy1024
523e7e2ffa LibMedia: Make Demuxer atomically ref-counted
We'll need to share the demuxer between multiple decoder providers, and
those will hold references to the demuxer from their own decoder
threads.
2025-10-27 17:28:49 -07:00
Zaggy1024
27742ef26d LibMedia+LibWeb: Never return errors when getting PlaybackStream time
We should be fine to just fall back to zero or the last returned value
if we encounter an error in PlaybackStream::total_time_played(), and
this also simplifies the using code.
2025-10-27 17:28:49 -07:00
Tim Ledbetter
9da723b5c6 LibWeb: Ensure layout is up to date before resolving canvas colors 2025-10-27 16:56:01 -07:00
Jelle Raaijmakers
4dbae64dce LibWeb: Unify objectBoundingBox and userSpaceOnUse coord transformations
There's a fairly complicated interaction between an SVG gradient's paint
transformation and the gradient coordinate transformation required to
correctly draw gradient fills. This was especially noticeable when
scaling down an SVG, resulting in broken gradient coordinates and
graphical glitches.

This changes the objectBoundingBox units to immediately map to the
bounding box's coordinate system, so we can unify the gradient paint
transformation logic and make it a lot simpler. We only need to undo the
bounding box offset and apply the paint transformation to fix a lot of
gradient fill bugs.
2025-10-27 16:42:27 -07:00
Jelle Raaijmakers
beb1d60714 LibWeb: Simplify calculation of the SVG viewbox transform
We don't need to construct a new AffineTransform and multiply that by
yet another AffineTransform; we can simply translate and scale the
transform that's already in place. No functional changes.
2025-10-27 16:42:27 -07:00
Jelle Raaijmakers
6f50c35d68 LibWeb: Misc. cleanup of code
Some things I came across while working on a bugfix. No functional
changes.
2025-10-27 16:42:27 -07:00
Jelle Raaijmakers
e4de6c0d05 LibWeb: Return early if scroll offset is zero
No need to calculate scroll offsets and translate update list commands
if the cumulative offset was zero to begin with.
2025-10-27 16:42:27 -07:00
Jelle Raaijmakers
c0d08b68af LibWeb: Dump path_bounding_rect for FillPath
This makes it easier to differentiate between FillPath commands in the
display list output.
2025-10-27 16:42:27 -07:00
Tim Ledbetter
e1ff1e2095 LibWeb: Implement CanvasPattern.setTransform()
This method applies the given transformation matrix to a pattern.
2025-10-27 16:41:02 -07:00
Andreas Kling
9312a9f86f LibJS: Move InstantiateOrdinaryFunctionExpression into interpreter
This is execution time stuff and doesn't belong in the AST.
2025-10-27 21:14:33 +01:00
Andreas Kling
44fa9566a8 LibJS: Generate bytecode for the BlockDeclarationInstantiation AO
This necessitated adding some new instructions for creating mutable and
immutable bindings.
2025-10-27 21:14:33 +01:00
Andreas Kling
892c7d980e LibJS: Let JS::Script remember whether its code is strict mode
We don't want to rely on having the AST node just to answer the question
"is this script strict mode?"
2025-10-27 21:14:33 +01:00
Andreas Kling
b712caf855 LibJS: Move bytecode executable cache to SharedFunctionInstanceData
This shrinks every Statement and ECMAScriptFunctionObject by one
pointer, and puts the bytecode cache in the only place that actually
makes use of it anyway: functions.
2025-10-27 21:14:33 +01:00
Andreas Kling
3a38040c82 LibJS: Make SharedFunctionInstanceData GC-allocated 2025-10-27 21:14:33 +01:00
Tete17
b77f658c83 LibWeb: Add a bunch of new passing WPT tests 2025-10-27 16:14:20 +00:00
Tete17
c591f8c14f LibWeb: Amend DomParser to make it compatible with TrustedTypes 2025-10-27 16:14:20 +00:00
Tete17
33285467a8 LibWeb: Amend ShadowRoot to make it compatible with TrustedTypes 2025-10-27 16:14:20 +00:00
Tete17
db41ea8117 LibWeb: Amend Element interface to make it compatible with TrustedTypes 2025-10-27 16:14:20 +00:00
Tete17
1368744d33 LibWeb: Amend Document interface to make it compatible with TrustedTypes 2025-10-27 16:14:20 +00:00
Tete17
2fa84f1683 LibWeb: Properly propagate errors for Node set_text_content
This function was supposed to throw errors even before the TrustedTypes
spec thanks to the CharacterData replaceData call but had a MUST.

This changes this to ensure this function can throw an error
2025-10-27 16:14:20 +00:00
Tete17
887537b061 LibWeb: Implement TrustedTypes spec for set_value on Attribute 2025-10-27 16:14:20 +00:00
Tete17
e2adce84e7 LibWeb: Implement TrustedTypes spec for the concept of set_attribute_ns 2025-10-27 16:14:20 +00:00
Tete17
4b00a61479 LibWeb: Implement TrustedTypes spec for the concept of set_attribute 2025-10-27 16:14:20 +00:00
Tete17
df543cf31a LibWeb: Implement TrustedTypes spec for set_attribute on Element
This part of the spec is still under a PR in GitHub, but it should be
safe to implement like it is.
2025-10-27 16:14:20 +00:00
Callum Law
5381146e85 LibWeb: Include PropertyID.h in fewer header files
This reduces the size of the recompile when PropertyID.h is modified
from ~1500 to ~125
2025-10-27 14:50:54 +00:00
Callum Law
59a1184469 LibWeb: Remove custom hash function for PropertyID
The default hash function for enums does the same thing
2025-10-27 14:50:54 +00:00
Callum Law
12716dccf0 LibWeb: Avoid including ComputedProperties.h in Element.h
This reduces the size of the recompile when ComputedProperties.h is
modified from ~1200 to ~70
2025-10-27 14:50:54 +00:00
Callum Law
64f438857b LibWeb: Factor out some class template methods into implementation files
Reduces the rebuild required for changes to the .cpp parts (or their
includes)
2025-10-27 14:50:54 +00:00
Timothy Flynn
2a68087dfc LibWeb: Do not assume hovered URLs are valid
Let's just not display any tooltip for invalid URLs. This matches how
Firefox behaves.
2025-10-27 14:15:03 +00:00
Rocco Corsi
73b01a975a CI: Add All_Debug CI job 2025-10-27 08:46:02 -04:00
Callum Law
76dadd45d6 LibWeb: Favour !important property values over animated values 2025-10-27 09:51:50 +00:00
Callum Law
c8f345356e LibWeb: Maintain property importance into ComputedProperties
Previously we wouldn't propagate this from CascadedProperties to
ComputedProperties
2025-10-27 09:51:50 +00:00
Callum Law
823dd11b67 LibWeb: Add ComputedProperties::set_property_without_modifying_flags
There are a few places in style computation where we want to update the
value of a property without modifying the inherited or important flags.

Previously we would look up these flags and pass them to the normal
`set_property` method but this is unnecessary and was easy to forget to
do.
2025-10-27 09:51:50 +00:00
Callum Law
84762021b8 LibWeb: Support triggering multiple animations per animation property
We also now use the computed (rather than cascaded) values when
triggering animations.
2025-10-27 09:48:25 +00:00
Callum Law
18477b0d84 LibWeb: Promote animation-composition values to enum
This brings us in line with the other `animation-*` enum properties
(`animation-play-state` and `animation-fill-mode`)
2025-10-27 09:48:25 +00:00
Tim Ledbetter
8854bb62c6 LibGfx: Use CSS sRGB serialization for canvas fill styles 2025-10-26 18:55:22 +01:00
Tim Ledbetter
ebd802f6fc LibWeb+LibGfx: Move CSS sRGB color serialization function to LibGfx 2025-10-26 18:55:22 +01:00
Rocco Corsi
633fc45e0f LibWeb: Make Worker.cpp compile again with WEB_WORKER_DEBUG enabled 2025-10-26 12:47:06 -04:00
Dave-London
5f963e1c52 Documentation: Remove de-listed VSCode extension reference
The "SerenityOS DSL Syntax Highlight" extension has been de-listed
from both the VS Code Marketplace and Open VSX. Remove the broken
section from the documentation to prevent confusion.
2025-10-26 12:44:44 -04:00
Tim Ledbetter
9dceb06992 LibWeb: Don't paint canvas objects with non-visible fill styles 2025-10-26 16:45:38 +01:00
Ben Eidson
01947ded23 LibWeb/WebAudio: Implement basic startRendering
Adds passing WPT. Does not handle actually rendering audio yet.
2025-10-26 14:19:21 +01:00
ayeteadoe
5abb5d555a LibWeb: Implement createImageBitmap() with an ImageBitmap source 2025-10-26 09:01:20 +01:00
Luke Wilde
d733bf54cc LibWeb/WebGL: Avoid copying canvas surface when uploading it 2025-10-25 12:56:17 +02:00
Luke Wilde
d65f0e4490 LibWeb/WebGL: Calculate the correct pitch for compacted types
It's not correct to multiply the number of components by the number of
bytes, since compact formats such as 4_4_4_4 have 4 components but
_always_ stored in 2 bytes, not 8 if you were to do such a
multiplication.

Fixes textures on Google Maps having large blank stripes.
2025-10-25 12:56:17 +02:00
Luke Wilde
3e7061da40 LibWeb/WebGL: Respect UNPACK_FLIP_Y_WEBGL pixel storage parameter
When this is true, we have to vertically flip TexImageSource provided
images before uploading them.

Fixes several graphical glitches on Google Maps.
Fixes globe being upside down on Shopify's homepage.
Likely fixes more websites.
2025-10-25 12:56:17 +02:00
Luke Wilde
008699c129 LibWeb/WebGL: Return null from getUniformLocation if uniform isn't found 2025-10-25 12:56:17 +02:00
aplefull
5632a52531 LibRegex: Properly track code units in u-v modes
Previously, both string_position and view_index used code unit offsets
regardless of mode. Now in unicode mode, these variables track code
point positions while string_position_in_code_units is properly
updated to reflect code unit offsets.
2025-10-24 21:23:06 +02:00
Lorenz A
fb258639d1 LibWeb: CSS selector read-write honor is_allowed_to_be_readonly 2025-10-24 19:15:58 +01:00
aplefull
cd4ac4f30f LibJS: Escape line terminators in regex source 2025-10-24 13:24:55 -04:00
aplefull
7ce4abe330 LibRegex+LibUnicode: Add unicode string properties 2025-10-24 13:24:55 -04:00
Junior Rantila
8c8961171c UI/AppKit: Make project buildable on macOS < 15
When trying to build Ladybird on macOS 14.3, it fails with the error:

```

No visible @interface for 'NSToolbar' declares the selector
'setAllowsDisplayModeCustomization:' (clang arc_may_not_respond)

```

This is caused by macOS < 15 not having the @interface definition for
in NSToolbar for setAllowsUserCustomization:(BOOL).

By dynamically calling the method, we can avoid the error altogether.
2025-10-24 12:41:42 -04:00
Aliaksandr Kalenik
6c71960425 LibWebView: Add common source loop source for deferred_invoke() on macOS
Previously, `EventLoopImplementationMacOS` didn't process
`deferred_invoke()` callbacks during window resizing because they were
scheduled in the "default" run-loop mode, which isn't serviced while the
run loop is in "Event Tracking" mode.

Commit 3bbe1b0c changed socket notifiers to schedule in
`kCFRunLoopCommonModes`, which kept IPC reads responsive during resizing
(we process IPC messages in the notifier's read hook). This change does
the same for `deferred_invoke()`: it installs a `CFRunLoopSource` in
`kCFRunLoopCommonModes` and signals it whenever a deferred callback is
enqueued.
2025-10-24 16:04:42 +02:00
Luke Wilde
d4deafe5fe LibJS: Track current shape dictionary generation in PropertyLookupCache
When an object becomes too big (currently 64 properties or more), we
change its shape to a dictionary and don't do any further transitions.

However, this means the Shape of the object no longer changes, so the
cache invalidation check of `current_shape != cache.shape` is no longer
a valid check.

This fixes that by keeping track of a generation number for the Shape
both on the Shape object and in the cache, allowing that to be checked
instead of the Shape identity. The generation is incremented whenever
the dictionary is mutated.

Fixes stale cache lookups on Gmail preventing emails from being
displayed.

I was not able to produce a reproduction for this, plus the generation
count was over the 20k mark on Gmail.
2025-10-24 15:35:04 +02:00
Luke Wilde
a21d247b0e LibJS: Actually mark uncacheable dictionary shapes as uncacheable 2025-10-24 15:35:04 +02:00
Jelle Raaijmakers
ba78e3d4be CI+Meta: Synchronize commit linting rules
Our CI commit linter now evaluates commit messages line by line, similar
to `Meta/lint-commit.sh`.

The URL rule was updated to allow any freestanding URL, optionally
prefixed by whitespace.
2025-10-24 13:09:46 +02:00
Jelle Raaijmakers
e3a56ef913 CI: Move commit message line rule down in list
This synchronizes the order of rules between `Meta/lint-commit.sh` and
this workflow. No functional changes.
2025-10-24 13:09:46 +02:00
Jelle Raaijmakers
e197ab8ff6 CI: Ignore CR in commit message linting patterns
We already check if CR is present in the first rule, and although it
would be nice to see all errors at once, we expect people to run
`Meta/lint-commit.sh` before pushing as well. This simplifies our
patterns a bit.
2025-10-24 13:09:46 +02:00
Jelle Raaijmakers
86725de23d LibGfx: Use correct include path for SkImage.h
Fixes our flatpak builds.
2025-10-24 12:44:24 +02:00
Tim Ledbetter
5294559f7b LibWeb: Use logical properties in UA stylesheet 2025-10-24 10:11:49 +01:00
Andreas Kling
a056b26e56 LibWeb: Don't create identical StyleValueList if absolutization is no-op
This case is actually incredibly common, and this ends up reducing the
memory footprint on https://gymgrossisten.com/ by 2.07 MiB.
2025-10-24 08:52:53 +02:00
Andreas Kling
4f684bb4c9 LibGfx: Don't create AnonymousBuffer for each bitmap in BitmapSequence
When decoding a BitmapSequence received over IPC, we were creating an
AnonymousBuffer for each bitmap and then making a Gfx::Bitmap wrapper
around it.

This was unnecessarily using up one file descriptor per bitmap, and also
wasting a lot of memory for small bitmaps since we always allocated at
least one VM page.

This patch changes the BitmapSequence decoder to use malloc memory
instead, saving file descriptors and using less memory overall.
2025-10-24 08:52:53 +02:00
Andreas Kling
3593c3b687 LibWeb: Throw out decoded UTF-32 data in HTMLTokenizer after parser runs
This ends up saving quite a bit of memory on many pages, since UTF-32
uses 4 bytes per code points.

As an example, it reduces the footprint on https://gymgrossisten.com/
by 2 MiB.
2025-10-24 08:52:53 +02:00
Andreas Kling
b10f2993b3 LibWeb: Use Optional<ssize_t> for the HTMLTokenizer insertion points
Instead of making a custom struct, we can just use Optional here,
to make the code feel a bit more idiomatic.
2025-10-24 08:52:53 +02:00
Tim Ledbetter
9df6ccf772 LibWeb: Add MathML doctypes that enable XHTML entities 2025-10-23 22:08:40 +02:00
Tim Ledbetter
1c00279488 LibWeb: Reset Painter when resetting canvas to its initial state 2025-10-23 18:52:36 +02:00
Tim Ledbetter
017e8a5b8d LibGfx: Add a method to reset a Painter to its initial state 2025-10-23 18:52:36 +02:00
Andreas Kling
dbf041aa98 LibWeb: Ignore repaint requests inside iframes with visibility: hidden
This reduces idle CPU usage on https://gymgrossisten.com/ from 100%
(split between WebContent and Ladybird) to ~4% on my machine.
2025-10-23 17:42:37 +02:00
Tim Ledbetter
f34361950c Tests: Rebaseline failing IndexedDB tests 2025-10-23 15:55:17 +02:00
Tim Ledbetter
49a46522d0 Meta: Use binary search instead of a Trie for public suffix list lookups 2025-10-23 15:01:13 +02:00
Tim Ledbetter
15518f119c Tests: Add some basic public suffix tests 2025-10-23 15:01:13 +02:00
Luke Wilde
85e8d2ba38 LibWeb/IndexedDB: Prevent copying and moving of RequestList
RequestList cannot be copied or moved, because m_pending_request_queue
contains lambdas that store pointers to the original RequestList and
completion steps that we don't have a reference to.

Fixes a bunch of WPT regressions and imports the ones that work.
2025-10-23 14:42:45 +02:00
Luke Wilde
4ede2cdf18 LibWebView+WebContent: Allow setting the default time zone
This is used by tests to set the default time zone to UTC.

This is because certain tests create JavaScript Date objects, which are
in the current timezone.
2025-10-23 14:42:45 +02:00
stelar7
d0bfb85c22 LibWeb/IDB: Mark request as errored if upgrade transaction is aborted 2025-10-23 14:26:54 +02:00
Jelle Raaijmakers
62ae4e878f LibWeb: Implement support for drawing with CanvasPattern
We already had the API, but drawing to the canvas was not affected by
any created CanvasPattern. This moves CanvasPatternPaintStyle to LibGfx
so we don't have to reach into LibWeb, and implements the plumbing to
let Skia use images as a fill pattern.
2025-10-23 13:20:03 +01:00
Jelle Raaijmakers
9753b8e62c Tests: Import WPT canvas repeating pattern test 2025-10-23 13:20:03 +01:00
Pavel Shliak
5ee1031b89 LibWeb: Implement SVGFEColorMatrixElement and feColorMatrix 2025-10-23 13:36:26 +02:00
ste
b50b89b4a8 Meta: Do not set -march=native on riscv64 2025-10-23 12:17:21 +02:00
ste
b28a97b399 Meta: Add build support for RISC-V (riscv64)
Co-authored-by: Bas M. <bas@opvolger.net>
2025-10-23 12:17:21 +02:00
mikiubo
5b2a71a712 LibWeb: Implement XMLFragmentParser
Implement XMLFragmentParser based on the specification:
https://html.spec.whatwg.org/multipage/xhtml.html

Fixes one WPT in:
domparsing/insert_adjacent_html-xhtml.xhtml
2025-10-23 11:06:39 +01:00
mikiubo
0b715b20a2 LibWeb: Make HTML fragment parsing return ExceptionOr
Update Element::parse_fragment and Node::unsafely_set_html to
propagate exceptions.

This refactor is needed as a prerequisite for implementing the XML
fragment parser, which requires consistent error handling in fragment
parsing.
2025-10-23 11:06:39 +01:00
Callum Law
8417d74328 LibWeb: Parse transition property as a coordinating list shorthand
We don't need all this specific logic for parsing the `transition`
property - we also now maintain `none` as such until use time which
gains us a couple extra tests
2025-10-23 10:09:11 +01:00
Callum Law
94c788f2e0 LibWeb: Make animation parsing logic reusable
This is useful for parsing of other coordinating list shorthands
2025-10-23 10:09:11 +01:00
Callum Law
bb7d5747e7 LibWeb: Serialize transition as a coordinating list shorthand 2025-10-23 10:09:11 +01:00
Callum Law
2e6988d681 LibWeb: Make logic for serializing coordinating list shorthand reusable
Previously this was just used for `animation` serialization but can be
used for other properties (e.g. transition, background) as well
2025-10-23 10:09:11 +01:00
Callum Law
fc5cdd69a0 LibWeb: Extract transition coordination logic to reusable method
This will be useful for other coordinating list property groups (e.g.
background and animation)
2025-10-23 10:09:11 +01:00
Callum Law
e78cb71eb3 LibWeb: Dont skip invalid properties when coordinating transition list
Unrecognized property names should still be kept in the list to preserve
matching of indices (e.g. that the Nth property should associate with
the Nth duration)
2025-10-23 10:09:11 +01:00
Callum Law
fd2f3b1f03 Tests: Import transition parsing tests 2025-10-23 10:09:11 +01:00
Callum Law
0b45a68423 LibWeb: Avoid early conversion to CSSPixels when simplifying calculation
Converting to CSSPixels caused us to lose precision and the sign of
signed zeroes.

The values we resolve against in Length::ResolutionContext are still
themselves rounded too early but this is in the right direction.
2025-10-23 09:34:12 +01:00
Callum Law
43b06cbbdd LibWeb: Propagate NaN through CSS sign() function 2025-10-23 09:34:12 +01:00
Callum Law
d32f99b16f LibWeb: Ensure all passed arguments are used when parsing math functions
This disallows things such as `log(1, foobar)` where we would previously
just skip over `foobar` and parse this as `log(1)`
2025-10-23 09:34:12 +01:00
Callum Law
1977a976da LibWeb: Handle inaccuracies resolving transformation matrix type
Doing trigonometric calculations with floating point numbers can
introduce small inaccuracies. This meant that we would sometimes
incorrectly generate a 3d rather than 2d matrix for the resolved value
of `transform`.

Gains us 3 WPT tests.
2025-10-23 09:34:12 +01:00
Callum Law
2d331b9176 LibWeb: Don't swap atan2 arguments in with_simplified_children 2025-10-23 09:34:12 +01:00
Zaggy1024
670cbccb4c Tests: Enable ThreadedPromise tests on Windows 2025-10-22 17:32:45 -05:00
Zaggy1024
3c9ddc9b7f Tests: Add a test for EventLoop::pump(PollForEvents) 2025-10-22 17:32:45 -05:00
Zaggy1024
e0a75d5084 LibCore: Implement polling for events on Windows 2025-10-22 17:32:45 -05:00
Tim Ledbetter
2fd424ccb6 LibWeb: Draw canvas arcs and ellipses correctly when radius is zero
In this case, we should just draw a line from the last point in the
path to the start point. Previously, a division by zero caused nothing
to be drawn.
2025-10-22 16:08:57 +02:00
Undefine
865699066e Documentation: Add libdrm to FreeBSD packages 2025-10-22 13:54:54 +02:00
Undefine
40bc1c0eb5 LibGfx: Enable Vulkan shared images and WebGL on FreeBSD 2025-10-22 13:54:54 +02:00
Undefine
bfa86f7961 LibGfx: Use PkgConfig to find libdrm
On FreeBSD the libdrm headers are not in a standard location so this
makes the include more portable.
2025-10-22 13:54:54 +02:00
Undefine
abe536652f Meta+LibCore: Disable --no-undefined on FreeBSD and remove LibCore hack
It turns out it's much cleaner to just allow undefined symbols like we
already do for sanitizer Clang and remove the weak symbol definition.
2025-10-22 13:54:54 +02:00
Undefine
bd8c6c1431 Meta: Add the dbus feature to qtbase
D-Bus is needed for Qt to build its accessibility support and since we
build D-Bus regardless it doesn't add any overhead.
2025-10-22 13:54:54 +02:00
Undefine
5fd0586125 Meta: Build dbus on FreeBSD 2025-10-22 13:54:54 +02:00
Tim Ledbetter
976912f3e9 LibWeb: Normalize negative drawImage() source/destination dimensions 2025-10-22 12:29:04 +02:00
caztanj
e93f44112d LibJS: Do not verify cycle root's status is linked in CyclicModule
This VERIFY is both incorrect and redundant. The VERIFY at step 2
verifies the status when evaluate is called on m_cycle_root.
2025-10-22 11:54:56 +02:00
Feng Yu
61c36e2865 LibJS: Sync additional Import Attributes spec changes
Some steps were not updated with tc39/ecma262#3057. This patch
syncs the remaining changes.
2025-10-22 10:58:19 +02:00
Jelle Raaijmakers
f8c4043460 LibWeb: Repeat shader for repeating linear gradient
We implemented repeating linear gradients by expanding a vector of color
stops until the entire range was covered. This is both a bit wasteful
and caused Skia to draw corrupted gradients to screen whenever the total
amount of color stops and positions exceeded 127.

Instead of doing that, use the original color stops for the shader and
repeat it instead of clamping it. We need to do a bit of math to project
positions correctly, but after that the shader repeats itself nicely.

While we're here, calculate the gradient's length and the center point
as floats instead of ints, yielding a slight but noticeable improvement
in gradient rendering (see the diff on the zig zag pattern in
css-gradients.html for an example of this).
2025-10-22 10:45:18 +02:00
Jelle Raaijmakers
8af6da64a6 Tests: Rework CSS gradients test layout
Put the CSS gradients in a grid instead of a single long column. This
makes it much easier to detect changes / view diffs.
2025-10-22 10:45:18 +02:00
Jelle Raaijmakers
e6ddc995a7 LibWeb: Remove unused include from GradientData.h 2025-10-22 10:45:18 +02:00
Jelle Raaijmakers
868c29545a LibWeb: Skip one single positive linear gradient color stop repeat
We prime `color_stop_list_with_expanded_repeat` with the input
`color_stop_list`, so we don't actually need to copy the stops for the
first iteration. This didn't result in graphical glitches, but these
entries were unnecessary irregardless.
2025-10-22 10:45:18 +02:00
Tim Ledbetter
c4e56cc845 LibWeb: Throw error when calling drawImage() with a broken image 2025-10-22 10:44:58 +02:00
Lorenz A
96b34ea744 LibWeb: Add MathML Element presentational hints 2025-10-22 01:46:41 +02:00
Tim Ledbetter
f1571c4217 LibWeb: Ensure drawImage() always uses the first image frame 2025-10-22 01:25:46 +02:00
Jelle Raaijmakers
2c78fd5b89 AK: Remove unused Checked<T> code
We could never hit the #else branches for some of these methods, because
we already relied on having __builtin_*_overflow() readily available in
earlier methods.

multiplication_would_overflow() with three arguments was only used in a
test, so let's get rid of that as well.
2025-10-22 00:26:23 +02:00
Callum Law
f49cf75d44 LibWeb: Don't pass unnecessary PropertyComputationDependencies struct
Since we now have access to the `AbstractElement` through the
`ComputationContext` we can just set the flag that this element relies
on tree counting functions directly, no need to pass this struct around.
2025-10-22 00:01:30 +02:00
Callum Law
5b9a36b172 LibWeb: Pass AbstractElement in ComputationContext
Passing the `AbstractElement` rather than the
`TreeCountingFunctionResolutionContext` allows us to only compute the
resolution context when necessary (i.e. when we actually need to resolve
a tree counting function)
2025-10-22 00:01:30 +02:00
Callum Law
a4184fda1f LibWeb: Avoid iterating in children_changed unless necessary
Previously when one child of an element changed we would iterate over
every child to check whether they needed to be invalidated because they
relied on tree counting functions.

We now skip this in most cases by only doing it when at least one child
relies on tree counting functions.
2025-10-22 00:01:30 +02:00
Lorenz A
6afd39b16a LibWeb: Keep the tokens in ListOfActiveFormattingElements 2025-10-21 23:36:07 +02:00
Luke Wilde
b8bbebd3ff LibWeb/WebGL: Upload blank image in texImage2D if pixels is null 2025-10-21 23:29:50 +02:00
Luke Wilde
4ebe43af58 LibWeb/WebGL2: Implement most of the transform feedback APIs 2025-10-21 23:29:50 +02:00
Luke Wilde
39d42b7b73 LibWeb/WebGL2: Implement waitSync 2025-10-21 23:29:50 +02:00
Luke Wilde
3005cc30b4 LibWeb/WebGL2: Check if WebGLSync object belongs to the current context 2025-10-21 23:29:50 +02:00
Luke Wilde
3d2874bc4e LibWeb/WebGL2: Implement invalidateSubFramebuffer 2025-10-21 23:29:50 +02:00
Luke Wilde
b949c8ea47 LibWeb/WebGL2: Implement framebufferTextureLayer 2025-10-21 23:29:50 +02:00
Luke Wilde
5c1bf5c3f6 LibWeb/WebGL2: Implement most of the query APIs 2025-10-21 23:29:50 +02:00
Luke Wilde
2b941731a7 LibWeb/WebGL2: Implement compressedTex(Sub)Image3D 2025-10-21 23:29:50 +02:00
Luke Wilde
8dcbe69eb6 LibWeb/WebGL2: Implement drawRangeElements 2025-10-21 23:29:50 +02:00
Luke Wilde
2c13a2a68c LibWeb/WebGL2: Implement vertexAttribI4(u)i(v) 2025-10-21 23:29:50 +02:00
Luke Wilde
35763ffe53 LibWeb/WebGL2: Implement uniform{1,2,3,4}uiv 2025-10-21 23:29:50 +02:00
Luke Wilde
59bea36a59 LibWeb/WebGL2: Implement remaining uniformMatrix methods 2025-10-21 23:29:50 +02:00
Tim Ledbetter
7db73118e9 LibWeb+LibGfx: Draw shadows for stroke joins and caps 2025-10-21 18:55:08 +02:00
Tim Ledbetter
0f295e8989 LibWeb: Take transforms into account when drawing shadows 2025-10-21 18:55:08 +02:00
Tim Ledbetter
0516c414d4 LibWeb: Don't draw shadows for transparent gradient fills 2025-10-21 18:55:08 +02:00
Tim Ledbetter
13f551612c LibWeb: Don't draw shadows if shadow offset and blur are not set 2025-10-21 18:55:08 +02:00
Tim Ledbetter
eb44cca5bd LibWeb: Ignore non-finite shadow offset values 2025-10-21 18:55:08 +02:00
Tim Ledbetter
b99c0c6a7f LibWeb: Account for paint style and global alpha when drawing shadows 2025-10-21 18:55:08 +02:00
Ali Mohammad Pur
553a7a9278 AK: Remove unused and unintuitive Trait for char*
We don't use char* in containers, and the expectation would be a pointer
compare instead of a string compare; so remove this specialisation
entirely.
2025-10-21 18:20:48 +02:00
Tim Ledbetter
494fcc40ac LibWeb: Account for transforms in isPointInPath() 2025-10-21 17:42:28 +02:00
Andreas Kling
d17f666a8c LibWeb: Better CSS inheritance for nodes that represent a pseudo-element
When we compute style for elements inside a UA-internal shadow tree that
represent a pseudo-element (e.g ::placeholder), we actually run the
StyleComputer machinery for (host element :: pseudo-element).

While that lets us match the correct selectors, it was incorrectly
applying CSS inheritance, since we'd also then inherit from whatever was
above the host element in the tree.

This patch fixes the issue by introducing an inheritance override in
AbstractElement and then using that to force inheritance from whatever
is actually directly above in the DOM for these elements instead of
jumping all the way up past the host.

This fixes an issue where `text-align: center` on input type=text
elements would render the main text centered but placeholder text was
still left-aligned.
2025-10-21 16:42:00 +02:00
Andreas Kling
9e064bd3ff LibWeb: Make input type=text placeholder render as a block-level element
This will make the width: 100% we already apply actually do something
useful to the placeholders.
2025-10-21 16:42:00 +02:00
R-Goc
e8a9d23f92 CSS: Use i64 in StepsEasingFunction
Unbreaks Windows build. resolve_integer returns an i64 making creation
of a StepsEasingFunction with its result a narrowing conversion before
this change as a long is 32 bits on Windows.
2025-10-21 14:51:47 +02:00
Psychpsyo
80b629578e LibWeb: Fix partially selecting non-text nodes
Steps 4 and 5 were swapped since marking all the nodes between the start
and end of the selection now also marks the end node as full, even if it
should be marked as End.
There could be extra logic to avoid marking it if it is a text node, but
this seems easier.

As a whole, this fixes partially selected non-text nodes. In such cases,
where the selection starts or ends inside a node with descendants, it is
impossible to just select from the start node to the end node since that
would select all descendants of the start node and none of the end node.
Previously, this was only half considered and only if the start node was
a descendant of the end node.
2025-10-21 10:23:10 +01:00
Luke Wilde
66a36050bd LibWeb/WebGL: Use surfaceless contexts when making a context current
After we bind the surface to the context for the first time when
allocating the backing image, it internally has a permanent reference
count above 0.

If we switch to a different context and back, the switch back will fail
because the surface has a reference count, which makes ANGLE think
we're trying to use it on a different thread.

However, we can avoid this by using surfaceless contexts, since we
don't use the default framebuffer.

Fixes multiple WebGL contexts on the page freezing.
2025-10-21 09:59:59 +02:00
Tim Ledbetter
d3ca038b2c LibWeb: Ensure putImageData() is unaffected by drawing state 2025-10-21 09:52:16 +02:00
Tim Ledbetter
2ac4544a81 LibWeb: Align CanvasRenderingContext2D::putImageData() with the spec
This change implements `putImageData()` with `dirtyX`, `dirtyY`,
`dirtyWidth` and `dirtyHeight` arguments.
2025-10-21 09:52:16 +02:00
Aliaksandr Kalenik
509c86dca0 LibIPC: Simplify IPC read hook
- Return `PeerEOF` enum instead of `Error` containing string from
  `drain_messages_from_peer()`. There are no other error types to return
  from this function, so boolean-like enum is sufficient.
- Don't override read hook in `ConnectionFromClient` constructor. It was
  previously redefined only to suppress EOF error returned by
  `drain_messages_from_peer()`.
2025-10-21 09:31:22 +02:00
Rocco Corsi
edb60e38bd LibWeb: Update painting backing stores in all cases
To consistently update the backing stores in reallocate_backing_stores()
the variables storing the backing stores need to be set to nullptr at
the start so that they are properly update later on near the end of the
function.
2025-10-21 09:17:53 +02:00
Callum Law
2404f95e03 LibWeb: Invalidate style for tree counting functions when required
We mark any element that relies on tree counting functions as needing a
style update when a sibling is inserted/removed.
2025-10-20 16:12:08 +01:00
Callum Law
e9036c7c75 LibWeb: Support tree counting functions within calc() 2025-10-20 16:12:08 +01:00
Callum Law
55bcdcf824 LibWeb: Set up initial infrastructure for non-math functions in calc
There are some non-math functions (such as tree counting functions)
which we should allow within `calc()`s . This commit implements the
initial infrastructure for this.

We don't yet parse any of these non-math functions in
`parse_a_calculation` so there is no functional change.
2025-10-20 16:12:08 +01:00
Callum Law
831e471444 LibWeb: Support top-level tree counting functions
Adds support for `sibling-index()` and `sibling-count()` when parsing
`<number>` and `<integer>`. This is achieved by a new
`TreeCountingFunctionStyleValue` class which is converted within
`absolutized` to `NumberStyleValue` and `IntegerStyleValue` respectively

There are still a few kinks to work out in order to support these
everywhere, namely:
 - There are some `StyleValue`s which aren't absolutized (i.e. those
   which are stored within another `StyleValue` without an
   `absolutize()` method.
 - We don't have a way to represent this new `StyleValue` within
   `{Number,Integer}OrCalculated`. This would be fixed if we were to
   instead just use the `StyleValue` classes until style computation at
   which time they would be absolutized into their respective
   primitives (double, i64, etc) bypassing the need for *OrCalculated
   entirely.
2025-10-20 16:12:08 +01:00
Callum Law
9cd23e3ae5 LibWeb: Compute and propagate tree-counting function resolution context
Tree counting functions should be resolved at style computation time -
to do this we will need to know the element's sibling count and index.

This commit computes that information and propagates it to the various
`StyleValue::to_computed_value` methods.
2025-10-20 16:12:08 +01:00
Callum Law
fd31fbd84b LibWeb: Add method for whether tree-counting function is allowed
Some contexts (e.g. descriptors, media conditions) don't allow tree
counting functions, this commit adds an easy way to check if the current
value context is one of those.
2025-10-20 16:12:08 +01:00
Callum Law
fbc6a4c96f LibWeb: Allow setting initial value context when creating CSS parser 2025-10-20 16:12:08 +01:00
Callum Law
85239fb1da Tests: Import tree-counting function WPT tests 2025-10-20 16:12:08 +01:00
Callum Law
8284a99f0a LibWeb: Add absolutized method for FontStyleStyleValue 2025-10-20 16:12:08 +01:00
Callum Law
28451b16c9 LibWeb: Absolutize StyleValues before computing font properties
We also avoid prematurely constructing CSSPixels when computing
font-size which gains us a couple of test passes
2025-10-20 16:12:08 +01:00
Callum Law
ca9d107a1a LibWeb: Remove unused variable in white-space property serialization 2025-10-20 16:12:08 +01:00
Luke Wilde
ab3eb9adab LibWeb/WebGL: Replace manual pointer math with Span
This deduplicates a lot of sensitive pointer math by using Span, which
performs this math for us with more safety checks and with type
information.

This also allows us to use the correctly typed Span for typed arrays,
which automatically fixes srcOffset to now offset by the number of
elements instead of bytes. This goes for srcLengthOverride too.

Fixes the Rive animations on Shopify's homepage not appearing.
Fixes some Unity applications such as ArcViewer having missing
graphics.
2025-10-20 16:26:12 +02:00
Luke Wilde
afd170d16c AK: Add the ability to reinterpret a Span to a given type
This allows you to reinterpret a Span to any given type, maintaining
the original data and working out the new size for you.

The target type must evenly fit into the Span's original type, ensuring
bytes are not dropped.
2025-10-20 16:26:12 +02:00
Luke Wilde
b15f4424f9 LibWeb/WebGL: Implement the EXT_texture_filter_anisotropic extension 2025-10-20 15:33:33 +02:00
Luke Wilde
d08915a0cd LibWeb/WebGL: Implement the WEBGL_compressed_texture_s3tc_srgb extension 2025-10-20 15:33:33 +02:00
Luke Wilde
ddf60ebe9e LibWeb/WebGL2: Implement the EXT_texture_norm16 extension 2025-10-20 15:33:33 +02:00
Luke Wilde
93d3ebfd59 LibWeb/WebGL2: Implement the EXT_render_snorm extension 2025-10-20 15:33:33 +02:00
Sam Atkins
7ba496b798 LibWeb/CSS: Add ValueTypes for <foo-percentage> 2025-10-20 13:55:23 +01:00
Sam Atkins
c619c90e23 Tests: Import some @property tests 2025-10-20 13:55:23 +01:00
Tim Ledbetter
dab994c4f3 LibWeb: Remove redundant SVG presentation attribute handling 2025-10-20 12:38:19 +01:00
Amish K. Naidu
884b7fcbf5 LibWeb: Peek after extracting next chunk in InlineLevelIterator
Peek is used to determine if the current chunk is
the last and if so, add trailing box metrics.
For this to work correctly, it must be done only
after calling next(), otherwise peek gives us the
current chunk.
2025-10-20 12:37:25 +01:00
Lorenz A
eeef370902 LibWeb: Check that elements are HTML elements in StackOfOpenElements 2025-10-20 12:14:14 +01:00
Tim Ledbetter
08641c9e15 LibWeb: Ensure CanvasPath::arc() nonfinite radius argument is ignored 2025-10-20 12:12:16 +01:00
Tim Ledbetter
303ebc0a67 LibWeb: Ignore non-finite arguments to canvas text drawing methods 2025-10-20 12:12:16 +01:00
Callum Law
26b82986c4 LibWeb: Clean up parse_css_value_for_properties
We no longer rely on parsing easing functions before keywords so this
can be moved down with the other parse_for_type calls.

`parse_for_type` is used for more than just parsing easing functions so
the variable name `maybe_easing_function` was misleading
2025-10-20 11:27:44 +01:00
Callum Law
03be70087d LibWeb: Maintain easing keywords as KeywordStyleValue until use-time
This excludes `step-end` and `step-start` which are expected to be
converted to the equivalent function at parse time.

We are expected to serialize these as the explicit keywords - previously
we would parse as `EasingStyleValue` and serialize equivalent functions
as the keywords. This caused issues as we would incorrectly serialize
even explicit functions as the keyword.

This also allows us to move the magic easing functions to
`EasingFunction` rather than `EasingStyleValue` which is a bit tidier
2025-10-20 11:27:44 +01:00
Callum Law
755a576013 LibWeb: Support relative lengths within easing function calc()s 2025-10-20 11:27:44 +01:00
Callum Law
ad41f053b8 LibWeb: Clamp calculated steps() interval count using normal system
Previously we were doing this ad-hoc later in the process but we now
have the `calc` clamping system which can simplify things.

This reveals some false-positives in that we don't handle relative
lengths within these `calc`s but these are fixed in the next commit
2025-10-20 11:27:44 +01:00
Callum Law
06a57a280d LibWeb: Clamp calculated cubic-bezier() X coords using normal system
Previously we were doing this ad-hoc later in the process but we now
have the `calc` clamping system which can simplify things
2025-10-20 11:27:44 +01:00
Callum Law
ef4f01ea44 LibWeb: Discard Number type when converting to CalculationNode
Before this change we would maintain explicit signs when serializing
e.g. `animation-iteration-count: calc(+1)` would serialize as `calc(+1)`
rather than `calc(1)` as intended
2025-10-20 11:27:44 +01:00
Callum Law
2f83356c0f LibWeb: Support calc within linear() easing function 2025-10-20 11:27:44 +01:00
Callum Law
91925db9ca LibWeb: Don't canonicalize linear easing function points until use time
Canonicalization can require information that is only known after
compute time (i.e. resolved relative lengths within calcs).

This also allows us to get rid of the `had_explicit_input` flag and just
rely on whether Optional has a value
2025-10-20 11:27:44 +01:00
Callum Law
95e26819d9 LibWeb: Separate use time easing functions from EasingStyleValue
In the future there will be different methods of creating these use-time
easing functions (e.g. from `KeywordStyleValue`s)
2025-10-20 11:27:44 +01:00
Callum Law
0e30de82cc LibWeb: Implement <step-position> as an enum
This simplifies parsing and serialization by using the generated
functions
2025-10-20 11:27:44 +01:00
Callum Law
4c97b336c3 Tests: Import timing function tests 2025-10-20 11:27:44 +01:00
Sam Atkins
41b4292447 LibDevTools+LibWebView: Implement initial accessibility tree view
This is enough for Firefox to display the Accessibility tab, containing
our accessibility tree which can be inspected. Most information is
blank for now.

There's quite a bit of duplication between AccessibilityWalkerActor and
WalkerActor - it might be worth trying to make a base class once the
details are figured out. Frustratingly, the two don't work quite the
same: for a lot of messages that would be sent to WalkerActor, the
accessibility equivalent is sent to the AccessibilityNodeActor instead.

Co-authored-by: Tim Flynn <trflynn89@pm.me>
2025-10-20 10:51:19 +01:00
Sam Atkins
c9811af3b5 LibWeb/DOM: Set missing JSON attributes for a11y text nodes
Firefox DevTools expects all nodes to have an ID and type, and for a
text node's name to be its text.
2025-10-20 10:51:19 +01:00
Sam Atkins
d5fe7f7a98 LibDevTools: Pull DOMNode type and related code into new Node.h
Accessibility nodes will share this code, so make it available, and
rename DOMNode to Node.
2025-10-20 10:51:19 +01:00
Luke Wilde
eeb5446c1b LibWeb: Avoid including Navigable.h in headers
This greatly reduces how much is recompiled when changing Navigable.h,
from >1000 to 82.
2025-10-20 10:16:55 +01:00
Undefine
7bccd65b4a LibWasm: Make sure try_table creates a new frame while validating
The spec says that while validating this opcode a new label should
be pushed.

Fixes a crash in instance.wast on WPT.
2025-10-19 17:28:11 +02:00
Undefine
07c86542b6 LibWasm: Properly read and validate limits for I64 memories and tables
Since memory64 got merged into the spec the minimum value for limits
is now actualy 64-bit and the maximum sizes for memories and tables
for I64 address types were increased.

Fixes 5 tests in memory64.wast nad 8 tests in table64.wast on WPT.
2025-10-19 17:28:11 +02:00
Undefine
692195ae88 LibWasm: Cast to long double before checking if trunactions is in range
I found that this fixes some precision issues while comparing to values
on the edge of the limits.

Fixes 6 tests in conversions.wast on WPT.
2025-10-19 17:28:11 +02:00
Luke Wilde
11a1e97e40 LibWeb: Invalidate layout tree after removing element from top layer
Otherwise the layout tree will still contain the top layer element(s).

Fixes Steam Events & Announcements `<dialog>` modal visually not fully
disappearing upon removal.
2025-10-19 16:58:47 +02:00
Tim Ledbetter
34857ba554 LibWeb: Apply dithering when painting gradients 2025-10-19 16:53:00 +02:00
stelar7
98f91a3fcc LibWeb/IDB: Dont run wait callbacks as a database task 2025-10-18 13:50:01 +01:00
ljamar
7fb65283c2 LibWeb: Ignore non-finite args in CanvasRenderingContext2D::clear_rect() 2025-10-17 17:41:58 +01:00
Callum Law
b1801c0bc9 LibWeb: Avoid crash evaluating media query in document lacking window
In some cases a document may lack an associated window - to fix this for
now we just return false but perhaps there are some media queries we
should still attempt to resolve.
2025-10-17 18:24:59 +02:00
Andreas Kling
7ee8645b9c LibJS: Avoid redundant GC::Weak checks in hot inline cache code paths
By copying out pointees to a temporary, we can avoid some redundant
pointer chasing inside GC::Weak when hitting our inline caches.
2025-10-17 17:22:16 +02:00
Andreas Kling
dfa796a4e4 LibJS+LibWeb: Use GC::Weak instead of AK::WeakPtr for GC-allocated types
This makes some common types like JS::Object smaller (by 8 bytes) and
yields a minor speed improvement on many benchmarks.
2025-10-17 17:22:16 +02:00
Andreas Kling
25a5ed94d6 LibGC: Add GC::Weak<T> as an alternative to AK::WeakPtr<T>
This is a weak pointer that integrates with the garbage collector.
It has a number of differences compared to AK::WeakPtr, including:

- The "control block" is allocated from a well-packed WeakBlock owned by
  the GC heap, not just a generic malloc allocation.

- Pointers to dead cells are nulled out by the garbage collector
  immediately before running destructors.

- It works on any GC::Cell derived type, meaning you don't have to
  inherit from AK::Weakable for the ability to be weakly referenced.

- The Weak always points to a control block, even when "null" (it then
  points to a null WeakImpl), which means one less null check when
  chasing pointers.
2025-10-17 17:22:16 +02:00
Andreas Kling
127208f3d6 LibWeb: Move Document unregistration from EventLoop to finalizer
This ensure that it's not visible to event loops by the time we're
running destructors.
2025-10-17 17:22:16 +02:00
Callum Law
1a3635cda5 LibWeb: Parse the shape-margin property 2025-10-17 11:10:05 +01:00
Callum Law
9c7202e3f3 LibWeb: Parse the shape-image-threshold property 2025-10-17 11:10:05 +01:00
Callum Law
0e82ab2966 LibWeb: Define <opacity-value> as a ValueType
Since `<opacity-value>` is used across multiple properties it makes
sense to have it defined as a value.
2025-10-17 11:10:05 +01:00
Callum Law
01c5b6f74f LibWeb: Parse the shape-outside property 2025-10-17 11:10:05 +01:00
Callum Law
fb64be2f78 Tests: Import some css-shapes tests 2025-10-17 11:10:05 +01:00
Andreas Kling
383dd28217 LibWebView: Add CLI option to run with content filters disabled
Let's have a way to run all the JavaScript the web wants to give us.
This was previously available as a Debug menu option, and this makes
it available from process startup.
2025-10-17 11:24:57 +02:00
stelar7
ced862c460 LibWeb/IDB: Apply default cursor direction
IDBGetAllOptions is supposed to have a default value for direction.
When the value passed is not a potentially valid key range, we
need to default the direction argument, and not assume its set

Spec issue: https://github.com/w3c/IndexedDB/pull/478
2025-10-17 09:42:39 +02:00
Callum Law
cdbf4f49e1 LibWeb: Support '<zero>' in '<color-stop-angle>`
`<color-stop-angle> = [ <angle-percentage> | <zero> ]{1,2}` but we were
previously parsing instead as `<angle-percentage>{1,2}`.
2025-10-17 08:37:18 +01:00
Callum Law
cc2c8e8615 LibWeb: Resolve percentages in <angular-color-stop-list> as angles 2025-10-17 08:37:18 +01:00
Tim Ledbetter
62c00712fa LibWeb: Update HTMLScriptElement::prepare_script() spec text 2025-10-16 16:46:48 +02:00
Tim Ledbetter
24a7eac4ab LibWeb: Delay module script execution until current script has ended 2025-10-16 16:46:48 +02:00
Lorenz A
e73e0b3c92 LibWeb: Implement CSS decode bytes algo 2025-10-16 16:44:42 +02:00
aplefull
4b989b8efd LibRegex: Add support for forward references to named capture groups
This commit implements support for forward references to named capture
groups. We now allow patterns like \k<name>(?<name>x) and
self-references like (?<name>\k<name>x).
2025-10-16 16:37:54 +02:00
aplefull
25a47ceb1b LibRegex+LibJS: Include all named capture groups in source order
Previously, named capture groups in RegExp results did not always follow
their source order, and unmatched groups were omitted. According to the
spec, all named capture groups must appear in the result object in the
order they are defined, even if they did not participate in the match.
This commit makes sure we follow this requirement.
2025-10-16 16:37:54 +02:00
aplefull
c4eef822de LibRegex: Fix backreferences to undefined capture groups
Fixes handling of backreferences when the referenced capture group is
undefined or hasn't participated in the match.
CharacterCompareType::NamedReference is added to distinguish numbered
(\1) from named (\k<name>) backreferences. Numbered backreferences use
exact group lookup. Named backreferences search for participating
groups among duplicates.
2025-10-16 16:37:54 +02:00
Timothy Flynn
9b8f6b8108 RequestServer: Issue a network request for failed cached responses
If transferring a cached response body fails for any reason, we will now
issue a network request instead of failing the request outright.

The catch here is that we will have already transferred the response
code and headers to the client, and potentially some of the body. So we
attempt to only request the remaining data over the network using a
range request. This feels a bit sketchy, but this is also how Chromium
behaves.

However, the server may or may not support range requests. If they do,
we can expect an HTTP 206 response with the bytes we need. If not, we
will receive an HTTP 200 (assuming the request succeeded), along with
the entire object's body. In this case, we also behave like Chromium,
and internally drop number of bytes we had already transferred.
2025-10-16 09:06:48 -04:00
Timothy Flynn
fc9233f198 RequestServer: Delete unreadable cache files (for now)
If we are unable to pipe the response body from a cache file to the
client, let's take the extra safe approach of deleting the cache file
for now. We already remove the file if we weren't able to read its
metadata during initialization.
2025-10-16 09:06:48 -04:00
Jelle Raaijmakers
27cb5d8c1e CI: Switch Flatpak builds back temporarily to GitHub runners
Until Blacksmith runners enable redirect_dir, the flatpak builder
container is unable to install updates for packages in its system dirs.
2025-10-16 14:26:19 +02:00
Jelle Raaijmakers
188384710a CI: Remove options with default values from Flatpak build
No functional changes.
2025-10-16 14:26:19 +02:00
Zaggy1024
9cd0f9c445 LibMedia: Support BT.470 System B/G color primaries 2025-10-16 05:12:29 -05:00
Zaggy1024
b684bc0a9d LibMedia: Reorder the BT.2020 matrix to match previous lines 2025-10-16 05:12:29 -05:00
Callum Law
2af071380e LibWeb: Dont load a style sheet's fonts until it has an owning document
We need a style sheet to have an owning document to load it's fonts (to
generate a length resolution context).

Fixes #6445
2025-10-16 10:27:32 +01:00
Callum Law
9651969708 LibWeb: Propagate CSSStyleSheet owning documents and shadow roots
Previously these were only stored on the root style sheet and were
accessed by imported stylesheets via their owner rule.

Propagating these to imported style sheets allows us to more easily know
when they change for said imported style sheets.
2025-10-16 10:27:32 +01:00
Callum Law
3708fc6aa7 LibWeb: Resolve relative lengths in @font-face using correct viewport
As with everywhere else we should be using the document rather than the
window's viewports.

Fixes #6467
2025-10-16 10:27:32 +01:00
Callum Law
29fb63c928 LibWeb: Support length resolution context for document lacking navigable
Some documents (e.g. those created by DOMParser.parseFromString()) will
not be associated with a navigable. These documents effectively have a
viewport of 0x0.
2025-10-16 10:27:32 +01:00
Andreas Kling
c23ed104e5 LibJS: Micro-optimize ECMAScriptFunctionObject::internal_construct()
- Add FLATTEN (same as we do for internal_call()).
- Demote nice-to-have VERIFYs to ASSERTs.
- Pass already-known Realm to ordinary_create_from_constructor

1.03x speedup on Octane/earley-boyer.js
2025-10-16 10:47:10 +02:00
aplefull
5df216218b LibGfx: Correctly determine when to invert CMYK
We should invert CMYK data only if color space is JCS_CMYK and either
there is no Adobe marker, or the Adobe transform is 0. Transform 2
indicates YCCK data, which we should not invert.
2025-10-15 21:50:16 +02:00
Tim Ledbetter
e55060ef6e LibWeb: Support the display-p3-linear color space in color functions 2025-10-15 18:40:48 +02:00
Tim Ledbetter
b08ecc0cd1 Tests: Update WPT color parsing tests 2025-10-15 18:40:48 +02:00
mikiubo
d4df0e1db9 LibWeb: Make Event.currentTarget return WindowProxy instead of Window
Make WindowProxy implement the EventTarget interface. Add a new method
current_target_for_bindings() that returns a WindowProxy object instead
of directly exposing the Window object.

These changes fixes several WPT tests that contain `window ===
currentTarget`.
2025-10-15 15:36:34 +02:00
Jelle Raaijmakers
373b2838db CI: Don't run merge conflict labeler on forks 2025-10-15 10:36:07 +02:00
Jelle Raaijmakers
980e715668 CI: Run js-benchmarks with optional wasm binary
If we run js-benchmarks against older builds that did not yet include
the wasm repl, make sure we skip extracting the file and passing the
`--wasm-executable` argument.
2025-10-15 10:36:07 +02:00
Ali Mohammad Pur
92c0cbc453 LibWasm+LibWeb: Stub wasm-gc's heap reference types
WPT inserts these into all modules regardless of whether they're used,
so let's just parse and ignore them.
2025-10-15 01:26:29 +02:00
Ali Mohammad Pur
33d2959a4c LibWeb: Stub wasm exceptions and memory64 API modifications 2025-10-15 01:26:29 +02:00
Ali Mohammad Pur
d99f663b1a LibWasm: Implement parsing/validation for proposal exception-handling
Actual execution traps for now.
2025-10-15 01:26:29 +02:00
Ali Mohammad Pur
8138c2f48b LibWasm: Follow the updated spec on instantiation
The spec now permits access to all globals for all segment initializers,
as well as previously-defined globals for the global initializers.
2025-10-15 01:26:29 +02:00
Ali Mohammad Pur
ddb35dcb5f LibWasm: Accept proposal 'memory64' (but don't actually run it)
This is a WIP implementation.
2025-10-15 01:26:29 +02:00
Ali Mohammad Pur
d6f3f5fd51 LibWasm: Implement proposal 'relaxed-simd' 2025-10-15 01:26:29 +02:00
Ali Mohammad Pur
77237af33f LibWasm: Add support for proposal 'extended-const' 2025-10-15 01:26:29 +02:00
Ali Mohammad Pur
6a6f747701 LibWasm: Add support for proposal 'tail-call' 2025-10-15 01:26:29 +02:00
Andreas Kling
d065171791 LibJS: Use property lookup caches for some of our hot C++ gets
We can use caching in a million more places. This is just me running JS
benchmarks and looking at which get() call sites were hot and putting
caches there.

Lots of nice speedups all over the place, some examples:

1.19x speedup on Octane/raytrace.js
1.13x speedup on Octane/earley-boyer.js
1.12x speedup on Kraken/ai-astar.js
1.10x speedup on Octane/box2d.js
1.08x speedup on Octane/gbemu.js
1.05x speedup on Octane/regexp.js
2025-10-14 15:47:38 +02:00
Andreas Kling
0fb9ba1e3a LibJS: Add Value::get() and Object::get() overloads with lookup cache
To speed up property access, callers of get() can now provide a lookup
cache like so:

    static Bytecode::PropertyLookupCache cache;
    auto value = TRY(object.get(property, cache));

Note that the cache has to be `static` or it won't make sense!

This basically brings the inline caches from our bytecode VM straight
into C++ land, allowing us to gain serious performance improvements.
The implementation shares code with the GetById bytecode instruction.
2025-10-14 15:47:38 +02:00
Andreas Kling
26c1dea22a LibJS: Move GetById logic to a separate header to prepare for reuse
We also make the code a bit more generic by making callers provide
(templated) callbacks that produce the property name and base expression
string if any.
2025-10-14 15:47:38 +02:00
Sam Atkins
3716db1c61 LibWeb/CSS: Implement converting CSSTransformValues to StyleValues 2025-10-14 13:41:47 +01:00
Sam Atkins
5178d1ebe3 LibWeb/CSS: Add flag to disable create-internal-rep type checking
CSSTransformComponents hold other CSSStyleValues as their parameters. We
want to be able to create internal representations from those parameters
without them caring if they would be valid when directly assigned to the
property.

This is a temporary solution to make transform functions work. To be
completely correct, we need to know what is allowed in that context,
along with value ranges - a combination of the contexts we create when
parsing, and when computing calculations. For transform functions, this
doesn't matter, as there's no limit to the range of allowed values.
2025-10-14 13:41:47 +01:00
Sam Atkins
35fd3bda79 LibWeb/CSS: Promote <transform-list/function> to parsable types
The `transform` property is now parsed based on its JSON data, and
shouldn't behave any differently than before.

This makes `<transform-list>` and `<transform-function>` work in the
`syntax` descriptor for `@property`, and also means we know that
`transform` can accept the `none` keyword. We get a few WPT passes out
of that.
2025-10-14 13:41:47 +01:00
Sam Atkins
65ba5acf9d Tests: Import @property { syntax } parsing test
This gets some extra passes with the next commit.
2025-10-14 13:41:47 +01:00
Sam Atkins
8fffce07df LibWeb/CSS: Remove unused GenericShorthands.h include 2025-10-14 13:41:47 +01:00
Jelle Raaijmakers
21932661c2 CI: Update .github/actions/ folder to latest when building older commits
Older commits also have older setup code, such as requiring older XCode
versions - which fails on newer macOS systems. Let's always use the
latest custom actions so we can retroactively support these older
builds.
2025-10-14 14:19:22 +02:00
Timothy Flynn
163e8e5b44 LibWebView+RequestServer: Support clearing the HTTP disk cache
This is a bit of a blunt hammer, but this hooks an action to clear the
HTTP disk cache into the existing Clear Cache action. Upon invocation,
it stops all existing cache entries from making further progress, and
then deletes the entire cache index and all cache files.

In the future, we will of course want more fine-grained control over
cache deletion, e.g. via an about:history page.
2025-10-14 13:40:33 +02:00
Timothy Flynn
42eaea1043 LibWebView: Add a command line flag to enable the HTTP disk cache
This adds a RequestServerOptions structure to hold this option and the
only other RS option we currently have (certificates).
2025-10-14 13:40:33 +02:00
Timothy Flynn
3516a2344f LibRequests+RequestServer: Begin implementing an HTTP disk cache
This adds a disk cache for HTTP responses received from the network. For
now, we take a rather conservative approach to caching. We don't cache a
response until we're 100% sure it is cacheable (there are heuristics we
can implement in the future based on the absence of specific headers).

The cache is broken into 2 categories of files:

1. An index file. This is a SQL database containing metadata about each
   cache entry (URL, timestamps, etc.).
2. Cache files. Each cached response is in its own file. The file is an
   amalgamation of all info needed to reconstruct an HTTP response. This
   includes the status code, headers, body, etc.

A cache entry is created once we receive the headers for a response. The
index, however, is not updated at this point. We stream the body into
the cache entry as it is received. Once we've successfully cached the
entire body, we create an index entry in the database. If any of these
steps failed along the way, the cache entry is removed and the index is
left untouched.

Subsequent requests are checked for cache hits from the index. If a hit
is found, we read just enough of the cache entry to inform WebContent of
the status code and headers. The body of the response is piped to WC via
syscalls, such that the transfer happens entirely in the kernel; no need
to allocate the memory for the body in userspace (WC still allocates a
buffer to hold the data, of course). If an error occurs while piping the
body, we currently error out the request. There is a FIXME to switch to
a network request.

Cache hits are also validated for freshness before they are used. If a
response has expired, we remove it and its index entry, and proceed with
a network request.
2025-10-14 13:40:33 +02:00
Timothy Flynn
411aed96ab LibDatabase: Support all C++ integral types in SQL storage 2025-10-14 13:40:33 +02:00
Timothy Flynn
187d02c45d LibDatabase+LibWebView: Extract our SQLite wrapper to its own library
It currently lives in LibWebView as it was only used for cookies and
local storage, both of which are managed in the UI process. Let's move
it to its own library now to allow other processes to use it, without
having to depend on LibWebView (and therefore LibWeb).
2025-10-14 13:40:33 +02:00
Timothy Flynn
e433dee543 LibCore: Add a system wrapper to pipe a file
This uses splice on Linux and mmap+write elsewhere to transfer a file to
a pipe. Note we cannot use sendfile because (at least on macOS) the
receiving fd must be a socket.
2025-10-14 13:40:33 +02:00
Timothy Flynn
62e52640d0 LibCore: Add a standard path for cache data 2025-10-14 13:40:33 +02:00
Jelle Raaijmakers
d349e91339 CI: Inject COMMIT file into archives when missing
This allows us to run js-benchmarks against older commits and have the
workflow correctly identify the commit used to build the binaries. In
order to actually build commits where the wasm binary was not yet built,
we have to account for missing archives as well.
2025-10-14 12:47:25 +02:00
ayeteadoe
0d5136ae5c LibWeb: Add support for bitmap scaling in createImageBitmap() 2025-10-14 12:19:33 +02:00
ayeteadoe
05f3bd0fa8 Tests/LibWeb: Import several scaling createImageBitmap() tests 2025-10-14 12:19:33 +02:00
Tim Ledbetter
0bdb831c68 LibWeb: Avoid null dereference in ListItemBox specified content check 2025-10-14 10:27:11 +01:00
Tim Ledbetter
2f5481284d Meta: Don't attempt to download WPT test resources from external URLs 2025-10-14 10:27:11 +01:00
stelar7
61185d98aa LibWeb/IDB: Adjust how negative numbers increment the key generator
Directly mapping a negative double to a u64 causes it to wrap around
to the max value. We work around this here by comparing as doubles,
and only incrementing the generator if the new value is greater

Fixes #6455
2025-10-14 10:26:28 +01:00
Aliaksandr Kalenik
207f313b4b LibWeb: Delete unused Document::set_visibility_state() 2025-10-14 11:23:29 +02:00
Aliaksandr Kalenik
4853e2ffb1 LibWeb: Don't reach into layout node to check if paintable is SVG 2025-10-14 11:23:29 +02:00
Aliaksandr Kalenik
881ef21d40 LibWeb: Get rid of SVGPathPaintable::layout_box()
It was used exclusively to get corresponding DOM node pointer, which is
unnecessary indirection as Paintable owns the DOM node pointer directly.
2025-10-14 11:23:29 +02:00
Aliaksandr Kalenik
f706c883eb LibWeb: Don't reach into layout node to check if Paintable is positioned
We copy this information into Paintable, so it could be taken directly
from there.
2025-10-14 11:23:29 +02:00
Aliaksandr Kalenik
9e838cffb4 LibWeb: Copy "is inert" attribute into Paintable
...instead of reaching into DOM tree during hit-testing in order to
figure out if an element is inert. This is a part of the effert to make
possible running hit-testing solely based on data contained by the
paintable tree.
2025-10-14 11:23:29 +02:00
Aliaksandr Kalenik
81aeee3fb4 LibWeb: Get rid of PaintableBox::is_viewport()
This function used layout node pointer to check if it's corresponding to
viewport. There is no need for that, since `is_viewport_paintable()`
does exactly the same check without going through layout node.
2025-10-14 11:23:29 +02:00
InvalidUsernameException
1ffb0ca311 LibWeb: Remove redundant function parameter
This function can really only be called with a box and its containing
block, otherwise the results are not meaningful. Instead of passing
these two dependent values separatly, reduce it down to a single
parameter to not make the function appear more general than it is.
2025-10-14 10:23:27 +02:00
InvalidUsernameException
70c46e081d LibWeb: Correctly calculate nested positioned elements' static position
If there are multiple nested `position: fixed` or `position: absolute`
elements that are positioned based on their static position due to not
specifying any insets, we sum up all their ancestor offsets to calculate
said static position.

However, these offsets represent the offset to the containing block. So
summing up all the ancestor blocks will count elements multiple times
for cases where the containing block is not based on the closest element
capable of forming a containing block (i.e. absolute and fixed position
elements) when multiple such elements are nested.

With this change we only iterate over ancestors forming containing
blocks instead of over all ancestors boxes. To sum up everything between
the box currently being positioned and its containing block, we start
the iteration on the parent box of the current box.

This fixes 3 WPT tests that I could find. But these tests are not
intended to test the error cases fixed here, they just incidentally rely
on the correct behavior. As such, I have added dedicated tests myself.
Note that two of the tests already pass on master, but they seemed like
a good cases to have anyway.
2025-10-14 10:23:27 +02:00
Andreas Kling
0e4450f4b3 LibJS: Avoid function call if @@hasInstance is default implementation
This makes the instanceof operator signficantly faster by avoiding a
generic function call to @@hasInstance unless it has been overridden.

1.15x speed-up on Octane/earley-boyer.js
2025-10-13 17:15:44 +02:00
Andreas Kling
5a7b0a07cb LibJS: Mark global function declarations as globals
This allows us to use the GetGlobal and SetGlobal bytecode instructions
for them, enabling cached accesses.

2.62x speed-up on this Fibonacci program:

    function fib(n) {
        return n < 2 ? n : fib(n - 1) + fib(n - 2);
    }
    for (let i = 0; i < 50_000; ++i)
        fib(10);
2025-10-13 17:15:44 +02:00
Rocco Corsi
44d2a74eeb LibJS: Increase the stack limit when ASAN enabled
Linux, x86_64, Sanitizer, GNU runners on GitHub Action fail randomly
with a stack overflow on recursive test called:
Libraries/LibJS/Tests/runtime-error-call-stack-size.js
2025-10-13 09:07:39 -04:00
Tete17
460ffcbe1d LibWeb: Add new passing WPT tests 2025-10-13 13:22:01 +01:00
Tete17
18199f8f2a LibWeb: Hook TrustedTypes to the ServiceWorkers api 2025-10-13 13:22:01 +01:00
Tete17
74aa7e8a82 LibWeb: Hook TrustedTypes to the Workers api 2025-10-13 13:22:01 +01:00
Tete17
e6ac064a34 LibWeb: Hook TrustedTypes to the SharedWorkers api 2025-10-13 13:22:01 +01:00
Tete17
bd4e3fd3e0 LibWeb: Hook TrustedTypes to the Range Dom api 2025-10-13 13:22:01 +01:00
Tim Ledbetter
701ef22952 LibWeb: Use correct percentage basis when resolving line height 2025-10-13 10:17:58 +01:00
Sam Atkins
078bc1a471 LibWeb/CSS: Allow converting CSSMathValues to StyleValues
Because we store calculations as a tree of CalculationNodes inside a
CalculatedStyleValue, instead of a tree of StyleValues directly, this
implements a create_calculation_node() method on CSSNumericValue.
CSSMathValue::create_an_internal_representation() then calls
create_calculation_node() on itself, and wraps it in a
CalculatedStyleValue.

Lots of WPT passes again! Some regressions, which are expected: `cursor`
fails a test for the same reason it fails other that set it to some
kind of numeric value: We don't distinguish between "can contain a
number" and "can accept a number by itself". This will affect any
similar properties, but overall this is a big improvement.
2025-10-13 09:59:38 +01:00
Sam Atkins
37cffba30e LibWeb/CSS: Forward-declare some calculation-related types
Saves us having to include CalculatedStyleValue.h from some headers.
2025-10-13 09:59:38 +01:00
Sam Atkins
df7abe1dc2 LibWeb/CSS: Extract CSSUnitValue -> CSS Value code
create_numeric_value() will be used next to create a CalculationNode,
and I didn't want to have to duplicate the "create a value based on the
unit name" code.

No behaviour change.
2025-10-13 09:59:38 +01:00
Sam Atkins
0afa93e639 LibWeb/CSS: Add a Property -> CalculationContext factory method
We have this code duplicated in multiple places, and we'll want to
handle registered custom properties too at some point, so wrap it in a
reusable `CalculationContext::for_property()` method.

Noticed while doing this that ValueParsingContext will eventually need
to take a PropertyNameAndID, not a PropertyID, so I've added a FIXME.
2025-10-13 09:59:38 +01:00
=
a3e973970a LibWeb/CSS: Check overflow value before determining box baseline
The CSS spec says the baseline of an inline-block should be the bottom
margin when either the overflow property is not 'visible' or there are
no in-flow line boxes. Previously, only the latter case was checked.

This fixes 1 WPT test:
https://wpt.live/css/css-align/baseline-of-scrollable-1a.html
2025-10-13 09:47:32 +01:00
Tim Ledbetter
1a640b1d95 LibWeb: Avoid crash when shadow root has null focused area 2025-10-12 22:50:12 +01:00
Andreas Kling
a76f420207 LibJS: Add fast path for division of two numbers
We already had fast paths for Add, Sub and Mul. Might as well do Div.

1.18x speed-up on this micro-benchmark:

    (() => {
        let a = 1234;
        for (let i = 0; i < 100_000_000; ++i)
            a / a;
    })()
2025-10-11 20:08:58 +02:00
Andreas Kling
755c8d8cd6 LibJS: Sprinkle [[likely]] on the fast paths in ValueInlines.h
1.04x speed-up on JetStream/quicksort.c.js
2025-10-11 20:08:58 +02:00
Andreas Kling
7462c10ee2 LibJS: Sprinkle [[unlikely]] on the PutBy* cache miss code paths
These caches are gonna be hitting most of the time, so let's give the
compiler a hint about this.

1.01x speed-up on MicroBench/pic-add-own.js
2025-10-11 20:08:58 +02:00
Andreas Kling
b47f8f94fe LibJS: Split PutBy* instructions into specialized per-kind variants
This allows the compiler to fold away lots of unused code for each kind.

1.10x speed-up on MicroBench/pic-add-own.js :^)
2025-10-11 20:08:58 +02:00
Andreas Kling
d13b4f3e39 LibJS: Remove unused member PutPrivateById::m_kind
Private property puts never have a PutKind.
2025-10-11 20:08:58 +02:00
Andreas Kling
e7a3c4dbad LibJS: Rename Bytecode::Op::PropertyKind => Bytecode::PutKind
This is only used to specify how a property is being added to an object
by Put* instructions, so let's call it PutKind.

Also add an enumeration X macro for it to prepare for upcoming
specializations.
2025-10-11 20:08:58 +02:00
1494 changed files with 41382 additions and 11734 deletions

View File

@ -7,4 +7,4 @@ dnf install -y git gh
# Ladybird dev dependencies
dnf install -y autoconf-archive automake ccache cmake curl google-noto-sans-mono-fonts liberation-sans-fonts \
libglvnd-devel libtool nasm ninja-build patchelf perl-FindBin perl-IPC-Cmd perl-lib qt6-qtbase-devel \
qt6-qtmultimedia-devel qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static
qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static

View File

@ -24,7 +24,7 @@ install_llvm_key() {
### Install packages
apt update -y
apt install -y lsb-release git python3 autoconf autoconf-archive automake build-essential cmake libdrm-dev libgl1-mesa-dev libtool nasm ninja-build pkg-config qt6-base-dev qt6-tools-dev-tools qt6-multimedia-dev qt6-wayland ccache fonts-liberation2 zip unzip curl tar
apt install -y lsb-release git python3 autoconf autoconf-archive automake build-essential cmake libdrm-dev libgl1-mesa-dev libtool nasm ninja-build pkg-config qt6-base-dev qt6-tools-dev-tools qt6-wayland ccache fonts-liberation2 zip unzip curl tar
### Ensure new enough host compiler is available
VERSION="0.0.0"

View File

@ -53,13 +53,6 @@ runs:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100
# FIXME: https://github.com/WebAssembly/wabt/issues/2533
# wabt doesn't have binary releases for arm64 Linux
curl -f -L -o wabt-1.0.35-ubuntu-20.04.tar.gz https://github.com/WebAssembly/wabt/releases/download/1.0.35/wabt-1.0.35-ubuntu-20.04.tar.gz
tar -xzf ./wabt-1.0.35-ubuntu-20.04.tar.gz
rm ./wabt-1.0.35-ubuntu-20.04.tar.gz
echo "${{ github.workspace }}/wabt-1.0.35/bin" >> $GITHUB_PATH
- name: 'Select latest Xcode'
if: ${{ inputs.os == 'macOS' || inputs.os == 'Android' }}
uses: maxim-lobanov/setup-xcode@v1
@ -117,7 +110,21 @@ runs:
run: |
set -e
brew update
brew install autoconf autoconf-archive automake bash ccache coreutils libtool llvm@20 nasm ninja pkg-config qt unzip wabt
brew install autoconf autoconf-archive automake bash ccache coreutils libtool llvm@20 nasm ninja pkg-config qt unzip
- name: 'Install wabt'
shell: bash
run: |
if ${{ inputs.os == 'Linux' }} ; then
curl -f -L -o wabt-1.0.35.tar.gz https://github.com/WebAssembly/wabt/releases/download/1.0.35/wabt-1.0.35-ubuntu-20.04.tar.gz
else
curl -f -L -o wabt-1.0.35.tar.gz https://github.com/WebAssembly/wabt/releases/download/1.0.35/wabt-1.0.35-macos-14.tar.gz
fi
tar -xzf ./wabt-1.0.35.tar.gz
rm ./wabt-1.0.35.tar.gz
echo "${{ github.workspace }}/wabt-1.0.35/bin" >> $GITHUB_PATH
- name: 'Set required environment variables'
if: ${{ inputs.os == 'Linux' && inputs.arch == 'arm64' }}

View File

@ -23,15 +23,16 @@ jobs:
&& contains(github.event.pull_request.labels.*.name, 'flatpak')
name: Flatpak ${{ matrix.arch }}
# FIXME: Temporarily run on GitHub runners until Blacksmith runners have overlay redirect_dir enabled.
strategy:
fail-fast: false
matrix:
arch: ['x86_64']
runner_labels: ['["blacksmith-8vcpu-ubuntu-2404"]']
runner_labels: ['["ubuntu-24.04"]']
include:
- arch: 'aarch64'
runner_labels: '["blacksmith-8vcpu-ubuntu-2404-arm"]'
runner_labels: '["ubuntu-24.04-arm"]'
secrets: inherit
uses: ./.github/workflows/flatpak-template.yml

View File

@ -44,6 +44,13 @@ jobs:
clang_plugins: false
runner_labels: '["blacksmith-16vcpu-ubuntu-2404"]'
- os_name: 'Linux'
arch: 'x86_64'
build_preset: 'All_Debug'
toolchain: 'Clang'
clang_plugins: false
runner_labels: '["blacksmith-16vcpu-ubuntu-2404"]'
secrets: inherit
uses: ./.github/workflows/lagom-template.yml
with:

View File

@ -24,7 +24,5 @@ jobs:
with:
bundle: Ladybird.flatpak
manifest-path: Meta/CMake/flatpak/org.ladybird.Ladybird.json
cache: 'true'
arch: ${{ inputs.arch }}
# Note: default cache key is 'flatpak-builder-${arch}-${sha256(manifestPath)}'
upload-artifact: 'true'

View File

@ -49,6 +49,19 @@ jobs:
with:
ref: ${{ inputs.reference_to_build }}
- name: 'Determine build commit hash'
id: build-commit
shell: bash
run: |
echo "sha=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"
- name: 'Use latest custom actions'
if: ${{ inputs.reference_to_build != '' && github.sha != steps.build-commit.outputs.sha }}
shell: bash
run: |
git fetch origin master
git checkout origin/master -- .github/actions
- name: "Set up environment"
uses: ./.github/actions/setup
with:
@ -102,6 +115,17 @@ jobs:
run: |
cpack
# Inject the COMMIT file for older builds (before commit 5c5de0e30e04).
for package in ladybird-*.tar.gz; do
if ! tar -tzf "${package}" | grep -qx COMMIT; then
echo "${{ steps.build-commit.outputs.sha }}" > COMMIT
gunzip "${package}"
tar --append --file="${package%.gz}" COMMIT
gzip "${package%.gz}"
rm COMMIT
fi
done
- name: Save Caches
uses: ./.github/actions/cache-save
with:
@ -115,34 +139,38 @@ jobs:
- name: Sanity-check the js repl
shell: bash
run: |
set -e
tar -xvzf Build/distribution/ladybird-js-${{ matrix.package_type }}.tar.gz
./bin/js -c "console.log('Hello, World\!');" > js-repl-out.txt
if ! grep -q "\"Hello, World\!\"" js-repl-out.txt; then
path="Build/distribution/ladybird-js-${{ matrix.package_type }}.tar.gz"
if [ -f "${path}" ]; then
tar -xvzf "${path}"
bin/js -c "console.log('Hello, World\!');" > js-repl-out.txt
if ! grep -q "\"Hello, World\!\"" js-repl-out.txt; then
echo "Sanity check failed: \"Hello, World\!\" not found in output."
exit 1
fi
fi
- name: Sanity-check the wasm repl
shell: bash
run: |
set -e
tar -xvzf Build/distribution/ladybird-wasm-${{ matrix.package_type }}.tar.gz
./bin/wasm -e run_sanity_check -w ${{ github.workspace }}/Libraries/LibWasm/Tests/CI/ci-sanity-check.wasm > wasm-repl-out.txt
if ! grep -q "Hello, World\!" wasm-repl-out.txt; then
path="Build/distribution/ladybird-wasm-${{ matrix.package_type }}.tar.gz"
if [ -f "${path}" ]; then
tar -xvzf "${path}"
bin/wasm -e run_sanity_check -w ${{ github.workspace }}/Libraries/LibWasm/Tests/CI/ci-sanity-check.wasm > wasm-repl-out.txt
if ! grep -q "Hello, World\!" wasm-repl-out.txt; then
echo "Sanity check failed: Hello, World\! not found in output."
exit 1
fi
fi
- name: Upload js package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: ladybird-js-${{ matrix.package_type }}
path: Build/distribution/ladybird-js-*.tar.gz
retention-days: 7
- name: Upload wasm package
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: ladybird-wasm-${{ matrix.package_type }}
path: Build/distribution/ladybird-wasm-*.tar.gz

View File

@ -53,7 +53,6 @@ jobs:
sudo apt-get install -y python3-venv
- name: 'Download JS repl artifact'
id: download-js-artifact
uses: dawidd6/action-download-artifact@v11
with:
run_id: ${{ github.event.workflow_run.id }}
@ -73,15 +72,16 @@ jobs:
commit_hash=$(cat js-repl/COMMIT)
echo "sha=${commit_hash}" >> "${GITHUB_OUTPUT}"
- name: 'Download Wasm repl artifact'
id: download-wasm-artifact
- name: 'Download wasm repl artifact'
uses: dawidd6/action-download-artifact@v11
with:
run_id: ${{ github.event.workflow_run.id }}
name: ladybird-wasm-${{ matrix.package_type }}
path: wasm-repl
if_no_artifact_found: warn
- name: 'Extract Wasm repl'
- name: 'Extract wasm repl'
if: ${{ hashFiles('wasm-repl/*.tar.gz') != '' }}
shell: bash
run: |
cd wasm-repl
@ -95,20 +95,27 @@ jobs:
source .venv/bin/activate
python3 -m pip install -r requirements.txt
run_options="--iterations=5"
run_options='--iterations=5'
js_path="${{ github.workspace }}/js-repl/bin/js"
run_options="${run_options} --executable=${js_path}"
wasm_path="${{ github.workspace }}/wasm-repl/bin/wasm"
if [ -x "${wasm_path}" ]; then
run_options="${run_options} --wasm-executable=${wasm_path}"
else
echo "Wasm repl not found; skipping wasm benchmarks."
fi
if [ "${{ github.event.workflow_run.event }}" = "workflow_dispatch" ]; then
# Upstream was run manually; we might run into failing benchmarks as a result of older builds.
run_options="${run_options} --continue-on-failure"
fi
./run.py \
--executable=${{ github.workspace }}/js-repl/bin/js \
--wasm-executable=${{ github.workspace }}/wasm-repl/bin/wasm \
${run_options}
./run.py ${run_options}
- name: 'Save results as an artifact'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: js-benchmarks-results-${{ matrix.os_name }}-${{ matrix.arch }}
path: js-benchmarks/results.json

View File

@ -203,15 +203,15 @@ jobs:
UBSAN_OPTIONS: 'log_path="${{ github.workspace }}/ubsan.log"'
- name: Test
if: ${{ inputs.build_preset != 'Fuzzers' && !contains(inputs.build_preset, 'Sanitizer') }}
if: ${{ inputs.build_preset != 'Fuzzers' && inputs.build_preset != 'All_Debug' && !contains(inputs.build_preset, 'Sanitizer') }}
working-directory: ${{ github.workspace }}
run: ctest --output-on-failure --test-dir Build --timeout 1800
env:
TESTS_ONLY: 1
- name: Upload LibWeb Test Artifacts
if: ${{ always() && inputs.build_preset != 'Fuzzers' }}
uses: actions/upload-artifact@v4
if: ${{ always() && inputs.build_preset != 'Fuzzers' && inputs.build_preset != 'All_Debug' }}
uses: actions/upload-artifact@v5
with:
name: libweb-test-artifacts-${{ inputs.os_name }}-${{ inputs.build_preset }}-${{ inputs.toolchain }}-${{ inputs.clang-plugins }}
path: ${{ github.workspace }}/Build/Lagom/Tests/LibWeb/test-web/test-dumps/

View File

@ -28,7 +28,7 @@ jobs:
- name: Install JS Dependencies
shell: bash
run: npm install -g prettier@3.3.3
run: npm install -g prettier@3.6.2
- name: Lint
run: ${{ github.workspace }}/Meta/lint-ci.sh

View File

@ -24,31 +24,36 @@ jobs:
error: "Commit message contains CRLF line breaks (only unix-style LF linebreaks are allowed)",
},
{
pattern: /^.+(\r?\n(\r?\n.*)*)?$/,
line: 2,
pattern: /^$/,
error: "Empty line between commit title and body is missing",
},
{
pattern: /^.{0,72}(?:\r?\n(?:(.{0,72})|(.*?([a-z]+:\/\/)?(([a-zA-Z0-9_]|-)+\.)+[a-z]{2,}(:\d+)?([a-zA-Z_0-9@:%\+.~\?&/=]|-)+).*?))*$/,
error: "Commit message lines are too long (maximum allowed is 72 characters, except for URLs)",
},
{
pattern: /^((?!^Merge branch )[\s\S])*$/,
line: 1,
pattern: /^(?!Merge branch )/,
error: "Commit is a git merge commit, use the rebase command instead",
},
{
pattern: /^\S.*?\S: .+/,
line: 1,
pattern: /^(Revert "|\S+: )/,
error: "Missing category in commit title (if this is a fix up of a previous commit, it should be squashed)",
},
{
line: 1,
pattern: /^\S.*?: [A-Z0-9]/,
error: "First word of commit after the subsystem is not capitalized",
},
{
pattern: /^.+[^.\n](\r?\n.*)*$/,
line: 1,
pattern: /[^.]$/,
error: "Commit title ends in a period",
},
{
pattern: /^((?!Signed-off-by: )[\s\S])*$/,
pattern: /^(.{0,72}|\s*[a-z]+:\/\/([a-z0-9\-]+\.)+[a-z]{2,}(:\d+)?(\/[a-zA-Z_0-9@:%+.~?&=\-]+)*)$/,
error: "Commit message lines are too long (maximum allowed is 72 characters, except for URLs on their own line)",
},
{
pattern: /^(?!Signed-off-by: )/,
error: "Commit body contains a Signed-off-by tag",
},
];
@ -69,14 +74,20 @@ jobs:
if (author !== null && excludedBotIds.includes(author.id)) {
continue;
}
const commitErrors = [];
for (const { pattern, error } of rules) {
if (!pattern.test(message)) {
commitErrors.push(error);
let commitErrors = [];
message.split("\n").forEach((line, index) => {
for (const { line: ruleLine, pattern, error } of rules) {
if (ruleLine !== undefined && ruleLine !== index + 1) {
continue;
}
if (!pattern.test(line)) {
commitErrors.push(error);
}
}
}
});
if (commitErrors.length > 0) {
const title = message.split("\n")[0];
commitErrors = [...new Set(commitErrors)]; // remove duplicates
errors.push([`${title} (${sha}):`, ...commitErrors].join("\n "));
}
}

View File

@ -13,9 +13,12 @@ on:
jobs:
auto-labeler:
runs-on: blacksmith-2vcpu-ubuntu-2404
if: github.repository == 'LadybirdBrowser/ladybird'
permissions:
contents: read
pull-requests: write
steps:
- uses: eps1lon/actions-label-merge-conflict@v3
with:

View File

@ -81,15 +81,17 @@ jobs:
flatpak:
if: github.repository == 'LadybirdBrowser/ladybird'
name: Flatpak ${{ matrix.arch }}
# FIXME: Temporarily run on GitHub runners until Blacksmith runners have overlay redirect_dir enabled.
strategy:
fail-fast: false
matrix:
arch: [ 'x86_64' ]
runner_labels: [ '["blacksmith-8vcpu-ubuntu-2404"]' ]
runner_labels: [ '["ubuntu-24.04"]' ]
include:
- arch: 'aarch64'
runner_labels: '["blacksmith-8vcpu-ubuntu-2404-arm"]'
runner_labels: '["ubuntu-24.04-arm"]'
secrets: inherit
uses: ./.github/workflows/flatpak-template.yml

View File

@ -3,7 +3,7 @@
"bracketSpacing": true,
"endOfLine": "lf",
"insertPragma": false,
"printWidth": 100,
"printWidth": 120,
"quoteProps": "as-needed",
"semi": true,
"singleQuote": false,

View File

@ -348,14 +348,9 @@ public:
{
#if __has_builtin(__builtin_add_overflow_p)
return __builtin_add_overflow_p(u, v, (T)0);
#elif __has_builtin(__builtin_add_overflow)
#else
T result;
return __builtin_add_overflow(u, v, &result);
#else
Checked checked;
checked = u;
checked += v;
return checked.has_overflow();
#endif
}
@ -364,14 +359,9 @@ public:
{
#if __has_builtin(__builtin_sub_overflow_p)
return __builtin_sub_overflow_p(u, v, (T)0);
#elif __has_builtin(__builtin_sub_overflow)
#else
T result;
return __builtin_sub_overflow(u, v, &result);
#else
Checked checked;
checked = u;
checked -= v;
return checked.has_overflow();
#endif
}
@ -404,27 +394,12 @@ public:
{
#if __has_builtin(__builtin_mul_overflow_p)
return __builtin_mul_overflow_p(u, v, (T)0);
#elif __has_builtin(__builtin_mul_overflow)
#else
T result;
return __builtin_mul_overflow(u, v, &result);
#else
Checked checked;
checked = u;
checked *= v;
return checked.has_overflow();
#endif
}
template<typename U, typename V, typename X>
[[nodiscard]] static constexpr bool multiplication_would_overflow(U u, V v, X x)
{
Checked checked;
checked = u;
checked *= v;
checked *= x;
return checked.has_overflow();
}
private:
T m_value {};
bool m_overflow { false };

View File

@ -276,11 +276,13 @@
# define ASAN_UNPOISON_MEMORY_REGION(addr, size) __asan_unpoison_memory_region(addr, size)
# define LSAN_REGISTER_ROOT_REGION(base, size) __lsan_register_root_region(base, size)
# define LSAN_UNREGISTER_ROOT_REGION(base, size) __lsan_unregister_root_region(base, size)
# define LSAN_IGNORE_OBJECT(base) __lsan_ignore_object(base)
#else
# define ASAN_POISON_MEMORY_REGION(addr, size)
# define ASAN_UNPOISON_MEMORY_REGION(addr, size)
# define LSAN_REGISTER_ROOT_REGION(base, size)
# define LSAN_UNREGISTER_ROOT_REGION(base, size)
# define LSAN_IGNORE_OBJECT(base)
#endif
#if __has_feature(blocks)

View File

@ -354,6 +354,24 @@ public:
}
return {};
}
template<typename TargetType>
ALWAYS_INLINE constexpr Span<TargetType> reinterpret()
{
if constexpr (sizeof(T) % sizeof(TargetType) != 0)
VERIFY((size() * sizeof(T)) % sizeof(TargetType) == 0);
return Span<TargetType> { reinterpret_cast<TargetType*>(data()), (size() * sizeof(T)) / sizeof(TargetType) };
}
template<typename TargetType>
ALWAYS_INLINE constexpr Span<TargetType const> reinterpret() const
{
if constexpr (sizeof(T) % sizeof(TargetType) != 0)
VERIFY((size() * sizeof(T)) % sizeof(TargetType) == 0);
return Span<TargetType const> { reinterpret_cast<TargetType const*>(data()), (size() * sizeof(T)) / sizeof(TargetType) };
}
};
template<typename T>
@ -381,14 +399,14 @@ template<typename T>
requires(IsTrivial<T>)
ReadonlyBytes to_readonly_bytes(Span<T> span)
{
return ReadonlyBytes { static_cast<void const*>(span.data()), span.size() * sizeof(T) };
return span.template reinterpret<u8 const>();
}
template<typename T>
requires(IsTrivial<T> && !IsConst<T>)
Bytes to_bytes(Span<T> span)
{
return Bytes { static_cast<void*>(span.data()), span.size() * sizeof(T) };
return span.template reinterpret<u8>();
}
}

View File

@ -248,17 +248,16 @@ Duration now_time_from_filetime()
Duration now_time_from_query_performance_counter()
{
static LARGE_INTEGER ticks_per_second;
// FIXME: Limit to microseconds for now, but could probably use nanos?
static float ticks_per_microsecond;
static f64 ticks_per_nanosecond;
if (ticks_per_second.QuadPart == 0) {
QueryPerformanceFrequency(&ticks_per_second);
VERIFY(ticks_per_second.QuadPart != 0);
ticks_per_microsecond = static_cast<float>(ticks_per_second.QuadPart) / 1'000'000.0F;
ticks_per_nanosecond = static_cast<f64>(ticks_per_second.QuadPart) / 1'000'000'000.0;
}
LARGE_INTEGER now_time {};
QueryPerformanceCounter(&now_time);
return Duration::from_microseconds(static_cast<i64>(now_time.QuadPart / ticks_per_microsecond));
return Duration::from_nanoseconds(static_cast<i64>(now_time.QuadPart / ticks_per_nanosecond));
}
Duration now_time_from_clock(int clock_id)

View File

@ -14,6 +14,7 @@
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Types.h>
#include <math.h>
#ifdef AK_OS_WINDOWS
# include <time.h>
@ -213,6 +214,19 @@ private:
public:
[[nodiscard]] constexpr static Duration from_seconds(i64 seconds) { return Duration(seconds, 0); }
[[nodiscard]] constexpr static Duration from_seconds_f64(f64 seconds)
{
VERIFY(!isnan(seconds));
if (seconds >= static_cast<f64>(NumericLimits<i64>::max()))
return from_seconds(NumericLimits<i64>::max());
if (seconds <= static_cast<f64>(NumericLimits<i64>::min()))
return from_seconds(NumericLimits<i64>::min());
i64 integer_seconds = static_cast<i64>(seconds);
if (static_cast<f64>(integer_seconds) > seconds)
integer_seconds--;
u32 integer_nanoseconds = static_cast<u32>((seconds - static_cast<f64>(integer_seconds)) * 1'000'000'000);
return Duration(integer_seconds, integer_nanoseconds);
}
[[nodiscard]] constexpr static Duration from_nanoseconds(i64 nanoseconds)
{
i64 seconds = sane_mod(nanoseconds, 1'000'000'000);

View File

@ -83,15 +83,6 @@ struct Traits<T> : public DefaultTraits<T> {
static constexpr bool is_trivially_serializable() { return Traits<UnderlyingType<T>>::is_trivially_serializable(); }
};
template<typename T>
requires(Detail::IsPointerOfType<char, T>) struct Traits<T> : public DefaultTraits<T> {
static unsigned hash(T const value) { return string_hash(value, strlen(value)); }
static constexpr bool equals(T const a, T const b) { return strcmp(a, b); }
static constexpr bool is_trivial() { return true; }
// NOTE: Trivial types always have fast equality checks.
static constexpr bool may_have_slow_equality_check() { return false; }
};
}
#if USING_AK_GLOBALLY

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2024, stasoid <stasoid@yahoo.com>
* Copyright (c) 2025, ayeteadoe <ayeteadoe@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -9,6 +10,7 @@
#pragma once
#include <AK/Assertions.h>
#include <AK/Platform.h>
#ifdef AK_OS_WINDOWS // needed for Swift
@ -18,4 +20,40 @@
# undef IN
# pragma comment(lib, "ws2_32.lib")
# include <io.h>
# include <stdlib.h>
inline void initiate_wsa()
{
WSADATA wsa;
WORD version = MAKEWORD(2, 2);
int rc = WSAStartup(version, &wsa);
VERIFY(rc == 0 && wsa.wVersion == version);
}
inline void terminate_wsa()
{
int rc = WSACleanup();
VERIFY(rc == 0);
}
static void invalid_parameter_handler(wchar_t const*, wchar_t const*, wchar_t const*, unsigned int, uintptr_t)
{
}
inline void override_crt_invalid_parameter_handler()
{
// Make _get_osfhandle return -1 instead of crashing on invalid fd in release (debug still __debugbreak's)
_set_invalid_parameter_handler(invalid_parameter_handler);
}
inline void windows_init()
{
initiate_wsa();
override_crt_invalid_parameter_handler();
}
inline void windows_shutdown()
{
terminate_wsa();
}
#endif

View File

@ -1,18 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="color-scheme" content="light dark">
<title>About URLs</title>
</head>
<body>
<h1>List of About URLs</h1>
<ul>
<li><a href="about:about">about:about</a></li>
<li><a href="about:newtab">about:newtab</a></li>
<li><a href="about:processes">about:processes</a></li>
<li><a href="about:settings">about:settings</a></li>
<li><a href="about:version">about:version</a></li>
</ul>
</body>
<head>
<meta charset="UTF-8" />
<meta name="color-scheme" content="light dark" />
<title>About URLs</title>
</head>
<body>
<h1>List of About URLs</h1>
<ul>
<li><a href="about:about">about:about</a></li>
<li><a href="about:newtab">about:newtab</a></li>
<li><a href="about:processes">about:processes</a></li>
<li><a href="about:settings">about:settings</a></li>
<li><a href="about:version">about:version</a></li>
</ul>
</body>
</html>

View File

@ -1,12 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>New Tab</title>
<style>
html {
color-scheme: light dark;
}
</style>
</head>
<head>
<meta charset="UTF-8" />
<title>New Tab</title>
<style>
html {
color-scheme: light dark;
}
</style>
</head>
</html>

View File

@ -300,10 +300,7 @@
<body>
<header>
<picture>
<source
srcset="resource://icons/128x128/app-browser.png"
media="(prefers-color-scheme: dark)"
/>
<source srcset="resource://icons/128x128/app-browser.png" media="(prefers-color-scheme: dark)" />
<img src="resource://icons/128x128/app-browser-dark.png" />
</picture>
<h1>Ladybird Settings</h1>
@ -350,9 +347,7 @@
<div class="card-group">
<label for="autocomplete-engine">Search Suggestions</label>
<p class="description">
Select the engine that will provide search suggestions.
</p>
<p class="description">Select the engine that will provide search suggestions.</p>
<select id="autocomplete-engine">
<option value="">Disable search suggestions</option>
<hr />
@ -379,9 +374,7 @@
<div class="card-body">
<div class="card-group">
<div class="inline-container">
<label for="global-privacy-control-toggle">
Enable Global Privacy Control
</label>
<label for="global-privacy-control-toggle">Enable Global Privacy Control</label>
<input id="global-privacy-control-toggle" type="checkbox" switch />
</div>
<p class="description">Tell websites not to sell or share your data.</p>
@ -471,11 +464,7 @@
<div class="form-group">
<label for="search-custom-url">Search URL</label>
<p class="description">Use %s as a placeholder for the search query</p>
<input
id="search-custom-url"
type="url"
placeholder="https://example.com/search?q=%s"
/>
<input id="search-custom-url" type="url" placeholder="https://example.com/search?q=%s" />
</div>
<div class="dialog-controls">
<button id="search-custom-add" class="primary-button">Add&nbsp;Engine</button>
@ -502,9 +491,7 @@
</div>
</div>
<div class="dialog-footer">
<button id="site-settings-remove-all" class="secondary-button">
Remove All Sites
</button>
<button id="site-settings-remove-all" class="secondary-button">Remove All Sites</button>
</div>
</dialog>

View File

@ -1,23 +1,6 @@
const defaultZoomLevelDropdown = document.querySelector("#default-zoom-level");
const zoomLevelFactors = [
1 / 3.0,
0.5,
2 / 3.0,
0.75,
0.8,
0.9,
1.0,
1.1,
1.25,
1.5,
1.75,
2.0,
2.5,
3.0,
4.0,
5.0,
];
const zoomLevelFactors = [1 / 3.0, 0.5, 2 / 3.0, 0.75, 0.8, 0.9, 1.0, 1.1, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 4.0, 5.0];
const zoomLevelFactorMap = zoomLevelFactors.map(factor => ({
factor,

View File

@ -1,65 +1,67 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Index of %path%</title>
<style>
html {
color-scheme: light dark;
}
<head>
<meta charset="UTF-8" />
<title>Index of %path%</title>
<style>
html {
color-scheme: light dark;
}
header {
margin-bottom: 10px;
}
header {
margin-bottom: 10px;
}
h1 {
display: inline;
}
h1 {
display: inline;
}
a {
text-decoration: none;
}
a {
text-decoration: none;
}
a:focus,
a:hover {
text-decoration: underline;
}
a:focus,
a:hover {
text-decoration: underline;
}
table {
font-family: monospace;
}
table {
font-family: monospace;
}
.folder,
.file,
.open-parent {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 5px;
background-size: contain;
}
.folder,
.file,
.open-parent {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 5px;
background-size: contain;
}
.folder {
background-image: url('resource://icons/32x32/filetype-folder.png');
}
.folder {
background-image: url("resource://icons/32x32/filetype-folder.png");
}
.file {
background-image: url('resource://icons/32x32/filetype-unknown.png');
}
.file {
background-image: url("resource://icons/32x32/filetype-unknown.png");
}
.open-parent {
background-image: url('resource://icons/16x16/open-parent-directory.png');
}
</style>
</head>
<body>
<header>
<span class="folder" style="width: 24px; height: 24px;"></span>
<h1>Index of %path%</h1>
</header>
<p><a href="%parent_url%"><span class="open-parent"></span>Open Parent Directory</a></p>
<hr>
%contents%
<hr>
</body>
.open-parent {
background-image: url("resource://icons/16x16/open-parent-directory.png");
}
</style>
</head>
<body>
<header>
<span class="folder" style="width: 24px; height: 24px"></span>
<h1>Index of %path%</h1>
</header>
<p>
<a href="%parent_url%"><span class="open-parent"></span>Open Parent Directory</a>
</p>
<hr />
%contents%
<hr />
</body>
</html>

View File

@ -1,58 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Error!</title>
<style>
:root {
color-scheme: light dark;
font-family: system-ui, sans-serif;
}
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
box-sizing: border-box;
margin: 0;
padding: 1rem;
text-align: center;
}
header {
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
margin-bottom: 1rem;
}
svg {
height: 64px;
width: auto;
stroke: currentColor;
fill: none;
stroke-width: 1.5;
stroke-linecap: round;
stroke-linejoin: round;
}
h1 {
margin: 0;
font-size: 1.5rem;
}
p {
font-size: 1rem;
color: #555;
}
</style>
</head>
<body>
<header>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.5 21.5">
<path d="M11.75.75h-9c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-13l-5-5z"/>
<path d="M10.75.75v4c0 1.1.9 2 2 2h4M5.75 9.75v2M11.75 9.75v2M5.75 16.75c1-2.67 5-2.67 6 0"/>
</svg>
<h1>Failed to load %failed_url%</h1>
</header>
<p>%error_message%</p>
</body>
<head>
<meta charset="UTF-8" />
<title>Error!</title>
<style>
:root {
color-scheme: light dark;
font-family: system-ui, sans-serif;
}
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
box-sizing: border-box;
margin: 0;
padding: 1rem;
text-align: center;
}
header {
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
margin-bottom: 1rem;
}
svg {
height: 64px;
width: auto;
stroke: currentColor;
fill: none;
stroke-width: 1.5;
stroke-linecap: round;
stroke-linejoin: round;
}
h1 {
margin: 0;
font-size: 1.5rem;
}
p {
font-size: 1rem;
color: #555;
}
</style>
</head>
<body>
<header>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.5 21.5">
<path d="M11.75.75h-9c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-13l-5-5z" />
<path d="M10.75.75v4c0 1.1.9 2 2 2h4M5.75 9.75v2M11.75 9.75v2M5.75 16.75c1-2.67 5-2.67 6 0" />
</svg>
<h1>Failed to load %failed_url%</h1>
</header>
<p>%error_message%</p>
</body>
</html>

View File

@ -1,58 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>About %browser_name%</title>
<style>
html {
color-scheme: light dark;
}
img {
float: left;
margin-right: 10px;
height: 48px;
}
th {
text-align: right;
}
td {
font-family: monospace;
}
</style>
</head>
<body>
<header>
<picture>
<source srcset="resource://icons/128x128/app-browser.png" media="(prefers-color-scheme: dark)">
<img src="resource://icons/128x128/app-browser-dark.png">
</picture>
<h1>About %browser_name%</h1>
</header>
<table>
<tr>
<th>Version:</th>
<td>%browser_version%</td>
</tr>
<tr>
<th>Arch:</th>
<td>%arch_name%</td>
</tr>
<tr>
<th>Operating System:</th>
<td>%os_name%</td>
</tr>
<tr>
<th>User Agent:</th>
<td>%user_agent%</td>
</tr>
<tr>
<th>Command Line:</th>
<td>%command_line%</td>
</tr>
<tr>
<th>Executable Path:</th>
<td>%executable_path%</td>
</tr>
</table>
</body>
<head>
<meta charset="UTF-8" />
<title>About %browser_name%</title>
<style>
html {
color-scheme: light dark;
}
img {
float: left;
margin-right: 10px;
height: 48px;
}
th {
text-align: right;
}
td {
font-family: monospace;
}
</style>
</head>
<body>
<header>
<picture>
<source srcset="resource://icons/128x128/app-browser.png" media="(prefers-color-scheme: dark)" />
<img src="resource://icons/128x128/app-browser-dark.png" />
</picture>
<h1>About %browser_name%</h1>
</header>
<table>
<tr>
<th>Version:</th>
<td>%browser_version%</td>
</tr>
<tr>
<th>Arch:</th>
<td>%arch_name%</td>
</tr>
<tr>
<th>Operating System:</th>
<td>%os_name%</td>
</tr>
<tr>
<th>User Agent:</th>
<td>%user_agent%</td>
</tr>
<tr>
<th>Command Line:</th>
<td>%command_line%</td>
</tr>
<tr>
<th>Executable Path:</th>
<td>%executable_path%</td>
</tr>
</table>
</body>
</html>

View File

@ -82,6 +82,18 @@
"VCPKG_OVERLAY_TRIPLETS": "${fileDir}/Meta/CMake/vcpkg/debug-triplets"
}
},
{
"name": "All_Debug",
"inherits": "unix_base",
"displayName": "All Debug Config",
"description": "All Debug Unix build",
"binaryDir": "${fileDir}/Build/alldebug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"VCPKG_OVERLAY_TRIPLETS": "${fileDir}/Meta/CMake/vcpkg/debug-triplets",
"ENABLE_ALL_THE_DEBUG_MACROS": "ON"
}
},
{
"name": "Windows_Experimental",
"inherits": "windows_base",

View File

@ -9,9 +9,6 @@ We currently use gcc-14 and clang-20 in our CI pipeline. If these versions are n
CMake 3.25 or newer must be available in $PATH.
> [!NOTE]
> In all of the below lists of packages, the Qt6 multimedia package is not needed if your Linux system supports PulseAudio.
---
### Debian/Ubuntu:
@ -67,34 +64,34 @@ sudo apt update && sudo apt install g++-14 libstdc++-14-dev
#### Audio support:
- Recommendation: Install PulseAudio development package:
- Install PulseAudio development package:
```bash
sudo apt install libpulse-dev
```
- Alternative: Install Qt6's multimedia package:
```bash
sudo apt install qt6-multimedia-dev
```
### Arch Linux/Manjaro:
```
sudo pacman -S --needed autoconf-archive automake base-devel ccache cmake curl libgl nasm ninja qt6-base qt6-multimedia qt6-tools qt6-wayland ttf-liberation tar unzip zip
sudo pacman -S --needed autoconf-archive automake base-devel ccache cmake curl libgl nasm ninja qt6-base qt6-tools qt6-wayland ttf-liberation tar unzip zip
```
Optionally, install the PulseAudio headers for audio playback support:
```
sudo pacman -S libpulse
```
### Fedora or derivatives:
```
sudo dnf install autoconf-archive automake ccache cmake curl git libdrm-devel liberation-sans-fonts libglvnd-devel libtool nasm ninja-build patchelf perl-FindBin perl-IPC-Cmd perl-lib perl-Time-Piece qt6-qtbase-devel qt6-qtmultimedia-devel qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static
sudo dnf install autoconf-archive automake ccache cmake curl git libdrm-devel liberation-sans-fonts libglvnd-devel libtool nasm ninja-build patchelf perl-FindBin perl-IPC-Cmd perl-lib perl-Time-Piece qt6-qtbase-devel qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static
```
### openSUSE:
```
sudo zypper install autoconf-archive automake ccache cmake curl gcc14 gcc14-c++ git liberation-fonts libglvnd-devel libtool nasm ninja qt6-base-devel qt6-multimedia-devel qt6-tools-devel qt6-wayland-devel tar unzip zip
sudo zypper install autoconf-archive automake ccache cmake curl gcc14 gcc14-c++ git liberation-fonts libglvnd-devel libtool nasm ninja qt6-base-devel qt6-tools-devel qt6-wayland-devel tar unzip zip
```
If one or more of the base repository packages are flagged as having an out-of-date version during the build process, you may need add the `devel:tools:building` repository. For example, on Leap 15.6, the `autoconf` package might be version 2.69, whereas the `gperf` package requires 2.70 to build.
@ -119,11 +116,10 @@ Nothing to do.
> sudo zypper install autoconf-2.72-80.d_t_b.1.noarch
```
It is currently recommended to install the `libpulse-devel` package to avoid runtime dynamic linking issues. If issues persist you may need to remove the `qt6-multimedia-devel` package to avoid linking issues.
It is necessary to install the `libpulse-devel` package to enable audio playback:
```
sudo zypper install libpulse-devel
sudo zypper remove qt6-multimedia-devel
```
The build process requires at least python3.7; openSUSE Leap only features Python 3.6 as default, so it is recommendable to install the package `python312` and create a virtual environment (venv) in this case.
@ -141,7 +137,7 @@ This virtual environment can be created once and reused in future shell sessions
```
sudo xbps-install -Su # (optional) ensure packages are up to date to avoid "Transaction aborted due to unresolved dependencies."
sudo xbps-install -S git bash gcc python3 curl cmake zip unzip linux-headers make pkg-config autoconf automake autoconf-archive nasm MesaLib-devel ninja qt6-base-devel qt6-multimedia-devel qt6-tools-devel qt6-wayland-devel
sudo xbps-install -S git bash gcc python3 curl cmake zip unzip linux-headers make pkg-config autoconf automake autoconf-archive nasm MesaLib-devel ninja qt6-base-devel qt6-tools-devel qt6-wayland-devel
```
### NixOS or with Nix:
@ -209,7 +205,7 @@ Or, download a version of Gradle >= 8.0.0, and run the ``gradlew`` program in ``
### FreeBSD
```
pkg install autoconf-archive automake autoconf bash cmake curl gmake gn libtool libxcb libxkbcommon libX11 libXrender libXi nasm ninja patchelf pkgconf python3 qt6-base qt6-multimedia unzip zip
pkg install autoconf-archive automake autoconf bash cmake curl gmake gn libdrm libtool libxcb libxkbcommon libX11 libXrender libXi nasm ninja patchelf pkgconf python3 qt6-base unzip zip
```
## Build steps

View File

@ -36,12 +36,6 @@ Run ``./Meta/ladybird.py run ladybird`` at least once to generate the ``compile_
- clangd has a tendency to crash when stressing bleeding edge compiler features. You can usually just restart it via the command palette. If that doesn't help, close currently open C++ files and/or switch branches before restarting, which helps sometimes.
### DSL syntax highlighting
There's a syntax highlighter extension for domain specific language files (.idl, .ipc) called "SerenityOS DSL Syntax Highlight", available [here](https://marketplace.visualstudio.com/items?itemName=kleinesfilmroellchen.serenity-dsl-syntaxhighlight) or [here](https://open-vsx.org/extension/kleinesfilmroellchen/serenity-dsl-syntaxhighlight).
The extension provides syntax highlighting for these files, [Web IDL](https://webidl.spec.whatwg.org/), and LibJS's
serialization format (no extension) as output by js with the -d option.
### Microsoft C/C++ tools
Note that enabling the extension in the same workspace as the clangd and clang-format extensions will cause conflicts.

View File

@ -1,5 +1,6 @@
add_subdirectory(LibCompress)
add_subdirectory(LibCrypto)
add_subdirectory(LibDatabase)
add_subdirectory(LibDiff)
add_subdirectory(LibDNS)
add_subdirectory(LibGC)

View File

@ -13,9 +13,6 @@
#if defined(AK_OS_MACOS) || defined(AK_OS_IOS)
# include <crt_externs.h>
#elif defined(AK_OS_FREEBSD)
// FIXME: Remove this once FreeBSD exports environ from libc
extern "C" __attribute__((weak)) char** environ;
#elif !defined(AK_OS_WINDOWS)
extern "C" char** environ;
#endif

View File

@ -93,7 +93,7 @@ int EventLoopImplementationWindows::exec()
VERIFY_NOT_REACHED();
}
size_t EventLoopImplementationWindows::pump(PumpMode)
size_t EventLoopImplementationWindows::pump(PumpMode pump_mode)
{
auto& event_queue = ThreadEventQueue::current();
auto* thread_data = ThreadData::the();
@ -115,7 +115,9 @@ size_t EventLoopImplementationWindows::pump(PumpMode)
event_handles.append(entry.key.handle);
bool has_pending_events = event_queue.has_pending_events();
int timeout = has_pending_events ? 0 : INFINITE;
DWORD timeout = 0;
if (!has_pending_events && pump_mode == PumpMode::WaitForEvents)
timeout = INFINITE;
DWORD result = WaitForMultipleObjects(event_count, event_handles.data(), FALSE, timeout);
if (result == WAIT_TIMEOUT) {
// FIXME: This verification sometimes fails with ERROR_INVALID_HANDLE, but when I check

View File

@ -137,7 +137,7 @@ void PosixSocketHelper::close()
// shutdown is required for another end to receive FD_CLOSE
shutdown(m_fd, SD_BOTH);
MUST(System::close(m_fd));
closesocket(m_fd);
m_fd = -1;
}

View File

@ -129,6 +129,27 @@ ByteString StandardPaths::videos_directory()
return LexicalPath::canonicalized_path(builder.to_byte_string());
}
ByteString StandardPaths::cache_directory()
{
#if defined(AK_OS_WINDOWS) || defined(AK_OS_HAIKU)
return user_data_directory();
#else
if (auto cache_directory = get_environment_if_not_empty("XDG_CACHE_HOME"sv); cache_directory.has_value())
return LexicalPath::canonicalized_path(*cache_directory);
StringBuilder builder;
builder.append(home_directory());
# if defined(AK_OS_MACOS)
builder.append("/Library/Caches"sv);
# else
builder.append("/.cache"sv);
# endif
return LexicalPath::canonicalized_path(builder.to_byte_string());
#endif
}
ByteString StandardPaths::config_directory()
{
StringBuilder builder;

View File

@ -22,6 +22,7 @@ public:
static ByteString pictures_directory();
static ByteString videos_directory();
static ByteString tempfile_directory();
static ByteString cache_directory();
static ByteString config_directory();
static ByteString user_data_directory();
static Vector<ByteString> system_data_directories();

View File

@ -870,4 +870,27 @@ ErrorOr<void> set_close_on_exec(int fd, bool enabled)
return {};
}
ErrorOr<size_t> transfer_file_through_pipe(int source_fd, int target_fd, size_t source_offset, size_t source_length)
{
#if defined(AK_OS_LINUX)
auto sent = ::splice(source_fd, reinterpret_cast<off_t*>(&source_offset), target_fd, nullptr, source_length, SPLICE_F_MOVE | SPLICE_F_NONBLOCK);
if (sent < 0)
return Error::from_syscall("send_file_to_pipe"sv, errno);
return sent;
#else
static auto page_size = PAGE_SIZE;
// mmap requires the offset to be page-aligned, so we must handle that here.
auto aligned_source_offset = (source_offset / page_size) * page_size;
auto offset_adjustment = source_offset - aligned_source_offset;
auto mapped_source_length = source_length + offset_adjustment;
// FIXME: We could use MappedFile here if we update it to support offsets and not auto-close the source fd.
auto* mapped = TRY(mmap(nullptr, mapped_source_length, PROT_READ, MAP_SHARED, source_fd, aligned_source_offset));
ScopeGuard guard { [&]() { (void)munmap(mapped, mapped_source_length); } };
return TRY(write(target_fd, { static_cast<u8*>(mapped) + offset_adjustment, source_length }));
#endif
}
}

View File

@ -190,4 +190,6 @@ bool is_socket(int fd);
ErrorOr<void> sleep_ms(u32 milliseconds);
ErrorOr<void> set_close_on_exec(int fd, bool enabled);
ErrorOr<size_t> transfer_file_through_pipe(int source_fd, int target_fd, size_t source_offset, size_t source_length);
}

View File

@ -23,24 +23,6 @@ namespace Core::System {
int windows_socketpair(SOCKET socks[2], int make_overlapped);
static void invalid_parameter_handler(wchar_t const*, wchar_t const*, wchar_t const*, unsigned int, uintptr_t)
{
}
static int init_crt_and_wsa()
{
WSADATA wsa;
WORD version = MAKEWORD(2, 2);
int rc = WSAStartup(version, &wsa);
VERIFY(!rc && wsa.wVersion == version);
// Make _get_osfhandle return -1 instead of crashing on invalid fd in release (debug still __debugbreak's)
_set_invalid_parameter_handler(invalid_parameter_handler);
return 0;
}
static auto dummy = init_crt_and_wsa();
ErrorOr<int> open(StringView path, int options, mode_t mode)
{
ByteString str = path;
@ -403,4 +385,14 @@ ErrorOr<void> kill(pid_t pid, int signal)
return {};
}
ErrorOr<size_t> transfer_file_through_pipe(int source_fd, int target_fd, size_t source_offset, size_t source_length)
{
(void)source_fd;
(void)target_fd;
(void)source_offset;
(void)source_length;
return Error::from_string_literal("FIXME: Implement System::transfer_file_through_pipe on Windows (for HTTP disk cache)");
}
}

View File

@ -0,0 +1,8 @@
set(SOURCES
Database.cpp
)
find_package(SQLite3 REQUIRED)
ladybird_lib(LibDatabase database EXPLICIT_SYMBOL_EXPORT)
target_link_libraries(LibDatabase PRIVATE LibCore SQLite::SQLite3)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022-2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -8,12 +8,11 @@
#include <AK/String.h>
#include <AK/Time.h>
#include <LibCore/Directory.h>
#include <LibCore/StandardPaths.h>
#include <LibWebView/Database.h>
#include <LibDatabase/Database.h>
#include <sqlite3.h>
namespace WebView {
namespace Database {
static constexpr StringView sql_error(int error_code)
{
@ -39,13 +38,25 @@ static constexpr StringView sql_error(int error_code)
} \
})
ErrorOr<NonnullRefPtr<Database>> Database::create()
{
// FIXME: Move this to a generic "Ladybird data directory" helper.
auto database_path = ByteString::formatted("{}/Ladybird", Core::StandardPaths::user_data_directory());
TRY(Core::Directory::create(database_path, Core::Directory::CreateDirectories::Yes));
#define ENUMERATE_SQL_TYPES \
__ENUMERATE_TYPE(String) \
__ENUMERATE_TYPE(UnixDateTime) \
__ENUMERATE_TYPE(i8) \
__ENUMERATE_TYPE(i16) \
__ENUMERATE_TYPE(i32) \
__ENUMERATE_TYPE(long) \
__ENUMERATE_TYPE(long long) \
__ENUMERATE_TYPE(u8) \
__ENUMERATE_TYPE(u16) \
__ENUMERATE_TYPE(u32) \
__ENUMERATE_TYPE(unsigned long) \
__ENUMERATE_TYPE(unsigned long long) \
__ENUMERATE_TYPE(bool)
auto database_file = ByteString::formatted("{}/Ladybird.db", database_path);
ErrorOr<NonnullRefPtr<Database>> Database::create(ByteString const& directory, StringView name)
{
TRY(Core::Directory::create(directory, Core::Directory::CreateDirectories::Yes));
auto database_file = ByteString::formatted("{}/{}.db", directory, name);
sqlite3* m_database { nullptr };
SQL_TRY(sqlite3_open(database_file.characters(), &m_database));
@ -67,7 +78,7 @@ Database::~Database()
sqlite3_close(m_database);
}
ErrorOr<Database::StatementID> Database::prepare_statement(StringView statement)
ErrorOr<StatementID> Database::prepare_statement(StringView statement)
{
sqlite3_stmt* prepared_statement { nullptr };
SQL_TRY(sqlite3_prepare_v2(m_database, statement.characters_without_null_termination(), static_cast<int>(statement.length()), &prepared_statement, nullptr));
@ -111,18 +122,21 @@ void Database::apply_placeholder(StatementID statement_id, int index, ValueType
StringView string { value };
SQL_MUST(sqlite3_bind_text(statement, index, string.characters_without_null_termination(), static_cast<int>(string.length()), SQLITE_TRANSIENT));
} else if constexpr (IsSame<ValueType, UnixDateTime>) {
SQL_MUST(sqlite3_bind_int64(statement, index, value.offset_to_epoch().to_milliseconds()));
} else if constexpr (IsSame<ValueType, int>) {
SQL_MUST(sqlite3_bind_int(statement, index, value));
} else if constexpr (IsSame<ValueType, bool>) {
SQL_MUST(sqlite3_bind_int(statement, index, static_cast<int>(value)));
apply_placeholder(statement_id, index, value.offset_to_epoch().to_milliseconds());
} else if constexpr (IsIntegral<ValueType>) {
if constexpr (sizeof(ValueType) <= sizeof(int))
SQL_MUST(sqlite3_bind_int(statement, index, static_cast<int>(value)));
else
SQL_MUST(sqlite3_bind_int64(statement, index, static_cast<sqlite3_int64>(value)));
} else {
static_assert(DependentFalse<ValueType>);
}
}
template void Database::apply_placeholder(StatementID, int, String const&);
template void Database::apply_placeholder(StatementID, int, UnixDateTime const&);
template void Database::apply_placeholder(StatementID, int, int const&);
template void Database::apply_placeholder(StatementID, int, bool const&);
#define __ENUMERATE_TYPE(type) \
template DATABASE_API void Database::apply_placeholder(StatementID, int, type const&);
ENUMERATE_SQL_TYPES
#undef __ENUMERATE_TYPE
template<typename ValueType>
ValueType Database::result_column(StatementID statement_id, int column)
@ -133,20 +147,21 @@ ValueType Database::result_column(StatementID statement_id, int column)
auto const* text = reinterpret_cast<char const*>(sqlite3_column_text(statement, column));
return MUST(String::from_utf8(StringView { text, strlen(text) }));
} else if constexpr (IsSame<ValueType, UnixDateTime>) {
auto milliseconds = sqlite3_column_int64(statement, column);
auto milliseconds = result_column<sqlite3_int64>(statement_id, column);
return UnixDateTime::from_milliseconds_since_epoch(milliseconds);
} else if constexpr (IsSame<ValueType, int>) {
return sqlite3_column_int(statement, column);
} else if constexpr (IsSame<ValueType, bool>) {
return static_cast<bool>(sqlite3_column_int(statement, column));
} else if constexpr (IsIntegral<ValueType>) {
if constexpr (sizeof(ValueType) <= sizeof(int))
return static_cast<ValueType>(sqlite3_column_int(statement, column));
else
return static_cast<ValueType>(sqlite3_column_int64(statement, column));
} else {
static_assert(DependentFalse<ValueType>);
}
VERIFY_NOT_REACHED();
}
template String Database::result_column(StatementID, int);
template UnixDateTime Database::result_column(StatementID, int);
template int Database::result_column(StatementID, int);
template bool Database::result_column(StatementID, int);
#define __ENUMERATE_TYPE(type) \
template DATABASE_API type Database::result_column(StatementID, int);
ENUMERATE_SQL_TYPES
#undef __ENUMERATE_TYPE
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022-2025, Tim Flynn <trflynn89@ladybird.org>
* Copyright (c) 2023, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -13,19 +13,18 @@
#include <AK/RefCounted.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
#include <LibWebView/Forward.h>
#include <LibDatabase/Forward.h>
struct sqlite3;
struct sqlite3_stmt;
namespace WebView {
namespace Database {
class WEBVIEW_API Database : public RefCounted<Database> {
class DATABASE_API Database : public RefCounted<Database> {
public:
static ErrorOr<NonnullRefPtr<Database>> create();
static ErrorOr<NonnullRefPtr<Database>> create(ByteString const& directory, StringView name);
~Database();
using StatementID = size_t;
using OnResult = Function<void(StatementID)>;
ErrorOr<StatementID> prepare_statement(StringView statement);

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <LibDatabase/Export.h>
namespace Database {
class Database;
using StatementID = size_t;
}

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibDevTools/Actors/AccessibilityActor.h>
#include <LibDevTools/Actors/AccessibilityWalkerActor.h>
#include <LibDevTools/Actors/TabActor.h>
#include <LibDevTools/DevToolsDelegate.h>
#include <LibDevTools/DevToolsServer.h>
namespace DevTools {
NonnullRefPtr<AccessibilityActor> AccessibilityActor::create(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab)
{
return adopt_ref(*new AccessibilityActor(devtools, move(name), move(tab)));
}
AccessibilityActor::AccessibilityActor(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab)
: Actor(devtools, move(name))
, m_tab(move(tab))
{
}
AccessibilityActor::~AccessibilityActor() = default;
void AccessibilityActor::enable()
{
if (m_enabled)
return;
m_enabled = true;
JsonObject init_event;
init_event.set("type"sv, "init"sv);
send_message(move(init_event));
}
void AccessibilityActor::handle_message(Message const& message)
{
if (message.type == "bootstrap"sv) {
JsonObject bootstrap;
bootstrap.set("enabled"sv, m_enabled);
JsonObject response;
response.set("state"sv, move(bootstrap));
send_response(message, move(response));
return;
}
if (message.type == "getSimulator"sv) {
// FIXME: This would return a SimulatorActor for applying visual filters over the whole viewport.
// For now, return null.
JsonObject response;
response.set("simulator"sv, JsonValue {});
send_response(message, move(response));
return;
}
if (message.type == "getTraits"sv) {
JsonObject traits;
traits.set("tabbingOrder"sv, true);
JsonObject response;
response.set("traits"sv, move(traits));
send_response(message, move(response));
return;
}
if (message.type == "getWalker"sv) {
if (auto tab = m_tab.strong_ref()) {
devtools().delegate().inspect_accessibility_tree(tab->description(),
async_handler<AccessibilityActor>(message, [](auto& self, auto accessibility_tree, auto& response) {
if (!AccessibilityWalkerActor::is_suitable_for_accessibility_inspection(accessibility_tree)) {
dbgln_if(DEVTOOLS_DEBUG, "Did not receive a suitable accessibility tree: {}", accessibility_tree);
return;
}
self.received_accessibility_tree(response, move(accessibility_tree.as_object()));
}));
}
return;
}
send_unrecognized_packet_type_error(message);
}
void AccessibilityActor::received_accessibility_tree(JsonObject& response, JsonObject accessibility_tree)
{
auto& walker_actor = devtools().register_actor<AccessibilityWalkerActor>(m_tab, move(accessibility_tree));
m_walker = walker_actor;
JsonObject walker;
walker.set("actor"sv, walker_actor.name());
response.set("walker"sv, move(walker));
}
RefPtr<TabActor> AccessibilityActor::tab_for(WeakPtr<AccessibilityActor> const& weak_accessibility)
{
if (auto accessibility = weak_accessibility.strong_ref())
return accessibility->m_tab.strong_ref();
return {};
}
RefPtr<AccessibilityWalkerActor> AccessibilityActor::walker_for(WeakPtr<AccessibilityActor> const& weak_accessibility)
{
if (auto accessibility = weak_accessibility.strong_ref())
return accessibility->m_walker.strong_ref();
return {};
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibDevTools/Actor.h>
namespace DevTools {
class DEVTOOLS_API AccessibilityActor final : public Actor {
public:
static constexpr auto base_name = "accessibility"sv;
static NonnullRefPtr<AccessibilityActor> create(DevToolsServer&, String name, WeakPtr<TabActor>);
virtual ~AccessibilityActor() override;
static RefPtr<TabActor> tab_for(WeakPtr<AccessibilityActor> const&);
static RefPtr<AccessibilityWalkerActor> walker_for(WeakPtr<AccessibilityActor> const&);
void enable();
private:
AccessibilityActor(DevToolsServer&, String name, WeakPtr<TabActor>);
virtual void handle_message(Message const&) override;
void received_accessibility_tree(JsonObject& response, JsonObject accessibility_tree);
WeakPtr<TabActor> m_tab;
WeakPtr<AccessibilityWalkerActor> m_walker;
bool m_enabled { false };
};
}

View File

@ -0,0 +1,160 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Enumerate.h>
#include <LibDevTools/Actors/AccessibilityNodeActor.h>
#include <LibDevTools/Actors/AccessibilityWalkerActor.h>
namespace DevTools {
NonnullRefPtr<AccessibilityNodeActor> AccessibilityNodeActor::create(DevToolsServer& devtools, String name, NodeIdentifier node_identifier, WeakPtr<AccessibilityWalkerActor> walker)
{
return adopt_ref(*new AccessibilityNodeActor(devtools, move(name), move(node_identifier), move(walker)));
}
AccessibilityNodeActor::AccessibilityNodeActor(DevToolsServer& devtools, String name, NodeIdentifier node_identifier, WeakPtr<AccessibilityWalkerActor> walker)
: Actor(devtools, move(name))
, m_node_identifier(node_identifier)
, m_walker(move(walker))
{
}
AccessibilityNodeActor::~AccessibilityNodeActor() = default;
void AccessibilityNodeActor::handle_message(Message const& message)
{
if (message.type == "audit"sv) {
// FIXME: Implement accessibility audits.
JsonObject audit;
JsonObject response;
response.set("type"sv, "audited"sv);
response.set("audit"sv, audit);
send_response(message, move(response));
// For whatever reason, we need to send this a second time with no `type`.
JsonObject second_response;
second_response.set("audit"sv, audit);
send_message(move(second_response));
return;
}
if (message.type == "children"sv) {
if (auto walker = m_walker.strong_ref()) {
auto ancestor_node = walker->accessibility_node(name());
if (!ancestor_node.has_value()) {
send_unknown_actor_error(message, name());
return;
}
JsonArray children;
if (auto child_nodes = ancestor_node->node.get_array("children"sv); child_nodes.has_value()) {
child_nodes->for_each([&](JsonValue const& child) {
children.must_append(walker->serialize_node(child.as_object()));
});
}
JsonObject response;
response.set("children"sv, move(children));
send_response(message, move(response));
return;
}
send_unknown_actor_error(message, name());
return;
}
if (message.type == "getRelations"sv) {
if (auto walker = m_walker.strong_ref()) {
auto accessibility_node = walker->accessibility_node(name());
if (!accessibility_node.has_value()) {
send_unknown_actor_error(message, name());
return;
}
auto root_node = walker->root_accessibility_node();
JsonArray relations;
auto report_relation = [&](StringView type, Node const& node) {
JsonArray targets;
MUST(targets.append(walker->serialize_node(node.node)));
JsonObject relation;
relation.set("targets"sv, move(targets));
relation.set("type"sv, type);
MUST(relations.append(move(relation)));
};
// For the root node, list itself as an "embeds" relation.
if (root_node.has_value() && root_node->identifier == accessibility_node->identifier)
report_relation("embeds"sv, root_node.value());
// For all nodes, list the root as the "containing document" relation.
report_relation("containing document"sv, root_node.value());
// FIXME: Figure out what other relations we need to report here.
JsonObject response;
response.set("relations"sv, move(relations));
send_response(message, move(response));
return;
}
send_unknown_actor_error(message, name());
return;
}
if (message.type == "hydrate"sv) {
auto accessibility_node = AccessibilityWalkerActor::accessibility_node_for(m_walker, name());
if (!accessibility_node.has_value()) {
send_unknown_actor_error(message, name());
return;
}
auto parent_node = AccessibilityWalkerActor::parent_of_accessibility_node_for(m_walker, *accessibility_node);
auto const& node_json = accessibility_node->node;
auto dom_node_type = parse_dom_node_type(node_json.get_string("type"sv).value());
auto index_in_parent = 0;
if (parent_node.has_value()) {
if (auto parent_children = parent_node->node.get_array("children"sv); parent_children.has_value()) {
for (auto const& [index, child] : enumerate(parent_children->values())) {
if (child.as_object().get_i64("id"sv) == accessibility_node->identifier.id) {
index_in_parent = index;
break;
}
}
}
}
// FIXME: Populate these.
JsonArray actions;
JsonObject attributes;
JsonArray states;
JsonObject properties;
properties.set("actions"sv, move(actions));
properties.set("attributes"sv, move(attributes));
properties.set("description"sv, node_json.get_string("description"sv).value_or({}));
properties.set("domNodeType"sv, to_underlying(dom_node_type));
properties.set("indexInParent"sv, index_in_parent);
// FIXME: Value of the accesskey attribute
properties.set("keyboardShortcut"sv, ""sv);
properties.set("states"sv, move(states));
// FIXME: Implement
properties.set("value"sv, ""sv);
JsonObject response;
response.set("properties"sv, move(properties));
send_response(message, move(response));
return;
}
send_unrecognized_packet_type_error(message);
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibDevTools/Actor.h>
#include <LibDevTools/Actors/NodeActor.h>
#include <LibDevTools/Forward.h>
#include <LibWeb/Forward.h>
namespace DevTools {
class DEVTOOLS_API AccessibilityNodeActor final : public Actor {
public:
static constexpr auto base_name = "accessibility-node"sv;
static NonnullRefPtr<AccessibilityNodeActor> create(DevToolsServer&, String name, NodeIdentifier, WeakPtr<AccessibilityWalkerActor>);
virtual ~AccessibilityNodeActor() override;
NodeIdentifier const& node_identifier() const { return m_node_identifier; }
WeakPtr<AccessibilityWalkerActor> const& walker() const { return m_walker; }
private:
AccessibilityNodeActor(DevToolsServer&, String name, NodeIdentifier, WeakPtr<AccessibilityWalkerActor>);
virtual void handle_message(Message const&) override;
NodeIdentifier m_node_identifier;
WeakPtr<AccessibilityWalkerActor> m_walker;
};
}

View File

@ -0,0 +1,227 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibDevTools/Actors/AccessibilityNodeActor.h>
#include <LibDevTools/Actors/AccessibilityWalkerActor.h>
#include <LibDevTools/Actors/TabActor.h>
#include <LibDevTools/DevToolsServer.h>
namespace DevTools {
NonnullRefPtr<AccessibilityWalkerActor> AccessibilityWalkerActor::create(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, JsonObject accessibility_tree)
{
return adopt_ref(*new AccessibilityWalkerActor(devtools, move(name), move(tab), move(accessibility_tree)));
}
AccessibilityWalkerActor::AccessibilityWalkerActor(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, JsonObject accessibility_tree)
: Actor(devtools, move(name))
, m_tab(move(tab))
, m_accessibility_tree(move(accessibility_tree))
{
populate_accessibility_tree_cache();
}
AccessibilityWalkerActor::~AccessibilityWalkerActor() = default;
void AccessibilityWalkerActor::handle_message(Message const& message)
{
if (message.type == "children"sv) {
JsonArray children;
MUST(children.append(serialize_root()));
JsonObject response;
response.set("children"sv, move(children));
send_response(message, move(response));
return;
}
if (message.type == "hideTabbingOrder"sv) {
// Blank response is expected.
send_response(message, JsonObject {});
return;
}
if (message.type == "highlightAccessible"sv) {
// FIXME: Highlight things.
JsonObject response;
response.set("value"sv, false);
send_response(message, move(response));
return;
}
if (message.type == "unhighlight"sv) {
// FIXME: Unhighlight things.
send_response(message, JsonObject {});
return;
}
send_unrecognized_packet_type_error(message);
}
bool AccessibilityWalkerActor::is_suitable_for_accessibility_inspection(JsonValue const& node)
{
if (!node.is_object())
return false;
auto const& object = node.as_object();
if (!object.has_string("type"sv) || !object.has_string("role"sv))
return false;
if (!object.has_i64("id"sv))
return false;
if (auto text = object.get_string("text"sv); text.has_value()) {
if (AK::StringUtils::is_whitespace(*text))
return false;
}
return true;
}
JsonValue AccessibilityWalkerActor::serialize_root() const
{
return serialize_node(m_accessibility_tree);
}
JsonValue AccessibilityWalkerActor::serialize_node(JsonObject const& node) const
{
auto tab = m_tab.strong_ref();
if (!tab)
return {};
auto actor = node.get_string("actor"sv);
if (!actor.has_value())
return {};
auto name = node.get_string("name"sv).value_or(""_string);
auto type = node.get_string("type"sv).release_value();
auto role = node.get_string("role"sv).release_value();
auto child_count = 0u;
if (auto children = node.get_array("children"sv); children.has_value())
child_count = children->size();
JsonObject serialized;
serialized.set("actor"sv, actor.release_value());
serialized.set("name"sv, move(name));
serialized.set("role"sv, move(role));
serialized.set("useChildTargetToFetchChildren"sv, false);
serialized.set("childCount"sv, child_count);
serialized.set("checks"sv, JsonObject {});
return serialized;
}
Optional<Node> AccessibilityWalkerActor::accessibility_node_for(WeakPtr<AccessibilityWalkerActor> const& weak_walker, StringView actor)
{
if (auto walker = weak_walker.strong_ref())
return walker->accessibility_node(actor);
return {};
}
Optional<Node> AccessibilityWalkerActor::accessibility_node(StringView actor)
{
auto tab = m_tab.strong_ref();
if (!tab)
return {};
auto maybe_node = m_actor_to_node_map.get(actor);
if (!maybe_node.has_value() || !maybe_node.value())
return {};
auto const& node = *maybe_node.value();
auto identifier = NodeIdentifier::for_node(node);
return Node { .node = node, .identifier = move(identifier), .tab = tab.release_nonnull() };
}
Optional<Node> AccessibilityWalkerActor::parent_of_accessibility_node_for(WeakPtr<AccessibilityWalkerActor> const& weak_walker, Node const& accessibility_node)
{
if (auto walker = weak_walker.strong_ref())
return walker->parent_of_accessibility_node(accessibility_node);
return {};
}
Optional<Node> AccessibilityWalkerActor::parent_of_accessibility_node(Node const& accessibility_node)
{
auto tab = m_tab.strong_ref();
if (!tab)
return {};
auto maybe_parent_node = m_node_to_parent_map.get(&accessibility_node.node);
if (!maybe_parent_node.has_value() || !maybe_parent_node.value())
return {};
auto const& parent_node = *maybe_parent_node.value();
auto identifier = NodeIdentifier::for_node(parent_node);
return Node { .node = parent_node, .identifier = move(identifier), .tab = tab.release_nonnull() };
}
Optional<Node> AccessibilityWalkerActor::root_accessibility_node_for(WeakPtr<AccessibilityWalkerActor> const& weak_walker)
{
if (auto walker = weak_walker.strong_ref())
return walker->root_accessibility_node();
return {};
}
Optional<Node> AccessibilityWalkerActor::root_accessibility_node()
{
auto tab = m_tab.strong_ref();
if (!tab)
return {};
auto identifier = NodeIdentifier::for_node(m_accessibility_tree);
return Node { .node = m_accessibility_tree, .identifier = move(identifier), .tab = tab.release_nonnull() };
}
void AccessibilityWalkerActor::populate_accessibility_tree_cache()
{
m_node_to_parent_map.clear();
m_actor_to_node_map.clear();
m_node_id_to_actor_map.clear();
populate_accessibility_tree_cache(m_accessibility_tree, nullptr);
}
void AccessibilityWalkerActor::populate_accessibility_tree_cache(JsonObject& node, JsonObject const* parent)
{
auto const& node_actor = actor_for_node(node);
node.set("actor"sv, node_actor.name());
m_node_to_parent_map.set(&node, parent);
m_actor_to_node_map.set(node_actor.name(), &node);
m_node_id_to_actor_map.set(node_actor.node_identifier().id, node_actor.name());
auto children = node.get_array("children"sv);
if (!children.has_value())
return;
children->values().remove_all_matching([&](JsonValue const& child) {
return !is_suitable_for_accessibility_inspection(child);
});
children->for_each([&](JsonValue& child) {
populate_accessibility_tree_cache(child.as_object(), &node);
});
}
AccessibilityNodeActor const& AccessibilityWalkerActor::actor_for_node(JsonObject const& node)
{
auto identifier = NodeIdentifier::for_node(node);
if (auto it = m_node_actors.find(identifier); it != m_node_actors.end()) {
if (auto node_actor = it->value.strong_ref())
return *node_actor;
}
auto& node_actor = devtools().register_actor<AccessibilityNodeActor>(identifier, *this);
m_node_actors.set(identifier, node_actor);
return node_actor;
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibDevTools/Actor.h>
#include <LibDevTools/Actors/AccessibilityNodeActor.h>
#include <LibDevTools/Forward.h>
namespace DevTools {
class DEVTOOLS_API AccessibilityWalkerActor final : public Actor {
public:
static constexpr auto base_name = "accessibility-walker"sv;
static NonnullRefPtr<AccessibilityWalkerActor> create(DevToolsServer&, String name, WeakPtr<TabActor>, JsonObject accessibility_tree);
virtual ~AccessibilityWalkerActor() override;
static bool is_suitable_for_accessibility_inspection(JsonValue const&);
JsonValue serialize_root() const;
JsonValue serialize_node(JsonObject const&) const;
static Optional<Node> accessibility_node_for(WeakPtr<AccessibilityWalkerActor> const&, StringView actor);
Optional<Node> accessibility_node(StringView actor);
static Optional<Node> parent_of_accessibility_node_for(WeakPtr<AccessibilityWalkerActor> const&, Node const&);
Optional<Node> parent_of_accessibility_node(Node const&);
static Optional<Node> root_accessibility_node_for(WeakPtr<AccessibilityWalkerActor> const&);
Optional<Node> root_accessibility_node();
private:
AccessibilityWalkerActor(DevToolsServer&, String name, WeakPtr<TabActor>, JsonObject accessibility_tree);
virtual void handle_message(Message const&) override;
void populate_accessibility_tree_cache();
void populate_accessibility_tree_cache(JsonObject& node, JsonObject const* parent);
AccessibilityNodeActor const& actor_for_node(JsonObject const& node);
WeakPtr<TabActor> m_tab;
JsonObject m_accessibility_tree;
HashMap<JsonObject const*, JsonObject const*> m_node_to_parent_map;
HashMap<String, JsonObject const*> m_actor_to_node_map;
HashMap<Web::UniqueNodeID, String> m_node_id_to_actor_map;
HashMap<NodeIdentifier, WeakPtr<AccessibilityNodeActor>> m_node_actors;
};
}

View File

@ -7,6 +7,7 @@
#include <AK/Enumerate.h>
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <LibDevTools/Actors/AccessibilityActor.h>
#include <LibDevTools/Actors/CSSPropertiesActor.h>
#include <LibDevTools/Actors/ConsoleActor.h>
#include <LibDevTools/Actors/FrameActor.h>
@ -20,12 +21,12 @@
namespace DevTools {
NonnullRefPtr<FrameActor> FrameActor::create(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, WeakPtr<CSSPropertiesActor> css_properties, WeakPtr<ConsoleActor> console, WeakPtr<InspectorActor> inspector, WeakPtr<StyleSheetsActor> style_sheets, WeakPtr<ThreadActor> thread)
NonnullRefPtr<FrameActor> FrameActor::create(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, WeakPtr<CSSPropertiesActor> css_properties, WeakPtr<ConsoleActor> console, WeakPtr<InspectorActor> inspector, WeakPtr<StyleSheetsActor> style_sheets, WeakPtr<ThreadActor> thread, WeakPtr<AccessibilityActor> accessibility)
{
return adopt_ref(*new FrameActor(devtools, move(name), move(tab), move(css_properties), move(console), move(inspector), move(style_sheets), move(thread)));
return adopt_ref(*new FrameActor(devtools, move(name), move(tab), move(css_properties), move(console), move(inspector), move(style_sheets), move(thread), move(accessibility)));
}
FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, WeakPtr<CSSPropertiesActor> css_properties, WeakPtr<ConsoleActor> console, WeakPtr<InspectorActor> inspector, WeakPtr<StyleSheetsActor> style_sheets, WeakPtr<ThreadActor> thread)
FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, WeakPtr<CSSPropertiesActor> css_properties, WeakPtr<ConsoleActor> console, WeakPtr<InspectorActor> inspector, WeakPtr<StyleSheetsActor> style_sheets, WeakPtr<ThreadActor> thread, WeakPtr<AccessibilityActor> accessibility)
: Actor(devtools, move(name))
, m_tab(move(tab))
, m_css_properties(move(css_properties))
@ -33,6 +34,7 @@ FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr<TabActor>
, m_inspector(move(inspector))
, m_style_sheets(move(style_sheets))
, m_thread(move(thread))
, m_accessibility(move(accessibility))
{
if (auto tab = m_tab.strong_ref()) {
devtools.delegate().listen_for_console_messages(
@ -127,10 +129,12 @@ JsonObject FrameActor::serialize_target() const
target.set("traits"sv, move(traits));
if (auto css_properties = m_css_properties.strong_ref())
target.set("cssPropertiesActor"sv, css_properties->name());
if (auto accessibility = m_accessibility.strong_ref())
target.set("accessibilityActor"sv, accessibility->name());
if (auto console = m_console.strong_ref())
target.set("consoleActor"sv, console->name());
if (auto css_properties = m_css_properties.strong_ref())
target.set("cssPropertiesActor"sv, css_properties->name());
if (auto inspector = m_inspector.strong_ref())
target.set("inspectorActor"sv, inspector->name());
if (auto style_sheets = m_style_sheets.strong_ref())

View File

@ -20,7 +20,7 @@ class DEVTOOLS_API FrameActor final : public Actor {
public:
static constexpr auto base_name = "frame"sv;
static NonnullRefPtr<FrameActor> create(DevToolsServer&, String name, WeakPtr<TabActor>, WeakPtr<CSSPropertiesActor>, WeakPtr<ConsoleActor>, WeakPtr<InspectorActor>, WeakPtr<StyleSheetsActor>, WeakPtr<ThreadActor>);
static NonnullRefPtr<FrameActor> create(DevToolsServer&, String name, WeakPtr<TabActor>, WeakPtr<CSSPropertiesActor>, WeakPtr<ConsoleActor>, WeakPtr<InspectorActor>, WeakPtr<StyleSheetsActor>, WeakPtr<ThreadActor>, WeakPtr<AccessibilityActor>);
virtual ~FrameActor() override;
void send_frame_update_message();
@ -28,7 +28,7 @@ public:
JsonObject serialize_target() const;
private:
FrameActor(DevToolsServer&, String name, WeakPtr<TabActor>, WeakPtr<CSSPropertiesActor>, WeakPtr<ConsoleActor>, WeakPtr<InspectorActor>, WeakPtr<StyleSheetsActor>, WeakPtr<ThreadActor>);
FrameActor(DevToolsServer&, String name, WeakPtr<TabActor>, WeakPtr<CSSPropertiesActor>, WeakPtr<ConsoleActor>, WeakPtr<InspectorActor>, WeakPtr<StyleSheetsActor>, WeakPtr<ThreadActor>, WeakPtr<AccessibilityActor>);
void style_sheets_available(JsonObject& response, Vector<Web::CSS::StyleSheetIdentifier> style_sheets);
@ -45,6 +45,7 @@ private:
WeakPtr<InspectorActor> m_inspector;
WeakPtr<StyleSheetsActor> m_style_sheets;
WeakPtr<ThreadActor> m_thread;
WeakPtr<AccessibilityActor> m_accessibility;
i32 m_highest_notified_message_index { -1 };
i32 m_highest_received_message_index { -1 };

View File

@ -11,20 +11,12 @@
#include <AK/Traits.h>
#include <LibDevTools/Actor.h>
#include <LibDevTools/Forward.h>
#include <LibDevTools/Node.h>
#include <LibWeb/CSS/Selector.h>
#include <LibWeb/Forward.h>
namespace DevTools {
struct DEVTOOLS_API NodeIdentifier {
static NodeIdentifier for_node(JsonObject const& node);
bool operator==(NodeIdentifier const&) const = default;
Web::UniqueNodeID id { 0 };
Optional<Web::CSS::PseudoElement> pseudo_element;
};
class NodeActor final : public Actor {
public:
static constexpr auto base_name = "node"sv;

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibDevTools/Actors/AccessibilityActor.h>
#include <LibDevTools/Actors/ParentAccessibilityActor.h>
#include <LibDevTools/DevToolsServer.h>
namespace DevTools {
NonnullRefPtr<ParentAccessibilityActor> ParentAccessibilityActor::create(DevToolsServer& devtools, String name)
{
return adopt_ref(*new ParentAccessibilityActor(devtools, move(name)));
}
ParentAccessibilityActor::ParentAccessibilityActor(DevToolsServer& devtools, String name)
: Actor(devtools, move(name))
{
}
ParentAccessibilityActor::~ParentAccessibilityActor() = default;
void ParentAccessibilityActor::handle_message(Message const& message)
{
if (message.type == "bootstrap"sv) {
JsonObject state;
state.set("canBeDisabled"sv, true);
state.set("canBeEnabled"sv, true);
JsonObject response;
response.set("state"sv, move(state));
send_response(message, move(response));
return;
}
if (message.type == "enable"sv) {
// First, a change event
JsonObject response;
response.set("canBeDisabled"sv, true);
response.set("type"sv, "canBeDisabledChange"sv);
send_response(message, move(response));
// Then a blank message is expected
send_message(JsonObject {});
// Then each AccessibilityActor is enabled and sends an "init" message
for (auto const& [name, actor] : devtools().actor_registry()) {
if (auto* accessibility_actor = as_if<AccessibilityActor>(*actor))
accessibility_actor->enable();
}
return;
}
send_unrecognized_packet_type_error(message);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullRefPtr.h>
#include <LibDevTools/Actor.h>
#include <LibDevTools/Forward.h>
namespace DevTools {
class DEVTOOLS_API ParentAccessibilityActor final : public Actor {
public:
static constexpr auto base_name = "parent-accessibility"sv;
static NonnullRefPtr<ParentAccessibilityActor> create(DevToolsServer&, String name);
virtual ~ParentAccessibilityActor() override;
private:
ParentAccessibilityActor(DevToolsServer&, String name);
virtual void handle_message(Message const&) override;
};
}

View File

@ -6,6 +6,7 @@
#include <AK/JsonObject.h>
#include <LibDevTools/Actors/DeviceActor.h>
#include <LibDevTools/Actors/ParentAccessibilityActor.h>
#include <LibDevTools/Actors/PreferenceActor.h>
#include <LibDevTools/Actors/ProcessActor.h>
#include <LibDevTools/Actors/RootActor.h>
@ -56,6 +57,8 @@ void RootActor::handle_message(Message const& message)
for (auto const& actor : devtools().actor_registry()) {
if (is<DeviceActor>(*actor.value))
response.set("deviceActor"sv, actor.key);
else if (is<ParentAccessibilityActor>(*actor.value))
response.set("parentAccessibilityActor"sv, actor.key);
else if (is<PreferenceActor>(*actor.value))
response.set("preferenceActor"sv, actor.key);
}

View File

@ -6,6 +6,7 @@
#include <AK/JsonArray.h>
#include <AK/StringUtils.h>
#include <LibDevTools/Actors/AccessibilityNodeActor.h>
#include <LibDevTools/Actors/LayoutInspectorActor.h>
#include <LibDevTools/Actors/TabActor.h>
#include <LibDevTools/Actors/WalkerActor.h>
@ -127,6 +128,54 @@ void WalkerActor::handle_message(Message const& message)
return;
}
if (message.type == "getNodeFromActor"sv) {
auto path = get_required_parameter<JsonArray>(message, "path"sv);
if (!path.has_value())
return;
auto actor_id = get_required_parameter<String>(message, "actorID"sv);
if (!actor_id.has_value())
return;
// The ["rawAccessible","DOMNode"] path retrieves the DOM node corresponding to an AccessibilityNodeActor.
if (path->size() == 2) {
auto const& first = path->at(0);
auto const& second = path->at(1);
if (first.is_string() && first.as_string() == "rawAccessible"sv
&& second.is_string() && second.as_string() == "DOMNode"sv) {
auto maybe_accessibility_actor = devtools().actor_registry().find(actor_id.value());
if (maybe_accessibility_actor == devtools().actor_registry().end()) {
send_unknown_actor_error(message, actor_id.value());
return;
}
auto accessibility_actor = as<AccessibilityNodeActor>(maybe_accessibility_actor->value.ptr());
if (auto node_actor_name = m_dom_node_id_to_actor_map.get(accessibility_actor->node_identifier().id); node_actor_name.has_value()) {
auto dom_node = dom_node_for(this, node_actor_name.value());
if (!dom_node.has_value()) {
send_unknown_actor_error(message, node_actor_name.value());
return;
}
JsonObject node;
node.set("node"sv, serialize_node(dom_node->node));
node.set("newParents"sv, JsonArray {});
response.set("node"sv, move(node));
send_response(message, move(response));
return;
}
}
}
JsonObject error;
error.set("error"sv, "unrecognizedNodePath"sv);
error.set("message"sv, MUST(String::formatted("Unrecognized or missing path for getNodeFromActor: '{}'", message.data.serialized())));
send_response(message, move(error));
return;
}
if (message.type == "getOffsetParent"sv) {
response.set("node"sv, JsonValue {});
send_response(message, move(response));
@ -388,19 +437,6 @@ JsonValue WalkerActor::serialize_root() const
return serialize_node(m_dom_tree);
}
static constexpr Web::DOM::NodeType parse_node_type(StringView type)
{
if (type == "document"sv)
return Web::DOM::NodeType::DOCUMENT_NODE;
if (type == "element"sv)
return Web::DOM::NodeType::ELEMENT_NODE;
if (type == "text"sv)
return Web::DOM::NodeType::TEXT_NODE;
if (type == "comment"sv)
return Web::DOM::NodeType::COMMENT_NODE;
return Web::DOM::NodeType::INVALID;
}
JsonValue WalkerActor::serialize_node(JsonObject const& node) const
{
auto tab = m_tab.strong_ref();
@ -414,7 +450,7 @@ JsonValue WalkerActor::serialize_node(JsonObject const& node) const
auto name = node.get_string("name"sv).release_value();
auto type = node.get_string("type"sv).release_value();
auto dom_type = parse_node_type(type);
auto dom_type = parse_dom_node_type(type);
JsonValue node_value;
auto is_top_level_document = &node == &m_dom_tree;
@ -450,7 +486,7 @@ JsonValue WalkerActor::serialize_node(JsonObject const& node) const
if (auto parent_actor = m_dom_node_id_to_actor_map.get(parent_id); parent_actor.has_value()) {
if (auto parent_node = WalkerActor::dom_node_for(this, *parent_actor); parent_node.has_value()) {
dom_type = parse_node_type(parent_node->node.get_string("type"sv).value());
dom_type = parse_dom_node_type(parent_node->node.get_string("type"sv).value());
is_displayed = !is_top_level_document && parent_node->node.get_bool("visible"sv).value_or(false);
}
}
@ -518,14 +554,14 @@ JsonValue WalkerActor::serialize_node(JsonObject const& node) const
return serialized;
}
Optional<WalkerActor::DOMNode> WalkerActor::dom_node_for(WeakPtr<WalkerActor> const& weak_walker, StringView actor)
Optional<Node> WalkerActor::dom_node_for(WeakPtr<WalkerActor> const& weak_walker, StringView actor)
{
if (auto walker = weak_walker.strong_ref())
return walker->dom_node(actor);
return {};
}
Optional<WalkerActor::DOMNode> WalkerActor::dom_node(StringView actor)
Optional<Node> WalkerActor::dom_node(StringView actor)
{
auto tab = m_tab.strong_ref();
if (!tab)
@ -538,7 +574,7 @@ Optional<WalkerActor::DOMNode> WalkerActor::dom_node(StringView actor)
auto const& dom_node = *maybe_dom_node.value();
auto identifier = NodeIdentifier::for_node(dom_node);
return DOMNode { .node = dom_node, .identifier = move(identifier), .tab = tab.release_nonnull() };
return Node { .node = dom_node, .identifier = move(identifier), .tab = tab.release_nonnull() };
}
Optional<JsonObject const&> WalkerActor::find_node_by_selector(JsonObject const& node, StringView selector)

View File

@ -13,6 +13,7 @@
#include <LibDevTools/Actor.h>
#include <LibDevTools/Actors/NodeActor.h>
#include <LibDevTools/Forward.h>
#include <LibDevTools/Node.h>
#include <LibWeb/Forward.h>
#include <LibWebView/Forward.h>
@ -28,13 +29,8 @@ public:
static bool is_suitable_for_dom_inspection(JsonValue const&);
JsonValue serialize_root() const;
struct DOMNode {
JsonObject const& node;
NodeIdentifier identifier;
NonnullRefPtr<TabActor> tab;
};
static Optional<DOMNode> dom_node_for(WeakPtr<WalkerActor> const&, StringView actor);
Optional<DOMNode> dom_node(StringView actor);
static Optional<Node> dom_node_for(WeakPtr<WalkerActor> const&, StringView actor);
Optional<Node> dom_node(StringView actor);
private:
WalkerActor(DevToolsServer&, String name, WeakPtr<TabActor>, JsonObject dom_tree);

View File

@ -7,6 +7,7 @@
#include <AK/Debug.h>
#include <AK/JsonObject.h>
#include <LibCore/EventLoop.h>
#include <LibDevTools/Actors/AccessibilityActor.h>
#include <LibDevTools/Actors/CSSPropertiesActor.h>
#include <LibDevTools/Actors/ConsoleActor.h>
#include <LibDevTools/Actors/FrameActor.h>
@ -95,8 +96,9 @@ void WatcherActor::handle_message(Message const& message)
auto& inspector = devtools().register_actor<InspectorActor>(m_tab);
auto& style_sheets = devtools().register_actor<StyleSheetsActor>(m_tab);
auto& thread = devtools().register_actor<ThreadActor>();
auto& accessibility = devtools().register_actor<AccessibilityActor>(m_tab);
auto& target = devtools().register_actor<FrameActor>(m_tab, css_properties, console, inspector, style_sheets, thread);
auto& target = devtools().register_actor<FrameActor>(m_tab, css_properties, console, inspector, style_sheets, thread, accessibility);
m_target = target;
response.set("type"sv, "target-available-form"sv);

View File

@ -1,5 +1,8 @@
set(SOURCES
Actor.cpp
Actors/AccessibilityActor.cpp
Actors/AccessibilityNodeActor.cpp
Actors/AccessibilityWalkerActor.cpp
Actors/ConsoleActor.cpp
Actors/CSSPropertiesActor.cpp
Actors/DeviceActor.cpp
@ -9,6 +12,7 @@ set(SOURCES
Actors/LayoutInspectorActor.cpp
Actors/NodeActor.cpp
Actors/PageStyleActor.cpp
Actors/ParentAccessibilityActor.cpp
Actors/PreferenceActor.cpp
Actors/ProcessActor.cpp
Actors/RootActor.cpp

View File

@ -32,6 +32,9 @@ public:
using OnTabInspectionComplete = Function<void(ErrorOr<JsonValue>)>;
virtual void inspect_tab(TabDescription const&, OnTabInspectionComplete) const { }
using OnAccessibilityTreeInspectionComplete = Function<void(ErrorOr<JsonValue>)>;
virtual void inspect_accessibility_tree(TabDescription const&, OnAccessibilityTreeInspectionComplete) const { }
using OnDOMNodePropertiesReceived = Function<void(WebView::DOMNodeProperties)>;
virtual void listen_for_dom_properties(TabDescription const&, OnDOMNodePropertiesReceived) const { }
virtual void stop_listening_for_dom_properties(TabDescription const&) const { }

View File

@ -11,6 +11,7 @@
#include <LibCore/Socket.h>
#include <LibCore/TCPServer.h>
#include <LibDevTools/Actors/DeviceActor.h>
#include <LibDevTools/Actors/ParentAccessibilityActor.h>
#include <LibDevTools/Actors/PreferenceActor.h>
#include <LibDevTools/Actors/ProcessActor.h>
#include <LibDevTools/Actors/TabActor.h>
@ -79,6 +80,7 @@ ErrorOr<void> DevToolsServer::on_new_client()
register_actor<DeviceActor>();
register_actor<PreferenceActor>();
register_actor<ProcessActor>(ProcessDescription { .is_parent = true });
register_actor<ParentAccessibilityActor>();
return {};
}

View File

@ -11,6 +11,9 @@
namespace DevTools {
class Actor;
class AccessibilityActor;
class AccessibilityNodeActor;
class AccessibilityWalkerActor;
class Connection;
class ConsoleActor;
class CSSPropertiesActor;
@ -23,6 +26,7 @@ class InspectorActor;
class LayoutInspectorActor;
class NodeActor;
class PageStyleActor;
class ParentAccessibilityActor;
class PreferenceActor;
class ProcessActor;
class RootActor;
@ -35,6 +39,7 @@ class WalkerActor;
class WatcherActor;
struct CSSProperty;
struct Node;
struct ProcessDescription;
struct TabDescription;

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/JsonObject.h>
#include <LibDevTools/Actors/TabActor.h>
#include <LibDevTools/Forward.h>
#include <LibWeb/CSS/PseudoElement.h>
#include <LibWeb/DOM/NodeType.h>
#include <LibWeb/Forward.h>
namespace DevTools {
struct DEVTOOLS_API NodeIdentifier {
static NodeIdentifier for_node(JsonObject const& node);
bool operator==(NodeIdentifier const&) const = default;
Web::UniqueNodeID id { 0 };
Optional<Web::CSS::PseudoElement> pseudo_element;
};
struct DEVTOOLS_API Node {
JsonObject const& node;
NodeIdentifier identifier;
NonnullRefPtr<TabActor> tab;
};
static constexpr Web::DOM::NodeType parse_dom_node_type(StringView type)
{
if (type == "document"sv)
return Web::DOM::NodeType::DOCUMENT_NODE;
if (type == "element"sv)
return Web::DOM::NodeType::ELEMENT_NODE;
if (type == "text"sv)
return Web::DOM::NodeType::TEXT_NODE;
if (type == "comment"sv)
return Web::DOM::NodeType::COMMENT_NODE;
return Web::DOM::NodeType::INVALID;
}
}

View File

@ -9,6 +9,7 @@ set(SOURCES
RootVector.cpp
Heap.cpp
HeapBlock.cpp
WeakBlock.cpp
WeakContainer.cpp
)

View File

@ -20,6 +20,7 @@ class Heap;
class HeapBlock;
class NanBoxedValue;
class WeakContainer;
class WeakImpl;
template<typename T>
class Function;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2020-2025, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -20,6 +20,8 @@
#include <LibGC/HeapBlock.h>
#include <LibGC/NanBoxedValue.h>
#include <LibGC/Root.h>
#include <LibGC/Weak.h>
#include <LibGC/WeakInlines.h>
#include <setjmp.h>
#ifdef HAS_ADDRESS_SANITIZER
@ -28,10 +30,18 @@
namespace GC {
static Heap* s_the;
Heap& Heap::the()
{
return *s_the;
}
Heap::Heap(void* private_data, AK::Function<void(HashMap<Cell*, GC::HeapRoot>&)> gather_embedder_roots)
: HeapBase(private_data)
, m_gather_embedder_roots(move(gather_embedder_roots))
{
s_the = this;
static_assert(HeapBlock::min_possible_cell_size <= 32, "Heap Cell tracking uses too much data!");
m_size_based_cell_allocators.append(make<CellAllocator>(64));
m_size_based_cell_allocators.append(make<CellAllocator>(96));
@ -258,6 +268,7 @@ void Heap::collect_garbage(CollectionType collection_type, bool print_report)
mark_live_cells(roots);
}
finalize_unmarked_cells();
sweep_weak_blocks();
sweep_dead_cells(print_report, collection_measurement_timer);
}
@ -462,6 +473,22 @@ void Heap::finalize_unmarked_cells()
});
}
void Heap::sweep_weak_blocks()
{
for (auto& weak_block : m_usable_weak_blocks) {
weak_block.sweep();
}
Vector<WeakBlock&> now_usable_weak_blocks;
for (auto& weak_block : m_full_weak_blocks) {
weak_block.sweep();
if (weak_block.can_allocate())
now_usable_weak_blocks.append(weak_block);
}
for (auto& weak_block : now_usable_weak_blocks) {
m_usable_weak_blocks.append(weak_block);
}
}
void Heap::sweep_dead_cells(bool print_report, Core::ElapsedTimer const& measurement_timer)
{
dbgln_if(HEAP_DEBUG, "sweep_dead_cells:");
@ -559,4 +586,21 @@ void Heap::uproot_cell(Cell* cell)
m_uprooted_cells.append(cell);
}
WeakImpl* Heap::create_weak_impl(void* ptr)
{
if (m_usable_weak_blocks.is_empty()) {
// NOTE: These are leaked on Heap destruction, but that's fine since Heap is tied to process lifetime.
auto* weak_block = WeakBlock::create();
m_usable_weak_blocks.append(*weak_block);
}
auto* weak_block = m_usable_weak_blocks.first();
auto* new_weak_impl = weak_block->allocate(static_cast<Cell*>(ptr));
if (!weak_block->can_allocate()) {
m_full_weak_blocks.append(*weak_block);
}
return new_weak_impl;
}
}

View File

@ -25,6 +25,7 @@
#include <LibGC/Root.h>
#include <LibGC/RootHashMap.h>
#include <LibGC/RootVector.h>
#include <LibGC/WeakBlock.h>
#include <LibGC/WeakContainer.h>
namespace GC {
@ -37,6 +38,8 @@ public:
explicit Heap(void* private_data, AK::Function<void(HashMap<Cell*, GC::HeapRoot>&)> gather_embedder_roots);
~Heap();
static Heap& the();
template<typename T, typename... Args>
Ref<T> allocate(Args&&... args)
{
@ -81,6 +84,8 @@ public:
void enqueue_post_gc_task(AK::Function<void()>);
WeakImpl* create_weak_impl(void*);
private:
friend class MarkingVisitor;
friend class GraphConstructorVisitor;
@ -113,6 +118,7 @@ private:
void mark_live_cells(HashMap<Cell*, HeapRoot> const& live_cells);
void finalize_unmarked_cells();
void sweep_dead_cells(bool print_report, Core::ElapsedTimer const&);
void sweep_weak_blocks();
ALWAYS_INLINE CellAllocator& allocator_for_size(size_t cell_size)
{
@ -159,6 +165,9 @@ private:
AK::Function<void(HashMap<Cell*, GC::HeapRoot>&)> m_gather_embedder_roots;
Vector<AK::Function<void()>> m_post_gc_tasks;
WeakBlock::List m_usable_weak_blocks;
WeakBlock::List m_full_weak_blocks;
} SWIFT_IMMORTAL_REFERENCE;
inline void Heap::did_create_root(Badge<RootImpl>, RootImpl& impl)

View File

@ -71,6 +71,9 @@ public:
operator T&() const { return *m_ptr; }
operator bool() const = delete;
bool operator!() const = delete;
private:
T* m_ptr { nullptr };
};

166
Libraries/LibGC/Weak.h Normal file
View File

@ -0,0 +1,166 @@
/*
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Badge.h>
#include <AK/RefPtr.h>
#include <LibGC/Export.h>
#include <LibGC/Ptr.h>
namespace GC {
class WeakBlock;
class WeakImpl {
public:
// NOTE: Null GC::Weaks point at this WeakImpl. This allows Weak to always chase the impl pointer without null-checking it.
static GC_API WeakImpl the_null_weak_impl;
WeakImpl() = default;
WeakImpl(void* ptr)
: m_ptr(ptr)
{
}
void* ptr() const { return m_ptr; }
void set_ptr(Badge<WeakBlock>, void* ptr) { m_ptr = ptr; }
bool operator==(WeakImpl const& other) const { return m_ptr == other.m_ptr; }
bool operator!=(WeakImpl const& other) const { return m_ptr != other.m_ptr; }
void ref() const { ++m_ref_count; }
void unref() const
{
VERIFY(m_ref_count);
--m_ref_count;
}
size_t ref_count() const { return m_ref_count; }
enum class State {
Allocated,
Freelist,
};
void set_state(State state) { m_state = state; }
State state() const { return m_state; }
private:
mutable size_t m_ref_count { 0 };
State m_state { State::Allocated };
void* m_ptr { nullptr };
};
template<typename T>
class Weak {
public:
constexpr Weak() = default;
Weak(nullptr_t) { }
Weak(T const* ptr);
Weak(T const& ptr);
template<typename U>
Weak(Weak<U> const& other)
requires(IsConvertible<U*, T*>);
Weak(Ref<T> const& other);
template<typename U>
Weak(Ref<U> const& other)
requires(IsConvertible<U*, T*>);
template<typename U>
Weak& operator=(Weak<U> const& other)
requires(IsConvertible<U*, T*>)
{
m_impl = other.impl();
return *this;
}
Weak& operator=(Ref<T> const& other);
template<typename U>
Weak& operator=(Ref<U> const& other)
requires(IsConvertible<U*, T*>);
Weak& operator=(T const& other);
template<typename U>
Weak& operator=(U const& other)
requires(IsConvertible<U*, T*>);
Weak& operator=(T const* other);
template<typename U>
Weak& operator=(U const* other)
requires(IsConvertible<U*, T*>);
T* operator->() const
{
ASSERT(ptr());
return ptr();
}
[[nodiscard]] T& operator*() const
{
ASSERT(ptr());
return *ptr();
}
Ptr<T> ptr() const { return static_cast<T*>(impl().ptr()); }
explicit operator bool() const { return !!ptr(); }
bool operator!() const { return !ptr(); }
operator T*() const { return ptr(); }
Ref<T> as_nonnull() const
{
ASSERT(ptr());
return *ptr();
}
WeakImpl& impl() const { return *m_impl; }
private:
NonnullRefPtr<WeakImpl> m_impl { WeakImpl::the_null_weak_impl };
};
template<typename T, typename U>
inline bool operator==(Weak<T> const& a, Ptr<U> const& b)
{
return a.ptr() == b.ptr();
}
template<typename T, typename U>
inline bool operator==(Weak<T> const& a, Ref<U> const& b)
{
return a.ptr() == b.ptr();
}
}
namespace AK {
template<typename T>
struct Traits<GC::Weak<T>> : public DefaultTraits<GC::Weak<T>> {
static unsigned hash(GC::Weak<T> const& value)
{
return Traits<T*>::hash(value.ptr());
}
};
template<typename T>
struct Formatter<GC::Weak<T>> : Formatter<T const*> {
ErrorOr<void> format(FormatBuilder& builder, GC::Weak<T> const& value)
{
return Formatter<T const*>::format(builder, value.ptr());
}
};
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGC/Cell.h>
#include <LibGC/WeakBlock.h>
#include <sys/mman.h>
#if defined(AK_OS_WINDOWS)
# include <AK/Windows.h>
# include <memoryapi.h>
#endif
namespace GC {
WeakImpl WeakImpl::the_null_weak_impl;
WeakBlock* WeakBlock::create()
{
#if !defined(AK_OS_WINDOWS)
auto* block = (HeapBlock*)mmap(nullptr, WeakBlock::BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
VERIFY(block != MAP_FAILED);
#else
auto* block = (HeapBlock*)VirtualAlloc(NULL, WeakBlock::BLOCK_SIZE, MEM_COMMIT, PAGE_READWRITE);
VERIFY(block);
#endif
return new (block) WeakBlock;
}
WeakBlock::WeakBlock()
{
for (size_t i = 0; i < IMPL_COUNT; ++i) {
m_impls[i].set_ptr({}, i + 1 < IMPL_COUNT ? &m_impls[i + 1] : nullptr);
m_impls[i].set_state(WeakImpl::State::Freelist);
}
m_freelist = &m_impls[0];
}
WeakBlock::~WeakBlock() = default;
WeakImpl* WeakBlock::allocate(Cell* cell)
{
auto* impl = m_freelist;
if (!impl)
return nullptr;
VERIFY(impl->ref_count() == 0);
m_freelist = impl->ptr() ? static_cast<WeakImpl*>(impl->ptr()) : nullptr;
impl->set_ptr({}, cell);
impl->set_state(WeakImpl::State::Allocated);
return impl;
}
void WeakBlock::deallocate(WeakImpl* impl)
{
VERIFY(impl->ref_count() == 0);
impl->set_ptr({}, m_freelist);
impl->set_state(WeakImpl::State::Freelist);
m_freelist = impl;
}
void WeakBlock::sweep()
{
for (size_t i = 0; i < IMPL_COUNT; ++i) {
auto& impl = m_impls[i];
if (impl.state() == WeakImpl::State::Freelist)
continue;
auto* cell = static_cast<Cell*>(impl.ptr());
if (!cell || !cell->is_marked())
impl.set_ptr({}, nullptr);
if (impl.ref_count() == 0)
deallocate(&impl);
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/IntrusiveList.h>
#include <LibGC/Forward.h>
#include <LibGC/Weak.h>
namespace GC {
class GC_API WeakBlock {
public:
static constexpr size_t BLOCK_SIZE = 16 * KiB;
static WeakBlock* create();
WeakImpl* allocate(Cell*);
void deallocate(WeakImpl*);
bool can_allocate() const { return m_freelist != nullptr; }
void sweep();
private:
WeakBlock();
~WeakBlock();
IntrusiveListNode<WeakBlock> m_list_node;
public:
using List = IntrusiveList<&WeakBlock::m_list_node>;
WeakImpl* m_freelist { nullptr };
static constexpr size_t IMPL_COUNT = (BLOCK_SIZE - sizeof(m_list_node) - sizeof(WeakImpl*)) / sizeof(WeakImpl);
WeakImpl m_impls[IMPL_COUNT];
};
static_assert(sizeof(WeakBlock) <= WeakBlock::BLOCK_SIZE);
}

View File

@ -0,0 +1,113 @@
/*
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGC/Weak.h>
namespace GC {
template<typename T>
Weak<T>::Weak(T const* ptr)
: m_impl(ptr ? *ptr->heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(ptr))) : WeakImpl::the_null_weak_impl)
{
}
template<typename T>
Weak<T>::Weak(T const& ptr)
: m_impl(*ptr.heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(&ptr))))
{
}
template<typename T>
template<typename U>
Weak<T>::Weak(Weak<U> const& other)
requires(IsConvertible<U*, T*>)
: m_impl(other.impl())
{
}
template<typename T>
Weak<T>::Weak(Ref<T> const& other)
: m_impl(*other.ptr()->heap().create_weak_impl(other.ptr()))
{
}
template<typename T>
template<typename U>
Weak<T>::Weak(Ref<U> const& other)
requires(IsConvertible<U*, T*>)
: m_impl(*other.ptr()->heap().create_weak_impl(other.ptr()))
{
}
template<typename T>
template<typename U>
Weak<T>& Weak<T>::operator=(U const& other)
requires(IsConvertible<U*, T*>)
{
if (ptr() != other) {
m_impl = *other.heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(&other)));
}
return *this;
}
template<typename T>
Weak<T>& Weak<T>::operator=(Ref<T> const& other)
{
if (ptr() != other.ptr()) {
m_impl = *other.ptr()->heap().create_weak_impl(other.ptr());
}
return *this;
}
template<typename T>
template<typename U>
Weak<T>& Weak<T>::operator=(Ref<U> const& other)
requires(IsConvertible<U*, T*>)
{
if (ptr() != other.ptr()) {
m_impl = *other.ptr()->heap().create_weak_impl(other.ptr());
}
return *this;
}
template<typename T>
Weak<T>& Weak<T>::operator=(T const& other)
{
if (ptr() != &other) {
m_impl = *other.heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(&other)));
}
return *this;
}
template<typename T>
Weak<T>& Weak<T>::operator=(T const* other)
{
if (ptr() != other) {
if (other)
m_impl = *other->heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(other)));
else
m_impl = WeakImpl::the_null_weak_impl;
}
return *this;
}
template<typename T>
template<typename U>
Weak<T>& Weak<T>::operator=(U const* other)
requires(IsConvertible<U*, T*>)
{
if (ptr() != other) {
if (other)
m_impl = *other->heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(other)));
else
m_impl = WeakImpl::the_null_weak_impl;
}
return *this;
}
}

View File

@ -10,6 +10,11 @@
#include <AK/Checked.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ShareableBitmap.h>
#include <LibGfx/SkiaUtils.h>
#include <core/SkBitmap.h>
#include <core/SkColorSpace.h>
#include <core/SkImage.h>
#include <errno.h>
#ifdef AK_OS_MACOS
@ -122,6 +127,16 @@ ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_with_anonymous_buffer(BitmapFormat
return adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, move(buffer), size));
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_with_raw_data(BitmapFormat format, AlphaType alpha_type, ReadonlyBytes raw_data, IntSize size)
{
if (size_would_overflow(format, size))
return Error::from_string_literal("Gfx::Bitmap::create_with_raw_data size overflow");
auto backing_store = TRY(Bitmap::allocate_backing_store(format, size, InitializeBackingStore::No));
raw_data.copy_to(Bytes { backing_store.data, backing_store.size_in_bytes });
return AK::adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, size, backing_store));
}
Bitmap::Bitmap(BitmapFormat format, AlphaType alpha_type, Core::AnonymousBuffer buffer, IntSize size)
: m_size(size)
, m_data(buffer.data<void>())
@ -184,6 +199,24 @@ ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::cropped(Gfx::IntRect crop, Gfx::Colo
return new_bitmap;
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::scaled(int const width, int const height, ScalingMode const scaling_mode) const
{
auto const source_info = SkImageInfo::Make(this->width(), this->height(), to_skia_color_type(format()), to_skia_alpha_type(format(), alpha_type()), nullptr);
SkPixmap const source_sk_pixmap(source_info, begin(), pitch());
SkBitmap source_sk_bitmap;
source_sk_bitmap.installPixels(source_sk_pixmap);
source_sk_bitmap.setImmutable();
auto scaled_bitmap = TRY(Gfx::Bitmap::create(format(), alpha_type(), { width, height }));
auto const scaled_info = SkImageInfo::Make(scaled_bitmap->width(), scaled_bitmap->height(), to_skia_color_type(scaled_bitmap->format()), to_skia_alpha_type(scaled_bitmap->format(), scaled_bitmap->alpha_type()), nullptr);
SkPixmap const scaled_sk_pixmap(scaled_info, scaled_bitmap->begin(), scaled_bitmap->pitch());
sk_sp<SkImage> source_sk_image = source_sk_bitmap.asImage();
if (!source_sk_image->scalePixels(scaled_sk_pixmap, to_skia_sampling_options(scaling_mode)))
return Error::from_string_literal("Unable to scale pixels for bitmap");
return scaled_bitmap;
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::to_bitmap_backed_by_anonymous_buffer() const
{
if (m_buffer.is_valid()) {
@ -219,7 +252,7 @@ Gfx::ShareableBitmap Bitmap::to_shareable_bitmap() const
return Gfx::ShareableBitmap { bitmap_or_error.release_value_but_fixme_should_propagate_errors(), Gfx::ShareableBitmap::ConstructWithKnownGoodBitmap };
}
ErrorOr<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, IntSize size)
ErrorOr<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, IntSize size, InitializeBackingStore initialize_backing_store)
{
if (size.is_empty())
return Error::from_string_literal("Gfx::Bitmap backing store size is empty");
@ -230,7 +263,11 @@ ErrorOr<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, IntSiz
auto const pitch = minimum_pitch(size.width(), format);
auto const data_size_in_bytes = size_in_bytes(pitch, size.height());
void* data = kcalloc(1, data_size_in_bytes);
void* data;
if (initialize_backing_store == InitializeBackingStore::Yes)
data = kcalloc(1, data_size_in_bytes);
else
data = kmalloc(data_size_in_bytes);
if (data == nullptr)
return Error::from_errno(errno);
return BackingStore { data, pitch, data_size_in_bytes };

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2022, Timothy Slater <tslater2006@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -13,6 +13,7 @@
#include <LibGfx/Color.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Rect.h>
#include <LibGfx/ScalingMode.h>
namespace Gfx {
@ -69,11 +70,14 @@ public:
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create(BitmapFormat, AlphaType, IntSize);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_shareable(BitmapFormat, AlphaType, IntSize);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_wrapper(BitmapFormat, AlphaType, IntSize, size_t pitch, void*, Function<void()>&& destruction_callback = {});
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_with_raw_data(BitmapFormat, AlphaType, ReadonlyBytes, IntSize);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_with_anonymous_buffer(BitmapFormat, AlphaType, Core::AnonymousBuffer, IntSize);
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> clone() const;
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> cropped(Gfx::IntRect, Gfx::Color outside_color = Gfx::Color::Black) const;
ErrorOr<NonnullRefPtr<Bitmap>> scaled(int width, int height, ScalingMode scaling_mode) const;
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> to_bitmap_backed_by_anonymous_buffer() const;
[[nodiscard]] ShareableBitmap to_shareable_bitmap() const;
@ -169,7 +173,11 @@ private:
Bitmap(BitmapFormat, AlphaType, IntSize, size_t pitch, void*, Function<void()>&& destruction_callback);
Bitmap(BitmapFormat, AlphaType, Core::AnonymousBuffer, IntSize);
static ErrorOr<BackingStore> allocate_backing_store(BitmapFormat format, IntSize size);
enum class InitializeBackingStore {
No,
Yes,
};
static ErrorOr<BackingStore> allocate_backing_store(BitmapFormat format, IntSize size, InitializeBackingStore = InitializeBackingStore::Yes);
IntSize m_size;
void* m_data { nullptr };

View File

@ -128,14 +128,9 @@ ErrorOr<Gfx::BitmapSequence> decode(Decoder& decoder)
if (size_check.has_overflow() || size_check.value() > bytes.size())
return Error::from_string_literal("IPC: Invalid Gfx::BitmapSequence buffer data");
auto buffer = TRY(Core::AnonymousBuffer::create_with_size(size_in_bytes));
auto buffer_bytes = Bytes { buffer.data<u8>(), buffer.size() };
bytes.slice(bytes_read, size_in_bytes).copy_to(buffer_bytes);
auto slice = bytes.slice(bytes_read, size_in_bytes);
bytes_read += size_in_bytes;
bitmap = TRY(Gfx::Bitmap::create_with_anonymous_buffer(metadata.format, metadata.alpha_type, move(buffer), metadata.size));
bitmap = TRY(Gfx::Bitmap::create_with_raw_data(metadata.format, metadata.alpha_type, slice, metadata.size));
}
bitmaps.append(bitmap);

View File

@ -173,4 +173,9 @@ endif()
if (HAS_VULKAN)
target_link_libraries(LibCore PUBLIC Vulkan::Vulkan Vulkan::Headers)
if ((LINUX AND NOT ANDROID) OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
pkg_check_modules(LibDRM REQUIRED libdrm)
target_include_directories(LibGfx PUBLIC ${LibDRM_INCLUDE_DIRS})
endif()
endif()

View File

@ -25,6 +25,73 @@
namespace Gfx {
namespace {
char nth_digit(u32 value, u8 digit)
{
// This helper is used to format integers.
// nth_digit(745, 1) -> '5'
// nth_digit(745, 2) -> '4'
// nth_digit(745, 3) -> '7'
VERIFY(value < 1000);
VERIFY(digit <= 3);
VERIFY(digit > 0);
while (digit > 1) {
value /= 10;
digit--;
}
return '0' + value % 10;
}
Array<char, 4> format_to_8bit_compatible(u8 value)
{
// This function formats to the shortest string that roundtrips at 8 bits.
// As an example:
// 127 / 255 = 0.498 ± 0.001
// 128 / 255 = 0.502 ± 0.001
// But round(.5 * 255) == 128, so this function returns (note that it's only the fractional part):
// 127 -> "498"
// 128 -> "5"
u32 const three_digits = (value * 1000u + 127) / 255;
u32 const rounded_to_two_digits = (three_digits + 5) / 10 * 10;
if ((rounded_to_two_digits * 255 / 100 + 5) / 10 != value)
return { nth_digit(three_digits, 3), nth_digit(three_digits, 2), nth_digit(three_digits, 1), '\0' };
u32 const rounded_to_one_digit = (three_digits + 50) / 100 * 100;
if ((rounded_to_one_digit * 255 / 100 + 5) / 10 != value)
return { nth_digit(rounded_to_two_digits, 3), nth_digit(rounded_to_two_digits, 2), '\0', '\0' };
return { nth_digit(rounded_to_one_digit, 3), '\0', '\0', '\0' };
}
}
// https://www.w3.org/TR/css-color-4/#serializing-sRGB-values
void Color::serialize_a_srgb_value(StringBuilder& builder) const
{
// The serialized form is derived from the computed value and thus, uses either the rgb() or rgba() form
// (depending on whether the alpha is exactly 1, or not), with lowercase letters for the function name.
// NOTE: Since we use Gfx::Color, having an "alpha of 1" means its value is 255.
if (alpha() == 0)
builder.appendff("rgba({}, {}, {}, 0)", red(), green(), blue());
else if (alpha() == 255)
builder.appendff("rgb({}, {}, {})", red(), green(), blue());
else
builder.appendff("rgba({}, {}, {}, 0.{})", red(), green(), blue(), format_to_8bit_compatible(alpha()).data());
}
String Color::serialize_a_srgb_value() const
{
StringBuilder builder;
serialize_a_srgb_value(builder);
return builder.to_string_without_validation();
}
String Color::to_string(HTMLCompatibleSerialization html_compatible_serialization) const
{
// If the following conditions are all true:
@ -45,9 +112,7 @@ String Color::to_string(HTMLCompatibleSerialization html_compatible_serializatio
}
// Otherwise, for sRGB the CSS serialization of sRGB values is used and for other color spaces, the relevant serialization of the <color> value.
if (alpha() < 255)
return MUST(String::formatted("rgba({}, {}, {}, {})", red(), green(), blue(), alpha() / 255.0));
return MUST(String::formatted("rgb({}, {}, {})", red(), green(), blue()));
return serialize_a_srgb_value();
}
String Color::to_string_without_alpha() const
@ -422,6 +487,15 @@ Color Color::from_linear_srgb(float red, float green, float blue, float alpha)
clamp(lroundf(alpha * 255.f), 0, 255));
}
Color Color::from_linear_display_p3(float r, float g, float b, float alpha)
{
float x = 0.48657095 * r + 0.26566769 * g + 0.19821729 * b;
float y = 0.22897456 * r + 0.69173852 * g + 0.07928691 * b;
float z = 0.00000000 * r + 0.04511338 * g + 1.04394437 * b;
return from_xyz65(x, y, z, alpha);
}
// https://www.w3.org/TR/css-color-4/#predefined-a98-rgb
Color Color::from_a98rgb(float r, float g, float b, float alpha)
{
@ -453,11 +527,7 @@ Color Color::from_display_p3(float r, float g, float b, float alpha)
auto linear_g = to_linear(g);
auto linear_b = to_linear(b);
float x = 0.48657095 * linear_r + 0.26566769 * linear_g + 0.19821729 * linear_b;
float y = 0.22897456 * linear_r + 0.69173852 * linear_g + 0.07928691 * linear_b;
float z = 0.00000000 * linear_r + 0.04511338 * linear_g + 1.04394437 * linear_b;
return from_xyz65(x, y, z, alpha);
return from_linear_display_p3(linear_r, linear_g, linear_b, alpha);
}
// https://www.w3.org/TR/css-color-4/#predefined-prophoto-rgb

View File

@ -212,6 +212,7 @@ public:
static Color from_a98rgb(float r, float g, float b, float alpha = 1.0f);
static Color from_display_p3(float r, float g, float b, float alpha = 1.0f);
static Color from_lab(float L, float a, float b, float alpha = 1.0f);
static Color from_linear_display_p3(float r, float g, float b, float alpha = 1.0f);
static Color from_linear_srgb(float r, float g, float b, float alpha = 1.0f);
static Color from_pro_photo_rgb(float r, float g, float b, float alpha = 1.0f);
static Color from_rec2020(float r, float g, float b, float alpha = 1.0f);
@ -501,6 +502,9 @@ public:
String to_string_without_alpha() const;
Utf16String to_utf16_string_without_alpha() const;
void serialize_a_srgb_value(StringBuilder&) const;
String serialize_a_srgb_value() const;
ByteString to_byte_string() const;
ByteString to_byte_string_without_alpha() const;
static Optional<Color> from_string(StringView);

View File

@ -148,7 +148,10 @@ ErrorOr<void> JPEGLoadingContext::decode()
// Photoshop writes inverted CMYK data (i.e. Photoshop's 0 should be 255). We convert this
// to expected values.
if (cinfo.saw_Adobe_marker) {
bool should_invert_cmyk = cinfo.jpeg_color_space == JCS_CMYK
&& (!cinfo.saw_Adobe_marker || cinfo.Adobe_transform == 0);
if (should_invert_cmyk) {
for (int i = 0; i < cmyk_bitmap->size().height(); ++i) {
auto* line = cmyk_bitmap->scanline(i);

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -13,6 +14,7 @@
#include <LibGfx/Color.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Gradients.h>
#include <LibGfx/ImmutableBitmap.h>
#include <LibGfx/Rect.h>
namespace Gfx {
@ -20,6 +22,7 @@ namespace Gfx {
class PaintStyle : public RefCounted<PaintStyle> {
public:
virtual ~PaintStyle() = default;
virtual bool is_visible() const { return true; }
};
class SolidColorPaintStyle : public PaintStyle {
@ -29,6 +32,8 @@ public:
return adopt_nonnull_ref_or_enomem(new (nothrow) SolidColorPaintStyle(color));
}
bool is_visible() const override { return m_color.alpha() > 0; }
Color const& color() const { return m_color; }
private:
@ -65,11 +70,47 @@ public:
Optional<float> repeat_length() const { return m_repeat_length; }
bool is_visible() const override
{
return any_of(m_color_stops, [](auto& stop) { return stop.color.alpha() > 0; });
}
private:
Vector<ColorStop, 4> m_color_stops;
Optional<float> m_repeat_length;
};
class CanvasPatternPaintStyle : public PaintStyle {
public:
enum class Repetition : u8 {
Repeat,
RepeatX,
RepeatY,
NoRepeat
};
static ErrorOr<NonnullRefPtr<CanvasPatternPaintStyle>> create(RefPtr<ImmutableBitmap> image, Repetition repetition)
{
return adopt_nonnull_ref_or_enomem(new (nothrow) CanvasPatternPaintStyle(image, repetition));
}
RefPtr<ImmutableBitmap> image() const { return m_image; }
Repetition repetition() const { return m_repetition; }
Optional<AffineTransform> const& transform() const { return m_transform; }
void set_transform(AffineTransform const& transform) { m_transform = transform; }
private:
CanvasPatternPaintStyle(RefPtr<ImmutableBitmap> image, Repetition repetition)
: m_image(image)
, m_repetition(repetition)
{
}
RefPtr<ImmutableBitmap> m_image;
Repetition m_repetition { Repetition::Repeat };
Optional<AffineTransform> m_transform;
};
// The following paint styles implement the gradients required for the HTML canvas.
// These gradients are (unlike CSS ones) not relative to the painted shape, and do not
// support premultiplied alpha.

View File

@ -29,7 +29,7 @@ public:
virtual void draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode, Optional<Gfx::Filter> filters, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) = 0;
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness) = 0;
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) = 0;
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::Path::CapStyle, Gfx::Path::JoinStyle, float miter_limit, Vector<float> const& dash_array, float dash_offset) = 0;
virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, Optional<Gfx::Filter>, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) = 0;
virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, Optional<Gfx::Filter>, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::Path::CapStyle const&, Gfx::Path::JoinStyle const&, float miter_limit, Vector<float> const&, float dash_offset) = 0;
@ -43,6 +43,8 @@ public:
virtual void restore() = 0;
virtual void clip(Gfx::Path const&, Gfx::WindingRule) = 0;
virtual void reset() = 0;
};
}

View File

@ -2,23 +2,27 @@
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#define AK_DONT_REPLACE_STD
#include <AK/GenericShorthands.h>
#include <AK/OwnPtr.h>
#include <AK/String.h>
#include <AK/TypeCasts.h>
#include <LibGfx/Filter.h>
#include <LibGfx/ImmutableBitmap.h>
#include <LibGfx/PainterSkia.h>
#include <LibGfx/PathSkia.h>
#include <LibGfx/SkiaUtils.h>
#include <AK/TypeCasts.h>
#include <core/SkCanvas.h>
#include <core/SkImage.h>
#include <core/SkPath.h>
#include <core/SkPathEffect.h>
#include <effects/SkBlurMaskFilter.h>
#include <effects/SkDashPathEffect.h>
#include <effects/SkGradientShader.h>
@ -43,14 +47,12 @@ struct PainterSkia::Impl {
}
};
static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style)
static void apply_paint_style(SkPaint& paint, PaintStyle const& style)
{
if (is<Gfx::SolidColorPaintStyle>(style)) {
auto const& solid_color = static_cast<Gfx::SolidColorPaintStyle const&>(style);
paint.setColor(to_skia_color(solid_color.color()));
} else if (is<Gfx::CanvasLinearGradientPaintStyle>(style)) {
auto const& linear_gradient = static_cast<Gfx::CanvasLinearGradientPaintStyle const&>(style);
auto const& color_stops = linear_gradient.color_stops();
if (auto const& solid_color = as_if<SolidColorPaintStyle>(style)) {
paint.setColor(to_skia_color(solid_color->color()));
} else if (auto const& linear_gradient = as_if<Gfx::CanvasLinearGradientPaintStyle>(style)) {
auto const& color_stops = linear_gradient->color_stops();
Vector<SkColor> colors;
colors.ensure_capacity(color_stops.size());
@ -61,16 +63,13 @@ static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style)
positions.append(color_stop.position);
}
Array<SkPoint, 2> points;
points[0] = to_skia_point(linear_gradient.start_point());
points[1] = to_skia_point(linear_gradient.end_point());
Array points { to_skia_point(linear_gradient->start_point()), to_skia_point(linear_gradient->end_point()) };
SkMatrix matrix;
auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), positions.data(), color_stops.size(), SkTileMode::kClamp, 0, &matrix);
paint.setShader(shader);
} else if (is<Gfx::CanvasRadialGradientPaintStyle>(style)) {
auto const& radial_gradient = static_cast<Gfx::CanvasRadialGradientPaintStyle const&>(style);
auto const& color_stops = radial_gradient.color_stops();
} else if (auto const* radial_gradient = as_if<CanvasRadialGradientPaintStyle>(style)) {
auto const& color_stops = radial_gradient->color_stops();
Vector<SkColor> colors;
colors.ensure_capacity(color_stops.size());
@ -81,10 +80,10 @@ static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style)
positions.append(color_stop.position);
}
auto start_center = radial_gradient.start_center();
auto end_center = radial_gradient.end_center();
auto start_radius = radial_gradient.start_radius();
auto end_radius = radial_gradient.end_radius();
auto start_center = radial_gradient->start_center();
auto end_center = radial_gradient->end_center();
auto start_radius = radial_gradient->start_radius();
auto end_radius = radial_gradient->end_radius();
auto start_sk_point = to_skia_point(start_center);
auto end_sk_point = to_skia_point(end_center);
@ -92,6 +91,34 @@ static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style)
SkMatrix matrix;
auto shader = SkGradientShader::MakeTwoPointConical(start_sk_point, start_radius, end_sk_point, end_radius, colors.data(), positions.data(), color_stops.size(), SkTileMode::kClamp, 0, &matrix);
paint.setShader(shader);
} else if (auto const* canvas_pattern = as_if<CanvasPatternPaintStyle>(style)) {
auto image = canvas_pattern->image();
if (!image)
return;
auto const* sk_image = image->sk_image();
auto repetition = canvas_pattern->repetition();
auto repeat_x = first_is_one_of(repetition, CanvasPatternPaintStyle::Repetition::Repeat, CanvasPatternPaintStyle::Repetition::RepeatX);
auto repeat_y = first_is_one_of(repetition, CanvasPatternPaintStyle::Repetition::Repeat, CanvasPatternPaintStyle::Repetition::RepeatY);
// FIXME: Implement sampling configuration.
SkSamplingOptions sk_sampling_options { SkFilterMode::kLinear };
Optional<SkMatrix> transformation_matrix;
if (canvas_pattern->transform().has_value()) {
auto const& transform = canvas_pattern->transform().value();
transformation_matrix = SkMatrix::MakeAll(
transform.a(), transform.c(), transform.e(),
transform.b(), transform.d(), transform.f(),
0, 0, 1);
}
auto shader = sk_image->makeShader(
repeat_x ? SkTileMode::kRepeat : SkTileMode::kDecal,
repeat_y ? SkTileMode::kRepeat : SkTileMode::kDecal,
sk_sampling_options, transformation_matrix.has_value() ? &transformation_matrix.value() : nullptr);
paint.setShader(move(shader));
} else {
dbgln("FIXME: Unsupported PaintStyle");
}
}
@ -115,6 +142,9 @@ static SkPaint to_skia_paint(Gfx::PaintStyle const& style, Optional<Gfx::Filter
PainterSkia::PainterSkia(NonnullRefPtr<Gfx::PaintingSurface> painting_surface)
: m_impl(adopt_own(*new Impl { move(painting_surface) }))
{
m_impl->with_canvas([this](auto& canvas) {
m_initial_save_count = canvas.save();
});
}
PainterSkia::~PainterSkia() = default;
@ -188,7 +218,7 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thi
});
}
void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator)
void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::Path::CapStyle cap_style, Gfx::Path::JoinStyle join_style, float miter_limit, Vector<float> const& dash_array, float dash_offset)
{
// Skia treats zero thickness as a special case and will draw a hairline, while we want to draw nothing.
if (thickness <= 0)
@ -200,6 +230,10 @@ void PainterSkia::stroke_path(Gfx::Path const& path, Gfx::Color color, float thi
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(thickness);
paint.setColor(to_skia_color(color));
paint.setStrokeCap(to_skia_cap(cap_style));
paint.setStrokeJoin(to_skia_join(join_style));
paint.setStrokeMiter(miter_limit);
paint.setPathEffect(SkDashPathEffect::Make(dash_array.data(), dash_array.size(), dash_offset));
paint.setBlender(to_skia_blender(compositing_and_blending_operator));
auto sk_path = to_skia_path(path);
impl().with_canvas([&](auto& canvas) {
@ -312,4 +346,11 @@ void PainterSkia::clip(Gfx::Path const& path, Gfx::WindingRule winding_rule)
});
}
void PainterSkia::reset()
{
impl().with_canvas([&](auto& canvas) {
canvas.restoreToCount(m_initial_save_count);
});
}
}

View File

@ -7,7 +7,6 @@
#pragma once
#include <AK/NonnullOwnPtr.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/CompositingAndBlendingOperator.h>
#include <LibGfx/Painter.h>
#include <LibGfx/PaintingSurface.h>
@ -23,7 +22,7 @@ public:
virtual void fill_rect(Gfx::FloatRect const&, Color) override;
virtual void draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode, Optional<Gfx::Filter>, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) override;
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness) override;
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) override;
virtual void stroke_path(Gfx::Path const&, Gfx::Color, float thickness, float blur_radius, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::Path::CapStyle, Gfx::Path::JoinStyle, float miter_limit, Vector<float> const& dash_array, float dash_offset) override;
virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, Optional<Gfx::Filter>, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator) override;
virtual void stroke_path(Gfx::Path const&, Gfx::PaintStyle const&, Optional<Gfx::Filter>, float thickness, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Gfx::Path::CapStyle const&, Gfx::Path::JoinStyle const&, float miter_limit, Vector<float> const&, float dash_offset) override;
virtual void fill_path(Gfx::Path const&, Gfx::Color, Gfx::WindingRule) override;
@ -33,11 +32,13 @@ public:
virtual void save() override;
virtual void restore() override;
virtual void clip(Gfx::Path const&, Gfx::WindingRule) override;
virtual void reset() override;
private:
struct Impl;
Impl& impl() { return *m_impl; }
NonnullOwnPtr<Impl> m_impl;
u32 m_initial_save_count { 0 };
};
}

View File

@ -1,6 +1,7 @@
/*
* Copyright (c) 2024, Pavel Shliak <shlyakpavel@gmail.com>
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -11,6 +12,7 @@
#include <LibGfx/Bitmap.h>
#include <LibGfx/CompositingAndBlendingOperator.h>
#include <LibGfx/Filter.h>
#include <LibGfx/PaintStyle.h>
#include <LibGfx/PathSkia.h>
#include <LibGfx/ScalingMode.h>
#include <LibGfx/WindingRule.h>
@ -20,7 +22,6 @@
#include <core/SkImageFilter.h>
#include <core/SkPaint.h>
#include <core/SkPath.h>
#include <core/SkPathEffect.h>
#include <core/SkSamplingOptions.h>
namespace Gfx {

View File

@ -12,9 +12,9 @@
# include <AK/NonnullRefPtr.h>
# include <AK/RefCounted.h>
# include <vulkan/vulkan.h>
# ifdef AK_OS_LINUX
# if defined(AK_OS_LINUX) || defined(AK_OS_FREEBSD)
# include <libdrm/drm_fourcc.h>
// Sharable Vulkan images are currently only implemented on Linux
// Sharable Vulkan images are currently only implemented on Linux and FreeBSD
# define USE_VULKAN_IMAGES 1
# endif

View File

@ -21,10 +21,7 @@ ConnectionBase::ConnectionBase(IPC::Stub& local_stub, NonnullOwnPtr<Transport> t
{
m_transport->set_up_read_hook([this] {
NonnullRefPtr protect = *this;
if (auto result = drain_messages_from_peer(); result.is_error())
dbgln("Read hook error while draining messages: {}", result.error());
drain_messages_from_peer();
handle_messages();
});
}
@ -96,7 +93,7 @@ void ConnectionBase::wait_for_transport_to_become_readable()
m_transport->wait_until_readable();
}
ErrorOr<void> ConnectionBase::drain_messages_from_peer()
ConnectionBase::PeerEOF ConnectionBase::drain_messages_from_peer()
{
auto schedule_shutdown = m_transport->read_as_many_messages_as_possible_without_blocking([&](auto&& raw_message) {
if (auto message = try_parse_message(raw_message.bytes, raw_message.fds)) {
@ -117,10 +114,10 @@ ErrorOr<void> ConnectionBase::drain_messages_from_peer()
deferred_invoke([this] {
shutdown();
});
return Error::from_string_literal("IPC connection EOF");
return PeerEOF::Yes;
}
return {};
return PeerEOF::No;
}
OwnPtr<IPC::Message> ConnectionBase::wait_for_specific_endpoint_message_impl(u32 endpoint_magic, int message_id)
@ -140,7 +137,7 @@ OwnPtr<IPC::Message> ConnectionBase::wait_for_specific_endpoint_message_impl(u32
break;
wait_for_transport_to_become_readable();
if (drain_messages_from_peer().is_error())
if (drain_messages_from_peer() == PeerEOF::Yes)
break;
}

View File

@ -41,7 +41,11 @@ protected:
OwnPtr<IPC::Message> wait_for_specific_endpoint_message_impl(u32 endpoint_magic, int message_id);
void wait_for_transport_to_become_readable();
ErrorOr<void> drain_messages_from_peer();
enum class PeerEOF {
No,
Yes
};
PeerEOF drain_messages_from_peer();
void handle_messages();

View File

@ -31,11 +31,6 @@ public:
, ClientEndpoint::template Proxy<ServerEndpoint>(*this, {})
, m_client_id(client_id)
{
this->transport().set_up_read_hook([this] {
NonnullRefPtr protect = *this;
// FIXME: Do something about errors.
(void)this->drain_messages_from_peer();
});
}
virtual ~ConnectionFromClient() override = default;

View File

@ -87,39 +87,6 @@ void LabelledStatement::dump(int indent) const
m_labelled_item->dump(indent + 2);
}
// 15.2.5 Runtime Semantics: InstantiateOrdinaryFunctionExpression, https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionexpression
Value FunctionExpression::instantiate_ordinary_function_expression(VM& vm, Utf16FlyString given_name) const
{
auto& realm = *vm.current_realm();
if (given_name.is_empty())
given_name = Utf16FlyString {};
auto own_name = name();
auto has_own_name = !own_name.is_empty();
auto const& used_name = has_own_name ? own_name : given_name;
auto environment = GC::Ref { *vm.running_execution_context().lexical_environment };
if (has_own_name) {
VERIFY(environment);
environment = new_declarative_environment(*environment);
MUST(environment->create_immutable_binding(vm, own_name, false));
}
auto private_environment = vm.running_execution_context().private_environment;
auto closure = ECMAScriptFunctionObject::create_from_function_node(*this, used_name, realm, environment, private_environment);
// FIXME: 6. Perform SetFunctionName(closure, name).
// FIXME: 7. Perform MakeConstructor(closure).
if (has_own_name)
MUST(environment->initialize_binding(vm, own_name, closure, Environment::InitializeBindingHint::Normal));
return closure;
}
Optional<Utf16String> CallExpression::expression_string() const
{
if (is<Identifier>(*m_callee))
@ -355,7 +322,6 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> ClassExpression::create_class_const
}
auto prototype = Object::create_prototype(realm, proto_parent);
VERIFY(prototype);
// FIXME: Step 14.a is done in the parser. By using a synthetic super(...args) which does not call @@iterator of %Array.prototype%
auto const& constructor = *m_constructor;
@ -853,14 +819,14 @@ FunctionNode::FunctionNode(RefPtr<Identifier const> name, ByteString source_text
FunctionNode::~FunctionNode() = default;
void FunctionNode::set_shared_data(RefPtr<SharedFunctionInstanceData> shared_data) const
void FunctionNode::set_shared_data(GC::Ptr<SharedFunctionInstanceData> shared_data) const
{
m_shared_data = move(shared_data);
}
RefPtr<SharedFunctionInstanceData> FunctionNode::shared_data() const
GC::Ptr<SharedFunctionInstanceData> FunctionNode::shared_data() const
{
return m_shared_data;
return m_shared_data.ptr();
}
void FunctionNode::dump(int indent, ByteString const& class_name) const
@ -1621,80 +1587,6 @@ bool ImportStatement::has_bound_name(Utf16FlyString const& name) const
});
}
// 14.2.3 BlockDeclarationInstantiation ( code, env ), https://tc39.es/ecma262/#sec-blockdeclarationinstantiation
void ScopeNode::block_declaration_instantiation(VM& vm, Environment* environment) const
{
// See also B.3.2.6 Changes to BlockDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-blockdeclarationinstantiation
auto& realm = *vm.current_realm();
VERIFY(environment);
// 1. Let declarations be the LexicallyScopedDeclarations of code.
// 2. Let privateEnv be the running execution context's PrivateEnvironment.
auto private_environment = vm.running_execution_context().private_environment;
// Note: All the calls here are ! and thus we do not need to TRY this callback.
// We use MUST to ensure it does not throw and to avoid discarding the returned ThrowCompletionOr<void>.
// 3. For each element d of declarations, do
MUST(for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
auto is_constant_declaration = declaration.is_constant_declaration();
// NOTE: Due to the use of MUST with `create_immutable_binding` and `create_mutable_binding` below,
// an exception should not result from `for_each_bound_name`.
// a. For each element dn of the BoundNames of d, do
MUST(declaration.for_each_bound_identifier([&](Identifier const& identifier) {
if (identifier.is_local()) {
// NOTE: No need to create bindings for local variables as their values are not stored in an environment.
return;
}
auto const& name = identifier.string();
// i. If IsConstantDeclaration of d is true, then
if (is_constant_declaration) {
// 1. Perform ! env.CreateImmutableBinding(dn, true).
MUST(environment->create_immutable_binding(vm, name, true));
}
// ii. Else,
else {
// 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6.
if (!MUST(environment->has_binding(name)))
MUST(environment->create_mutable_binding(vm, name, false));
}
}));
// b. If d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then
if (is<FunctionDeclaration>(declaration)) {
// i. Let fn be the sole element of the BoundNames of d.
auto& function_declaration = static_cast<FunctionDeclaration const&>(declaration);
// ii. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
auto function = ECMAScriptFunctionObject::create_from_function_node(
function_declaration,
function_declaration.name(),
realm,
environment,
private_environment);
// iii. Perform ! env.InitializeBinding(fn, fo). NOTE: This step is replaced in section B.3.2.6.
if (function_declaration.name_identifier()->is_local()) {
auto& running_execution_context = vm.running_execution_context();
auto number_of_registers = running_execution_context.executable->number_of_registers;
auto number_of_constants = running_execution_context.executable->constants.size();
auto local_index = function_declaration.name_identifier()->local_index();
if (local_index.is_variable()) {
running_execution_context.local(local_index.index + number_of_registers + number_of_constants) = function;
} else {
VERIFY_NOT_REACHED();
}
} else {
VERIFY(is<DeclarativeEnvironment>(*environment));
static_cast<DeclarativeEnvironment&>(*environment).initialize_or_set_mutable_binding({}, vm, function->name(), function);
}
}
}));
}
// 16.1.7 GlobalDeclarationInstantiation ( script, env ), https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
ThrowCompletionOr<void> Program::global_declaration_instantiation(VM& vm, GlobalEnvironment& global_environment) const
{

View File

@ -176,12 +176,6 @@ public:
: ASTNode(move(source_range))
{
}
Bytecode::Executable* bytecode_executable() const { return m_bytecode_executable; }
void set_bytecode_executable(Bytecode::Executable* bytecode_executable) { m_bytecode_executable = make_root(bytecode_executable); }
private:
GC::Root<Bytecode::Executable> m_bytecode_executable;
};
// 14.13 Labelled Statements, https://tc39.es/ecma262/#sec-labelled-statements
@ -340,8 +334,6 @@ public:
ThrowCompletionOr<void> for_each_var_function_declaration_in_reverse_order(ThrowCompletionOrVoidCallback<FunctionDeclaration const&>&& callback) const;
ThrowCompletionOr<void> for_each_var_scoped_variable_declaration(ThrowCompletionOrVoidCallback<VariableDeclaration const&>&& callback) const;
void block_declaration_instantiation(VM&, Environment*) const;
ThrowCompletionOr<void> for_each_function_hoistable_with_annexB_extension(ThrowCompletionOrVoidCallback<FunctionDeclaration&>&& callback) const;
auto const& local_variables_names() const { return m_local_variables_names; }
@ -799,10 +791,9 @@ public:
bool uses_this_from_environment() const { return m_parsing_insights.uses_this_from_environment; }
virtual bool has_name() const = 0;
virtual Value instantiate_ordinary_function_expression(VM&, Utf16FlyString given_name) const = 0;
RefPtr<SharedFunctionInstanceData> shared_data() const;
void set_shared_data(RefPtr<SharedFunctionInstanceData>) const;
GC::Ptr<SharedFunctionInstanceData> shared_data() const;
void set_shared_data(GC::Ptr<SharedFunctionInstanceData>) const;
virtual ~FunctionNode();
@ -824,7 +815,7 @@ private:
Vector<LocalVariable> m_local_variables_names;
mutable RefPtr<SharedFunctionInstanceData> m_shared_data;
mutable GC::Root<SharedFunctionInstanceData> m_shared_data;
};
class FunctionDeclaration final
@ -849,7 +840,6 @@ public:
void set_should_do_additional_annexB_steps() { m_is_hoisted = true; }
bool has_name() const override { return true; }
Value instantiate_ordinary_function_expression(VM&, Utf16FlyString) const override { VERIFY_NOT_REACHED(); }
virtual ~FunctionDeclaration() { }
@ -876,8 +866,6 @@ public:
bool has_name() const override { return !name().is_empty(); }
Value instantiate_ordinary_function_expression(VM&, Utf16FlyString given_name) const override;
virtual ~FunctionExpression() { }
private:

View File

@ -645,15 +645,15 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> AssignmentExpression::g
if (expression.is_computed()) {
if (!lhs_is_super_expression)
generator.emit_put_by_value(*base, *computed_property, rval, Bytecode::Op::PropertyKind::KeyValue, move(base_identifier));
generator.emit_put_by_value(*base, *computed_property, rval, Bytecode::PutKind::Normal, move(base_identifier));
else
generator.emit_put_by_value_with_this(*base, *computed_property, *this_value, rval, Op::PropertyKind::KeyValue);
generator.emit_put_by_value_with_this(*base, *computed_property, *this_value, rval, PutKind::Normal);
} else if (expression.property().is_identifier()) {
auto identifier_table_ref = generator.intern_identifier(as<Identifier>(expression.property()).string());
if (!lhs_is_super_expression)
generator.emit_put_by_id(*base, identifier_table_ref, rval, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache(), move(base_identifier));
generator.emit_put_by_id(*base, identifier_table_ref, rval, Bytecode::PutKind::Normal, generator.next_property_lookup_cache(), move(base_identifier));
else
generator.emit<Bytecode::Op::PutByIdWithThis>(*base, *this_value, identifier_table_ref, rval, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache());
generator.emit<Bytecode::Op::PutNormalByIdWithThis>(*base, *this_value, identifier_table_ref, rval, generator.next_property_lookup_cache());
} else if (expression.property().is_private_identifier()) {
auto identifier_table_ref = generator.intern_identifier(as<PrivateIdentifier>(expression.property()).string());
generator.emit<Bytecode::Op::PutPrivateById>(*base, identifier_table_ref, rval);
@ -1151,19 +1151,19 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ObjectExpression::gener
generator.push_home_object(object);
for (auto& property : m_properties) {
Bytecode::Op::PropertyKind property_kind;
Bytecode::PutKind property_kind;
switch (property->type()) {
case ObjectProperty::Type::KeyValue:
property_kind = Bytecode::Op::PropertyKind::DirectKeyValue;
property_kind = Bytecode::PutKind::Own;
break;
case ObjectProperty::Type::Getter:
property_kind = Bytecode::Op::PropertyKind::Getter;
property_kind = Bytecode::PutKind::Getter;
break;
case ObjectProperty::Type::Setter:
property_kind = Bytecode::Op::PropertyKind::Setter;
property_kind = Bytecode::PutKind::Setter;
break;
case ObjectProperty::Type::ProtoSetter:
property_kind = Bytecode::Op::PropertyKind::ProtoSetter;
property_kind = Bytecode::PutKind::Prototype;
break;
case ObjectProperty::Type::Spread:
generator.emit<Bytecode::Op::PutBySpread>(object, TRY(property->key().generate_bytecode(generator)).value());
@ -1175,13 +1175,13 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ObjectExpression::gener
Bytecode::IdentifierTableIndex key_name = generator.intern_identifier(string_literal.value());
Optional<ScopedOperand> value;
if (property_kind == Bytecode::Op::PropertyKind::ProtoSetter) {
if (property_kind == Bytecode::PutKind::Prototype) {
value = TRY(property->value().generate_bytecode(generator)).value();
} else {
auto identifier = string_literal.value();
if (property_kind == Bytecode::Op::PropertyKind::Getter)
if (property_kind == Bytecode::PutKind::Getter)
identifier = Utf16String::formatted("get {}", identifier);
else if (property_kind == Bytecode::Op::PropertyKind::Setter)
else if (property_kind == Bytecode::PutKind::Setter)
identifier = Utf16String::formatted("set {}", identifier);
auto name = generator.intern_identifier(identifier);
@ -2540,7 +2540,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> TaggedTemplateLiteral::
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(raw_string_regs.size(), raw_strings_array, raw_string_regs);
}
generator.emit_put_by_id(strings_array, generator.intern_identifier("raw"_utf16_fly_string), raw_strings_array, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache());
generator.emit_put_by_id(strings_array, generator.intern_identifier("raw"_utf16_fly_string), raw_strings_array, Bytecode::PutKind::Normal, generator.next_property_lookup_cache());
auto arguments = generator.allocate_register();
if (!argument_regs.is_empty())

View File

@ -26,6 +26,7 @@ namespace JS::Bytecode {
O(MathSin, math_sin, Math, sin, 1) \
O(MathCos, math_cos, Math, cos, 1) \
O(MathTan, math_tan, Math, tan, 1) \
O(OrdinaryHasInstance, ordinary_has_instance, InternalBuiltin, ordinary_has_instance, 1) \
O(ArrayIteratorPrototypeNext, array_iterator_prototype_next, ArrayIteratorPrototype, next, 0) \
O(MapIteratorPrototypeNext, map_iterator_prototype_next, MapIteratorPrototype, next, 0) \
O(SetIteratorPrototypeNext, set_iterator_prototype_next, SetIteratorPrototype, next, 0) \

View File

@ -25,7 +25,7 @@ Executable::Executable(
size_t number_of_property_lookup_caches,
size_t number_of_global_variable_caches,
size_t number_of_registers,
bool is_strict_mode)
Strict strict)
: bytecode(move(bytecode))
, string_table(move(string_table))
, identifier_table(move(identifier_table))
@ -33,7 +33,7 @@ Executable::Executable(
, constants(move(constants))
, source_code(move(source_code))
, number_of_registers(number_of_registers)
, is_strict_mode(is_strict_mode)
, is_strict_mode(strict == Strict::Yes)
{
property_lookup_caches.resize(number_of_property_lookup_caches);
global_variable_caches.resize(number_of_global_variable_caches);

View File

@ -10,8 +10,9 @@
#include <AK/NonnullOwnPtr.h>
#include <AK/OwnPtr.h>
#include <AK/Utf16FlyString.h>
#include <AK/WeakPtr.h>
#include <LibGC/CellAllocator.h>
#include <LibGC/Weak.h>
#include <LibGC/WeakInlines.h>
#include <LibJS/Bytecode/IdentifierTable.h>
#include <LibJS/Bytecode/Label.h>
#include <LibJS/Bytecode/StringTable.h>
@ -37,11 +38,12 @@ struct PropertyLookupCache {
GetPropertyInPrototypeChain,
};
Type type { Type::Empty };
WeakPtr<Shape> from_shape;
WeakPtr<Shape> shape;
GC::Weak<Shape> from_shape;
GC::Weak<Shape> shape;
Optional<u32> property_offset;
WeakPtr<Object> prototype;
WeakPtr<PrototypeChainValidity> prototype_chain_validity;
GC::Weak<Object> prototype;
GC::Weak<PrototypeChainValidity> prototype_chain_validity;
Optional<u32> shape_dictionary_generation;
};
AK::Array<Entry, max_number_of_shapes_to_remember> entries;
};
@ -73,7 +75,7 @@ public:
size_t number_of_property_lookup_caches,
size_t number_of_global_variable_caches,
size_t number_of_registers,
bool is_strict_mode);
Strict);
virtual ~Executable() override;
@ -90,6 +92,8 @@ public:
size_t number_of_registers { 0 };
bool is_strict_mode { false };
size_t registers_and_constants_and_locals_count { 0 };
struct ExceptionHandlers {
size_t start_offset;
size_t end_offset;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -179,7 +179,7 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
if (!function.is_strict_mode()) {
bool can_elide_lexical_environment = !scope_body || !scope_body->has_non_local_lexical_declarations();
if (!can_elide_lexical_environment) {
emit<Op::CreateLexicalEnvironment>(function.shared_data().m_lex_environment_bindings_count);
emit<Op::CreateLexicalEnvironment>(OptionalNone {}, function.shared_data().m_lex_environment_bindings_count);
}
}
@ -218,6 +218,14 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
CodeGenerationErrorOr<GC::Ref<Executable>> Generator::compile(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind, GC::Ptr<ECMAScriptFunctionObject const> function, MustPropagateCompletion must_propagate_completion, Vector<LocalVariable> local_variable_names)
{
Generator generator(vm, function, must_propagate_completion);
if (is<Program>(node))
generator.m_strict = static_cast<Program const&>(node).is_strict_mode() ? Strict::Yes : Strict::No;
else if (is<FunctionBody>(node))
generator.m_strict = static_cast<FunctionBody const&>(node).in_strict_mode() ? Strict::Yes : Strict::No;
else if (is<FunctionDeclaration>(node))
generator.m_strict = static_cast<FunctionDeclaration const&>(node).is_strict_mode() ? Strict::Yes : Strict::No;
generator.m_local_variables = local_variable_names;
generator.switch_to_basic_block(generator.make_block());
@ -260,14 +268,6 @@ CodeGenerationErrorOr<GC::Ref<Executable>> Generator::compile(VM& vm, ASTNode co
}
}
bool is_strict_mode = false;
if (is<Program>(node))
is_strict_mode = static_cast<Program const&>(node).is_strict_mode();
else if (is<FunctionBody>(node))
is_strict_mode = static_cast<FunctionBody const&>(node).in_strict_mode();
else if (is<FunctionDeclaration>(node))
is_strict_mode = static_cast<FunctionDeclaration const&>(node).is_strict_mode();
size_t size_needed = 0;
for (auto& block : generator.m_root_basic_blocks) {
size_needed += block->size();
@ -453,7 +453,7 @@ CodeGenerationErrorOr<GC::Ref<Executable>> Generator::compile(VM& vm, ASTNode co
generator.m_next_property_lookup_cache,
generator.m_next_global_variable_cache,
generator.m_next_register,
is_strict_mode);
generator.m_strict);
Vector<Executable::ExceptionHandlers> linked_exception_handlers;
@ -477,6 +477,8 @@ CodeGenerationErrorOr<GC::Ref<Executable>> Generator::compile(VM& vm, ASTNode co
executable->argument_index_base = number_of_registers + number_of_constants + number_of_locals;
executable->length_identifier = generator.m_length_identifier;
executable->registers_and_constants_and_locals_count = executable->number_of_registers + executable->constants.size() + executable->local_variable_names.size();
generator.m_finished = true;
return executable;
@ -570,10 +572,58 @@ bool Generator::emit_block_declaration_instantiation(ScopeNode const& scope_node
if (!needs_block_declaration_instantiation)
return false;
// FIXME: Generate the actual bytecode for block declaration instantiation
// and get rid of the BlockDeclarationInstantiation instruction.
auto environment = allocate_register();
emit<Bytecode::Op::CreateLexicalEnvironment>(environment);
start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
emit<Bytecode::Op::BlockDeclarationInstantiation>(scope_node);
MUST(scope_node.for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
auto is_constant_declaration = declaration.is_constant_declaration();
// NOTE: Due to the use of MUST with `create_immutable_binding` and `create_mutable_binding` below,
// an exception should not result from `for_each_bound_name`.
// a. For each element dn of the BoundNames of d, do
MUST(declaration.for_each_bound_identifier([&](Identifier const& identifier) {
if (identifier.is_local()) {
// NOTE: No need to create bindings for local variables as their values are not stored in an environment.
return;
}
auto const& name = identifier.string();
// i. If IsConstantDeclaration of d is true, then
if (is_constant_declaration) {
// 1. Perform ! env.CreateImmutableBinding(dn, true).
emit<Bytecode::Op::CreateImmutableBinding>(environment, intern_identifier(name), true);
}
// ii. Else,
else {
// 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6.
emit<Bytecode::Op::CreateMutableBinding>(environment, intern_identifier(name), false);
}
}));
// b. If d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then
if (is<FunctionDeclaration>(declaration)) {
// i. Let fn be the sole element of the BoundNames of d.
auto& function_declaration = static_cast<FunctionDeclaration const&>(declaration);
// ii. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
auto fo = allocate_register();
emit<Bytecode::Op::NewFunction>(fo, function_declaration, OptionalNone {});
// iii. Perform ! env.InitializeBinding(fn, fo). NOTE: This step is replaced in section B.3.2.6.
if (function_declaration.name_identifier()->is_local()) {
auto local_index = function_declaration.name_identifier()->local_index();
if (local_index.is_variable()) {
emit<Bytecode::Op::Mov>(local(local_index), fo);
} else {
VERIFY_NOT_REACHED();
}
} else {
emit<Bytecode::Op::InitializeLexicalBinding>(intern_identifier(function_declaration.name()), fo);
}
}
}));
return true;
}
@ -761,21 +811,21 @@ CodeGenerationErrorOr<void> Generator::emit_store_to_reference(JS::ASTNode const
if (super_reference.referenced_name.has_value()) {
// 5. Let propertyKey be ? ToPropertyKey(propertyNameValue).
// FIXME: This does ToPropertyKey out of order, which is observable by Symbol.toPrimitive!
emit_put_by_value_with_this(*super_reference.base, *super_reference.referenced_name, *super_reference.this_value, value, Op::PropertyKind::KeyValue);
emit_put_by_value_with_this(*super_reference.base, *super_reference.referenced_name, *super_reference.this_value, value, PutKind::Normal);
} else {
// 3. Let propertyKey be StringValue of IdentifierName.
auto identifier_table_ref = intern_identifier(as<Identifier>(expression.property()).string());
emit<Bytecode::Op::PutByIdWithThis>(*super_reference.base, *super_reference.this_value, identifier_table_ref, value, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache());
emit<Bytecode::Op::PutNormalByIdWithThis>(*super_reference.base, *super_reference.this_value, identifier_table_ref, value, next_property_lookup_cache());
}
} else {
auto object = TRY(expression.object().generate_bytecode(*this)).value();
if (expression.is_computed()) {
auto property = TRY(expression.property().generate_bytecode(*this)).value();
emit_put_by_value(object, property, value, Op::PropertyKind::KeyValue, {});
emit_put_by_value(object, property, value, PutKind::Normal, {});
} else if (expression.property().is_identifier()) {
auto identifier_table_ref = intern_identifier(as<Identifier>(expression.property()).string());
emit_put_by_id(object, identifier_table_ref, value, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache());
emit_put_by_id(object, identifier_table_ref, value, Bytecode::PutKind::Normal, next_property_lookup_cache());
} else if (expression.property().is_private_identifier()) {
auto identifier_table_ref = intern_identifier(as<PrivateIdentifier>(expression.property()).string());
emit<Bytecode::Op::PutPrivateById>(object, identifier_table_ref, value);
@ -804,15 +854,15 @@ CodeGenerationErrorOr<void> Generator::emit_store_to_reference(ReferenceOperands
}
if (reference.referenced_identifier.has_value()) {
if (reference.base == reference.this_value)
emit_put_by_id(*reference.base, *reference.referenced_identifier, value, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache());
emit_put_by_id(*reference.base, *reference.referenced_identifier, value, Bytecode::PutKind::Normal, next_property_lookup_cache());
else
emit<Bytecode::Op::PutByIdWithThis>(*reference.base, *reference.this_value, *reference.referenced_identifier, value, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache());
emit<Bytecode::Op::PutNormalByIdWithThis>(*reference.base, *reference.this_value, *reference.referenced_identifier, value, next_property_lookup_cache());
return {};
}
if (reference.base == reference.this_value)
emit_put_by_value(*reference.base, *reference.referenced_name, value, Op::PropertyKind::KeyValue, {});
emit_put_by_value(*reference.base, *reference.referenced_name, value, PutKind::Normal, {});
else
emit_put_by_value_with_this(*reference.base, *reference.referenced_name, *reference.this_value, value, Op::PropertyKind::KeyValue);
emit_put_by_value_with_this(*reference.base, *reference.referenced_name, *reference.this_value, value, PutKind::Normal);
return {};
}
@ -1153,20 +1203,38 @@ void Generator::emit_get_by_value_with_this(ScopedOperand dst, ScopedOperand bas
emit<Op::GetByValueWithThis>(dst, base, property, this_value);
}
void Generator::emit_put_by_id(Operand base, IdentifierTableIndex property, Operand src, Op::PropertyKind kind, u32 cache_index, Optional<IdentifierTableIndex> base_identifier)
void Generator::emit_put_by_id(Operand base, IdentifierTableIndex property, Operand src, PutKind kind, u32 cache_index, Optional<IdentifierTableIndex> base_identifier)
{
auto string = m_identifier_table->get(property);
if (!string.is_empty() && !(string.code_unit_at(0) == '0' && string.length_in_code_units() > 1)) {
auto property_index = string.to_number<u32>(TrimWhitespace::No);
if (property_index.has_value() && property_index.value() < NumericLimits<u32>::max()) {
emit<Op::PutByNumericId>(base, property_index.release_value(), src, kind, cache_index, move(base_identifier));
#define EMIT_PUT_BY_NUMERIC_ID(kind) \
case PutKind::kind: \
emit<Op::Put##kind##ByNumericId>(base, property_index.release_value(), src, cache_index, move(base_identifier)); \
break;
switch (kind) {
JS_ENUMERATE_PUT_KINDS(EMIT_PUT_BY_NUMERIC_ID)
default:
VERIFY_NOT_REACHED();
}
#undef EMIT_PUT_BY_NUMERIC_ID
return;
}
}
emit<Op::PutById>(base, property, src, kind, cache_index, move(base_identifier));
#define EMIT_PUT_BY_ID(kind) \
case PutKind::kind: \
emit<Op::Put##kind##ById>(base, property, src, cache_index, move(base_identifier)); \
break;
switch (kind) {
JS_ENUMERATE_PUT_KINDS(EMIT_PUT_BY_ID)
default:
VERIFY_NOT_REACHED();
}
#undef EMIT_PUT_BY_ID
}
void Generator::emit_put_by_value(ScopedOperand base, ScopedOperand property, ScopedOperand src, Bytecode::Op::PropertyKind kind, Optional<IdentifierTableIndex> base_identifier)
void Generator::emit_put_by_value(ScopedOperand base, ScopedOperand property, ScopedOperand src, Bytecode::PutKind kind, Optional<IdentifierTableIndex> base_identifier)
{
if (property.operand().is_constant() && get_constant(property).is_string()) {
auto property_key = MUST(get_constant(property).to_property_key(vm()));
@ -1175,19 +1243,46 @@ void Generator::emit_put_by_value(ScopedOperand base, ScopedOperand property, Sc
return;
}
}
emit<Op::PutByValue>(base, property, src, kind, base_identifier);
#define EMIT_PUT_BY_VALUE(kind) \
case PutKind::kind: \
emit<Op::Put##kind##ByValue>(base, property, src, move(base_identifier)); \
break;
switch (kind) {
JS_ENUMERATE_PUT_KINDS(EMIT_PUT_BY_VALUE)
default:
VERIFY_NOT_REACHED();
}
#undef EMIT_PUT_BY_VALUE
}
void Generator::emit_put_by_value_with_this(ScopedOperand base, ScopedOperand property, ScopedOperand this_value, ScopedOperand src, Bytecode::Op::PropertyKind kind)
void Generator::emit_put_by_value_with_this(ScopedOperand base, ScopedOperand property, ScopedOperand this_value, ScopedOperand src, Bytecode::PutKind kind)
{
if (property.operand().is_constant() && get_constant(property).is_string()) {
auto property_key = MUST(get_constant(property).to_property_key(vm()));
if (property_key.is_string()) {
emit<Op::PutByIdWithThis>(base, this_value, intern_identifier(property_key.as_string()), src, kind, m_next_property_lookup_cache++);
#define EMIT_PUT_BY_ID_WITH_THIS(kind) \
case PutKind::kind: \
emit<Op::Put##kind##ByIdWithThis>(base, this_value, intern_identifier(property_key.as_string()), src, m_next_property_lookup_cache++); \
break;
switch (kind) {
JS_ENUMERATE_PUT_KINDS(EMIT_PUT_BY_ID_WITH_THIS)
default:
VERIFY_NOT_REACHED();
}
#undef EMIT_PUT_BY_ID_WITH_THIS
return;
}
}
emit<Bytecode::Op::PutByValueWithThis>(base, property, this_value, src, kind);
#define EMIT_PUT_BY_VALUE_WITH_THIS(kind) \
case PutKind::kind: \
emit<Op::Put##kind##ByValueWithThis>(base, property, this_value, src); \
break;
switch (kind) {
JS_ENUMERATE_PUT_KINDS(EMIT_PUT_BY_VALUE_WITH_THIS)
default:
VERIFY_NOT_REACHED();
}
#undef EMIT_PUT_BY_VALUE_WITH_THIS
}
void Generator::emit_iterator_value(ScopedOperand dst, ScopedOperand result)

View File

@ -93,6 +93,7 @@ public:
grow(sizeof(OpType));
void* slot = m_current_basic_block->data() + slot_offset;
new (slot) OpType(forward<Args>(args)...);
static_cast<OpType*>(slot)->set_strict(m_strict);
if constexpr (OpType::IsTerminator)
m_current_basic_block->terminate({});
m_current_basic_block->add_source_map_entry(slot_offset, { m_current_ast_node->start_offset(), m_current_ast_node->end_offset() });
@ -110,6 +111,7 @@ public:
grow(size_to_allocate);
void* slot = m_current_basic_block->data() + slot_offset;
new (slot) OpType(forward<Args>(args)...);
static_cast<OpType*>(slot)->set_strict(m_strict);
if constexpr (OpType::IsTerminator)
m_current_basic_block->terminate({});
m_current_basic_block->add_source_map_entry(slot_offset, { m_current_ast_node->start_offset(), m_current_ast_node->end_offset() });
@ -329,10 +331,10 @@ public:
void emit_get_by_value(ScopedOperand dst, ScopedOperand base, ScopedOperand property, Optional<IdentifierTableIndex> base_identifier = {});
void emit_get_by_value_with_this(ScopedOperand dst, ScopedOperand base, ScopedOperand property, ScopedOperand this_value);
void emit_put_by_id(Operand base, IdentifierTableIndex property, Operand src, Op::PropertyKind kind, u32 cache_index, Optional<IdentifierTableIndex> base_identifier = {});
void emit_put_by_id(Operand base, IdentifierTableIndex property, Operand src, PutKind kind, u32 cache_index, Optional<IdentifierTableIndex> base_identifier = {});
void emit_put_by_value(ScopedOperand base, ScopedOperand property, ScopedOperand src, Bytecode::Op::PropertyKind, Optional<IdentifierTableIndex> base_identifier);
void emit_put_by_value_with_this(ScopedOperand base, ScopedOperand property, ScopedOperand this_value, ScopedOperand src, Bytecode::Op::PropertyKind);
void emit_put_by_value(ScopedOperand base, ScopedOperand property, ScopedOperand src, Bytecode::PutKind, Optional<IdentifierTableIndex> base_identifier);
void emit_put_by_value_with_this(ScopedOperand base, ScopedOperand property, ScopedOperand this_value, ScopedOperand src, Bytecode::PutKind);
void emit_iterator_value(ScopedOperand dst, ScopedOperand result);
void emit_iterator_complete(ScopedOperand dst, ScopedOperand result);
@ -383,6 +385,8 @@ private:
Vector<FlyString> language_label_set;
};
Strict m_strict { Strict::No };
BasicBlock* m_current_basic_block { nullptr };
ASTNode const* m_current_ast_node { nullptr };
UnwindContext const* m_current_unwind_context { nullptr };

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -10,6 +10,7 @@
#include <AK/Function.h>
#include <AK/Span.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/PutKind.h>
#include <LibJS/Forward.h>
#include <LibJS/SourceRange.h>
@ -23,7 +24,6 @@
O(BitwiseNot) \
O(BitwiseOr) \
O(BitwiseXor) \
O(BlockDeclarationInstantiation) \
O(Call) \
O(CallBuiltin) \
O(CallConstruct) \
@ -37,6 +37,8 @@
O(CopyObjectExcludingProperties) \
O(CreateArguments) \
O(CreateLexicalEnvironment) \
O(CreateImmutableBinding) \
O(CreateMutableBinding) \
O(CreatePrivateEnvironment) \
O(CreateRestParams) \
O(CreateVariable) \
@ -122,12 +124,37 @@
O(PrepareYield) \
O(PostfixDecrement) \
O(PostfixIncrement) \
O(PutById) \
O(PutByNumericId) \
O(PutByIdWithThis) \
O(PutNormalById) \
O(PutOwnById) \
O(PutGetterById) \
O(PutSetterById) \
O(PutPrototypeById) \
O(PutNormalByNumericId) \
O(PutOwnByNumericId) \
O(PutGetterByNumericId) \
O(PutSetterByNumericId) \
O(PutPrototypeByNumericId) \
O(PutNormalByIdWithThis) \
O(PutOwnByIdWithThis) \
O(PutGetterByIdWithThis) \
O(PutSetterByIdWithThis) \
O(PutPrototypeByIdWithThis) \
O(PutNormalByNumericIdWithThis) \
O(PutOwnByNumericIdWithThis) \
O(PutGetterByNumericIdWithThis) \
O(PutSetterByNumericIdWithThis) \
O(PutPrototypeByNumericIdWithThis) \
O(PutBySpread) \
O(PutByValue) \
O(PutByValueWithThis) \
O(PutNormalByValue) \
O(PutOwnByValue) \
O(PutGetterByValue) \
O(PutSetterByValue) \
O(PutPrototypeByValue) \
O(PutNormalByValueWithThis) \
O(PutOwnByValueWithThis) \
O(PutGetterByValueWithThis) \
O(PutSetterByValueWithThis) \
O(PutPrototypeByValueWithThis) \
O(PutPrivateById) \
O(ResolveSuperBase) \
O(ResolveThisBinding) \
@ -161,7 +188,7 @@ public:
constexpr static bool IsTerminator = false;
static constexpr bool IsVariableLength = false;
enum class Type {
enum class Type : u8 {
#define __BYTECODE_OP(op) \
op,
ENUMERATE_BYTECODE_OPS(__BYTECODE_OP)
@ -175,6 +202,9 @@ public:
void visit_operands(Function<void(Operand&)> visitor);
static void destroy(Instruction&);
Strict strict() const { return m_strict; }
void set_strict(Strict strict) { m_strict = strict; }
protected:
explicit Instruction(Type type)
: m_type(type)
@ -186,6 +216,7 @@ protected:
private:
Type m_type {};
Strict m_strict {};
};
class InstructionStreamIterator {

File diff suppressed because it is too large Load Diff

View File

@ -25,36 +25,32 @@ public:
explicit Interpreter(VM&);
~Interpreter();
[[nodiscard]] Realm& realm() { return *m_realm; }
[[nodiscard]] Object& global_object() { return *m_global_object; }
[[nodiscard]] DeclarativeEnvironment& global_declarative_environment() { return *m_global_declarative_environment; }
[[nodiscard]] Realm& realm() { return *m_running_execution_context->realm; }
[[nodiscard]] Object& global_object() { return *m_running_execution_context->global_object; }
[[nodiscard]] DeclarativeEnvironment& global_declarative_environment() { return *m_running_execution_context->global_declarative_environment; }
VM& vm() { return m_vm; }
VM const& vm() const { return m_vm; }
ThrowCompletionOr<Value> run(Script&, GC::Ptr<Environment> lexical_environment_override = nullptr);
ThrowCompletionOr<Value> run(SourceTextModule&);
ThrowCompletionOr<Value> run(Bytecode::Executable& executable, Optional<size_t> entry_point = {}, Value initial_accumulator_value = js_special_empty_value())
{
auto result_and_return_register = run_executable(executable, entry_point, initial_accumulator_value);
return move(result_and_return_register.value);
}
ThrowCompletionOr<Value> run_executable(ExecutionContext&, Executable&, Optional<size_t> entry_point);
struct ResultAndReturnRegister {
ThrowCompletionOr<Value> value;
Value return_register_value;
};
ResultAndReturnRegister run_executable(Bytecode::Executable&, Optional<size_t> entry_point, Value initial_accumulator_value = js_special_empty_value());
ThrowCompletionOr<Value> run_executable(ExecutionContext& context, Executable& executable, Optional<size_t> entry_point, Value initial_accumulator_value)
{
context.registers_and_constants_and_locals_and_arguments_span()[0] = initial_accumulator_value;
return run_executable(context, executable, entry_point);
}
ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); }
ALWAYS_INLINE Value& saved_return_value() { return reg(Register::saved_return_value()); }
Value& reg(Register const& r)
{
return m_registers_and_constants_and_locals_arguments.data()[r.index()];
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[r.index()];
}
Value reg(Register const& r) const
{
return m_registers_and_constants_and_locals_arguments.data()[r.index()];
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[r.index()];
}
[[nodiscard]] Value get(Operand) const;
@ -63,6 +59,8 @@ public:
Value do_yield(Value value, Optional<Label> continuation);
void do_return(Value value)
{
if (value.is_special_empty_value())
value = js_undefined();
reg(Register::return_value()) = value;
reg(Register::exception()) = js_special_empty_value();
}
@ -75,8 +73,8 @@ public:
void enter_object_environment(Object&);
Executable& current_executable() { return *m_current_executable; }
Executable const& current_executable() const { return *m_current_executable; }
Executable& current_executable() { return *m_running_execution_context->executable; }
Executable const& current_executable() const { return *m_running_execution_context->executable; }
ExecutionContext& running_execution_context() { return *m_running_execution_context; }
@ -95,17 +93,10 @@ private:
ExitFromExecutable,
ContinueInThisExecutable,
};
[[nodiscard]] HandleExceptionResponse handle_exception(size_t& program_counter, Value exception);
[[nodiscard]] HandleExceptionResponse handle_exception(u32& program_counter, Value exception);
VM& m_vm;
Optional<size_t> m_scheduled_jump;
GC::Ptr<Executable> m_current_executable { nullptr };
GC::Ptr<Realm> m_realm { nullptr };
GC::Ptr<Object> m_global_object { nullptr };
GC::Ptr<DeclarativeEnvironment> m_global_declarative_environment { nullptr };
Span<Value> m_registers_and_constants_and_locals_arguments;
ExecutionContext* m_running_execution_context { nullptr };
ReadonlySpan<Utf16FlyString> m_identifier_table;
};
JS_API extern bool g_dump_bytecode;

Some files were not shown because too many files have changed in this diff Show More