# Technical Documentation: LadybirdBrowser/ladybird

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/LadybirdBrowser/ladybird)

### Overview (README.md)

# Ladybird

[Ladybird](https://ladybird.org) is a truly independent web browser, using a novel engine based on web standards.

> [!IMPORTANT]
> Ladybird is in a pre-alpha state, and only suitable for use by developers
>

## Features

We aim to build a complete, usable browser for the modern web.

Ladybird uses a multi-process architecture with a main UI process, several WebContent renderer processes,
an ImageDecoder process, and a RequestServer process.

Image decoding and network connections are done out of process to be more robust against malicious content.
Each tab has its own renderer process, which is sandboxed from the rest of the system.

At the moment, many core library support components are inherited from SerenityOS:

- LibWeb: Web rendering engine
- LibJS: JavaScript engine
- LibWasm: WebAssembly implementation
- LibCrypto/LibTLS: Cryptography primitives and Transport Layer Security
- LibHTTP: HTTP/1.1 client
- LibGfx: 2D Graphics Library, Image Decoding and Rendering
- LibUnicode: Unicode and locale support
- LibMedia: Audio and video playback
- LibCore: Event loop, OS abstraction layer
- LibIPC: Inter-process communication

## How do I build and run this?

See [build instructions](Documentation/BuildInstructionsLadybird.md) for information on how to build Ladybird.

Ladybird runs on Linux, macOS, Windows (with WSL2), and many other \*Nixes.

## How do I read the documentation?

Code-related documentation can be found in the [documentation](Documentation/) folder.

## Get in touch and participate!

Join [our Discord server](https://discord.gg/nvfjVJ4Svh) to participate in issue and development discussions.

Please read [Getting involved with Ladybird](Documentation/GettingStartedContributing.md) if you're new to Ladybird and want to help.

Before opening an issue, please see the [issue policy](CONTRIBUTING.md#issue-policy) and the [detailed issue-reporting guidelines](ISSUES.md).

The project participation guidelines can be found in [`CONTRIBUTING.md`](CONTRIBUTING.md).

## License

Ladybird is licensed under a 2-clause BSD license.

---

### Documentation README (`Documentation/README.md`)

# Ladybird Documentation

Ladybird development moves quickly, so some of these might be out of date. Please let us know if something here is wrong.
If you have any questions that are not answered here or in the [FAQ](FAQ.md), you are welcome to ask on [Discord](../README.md#get-in-touch-and-participate).

## Building and Running
* [Build Instructions](BuildInstructionsLadybird.md)
* [Advanced Build Instructions](AdvancedBuildInstructions.md)
* [Troubleshooting](Troubleshooting.md)
* [Testing](Testing.md)
* [Profiling the Build](BuildProfilingInstructions.md)

## Configuring Editors
* [Android Studio](EditorConfiguration/AndroidStudioConfiguration.md)
* [CLion](EditorConfiguration/CLionConfiguration.md)
* [Emacs](EditorConfiguration/EmacsConfiguration.md)
* [Helix](EditorConfiguration/HelixConfiguration.md)
* [NVim](EditorConfiguration/NvimConfiguration.md)
* [Qt Creator](EditorConfiguration/QtCreatorConfiguration.md)
* [Vim](EditorConfiguration/VimConfiguration.md)
* [VS Code](EditorConfiguration/VSCodeConfiguration.md)

## Development
* [Project Participation](../CONTRIBUTING.md)
* [Code Policy](CodePolicy.md)
* [Coding Style](CodingStyle.md)
* [Common Patterns](Patterns.md)
* [Guidelines for Text in UI](HumanInterfaceGuidelines/Text.md)
* [EventLoop](EventLoop.md)
* [Smart Pointers](SmartPointers.md)
* [String Formatting](StringFormatting.md)

## Browser/LibWeb
* [General Architecture](ProcessArchitecture.md)
* [Omnibox Design](Omnibox.md)
* [LibWeb: From Loading to Painting](LibWebFromLoadingToPainting.md)
* [LibWeb: Browsing Contexts and Navigables](BrowsingContextsAndNavigables.md)
* [How to Add a CSS Property](CSSProperties.md)
* [How to Add an IDL File](AddNewIDLFile.md)
* [LibWeb Code Style & Patterns](LibWebPatterns.md)
* [Investigating Websites with WebDriver](WebDriver.md)
* [CSS Generated Files](CSSGeneratedFiles.md)
* [Media Pipeline Design](MediaPipelineDesign.md)

---

### Documentation AddNewIDLFile (`Documentation/AddNewIDLFile.md`)

# Adding a new IDL file

Ladybird's build system does a lot of work of turning the IDL from a Web spec into code, but there are a few things you'll need to do yourself.

For the sake of example, let's say you're wanting to add the `HTMLDetailsElement`.

1. Create `LibWeb/HTML/HTMLDetailsElement.idl` with the contents of the IDL section of the spec. In this case, that would be:
```webidl
[Exposed=Window]
interface HTMLDetailsElement : HTMLElement {
    [HTMLConstructor] constructor();

    [CEReactions] attribute boolean open;
};
```

2. Add the IDL file to [`LibWeb/idl_files.cmake`](../Libraries/LibWeb/idl_files.cmake).

3. Forward declare the generated class in [`LibWeb/Forward.h`](../Libraries/LibWeb/Forward.h):
    - `HTMLDetailsElement` in its namespace.

---

### Documentation AdvancedBuildInstructions (`Documentation/AdvancedBuildInstructions.md`)

# Advanced Build Instructions

This file covers a few advanced scenarios that go beyond what the basic build guide provides.

## Ninja build targets

The `Meta/ladybird.py` script provides an abstraction over the build targets which are made available by CMake. The
following build targets cannot be accessed through the script and have to be used directly by changing the current
directory to `Build/release` and then running `ninja <target>`:

- `ninja check-style`: Runs the same linters the CI does to verify project style on changed files
- `ninja lint-shell-scripts`: Checks style of shell scripts in the source tree with shellcheck
- `ninja all_generated`: Builds all generated code. Useful for running analysis tools that can use compile_commands.json without a full system build

When testing is enabled, the default build also compiles the main Rust
workspace's test binaries and Flap's test targets on supported platforms.
It does not run them. Use `ninja rust-tests` to compile just these Rust tests,
or `ctest -R '^(RustWorkspace|Flap)$' --output-on-failure` to run the suites.

## CMake build options

There are some optional features that can be enabled during compilation that are intended to help with specific types of development work or introduce experimental features. Currently, the following build options are available:
- `ENABLE_ADDRESS_SANITIZER`: builds in runtime checks for memory corruption bugs (like buffer overflows and memory leaks) in Lagom test cases.
- `ENABLE_MEMORY_SANITIZER`: enables runtime checks for uninitialized memory accesses in Lagom test cases.
- `ENABLE_UNDEFINED_SANITIZER`: builds in runtime checks for [undefined behavior](https://en.wikipedia.org/wiki/Undefined_behavior) (like null pointer dereferences and signed integer overflows) in Lagom and Ladybird.
- `UNDEFINED_BEHAVIOR_IS_FATAL`: makes all undefined behavior sanitizer errors non-recoverable. This option reduces the performance overhead of `ENABLE_UNDEFINED_SANITIZER`.
- `ENABLE_COMPILER_EXPLORER_BUILD`: Skip building non-library entities in Lagom (this only applies to Lagom).
- `ENABLE_FUZZERS`: builds [fuzzers](../Meta/Fuzzers/README.md) for various parts of the system.
- `ENABLE_FUZZERS_LIBFUZZER`: builds Clang libFuzzer-based [fuzzers](../Meta/Fuzzers/README.md) for various parts of the system.
- `ENABLE_FUZZERS_OSSFUZZ`: builds OSS-Fuzz compatible [fuzzers](../Meta/Fuzzers/README.md) for various parts of the system.
- `ENABLE_ALL_THE_DEBUG_MACROS`: used for checking whether debug code compiles on CI. This should not be set normally, as it clutters the console output and makes the system run very slowly. Instead, enable only the needed debug macros, as described below.
- `ENABLE_COMPILETIME_FORMAT_CHECK`: checks for the validity of `std::format`-style format string during compilation. Enabled by default.
- `ENABLE_JS_JIT`: builds the optimizing JIT for JavaScript. Enabled by default on x86-64 and AArch64 Linux and macOS, the platforms it generates code for. Without it, LibJS runs JavaScript in its interpreter alone, and the interpreter is built without the feedback collection and tier-up checks that only the JIT needs. See [the configuration of the JIT](../Libraries/LibJS/JIT/ARCHITECTURE.md#13-configuration).
- `INCLUDE_WASM_SPEC_TESTS`: downloads and includes the WebAssembly spec testsuite tests. In order to use this option, you will need to install `prettier` and `wasm-tools`.
- `INCLUDE_FLAC_SPEC_TESTS`: downloads and includes the xiph.org FLAC test suite.
- `LADYBIRD_CACHE_DIR`: sets the location of a shared cache of downloaded files. Should not need to be set manually unless managing a distribution package.
- `ENABLE_NETWORK_DOWNLOADS`: allows downloading files from the internet during the build. Default on, turning off enables offline builds. For offline builds, the structure of the LADYBIRD_CACHE_DIR must be set up the way that the build expects.
- `ENABLE_CLANG_PLUGINS`: enables Clang plugins which analyze the code for programming mistakes. See [Clang Plugins](#clang-plugins) below.

Many parts of the codebase have debug functionality, mostly consisting of additional messages printed to the debug console. This is done via the `<component_name>_DEBUG` macros, which can be enabled individually at build time. They are listed in [this file](../Meta/CMake/all_the_debug_macros.cmake).

To toggle or change a build option, see the [CMake Cache Manipulation](#cmake-cache-manipulation) section below.

## CMake Cache Manipulation

CMake caches variables and options in the binary directory. This allows a developer to tailor variables that are `set()` within the persistent configuration cache.

There are three main ways to manipulate the cache:
- `cmake path/to/binary/dir -DVAR_NAME=Value`
- `ccmake` (TUI interface)
- `cmake-gui`

Options can be set via the initial `cmake` invocation that creates the binary directory to set the initial cache for the binary directory.
Once the binary directory exists, any of the three options above can be used to change the value of cache variables.

For example, boolean options such as `ENABLE_<setting>` or `<component_name>_DEBUG` can be enabled with the value `ON` and disabled with `OFF`:

```console
# Reconfigure an existing binary directory with process debug enabled
$ cmake -B Build/ladybird -DPROCESS_DEBUG=ON
```

For more information on how the CMake cache works, see the CMake guide for [Running CMake](https://cmake.org/runningcmake/). Additional context is available in the CMake documentation for
[variables](https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#variables) and [set()](https://cmake.org/cmake/help/latest/command/set.html#set-cache-entry).

## Tests

For information on running host and target tests, see [Testing](Testing.md). The documentation there also contains useful information for debugging CI test failures.

## Clang-format updates

Some OS distributions don't ship bleeding-edge clang-format binaries. Below are 2 options to acquire an updated clang-format tool, in order of preference:

1) If you have a Debian-based (apt-based) distribution, use the [LLVM apt repositories](https://apt.llvm.org) to install the latest release of clang-format.
2) Compile LLVM from source as described in the LLVM documentation [here](https://llvm.org/docs/GettingStarted.html#compiling-the-llvm-suite-source-code).

## Clangd Configuration

The repository has a `.clangd` configuration file in the root directory. One of the configuration stanzas in that file
specifies the location for a compilation database. Depending on your build configuration (e.g. Release, Debug, Sanitizer),
the path to the compilation database in that file may not be correct. You may edit the `.clangd` file to point at your
build directory.

## Clang Plugins

Clang plugins are used to validate the code at compile time. Currently, they are used to detect JavaScript-related
garbage collection faux pas, such as neglecting to visit a garbage-collected type.

In order to enable Clang plugins, you will need Clang's development headers installed. For example, on Ubuntu this is
the `libclang-dev` package.

When Clang plugins are enabled, it is recommended to have the following environment variable set for ccache:

```bash
export CCACHE_COMPILERCHECK="%compiler% -v"
```

By default, ccache will include the plugins themselves in file hashes. So if a plugin changes, the hash of every file
will change, and you will be stuck with an uncached build. This setting will prevent ccache from using plugins in the
file hashes.

## Building the Flatpak

Ladybird has an in-tree Flatpak manifest that can be used to build a Flatpak package for Ladybird. The recommended way
to build the Flatpak is to use the `flatpak-builder` tool, which is available in most distributions. See the
[Flatpak setup documentation](https://flatpak.org/setup/) on how to configure your environment for user Flatpak
builds, and to configure the Flathub repo. The Ladybird Flatpak manifest at
`Meta/CMake/flatpak/org.ladybird.Ladybird.json` can be built with the following command:

```bash
flatpak-builder --user --force-clean --install-deps-from=flathub \
  --ccache --repo=Build/repo --install Build/flatpak \
  Meta/CMake/flatpak/org.ladybird.Ladybird.json
```

This command will build the Flatpak bundle and install it into the local Flatpak repository at `Build/repo`. Expect this
to take a long time, as it will download and build all the dependencies of Ladybird as well. `flatpak-builder` will drop
caches and build files in `.flatpak-builder`, as well as in the two subfolders `Build/repo` and `Build/flatpak`.

The Flatpak can be run with the following command:

```bash
flatpak run --user org.ladybird.Ladybird
```

In order to debug the Flatpak, you can pass the `--devel` and `--command=sh` flags to `flatpak run`. This will drop you
into a shell in the Flatpak sandbox:

```bash
flatpak run --user --command=sh --devel org.ladybird.Ladybird
```

## Debugging without any optimizations

It’s possible that when trying to inspect certain frame variables in your debugger, you’ll get an error similar to the following:

> error: Couldn't look up symbols: __ZN2AK6Detail10StringBaseD2Ev
> Hint: The expression tried to call a function that is not present in the target, perhaps because it was optimized out by the compiler.

If you do run into such an error, the rest of this section explains how to deal with it.

> [!WARNING]
> You probably only want to make the build-file change described below while you’re in the middle of examining the state of a particular build in your debugger — and then you’ll want to revert it after you’re done debugging. You otherwise probably don’t want to have the build-file change in place while you’re running WPT tests or in-tree tests and checking the results.

1. At your command-line prompt in your shell environment, copy and paste the following:

      ```diff
      $ patch -p1 <<EOF
      diff --git a/Meta/CMake/compile_options.cmake b/Meta/CMake/compile_options.cmake
      index 0bb2be833b6..modified 100644
      --- a/Meta/CMake/compile_options.cmake
      +++ b/Meta/CMake/compile_options.cmake
      @@ -235,7 +235,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
           if (NOT MSVC)
               add_cxx_compile_options(-ggdb3)
      -        add_cxx_compile_options(-Og)
      +        add_cxx_compile_options(-O0)
           endif()
       elseif (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
           if (NOT MSVC)
      EOF
      ```
   …or else copy and paste that patch, and apply it in whatever way you normally use for applying patches.

   That will patch the build config in such a way as to disable all compiler optimizations and make all debug symbols always available.

2. At your command-line prompt in your shell environment, run the following command:

      ```
      git update-index --skip-worktree Meta/CMake/compile_options.cmake
      ```

   That will cause git to ignore the change you made to that build file. Otherwise, if you don’t run that command, git will consider that build file to have been modified, and you might then end up inadvertently committing the changes to that build file as part of some actual code change you’re making to the sources that you’re in the process of debugging.

3. Run a build again with the `Debug` preset, and then go back into your debugger. You’ll now be able to inspect any frame variable that you weren’t able to previously.

After you’ve finished debugging your code changes with that build, you can revert the above changes by doing this:

1. At your command-line prompt in your shell environment, run the following:

      ```
      git update-index --no-skip-worktree Meta/CMake/compile_options.cmake \
          && git checkout Meta/CMake/compile_options.cmake
      ```

That will restore your git environment to the state it was in before you patched the build file.

## Debugging with Vulkan Validation Layers

To turn on Vulkan validation layers for the release build, for example, use:

```
cmake -B Build/release -DVULKAN_VALIDATION_LAYERS_DEBUG=ON
```

and then build normally.

When running Ladybird the message `Vulkan validation layers: active` will be visible during the creation of the VulkanContext if the Vulkan validation layers are properly setup.

If instead `Vulkan validation layers: not available` appears then the system is most likely missing the validation layers. On Ubuntu adding the package `vulkan-validationlayers` will add them in, other distros may also have a package but the name could be different (for example, on Arch, Fedora and NixOS the name is `vulkan-validation-layers`.

Another way to get the validation layers is to use the Vulkan SDK, for more info see:
https://github.com/KhronosGroup/Vulkan-ValidationLayers

If absolutely no output related to the Vulkan validation layers is received, then the initial `cmake` command in this section was not performed or the VulkanContext was not created successfully or was not created at all.

---

### Documentation BrowsingContextsAndNavigables (`Documentation/BrowsingContextsAndNavigables.md`)

# LibWeb: Browsing Contexts and Navigables

**NOTE: This document is a work in progress!**

## Introduction: How does code execute, really?

Before we can dive into how LibWeb and Ladybird implement the HTML web page navigation operations,
we need to dive into some fundamental specification concepts. Starting with, how does code actually
execute in a  (possibly virtual) machine? Next we'll look at what that means for the ECMAScript
Specification (JavaScript), and finally how the ECMAScript code execution model ties into the
HTML specification to model how to display web content into a browser tab. 

### Native Code Execution: A Primer

When modeling the execution of a native program written in a popular systems language like
C, C++, or Rust, most systems programmers should be familiar with the concepts of *threads*
and *processes*. In a "hosted" environment, the execution of one's userspace program generally
starts with an underlying operating system creating a process for the application to run in.
This process will contain a memory space for program data and code to live in, and an initial,
or main thread to start execution on. In order for the operating system to change which
thread is executing on a particular CPU core, it needs to save and restore the *Execution Context*
for that thread. The Execution Context for a native thread generally consists of a set of
CPU registers, any floating point state, a program counter that tracks which instruction should
be loaded next, and a stack pointer that points to the local data the thread was using to track
its function call stack and local variables. The programmer can also request additional threads
through a system call, providing a set of thread attributes and a function to call as the entry
point.

For traditional compiled programs, the concept of accessing variables and functions is split into
two phases. At compile time, local variables and arguments are folded into stack slots and
allocated into registers. Exported variables and functions are written into the executable object
file (ELF, Mach-O, PE, etc.) and are visible to external tools as symbols, as referenced by a
symbol table contained within the object file format. Normally local variable and argument
names and locations are lost in the compile+link steps, but the compiler can be configured to
emit extra debug information to allow debuggers to access and modify them at runtime. In order
to support something like the dynamic imports of interpreted languages, the programmer has to
call a platform-specific function to load the new module (e.g. ``dlopen`` or ``LoadLibrary``).
But after the module is opened, in order to actually refer to any exported symbols from that module the
programmer has to retrieve the address of each symbol through another platform specific function
(e.g. ``dlsym`` or ``GetProcAddress``), once per symbol.

### ECMAScript Execution Model: Realms and Agents

The ECMAScript specification has analogs for almost all of these concepts in the section on
[Executable Code and Execution Contexts](https://tc39.es/ecma262/#sec-executable-code-and-execution-contexts).

Working in the other direction from the native code explanation, ECMAScript describes the accessibility
and scopes of functions, variables, and arguments in terms of [*Environment Records*](https://tc39.es/ecma262/#sec-environment-records).
Note that these Environment Records are not actually visible to executing code, and are simply a mechanism
used by the specification authors to model the language.  Every function and module has a type
of Environment Record that contains the variables, functions, catch clause bindings, and other
language constructs that affect which names are visible at any location in the code. These Environment Records
are nested, in a tree-like structure that somewhat matches the Abstract Syntax Tree (AST).

The root of the tree of Environment Records is the Global Environment Record, which corresponds to the
Global Object and its properties. In JavaScript, there is always a ``this`` value representing the current
object context. At global scope, the Global Object normally takes that responsibility. In a REPL, that might
be some REPL specific global object that has global functions to call for doing things like loading
from the filesystem, or even be as complex as Node or Bun. In a Browser context, the Global object is
normally the Window, unless there's a Worker involved. For historical reasons the global ``this`` binding for
Window contexts is actually a WindowProxy that wraps the Window. This concept is quite different from a native
executable, where there's no actual object representing the global scope, simply symbols that the
linker and loader make available to each module.

While the Global Object and its Global Environment represent the root of the tree of identifiers visible
to the executing JavaScript code, the Global Object isn't sufficient to model all the state around
a conceptual thread of execution in ECMAScript. This is where the two concepts of [*Realms*](https://tc39.es/ecma262/#sec-code-realms)
and [*Execution Contexts*](https://tc39.es/ecma262/#sec-execution-contexts) come into play.
A [*Realm Record*](https://tc39.es/ecma262/#realm-record) is a container that holds a global object,
its associated Global Environment, a set of intrinsic objects, and any *host* (also called an *embedder*
in some specification documents) defined extra state that needs to be associated with the realm.
In LibWeb, the Host Defined slot holds an object that has the HTML Environment Settings Object for each realm,
as well as all the prototypes, constructors, and namespaces that need to be exposed on the Global Object
for Web APIs. On top of the Realm abstraction, ECMAScript uses the Execution Context to model the state
of execution of one particular script or module. Each Execution Context belongs to an [*execution context stack*](https://tc39.es/ecma262/#execution-context-stack)
with the topmost context named the [*running execution context*](https://tc39.es/ecma262/#running-execution-context).
An Execution Context has information about the current function, the script or module that the current code block belongs to,
additional Environment Records required to access names in the current scope, any running generator state,
and most importantly to the thread analogy, the state needed to suspend and resume execution of that script.
As with Environment Records, Realms and Execution contexts are not directly accessible to running JavaScript code.

The final missing piece for the JavaScript execution model is how these stacks of Execution Contexts
are actually scheduled to run by the ECMAScript implementation. In the most common case, this means directly
mapping the ECMAScript model to the earlier native concepts of threads and processes in a way that
allows for flexibility in the implementation strategies. The last thing that the specification authors want
to do is constrain implementations so much that innovation and experimentation becomes impossible.
The method for this mapping is the two related specification mechanisms [*Agents*](https://tc39.es/ecma262/#sec-agents)
and [*Agent Clusters*](https://tc39.es/ecma262/#sec-agent-clusters). The Execution Context stack mentioned
above actually belongs to an Agent, which holds said stack, a set of metadata about the memory model,
and a shared reference to an [*executing thread*](https://tc39.es/ecma262/#executing-thread).
According to ECMAScript, there should always be at least one Execution Context on the stack, to allow concepts
such as the running execution context to always refer to the topmost Execution Context of the [*surrounding agent*](https://tc39.es/ecma262/#surrounding-agent).
However, the HTML specification opts to remove the default execution context from the execution context stack
at creation, and instead manually pushes and pops execution contexts for script, module, and callback execution.
The relationship between Realms and Agents is not 1-1, but N-1. In the ECMAScript specification, this manifests
as a part of the [*Shadow Realm proposal*](https://tc39.es/proposal-shadowrealm/), while the Web platform
requires multiple Realms per Agent to specify the historical behavior of ``<iframe>`` and related elements.

An Agent holds a stack of Execution Contexts, with the topmost entry being the running execution context.
Each Execution Context holds a Realm and a specific script's context, including the current function and
any state required to pause and resume the execution for that context. The Realm holds the Global
Object for the Execution Context, and any ECMAScript or host-specific intrinsics required to create the
desired environment for code to run in. More loosely, an Agent is a specification artifact that somewhat
maps the execution of a JavaScript script or module to a native thread of execution. But the specification
does so in a way that allows a host/embedder to choose to switch out which Agent is currently executing
its running execution context on that native thread, and which Realm within that Agent owns the running execution
context.

SharedArrayBuffers and Atomics add a special kind of wrinkle to the ECMAScript specification. Defining
how that work required the formalization of a memory model, similar to what C++11 and C11 and Java 5 had
to do before them. The Agent Cluster is the formalism that ties the memory model back to the execution
model. As described in the specification, an Agent Cluster is a set of Agents that can communicate
via shared memory. The exact mechanism is unspecified, but the hard rule is that all Agents within
a particular Agent Cluster must observe the same order of reads and writes to SharedArrayBuffers and
as a result of ECMAScript Atomic objects.

The net result of all this memory model and atomic specification language is that loosely, an Agent models
a "candidate execution" of some code module that can execute on a thread, and any suspended execution
contexts from things like generators or async that are part of that module and its dynamic imports.
An Agent Cluster models the interaction of agents that share the ability to communicate via shared memory.
The simplest reading of this is that the specification authors had in mind the type of memory sharing
that threads within the same process have in native code execution. So an Agent Cluster loosely models
a collection of Agents (read: threads) that execute independently of each other within the same implementation
defined manner for sharing memory between different threads (read: process).

### HTML Execution Model: Global Scopes

The Document Object Model (DOM) specifications are written in such a way that implementers can
create language bindings for any language to access the page. However, experience has shown that the
most popular way to script web content in modern web browsers is through JavaScript bindings. As such,
the HTML specification is specifically tailored to meet the constraints of JavaScript execution in its
scripting APIs and related concepts. Great care is taken to ensure that JavaScript written by different
authors cannot interfere with each other, and that arbitrary scripts cannot exfiltrate information about
the page content to third-party destinations.

The HTML specification therefore has a section on [Agents and Agent Clusters](https://html.spec.whatwg.org/multipage/webappapis.html#agents-and-agent-clusters)
at the start of the section on how scripting behaves on the Web platform.

TODO: Finish this section

## HTML Navigation: Juggling Origins



### Global Scopes, Browsing Contexts, Browsing Context Groups, Navigables, and Traversable Navigables

TODO:

- Agents defined by the HTML Spec
- Global Objects (Global Scopes) defined by the HTML Spec
- Agents and Browsing Context Groups
- Navigables and their relationship to Browsing Contexts
- Walk through construction of a browser tab, its traversable navigable, and its navigation both same and
  cross-origin
- Walk through construction of a browser tab with a nested browsing context and what happens when the
  nested context within its navigable container navigates on its own

---

### Documentation BuildInstructionsLadybird (`Documentation/BuildInstructionsLadybird.md`)

# Ladybird browser build instructions

## Build Prerequisites

Qt6.10+ development packages, nasm, additional build tools, and a C++23 capable compiler are required.

> [!NOTE]
> Some distributions still package a Qt6 older than 6.10; for example, Debian 13 (trixie) ships Qt 6.8 — so configuring against it fails. If your Qt6 is older than 6.10, install a newer one from a newer distribution release, or directly from the [Qt online installer](https://www.qt.io/download-open-source), and then point the build to it using `CMAKE_PREFIX_PATH`.

A Rust toolchain is also required. You can install it via [rustup](https://rustup.rs/).

We currently use gcc-14 and clang-21 in our CI pipeline. If these versions are not available on your system, see
[`find_compiler.py`](../Meta/Utils/find_compiler.py) for the minimum compatible version.

CMake 3.30 or newer must be available in $PATH.

---

### Debian/Ubuntu:


```bash
sudo apt install autoconf autoconf-archive automake build-essential ccache cmake curl fonts-liberation2 git glslang-tools libdrm-dev libgl1-mesa-dev libncurses-dev libpulse-dev libtool libxcb-icccm4-dev nasm ninja-build pkg-config python3-venv qt6-base-private-dev qt6-positioning-dev qt6-tools-dev-tools qt6-wayland tar unzip zip
```

#### CMake 3.30 or newer:

- Recommendation: Install `CMake 3.30` or newer from [Kitware's apt repository](https://apt.kitware.com/):

> [!NOTE]
> Kitware’s apt repository is Ubuntu-only.

```bash
# Add Kitware GPG signing key
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | sudo tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null

# Optional: Verify the GPG key manually

# Use the key to authorize an entry for apt.kitware.com in apt sources list
echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/kitware.list

# Update apt package list and install cmake
sudo apt update -y && sudo apt install cmake -y
```

#### C++23-capable compiler:

- Recommendation: Install clang from [LLVM's apt repository](https://apt.llvm.org/):

```bash
# Add LLVM GPG signing key
sudo wget -O /usr/share/keyrings/llvm-snapshot.gpg.key https://apt.llvm.org/llvm-snapshot.gpg.key

# Optional: Verify the GPG key manually

# Use the key to authorize an entry for apt.llvm.org in apt sources list
echo "deb [signed-by=/usr/share/keyrings/llvm-snapshot.gpg.key] https://apt.llvm.org/$(lsb_release -sc)/ llvm-toolchain-$(lsb_release -sc)-21 main" | sudo tee -a /etc/apt/sources.list.d/llvm.list

# Update apt package list and install clang and associated packages
sudo apt update -y && sudo apt install clang-21 clangd-21 clang-tools-21 clang-format-21 clang-tidy-21 lld-21 -y
```

- Alternative: Install gcc from [Ubuntu Toolchain PPA](https://launchpad.net/~ubuntu-toolchain-r/+archive/ubuntu/test):

```bash
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt update && sudo apt install g++-14 libstdc++-14-dev
```

### Arch Linux/Manjaro:

```
sudo pacman -S --needed autoconf-archive base-devel ccache cmake curl git less libgl libpulse nasm ninja python qt6-base qt6-positioning qt6-tools ttf-liberation tar unzip xcb-util-wm zip
```

### Fedora or derivatives:

```
sudo dnf install autoconf-archive automake ccache cmake curl git glslang libdrm-devel liberation-sans-fonts libglvnd-devel libtool nasm ncurses-devel ninja-build patchelf perl-FindBin perl-IPC-Cmd perl-lib perl-Time-Piece pulseaudio-libs-devel qt6-qtbase-private-devel qt6-qtpositioning-devel qt6-qttools-devel qt6-qtwayland-devel tar unzip xcb-util-wm-devel zip zlib-ng-compat-static
```

### openSUSE:

```
sudo zypper install autoconf-archive automake ccache cmake curl gcc14 gcc14-c++ git liberation-fonts libglvnd-devel libpulse-devel libtool nasm ncurses-devel ninja qt6-base-private-devel qt6-positioning-devel qt6-tools-devel qt6-wayland-devel tar unzip xcb-util-wm-devel 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.

For Leap 15.6, run the following command to add this repo (you will need to adjust the URL for other openSUSE versions):

```
sudo zypper addrepo https://download.opensuse.org/repositories/devel:tools:building/15.6/devel:tools:building.repo
sudo zypper refresh
```

Below is an example printout that might be received depending which versions of autoconf are available for installation:

```
> sudo zypper install autoconf
Loading repository data...
Reading installed packages...
'autoconf' is already installed.
There is an update candidate for 'autoconf' from vendor 'obs://build.opensuse.org/devel:tools', while the current vendor is 'SUSE LLC <https://www.suse.com/>'. Use 'zypper install autoconf-2.72-80.d_t_b.1.noarch' to install this candidate.
Resolving package dependencies...
Nothing to do.
> sudo zypper install autoconf-2.72-80.d_t_b.1.noarch
```

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.

A virtual environment can be created in your home directory and once the `source` command is issued `python3 --version` will show that the current version is python 3.12 within the virtual environment shell session.
```
python3.12 -m venv ~/python312_venv
source ~/python312_venv/bin/activate
python3 --version
```

This virtual environment can be created once and reused in future shell sessions by sourcing the generated file `~/python312_venv/bin/activate` again.

### Void Linux:

```
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 libtool zip unzip linux-headers make pkg-config autoconf automake autoconf-archive nasm ncurses-devel MesaLib-devel ninja pulseaudio-devel qt6-base-private-devel qt6-position-devel qt6-tools-devel qt6-wayland-devel xcb-util-wm-devel
```

### NixOS or with Nix:

A Nix development shell is maintained [here](https://github.com/nix-community/nix-environments/tree/master/envs/ladybird),
in the [nix-environments](https://github.com/nix-community/nix-environments/) repository. If you encounter any problems
building with Nix, please create an issue there.

### macOS:

Xcode 15 or clang from homebrew is required to successfully build ladybird.

```
xcode-select --install
brew install autoconf autoconf-archive automake ccache cmake libtool nasm ninja pkg-config qt
```

If you wish to use clang from homebrew instead:
```
brew install llvm@21
```

> [!NOTE]
> It is recommended to add your terminal application (i.e. Terminal.app or iTerm.app) to the system list of developer tools.
> Doing so will reduce slow startup time of freshly compiled binaries, due to macOS validating the binary on its first run.
> This can be done in the "Developer Tools" section of the "Privacy & Security" system settings.

### Windows:

WSL2 is the supported way to build Ladybird on Windows. A native build is also possible, however it is still experimental,
and there are several issues.

#### WSL2
- Create a WSL2 environment using one of the Linux distros listed above. Ubuntu or Fedora is recommended.

- Install the required packages for the selected Linux distro in the WSL2 environment.

WSL1 is known to have issues. If you run into problems, please use WSL2.

MinGW/MSYS2 are not supported.

##### Clang-CL (experimental)

> [!NOTE]
> There are still windows specific issues and the functionality is limited.

In order to get pkg-config available for the vcpkg install, you can use Chocolatey to install it.
To install Chocolatey, see `https://chocolatey.org/install`.

Then Install pkg-config using chocolatey.
```
choco install pkgconfiglite -y
```
In a VS command prompt build using ladybird.py:
```
py Meta\ladybird.py build
```

### Android:

On a Unix-like platform, install the prerequisites for that platform and then see the [Android Studio guide](EditorConfiguration/AndroidStudioConfiguration.md).
Or, download a version of Gradle >= 8.0.0, and run the ``gradlew`` program in ``UI/Android``

### FreeBSD

```
pkg install autoconf-archive automake autoconf bash cmake ccache curl gmake gn libdrm libtool libxcb libxkbcommon libX11 libXrender libXi nasm ninja patchelf pkgconf pulseaudio python3 qt6-base qt6-positioning tar unzip zip
```
> [!NOTE]
> `zip`, `unzip`, and `tar` are required by the vcpkg bootstrap step. If any of these are missing,
> the build will fail with a Python `CalledProcessError` traceback rather than a clear error message.

## Build steps

### Using ladybird.py

The simplest way to build and run ladybird is via the ladybird.py script:

```bash
# From /path/to/ladybird
./Meta/ladybird.py run
```

On macOS, to build using clang from homebrew:
```bash
CC=$(brew --prefix llvm)/bin/clang CXX=$(brew --prefix llvm)/bin/clang++ ./Meta/ladybird.py run
```

You may also choose to start it in `gdb` using:
```bash
./Meta/ladybird.py gdb ladybird
```

The above commands will build a Release version of Ladybird. To instead build a Debug version, run the
`Meta/ladybird.py` script with the value of the `BUILD_PRESET` environment variable set to `Debug`, like this:

```bash
BUILD_PRESET=Debug ./Meta/ladybird.py run
```

Note that debug symbols are available in both Release and Debug builds.

If you want to run other applications, such as the JS REPL or the WebAssembly REPL, specify an executable with
`./Meta/ladybird.py run <executable_name>`.

To launch an existing binary without configuring, preparing dependencies, or building, use `--no-build`:

```bash
./Meta/ladybird.py run --no-build
./Meta/ladybird.py run --no-build js --evaluate 'console.log(1 + 1)'
./Meta/ladybird.py run --no-build --preset Debug js
```

Put `--no-build` before the executable name; arguments after the name are passed to the executable.
The command uses the selected preset's existing build and retains the usual runtime environment,
including sanitizer options. It does not update the binary or change its build configuration.

### The User Interfaces

Ladybird will be built with one of the following browser frontends, depending on the platform:
* [Android UI](https://developer.android.com/develop/ui) - The native UI on Android.
* [Qt](https://doc.qt.io/qt-6/) - The UI used on all other platforms.

### Build error messages you may encounter

The section lists out some particular error messages you may run into, and explains how to deal with them.

#### Unable to find a build program corresponding to "Ninja"

This error message is a red herring. We use vcpkg to manage our third-party dependencies, and this error is logged when
something went wrong building those dependencies. The output in your terminal will vary depending on what exactly went
wrong, but it should look something like:

```
error: building skia:x64-linux failed with: BUILD_FAILED
Elapsed time to handle skia:x64-linux: 1.6 s

-- Running vcpkg install - failed
CMake Error at Build/vcpkg/scripts/buildsystems/vcpkg.cmake:899 (message):
  vcpkg install failed.  See logs for more information:
  Build/release/vcpkg-manifest-install.log
Call Stack (most recent call first):
  /usr/share/cmake-3.30/Modules/CMakeDetermineSystem.cmake:146 (include)
  CMakeLists.txt:15 (project)

CMake Error: CMake was unable to find a build program corresponding to "Ninja".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
-- Configuring incomplete, errors occurred!  See logs for more information:
  Build/release/vcpkg-manifest-install.log
```

If the error is not immediately clear from the terminal output, be sure to check the specified `vcpkg-manifest-install.log`.
for more information.

### Resource files

Ladybird requires resource files from the ladybird/Base/res directory in order to properly load
icons, fonts, and other theming information. These files are copied into the build directory by
special CMake rules. The expected location of resource files can be tweaked by packagers using
the standard CMAKE_INSTALL_DATADIR variable. CMAKE_INSTALL_DATADIR is expected to be a path relative
to CMAKE_INSTALL_PREFIX. If it is not, things will break.

### Custom CMake build directory

The script Meta/ladybird.py and the Release preset in CMakePresets.json both define a build directory of
`Build/release`. For distribution purposes, or when building multiple configurations, it may be useful to create a custom
CMake build directory.

The install rules in UI/cmake/InstallRules.cmake define which binaries and libraries will be
installed into the configured CMAKE_PREFIX_PATH or path passed to ``cmake --install``.

Note that when using a custom build directory rather than Meta/ladybird.py, the user may need to provide a suitable C++
compiler (see [Build Prerequisites](BuildInstructionsLadybird.md#build-prerequisites)) via the CMAKE_C_COMPILER and
CMAKE_CXX_COMPILER cmake options.

```
cmake --preset Release -B MyBuildDir
# optionally, add -DCMAKE_CXX_COMPILER=<suitable compiler> -DCMAKE_C_COMPILER=<matching c compiler>
cmake --build --preset Release MyBuildDir
ninja -C MyBuildDir run-ladybird
```

### Building with limited system memory

The default build mode will run as many build steps in parallel as possible, which includes link steps;
this may be an issue for users with limited system memory (or users building with fat LTO in general).
If you wish to reduce the number of parallel link jobs, you may use the LAGOM_LINK_POOL_SIZE cmake option
to set a maximum limit for the number of parallel link jobs.

```
cmake --preset Release -B MyBuildDir -DLAGOM_LINK_POOL_SIZE=2
```

### Running manually

The Meta/ladybird.py script will execute the `run-ladybird` and `debug-ladybird` custom targets.
If you don't want to use the ladybird.py script to run the application, you can run the following commands:

To automatically run in gdb:
```
ninja -C Build/release debug-ladybird
```

To run without ninja rule on non-macOS systems:
```
./Build/release/bin/Ladybird
```

To run without ninja rule on macOS:
```
open -W --stdout $(tty) --stderr $(tty) ./Build/release/bin/Ladybird.app

# Or to launch with arguments:
open -W --stdout $(tty) --stderr $(tty) ./Build/release/bin/Ladybird.app --args https://ladybird.dev
```

### Debugging with CLion

Ladybird should be built with debug symbols first. This can be done by adding `-DCMAKE_BUILD_TYPE=Debug` to the cmake command line,
or selecting the Build Type Debug in the CLion CMake profile.

After running Ladybird as suggested above with `./Meta/ladybird.py run ladybird`, you can now in CLion use Run -> Attach to Process to connect. If debugging layout or rendering issues, filter the listing that opens for `WebContent` and attach to that.

Now breakpoints, stepping and variable inspection will work.

### Debugging with Xcode or Instruments on macOS

If all you want to do is use Instruments, then an Xcode project is not required.

Simply run the `ladybird.py` script as normal with a debug-style build. The build automatically signs the app bundle
with the entitlements from `Meta/DebugEntitlements.plist`, which includes `get-task-allow` for debugger and Instruments
attachment.

Now you can open the Instruments app and point it to the Ladybird app bundle.

Building the project with Xcode is not supported. The Xcode project generated by CMake does not properly execute custom
targets, and does not handle all target names in the project.

---

### Documentation BuildProfilingInstructions (`Documentation/BuildProfilingInstructions.md`)

# Build profiling instructions

There are three ways you can get information about compile times:

1. Just using `time ninja install` instead of `ninja install` to time it overall.
2. Reading ninja's log files to get a per-file compile time.
3. Enabling GCC or Clang flags to get a more detailed per-file breakdown of times for compiler passes within a file.

## Ninja log files

Ninja produces a handy log file that includes a per-cpp-file compilation time. This is useful for discovering which files take the most time, and so are good targets for speeding up the overall build time. A python3 script named `ninjatracing` converts the ninja log file into a JSON format that's readable by several profile viewers.

### Prerequisites

`ninjatracing` is available on [GitHub](https://github.com/nico/ninjatracing/blob/master/ninjatracing)

It requires `python3`, available at `python`. You can either create the symlink yourself, or just modify the ninjatracing file to say `python3` instead of `python`.

You also need to make sure `ninjatracing` is marked as executable, and available from your PATH (or somewhere convenient where you can manually call it from).

### Usage

First, we need to clean the build (and `ccache` if present) to make sure every file gets compiled and gives a meaningful time reading.
```console
ninja clean

ccache --clear
```

Then, execute ninja as normal:
```console
ninja
```

The log will be written to `.ninja_log` in the current directory by default. To convert it to the more useful JSON file, call `ninjatracing` from the directory you called ninja from:

```console
ninjatracing .ninja_log > trace.json

```

You can then drag-and-drop the file onto [Speedscope](https://www.speedscope.app/) or any other compatible flamegraph visualizer.

## GCC/Clang

Adding the `-ftime-report` flag to GCC will cause it to output a breakdown for each file it compiles, which looks like this:

<details>
	<summary>GCC -ftime-report output</summary>

```
/* Detailed source-code truncated for AI context efficiency. */
```

</details>

Depending on whether you understand the internals of the compiler, this may or may not be helpful to you! Generally, this is not recommended.

Clang also supports `-ftime-report`, but I have not tested the output for it.

To add the flag, edit the `CMakeLists.txt` in the ladybird root directory, and add `add_compile_options(-ftime-report)` to the list of compile options that start around line 220.

Additionally, you can add `-ftime-report-details` too, which again I have not tested.

---

### Documentation CSSGeneratedFiles (`Documentation/CSSGeneratedFiles.md`)

# CSS Generated Files

We generate a significant amount of CSS-related code, taking in one or more .json files in
[`Libraries/LibWeb/CSS`](../Libraries/LibWeb/CSS) and producing C++ code from them, located in
`Build/<build-preset>/Libraries/LibWeb/CSS/`.
It's likely that you'll need to work with these if you add or modify a CSS property or its values.

The generators are found in [`Meta/Generators`](../Meta/Generators).
They are run automatically as part of the build, and most of the time you can ignore them.

## Properties.json

Each CSS property has an entry here, which describes what values it accepts, whether it's inherited, and similar data.
This generates `PropertyID.h`, `PropertyID.cpp`, `GeneratedCSSStyleProperties.h`, `GeneratedCSSStyleProperties.cpp` and `GeneratedCSSStyleProperties.idl`.
Most of this data is found in the information box for that property in the relevant CSS spec.

The file is organized as a single JSON object, with keys being property names, and the values being the data for that property.
Each property will have some set of these fields on it:

(Note that required fields are not required on properties with `legacy-alias-for` or `logical-alias-for` set.)

| Field                               | Required | Default    | Description                                                                                                 | Generated functions                                                                                                                                                                                                                                         |
|-------------------------------------|----------|------------|-------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `affects-layout`                    | No       | `true`     | Boolean. Whether changing this property will invalidate the element's layout.                               | `bool property_affects_layout(PropertyID)`                                                                                                                                                                                                                  |
| `affects-stacking-context`          | No       | `false`    | Boolean. Whether this property can cause a new stacking context for the element.                            | `bool property_affects_stacking_context(PropertyID)`                                                                                                                                                                                                        |
| `animation-type`                    | Yes      |            | String. How the property should be animated. Defined by the spec. See below.                                | `AnimationType animation_type_from_longhand_property(PropertyID)`                                                                                                                                                                                           |
| `inherited`                         | Yes      |            | Boolean. Whether the property is inherited by its child elements. Only applicable to longhand properties.   | `bool is_inherited_property(PropertyID)`                                                                                                                                                                                                                    |
| `initial`                           | Yes      |            | String. The property's initial value if it is not specified.                                                | `NonnullRefPtr<StyleValue const> property_initial_value(PropertyID)`                                                                                                                                                                                        |
| `legacy-alias-for`                  | No       | Nothing    | String. The name of a property this is an alias for. See below.                                             |                                                                                                                                                                                                                                                             |
| `logical-alias-for`                 | No       | Nothing    | An object. See below.                                                                                       | `bool property_is_logical_alias(PropertyID);`<br/>`PropertyID map_logical_alias_to_physical_property(PropertyID, LogicalAliasMappingContext const&)`                                                                                                        |
| `longhands`                         | No       | `[]`       | Array of strings. If this is a shorthand, these are the property names that it expands out into.            | `Vector<PropertyID> longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> expanded_longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> shorthands_for_longhand(PropertyID)`                                                                 |
| `max-values`                        | No       | `1`        | Integer. How many values can be parsed for this property. eg, `margin` can have up to 4 values.             |                                                                                                                                                                                                                                                             |
| `multiplicity`                      | No       | `"single"` | String. Category for whether this property is a single value or a list of values. See below.                | `bool property_is_single_valued(PropertyID)`<br/>`bool property_is_list_valued(PropertyID)`                                                                                                                                                                 |
| `percentages-resolve-to`            | No       | Nothing    | String. What type percentages get resolved to. eg, for `width` percentages are resolved to `length` values. | `Optional<ValueType> property_resolves_percentages_relative_to(PropertyID)`                                                                                                                                                                                 |
| `positional-value-list-shorthand`   | No       | `false`    | Boolean. Whether this property is a "positional value list shorthand". See below.                           | `bool property_is_positional_value_list_shorthand(PropertyID)`                                                                                                                                                                                              |
| `quirks`                            | No       | `[]`       | Array of strings. Some properties have special behavior in "quirks mode", which are listed here. See below. |                                                                                                                                                                                                                                                             |
| `requires-computation`              | Yes      |            | String. When a property's value needs to be run through the computation process. See below.                 | `bool property_requires_computation_with_inherited_value(PropertyID)`<br/>`bool property_requires_computation_with_initial_value(PropertyID)`<br/>`bool property_requires_computation_with_cascaded_value(PropertyID)`                                      |
| `valid-identifiers`                 | No       | `[]`       | Array of strings. Which keywords the property accepts. See below.                                           | `bool property_accepts_keyword(PropertyID, Keyword)`                                                                                                                                                                                                        |
| `valid-types`                       | No       | `[]`       | Array of strings. Which value types the property accepts. See below.                                        | `bool property_accepts_type(PropertyID, ValueType)`                                                                                                                                                                                                         |
| `needs-layout-for-getcomputedstyle` | No       | `false`    | Boolean. Whether this property requires up-to-date layout before it could be queried by getComputedStyle()  | `bool property_needs_layout_for_getcomputedstyle(PropertyID)`                                                                                                                                                                                               |

### `animation-type`

The [Web Animations spec](https://www.w3.org/TR/web-animations/#animation-type) defines the valid values here:

| Spec term         | JSON value          |
|-------------------|---------------------|
| not animatable    | `none`              |
| discrete          | `discrete`          |
| by computed value | `by-computed-value` |
| repeatable list   | `repeatable-list`   |
| (See prose)       | `custom`            |

### `legacy-alias-for`
(Not to be confused with `logical-alias-for` below.)

[Legacy name aliases](https://drafts.csswg.org/css-cascade-5/#legacy-name-alias) are properties whose spec names have changed,
but the syntax has not, so setting the old one is defined as setting the new one directly.
For example, `font-stretch` was renamed to `font-width`, so `font-stretch` is now a legacy name alias for `font-width`.

### `logical-alias-for`
(Not to be confused with `legacy-alias-for` above.)

Logical aliases are properties like `margin-block-start`, which may assign a value to one of several other properties
(`margin-top`, `margin-bottom`, `margin-left`, or `margin-right`) depending on the element they are applied to.

`logical-alias-for` should be an object with two fields, both of which are required:

| Field     | Description                                                                                                                         |
|-----------|-------------------------------------------------------------------------------------------------------------------------------------|
| `group`   | String. Name of the logical property group this is associated with. (See [LogicalPropertyGroups.json](#logicalpropertygroupsjson).) |
| `mapping` | String. How this relates to the group. eg, if it's the block end value, `block-end`.                                                |

### `multiplicity`
The three possible values are `"single"`, `"list"`, and `"coordinating-list"`.

Most properties represent a single "thing", possibly made of multiple parts. For example, `border` takes a color, style
and thickness, but it's still a single border. Others are a list: `background` takes multiple layers, separated by
commas; `counter-increment` doesn't have commas but also is a list of counters that are incremented.

Lists can also be [coordinating list properties](https://drafts.csswg.org/css-values-4/#linked-properties), in which
case they are marked as `coordinating-list` instead of `list`.

### `positional-value-list-shorthand`
Some shorthand properties work differently to normal in that mapping of provided values to longhands isn't necessarily
1-to-1 and instead depends on the number of values provided, for example `margin`, `border-width`, `gap`, etc.

These properties have distinct behaviors in how they are parsed and serialized, having them marked allows us to
implement this behavior in a generic way.

### `quirks`

The [Quirks spec](https://quirks.spec.whatwg.org/#css) defines these.

| Spec term                    | JSON value           |
|------------------------------|----------------------|
| The hashless hex color quirk | `hashless-hex-color` |
| The unitless length quirk    | `unitless-length`    |

### `requires-computation`

There are three ways the specified value of a property on an element can be determined:

- Inherited, e.g. it is an inherited property and no value has been specified, or the `inherit` keyword has been
  specified
- Initial, e.g. it is _not_ an inherited property and no value has been specified, or the `initial` keyword has been
  specified
- Cascaded, a value has been cascaded for this element based on the styles

We then turn that specified value into a computed value by running the computation (see
`StyleComputer::compute_value_of_property`).

Depending on the property, we know that for some of the above cases the computed value is guaranteed to be the same as
the specified value, by specifying the cases where it _isn't_ we can limit running the computation process to only when
required.

| Value                 | Behavior                                                                                                      |
| --------------------- | ------------------------------------------------------------------------------------------------------------- |
| `always`              | Always runs the computation process, regardless of how the specified value was determined                     |
| `non-inherited-value` | Only run the computation process if the specified value was determined based on the initial or cascaded value |
| `cascaded-value`      | Only run the computation process if the specified value was determined based on a cascaded value              |
| `never`               | Always skip the computation process                                                                           |

### `valid-identifiers`

A list of CSS keyword names, that the property accepts. Consider defining an enum instead and putting its name in the
`valid-types` array, especially if the spec provides a name to a set of such keywords.

Some properties have [legacy value aliases](https://drafts.csswg.org/css-cascade-5/#css-legacy-value-alias), where one
keyword is parsed as another. These are supported as `"foo>bar"`, to make `foo` an alias for `bar`.

### `valid-types`

The `valid-types` array lists the names of CSS value types, as defined in the latest
[CSS Values and Units spec](https://www.w3.org/TR/css-values/), without the `<>` around them.
For numeric types, we use the [bracketed range notation](https://www.w3.org/TR/css-values-4/#css-bracketed-range-notation),
for example `width` can take any non-negative length, so it has `"length [0,∞]"` in its `valid-types` array.
For `<custom-ident>`s, the excluded identifiers are placed within `![]`, for example `"custom-ident ![all,none]"`.

## LogicalPropertyGroups.json

A set of matching CSS properties can be grouped together in what's called a
["logical property group"](https://drafts.csswg.org/css-logical-1/#logical-property-group).
For example, all of the `margin-*` properties are in the `margin` group.

This data is used to map logical properties (such as `margin-block-start`) into their physical counterparts (like
`margin-top`) at runtime, depending on the writing direction. We don't generate any code directly from this file.
Instead, it's used as part of generating the PropertyID code.

The file is a single object where the keys are the names of the logical property groups, and their values are objects
mapping physical dimensions, sides, or corners to the relevant property. Which keys are there depends on the group - for
example the `size` group has `width` and `height`.

## Descriptors.json

Descriptors are basically properties, but for at-rules instead of style. The overall structure is a JSON object, with
keys being at-rule names and the values being data about those at-rules. The main part is the data about the descriptors
that the at-rule can have.

The generated code provides:
- An `AtRuleID` enum, mostly used as a parameter for parsing descriptors, as multiple at-rules may have descriptors with 
  the same name.
- `FlyString to_string(AtRuleID)`, mostly for debug logging.
- A `DescriptorID` enum, listing every descriptor.
- `Optional<DescriptorID> descriptor_id_from_string(AtRuleID, Utf16View)` for getting a DescriptorID from a string, if
  it exists in that at-rule.
- `Utf16FlyString const& to_string(DescriptorID)` for serializing descriptor names.
- `bool at_rule_supports_descriptor(AtRuleID, DescriptorID)` to query if the given at-rule allows the descriptor.
- `RefPtr<StyleValue const> descriptor_initial_value(AtRuleID, DescriptorID)` for getting a descriptor's initial value.

### At-rule fields

Each at-rule object has the following fields. Both are required.

| Field                | Description                                                                                                          |
|----------------------|----------------------------------------------------------------------------------------------------------------------|
| `spec`               | String. URL to the spec that defines this at-rule.                                                                   |
| `descriptors`        | Object, with keys being descriptor names and values being objects of their properties. See below.                    |
| `custom-descriptors` | Object, the configuration for custom descriptors (i.e. --foo). Omission means that custom descriptors are disallowed |

### Descriptor fields

Each descriptor object can have the following fields:

| Field                                    | Default | Required | Description                                                                                            |
|------------------------------------------|---------|----------|--------------------------------------------------------------------------------------------------------|
| `allow-arbitrary-substitution-functions` | `false` | No       | Boolean. Whether this descriptor supports arbitrary substitution functions                             |
| `allow-css-wide-keywords`                | `false` | No       | Boolean. Whether PropertyID based parsing should include CSS-wide keywords (e.g. `initial`, `inherit`) |
| `initial`                                | Nothing | No       | String. The descriptor's initial value if none is provided.                                            |
| `legacy-alias-for`                       | Nothing | No       | String. The name of a different descriptor that this is an alias for.                                  |
| `syntax`                                 | N/A     | Yes      | Array of strings. Each string is one option, taken from the spec.                                      |
| `FIXME` or `NOTE`                        | Nothing | No       | Strings, for when you want to leave a note.                                                            |

### Custom descriptor fields

Each custom descriptor object has the following fields

| Field                                    | Default | Required | Description                                                                                            |
|------------------------------------------|---------|----------|--------------------------------------------------------------------------------------------------------|
| `allow-arbitrary-substitution-functions` | `false` | No       | Boolean. Whether this descriptor supports arbitrary substitution functions                             |
| `allow-css-wide-keywords`                | `false` | No       | Boolean. Whether PropertyID based parsing should include CSS-wide keywords (e.g. `initial`, `inherit`) |
| `syntax`                                 | N/A     | Yes      | Array of strings. Each string is one option.                                                           |
| `FIXME` or `NOTE`                        | Nothing | No       | Strings, for when you want to leave a note.                                                            |

## Keywords.json

This is a single JSON array of strings, each of which is a CSS keyword, for example `auto`, `none`, `medium`, or `currentcolor`.
This generates `Keyword.h` and `Keyword.cpp`.
All keyword values used by any property or media-feature need to be defined here.

The generated code provides:
- A `Keyword` enum as used by `KeywordStyleValue`
- `Optional<Keyword> keyword_from_string(StringView)` to attempt to convert a string into a Keyword
- `StringView string_from_keyword(Keyword)` to convert a Keyword back into a string
- `bool is_css_wide_keyword(StringView)` which returns whether the string is one of the special "CSS-wide keywords"

## Enums.json

This is a single JSON object, with enum names as keys and the values being arrays of keyword names.
This generates `Enums.h` and `Enums.cpp`.

We often want to define an enum that's a set of a few keywords.
`Enums.json` allows you to generate these enums automatically, along with functions to convert them to and from a Keyword,
or convert them to a string.
These enums also can be used in property definitions in `Properties.json` by putting their name in the `valid-types` array.
This helps reduce repetition, for example the `border-*-style` properties all accept the same set of keywords, so they
are implemented as a `line-style` enum.

The generated code provides these for each enum, using "foo" as an example:
- A `Foo` enum for its values
- `Optional<Foo> keyword_to_foo(Keyword)` to convert a `Keyword` to a `Foo`
- `Keyword to_keyword(Foo)` to convert the `Foo` back to a `Keyword`
- `StringView to_string(Foo)` to convert the `Foo` directly to a string

## PseudoClasses.json

This is a single JSON object, with selector pseudo-class names as keys and the values being objects with fields for the pseudo-class.
This generates `PseudoClass.h`.

Each entry has the following properties:

| Field                | Required                               | Default        | Description                                                                                                                                                                     |
|----------------------|----------------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `argument`           | Unless `legacy-alias-for` is specified | Nothing        | A string containing the grammar for the pseudo-class's function parameters - for identifier-style pseudo-classes it is left blank. The grammar is taken directly from the spec. |
| `legacy-alias-for`   | No                                     | Nothing        | Use to specify that this should be treated as a [legacy selector alias](https://drafts.csswg.org/selectors/#legacy-selector-alias) for the named pseudo-class.                  |

The generated code provides:
- A `PseudoClass` enum listing every pseudo-class name

## PseudoElements.json

This is a single JSON object, with pseudo-element names as keys and the values being objects with fields for the pseudo-element.
This generated `PseudoElement.h` and `PseudoElement.cpp`.

Each entry has the following properties:

| Field                | Required                   | Default        | Description                                                                                                                                                            |
|----------------------|----------------------------|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `alias-for`          | No                         | Nothing        | Use to specify that this should be treated as an alias for the named pseudo-element.                                                                                   |
| `function-syntax`    | No                         | Nothing        | Syntax for the function arguments if this is a function-type pseudo-element. Copied directly from the spec.                                                            |
| `implementation`     | Unless `type` is "function | Nothing        | How this pseudo-element is implemented, either `"synthetic"` or `"element-reference"` - see below.                                                                     |
| `is-allowed-in-has`  | No                         | `false`        | Whether this is a [`:has`-allowed pseudo-element](https://drafts.csswg.org/selectors/#has-allowed-pseudo-element).                                                     |
| `is-element-backed`  | No                         | `false`        | Whether this is an [element-backed pseudo-element](https://drafts.csswg.org/css-pseudo-4/#element-backed).                                                             |
| `is-highlight`       | No                         | `false`        | Whether this is a [highlight pseudo-element](https://drafts.csswg.org/css-pseudo-4/#highlight-pseudos).                                                                |
| `is-pseudo-root`     | No                         | `false`        | Whether this is a [pseudo-element root](https://drafts.csswg.org/css-view-transitions/#pseudo-element-root).                                                           |
| `is-tree-abiding`    | No                         | `false`        | Whether this is a [tree-abiding pseudo-element](https://drafts.csswg.org/css-pseudo-4/#tree-abiding).                                                                  |
| `property-whitelist` | No                         | Nothing        | Some pseudo-elements only permit certain properties. If so, name them in an array here. Some special values are allowed here for categories of properties - see below. |
| `spec`               | No                         | Nothing        | Link to the spec definition, for reference. Not used in generated code.                                                                                                |
| `type`               | No                         | `"identifier"` | What type of pseudo-element is this. Either "identifier", "function", or "both".                                                                                       |

The generated code provides:
- A `PseudoElement` enum listing every pseudo-element name
- `StringView pseudo_element_name(PseudoElement)` to convert a `PseudoElement` back into a string
- `bool is_tree_abiding_pseudo_element(PseudoElement)` returns whether the pseudo-element is tree-abiding
- `bool is_pseudo_element_root(PseudoElement)` returns whether the pseudo-element is a [pseudo-element root](https://drafts.csswg.org/css-view-transitions/#pseudo-element-root)
- `bool is_highlight_pseudo_element(PseudoElement)` returns whether the pseudo-element is a [highlight pseudo-element](https://drafts.csswg.org/css-pseudo-4/#highlight-pseudos)

### `property-whitelist`

This is an array of strings. Properties can be named directly ("color"), or categories of properties with a leading `#`
("#font-properties"), as the specs often says a group is allowed instead of listing the properties exactly.
Any properties we don't support yet can be prefixed with "FIXME:" and will be ignored.
The property groups and the properties that always apply are defined in
`PseudoElementPropertyGroups.txt` and generated into the Rust cascade tables.

The following categories are supported:

- `#background-properties`: `background` and its longhands
- `#border-properties`: `border`, `border-radius`, and their longhands
- `#custom-properties`: Custom properties, AKA CSS variables
- `#font-properties`: `font`, its longhands, and other `font-*` properties
- `#inline-layout-properties`: Properties defined in [CSS Inline](https://drafts.csswg.org/css-inline-3/)
- `#inline-typesetting-properties`: Properties defined in [CSS Text](https://drafts.csswg.org/css-text-4/)
- `#margin-properties`: `margin` and its longhands
- `#padding-properties`: `padding` and its longhands
- `#text-decoration-properties`: Properties defined in [CSS Text Decoration](https://drafts.csswg.org/css-text-decor-4/)

### `implementation`

Pseudo-elements are implemented in two ways, either:
 - "synthetic": The originating element is authoritative for the pseudo-element's data (e.g. animations, computed
  style, etc) and handles it's behavior (such as generating layout nodes) itself. This includes pseudo-elements such
  as "::first-line" and "::before"
 - "element-reference": The pseudo-element refers to a "real" element elsewhere in the DOM, usually in the originating
  element's shadow tree, and is thus not authoritative. This includes, among others, form pseudo-elements such as "::placeholder".

NOTE: "element-reference" is an implementation detail and is distinct from the spec concept of "element-backed"
    pseudo-elements, at time of writing all "element-backed" pseudo-elements are "element-reference", but not vice
    versa.

## MediaFeatures.json

This is a single JSON object, with media-feature names as keys and the values being objects with fields for the media-feature.
This generates `MediaFeatureID.h`.

A `<media-feature>` is a value that a media query can inspect.
They are listed in the [`@media` descriptor table](https://www.w3.org/TR/mediaqueries-5/#media-descriptor-table) in the latest Media Queries spec.

The definitions here are like a simplified version of the `Properties.json` definitions.

| Field            | Description                                                                                                                                                                                       |
|------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `type`           | String. How the media-feature is evaluated, either `discrete` or `range`.                                                                                                                         |
| `values`         | Array of strings. These are directly taken from the spec, with keywords as they are, and `<>` around type names. Types may be `<boolean>`, `<integer>`, `<length>`, `<ratio>`, or `<resolution>`. |
| `false-keywords` | Array of strings. These are any keywords that should be considered false when the media feature is evaluated as `@media (foo)`. Generally this will be a single value, such as `"none"`.          |

The generated code provides:
- A `MediaFeatureID` enum, listing each media-feature

## MathFunctions.json

This is a single JSON object, describing each [CSS math function](https://www.w3.org/TR/css-values/#math-function),
with the keys being the function name and the values being objects describing that function's properties.
This generates `MathFunctions.h` and `MathFunctions.cpp`.

Each entry has two properties:

| Field                  | Description                                                                                                                                                                                                            |
|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `parameter-validation` | Optional string. Either "same" or "consistent", depending on whether the spec says the input calculations should be the same type or consistent types. Defaults to "same". Ignore this if there is only one parameter. |
| `parameters`           | An array of parameter definition objects, see below.                                                                                                                                                                   |

Parameter definitions have the following properties:

| Field      | Description                                                                      |
|------------|----------------------------------------------------------------------------------|
| `name`     | String. Name of the parameter, as given in the spec.                             |
| `type`     | String. Accepted types for the parameter, as a single string, separated by `\|`. |
| `required` | Boolean. Whether this parameter is required.                                     |

The generated code provides:
- A `MathFunction` enum listing the math functions
- The implementation of the CSS Parser's `parse_math_function()` method

## TransformFunctions.json

This is a single JSON object, describing each [CSS transform function](https://www.w3.org/TR/css-transforms/#transform-functions),
with the keys being the function name and the values being objects describing that function's properties.
This generates `TransformFunctions.h`, and the Rust parser and serializer read it directly.

Each entry currently has a single property, `parameters`, which is an array of parameter definition objects.
Parameter definitions have the following properties:

| Field      | Description                                  |
|------------|----------------------------------------------|
| `type`     | String. Accepted type for the parameter.     |
| `required` | Boolean. Whether this parameter is required. |

The generated code provides:
- A `TransformFunction` enum listing the transform functions

## EnvironmentVariables.json

This is a single JSON object, describing each [CSS environment variable](https://drafts.csswg.org/css-env/#css-environment-variable),
with the keys being the environment variable names, and the values being objects describing the variable's properties.
This generates `EnvironmentVariable.h` and `EnvironmentVariable.cpp`.

Each entry has 3 properties, all taken from the spec:

| Field        | Description                                                         |
|--------------|---------------------------------------------------------------------|
| `spec`       | String. URL to the spec definition for this environment variable.   |
| `type`       | String. CSS value type of the variable, eg `<length>`.              |
| `dimensions` | Integer. Number of dimensions for the variable, or `0` for scalars. |

The generated code provides:
- An `EnvironmentVariable` enum listing the environment variables
- `StringView to_string(EnvironmentVariable)` to convert the `EnvironmentVariable` back to a string
- `ValueType environment_variable_type(EnvironmentVariable)` to get the variable's value type
- `u32 environment_variable_dimension_count(EnvironmentVariable)` to get its dimension count

## Units.json

This is a JSON object with the keys being dimension type names, and the values being objects. Those objects' keys are
unit names, and their values are data about each unit.
It generates `Units.h` and `Units.cpp`.

Each unit has the following properties:

| Field                      | Description                                                                                                                        |
|----------------------------|------------------------------------------------------------------------------------------------------------------------------------|
| `is-canonical-unit`        | Boolean, default `false`. Each dimension has one canonical unit.                                                                   |
| `number-of-canonical-unit` | Number. How many of the canonical units 1 of this is equivalent to. Ignore this for relative units, and the canonical unit itself. |
| `is-relative-to`           | String. Some length units are relative to the font or viewport. Set this to `"font"` or `"viewport"` for those.                    |

The generated code provides:
- A `DimensionType` enum, listing each type of dimension that has units defined.
- `Optional<DimensionType> dimension_for_unit(StringView)` for querying which dimension a unit applies to, if any.
- A `FooUnit` enum for each dimension "foo", which lists all the units of that dimension.
- For each of those...
  - `constexpr FooUnit canonical_foo_unit()` which is the canonical unit for that type.
  - `Optional<FooUnit> string_to_foo_unit(StringView)` for parsing a unit from a string.
  - `StringView to_string(FooUnit)` for serializing those units.
  - `bool units_are_compatible(FooUnit, FooUnit)` which returns whether these are compatible - basically whether you can convert from one to the other.
  - `double ratio_between_units(FooUnit, FooUnit)` to get a multiplier for converting the first unit into the second.
- `bool is_absolute(LengthUnit)`, `bool is_font_relative(LengthUnit)`, `bool is_viewport_relative(LengthUnit)`, and `bool is_relative(LengthUnit)` for checking the category of length units.

---

### Documentation CSSProperties (`Documentation/CSSProperties.md`)

# Adding or Modifying a CSS Property

There are several different places you need to make changes in order to add or modify a CSS property.
These are listed below in the order that Ladybird deals with them, starting at parsing and ending with them being used.

## Data

The first place you will need to go to is `CSS/Properties.json`. This file contains the definition for each
property, and is used to generate the `PropertyID` enum and a selection of functions. You may also need to
modify `CSS/Keywords.json`, `CSS/Enums.json`, and `CSS/LogicalPropertyGroups.json`.
See [CSSGeneratedFiles.md](CSSGeneratedFiles.md) for details.

## Parsing

For many properties, there is no need to add custom parsing code. Properties that take a single value, or shorthands
that are a list of their longhand properties, will be parsed automatically using the data in `Properties.json`.
However, there are many CSS properties with more complicated grammar and so they require custom parsing.

Property-parsing code goes in `CSS/Parser/PropertyParsing.cpp`, and `CSS/Parser/Parser.h`. First,
`Parser::parse_css_value()` is called, which has a switch for specific properties. Call your method from there. It
should return a `RefPtr` to a `StyleValue` or one of its subclasses.

For shorthands, you should normally use `ShorthandStyleValue`, which automatically expands its longhand values. You
might need to modify `ShorthandStyleValue::to_string` if your shorthand has special serialization rules. For example,
`border-radius` serializes with a `/` separating the horizontal and vertical components.

If your property's value can't be represented with an existing type, you might need to add a new style value class.
If you need to do this, pester @AtkinsSJ until he gets around to documenting it. ;^)

## Computed style

After parsing and style computation, longhand properties are stored as `StyleValue` pointers in
`ComputedProperties`. Any shorthands have been expanded out, and so we do not need to store them directly.

These longhands then need to be converted to a more usable form. To do this, add a getter to `ComputedProperties` with
the same name as the property. It should return a type that holds the value in a compact form.

Then, `CSS/ComputedValues.h` contains three classes that are relevant:
- `ComputedValues` holds the computed value of each property, in a flat format. Depending on whether the property is
  inherited or not, it needs adding to the `m_inherited` or `m_noninherited` structs, with a corresponding getter.
- `MutableComputedValues` also needs a setter for the value.
- `InitialValues` has a getter for the default value of the property. This isn't always needed, for example if the
  default computed value is an empty `Optional` or `Vector`.

Style is copied from `ComputedProperties` to `ComputedValues` in `NodeWithStyle::apply_style()`. Each property is
copied individually.

Then, read the value of your property with that `ComputedValues` getter we added. For example, this code reads the
computed values of `visibility` and `opacity`:

```c++
bool Paintable::is_visible() const
{
    auto const& computed_values = this->computed_values();
    return computed_values.visibility() == CSS::Visibility::Visible && computed_values.opacity() != 0;
}
```

## JavaScript

Some properties have special rules for getting the computed value from JS. For these, you will need to add to
`CSSStyleProperties::style_value_for_computed_property()`. Shorthands that are serialized in an unusual way
also need handling inside `CSSShorthandStyleValue::to_string()`.

---

### Documentation CodePolicy (`Documentation/CodePolicy.md`)

# Code Policy

This document describes expectations for code maintained in upstream Ladybird. It is primarily intended for project maintainers and people studying local Ladybird development.

## Testing policy

When possible, code changes should include tests when fixing bugs or adding new features.

If changes have relevant [Web Platform Tests (WPT)](https://wpt.fyi/) tests - especially if the changes cause Ladybird to pass any WPT tests it had not yet been passing - consider [importing those tests into the Ladybird tree](Testing.md#importing-web-platform-tests), and then committing the imported tests along with the code changes.

## Human language policy

In Ladybird, we treat human language as seriously as we do programming language.

The following applies to all user-facing strings, code, comments, and commit messages:

* The official project language is American English with ISO 8601 dates and metric units.
* Use proper spelling, grammar, and punctuation.
* Write in an authoritative and technical tone.
* Avoid contractions, slang, and idioms.
* Avoid humor, sarcasm, and other forms of non-literal language.
* Use gender-neutral pronouns, except when referring to a specific person.

Note that this also applies to debug logging and other internal strings, as they may be exposed to users in the future.

## Code policy

Nobody is perfect, and sometimes we mess things up. That said, here are some good do's & don'ts to try and stick to:

**Do:**

* Write in idiomatic project-style C++23, using the `AK` containers in all code.
* Conform to the project coding style found in [CodingStyle.md](CodingStyle.md). Use `clang-format` to automatically format C++ files.
* Choose expressive variable, function and class names. Make it as obvious as possible what the code is doing.
* Split your changes into separate, atomic commits (i.e. A commit per feature or fix, where the build, tests and the system are all functioning).
* Make sure your commits are rebased on the master branch.
* Wrap your commit messages at 72 characters.
* The first line of the commit message is the subject line, and must have the format "Category: Brief description of what's being changed". The category should be the name of a library, application, service, utility, etc.
  * Examples: `LibMedia`, `WebContent`, `CI`, `AK`, `RequestServer`, `js`
  * Don't use a category like "`Libraries`" or "`Utilities`", except for generic changes that affect a large portion of code within these directories.
  * Don't use specific component names, e.g. C++ class names, as the category either - mention them in the summary instead. E.g. `LibGUI: Brief description of what's being changed in FooWidget` rather than `FooWidget: Brief description of what's being changed`
  * Several categories may be combined with `+`, e.g. `LibJS+LibWeb+Browser: ...`
* Write the commit message subject line in the imperative mood ("Foo: Change the way dates work", not "Foo: Changed the way dates work").
* Write your commit messages in proper English, with care and punctuation.
* Amend existing commits when adding changes after a review, where relevant.
* Mark each review comment as "resolved" after pushing a fix with the requested changes, where relevant.
* Add your personal copyright line to files when making substantive changes. (Optional but encouraged!)
* Check the spelling of your code, comments and commit messages.
* If you have images that go along with your code, run `optipng -strip all` on them to optimize and strip away useless metadata - this can reduce file size from multiple kilobytes to a couple hundred bytes.

**Don't:**

* Introduce changes that are incompatible with the project licence (2-clause BSD.)
* Touch anything outside the stated scope of the change.
* Iterate excessively on your design across multiple commits.
* Use weasel-words like "refactor" or "fix" to avoid explaining what's being changed.
* End commit message subject lines with a period.
* Include commented-out code.
* Write in C. (Instead, take advantage of C++'s amenities, and don't limit yourself to the standard C library.)
* Attempt large architectural changes until you are familiar with the system and have worked on it for a while.
* Engage in excessive "feng shui programming" by moving code around without quantifiable benefit.
* Add jokes or other "funny" things to user-facing parts of the system.

## On usage of AI and LLMs

Usage of AI assistance is usually fine, but you are responsible for making sure the quality of the output is up to the standards of the project. Currently, AI-generated output is often too verbose and its quality is subpar compared to carefully written human work.

Do not use an AI or LLM to generate changes, respond to issues, or participate in project discussion without holding the output to the same standards as human-written content.

Do not use AI-generated descriptions or summaries as a substitute for understanding the relevant code, issue, or discussion.

## Commit hooks

The repository contains a file called `.pre-commit-config.yaml` that defines several commit hooks that can be run automatically just before and after creating a new commit. These hooks lint your commit message, and the changes it contains to ensure they will pass the automated CI checks.

The Python and workflow lint scripts use the versions pinned in
`Meta/Linters/requirements.txt`, including when called by commit hooks or CI.
They prepare a virtual environment under `Build/linters` on first use and reuse
it afterward. Python with `venv` and `pip` support is required; the first setup
downloads the pinned tools. Changing the requirements or Python interpreter
selects a fresh environment. Concurrent invocations share a setup lock.

To prepare the environment in advance or run a pinned tool directly:

```console
python3 Meta/Linters/run.py
python3 Meta/Linters/run.py ruff --version
python3 Meta/Linters/run.py ruff check Meta/ladybird.py
```

Once prepared, the environment can be reused without downloading the packages
again. Delete `Build/linters` to clear the cache when no linters are running.

To enable these hooks, first follow the installation instructions available at https://pre-commit.com/#install and then enable one or both of the following hooks:

* pre-commit hook - Runs `Meta/lint-ci.sh` and `Meta/lint-ports.py` to ensure changes to the code will pass linting:

  ```console
  pre-commit install
  ```

* post-commit hook - Lints the commit message to ensure it will pass the commit linting:

  ```console
  pre-commit install --hook-type commit-msg
  ```

## Git notes

The GitHub project contains [git notes](https://git-scm.com/docs/git-notes) for each commit that includes e.g. a link to the pull request from which the commit originated and reviewer information. These are updated automatically, but require an additional step locally to be able to see the notes in `git log`:

```bash
git config --add remote.upstream.fetch '+refs/notes/*:refs/notes/*'
```

> [!NOTE]
> The `upstream` remote in this command should be replaced with whatever you've named the LadybirdBrowser/ladybird.git
> remote in your local clone. Use `git remote -v` to find that name.

Now, any time you `git fetch`, the latest notes will be fetched as well. You will see information like the following when you run `git log`:

```
commit c1b0e180ba64d2ea7e815e2c2e93087ae9a26500
Author: Timothy Flynn <trflynn89@pm.me>
Date:   Mon Jul 29 10:18:25 2024 -0400

    LibWebView: Insert line numbers before each line in about:srcdoc

    The behavior chosen here (fixed-width counters, alignment, etc.) matches
    Firefox.

Notes:
    Author: https://github.com/trflynn89
    Commit: https://github.com/LadybirdBrowser/ladybird/commit/c1b0e180ba6
    Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/892
    Reviewed-by: https://github.com/AtkinsSJ ✅
```

---

### Documentation CodingStyle (`Documentation/CodingStyle.md`)

# Ladybird C++ coding style

For low-level styling (spaces, parentheses, brace placement, etc), all code should follow the format specified in `.clang-format` in the project root.

**Important: Make sure you use the correct version of `clang-format`!**

See [lint_clang_format.py](../Meta/Linters/lint_clang_format.py) for the version enforced in CI.

See [AdvancedBuildInstructions.md](AdvancedBuildInstructions.md#clang-format-updates) for instructions on how to get an
up-to-date version if your OS distribution does not ship the correct version.

This document describes the coding style used for C++ code in the Ladybird Browser project. All new code should conform to this style.

We'll definitely be tweaking and amending this over time, so let's consider it a living document. :)


### Names

A combination of CamelCase, snake\_case, and SCREAMING\_CASE:

- Use CamelCase (Capitalize the first letter, including all letters in an acronym) in a class, struct, or namespace name
- Use snake\_case (all lowercase, with underscores separating words) for variable and function names
- Use SCREAMING\_CASE for constants (both global and static member variables)

###### Right:

```cpp
struct Entry;
size_t buffer_size;
class FileDescriptor;
String absolute_path();
```

###### Wrong:

```cpp
struct data;
size_t bufferSize;
class Filedescriptor;
String MIME_Type();
```

When implementing spec algorithms and other constructs that a spec explicitly names, prefer closely matching the same names the spec uses, whenever possible.

Given the construct at https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-being-missing which has the literal name _“Suffering from being missing”_ in the spec, for example:

###### Right:

```c++
bool HTMLInputElement::suffering_from_being_missing(); // exactly matches the spec naming
```

###### Wrong:

```c++
bool HTMLInputElement::has_missing_constraint(); // arbitrarily differs from spec naming
```

Use full words, except in the rare case where an abbreviation would be more canonical and easier to understand.

###### Right:

```cpp
size_t character_size;
size_t length;
short tab_index; // More canonical.
```

###### Wrong:

```cpp
size_t char_size;
size_t len;
short tabulation_index; // Goofy.
```

Data members in C++ classes should be private. Static data members should be prefixed by "s\_". Other data members should be prefixed by "m\_". Global variables should be prefixed by "g\_".

###### Right:

```cpp
class String {
public:
    ...

private:
    int m_length { 0 };
};
```

###### Wrong:

```cpp
class String {
public:
    ...

    int length { 0 };
};
```

Precede setters with the word "set". Use bare words for getters. Setter and getter names should match the names of the variables being set/gotten.

###### Right:

```cpp
void set_count(int); // Sets m_count.
int count() const; // Returns m_count.
```

###### Wrong:

```cpp
void set_count(int); // Sets m_the_count.
int get_count() const; // Returns m_the_count.
```

Precede getters that return values through out arguments with the word "get".

###### Right:

```cpp
void get_filename_and_inode_id(String&, InodeIdentifier&) const;
```

###### Wrong:

```cpp
void filename_and_inode_id(String&, InodeIdentifier&) const;
```

Use descriptive verbs in function names.

###### Right:

```cpp
bool convert_to_ascii(short*, size_t);
```

###### Wrong:

```cpp
bool to_ascii(short*, size_t);
```

When there are two getters for a variable, and one of them automatically makes sure the requested object is instantiated, prefix that getter function with `ensure_`. As it ensures that an object is created, it should consequently also return a reference, not a pointer.

###### Right:

```cpp
Inode* inode();
Inode& ensure_inode();
```

###### Wrong:

```cpp
Inode& inode();
Inode* ensure_inode();
```

Leave meaningless variable names out of function declarations. A good rule of thumb is if the parameter type name contains the parameter name (without trailing numbers or pluralization), then the parameter name isn't needed. Usually, there should be a parameter name for bools, strings, and numerical types.

###### Right:

```cpp
void set_count(int);

void do_something(Context*);
```

###### Wrong:

```cpp
void set_count(int count);

void do_something(Context* context);
```

Prefer enums to bools on function parameters if callers are likely to be passing constants, since named constants are easier to read at the call site. An exception to this rule is a setter function, where the name of the function already makes clear what the boolean is.

###### Right:

```cpp
do_something(something, AllowFooBar::Yes);
paint_text_with_shadows(context, ..., text_stroke_width > 0, is_horizontal());
set_resizable(false);
```

###### Wrong:

```cpp
do_something(something, false);
set_resizable(NotResizable);
```

Enum members should use InterCaps with an initial capital letter.

Prefer `const` to `#define`. Prefer inline functions to macros.

`#defined` constants should use all uppercase names with words separated by underscores.

Use `#pragma once` instead of `#define` and `#ifdef` for header guards.

###### Right:

```cpp
// MyClass.h
#pragma once
```

###### Wrong:

```cpp
// MyClass.h
#ifndef MyClass_h
#define MyClass_h
```

### Other Punctuation

Constructors for C++ classes should initialize their members using C++ initializer syntax. Each member (and superclass) should be indented on a separate line, with the colon or comma preceding the member on that line. Prefer initialization at member definition whenever possible.

###### Right:

```cpp
class MyClass {
    ...
    Document* m_document { nullptr };
    int m_my_member { 0 };
};

MyClass::MyClass(Document* document)
    : MySuperClass()
    , m_document(document)
{
}

MyOtherClass::MyOtherClass()
    : MySuperClass()
{
}
```

###### Wrong:

```cpp
MyClass::MyClass(Document* document) : MySuperClass()
{
    m_myMember = 0;
    m_document = document;
}

MyClass::MyClass(Document* document) : MySuperClass()
    : m_my_member(0) // This should be in the header.
{
    m_document = document;
}

MyOtherClass::MyOtherClass() : MySuperClass() {}
```

Prefer index or range-for over iterators in Vector iterations for terse, easier-to-read code.

###### Right:

```cpp
for (auto& child : children)
    child->do_child_thing();
```


#### OK:

```cpp
for (int i = 0; i < children.size(); ++i)
    children[i]->do_child_thing();
```

###### Wrong:

```cpp
for (auto it = children.begin(); it != children.end(); ++it)
    (*it)->do_child_thing();
```

### Pointers and References

Both pointer types and reference types should be written with no space between the type name and the `*` or `&`.

An out argument of a function should be passed by reference except rare cases where it is optional in which case it should be passed by pointer.

###### Right:

```cpp
void MyClass::get_some_value(OutArgumentType& out_argument) const
{
    out_argument = m_value;
}

void MyClass::do_something(OutArgumentType* out_argument) const
{
    do_the_thing();
    if (out_argument)
        *out_argument = m_value;
}
```

###### Wrong:

```cpp
void MyClass::get_some_value(OutArgumentType* outArgument) const
{
    *out_argument = m_value;
}
```

### "using" Statements

In header files in the AK sub-library, however, it is acceptable to use "using" declarations at the end of the file to import one or more names in the AK namespace into the global scope.

###### Right:

```cpp
// AK/Vector.h

namespace AK {

} // namespace AK

using AK::Vector;
```

###### Wrong:

```cpp
// AK/Vector.h

namespace AK {

} // namespace AK

using namespace AK;
```

###### Wrong:

```cpp
// runtime/Object.h

namespace AK {

} // namespace AK

using AK::SomethingOrOther;
```

In C++ implementation files, do not use "using" declarations of any kind to import names in the standard template library. Directly qualify the names at the point they're used instead.

###### Right:

```cpp
// File.cpp

std::swap(a, b);
c = std::numeric_limits<int>::max()
```

###### Wrong:

```cpp
// File.cpp

using std::swap;
swap(a, b);
```

###### Wrong:

```cpp
// File.cpp

using namespace std;
swap(a, b);
```

### Types

Omit "int" when using "unsigned" modifier. Do not use "signed" modifier. Use "int" by itself instead.

###### Right:

```cpp
unsigned a;
int b;
```

###### Wrong:

```cpp
unsigned int a; // Doesn't omit "int".
signed b; // Uses "signed" instead of "int".
signed int c; // Doesn't omit "signed".
```

### Classes

For types with methods, prefer `class` over `struct`.

* For classes, make public getters and setters, keep members private with `m_` prefix.
* For structs, let everything be public and skip the `m_` prefix.

###### Right:

```cpp
struct Thingy {
    String name;
    int frob_count { 0 };
};

class Doohickey {
public:
    String const& name() const { return m_name; }
    int frob_count() const { return m_frob_count; }

    void jam();

private:
    String m_name;
    int m_frob_count { 0 };
}
```

###### Wrong:

```cpp
struct Thingy {
public:
    String m_name;
    int frob_count() const { return m_frob_count; }

private:
    int m_frob_count { 0 };
}

class Doohickey {
public:
    String const& name() const { return this->name; }

    void jam();

    String name;
    int frob_count { 0 };
};
```

Use a constructor to do an implicit conversion when the argument is reasonably thought of as a type conversion and the type conversion is fast. Otherwise, use the explicit keyword or a function returning the type. This only applies to single argument constructors.

###### Right:

```cpp
class LargeInt {
public:
    LargeInt(int);
...

class Vector {
public:
    explicit Vector(int size); // Not a type conversion.
    Vector create(Array); // Costly conversion.
...

```

###### Wrong:

```cpp
class Task {
public:
    Task(ExecutionContext&); // Not a type conversion.
    explicit Task(); // No arguments.
    explicit Task(ExecutionContext&, Other); // More than one argument.
...
```

### Singleton pattern

Use a static member function named "the()" to access the instance of the singleton.

###### Right:

```cpp
class UniqueObject {
public:
    static UniqueObject& the();
...
```

###### Wrong:

```cpp
class UniqueObject {
public:
    static UniqueObject& shared();
...
```

###### Wrong:

```cpp
class UniqueObject {
...
};

UniqueObject& my_unique_object(); // Free function.
```

### Comments

Comments should be written using `//` and not `/* */`, except for the copyright notice.

Write comments as proper sentences starting with a capital letter and ending with a period (or other punctuation).
One exception may be end of line comments like this: `if (x == y) // false for NaN`
Another exception is comments copied from specifications. These should be quoted verbatim, and not modified except for
wrapping or to insert necessary punctuation such as adding `**` when a number is raised to a power, as this is often
done using elements like `<sup>` which do not appear in the copied text.

Please wrap long comments onto multiple lines so that they are easier to read. Generally, 120 characters is a good width
to aim for.

Use FIXME: (without attribution) to denote items that need to be addressed in the future.
TODO: (without attribution) is also permitted.

###### Right:

```cpp
draw_jpg(); // FIXME: Make this code handle jpg in addition to the png support.
```

###### OK:

```cpp
draw_jpg(); // TODO: Make this code handle jpg in addition to the png support.
```

###### Wrong:

```cpp
draw_jpg(); // FIXME(joe): Make this code handle jpg in addition to the png support.
```

Explain *why* the code does something. The code itself should already say what is happening.

###### Right:

```cpp
i++; // Go to the next page.
```

```cpp
// Let users toggle the advice functionality by clicking on catdog.
catdog_widget.on_click = [&] {
    if (advice_timer->is_active())
        advice_timer->stop();
    else
        advice_timer->start();
};
```

###### Even better:

```cpp
page_index++;
```

###### Wrong:

```cpp
i++; // Increment i.
```

```cpp
// If the user clicks, toggle the timer state.
catdog_widget.on_click = [&] {
    if (advice_timer->is_active())
        advice_timer->stop();
    else
        advice_timer->start();
};
```

#### Spec notes &amp; questions

Many web specs include notes that are prefixed with `NOTE: ...`. To allow for verbatim copying of these notes into our
code, we retain the `NOTE:` prefix and use [`NB:`](https://en.wikipedia.org/wiki/Nota_bene) for our own notes.

This only applies to comments as part of code that is directly implementing a spec algorithm or behavior. Comments in
other places do not need a prefix.

Sometimes a draft spec text includes a spec question, which should be included with double square brackets:
`[[ ... ]]`.

##### Right:

```cpp
// 2. If property is in already serialized, continue with the steps labeled declaration loop.
// NOTE: The prefabulated aluminite will not be suitable for use here. If the listed spec note is so long that we reach
//       column 120, we wrap around and indent the lines to match up with the first line.
// NB: We _can_ actually use the aluminite since we unprefabulated it in step 1 for performance reasons.

// 3. For each property in window [[ in what order? ]]:
```

##### Wrong:

```cpp
// LB-NOTE: The aluminite might come pre-prefabulated at this point.
// Spec-note: Another example of a custom note prefix that we shouldn't use.
// There is no prefix whatsoever here, making it unclear whether this is a spec step, note or a developer note.

// 3. For each property in window (in what order?):
```

### Overriding Virtual Methods

The declaration of a virtual method inside a class must be declared with the `virtual` keyword. All subclasses of that class must also specify either the `override` keyword when overriding the virtual method, or the `final` keyword when overriding the virtual method and requiring that no further subclasses can override it.

###### Right:

```cpp
class Person {
public:
    virtual String description() { ... };
}

class Student : public Person {
public:
    virtual String description() override { ... }; // This is correct because it contains both the "virtual" and "override" keywords to indicate that the method is overridden.
}

```

```cpp
class Person {
public:
    virtual String description() { ... };
}

class Student : public Person {
public:
    virtual String description() final { ... }; // This is correct because it contains both the "virtual" and "final" keywords to indicate that the method is overridden and that no subclasses of "Student" can override "description".
}

```

###### Wrong:

```cpp
class Person {
public:
    virtual String description() { ... };
}

class Student : public Person {
public:
    String description() override { ... }; // This is incorrect because it uses only the "override" keyword to indicate that the method is virtual. Instead, it should use both the "virtual" and "override" keywords.
}
```

```cpp
class Person {
public:
    virtual String description() { ... };
}

class Student : public Person {
public:
    String description() final { ... }; // This is incorrect because it uses only the "final" keyword to indicate that the method is virtual and final. Instead, it should use both the "virtual" and "final" keywords.
}
```

```cpp
class Person {
public:
    virtual String description() { ... };
}

class Student : public Person {
public:
    virtual String description() { ... }; // This is incorrect because it uses only the "virtual" keyword to indicate that the method is overridden.
}
```

### Const placement

Use "east const" style where `const` is written on the right side of the type being qualified. See [this article](https://mariusbancila.ro/blog/2018/11/23/join-the-east-const-revolution/) for more information about east const.

###### Right:

```cpp
Salt const& m_salt;
```

###### Wrong:

```cpp
const Salt& m_salt;
```

### Casts

Before you consider a cast, please see if your problem can be solved another way that avoids the visual clutter.

- Integer constants can be specified to have (some) specific sizes with postfixes like `u, l, ul` etc. The same goes for single-precision floating-point constants with `f`.
- Working with smaller-size integers in arithmetic expressions is hard because of [implicit promotion](https://wiki.sei.cmu.edu/confluence/display/c/INT02-C.+Understand+integer+conversion+rules). Generally, it is fine to use `int` and other "large" types in local variables, and possibly cast at the end.
- If you `const_cast`, _really_ consider whether your APIs need to be adjusted in terms of their constness. Does the member function you're writing actually make sense to be `const`?
- If you do checked casts between base and derived types, also consider your APIs. For example: Does the function being called actually need to receive the more general type or is it fine with the more specialized type?

If you _do_ need to cast: **Don't use C-style casts**. The C-style cast has [complex behavior](https://en.cppreference.com/w/c/language/cast) that is undesired in many instances. Be aware of what sort of type conversion the code is trying to achieve, and use the appropriate (!) C++ cast operator, like `static_cast`, `reinterpret_cast`, `bit_cast`, `dynamic_cast` etc.

There is a single exception to this rule: marking a function parameter as used with `(void)parameter;`.

###### Right:
```cpp
MyParentClass& object = get_object();
// Verify the type...
MyChildClass& casted = static_cast<MyChildClass&>(object);
```

```cpp
// AK::Atomic::exchange()

alignas(T) u8 buffer[sizeof(T)];
T* ret = reinterpret_cast<T*>(buffer);
```

```cpp
// SeekableStream::tell()

// Seek with 0 and SEEK_CUR does not modify anything despite the const_cast,
// so it's safe to do this.
return const_cast<SeekableStream*>(this)->seek(0, SeekMode::FromCurrentPosition);
```

###### Wrong:
```cpp
// These should be static_cast.
size_t mask_length = (size_t)((u8)-1) + 1;
```

```cpp
// This should be reinterpret_cast.
return (u8 const*)string.characters_without_null_termination();
```

### Omission of curly braces from statement blocks

Curly braces may only be omitted from `if`/`else`/`for`/`while`/etc. statement blocks if the body is a single line.

Additionally, if any body of a connected if/else statement requires curly braces according to this rule, all of them do.

###### Right:
```cpp
if (condition)
    foo();
```

```cpp
if (condition) {
    foo();
    bar();
}
```

```cpp
if (condition) {
    foo();
} else if (condition) {
    bar();
    baz();
} else {
    qux();
}
```

```cpp
for (size_t i = i; condition; ++i) {
    if (other_condition)
        foo();
}
```

##### OK:

```cpp
if (condition) {
    foo();
}
```

###### Wrong:

```cpp
if (condition)
    // There is a comment here.
    foo();
```

```cpp
if (condition)
    foo();
else {
    bar();
    baz();
} else
    qux();
```

```cpp
for (size_t i = i; condition; ++i)
    if (other_condition)
        foo();
```

---

### Documentation CrashReports (`Documentation/CrashReports.md`)

# Crash reports

On macOS and Linux, Ladybird saves local text reports when the browser or a
helper process crashes: WebContent, WebWorker, RequestServer, ImageDecoder,
Compositor and WasmCompiler. Reports are stored in
`~/Library/Application Support/Ladybird/CrashReports/` on macOS and
`~/.local/share/Ladybird/CrashReports/` on Linux, or under
`$XDG_DATA_HOME/Ladybird/CrashReports/` if that variable is set.
The directory is private to the current user, and report files are read-only,
with mode `0400`.
Reports awaiting review are always kept; of the reports that have already been
offered, the newest 20 are kept. Filenames start with a UTC date and time,
for example
`2026-09-06T12-34-56Z-WebContent-a1B2c3.txt`, so they sort chronologically.
Hyphens in the time keep filenames compatible with Windows; the random suffix
avoids collisions. Retention includes reports saved with the older filenames.
Nothing is uploaded automatically.

Once the report of a WebContent crash is saved, the crash screen shows a review
of it, with sending it as the main action and **Reload page** next to it. After
the report is answered, reloading is what the screen offers. Any other crash,
such as one of the Compositor or RequestServer, shows a popover below the menu
button as soon as its report is saved. A newer crash takes the place of a
popover still on screen, and the popover closes when its tab goes out of view.
It offers to review the report on the same crash screen in a new blank tab,
which **Close tab** closes. A report on its way keeps being sent after the
screen that showed its review is closed or lost. A browser-process report is
recovered on the next launch, which shows the same popover for the newest report
still awaiting review. While no browser window is active, the popover waits
until one is. Browsers driven by WebDriver never ask. Ladybird automatically
offers each report at most once; leaving the crash screen or the popover without
answering keeps the report on the device without offering it again on a later
launch. A report of a crash from more than 14 days ago, or from before October
2, 2026 20:00 UTC, is never offered, but stays in the folder. Reports that have
been offered move into a `Seen/` subdirectory, where the newest 20 are kept for
reference. **Settings > Advanced > Crash reports > Open folder** remains
available even when nothing has crashed. Reload restores the failed page without
adding a crash-screen history entry; Back and Forward continue to use the
original session history. The crash screen is native browser UI, so it does not
depend on a web content process.

The review asks what the user was doing and lets them choose whether to send the
report. Report details lists its main fields, such as the failure, signal,
version and commit, and opens the full report, exactly as it would be attached,
in the system's text viewer. Submissions omit the website URL by default; when
the crashed page had one, the user can explicitly include it and edit it first.
Ladybird does not collect contact information. Network errors, timeouts, rate
limits and server errors are retried a few times, honoring the server's
`Retry-After`; a report the server rejects is not. A report that changed on disk
after it was reviewed is not sent. A successful submission removes the local
copy. A report that is declined or could not be sent stays on the device, but is
not offered again.

Reports and filenames identify the process type. Build information includes the
full Git commit, tracked-source modification state, C++ compiler identity and
version, macOS SDK version when applicable, CMake build options, and flags from
the helper's compilation command. Include/output paths, string-valued defines
and arbitrary compiler arguments are omitted. The metadata refreshes on
incremental builds and does not require Git at runtime. Source archives without
Git metadata report an unknown revision; local source modifications and
`-march=native` builds still require the corresponding source changes and
build-machine target to reproduce.

Reports also contain the time of the crash, the browser version, platform,
architecture, numeric kernel release, build configuration, process uptime when
available, termination signal or exit code, signal code when available, and a
bounded native stack. Stack frames identify their binaries by Mach-O UUID on
macOS or ELF build ID on Linux and contain object addresses with the load
relocation removed. When a binary is also loaded in the surviving browser, its
nearest available native symbol and the offset from that symbol are included.
Binary IDs identify builds, not users or devices.

Saved crash diagnostics do not collect page URLs, titles, content, JavaScript
stacks, cookies, network requests, console output, stderr, command lines,
environment variables, usernames, hostnames, installation paths, absolute
source paths, general register values, or memory dumps. Native symbol names
containing paths or non-printable characters are omitted. This also applies to
crashes in private windows.

Fatal `VERIFY` and `ASSERT` failures include their compile-time expression and
source location. Locations inside the checkout are repository-relative; external
locations include only the filename and line. Assertion text is bounded and does
not include evaluated operands or runtime page data. It is saved before terminal
formatting and backtrace generation, and remains available if those fail.

## Architecture

After a WebContent crash, the browser displays a native crash screen and retains
the failed URL, title and committed history entry. The replacement WebContent
process remains dormant until the user chooses a recovery action. The crash
screen provides reload and report review actions directly in the browser
process. LibWebView's `CrashReportReview` prepares a report for display and
validates the user's choices, and `CrashReportSubmission` sends it to the report
server; the Qt UI only presents them.

The browser creates an unlinked temporary file before spawning each helper and
passes a descriptor to the child. The child cannot access the report directory.
It snapshots native executable ranges and binary IDs before sandboxing. The
POSIX signal handler writes fixed-size records to the descriptor, starting with
the termination reason. It walks a bounded frame-pointer chain using Mach reads
on macOS and a preopened `/proc/self/mem` descriptor on Linux. Unreadable memory
ends the walk. Linux also has a bounded stack-scan fallback for code without
frame pointers. The handler does not allocate, format strings, acquire
application locks, or symbolize the stack. Signal termination is preserved so
the operating system can still handle the crash normally.

After process exit, the browser reads a bounded number of records and formats
the report. It never copies arbitrary child-process text into the report. Clean
exits, SIGTERM and SIGKILL do not produce reports. Other abnormal exits still
produce a minimal report when capture was unavailable. Helpers launched by a
test-mode browser do not produce automatic reports.

The browser process has no parent process to finish its report, so Ladybird
recovers its signal-safe crash data on the next launch. Clean exits and exits
without a captured fatal signal do not produce a browser report. The record also
keeps a description of the browser's build, so a report recovered after an
update describes the build that crashed. A record without one says so instead of
describing the launch that recovers it.

The capture implementation and bounded record format live in LibCore, so all
helpers can install the handler before sandboxing without linking browser UI
code. Browser-owned report formatting and storage remain in LibWebView. Windows
capture is not implemented yet; it will need native binary IDs, stack capture,
storage and exit-status handling.

## Limitations and symbolication

Only the crashing thread is captured. Stacks can be partial due to corruption,
missing frame pointers, JIT code, or modules loaded after handler
initialization. An alternate signal stack protects main-thread stack overflow;
stack overflow on other threads may only produce a minimal report. Early startup
crashes and other exits that bypass the handler also produce minimal
reports for helpers. Helper reports require the browser to survive long enough
to format them. Browser crashes before the handler is installed, or exits that
bypass its signal handler, may not produce a report. Disk errors can prevent
saving; they are reported to stderr.

Keep the binaries and debug symbols for distributed builds. A binary ID and object
address remain useful even when symbols were stripped from the user's install.
On macOS, use `dwarfdump --uuid <binary>` to match the report's build ID
(ignoring hyphens and case), then `atos -arch arm64 -o <binary>
<object-address>` to resolve a frame with the matching binary and dSYM. Use
`x86_64` for Intel reports. Report addresses are already unslid, so do not add
the user's ASLR slide. The report itself contains no local binary paths.

On Linux, use `readelf -n <binary>` to match the ELF build ID, then
`addr2line -f -C -e <binary> <object-address>` with matching debug symbols.

---

### Documentation DevTools (`Documentation/DevTools.md`)

# Firefox DevTools Integration

Ladybird contains a DevTools server. This document describes how to use the server and the protocol used to communicate
with the DevTools client.

## Using DevTools

The DevTools server may be enabled in the "Inspect" menu via the "Enable DevTools" menu item, or with the `--devtools`
command line flag. A banner will be displayed in Ladybird with the port on which the DevTools server is listening. This
port may be changed by providing a port to the command line flag, e.g. `--devtools=6001`. To disable DevTools, use the
menu item (which will now be titled "Disable DevTools"), or close the banner.

Once DevTools is enabled, you may use the "Open Client" button on the banner to open a Firefox DevTools client window to
inspect the current tab. Only one such window may be open at a time.

Alternatively, in Firefox, navigate to `about:debugging` and select the "Setup" tab. In the "Network Location" form,
enter the DevTools server address. In the above example with `--devtools=6001`, this will be `localhost:6001`. You will
only have to enter this information once:

<img src="Images/devtools_setup.png" alt="DevTools setup" width="675px" />

This will make the address appear on the left-side of the page. Click "Connect":

<img src="Images/devtools_connect.png" alt="DevTools connect" width="240px" />

Once connected, the listed address will now be clickable itself. Click it to be brought to a page which shows Ladybird's
tab list:

<img src="Images/devtools_tab_list.png" alt="DevTools tab list" width="675px" />

Click the "Inspect" button next to the tab you wish to debug. This will open another tab in Firefox with an inspector
panel, which you may use to view the DOM tree:

<img src="Images/devtools_dom_tree.png" alt="DevTools DOM tree" width="675px" />

## DevTools protocol

Firefox's documentation of its [DevTools protocol](https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html)
is quite incomplete, and even incorrect in some places. To aid with development, this section describes the protocol that
Ladybird has implemented. Note that this is only observed behavior - our implementation could certainly be incorrect or
incomplete in places.

The protocol itself is based on ["actors"](https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#actors).
Each packet contains a JSON object. Messages sent from the DevTools client contain a `"to"` field indicating the actor
for which the message is intended. Messages sent from the DevTools server contain a `"from"` field indicating the actor
from which the message originated.

A very important aspect of this protocol is that the client may send multiple requests to an actor at once without
waiting for a reply, to which the actor must then reply in order. Any requests that must be performed asynchronously
(such as fetching the serialized DOM tree from the WebContent process) must block the actor from sending replies for
subsequent requests.

To log communication between the DevTools server and client, enable the `DEVTOOLS_DEBUG` flag:

```bash
cmake -B Build/release -D DEVTOOLS_DEBUG=ON
```

Logged messages beginning with "<<" were sent from the server to the client. Messages beginning with ">>" were sent from
the client to the server.

### Session initialization

Once the DevTools client has connected, communication begins with a [root actor](../Libraries/LibDevTools/Actors/RootActor.cpp).
The root actor initiates the session by sending a message to the client describing itself:

```jsonc
<< {"from":"root","applicationType":"browser","traits":{"sources":false,"highlightable":true,"customHighlighters":true,"networkMonitor":false}}
```

The client then sends a connection request, to which the server replies with an empty message:

```jsonc
>> {"type":"connect","frontendVersion":"135.0","to":"root"}
<< {"from":"root"}
```

The client then asks the root actor to describe other top-level actors. We are required to provide a device actor and a
preference actor:

```jsonc
>> {"type":"getRoot","to":"root"}
<< {"from":"root","selected":0,"deviceActor":"server0-device1","preferenceActor":"server0-preference2"}
```

The device actor provides information about the device for which the server is running, such as application version and
host OS. We provide a [subset of fields](https://github.com/mozilla/gecko-dev/blob/master/devtools/shared/system.js)
that Firefox itself provides, as the other fields seem optional, and may not even make sense for Ladybird.

The client will immediately request this information after the `getRoot` reply:

```jsonc
>> {"type":"getDescription","to":"server0-device1"}
<< {"from":"server0-device1","value":{"apptype":"ladybird","name":"Ladybird","brandName":"Ladybird","version":"1.0","appbuildid":"Version 1.0","platformbuildid":"Version 1.0","platformversion":"135.0","useragent":"Mozilla/5.0 (macOS; AArch64) Ladybird/1.0","os":"macOS","arch":"AArch64"}}
```

The preference actor is used to query and update configuration options that resemble those found in Firefox's
`about:config` page. We don't implement anything concrete here. The client will request a few boolean configuration
options after the device request, to which the server will just reply with `false`:

```jsonc
>> {"type":"getBoolPref","value":"devtools.debugger.prompt-connection","to":"server0-preference2"}
<< {"from":"server0-preference2","value":false}
>> {"type":"getBoolPref","value":"browser.privatebrowsing.autostart","to":"server0-preference2"}
<< {"from":"server0-preference2","value":false}
>> {"type":"getBoolPref","value":"dom.serviceWorkers.enabled","to":"server0-preference2"}
<< {"from":"server0-preference2","value":false}
```

The client then asks for a list of add-ons, workers, and service workers, to which the server replies with an empty list:

```jsonc
>> {"type":"listTabs","to":"root"}
<< {"from":"root","addons":[]}

>> {"type":"listWorkers","to":"root"}
<< {"from":"root","workers":[]}

>> {"type":"listServiceWorkerRegistrations","to":"root"}
<< {"from":"root","registrations":[]}
```

Then client then asks for a list of processes, followed immediately by a request for the zeroth process. This zeroth
process is required, and seems to correspond to the browser's main process. We currently stub out this information:

```jsonc
>> {"type":"listProcesses","to":"root"}
>> {"type":"getProcess","id":0,"to":"root"}

<< {"from":"root","processes":[{"actor":"server0-process3","id":0,"isParent":true,"isWindowlessParent":false,"traits":{"watcher":true,"supportsReloadDescriptor":true}}]}
<< {"from":"root","processDescriptor":{"actor":"server0-process3","id":0,"isParent":true,"isWindowlessParent":false,"traits":{"watcher":true,"supportsReloadDescriptor":true}}}
```

The client then asks for a list of tabs. The server contains a [delegate interface](../Libraries/LibDevTools/DevToolsDelegate.h)
to be implemented by the WebView application. This interface includes a method to form a list open tabs. The server will
reply with this list:

```jsonc
>> {"type":"listTabs","to":"root"}
<< {"from":"root","tabs":[{"actor":"server0-tab4","title":"Ladybird","url":"https://ladybird.org/","browserId":1,"browsingContextID":1,"outerWindowID":1,"traits":{"watcher":true,"supportsReloadDescriptor":true}}]}
```

The client then asks for information about the tabs, including their favicon. The favicon is expected to be a URL, but
when provided, the DevTools client seems to hang. So we use a null value here, which Firefox itself also uses. The
server will reply with the same information provided in the `listTabs` request:

```jsonc
>> {"type":"getFavicon","to":"server0-tab4"}
<< {"from":"server0-tab4","favicon":null}

>> {"type":"getTab","browserId":1,"to":"root"}
<< {"from":"root","tab":{"actor":"server0-tab4","title":"Ladybird","url":"https://ladybird.org/","browserId":1,"browsingContextID":1,"outerWindowID":1,"traits":{"watcher":true,"supportsReloadDescriptor":true}}}
```

At this point, the session is established and Ladybird is listed on `about:debugging`.

### Inspecting a DOM tree

When the user inspects a tab, the client initiates the inspection with a request for a "watcher". Watchers are the
actors responsible for monitoring a wide variety of components. We currently only implement a "frame" watcher. The
server will create a watcher associated with the inspected tab, and reply with a set of watcher options indicating our
support of frame inspection:

```jsonc
>> {"type":"getWatcher","isServerTargetSwitchingEnabled":true,"isPopupDebuggingEnabled":false,"to":"server0-tab4"}
<< {"from":"server0-tab4","actor":"server0-watcher5","traits":{"shared_worker":false,"service_worker":false,"frame":true,"process":false,"worker":false,"resources":{"Cache":false,"console-message":false,"cookies":false,"css-change":false,"css-message":false,"css-registered-properties":false,"document-event":false,"error-message":false,"extension-storage":false,"indexed-db":false,"jstracer-state":false,"jstracer-trace":false,"last-private-context-exit":false,"local-storage":false,"network-event":false,"network-event-stacktrace":false,"platform-message":false,"reflow":false,"server-sent-event":false,"session-storage":false,"source":false,"stylesheet":false,"thread-state":false,"websocket":false}}}
```

The client then asks the server to watch the inspected tab's frame. The server must reply with multiple messages here.
The first message contains information about the inspected tab, as well as a list of other actors associated with the
watcher. We are required to have an inspector actor, a CSS properties actor, and a thread actor. We also have the optional
console actor. These actors are described below when they are requested by the client.

The second message contains a small set of information about the tab again. The third message is just an empty message,
which seems to indicate and end-of-transmission status:

```jsonc
>> {"type":"watchTargets","targetType":"frame","to":"server0-watcher5"}
<< {"from":"server0-watcher5","type":"target-available-form","target":{"actor":"server0-frame10","title":"xkcd: Scream Cipher","url":"https://xkcd.com/","browsingContextID":1,"outerWindowID":1,"isTopLevelTarget":true,"traits":{"frames":true,"isBrowsingContext":true,"logInPage":false,"navigation":true,"supportsTopLevelTargetFlag":true,"watchpoints":true},"cssPropertiesActor":"server0-css-properties6","consoleActor":"server0-console8","inspectorActor":"server0-inspector7","threadActor":"server0-thread9"}}
<< {"from":"server0-frame9","type":"frameUpdate","frames":[{"id":1,"title":"Ladybird","url":"https://ladybird.org/"}]}
<< {"from":"server0-watcher5"}
```

The client then asks for a couple of configuration actors, and sends some configuration options to those actors. These
actors are a target configuration actor and a thread configuration actor. The target configuration actor informs the
client about features supported by the target tab, such as an "offline mode". We currently stub these options to
indicate all features are not supported. The thread configuration actor is currently entirely a stub.

```
/* Detailed source-code truncated for AI context efficiency. */
```

The client then asks for a list of frames, to which the server replies with an empty message:

```jsonc
>> {"type":"listFrames","to":"server0-frame9"}
<< {"from":"server0-frame9"}
```

The client then asks for a CSS database. The server must reply with a list of every CSS property that the rendering
engine supports. The delegate interface includes a method for the application to provide this information. LibWebView
will reply with the list of properties generated by [Properties.json](../Libraries/LibWeb/CSS/Properties.json).

(For brevity, only the `font` property is included here, as the list of all properties is very large.)

```jsonc
>> {"type":"getCSSDatabase","to":"server0-css-properties6"}
<< {"from":"server0-css-properties6","properties":{"font":{"isInherited":true,"supports":[],"values":[],"subproperties":["font"]}}}
```

The client then asks the inspector actor for a few more actors in a row. The inspector actor is essentially just a
container that serves to hold other actors that actually perform inspection.

First, the client asks for a walker actor. This walker is what will actually own and traverse the DOM tree. It is at
this point that the server fetches the DOM tree from the WebContent process. The delegate interface includes a method
for the application to asynchronously provide this information. The inspector actor's message queue is blocked until we
receive the DOM tree (or encounter an error). The server will reply with information about the document element.

Next, the client asks for a page style actor and a highlighter actor. The page style actor is what will provide layout
and style information about a particular node. The highlighter actor's responsibility varies depending on the type of
highlighter that is requested. Here, the client asks for a viewport resize highlighter, which we currently do not
support.

```jsonc
>> {"type":"getWalker","options":{"showAllAnonymousContent":false},"to":"server0-inspector7"}
>> {"type":"getPageStyle","to":"server0-inspector7"}
>> {"type":"getHighlighterByType","typeName":"ViewportSizeOnResizeHighlighter","to":"server0-inspector7"}

<< {"from":"server0-inspector7","walker":{"actor":"server0-walker14","root":{"actor":"server0-node15","attrs":[],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"#document","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":false,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":true,"nodeName":"#document","nodeType":9,"nodeValue":null,"numChildren":1,"shadowRootMode":null,"traits":{}}}}
<< {"from":"server0-inspector7","pageStyle":{"actor":"server0-page-style12","traits":{"fontStyleLevel4":true,"fontWeightLevel4":true,"fontStretchLevel4":true,"fontVariations":true}}}
<< {"from":"server0-inspector7","highlighter":{"actor":"server0-highlighter13"}}
```

The client then asks for the frame's parent browsing context, to which the server currently replies with the same
context as the frame itself:

```jsonc
>> {"type":"getParentBrowsingContextID","browsingContextID":1,"to":"server0-watcher5"}
<< {"from":"server0-watcher5","browsingContextID":1}
```

The client then instructs the viewport resize highlighter actor to "show" the inspector actor, to which the server
currently replies with a nack:

```jsonc
>> {"type":"show","node":"server0-inspector7","to":"server0-highlighter13"}
<< {"from":"server0-highlighter13","value":false}
```

Then client then starts to ask for DOM node information. It begins by issuing a query selector request for the `<body>`
element, followed by requests for the children of the `<html>` element, the document element, and the `<body>` element.
The query selector indicates the actor name of the node from which to start searching, and the name of the requested
node. We do not currently create concrete actor objects for each node; rather, we just assign actor names to each node
in the DOM tree received from the WebContent process. The query selector reply includes a field to indicate that its
parent is not the parent from which the search started. Each serialized DOM node contains information such as its type,
display name, tag name, attributes, etc.

```
/* Detailed source-code truncated for AI context efficiency. */
```

The client then instructs the server to watch the root node. The server replies with information about the document
element again, followed by an empty message. We do not currently do anything more with this request.

```jsonc
>> {"type":"watchRootNode","to":"server0-walker14"}
<< {"from":"server0-walker14","type":"root-available","node":{"actor":"server0-node15","attrs":[],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"#document","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":false,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":true,"nodeName":"#document","nodeType":9,"nodeValue":null,"numChildren":1,"shadowRootMode":null,"traits":{}}}
<< {"from":"server0-walker14"}
```

At this point, the DOM tree in the DevTools client is interactable. As the user interacts with the client, the client
will send similar requests as the above to retrieve more DOM node information. The server will log an error for features
we do not yet support.

### Inspecting a DOM node

After the DOM tree becomes interactable, the client will inspect a default node on the page (typically the `<body>`
element). The client will immediately ask for the layout of the inspected node and for a layout inspector. The layout
consists of the box model metrics for the node. The layout inspector is an actor that provides e.g. grid and flexbox
information, but we currently just sub this out.

```jsonc
>> {"type":"getLayout","node":"server0-node31","autoMargins":true,"to":"server0-page-style12"}
>> {"type":"getLayoutInspector","to":"server0-walker14"}

<< {"from":"server0-page-style12","autoMargins":{},"width":1514,"height":10236.8125,"border-top-width":"0px","border-right-width":"0px","border-bottom-width":"0px","border-left-width":"0px","margin-top":"0px","margin-right":"0px","margin-bottom":"0px","margin-left":"0px","padding-top":"0px","padding-right":"0px","padding-bottom":"0px","padding-left":"0px","box-sizing":"border-box","display":"block","float":"none","line-height":"1.5","position":"static","z-index":"auto"}
<< {"from":"server0-walker14","actor":{"actor":"server0-layout-inspector568"}}
```

The client then asks for a selector for the inspected node. The server replies with the display name of the node:

```jsonc
>> {"type":"getUniqueSelector","to":"server0-node32"}
<< {"from":"server0-node32","value":"body"}
```

The client then asks for the applied style for the inspected node. This would be for the pane in the inspector tab which
allows the user to toggle and edit styles on a live DOM node. We do not yet have support for this in LibWeb, so the
server replies with an empty list:

```jsonc
>> {"type":"getApplied","node":"server0-node32","inherited":true,"matchedSelectors":true,"to":"server0-page-style12"}
<< {"from":"server0-page-style12","entries":[]}
```

The client then asks the layout inspector for the inspect node's flexbox and grid information. As noted above, we have
just stubbed this for now. The server replies with empty values:

```jsonc
>> {"type":"getCurrentFlexbox","node":"server0-node32","onlyLookAtParents":false,"to":"server0-layout-inspector568"}
>> {"type":"getGrids","rootNode":"server0-node15","to":"server0-layout-inspector568"}

<< {"from":"server0-layout-inspector568","flexbox":null}
<< {"from":"server0-layout-inspector568","grids":[]}
```

The client then asks if the inspected node's box model metrics are editable, and for the node's "offset parent". We
currently don't support such edits, and have stubbed the offset parent. The server again replies with empty values:

```jsonc
>> {"type":"isPositionEditable","node":"server0-node32","to":"server0-page-style12"}
<< {"from":"server0-page-style12","value":false}

>> {"type":"getOffsetParent","node":"server0-node32","to":"server0-walker14"}
<< {"from":"server0-walker14","node":null}
```

If the user switches the inspector to the "Computed" pane, then the client will ask for the inspected node's computed
style. The server collects the computed style from the WebContent process, and replies with that style:

(For brevity, only the `color` property is included here, as the list of all styles is very large.)

```jsonc
>> {"type":"getComputed","node":"server0-node32","markMatched":true,"onlyMatched":true,"filter":"user","to":"server0-page-style12"}
<< {"from":"server0-page-style12","computed":{"color":{"matched":true,"value":"canvastext"}}}
```

The user may select any other DOM node to inspect. As the mouse hovers over other DOM nodes, the client will ask (once)
if the server supports highlighting DOM nodes. We do support this, and the server will reply:

```jsonc
>> {"type":"supportsHighlighters","to":"server0-inspector7"}
<< {"from":"server0-inspector7","value":true}
```

The client will then ask for box model highlighter. This highlighter indicates when the WebContent process should
render an overlay over a particular node, with information about that node's box model. The server will create this
highlighter type, and reply:

```jsonc
>> {"type":"getHighlighterByType","typeName":"BoxModelHighlighter","to":"server0-inspector7"}
<< {"from":"server0-inspector7","highlighter":{"actor":"server0-highlighter569"}}
```

When the user's mouse enters a DOM node, the client will instruct the server to show the box model overlay for that node:
```jsonc
>> {"type":"show","node":"server0-node16","to":"server0-highlighter569"}
<< {"from":"server0-highlighter569","value":true}
```

When the user's mouse leaves a DOM node, the client will instruct the server to stop showing the box model overlay for
that node:

```jsonc
>> {"type":"hide","to":"server0-highlighter569"}
<< {"from":"server0-highlighter569"}
```

The above highlighter requests are repeated as the user's mouse is moved between nodes. When the user clicks on a DOM
node, that node then becomes the inspected node, and the client will ask for the box model / computed style for that
node.

### JavaScript console

During the initialization of the watcher actor, the server advertises to the client that it supports the console. The
client will then ask the server to begin some console-related listeners, which we do not yet support:

```jsonc
>> {"type":"startListeners","listeners":["DocumentEvents"],"to":"server0-console8"}
<< {"from":"server0-console8","error":"unrecognizedPacketType","message":"Unrecognized packet type: 'startListeners'"}

>> {"type":"startListeners","listeners":["PageError"],"to":"server0-console8"}
<< {"from":"server0-console8","error":"unrecognizedPacketType","message":"Unrecognized packet type: 'startListeners'"}
```

When the user enters text into the console, the client will ask the server for autocompletions and eager evaluation of
the input text. We do not yet support either of these features, so the server replies with some stubbed information:

```jsonc
>> {"type":"autocomplete","text":"1+","frameActor":null,"authorizedEvaluations":[],"expressionVars":[],"to":"server0-console8"}
>> {"type":"evaluateJSAsync","text":"1+","eager":true,"to":"server0-console8"}

<< {"from":"server0-console8","matches":[],"matchProp":""}
<< {"from":"server0-console8","resultID":"server0-console8-1"}
```

When the script is finally executed by the user, the client will ask for a non-eager evaluation of the script. At this
point, the server will send the script to the WebContent process for evaluation. Its result is serialized to a JSON
value, which is called a [grip](https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#grips). We
currently only support serialization of plain types; objects are serialized to a string for now. Since the request is
async, the server replies immediately with a pending result ID for the script. The actual result associated with that
ID is then sent once the script is complete:

```jsonc
>> {"type":"evaluateJSAsync","text":"1+1","disableBreaks":false,"to":"server0-console8"}
<< {"from":"server0-console8","resultID":"server0-console8-3"}
<< {"from":"server0-console8","type":"evaluationResult","timestamp":1740417141889,"resultID":"server0-console8-3","input":"1+1","result":2,"exception":null,"exceptionMessage":null,"helperResult":null}
```

Anytime console messages are evaluated by the page (`console.log`, `console.warn`, etc.), the server will independently
notify the client without any prompt. The same grip serialization used for console results is used here.

```jsonc
<< {"from":"server0-frame10","type":"resources-available-array","array":[["console-message",[{"level":"log","filename":"<eval>","line_number":1,"column_number":1,"time_stamp":1741096175644,"arguments":["hello!"]}]]]}
```

### Session termination

When the user disconnects from the DevTools server, the client will send a few cleanup messages. We currently do not
implement the first few that are sent:

```jsonc
>> {"type":"unwatchTargets","targetType":"frame","options":{},"to":"server0-watcher10"}
<< {"from":"server0-watcher10","error":"unrecognizedPacketType","message":"Unrecognized packet type: 'unwatchTargets'"}

>> {"type":"finalize","to":"server0-highlighter18"}
>> {"type":"finalize","to":"server0-highlighter573"}
<< {"from":"server0-highlighter18","error":"unrecognizedPacketType","message":"Unrecognized packet type: 'finalize'"}
<< {"from":"server0-highlighter573","error":"unrecognizedPacketType","message":"Unrecognized packet type: 'finalize'"}
```

We do implement the request to detach the frame, at which point we clear any inspected DOM node information from the
WebContent process:

```jsonc
>> {"type":"detach","to":"server0-frame14"}
<< {"from":"server0-frame14"}
```

## Inspecting the protocol with Firefox

When developing a DevTools feature, it's invaluable to see how the DevTools protocol is used by Firefox itself. First,
enable remote debugging in your Firefox profile's `about:config` page:

```
devtools.chrome.enabled = true
devtools.debugger.remote-enabled = true
```

You may wish to create a new profile for this, rather than modifying your default profile. To create a new profile from
the command line:

```bash
firefox -CreateProfile YOUR_PROFILE_NAME
```

If you are using your default profile, then close all Firefox windows. Restart Firefox via the command line, specifying
the DevTools server port:

```bash
firefox --start-debugger-server 6000
```

Or if you created a new profile, you do not need to close all Firefox windows. Instead, launch a new instance with the
`--new-instance` flag, and specify your new profile's path with the `--profile` flag:

```bash
firefox --start-debugger-server 6000 --new-instance --profile ~/snap/firefox/common/.mozilla/firefox/z20dtqwq.YOUR_PROFILE_NAME
```

After Firefox is running, we can now snoop the specified port. The Servo team has written a nice script using `tshark`
to listen on this port and pretty-print the JSON packets. You will have to install `tshark` before using the script.

https://github.com/servo/servo/blob/main/etc/devtools_parser.py

You may run into cases where the script does not log all captured packets. Since this occurs fairly regularly, it is
recommended to run this script in two phases. First, use the script to snoop on the DevTools port and write out a packet
capture. You may need to run this script with `sudo`.

```bash
./etc/devtools_parser.py -s /tmp/devtools.pcap -p 6000
```

(NOTE: It is important to only run this script *after* Firefox has started. Running the script sooner will not intercept
any DevTools packets.)

Now, use `about:debugging` to connect to the Firefox DevTools server and inspect a tab. Use the DevTools client to
exercise the feature you wish to implement.

Once complete, use `ctrl+c` on Servo's snooping script to complete the capture. The script will try to print all packets
at this point, but due to the aforementioned incomplete logging, it is likely that this will not print everything. So we
can use the script again to inspect the now-existing .pcap, and it should be able to print everything this way:

```bash
./etc/devtools_parser.py --use /tmp/devtools.pcap -p 6000
```

## Known issues

1. We occasionally fail to inspect a tab. There isn't any information logged by client or server, other than the client
   indicating the session was disconnected. The following is the entire communication log from an instance of this error:

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Documentation EventLoop (`Documentation/EventLoop.md`)

# LibCore's `EventLoop` system

This is not about web event loops, which are a separate LibWeb concept.

For handling application tasks concurrently on one thread, LibCore provides the `EventLoop` system. The event loop is found in many systems, and it can be briefly summarized as an in-process loop, running all the time, which processes incoming events from signals, notifiers, file watchers, timers etc. by running associated callbacks. The event loop relies on the fact that the callbacks will eventually return control to it, so that other events can be processed. In that way, it's like a cooperative multitasking scheduler in userspace.

LibCore's event loop system is mainly used with graphical applications, and LibGUI and IPC are deeply integrated into it. If you are working on a command-line application, chances are you don't have to deal with the event loop, though that's not a universal rule.

## How it works

When an event loop runs, it usually sits in `exec()` repeatedly `pump()`ing the loop. `exec()` waits for an event to happen and then runs all the callbacks associated with that event. After that, it goes back to sleep and waits for more events to happen.

On a lower level, this is what happens:

-   On `exec()`, the event loop enters the event loop stack. Then, the event loop is pumped repeatedly.
-   Each `pump()` first puts the event loop to sleep with `wait_for_event()`, then handles all events.
-   `wait_for_event()` uses the `select(2)` function to wait until one of the prepared file descriptors becomes writeable or readable, or until a timeout expires. This means that while an event loop has nothing to do, the kernel keeps the thread at sleep. That's also the reason you'll see most GUI applications being listed with a "Selecting" state in SystemMonitor. After `select(2)` returns, it immediately dispatches signals to all signal handlers. Other new events (expired timers, file notifications) are put into the event queue.
    -   The specific file descriptors are the file notifiers the event loop has registered (this is the original purpose of `select(2)`) as well as the wake pipe. This pipe is responsible for two things: Once a POSIX signal is received which the event loop has a handler for (this might happen at any time, on any thread), `handle_signal()` is entered, which writes the handler number to the pipe. But second, the `wake()` function just writes 0 (not a valid signal number) to the wake pipe, which is used to trigger an event loop wake from other threads.
    -   The timeout of the `select(2)` call might be infinite if there's no time-based wake condition. If there are timers, however, the `select(2)` timeout is the minimum of all timer timeouts. If there are available events before `select(2)` is even called, the timeout is zero, leading to a `select(2)` which immediately returns.
-   Event handling from the event queue happens by invoking associated callbacks. Remember that the event queue has two primary sources of events: `deferred_invoke()` adds an event immediately and signals a wake, while after returning from `select(2)` new events are created based on timers and notifications.
-   If the event loop's exit was requested (checked while handling events), it returns the pending events to the queue so that the next-lower event loop can handle them. The assumption is that that event loop will resume running shortly. The return value of `exec()` has comparable purpose to a process return code, which is why the pattern `return app.exec()` is so common in GUI applications (`GUI::Application::exec()` just runs an event loop under the hood). In any case, even if the event loop's exit doesn't mean program exit, it is removed from the event loop stack.

All of this applies per thread. The event loops share some global state (notifiers, timers, event loop stack etc.) which are thread-local variables. The convenience getter `EventLoop::current()` relies on this to give you the topmost event loop on the thread you're calling from.

The **event loop stack** is mainly used for nesting GUI windows. Each window adds another event loop onto the stack, and remaining events are added to the lower event loop's queue when it exits. The purpose behind this system is that `GUI::Window`s and other systems which provide their own event loop can just create and run this class depending on global variables without interfering with other event loops.

## What it can do

An event loop handles several kinds of events:

-   POSIX signals can be registered with `EventLoop::register_signal()`. This means that the event loop of the calling thread registers the specified POSIX signal and callback with the kernel, and you can be sure that the signal handler will run as a normal event without the weirdness that comes with POSIX signal handlers (such as unspecified thread).
-   EventLoop::post_event() allows calling code to fire an event targeting a specific Core::EventReceiver the next time the event loop is pumped.
-   Similarly, an arbitrary callback can be called on the next event loop iteration with `EventLoop::deferred_invoke()`.
-   Timer events, i.e. events that fire after a certain timeout, possibly repeatedly, can be created with `EventLoop::register_timer` and `Object::start_timer()`. A more user-friendly version is the `Core::Timer` utility class which does the same thing and allows you to attach any callback to the timer.
-   For when a "file" becomes readable or writeable, the utility class `Core::Notifier` interfaces with the event loop system to handle exactly that.

Note that all events are registered, and therefore happen, on the event loop of the thread that created the event.

## Dos and Don'ts with event loops

-   DO NOT store an event loop in a global variable. Because the event loop itself relies on global variable initialization, UBSAN will catch an initialization order fiasco. DO create your main event loop in `main` and pass it to classes which need it.
-   DO NOT access the current event loop if you don't know on which thread you are running. If there is no event loop on your thread, the program will crash. DO receive the specific event loop you need to talk to as an initializer variable.
-   DO NOT `pump()` and/or `exec()` the event loop of another thread. While handling events is fine, going to sleep and waking up relies on thread-local variables. DO signal events to event loops on other threads.

---

### Documentation FAQ (`Documentation/FAQ.md`)

# Frequently Asked Questions about Ladybird

## What does 'Independent' mean, if you're including third party dependencies?

Independent means:

- We implement the web platform standards ourselves: Ladybird is not a Blink/Chromium shell, not a WebKit port, not a Firefox fork.
- We don't take money from anyone with strings attached

## Is [some particular site] usable in Ladybird?

Ladybird is pre-alpha software you have to build from the sources yourself in order to even try. That said, it’s getting better day-by-day at handling some of the most widely-used sites on the web. So, you may find that a given site among the sites you visit daily is already usable in Ladybird. Or you may not. The only way you can really know at this point is to follow the [build instructions](BuildInstructionsLadybird.md) to build Ladybird and try it yourself.

## Windows support when?

There are very few maintainers working on Windows support. As such, maintaining a native Windows port would be
a lot of effort that distracts from building out the web platform standards in a reasonable amount of time.

After we have a solid foundation, we may consider a Windows port, but it's not a priority. In the meantime, Windows
developers can use other tools such as WSL2 to work on Ladybird.

## Will Ladybird support `$THING`?

Eventually, probably, if there's a Web Spec for it!

## When will you implement `$THING`?

There's no concrete roadmap of individual features yet. You can always raise an issue so we can keep track, if one does
not already exist!

### Will you have a built in ad blocker?

Ladybird supports user-configured content blocker lists, and we already have open issues about supporting uBlock via extensions.

## Is there a release roadmap?

- 2026: alpha release (daily driver for developers and early adopters) for Linux and macOS.
- 2027: beta release; downloadable app for Linux and macOS.
- 2028: stable release for general use
    

## Well, how do I run this thing then?

Simple, my friend! Just refer to the [build instructions](BuildInstructionsLadybird.md).

## I did a `git pull` and now the build is broken! What do I do?

If it builds on CI, it should build for you too. You may need to rebuild the toolchain. If that doesn't help, try it with a clean repo.

If you can't figure out what to do, ask in the `#build-problems` channel on Discord.

## Where did Ladybird come from?

For full details, see the [Ladybird: A new cross-platform browser project](https://awesomekling.substack.com/p/ladybird-a-new-cross-platform-browser-project) announcement from 12 September 2022.

Here’s a short timeline:

- 2019 June: Work on what eventually became Ladybird started as _LibHTML_ — the beginnings of an HTML viewer for [SerenityOS](https://github.com/SerenityOS/serenity) — with a commit titled [“LibHTML: Start working on a simple HTML library”](https://github.com/SerenityOS/serenity/commit/a67e8238389), and with this commit description:

  > _I'd like to have rich text, and we might as well use HTML for that. :^)_

  LibHTML eventually became [LibWeb](https://github.com/LadybirdBrowser/ladybird/tree/master/Libraries/LibWeb) — which in turn eventually grew into being the core part of the browser engine and browser to which, on 4 July 2022, [the name _Ladybird_ was given](https://www.youtube.com/watch?v=X38MTKHt3_I&t=29s).

- 2022 July: Renamed _Ladybird_ by Andreas in [“Let's make a Linux GUI for the SerenityOS browser”](https://youtu.be/X38MTKHt3_I) live-coding video.
- 2022 Sept: Spun off from SerenityOS to separate project: [“A new cross-platform browser project”](https://awesomekling.substack.com/p/ladybird-a-new-cross-platform-browser-project) announcement.
- 2024 June: [“I'm forking Ladybird and stepping down as SerenityOS BDFL”](https://awesomekling.substack.com/p/forking-ladybird-and-stepping-down-serenityos) announcement from Andreas.
- 2024 July: [Ladybird Browser Initiative](https://ladybird.org/posts/announcement/) launched by Andreas and GitHub co-founder [defunkt](https://twitter.com/defunkt) (Chris Wanstrath).

## What makes Ladybird/[Ladybird Browser Initiative](https://ladybird.org/) different?

- Fully independent: Written from scratch, using no code from any other browser engine.
- Singular focus: Doing only one single thing: building a new browser engine and browser.
- No monetization: Will never take funding from default search deals or any other forms of user monetization, ever.

## Are there video/audio announcements and interviews about the start of the Ladybird Browser Initiative?

- Ladybird Browser Initiative [announcement video](https://www.youtube.com/watch?v=k9edTqPMX_k) from defunkt explaining the project _raison dʼêtre_ + goals (July 2024).
- [Why we need Ladybird](https://changelog.com/podcast/604#t=5:08): _Changelog_ podcast interview with Andreas and defunkt (August 2024); [transcript](https://changelog.com/podcast/604#transcript); [chapters](https://changelog.com/podcast/604#chapters).
- [Eron Wolf announcement grant of $200K](https://youtu.be/p6k9qcRpW_k) from [FUTO](https://www.futo.org/about/what-is-futo/) to the project (August 2024).
- [Eron Wolf interview with Andreas](https://youtu.be/4xhaAAcKLtI) (August 2024).

## Can you describe some of the project goals and its culture?

- Eventually give everybody the choice of a whole new browser they can use for their daily browsing.
- Prove it is in fact possible to build a completely new browser, by implementing from the WHATWG/W3C/etc. specs.
- Have a lot of real fun together actually doing it.
- Prove that developing an engine doesn’t take hundreds of engineers — and not anything close to even just a hundred.
- Browser engineering: Further help de-mystify it and make it a standard thing to learn (hat tip: https://browser.engineering/).
- Using project Discord server for communication [discord.gg/nvfjVJ4Svh](https://discord.gg/nvfjVJ4Svh).
- Using [one GitHub repo](https://github.com/LadybirdBrowser/ladybird) for issues, CI, and test automation.

## What are some of the project coding conventions? And do you have any activity metrics?

- Implement web-platform features exactly according to the actual steps in spec algorithms.
- Abundant code comments with verbatim spec text copy/pasted in — showing exactly what’s being implemented.
- Additional _`“AD-HOC:”`_ comment convention to mark code that doesn’t map to any spec requirements.
- Class/file names tend to closely match actual current spec terms; e.g., `Navigable.h`, `Transferable.h`.
- [“critically reading standards and reporting what is wrong”](https://matrixlogs.bakkot.com/WHATWG/2024-08-23#L10)
- Project activity relative rankings: https://git-pulse.github.io/snapshots/?project=LadybirdBrowser_ladybird

## Do you have some general details about the code and basic architecture?

- Some use of third-party libraries (e.g., Harfbuzz, Skia, [simdutf](https://github.com/simdutf/simdutf), libcurl).
- Performance optimizing is not yet a super-high priority (but performance-boosting changes are regularly getting made).
- Code size:
  - Roughly same size (number of lines of code) as Servo.
  - About 1/15th as many lines of C++ code as WebKit.
  - About 1/20th as many lines as C++ code Gecko.
  - About 1/50th as many lines as C++ code Chromium.
- Level of standards support: [wpt.fyi/results?product=ladybird](https://wpt.fyi/results/?product=ladybird) has current test results for all WPT tests.
- [LadybirdBrowser/ladybird#features](https://github.com/LadybirdBrowser/ladybird/#features):
  - UI process, ImageDecoder process, RequestServer process, WebContent processes.
  - LibWeb: core web-rendering engine (HTML, CSS, Events, DOM, APIs).
  - LibJS: JavaScript engine written from scratch (currently JIT-less).
  - LibWasm: WebAssembly implementation written from scratch.
  - [AK](https://github.com/LadybirdBrowser/ladybird/tree/master/AK): Ladybird standard library/abstractions: asserts, smart pointers, strings, numbers (e.g., [fast_float](https://github.com/fastfloat/fast_float) impl.), more…

## What about funding?

- Funded _entirely_ through donations and sponsorships.
- https://donorbox.org/ladybird – donations of any amount: $10, $50, $100, etc.
- [Sponsorship opportunities](https://ladybird.org/#sponsors): Platinum $100,000 • Gold $50,000 • Silver $10,000 • Bronze $5,000 • Copper $1,000.
- Ladybird Browser Initiative announced/seeded with [1 million dollar donation](https://twitter.com/defunkt/status/1807779408092234134) from defunkt and his family.

## Do you have some places where I can watch videos related to the project?

- Ladybird [YouTube channel](https://www.youtube.com/@LadybirdBrowser): monthly Ladybird project updates from Andreas.
- Andreas’ [YouTube channel](https://www.youtube.com/@awesomekling): 1000+ videos from 6+ years; incl. “car talk” + OS/browser “hacking” (live-coding) videos.

## Is there some related background to help me understand what a browser _engine_ is and why it’s important?

- [Understanding the role of browser engines](https://assets.publishing.service.gov.uk/media/61b86737e90e07043c35f5be/Appendix_F_-_Understanding_the_role_of_browser_engines.pdf) (UK Competition and Markets Authority [Mobile ecosystems market study](https://www.gov.uk/cma-cases/mobile-ecosystems-market-study#interim-report)).

---

### Documentation GettingStartedContributing (`Documentation/GettingStartedContributing.md`)

# Getting involved with Ladybird

Welcome to the Ladybird web browser project!

This document is a beginner-friendly guide to getting familiar with Ladybird and helping the project through testing, bug reports, reductions, standards discussion, design discussion, security reports, and technical feedback.

## Getting familiar with the project

Ladybird is a large project making use of many homegrown and third-party libraries, written primarily in C++.

It is recommended you read the README and FAQs in case they already answer any questions you have:

* [README](/README.md)
* [FAQ](FAQ.md)
* [FAQ on the Ladybird website](https://ladybird.org/#faq)

The [Discord server](https://discord.gg/nvfjVJ4Svh) is the preferred way to get in contact with the maintainers and community.

## Getting familiar with browser engineering in general

If you’ve never worked on browser-engine code before, and you’re not sure where to begin — one great place to get started is by reading the book [Web Browser Engineering](https://browser.engineering/). It explains how browser engines in general work, and how they’re built — by walking you through real code for actually building all the parts of a basic but complete browser engine (networking code, HTML parsing, layout engine, JavaScript handling, and more), in a couple thousand lines of Python.

## Building the code

Ladybird must be built from source during this pre-alpha stage of development, and currently natively supports Linux and macOS; running it on Windows requires WSL.

Carefully follow the steps outlined in the [build instructions](BuildInstructionsLadybird.md). If you are facing issues, consult the [troubleshooting guide](Troubleshooting.md) and the #build-problems channel on Discord.

## Finding bugs and other issues

Here are some of the ways you can find an issue in Ladybird:

* By checking the [issue tracker](https://github.com/LadybirdBrowser/ladybird/issues).
* Manually, by using the browser as you normally would.
* By finding failing WPT tests on [WPT.fyi](https://wpt.fyi/results/?label=master&product=ladybird). You do not need to submit issue reports for individual tests.
* By finding WPT tests on [WPT.fyi](https://wpt.fyi/results/?label=master&product=ladybird) that are [timing out in Ladybird](https://wpt.fyi/results/?product=ladybird&q=status%3Atimeout). For a real-world walk-through of doing that from start to finish with an actual timing-out-in-Ladybird test case, see the [“Fixing a WPT timeout in Window.postMessage()”](https://www.youtube.com/watch?v=X4S9afzRTXs) “browser hacking” video.
* By using a profiling tool such as [Callgrind](https://valgrind.org/docs/manual/cl-manual.html) to find code that can be improved.
* By looking for [`TODO`](https://github.com/search?q=repo%3ALadybirdBrowser%2Fladybird%20%22%2F%2F%20TODO%22&type=code) and [`FIXME`](https://github.com/search?q=repo%3ALadybirdBrowser%2Fladybird+%22%2F%2F+FIXME%22&type=code) comments in the codebase.

If you’re not necessarily fluent in C++, beginning by troubleshooting WPT tests may be the very best way to get started helping the project — especially if you _do_ already have some proficiency with frontend JavaScript code.

That’s because without even knowing any C++ at all, you can still — by working just with the JavaScript code in the WPT test source — get a long way toward isolating the cause of a particular WPT test failure or timeout. That alone can be a very big help to maintainers and other people investigating the related C++ code.

## Submitting an issue

If you have found an issue that is not already in the [issue tracker](https://github.com/LadybirdBrowser/ladybird/issues), you may submit it. Do not submit general questions about the project, please use the Discord server instead.

Read the [project participation guidelines](/CONTRIBUTING.md), in particular the Issue policy and Human language policy. It is recommended you reduce the website to the most minimal amount of HTML/CSS/JS which still results in the error (if applicable), and provide the result expected from other browsers vs Ladybird. Read the [detailed issue-reporting guidelines](/ISSUES.md) for the exact steps to do so.

## Learning the codebase

### Getting familiar with C++

Ladybird requires at least a basic knowledge of C++. Consult a tutorial website like [Learn C++](https://www.learncpp.com/) and online references if you need help.

### Getting familiar with the codebase

Ladybird makes use of the included AK library instead of the C++ STL, and employs a specific coding style based around it. Unfortunately most AK and internal library facilities are not documented; you may need to look for the header files, and examples of usage in existing code.

Developer documentation:

* [Coding style](CodingStyle.md)
* [Coding patterns](Patterns.md)
* [Smart pointers](SmartPointers.md)
* [String formatting](StringFormatting.md)

---

### Documentation LibWebFromLoadingToPainting (`Documentation/LibWebFromLoadingToPainting.md`)

## LibWeb: From loading to painting

**NOTE: This document is a work in progress!**

FIXME: Add more information about everything.

FIXME: Add lots of links to relevant specs, etc.

### Resource loading

We make an IPC call to RequestServer, asking it to start loading the URL we've been asked to load.

### HTML parsing

The job of the HTML parser is to take an input stream, possibly determine its encoding, and then parse it into a DOM tree. For historical reasons, this is unusually complicated.

LibWeb implements the HTML tokenization and parsing algorithms as laid out in the HTML specification. While the spec is detailed, it's also highly idiosyncratic. The tokenizer and parser reach into each other and update the other's state in certain situations.

Due to the `document.write()` API, the parser also allows programmatic injection of new input into the parser from JavaScript. The document being parsed may run JavaScript, and this JavaScript may indirectly interact with the parser by feeding it override inputs.

### CSS parsing

* CSS parser
* Builds the CSSOM
* Values with variables can't be resolved until cascade, kept as "unresolved" values

### JS parsing & execution

* JS parser
* Builds JavaScript AST
* Interpreter runs AST
* Garbage collector (basic stop-the-world mark&sweep)

### Style computation

This step is concerned with finding the set of CSS values that apply to a given DOM element.

#### Selector matching

A CSS selector is represented by the CSS::Selector class.

Given the following selector:

```css
    #foo .bar.baz > img
```

We get the following C++ object tree:

```
* CSS::Selector
  * CSS::CompoundSelector (combinator: ImmediateChild)
    * CSS::SimpleSelector (type: TagName, value: img)
  * CSS::CompoundSelector (combinator: Descendant)
    * CSS::SimpleSelector (type: Class, value: bar)
    * CSS::SimpleSelector (type: Class, value: baz)
  * CSS::CompoundSelector (combinator: None)
    * CSS::SimpleSelector (type: ID)
```

Selectors are evaluated right-to-left, and we're looking for the first opportunity to reject it.

##### Optimization notes

We try to minimize the number of selectors that have to be evaluated for each DOM element.
The main optimization is a cache in StyleComputer that divides style rules into buckets based on what their rightmost complex selector must match. For example, a selector that can only match an element with the class "foo" will only ever be evaluated against elements that currently have the class "foo".

#### Cascading to the final values

The C in CSS is for "cascading" and "the cascade" refers to the process where all the CSS declarations that should apply to a DOM element are evaluated in order. The order is determined by a number of factors, such as selector specificity, rule order within the style sheet. There's also per-property consideration of the `!important` annotation, which allows authors to override the normal cascade order.

* To compute the "computed style" for an element...
* Perform the CSS cascade to determine final property values
* Interpolate variables
* Absolutize, etc

We separate CSS rules by their cascade origin. The two origins we're concerned with are "user-agent" and "author". If we supported custom user style sheets (we don't), they'd need a separate cascade origin as well.

The cascade origin determines the processing order for rules. The "user-agent" style is the least important, so it gets processed first. Then author style is added on top of that.

Note: the user-agent style is a built-in CSS style sheet that lives in the LibWeb source code [here](https://github.com/LadybirdBrowser/ladybird/blob/master/Libraries/LibWeb/CSS/Default.css).

The end product of style computation is a fully populated StyleProperties object. It has a StyleValue for each CSS::PropertyID. In spec parlance, these are the *computed* values. (Note that these are not the same as you get from `getComputedStyle()`, that API returns the *resolved* values.)

#### Resolving CSS custom properties ("variables")

Custom properties are resolved during the cascade. This is the earliest possible time where this can be done, as the value of a given variable depends on the result of the cascade.

### Building the layout tree

To prepare for layout, we combine the DOM with the results of style computation to create a new tree: the layout tree.
This is essentially the "box tree" in the CSS specifications, with some important differences that allow us to make better use of the C++ type system.

There isn't a 1:1 mapping between DOM nodes and layout nodes. Elements with `display:none` style don't generate a layout node at all.

A number of tree fix-ups are also performed:

- To maintain the invariant that block containers only have all block-level children or all inline-level children, inline-level boxes with block-level siblings are wrapped in anonymous wrapper boxes.
- If an individual table component box is found outside the context of a full table, a set of anonymous table boxes are generated to compensate and ensure that there's always a fully-formed table. (FIXME: This is currently buggy..)
- For list item boxes (`display: list-item`), a box representing the marker is inserted if needed.

### Layout

Layout starts at the ICB (initial containing block), which is the layout node that corresponds to the DOM document node.
CSS says that the ICB should be the size of the viewport, so the very first thing layout does is assign these dimensions.

Layout works through a mechanism call formatting contexts. CSS defines a number of different formatting contexts:
- Block Formatting Context (BFC)
- Inline Formatting Context (IFC)
- Table Formatting Context (TFC)
- Flex Formatting Context (FFC)

In LibWeb, to simplify the layout model, we also define our own SVGFormattingContext for embedded SVG content. This isn't part of the SVG specification, but simplifies the implementation.

Layout is fundamentally recursive. We start at the ICB, which always creates a BFC.

#### Block-level layout

BFC works by laying out its children, one by one in tree order. If its children are block level, they are laid out along the block axis of the BFC's root box. Block-axis margins between adjacent boxes are collapsed.

Floating boxes (CSS `float:left` or `float:right`) are interesting. First, we compute where they would have ended up if they weren't floating. Then we push them towards the left or right edge as requested. If they collide with the edge of another floating box before getting to the edge, they are stacked next to that box, forming a sort of "float line". BFC keeps track of floating boxes on both sides.

#### Inline-level layout

If its children are inline level, BFC creates an IFC to handle them. Unlike other formatting contexts, IFC can't work alone; it always works together with its parent BFC.

The job of IFC is to generate line boxes. Line boxes are essentially a sequence of inline content fragments, laid out along the inline axis. Line boxes are stored in the IFC's containing block box.

There are three main classes involved in inline-level layout:

- InlineFormattingContext (IFC)
- LineBuilder
- InlineLevelIterator

IFC drives the high-level loop by creating a LineBuilder and an InlineLevelIterator. It then traverses the inline-level content within the IFC containing block, one item at a time, by calling InlineLevelIterator::next().

The items produced by InlineLevelIterator are then passed along to LineBuilder, which adds them to the line box we're currently building.
When a line box is filled up, we insert a break and begin a new box after it.

We always keep track of how much space is available on the current line. When starting a new line, this is reset by computing the width of the IFC's containing block and then subtracting the size occupied by floating boxes on both sides. We get this information by querying the parent BFC about floating boxes intersecting the Y coordinate of the new line.

The result of performing a layout is a LayoutState object. This object contains the CSS "used values" (final metrics, including line boxes) for all box that were in scope of the layout. The LayoutState can either be committed (via `commit()`) or simply discarded. This allows us to perform non-destructive (or "immutable") layouts if we're only interested in measuring something.

### Paintable and the paint tree

When layout is finished, we take all the final metrics for each box and generate a new tree: the paint tree. The paint tree hangs off of the layout tree, and you can reach the corresponding paintable for a given layout node via `Layout::Node::paintable()`. There's also a convenience accessor in the DOM: `DOM::Node::paintable()`.

Unlike Layout::Node (which is basically a box tree node with associated style), Paintable is an object with fully finalized metrics. It has two jobs, painting and hit testing.

Note that not every layout node will have a paintable. If a layout node cannot possibly be visible, it may not require a paintable.

Every paintable has a corresponding layout node, and painting code will reach into the layout node for some information, to avoid having it in multiple places.

### Stacking contexts

There's one final thing we have to do before we can paint: we have to create stacking contexts.

Stacking contexts are a 3-dimensional model of layers (stacking contexts) that places content along a Z axis. This is what the CSS `z-index` property is for.

The set of rules for what becomes a stacking context are somewhat intricate, but the important thing is that we create a new tree: the stacking context tree.

The stacking context tree is rooted at the ICB, and can have zero or more descendants. Each descendant stacking context has a corresponding layout that it's attached to.

### Painting

Painting follows the order specified in the CSS2 appendix E. The rules are quite involved, but two main things to know about:

- Painting is driven through stacking contexts, which are painted back-to-front (stacking context tree order)
- Painting is performed in *phases*. For each stacking context, we paint in order: backgrounds & borders, floats, backgrounds & borders for inline and replaced content, foreground (text), focus outlines and overlays.

---

### Documentation LibWebPatterns (`Documentation/LibWebPatterns.md`)

# LibWeb Code Style & Patterns

This document aims to describe agreed upon code style and patterns used across LibWeb.

## Directory Structure

Generally we use a subdirectory, and thus C++ namespace, for each individual spec. For example XHR
(`xhr.spec.whatwg.org`):

-   Code lives in `LibWeb/XHR/`
-   Uses the C++ namespace `Web::XHR`

If necessary, code can also be grouped into sub-subdirectories (for example `HTML/Scripting/`).

Sometimes a spec document affects several areas of the web platform. An example of this is CSSOM,
which of course contains features belonging in `LibWeb/CSS/` / `Web::CSS`, but at the same time
implements additions to `Window` from the HTML spec. Use best judgement in those cases.

## Error Handling

The following error types are commonly used in LibWeb, each having a different purpose:

### `AK::ErrorOr<T>`

This error type is generally only used to propagate OOM errors from AK and other general libraries.
It should not be used to propagate any other kinds of errors, even though this is supported and used
in other parts of the system. For LibWeb, use any of the error types below.

This should be propagated as far as possible before being turned into a JS error object (via the
`TRY_OR_THROW_OOM` macro). This avoids a situation where almost everything returns a generic
`WebIDL::ExceptionOr<T>`.

### `Web::WebIDL::ExceptionOr<T>`

This is the most common and at the same time most broad error type in LibWeb. Internally it stores a
variant of supported errors:

-   `SimpleException`
-   `GC::Ref<DOMException>`
-   `JS::Completion` (from `JS::ThrowCompletionOr<T>`, assumed to be of `Type::Throw`)

Use this error type for anything that needs to interact with the JS bindings, which will generally
know how to turn any of the internally supported errors into JS objects.

### `Web::WebIDL::SimpleException`

This is a thin wrapper around various built-in errors from ECMAScript:

-   `EvalError`
-   `RangeError`
-   `ReferenceError`
-   `TypeError`
-   `URIError`

Instead of constructing one of these directly, create a `SimpleException` with the appropriate type
and message instead whenever required by a web spec. These will be converted into actual JS objects
in the bindings layer.

> **Note** Relevant WebIDL documentation: https://webidl.spec.whatwg.org/#dfn-simple-exception

### `Web::WebIDL::DOMException`

This is an error type from the WebIDL spec specifically for web AOs where none of the JS built-in
error types are sufficient. Like `SimpleException`, use when indicated by a web spec.

> **Note** Relevant WebIDL documentation: https://webidl.spec.whatwg.org/#idl-DOMException

### `JS::ThrowCompletionOr<T>`

This is an error type from LibJS, which uses "completions" to propagate errors and other types of AO
results. Don't use this in LibWeb unless absolutely necessary, e.g. when overriding a `JS::Object`
virtual method that returns this type.

At the call site, these should be wrapped in a `ExceptionOr<T>` as soon as possible for further
propagation.

> **Note** Relevant ECMAScript documentation:
> https://tc39.es/ecma262/#sec-completion-record-specification-type

## Comments

As in LibJS, **all** functions that represent an operation or JS function from a web specification
must have:

-   A spec link, above the function definition:

    ```cpp
    // https://fetch.spec.whatwg.org/#concept-fetch
    WebIDL::ExceptionOr<GC::Ref<Infrastructure::FetchController>> fetch(JS::Realm& realm, Infrastructure::Request& request, Infrastructure::FetchAlgorithms const& algorithms, UseParallelQueue use_parallel_queue)
    {
        // ...
    }
    ```

-   Comments for each individual step of the operation:

    ```cpp
    // 1. Assert: request’s mode is "navigate" or processEarlyHintsResponse is null.
    VERIFY(request.mode() == Infrastructure::Request::Mode::Navigate || !algorithms.process_early_hints_response().has_value());

    // 2. Let taskDestination be null.
    GC::Ptr<JS::Object> task_destination;

    // ...
    ```

    -   If a step cannot be implemented at the time, prepend it with a `FIXME`
    -   Add a blank line between code and the next comment

-   Optimizations (e.g. fast paths) should be marked as such with an `// OPTIMIZATION:` comment
    explaining the reasoning

-   When adding non-standard code for a feature that is otherwise well-specified, it should be
    marked as such. This does not universally apply as certain areas (layout and painting, for
    instance) are only broadly spec'd

-   If the spec has additional prose before or after its algorithm steps, that doesn't need to be
    copied into the code

## JS Interfaces

### IDL

Try to copy IDL verbatim, only making changes where necessary (IDL parser shortcomings, non-standard
extended attributes).

This includes not reordering functions, changing parameter names, etc.

The major difference to the spec is that we use four spaces for indentation, like for any other code
(not two).

### C++ Naming

Try to stick with the interface's exact name as much as possible for the class and file names (no
`Object` suffixes like in LibJS, for example). When there's a name clash, try to introduce a nested
namespace, e.g. `Fetch::Request` vs `Fetch::Infrastructure::Request`.

### File placement

The `.cpp`, `.h`, and `.idl` files for a given interface should all be in the same directory, unless
the implementation is hand-written when it cannot be generated from IDL. In those cases, no IDL file
is present and code should be placed in `Bindings/`.

---

### Documentation MediaPipelineDesign (`Documentation/MediaPipelineDesign.md`)

# Media Pipeline

## Overview

LibMedia turns demuxed media data into decoded audio and video, then feeds it to sinks that are
driven by playback time. The pipeline is pull-based: downstream nodes ask upstream nodes whether
data is available, then pull data when the reported status allows it.

The current pipeline is made of three categories of nodes:

- Producers materialize raw audio/video data.
- Sinks consume that raw audio/video data.
- Processors are both producers and sinks, pulling data from an input producer to transform it
  before a downstream sink makes use of it.

Audio and video use separate producer interfaces because the data they carry is different, but their
control flow is intentionally similar.

## Producer Interface

Producers expose three core operations:

- `peek()` reports the current status and, when data is available, exposes the head of the
  producer's output without removing it.
- `consume()` removes the head that a prior `peek()` exposed.
- `set_wake_handler(...)` is used by the downstream node to wake itself when a previously-observed
  status would result in it going to sleep.

Downstream nodes `peek()` when they need new data, then either read the head and `consume()` it or
go to sleep. Peeking without consuming lets a node decide whether to take the head, rather than
being forced to accept data it may have no room for.

## PipelineStatus

`PipelineStatus` is the shared status language between nodes:

- `Pending`: Data is not available yet, but may become available later.
- `HaveData`: A peek exposes data.
- `Blocked`: Data is not available yet, i.e. we have run out of buffered data from over the network.
- `Suspended`: The upstream node has released its decoding resources and will not produce data until
  it receives a seek.
- `EndOfStream`: We have reached the end of the input data.
- `Error`: The upstream node encountered an error and cannot continue without a seek.

`HaveData` carries data. All other statuses require handling by downstream code.

`Pending`, `Blocked`, `Suspended`, `EndOfStream` and `Error` are waiting statuses. A downstream node
that normally polls for upstream input seeing these should sleep until its wake handler fires.

`EndOfStream` and `Error` are terminal statuses. They resolve seeks even though they are still
"waiting" statuses for helper APIs, because no more data is required to decide the seek has
completed.

## Wake Handlers

Wake handlers do not carry state. A wake means the downstream node should `peek()` again.

Nodes should dispatch a wake when the downstream node may be waiting, and the status has changed to
one that resolves a seek: `HaveData`, `EndOfStream`, or `Error`.

Some nodes (i.e. AudioMixer) may also wake when moving away from a previously dispatched
`EndOfStream`, so stale EOS state can be recovered after a seek or after new input becomes
available.

## Seeking

Seeking is initiated downstream and forwarded upstream. For downstream nodes, a seek completes when
the upstream node reports a status that resolves the new position. Usually that means `HaveData`,
but `EndOfStream` or `Error` can also complete the wait.

Sinks report pipeline status changes to their users. A user such as the playback manager can
interpret a seek-resolving status as seek completion.

If a node forwards a seek upstream, it can clear or invalidate its cached data, since the upstream
node is required to emit data at or before the target timestamp.

However, if a node uses its cached data to resolve seeks, it must also resolve seeks past the last
frame in that cached data if its last known status is `EndOfStream`. This ensures that a seek past
EOS will not invalidate in-flight frames by bumping a seek generation.

## Decoded Data Producers

Decoded data producers run demux and decode video/audio data into queues in order to absorb variance
in processing for each individual coded frame.

When a downstream `consume()` removes an item from the queue, the producer wakes its decoder thread
so it can refill the queue. When the queue is full and no downstream activity occurs for the idle
timeout, the producer auto-suspends: it drops its decoder, its queue, and any frame pool buffers it
can free, then wakes the consumer with a `Suspended` status.

`peek()` and `consume()` defer the idle timeout, but they never resume a suspended producer. When a
downstream node needs new data, it must explicitly `seek()` the suspended node to resume it, so that
the pipeline may insert pre-roll data at any stage where it is needed (i.e. for audio stretching).

## Playback Manager

`PlaybackManager` creates, modifies and drives the end-to-end playback pipeline. When media data is
received, it will spawn a thread to determine what tracks are present, and connect the necessary
nodes for playback to begin.

Behavior is defined through state handlers derived from `PlaybackStateHandler`, with each overriding
methods for various functions and signals to determine when to transition to another state. Upon
entering or exiting, a state handler will drive changes to the pipeline, like pausing/resuming or
seeking.

The media time is derived from the audio sink when audio is used, otherwise `GenericTimeProvider`
calculates time from the monotonic clock.

## Audio Pipeline

The usual audio path is:

```text
DecodedAudioProducer
    -> AudioMixer
    -> AudioTimeStretchProcessor
    -> AudioPlaybackSink
    -> PlaybackStream
```

`AudioPlaybackSink` owns a small queue of `AudioBlock`s pulled from upstream, which it fills on a
separate thread to avoid blocking the `PlaybackStream` data callback. It starts pulling input when
its `start()` method is called, so that all inputs can be connected first. When its input reaches
`EndOfStream`, it continues producing silence to allow the time to advance in case there is video
data still available.

`AudioMixer` sequences/mixes audio from multiple inputs into a single output. It emits silence for
gaps, and pauses output when any input indicates data is not ready yet. When all inputs are at
`EndOfStream`, the output becomes `EndOfStream` as well.

`AudioTimeStretchProcessor` stretches audio without affecting pitch.

## Video Pipeline

The usual video path is:

```text
DecodedVideoProducer -> DisplayingVideoSink
```

`DisplayingVideoSink` keeps the current frame and the next frame. It advances when playback time
passes the current frame's display interval.

Video seeks are comparatively expensive, so the sink can satisfy some seeks locally when the
requested timestamp is already covered by the current or next frame interval. This avoids
unnecessary upstream seeks during scrubbing. The same optimization is less important for audio
because it decodes exponentially faster.

## Threading

The media pipeline crosses several threads:

- The main thread coordinates playback state and serializes cross-thread communication through its
  event loop
- Decoded data producers manage decoding threads to amortize the cost to decode each frame
- The audio sink runs a processing thread to amortize the cost of mixing/stretching each audio block
- The playback stream data callback is invoked from an (often system-managed) thread

Data should only be consumed via `peek()` and `consume()` from a single thread, to allow producers
to use lock-free structures internally. Control methods like `seek()` should similarly only initiate
from a single thread, or be otherwise serialized.

Avoid synchronous callback dispatch while holding locks unless the callee is known not to reenter
the same pipeline. Deferred dispatch is often used to keep lock ordering simple.

Wake handlers must be removed before disconnecting or destroying a node. An upstream node must never
invoke a handler after downstream has cleared it.

---

### Documentation Omnibox (`Documentation/Omnibox.md`)

# Omnibox

Ladybird's omnibox combines URL entry, search, history, bookmarks, adaptive learning, and remote
search suggestions. The implementation is used by the UI frontend through `LibWebView`.

The central rule is that ordering, automatic selection, and inline completion are separate
decisions. A result may be useful enough to display near the top without being safe to select or
complete automatically.

## Code structure

The implementation is divided into a few focused components:

- `Omnibox` owns an editing session. It tracks the user's query, displayed text, selected row,
  completion state, popup state, and active query generation.
- `Autocomplete` coordinates local and remote suggestion delivery.
- `AutocompleteService` runs local database work on a serial worker thread.
- `AutocompleteRanker` retrieves and scores history, bookmark, and adaptive candidates.
- `AutocompleteMuxer` merges duplicate destinations, applies diversity rules, and constructs the
  final result list.
- `HistoryStore`, `BookmarkStore`, and the omnibox engagement tables provide local evidence.
- `UI/Qt/Autocomplete` renders the shared suggestion model.

`OmniboxSuggestionProvider` is the boundary between the editing state machine and suggestion
generation. The production provider wraps `Autocomplete`; tests use a scripted provider so they can
deliver results in any order without relying on timing.

## Query lifecycle

Every edit starts a new query generation identified by an incrementing `AutocompleteQueryID`.
Local retrieval begins immediately. Remote search suggestions are debounced for 100 milliseconds.

The local worker owns a separate SQLite connection. There is at most one running local query and
one pending query per client. A newer pending query replaces an older one. When possible, a newer
query interrupts stale SQLite work rather than waiting for it to finish.

Results are delivered incrementally:

1. Local results are ranked and published.
2. A remote response, if enabled and successful, is merged into the same query generation.
3. The combined list is published again.

Remote suggestions from the preceding query may remain visible while a compatible continuation is
in flight. Only suggestions that still have the new input as a prefix are retained. A successful
response replaces these provisional rows.

The UI thread discards results when their query ID is no longer active. Ending an editing session,
dismissing the popup, or starting another session cancels provider work and invalidates the active
generation. Stale results can therefore neither reopen the popup nor become an Enter action.

## Suggestion model

`AutocompleteSuggestion` contains:

- Its source: literal URL, history, bookmark, adaptive engagement, or search.
- Destination text and optional title, subtitle, and favicon.
- The input used for presentation highlighting.
- A match class and separate relevance components.
- Whether it is the verbatim action.
- Whether it may be automatically selected.
- Whether it may be used for inline completion.

The score components are retained after ranking so duplicate destinations can combine independent
history, bookmark, and adaptive evidence without losing their provenance.

### Destination identity

URL destinations are compared semantically. Scheme differences remain meaningful, while equivalent
serializations of the same URL are merged. `www.` and bare hosts share a diversity family but are
not otherwise treated as the same destination.

Search destinations are identified by Unicode case-folded text with whitespace normalized. This
allows an exact remote suggestion to merge with the verbatim search action.

When history, bookmark, and adaptive candidates identify the same URL, their score components and
eligibility flags are combined. Bookmark presentation is preferred so its title and favicon remain
visible.

## Matching

URL matching accepts the forms users commonly type by ignoring an `http://` or `https://` prefix,
and optionally `www.`, when comparing a query with a candidate. Matching is case-insensitive.

Match classes, from strongest to weakest, include:

- Exact URL.
- URL prefix.
- Exact history or bookmark title.
- Title word prefix.
- URL substring.
- Title substring.
- Exact or prefix adaptive association.

Title matching uses Unicode case folding. URL completion uses ASCII case-insensitive comparison
because serialized non-ASCII URL components are already encoded.

History titles participate after three input code points. Exact history titles outrank title-prefix
matches, allowing a page named `Wikimedia Commons` to rank ahead of pages whose titles merely end
in `Wikimedia Commons`. They remain ineligible for automatic selection and inline completion.

Bookmark titles and folders normally participate after three input code points. An exact bookmark
title participates at any non-empty length, so a bookmark named `GH` is retrieved by `GH`, `gh`, or
`Gh`. This exception does not turn partial two-character title prefixes into candidates.

An exact title match remains ineligible for automatic selection and inline completion. It is a
strong retrieval signal, not permission to replace the user's search action or editor text.

## Ranking

Ranking is deterministic and uses bounded integer components. Match quality is the largest signal;
history quality, bookmark evidence, and adaptive engagement then refine candidates within the same
general match quality.

### History

History ranking distinguishes direct navigation intent from passive visits. Visit and direct-visit
scores decay over time and are bounded before being added to a candidate score. Reloading or
repeatedly encountering a page cannot produce unbounded relevance.

For host-like input, history also creates aggregate origin candidates. Evidence comes from a capped
number of distinct pages, which prevents one frequently reloaded deep page from making its origin
look intentional. The origin may borrow a favicon from a matching page.

Deep history pages are suppressed for one- and two-character host queries unless the input contains
a URL path. This keeps short result lists from filling with incidental pages.

### Bookmarks

A bookmark contributes a fixed relevance bonus in addition to match quality. URL-prefix bookmark
matches are strong navigation evidence. Bookmark title and folder matches are useful for retrieval
but are not automatically selected or inline-completed.

Deep bookmarks remain eligible for a two-character URL prefix. Exact short title matches also remain
eligible, but do not become automatic actions.

### Adaptive engagements

The omnibox records committed input-to-destination associations. Search and URL destinations are
stored separately. Explicit row choices and automatic commitments have separate counts.

Exact associations are stronger than prefix associations. Short adaptive prefixes require repeated
explicit use before they can become automatic:

- A one-character deep destination requires three explicit uses.
- A two-character deep destination requires two explicit uses.

An exact input-to-URL association of at least two characters becomes automatic after one explicit
choice, including search-like input containing whitespace. This allows the omnibox to learn a
deliberate navigation without allowing passive title matches to replace the search action.

Adaptive scores decay with time. Prefix evidence is scaled by the square root of the typed-prefix
length divided by the learned-input length, so very short prefixes receive less credit.

Merely displaying, automatically highlighting, or hovering a suggestion does not record an
engagement. A private session never records engagements.

## Result composition

The muxer first merges candidates with the same destination and sorts them by relevance. It then
constructs a bounded result list while preserving the verbatim action and limiting repetition.

The current limits are soft:

- At most four local navigation rows before deferred local rows.
- At most three remote search rows before deferred remote rows.
- At most two rows per origin for ordinary host queries.
- At most one row per origin and five total rows for one- and two-character host queries.

Deferred source rows may fill unused space after the first pass. This prevents a source cap from
leaving the popup unnecessarily sparse.

For short queries, candidates from the same origin are collapsed to one representative. Proven
adaptive destinations are preferred, followed by matching deep bookmarks, followed by origin rows.
An exact bookmark-title match is treated as a matching deep bookmark for this decision, preventing
an origin row from hiding it.

The highest-ranked candidate eligible for automatic selection is moved to the first row. A
higher-scoring but ineligible title or substring match may remain visible below it.

## Automatic selection

Automatic selection determines what Enter does when the user has not deliberately chosen another
row. It does not imply inline completion.

A candidate may be selected automatically when it is supported by strong navigational evidence,
for example:

- A sufficiently long URL prefix backed by direct visits.
- A bookmark URL prefix of at least two characters.
- An adaptive association whose evidence meets the short-prefix thresholds.

Title-only, folder, substring, previous-search, and remote suggestion matches are not automatic URL
actions. The verbatim URL-or-search action remains available in every non-empty result list.

Automatic defaults use hysteresis during incremental refreshes. A challenger replaces the current
default only when it exceeds the incumbent by both 10 percent and 100 relevance points. This avoids
visible selection churn as local and remote results arrive.

## Inline completion

Inline completion is intentionally stricter than automatic selection. A candidate must:

- Be eligible for automatic selection.
- Be a URL-prefix match.
- Extend the user's input at the end of the editor.
- Produce a non-empty suffix after optional scheme and `www.` handling.
- Not contain a query string unless an exact adaptive association permits it.

Title, folder, substring, search, and literal URL candidates never inline-complete. The typed prefix
is preserved exactly; only the borrowed suffix is selected.

An existing completion is retained across forward typing while it remains valid and is within 150
relevance points of the best inline candidate. A materially better non-inline default clears it.

Backspace suppresses completion for the resulting input. Typing a different input lifts the
suppression. Moving the cursor away from the end removes the completion and prevents late results
from applying another one. Right Arrow and End accept a completion, adopt the displayed text as the
new query, and requery it.

## Editing and activation

`Omnibox` keeps the user's query separate from borrowed display text. Display text has one of three
provenances:

- User text.
- Inline completion.
- Keyboard row preview.

Arrow keys explicitly select rows. A selected row may preview its destination in the editor while
the original query remains available for restoration. Pointer hover only changes row appearance; it
does not select, preview, or train the result.

Escape closes the popup and restores the query. Clicking outside does the same. If the user edits or
rejects a completion, Enter commits the text currently owned by the user rather than silently
choosing an unrelated automatic row.

An explicitly selected row remains selected across a refresh of the same generation when a result
with the same destination still exists. If it disappears, its preview is removed. Automatic
selection is recalculated separately.

Enter never waits for suggestions. If the displayed rows belong to an older query generation, the
current input is immediately committed through normal URL-or-search resolution.

## Persistence and deletion

History stores visit counts, direct-visit counts, last-use timestamps, and decayed scores. Redirect
chains distinguish direct navigation from visits produced by redirects so redirect targets do not
inherit typed intent.

Engagement rows store normalized input, destination kind, destination identity, explicit and
automatic use counts, and the last-use time. Database migrations add these fields to existing
profiles. The database layer supports floating-point values, statement interruption, and a busy
timeout because autocomplete reads concurrently with history writes.

Removing a history row also removes its engagement associations unless the destination remains
bookmarked. Forgetting a site and clearing all history remove the corresponding associations.

## Private browsing

Private autocomplete reads normal-profile history, bookmarks, and adaptive associations so local
suggestions remain useful. It does not write history or engagement data.

Remote suggestions use the private RequestServer connection. No history, bookmark, or engagement
data is sent to the search provider; the request contains only the query required by the configured
autocomplete engine.

## Presentation

Both native frontends render the same ordered suggestion list without source headers. Search rows
use a search icon. Local navigation rows use a favicon when available and otherwise a globe.
Bookmarks receive a star badge.

Matched portions of titles and URLs use shared match ranges from `LibWebView`, ensuring the UI
highlights the same text. The match uses both weight and brightness so it remains visible in
dark and light themes.

The popup displays at most six rows before scrolling. Rows with a title use two lines; URL-only and
search rows use one. Selection and pointer hover have separate visual treatments.

## Settings

Remote search suggestions are controlled by the autocomplete-engine setting. Disabling suggestions
clears the configured engine; the setting is persisted with the rest of the browser settings and is
restored on the next launch.

The search engine used for the verbatim action is independent of whether remote suggestions are
enabled.

## Tests

The implementation has three main deterministic test layers:

- `TestAutocompleteRanker` verifies matching, relevance components, eligibility, decay, and short
  query behavior.
- `TestAutocompleteMuxer` verifies destination merging, diversity, source limits, verbatim
  preservation, and representative selection.
- `TestOmnibox` drives the editing state machine with a scripted provider, covering stale delivery,
  completion acceptance and rejection, selection preservation, activation, and engagement recording.

`TestAutocomplete` exercises the asynchronous integration with the application, service worker,
bookmark updates, private-profile reads, and remote failure handling. History-store tests cover
persisted ranking state, migrations, and deletion behavior.

Tests control result delivery or inject a clock rather than depending on wall-clock timing.

---

### Documentation Patterns (`Documentation/Patterns.md`)

# Coding Patterns

## Introduction

Over time numerous reoccurring patterns have emerged from or were adopted by
the Ladybird code base. This document aims to track and describe them, so they
can be propagated further and the code base can be kept consistent. 

## `TRY(...)` Error Handling

The `TRY(..)` macro is used for error propagation in the Ladybird code base.
The goal being to reduce the amount of boiler plate error code required to
properly handle and propagate errors throughout the code base. 

Any code surrounded by `TRY(..)` will attempt to be executed, and any error
will immediately be returned from the function. If no error occurs then the
result of the contents of the TRY will be the result of the macro's execution. 

### Examples:

Example from LibGfx:

```cpp
#include <AK/Try.h>

... snip ...

ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_shareable(BitmapFormat format, IntSize size, int scale_factor)
{
    if (size_would_overflow(format, size, scale_factor))
        return Error::from_string_literal("Gfx::Bitmap::create_shareable size overflow");

    auto const pitch = minimum_pitch(size.width() * scale_factor, format);
    auto const data_size = size_in_bytes(pitch, size.height() * scale_factor);

    auto buffer = TRY(Core::AnonymousBuffer::create_with_size(round_up_to_power_of_two(data_size, PAGE_SIZE)));
    auto bitmap = TRY(Bitmap::create_with_anonymous_buffer(format, buffer, size, scale_factor, {}));
    return bitmap;
}
```

Note: Our `TRY(...)` macro functions similarly to the `?` [operator in rust](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator).

## `MUST(...)` Error Handling

The `MUST(...)` macro is similar to `TRY(...)` except the macro enforces that
the code run inside the macro must succeed, otherwise we assert.

Note that `MUST(...)` should not be used as a replacement for `TRY(...)` in cases where error propagation is not (currently) possible.
Instead, the `release_value_but_fixme_should_propagate_errors()` method of `ErrorOr<>` should be used to retrieve the value
and to mark the location for future improvement. `MUST(...)` is reserved for cases where we determine through other circumstances that it
should not be possible for the code inside the macro to fail or if a failure is serious enough that the program _needs_ to crash.

### Example:

```cpp
#include <AK/Vector.h>

... snip ...

ErrorOr<void> insert_one_to_onehundred(Vector<int>& vector)
{
    TRY(vector.try_ensure_capacity(vector.size() + 100));

    for (int i = 1; i <= 100; i++) {
        // We previously made sure that we allocated enough space, so the append operation shouldn't ever fail.
        MUST(vector.try_append(i));
    }

    return {};
}
```

## Fallible Constructors

The usual C++ constructors are incompatible with Ladybird's method of handling errors,
as potential errors are passed using the `ErrorOr` return type. As a replacement, classes
that require fallible operations during their construction define a static function that
is fallible instead.

This fallible function (which should usually be named `create`) will handle any errors while
preparing arguments for the internal constructor and run any required fallible operations after
the object has been initialized. The resulting object is then returned as `ErrorOr<T>` or
`ErrorOr<NonnullOwnPtr<T>>`.

### Example:

```cpp
class Decompressor {
public:
    static ErrorOr<NonnullOwnPtr<Decompressor>> create(NonnullOwnPtr<Core::Stream::Stream> stream)
    {
        auto buffer = TRY(CircularBuffer::create_empty(32 * KiB));
        auto decompressor = TRY(adopt_nonnull_own_or_enomem(new (nothrow) Decompressor(move(stream), move(buffer))));
        TRY(decompressor->initialize_settings_from_header());
        return decompressor;
    }

... snip ...

private:
    Decompressor(NonnullOwnPtr<Core::Stream::Stream> stream, CircularBuffer buffer)
        : m_stream(move(stream))
        , m_buffer(move(buffer))
    {
    }

    CircularBuffer m_buffer;
    NonnullOwnPtr<Core::Stream::Stream> m_stream;
}
```

## The `ladybird_main(..)` program entry point

Ladybird has moved to a pattern where executables do not expose a normal C
main function. A `ladybird_main(..)` is exposed instead. The main reasoning
is that the `Main::Arguments` struct can provide arguments in a more idiomatic
way that fits with the Ladybird's internal API surface area. The ErrorOr<int> likewise
allows the program to propagate errors seamlessly with the `TRY(...)` macro,
avoiding a significant amount of clunky C style error handling.

These executables are then linked with the `LibMain` library, which will link in
the normal C `int main(int, char**)` function which will call into the programs
`ladybird_main(..)` on program startup.

The creation of the pattern was documented in the following video:
[OS hacking: A better main() for SerenityOS C++ programs](https://www.youtube.com/watch?v=5PciKJW1rUc)

### Examples:

A function `main(..)` would normally look something like:

```cpp
int main(int argc, char** argv)
{
   return 0;
}
```

Instead, `ladybird_main(..)` is defined like this:

```cpp
#include <LibMain/Main.h>

ErrorOr<int> ladybird_main(Main::Arguments arguments)
{
    return 0;
}
```

## Intrusive Lists

[Intrusive lists](https://www.data-structures-in-practice.com/intrusive-linked-lists/) are used in some specific cases
in Ladybird. A data structure is said to be
"intrusive" when each element holds the metadata that tracks the
element's membership in the data structure. In the case of a list, this
means that every element in an intrusive linked list has a node embedded
inside it. The main advantage of intrusive
data structures is you don't need to worry about handling out of memory (OOM)
on insertion into the data structure. This means error handling code is
much simpler than say, using a `Vector` in environments that need to be durable
to OOM.

The common pattern for declaring an intrusive list is to add the storage
for the intrusive list node as a private member. A public type alias is
then used to expose the list type to anyone who might need to create it.

## Static Assertions of the size of a type

It's a universal pattern to use `static_assert` to validate the size of a
type matches the author's expectations. Unfortunately when these assertions
fail they don't give you the values that actually caused the failure. This
forces one to go investigate by printing out the size, or checking it in a
debugger, etc. 

For this reason `AK::AssertSize` was added. It exploits the fact that the
compiler will emit template argument values for compiler errors to provide
debugging information. Instead of getting no information you'll get the actual
type sizes in your compiler error output.

Example Usage:

```cpp
#include <AK/StdLibExtras.h>

struct Empty { };

static_assert(AssertSize<Empty, 1>());
```

## String View Literals

`AK::StringView` support for `operator""sv` which is a special string literal operator that was added as of
[C++17 to enable `std::string_view` literals](https://en.cppreference.com/w/cpp/string/basic_string_view/operator%22%22sv).

```cpp
[[nodiscard]] ALWAYS_INLINE constexpr AK::StringView operator""sv(const char* cstring, size_t length)
{
    return AK::StringView(cstring, length);
}
```

This allows `AK::StringView` to be constructed from string literals with no runtime
cost to find the string length, and the data the `AK::StringView` points to will 
reside in the data section of the binary.

Example Usage:
```cpp
#include <AK/String.h>
#include <AK/StringView.h>
#include <LibTest/TestCase.h>

TEST_CASE(string_view_literal_operator)
{
    StringView literal_view = "foo"sv;
    String test_string = "foo";

    EXPECT_EQ(literal_view.length(), test_string.length());
    EXPECT_EQ(literal_view, test_string);
}
```

## Source Location

C++20 added std::source_location, which lets you capture the
callers __FILE__ / __LINE__ / __FUNCTION__ etc as a default
argument to functions.
See: https://en.cppreference.com/w/cpp/utility/source_location

`AK::SourceLocation` is the implementation of this feature in
Ladybird's AK. It's become the idiomatic way to capture the location
when adding extra debugging instrumentation, without resorting to
littering the code with preprocessor macros.

To use it, you can add the `AK::SourceLocation` as a default argument
to any function, using `AK::SourceLocation::current()` to initialize the
default argument.

Example Usage:
```cpp
#include <AK/SourceLocation.h>
#include <AK/StringView.h>

static StringView example_fn(const SourceLocation& loc = SourceLocation::current())
{
    return loc.function_name();
}

int main(int, char**)
{
    return example_fn().length();
}
```

If you only want to only capture `AK::SourceLocation` data with a certain debug macro enabled, avoid
adding `#ifdef`'s to all functions which have the  `AK::SourceLocation` argument. Since SourceLocation
is just a simple struct, you can just declare an empty class which can be optimized away by the
compiler, and alias both to the same name.

Example Usage:

```cpp

#if LOCK_DEBUG
#    include <AK/SourceLocation.h>
#endif

#if LOCK_DEBUG
using LockLocation = SourceLocation;
#else
struct LockLocation {
    static constexpr LockLocation current() { return {}; }

private:
    constexpr LockLocation() = default;
};
#endif
```

## `type[]` vs. `Array<type>` vs. `Vector<type>` vs. `FixedArray<type>`

There are four "contiguous list" / array-like types, including C-style arrays themselves. They share a lot of their API, but their use cases are all slightly different, mostly relating to how they allocate their data.

Note that `Span<type>` differs from all of these types in that it provides a *view* on data owned by somebody else. The four types mentioned above all own their data, but they can provide `Span`'s which view all or part of their data. For APIs that aren't specific to the kind of list and don't need to handle resizing in any way, `Span` is a good choice.

* C-style arrays are generally discouraged (and this also holds for pointer+size-style arrays when passing them around). They are only used for the implementation of other collections or in specific circumstances.
* `Array` is a thin wrapper around C-style arrays similar to `std::array`, where the template arguments include the size of the array. It allocates its data inline, just as arrays do, and never does any dynamic allocations.
* `Vector` is similar to `std::vector` and represents a dynamic resizable array. For most basic use cases of lists, this is the go-to collection. It has an optional inline capacity (the second template argument) which will allocate inline as the name suggests, but this is not always used. If the contents outgrow the inline capacity, Vector will automatically switch to the standard out-of-line storage. This is allocated on the heap, and the space is automatically resized and moved when more (or less) space is needed.
* `FixedArray` is essentially a runtime-sized `Array`. It can't resize like `Vector`, but it's ideal for circumstances where the size is not known at compile time but doesn't need to change once the collection is initialized. `FixedArray` guarantees to not allocate or deallocate except for its constructor and destructor.

---

### Documentation Porting (`Documentation/Porting.md`)

# Porting to new Operating System Environments

This document outlines the steps required to port Ladybird to a new OS environment, and expectations for such ports to
be accepted into the mainline repository.

## Types of Ports

There are two types of ports that can be made to Ladybird:

- UI port: We define the "browser frontend" as the UI layer. This includes the browser window, tabs, address bar, etc.
- Platform port: This includes the underlying platform-specific code that interacts with the OS. This includes things like
  file I/O, networking, and process management.

### UI Ports

There are currently two supported UI ports:

* [Android UI](https://developer.android.com/develop/ui) - The native UI on Android.
* [Qt](https://doc.qt.io/qt-6/) - The UI used on all other platforms.

### Platform Ports

There are currently two supported platform ports:

- GNU/Linux: The Linux platform port that may work on other POSIX platforms
- macOS: The macOS platform port

Many other POSIX desktop platforms are known to work, or have worked in the past, but are not officially supported.
Among others, these include Alpine Linux, FreeBSD, OpenBSD, and Haiku. Support for these systems is community-driven,
meaning there is no regular CI to ensure that each master branch commit maintains their functionality.
Contributions to restore or improve compatibility are welcome!

There is currently one in progress platform port:

- Android: The Android platform port, using the Android SDK/NDK directly.

## Porting Steps

### UI ports

UI ports mostly concern themselves with the UI layer. This means the main Ladybird process, using LibWebView.

To create a new Ladybird UI, you will need to implement a new `WebView::ViewImplementation` subclass.
ViewImplementation is the main interface between the UI process and WebContent processes. It is expected that each tab
of the browser will have its own WebContent process. This is all managed by the WebView layer.

Each UI port must also subclass `WebView::Application` to add any UI-specific command-line flags.

TODO: Explain any more details that are necessary

### Platform ports

Platform ports concern themselves with the underlying OS-specific code. In Ladybird, this code is largely contained in
the AK and LibCore libraries.

AK is the standard template library for Ladybird. The first step of a new platform port is a new platform define in
`AK/Platform.h`. This define will be used to conditionally compile platform-specific code.
In AK, the most likely class to need platform-specific code is `AK::StackInfo`.

LibCore is an abstraction over POSIX. It contains classes to wrap lower level OS functionality into APIs that are
comfortable for Ladybird developers to use. The most likely place to need adjustment is `Core::System`, followed by
`Core::Process` and `Core::Socket`.

Ladybird makes heavy use of IPC, and the IPC layer is in `LibIPC`. This layer is mostly platform-agnostic, but there are
some platform-specific details in LibCore that may need to be adjusted. The IPC system is based on Unix domain sockets,
so  any platform that supports Unix domain sockets should be able to use the IPC system out of the box.

## Special Consideration

### Windows

Over the years excitement about a native Windows port has waxed and waned. The main issue is that Ladybird is built on
top of LibCore, which is a POSIX abstraction. Windows is not POSIX, and so a Windows port requires a significant amount
of effort to implement.

To limit the scope of the work, we have decided that a Windows port will need the following properties:

- Target x86_64-windows-msvc and arm64-windows-msvc only. We are not interested in accepting patches to make MinGW or MSYS2 work.
- The port must use `clang-cl.exe` as the compiler. We are not interested in accepting patches to make MSVC work.
- Platform `#ifdef`s must stay within AK and LibCore, with a few exceptions in LibWebView.
- Prefer separate cpp files with the same interface to `#ifdef`s within a single file.
- Avoid `#ifdef`s in headers, as much as possible.

---

### Documentation ProcessArchitecture (`Documentation/ProcessArchitecture.md`)

# Ladybird Browser process architecture

*NOTE: This document is partly aspirational, in that the state of the code does not yet fully reflect what's described here. Implementation is underway.*

The Ladybird web browser uses a multi-process architecture to improve stability and security in the face of arbitrary (and possibly hostile) web content.

## Process overview



Every instance of the **Browser** application can have one or more tabs open. Each tab has a unique **WebContent** service process spawned on its behalf.

Two important aspects of web browsing are further separated from the **WebContent** process: *network requests* and *image decoding*, segregated to the **RequestServer** and **ImageDecoder** processes respectively.

All processes are aggressively sandboxed using the `pledge()` and `unveil()` mechanisms. Furthermore, all processes except **Browser** run as an unprivileged user, separate from the primary logged-in desktop user.

### Process: WebContent

This process hosts the main HTML/CSS engine (**LibWeb**.) It also runs JavaScript (**LibJS**.) It gets input events from **Browser** and paints the web content into shared bitmaps. It can only communicate with the outside world via **RequestServer**.

### Process: RequestServer

This process can use networking protocols (like HTTP or HTTPS) to request files from the outside world. Each **WebContent** process gets its own **RequestServer** to do uploading or downloading on its behalf.

For DNS lookups, **RequestServer** asks for help from the system's global **LookupServer** service, which handles all outgoing DNS requests.

### Process: ImageDecoder

This process can decode images (PNG, JPEG, BMP, ICO, PBM, etc.) into bitmaps. Each image is decoded in a fresh **ImageDecoder** process. These are strongly sandboxed and can't do much except receive encoded bitmap data and return a bitmap to **WebContent** if decoding is successful.

### How processes are spawned

To get a fresh **WebContent** process, anyone with the suitable file system permissions can spawn one by connecting to
the socket at `/tmp/session/%sid/portal/webcontent`, with `%sid` being the current login session id. This socket is managed
by **SystemServer** and will spawn a new instance of **WebContent** for every connection.

The same basic concept applies to **RequestServer** and **ImageDecoder** as well, except that those services are spawned
by **WebContent** as needed, not by **Browser**.

## Class overview



In the GUI application process, a `OutOfProcessWebView` widget is placed somewhere in a window, and it takes care of spawning all of the helper processes, etc.

Internally, the `OutOfProcessWebView` has a `WebContentClient` object that implements the client side of the **WebContent** IPC protocol.

The `WebContentClient` speaks to a `WebContent::ConnectionFromClient` in the **WebContent** process. Internally, the `WebContent::ConnectionFromClient` has a `WebContent::PageHost` which hosts the **LibWeb** engine's main `Web::Page` object.

Inside **LibWeb**, a `Web::Page` has a main `Web::Frame`, which may have subframes corresponding to `<frame>` or `<iframe>` HTML elements. Each `Web::Frame` has a `Web::Document`, which is the root node of the DOM tree.

Describing the **LibWeb** object model in detail is outside the scope of this document.

---

### Documentation RequestServerWireLogging (`Documentation/RequestServerWireLogging.md`)

# RequestServer wire-activity logging

RequestServer emits up to four `dbgln` lines per HTTP fetch that actually
hit the network, so you can correlate "dead air" in a profile with the
download (or stall) responsible for it.

Cache hits do **not** appear here — only requests that produced traffic.
That is the point: if the log is silent during a profile gap, the gap is
not a download.

## The lines

For every fetched request you get a `wire:` line. Successful fetches that
delivered any body also get `wire+:` (decoded view), `wire++:` (encoded
view, when curl provided wire-level progress), and `wire^:` (pre-network
time spent inside RequestServer plus drain delay). When more than one
request completes in the same curl multi tick, a single `wire-batch:`
line is emitted explaining why their `wire:` timestamps cluster.

Several diagnostic lines fire only when something interesting happened
and don't belong to any single request: `wire-stall:` flags
event-loop blocks and slow synchronous curl calls, `wire-burst:` flags
request floods from a client, `wire-pipe-pressure:` flags WebContent
back-pressure on the response pipe, `LibDNS wire-dns:` flags slow DNS
lookups (especially the blocking `getaddrinfo` fall-back), and `UI
wire-cookie:` flags slow cookie-jar lookups on the UI side. See the
"Process-wide diagnostic lines" section below.

### `wire:` — one-line summary

```
wire: KIND METHOD URL HTTP-VERSION -> STATUS
    | wire X.X KiB sent X.X KiB
    | total N ms = queue N + dns N + tcp N + tls N + req N + wait N + body N
    | wire X.X KiB/s avg, X.X KiB/s during body
```

Fields:

- **KIND** — what happened:
  - `DOWNLOAD` — full body fetched from origin.
  - `REVAL-304` — conditional GET hit the cache; server returned 304, we
    served the cached body.
  - `REVAL-FULL` — conditional GET that the server invalidated (returned
    new content), so we replaced the cache entry.
  - `FAIL` — curl failed before completion. The line collapses to
    `... -> error: <curl message> (after N ms, wire X.X KiB)`.
  - `[bg]` suffix on the kind marks a background revalidation triggered
    by `stale-while-revalidate`.
- **wire X.X KiB** — bytes received **on the wire** (encoded; gzip /
  brotli / etc. as transferred). Comes from `CURLINFO_SIZE_DOWNLOAD_T`.
  Read this carefully — see the wire-vs-decoded section below.
- **sent X.X KiB** — request body bytes uploaded.
- **total N ms** — request lifetime, broken into libcurl phases. All
  phase numbers are wall-time deltas:
  - `queue` — time waiting in libcurl's pre-start queue (HTTP/2 and
    HTTP/3 stream multiplexing usually puts you here briefly).
  - `dns` — name resolution.
  - `tcp` — TCP handshake (or QUIC connect on HTTP/3).
  - `tls` — TLS handshake. Zero on plaintext, and zero on a reused
    connection — see "Reused-connection timing" below.
  - `req` — time between handshake completing and being ready to send
    the request bytes.
  - `wait` — server processing / time-to-first-byte. A high `wait` with
    everything else low means the origin took its time before sending
    the first response byte.
  - `body` — time from first response byte to last response byte.
- **wire X.X KiB/s avg** — `CURLINFO_SPEED_DOWNLOAD_T`, averaged over
  the entire request including TTFB. Useful as a sanity check, not as a
  throughput number.
- **wire X.X KiB/s during body** — wire bytes divided by `body` time.
  This is the throughput you actually got while bytes were flowing.

### `wire+:` — decoded-side per-chunk stats

```
wire+: decoded chunks=N bytes=X.X KiB span=N ms thru=X.X KiB/s
     | gap avg=N ms max=N ms stalls(>100ms)=N
     | worst gap began at +N ms after X.X KiB decoded
     | chunk bytes avg=N min=N max=N
```

Sampled inside our `WRITEFUNCTION` callback, so all numbers here are
**post-decompression** — i.e. the bytes our consumer actually receives.

- **chunks=N** — number of times curl handed us a buffer of decoded
  bytes.
- **bytes=X.X KiB** — total decoded body size (sum of all chunks).
- **span=N ms** — wall time between the first decoded chunk and the
  last.
- **thru=X.X KiB/s** — `bytes / span`, throughput as the consumer
  experienced it.
- **gap avg / max** — inter-chunk spacing. `max` is the longest pause
  between two consecutive decoded chunks.
- **stalls(>100ms)=N** — count of inter-chunk gaps longer than 100 ms.
  Useful for "was this one big pause, or several smaller ones?".
- **worst gap began at +N ms after X.X KiB decoded** — locates the
  worst gap in the timeline. If the answer is `+5 ms after 0.0 KiB`,
  the server flushed once and then thought; if it is
  `+800 ms after 90% of bytes`, the tail dragged.
- **chunk bytes avg / min / max** — distribution of decoded chunk
  sizes.

### `wire++:` — wire-side per-progress-tick stats

```
wire++: wire bytes=X.X KiB span=N ms thru=X.X KiB/s
      | wire max gap=N ms stalls(>100ms)=N
      | worst wire gap began at +N ms after X.X KiB on the wire
      | compression=Yx
```

Sampled inside `CURLOPT_XFERINFOFUNCTION`, which curl invokes both as
bytes hit the socket and as a heartbeat during silence. All numbers
here are **wire bytes** (encoded, before decompression).

- **wire bytes / span / thru** — encoded-side equivalents of the
  decoded line. `thru` is the real network throughput while bytes were
  flowing.
- **wire max gap / stalls / worst gap began at** — same idea as the
  decoded line, but reflecting actual silence on the network. This is
  what you want when the question is "is the server pausing or is curl
  buffering?".
- **compression=Yx** — `decoded_bytes / wire_bytes`. Common HTML pages
  land at 5–10×; static assets that are already compressed (.jpg,
  .mp3, .woff2) land at 1×.

### `wire^:` — pre-network time spent inside RequestServer

```
wire^: internal pre-curl=N ms = cache+init N + our-dns N + cookie N + curl-setup N | drain delay N ms
```

Wall-time accounting for everything that happens **before** libcurl owns the
request, plus the gap between curl marking it done and us logging.

- **internal pre-curl** — `curl_added_at - created_at`. Total time this
  request spent inside our state machine before `curl_multi_add_handle`.
  When this is a meaningful fraction of `total` on the `wire:` line, the
  delay is ours, not the network's.
- **cache+init** — Init / ReadCache / WaitForCache time. Big numbers
  here mean cache contention or a slow disk-cache lookup.
- **our-dns** — time inside our DNS resolver. Distinct from libcurl's
  `dns` field on the `wire:` line, which is **0** because we
  pre-resolve and pass the address via `CURLOPT_RESOLVE`. If the
  `wire:` line says `dns 0` and you trust the network is fine but
  things still feel slow, look here.
- **cookie** — round-trip IPC to the UI process to retrieve cookies for
  the URL. Skipped (`-`) for credential-less requests.
- **curl-setup** — gap from the last completed pre-network step to
  `curl_multi_add_handle`. Should be very small; if it isn't, something
  in `handle_fetch_state` got expensive.
- **drain delay** — `complete_observed_at - last_wire_byte_at`. How
  long between the last byte landing and `check_active_requests`
  noticing the completion. Usually sub-ms — non-zero means an event-loop
  delay between curl seeing the end and us draining its message queue.

Fields can show `-` when the relevant phase didn't run (e.g.
`our-dns -` for connect-only requests, `cookie -` when credentials
were disabled).

### `wire-batch:` — multiple completions per drain pass

```
RequestServer wire-batch: drained N completions in one curl multi tick
```

Emitted once per `check_active_requests` call when more than one request
completed since the previous drain. When you see this followed by N
`wire:` lines all sharing the same timestamp prefix, the clustering is
**cosmetic**: we drained the curl multi handle's done-queue in a tight
loop, so the `dbgln` calls happen microseconds apart. The actual
network completion times can be reconstructed from the `total` /
`body` numbers on each request, walked back from the drain timestamp.

If you want true per-request completion times, take the drain
timestamp and subtract each request's `drain delay` from `wire^:`.

## Process-wide diagnostic lines

These do **not** belong to any one request. They surface conditions that
affect the whole RequestServer process or its peers (WebContent, the UI
process). When investigating a stall, look at these first — a single
`wire-stall:` line often explains a dozen confusing `wire:` lines that
follow.

### `wire-stall:` — event-loop block detector

```
RequestServer wire-stall: N ms event-loop gap before 'LABEL' (previous handler: 'PREV')
RequestServer wire-stall: curl call 'LABEL' took N ms (synchronous in event loop)
```

Two flavours:

- **Event-loop gap.** Every notifier/handler we control samples
  `MonotonicTime::now()` on entry. If more than 100 ms passed since the
  previous sample, this fires, naming both the handler that just woke up
  and the previous handler that was the last thing to run. If `PREV` is
  the same handler each time, that handler is doing too much work
  synchronously. If `LABEL` is `curl-socket-ready` the kernel has been
  trying to deliver bytes to us for that long.

  **Exception:** when `LABEL` is `curl-timer-fired` and the timer fired
  within ±50 ms of the time libcurl asked us to wake it, the gap is
  by design (libcurl's internal heartbeat — typically 250 ms intervals)
  and the log line is suppressed. A real stall — the timer fires much
  later than its scheduled time — still reports.
- **Synchronous curl call.** `curl_multi_socket_action` should be very
  fast — it just hands a socket event to libcurl. If a single call
  exceeds 50 ms, libcurl is doing real work synchronously (TLS handshake
  on a fresh connection, certificate validation, callback dispatch into
  our `WRITEFUNCTION`). That work blocks every other request.

Labels currently emitted: `curl-timer-fired`, `curl-socket-ready`,
`check-active-requests`, `ipc-start-request`, `ipc-start-revalidation`,
`ipc-retrieved-cookie`, plus the curl wrappers
`multi_socket_action(timeout)` and `multi_socket_action(socket)`.

### `wire-burst:` — request flood from a client

```
RequestServer wire-burst: client N sent M requests in <100 ms
```

Per-client `start_request` arrival counter. When a single client lands
more than 5 `start_request` IPCs in a 100 ms window, the count is logged
the first time the next request arrives outside that window. Useful for
correlating a `wire-stall:` with "this is the burst that hit us".

### `wire-pipe-pressure:` — WebContent back-pressure on the response pipe

```
RequestServer wire-pipe-pressure: GET URL pipe full, buffering=N bytes
RequestServer wire-pipe-pressure: GET URL unblocked after N ms (peak buffered=N bytes); WebContent likely behind
```

Fires when `m_client_request_pipe->write` returns `EAGAIN` /
`EWOULDBLOCK`. RequestServer is producing response bytes faster than
WebContent's main thread can drain them — almost always because
WebContent is busy parsing HTML, executing JavaScript, or doing layout.
The "unblocked" line fires when the pipe drains and we resume writing,
but only if the back-pressure window lasted more than 50 ms. The
per-request totals are also surfaced in the `wire^:` line as
`pipe back-pressure events=N total=N ms peak-buffered=N bytes`. This is
the "WebContent is behind" diagnosis — it doesn't mean RequestServer is
slow.

### `LibDNS wire-dns:` — DNS lookup synchronous portion (and worker completions)

```
LibDNS wire-dns: lookup(NAME) path=PATH sync=N ms
LibDNS wire-dns: lookup(NAME) path=system-resolver-bg total=N ms = A(queue N + work N) | AAAA(queue N + work N) (off event loop)
```

The first form is emitted by `DNS::Resolver::lookup` whenever its
**synchronous** portion takes more than 5 ms. The synchronous portion is
what holds the event loop — anything past trivial here is interesting.
`path` classifies how the lookup was satisfied:

- `cache-hit` — answered entirely from our in-memory cache.
- `literal-ipv4` / `literal-ipv6` — `name` was an IP address literal.
- `system-resolver-bg` — dispatched two parallel `getaddrinfo` calls
  to ThreadPool workers, one for `AF_INET` (A records) and one for
  `AF_INET6` (AAAA records). The split avoids buggy stub resolvers
  (notably systemd-resolved under load) that drop the AAAA half of a
  coupled query and stall both. The promise resolves as soon as one
  side returns records, with a 50 ms grace window for the other side
  (Happy Eyeballs v2's "Resolution Delay", RFC 8305) so curl can prefer
  IPv6 when both are available.

  The synchronous portion is just the dispatch (sub-ms). When both
  workers have completed, a single line fires from the originating
  event loop with both halves of the breakdown:
    - `A(queue N + work N)` — IPv4 worker timing.
        - `queue` — wall time waiting in the ThreadPool work queue.
        - `work` — wall time inside `getaddrinfo` for AF_INET.
    - `AAAA(queue N + work N)` — same for the IPv6 worker.
    - `total` — wall-clock from dispatch to both workers reporting
      completion. May be much longer than what the user actually
      waited on if the slower side completed after the promise had
      already resolved.

  Either side reporting `-` for its fields would mean its results
  haven't been merged yet (only matters if you're reading partial state
  in a debugger; the log only fires after both completed). A large
  asymmetry between A and AAAA `work` (e.g., A=20 ms, AAAA=10000 ms)
  is a textbook stub-resolver-drops-AAAA pattern, but it no longer
  matters for the user's wait time — we resolved the promise off the
  fast side.
- `system-resolver-join-pending` — a worker is already running
  `getaddrinfo` for this name; we attached to its promise instead of
  spawning a second worker.
- `async-query` — sent a query over our DNS socket.
- `join-pending` — joined an in-flight async DNS query for the same name.
- `repeat-timeout` / `no-conn-dnssec-rejected` — error paths.

A consistent ~30 ms floor on `cache-hit` would indicate the cache is
not being short-circuited and we are paying lookup overhead per request.

A `system-resolver-bg completed in N ms (worker)` line with a large `N`
no longer freezes the event loop — it just means that one cold lookup
took a long time. Other requests should keep flowing during it.

### `UI wire-cookie:` — cookie-jar lookup time on the UI side

```
UI wire-cookie: get_cookie(URL) took N ms (N bytes returned)
```

Emitted by the UI process when `CookieJar::get_cookie` takes more than
5 ms. Pair this with the `cookie` field in `wire^:`:

- High `wire^:` cookie + matching `UI wire-cookie:` ⇒ the jar itself is
  slow.
- High `wire^:` cookie + no `UI wire-cookie:` ⇒ the UI process IPC
  was scheduled late (UI process busy with something else), but the
  jar lookup itself was fast.

## Wire vs decoded — the foot-gun

`wire X.X KiB` on the first line and the byte counts on the `wire+:`
line measure **different things**:

- **wire bytes** = encoded body on the network. This is what dictates
  network time.
- **decoded bytes** = bytes our consumer (the HTML parser, image
  decoder, etc.) sees. This is what dictates CPU work after download.

Comparing them gives you the compression ratio. Don't divide one by the
other's time and call it "throughput" — that's how you accidentally
report a network at 1.3 MB/s when the wire is doing 130 KB/s.

## Reused-connection timing

libcurl's phase markers (`namelookup`, `connect`, `appconnect`) report
**0** on a reused connection because no DNS / TCP / TLS work happened
again. The logger clamps each marker to the previous one's value so a
reused connection shows `dns 0 + tcp 0 + tls 0` and folds the queue
time into `queue` only. If you see a `req` value larger than zero on a
reused HTTP/2 / HTTP/3 connection, that is the time spent acquiring a
stream slot and writing the request, not redundant queue counting.

## Patterns and what they mean

| Symptom | Likely cause |
|---|---|
| One large `wire max gap` mid-transfer, low stall count, fast bytes either side (`wire++:`) | Server-side streaming / SSR with early flush. Origin sent the head, paused to generate the rest, then dumped it. Network is fine. |
| Many small stalls, low average throughput, no big gap (`wire++:`) | Bandwidth- or congestion-limited link, or HTTP/3 / TLS stack pacing. |
| Long single `wait` before any body, then everything fast (`wire:`) | Slow origin (server took its time computing the response). Not a network issue. |
| `wire thru` close to your link bandwidth, no stalls (`wire++:`) | You're saturating the pipe. Nothing to fix. |
| High `tls` on first request, `tls 0` afterwards (`wire:`) | Cold connection setup. Expected on first hit to a host. |
| Large `our-dns` with `dns 0` on `wire:` (`wire^:`) | Our DNS resolver is the bottleneck — libcurl reports 0 because we pre-resolved. Look at the resolver, not the network. |
| Large `cookie` (`wire^:`) | Cookie IPC round-trip to the UI process is slow — the UI process is busy or contended. |
| Non-zero `drain delay` (`wire^:`) | We noticed completion later than curl did — event-loop delay between curl's done-message landing and `check_active_requests` running. |
| `wire-batch:` followed by N `wire:` lines with the same timestamp prefix | Cosmetic clustering. The network completion times were spread out; only the logging happened in a tight loop. Trust the per-request phase numbers. |

If `wire+` shows stalls but `wire++` doesn't, the network was steady and
curl's decompressor was batching — not interesting. If `wire++` shows
the stall too, it really happened on the wire and the gap text tells
you when in the transfer it landed.

For a worked example of using these lines to diagnose a 1.5 s "slow
download" that turned out to be a 1062 ms server pause inside a
streaming-SSR response, see the discussion that originally added this
logging.

## Where the code lives

Most of this is implemented in `Services/RequestServer/Request.cpp`:

- `log_network_activity` emits the `wire:` line.
- `record_chunk` populates the decoded-side `WireStats`; `log_chunk_stats`
  emits the `wire+:` line.
- `on_xferinfo` (registered via `CURLOPT_XFERINFOFUNCTION`) populates the
  wire-side fields; `log_chunk_stats` also emits the `wire++:` line.
- `mark_lifecycle_event` records pre-network timestamps from the state
  handlers (`handle_dns_lookup_state`, `handle_retrieve_cookie_state`,
  `notify_retrieved_http_cookie`, `handle_fetch_state`,
  `handle_connect_state`); `log_chunk_stats` also emits the `wire^:` line.
- The per-request `WireStats` entry is created in the `Request`
  constructors (so `created_at` is the true creation time) and removed
  in `~Request`.

Process-wide diagnostic lines:

- `wire-batch:`, `wire-stall:`, `wire-burst:` are all in
  `Services/RequestServer/ConnectionFromClient.cpp`. The stall and burst
  detectors live at file scope as `note_event_tick`, `time_curl_call`,
  and `burst_state_by_client`; they are invoked from each event-handler
  entry point.
- `wire-pipe-pressure:` lives in `Request::write_queued_bytes_without_blocking`
  in `Services/RequestServer/Request.cpp`. It also feeds the back-pressure
  totals appended to `wire^:`.
- `LibDNS wire-dns:` lives in `Libraries/LibDNS/Resolver.h`, in
  `Resolver::lookup` — a `ScopeGuard` around the function body times the
  synchronous portion and classifies the resolution path.
- `UI wire-cookie:` lives in `Libraries/LibWebView/Application.cpp`, in
  the `on_retrieve_http_cookie` callback set up by `launch_request_server`.

All of the above lines are gated by the `REQUESTSERVER_WIRE_DEBUG`
debug macro (defined in `Meta/CMake/all_the_debug_macros.cmake`,
defaults to `ON`). To silence the whole subsystem at compile time,
set it to `OFF` and rebuild. Each individual line additionally
self-suppresses under its own threshold (100 ms event-loop gap,
50 ms curl call, 50 ms back-pressure window, 5 ms DNS sync, 5 ms
cookie jar) so even with the macro on, the log only fires when
something is interesting. Adjust the per-line thresholds in the
source if you want them tighter or louder.

---

### Documentation ScreenReaderSupport (`Documentation/ScreenReaderSupport.md`)

# Screen-reader support

Ladybird exposes its internal accessibility tree to the Orca and VoiceOver screen readers — and to other platform assistive technologies (AT) — by serializing tree data from the WebContent process and presenting it through platform-specific wrapper objects in the UI process.

On macOS, we use `LadybirdAccessibilityElement`, an Objective-C wrapper that implements the `NSAccessibility` protocol. It bypasses Qt’s `QMacAccessibilityElement` Cocoa bridge almost entirely: an NSView overlay hosts the wrapper objects, and a few very small, targeted swizzles on Qt’s Cocoa classes redirect Qt’s bridge to that overlay.

On Linux, we use Qt’s built-in AT-SPI2 bridge (`QSpiAccessibleBridge`) — by way of `AccessibilityInterface` (a `QAccessibleInterface` wrapper), along with a custom Orca script.

> [!NOTE]
> Qt’s built-in AT-SPI2 bridge implements direct communication over the Linux AT-SPI2 D-Bus accessibility protocol — rather than, say, making calls using the ATK C API, which is what Firefox and Chromium use for AT-SPI2 D-Bus communication.

The core underlying “infrastructure” code for IPC serialization, tree management, and WebContent-side tree building is shared across all platforms and ports/UIs.

## Key technologies

This section explains key technologies referenced throughout the rest of the document.

### AT-SPI2

AT-SPI2 is the Linux desktop-wide accessibility-bus protocol: a D-Bus protocol through which applications publish an object tree. Platform assistive technologies (AT) — screen readers, screen magnifiers, accessibility-tree inspectors — connect to the same bus and query that object tree.

The protocol defines interfaces on each accessible object: `Accessible`, `Text`, `Component`, `Action`, `Hypertext`, `Hyperlink`, `Document`, `Table`, `Value`, `Selection`, and a few others. Each interface is a D-Bus interface with methods like `GetText(start, end)` or `GetCaretOffset()`.

There is no AT-SPI2 spec; instead, the protocol is defined by its reference implementation, [at-spi2-core](https://gitlab.gnome.org/GNOME/at-spi2-core). The canonical surface for the AT-SPI2 protocol is that `at-spi2-core` reference implementation’s set of D-Bus interface XML files: `Accessible.xml`, `Text.xml`, `Component.xml`, `Hypertext.xml`, `Hyperlink.xml`, `Document.xml`, `Cache.xml`, and so on).

The client-side C library for AT-SPI2 is **`libatspi`**, which wraps the raw D-Bus calls in a GObject API; `pyatspi` is its Python binding, and Orca uses both. [A libatspi API reference](https://docs.gtk.org/atspi2/) documents the same interfaces from the client side, and the [ATK API reference](https://docs.gtk.org/atk/) complements that — because AT-SPI2 interfaces mirror their ATK counterparts almost one-to-one, so the ATK docs effectively describe the semantics of the matching AT-SPI2 methods.

### ATK

[ATK](https://docs.gtk.org/atk/) is a C/GObject API that actually predates AT-SPI2 and that’s historically been the shape of how GTK apps expose or “serve” their accessibility trees to clients. An ATK object is a GObject subclass which implements interfaces like `AtkText`, `AtkComponent`, etc. There’s a separate `libatk-bridge-2.0` library that takes an ATK tree at runtime and republishes it over the AT-SPI2 D-Bus to clients. Chromium, Firefox, and GTK3 apps (though not GTK4 apps) all implement ATK objects to serve/expose their accessibility trees, and rely on `libatk-bridge-2.0` to handle the D-Bus-side client communication.

### Qt AT-SPI2 bridge

Qt’s AT-SPI2 bridge (`QSpiAccessibleBridge`, compiled into `QtGui.so`) plays the same role that `libatk-bridge-2.0` plays for ATK-based apps: It takes a tree of `QAccessibleInterface` objects (Qt’s platform-agnostic accessibility API) and talks AT-SPI2 D-Bus directly — _without_ going through ATK or `libatk-bridge-2.0`.

The Qt AT-SPI2 bridge activates automatically when Qt detects a screen reader on the session bus, and there’s no supported way to disable it — and that constraint drives many of the design decisions recorded in other parts of this document.

### Orca and VoiceOver

The GNOME project’s **Orca** utility is the standard screen reader on Linux. It’s an AT-SPI2 client written in Python, uses `pyatspi`/`libatspi` to handle the AT-SPI2 communication with applications, and is what ultimately announces the accessibility tree to users. Orca has an extension system: Per-application Python modules/scripts in `~/.local/share/orca/orca-scripts/` that subclass `orca.scripts.default.Script` (or `web.Script` for browsers) to customize behavior for a specific application.

Apple’s **VoiceOver** utility is the standard screen reader on macOS. It talks to applications via the NSAccessibility protocol (the macOS equivalent to AT-SPI2).

The view of web content that Orca and VoiceOver “see” is a _snapshot_ of the accessibility tree — roles, names, bounds, states, and text — exposed by the browser engine to the browser’s UI process. The browser UI process transforms the accessibility tree into platform-specific AT protocol traffic, and Orca and VoiceOver receive that traffic and stitch the tree back together from it, and then speak the contents of the tree to users.

On Linux, the Qt AT-SPI2 bridge is a component in our Qt-based browser UI process which turns the Qt-side accessibility tree (`QAccessibleInterface` wrappers that get built around accessibility data from the browser engine) into AT-SPI2 D-Bus traffic.

On macOS, there is no separate bridge component: `LadybirdAccessibilityElement` (NSObject wrappers in the UI process, built around accessibility data from the browser engine) implements the NSAccessibility protocol directly — and VoiceOver uses the NSAccessibility protocol to query those wrappers across process boundaries.

## Architecture

```
/* Detailed source-code truncated for AI context efficiency. */
```

That mostly follows the Chromium/Firefox model rather than the WebKit model:

- No dependency on private Apple APIs (WebKit uses the private `NSAccessibilityRemoteUIElement` API)
- The accessibility wrapper objects live in the UI process
- The platform-agnostic parts (`AccessibilityNodeData` IPC data format, IPC endpoints, WebContent-side serialization, `AccessibilityTreeManager` UI-process tree manager) are shared between the two platforms

On Linux specifically, the Qt AT-SPI2 bridge and the custom Orca script work as a pair: the bridge publishes our tree over AT-SPI2 D-Bus in whatever shape we can give it through `QAccessibleInterface`, and the Orca script closes the gaps that remain on the Orca side (through Python subclass overrides and a handful of runtime monkey patches).

## IPC data format: AccessibilityNodeData

`AccessibilityNodeData` (in `Libraries/LibWebCommon/WebView/`, which both WebContent and the UI link) is the serialization format. Each node carries:

- `id` / `parent_id` / `child_ids` — tree structure via flat ID references
- `role` — ARIA role string (`button`, `heading`, `banner`, etc.)
- `name` / `description` / `value` — accessible name, description, value
- `bounds` — bounding rect in CSS pixels (viewport-relative)
- `is_focused` / `is_disabled` / `is_editable` / `is_multi_line` / `is_read_only` / `is_required` / `is_invalid` / `is_multi_selectable` / `is_pressed` / `is_visited` — individual state bits
- `heading_level` — for `h1`—`h6` and explicit `aria-level`
- `live` — `aria-live` value (`assertive`, `polite`, or empty)
- Table geometry: `column_span`, `row_span`, `cell_row_index`, `cell_column_index`, `table_row_count`, `table_column_count`, `column_header_ids`, `row_header_ids`, `table_caption_id`, `table_summary`
- `keybinding`, `checked_state`, `expanded_state`
- Text formatting: `font_family`, `font_size`, `font_weight`, `font_style`, `color`, `background_color`, `text_decoration`
- Caret/selection: `caret_offset`, `selection_start`, `selection_end`
- Text layout: `character_offsets` (per-character viewport positions), `line_break_character_offsets`, `line_heights` — used by macOS text-marker navigation

The IPC payload is a flat `Vector<AccessibilityNodeData>` with parent/child ID references, rather than a nested tree. That simplifies IPC encoding and lets the `AccessibilityTreeManager` build its own lookup tables.

## IPC endpoints

| Endpoint | Direction | Purpose |
| -------- | --------- | ------- |
| `request_accessibility_tree(page_id)` | UI —> WebContent | Request full tree snapshot |
| `perform_accessibility_action(page_id, node_id, action)` | UI —> WebContent | AT-triggered action: `press`, `click`, `focus`, `scroll_into_view` |
| `inspect_accessibility_tree(page_id)` | UI —> WebContent | Debug JSON dump for DevTools |
| `did_get_accessibility_tree(page_id, nodes)` | WebContent —> UI | Full tree response (also used for live updates) |
| `did_accessibility_focus_change(page_id, node_id)` | WebContent —> UI | Focus changed |
| `did_inspect_accessibility_tree(page_id, json)` | WebContent —> UI | Debug JSON response |

See the _Linux: QAccessibleInterface with custom Orca scripts_ section below for what `perform_accessibility_action`’s `scroll_into_view` value does — and why it’s distinct from `focus`.

## WebContent-side serialization

`AccessibilityTreeNode::serialize_tree_as_node_data()` walks the existing GC-managed accessibility tree (the same tree used for DevTools JSON inspection) and produces the flat vector. For each node:

- Role from `element—>role_or_default()` via `ARIA::role_name()`
- Name/description from the existing ACCNAME 1.2 implementation
- Bounds from `element—>get_bounding_client_rect()`
- Heading level from `aria_level()` (works for both `h1`—`h6` and `role="heading" aria-level="N"`)
- Live value from explicit `aria-live`, or implicit from role (alert —> assertive, status/log/marquee/timer —> polite)
- Table geometry from the grid layout
- Text formatting from computed style

`update_layout()` must be called before building the tree — because `exclude_from_accessibility_tree()` checks `layout_node()`, which asserts layout is up-to-date.

Text children of `head` (e.g., `title` text) are filtered out during serialization, since they’re not visible content. Text leaf nodes inherit their parent element’s bounding rect — since text DOM nodes don’t have their own layout box.

## UI-process tree manager: AccessibilityTreeManager

`AccessibilityTreeManager` (in `Libraries/LibWebView/`) is a platform-agnostic C++ class that caches the tree in the UI process. It provides:

- `node(id)` — lookup by node ID
- `root()` — document root node
- `hit_test(point)` — recursive hit testing (reverse child order, deepest node wins)
- `set_focused_node(node_id)` — updates focus tracking
- `text_leaves_in_order()` — flat DFS-ordered list of text leaf node IDs for text-marker navigation
- `update_tree(nodes)` — replaces the tree, with `aria-live` change detection: compares old and new node names, fires `on_live_region_changed` callback when content changes inside a live region

## macOS: NSAccessibility wrapper

`LadybirdAccessibilityElement` (in `UI/Qt/`) is an `NSObject` subclass implementing the `NSAccessibility` “informal” protocol. Each instance wraps one node by holding a node ID and a pointer to the `AccessibilityTreeManager`. Elements are cached in a dictionary keyed by node ID on the overlay view that hosts them (see _Linux: QAccessibleInterface with custom Orca scripts_ below).

### Why NSObject, not NSAccessibilityElement?

Chromium uses `NSAccessibilityElement` as its base class. We tried that but encountered two problems:

1. `NSAccessibilityElement`’s synthesized read-write properties (internal ivar storage) competed with our getter overrides — the accessibility system read the stored nil values rather than calling our methods.
2. `accessibilityParameterizedAttributeNames` overrides were never called — `NSAccessibilityElement` swallowed them.

`NSObject` with the “informal” protocol gives us full control. Tradeoff: we have to override certain “modern” property API methods that `NSAccessibilityElement` would provide automatically. In particular, we found that missing overrides for `accessibilityWindow` and `accessibilityTopLevelUIElement` caused VoiceOver to treat nested elements as orphaned (not part of any window) — breaking VoiceOver forward navigation into nested groups.

Likewise `accessibilityShouldUseUniqueId`, which `NSAccessibilityElement` answers `YES` and `NSObject` answers `NO`. Without a unique id, AppKit identifies an element to an assistive technology by its path of child indices from the window: Every request resolves that path again, asking each ancestor for its `AXChildren` on the way down — enough work on a big page for VoiceOver to report “Ladybird not responding”. And a handle VoiceOver holds then names a position, not an element. So `LadybirdAccessibilityElement` answers `YES`, as WebKit’s wrappers do, and gives the id up (`NSAccessibilityUnregisterUniqueIdForUIElement`) when its node leaves the tree or it’s deallocated — AppKit’s table of ids doesn’t retain the elements.

### Dual API support: “informal” and “modern”

The macOS accessibility system queries objects via two code paths:

1. **Older “informal” protocol** — `accessibilityAttributeValue:`, `accessibilityAttributeNames`, `accessibilityIsIgnored`
2. **“Modern” property API** — `accessibilityRole`, `accessibilityTitle`, `accessibilityChildren`, etc.

Both paths must return consistent data. The “informal” protocol is the source of truth, with “modern” property overrides that delegate to `accessibilityAttributeValue:`. All relevant modern overrides must be present; on `NSObject`, the defaults return nil, and VoiceOver may treat nil-returning elements as invalid.

### Role mapping

ARIA roles map to `NSAccessibility` roles via `aria_role_to_ns_role()`. Landmark roles (banner, navigation, main, etc.) map to `NSAccessibilityGroupRole`, with `AXLandmark*` subrole strings. `NSAccessibilityRoleDescription()` doesn’t recognize the `AXLandmark*` subrole strings (it returns `group` for all of them) — so custom role description strings are provided, matching what Safari, Chrome, and Firefox all do.

The `document` role maps to `@"AXWebArea"` with role description `@“HTML content”`. That was an undocumented private WebKit string adopted by all browser engines until Apple made it a public constant (`NSAccessibilityWebAreaRole`) in macOS 26. Without `AXWebArea`, VoiceOver treats the entire page as a generic group and summarizes it — rather than offering web-specific navigation (rotor, search predicates, text markers).

### Tree exclusion

Per the [ARIA spec’s tree-exclusion rules](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), elements in the following cases are excluded from the accessibility tree during `build_accessibility_tree()`, along with all their descendants:

- Elements with no layout node (`display:none`, HTML `hidden` attribute)
- Elements with `visibility:hidden` or `visibility:collapse` (those have layout nodes but aren’t visually perceptible)
- Elements with `aria-hidden=true`, including descendants (checked via `Element::is_aria_hidden()`, which walks up the ancestor chain)

Elements with `role=none` or `role=presentation` are also excluded, but their children are promoted to the parent (per the spec: _“their descendants and text content are generally included”_). If such an element has global ARIA attributes (like `aria-label`), the presentational role is overridden and the element isn’t excluded.

### Ignored-element handling

Elements with certain roles are “ignored” — transparent to VoiceOver. Their children are promoted to the nearest non-ignored ancestor. The `is_ignored_role()` static function centralizes this check:

- `generic` role with no name (HTML `div`, `span`, `body`)
- `paragraph` role with no name

That check is used in four places (all calling the same function):

- `accessibilityIsIgnored`
- parent walking in `accessibilityParent`
- `collectUnignoredChildren`
- the search-predicate DFS

Parent-child consistency is maintained: if element C appears in parent P’s `accessibilityChildren`, then C’s `accessibilityParent` returns P.

### VoiceOver navigation via search predicates

VoiceOver navigates web content (VO+Right/Left) via the `AXUIElementsForSearchPredicate` parameterized attribute, not via basic tree traversal. Without search predicates, it only does sibling navigation within groups.

`AXUIElementsForSearchPredicate` must be advertised by `accessibilityParameterizedAttributeNames` on every element. VoiceOver checks that list on the current element before calling it. If a nested group element doesn’t advertise search predicates, VoiceOver falls back to sibling-only navigation within that group.

The search-predicate implementation does a pre-order depth-first traversal. Two important behaviors:

1. **Leaf-like roles are navigation terminals.** Links, buttons, headings, menu items, tabs, radio buttons, checkboxes, and images don’t have their children included in the navigation order. Their accessible name already carries the text content. Without that, for e.g., a link with the text _“Planning”_, VoiceOver would visit both, _“link Planning”_ and _“text leaf Planning”_ — as separate stops.

2. **Container descendants are skipped.** When navigating forward past a container element (e.g., VoiceOver exits a `nav` and the parent’s search predicate receives the `nav` as the start element), all descendants of the start element are skipped. In pre-order DFS, descendants form a contiguous block after their ancestor — so if we _don’t_ skip that block, VoiceOver reads it out twice.

`shouldGroupAccessibilityChildren` returns `NO` on all elements — to prevent VoiceOver from grouping children by screen position rather than document order.

### Actions

`accessibilityPerformAction:` handles `NSAccessibilityPressAction` by sending a `perform_accessibility_action` IPC with action `press`. On the WebContent side, `HTMLElement::click()` is called on the target node, looked up by `Node::from_unique_id(UniqueNodeID)`. `accessibilitySetValue:forAttribute:` handles the focused attribute by sending action `focus`, which calls `HTML::run_focusing_steps()` on the target element.

### Table navigation

Table-specific attributes are returned conditionally based on role:

- **Table elements:** `AXRowCount`, `AXColumnCount`, `AXRows`, `AXVisibleRows`, `AXColumns`, `AXVisibleColumns`, `AXHeader` (computed from the tree structure)
- **Row elements:** `AXIndex` (zero-based row index spanning rowgroups)
- **Cell elements:** `AXRowIndexRange`, `AXColumnIndexRange` (position and span)

### Coordinate conversion

Element bounds arrive as CSS pixel rectangles relative to the viewport. On macOS, CSS pixels equal points (the `NSView` coordinate unit), so no device-pixel-ratio scaling is needed:

```objc
NSRect window_rect = [self convertRect:viewRect toView:nil];
return [self.window convertRectToScreen:window_rect];
```

### Text markers

Text markers enable cursor-level text navigation. Each marker encodes a node ID (i64) and a character offset (i32), in a 12-byte `AXTextMarkerRef`. Text-marker attributes are exposed only on the document root element (`AXWebArea` role) — not on structural containers. We found that exposing them on every element caused VoiceOver to treat containers as text content.

On the `AXWebArea` itself, three text-marker-related attributes work together to give VoiceOver a starting cursor position: `AXStartTextMarker` (the document beginning), `AXEndTextMarker` (the document end), and `AXSelectedTextMarkerRange` — a zero-width range at the first text leaf. Without `AXSelectedTextMarkerRange`, on page load, VoiceOver fires its load-complete handler but has no anchor for its cursor — which makes it fail to move into the web-content area.

### `AXWebArea` attributes for VoiceOver focus-on-load

Beyond text markers, the document `AXWebArea` element exposes a small set of attributes that VoiceOver uses to recognize it as the active reading target after page load:

- `AXFocused`: returns `YES` exactly when the `AXWebArea` is the page’s focused UI element — that is, when nothing in the page has the focus. VoiceOver expects the element the application reports as focused to say so itself. When it doesn’t, VoiceOver sets `AXFocused` on it, and then again after every tree update — announcing “web content” each time.
- `AXURL`: the page URL. VoiceOver compares this against its own page-identity tracking — to decide whether the load is a new page worth re-reading.
- `AXVisibleCharacterRange`: a non-empty range — so that VoiceOver doesn’t treat the `AXWebArea` as skippable empty content.

Together with `AXSelectedTextMarkerRange` and the `accessibilityFocusedUIElement` behavior described below, those attributes match what Safari and Chrome expose on their respective `AXWebArea` elements after page load. Without them, VoiceOver receives `AXLoadComplete` — but never moves its cursor into the web content.

### Live-region announcements

When `AccessibilityTreeManager::update_tree()` detects that a node’s `name` changed, and that node is inside a live region (has a non-empty, non-“off” `live` value in its ancestor chain), it fires the `on_live_region_changed` callback. `post_accessibility_announcement()` (in `WebContentViewAccessibility.mm`) posts `NSAccessibilityAnnouncementRequestedNotification` on the main window with `NSAccessibilityPriorityHigh` for “assertive” or `NSAccessibilityPriorityMedium` for “polite”.

### Live DOM-mutation updates

Once the UI has requested the tree, WebContent pushes a rebuilt tree via `did_get_accessibility_tree` whenever the DOM changes, and after every accessibility action (press, focus) — since an action can change what the tree reports without mutating the DOM. `PageClient::schedule_accessibility_tree_update()` debounces the rebuilds with a 200ms timer.

Until that first request, WebContent reports nothing: `Page::accessibility_interested()` gates the tree pushes and the focus-change reports alike, and only a tree request sets it. And the UIs request the tree only once an assistive technology is present — so users without one pay for neither, just as in Gecko, WebKit, and Blink, none of which report a focus change before an assistive technology turns accessibility on.

### WebContentView integration

`WebContentView` (`UI/Qt/WebContentView.cpp`) owns the `AccessibilityTreeManager`, and `WebContentViewAccessibility.mm` hosts the wrapper objects: its `WebContentAccessibilityView` overlay acts as the scroll-area container, with a single child, the document root `LadybirdAccessibilityElement`. The wiring:

- Load finish (a navigation listener) → `request_accessibility_tree()`, but only while an assistive technology is present, which Qt reports as `QAccessible::isActive()`: on macOS, `-[QNSView activateQtAccessibility]` turns that on at the first accessibility query into any Qt window. Without one, the view never requests the tree — so WebContent never sends it tree updates or focus changes (see _Live DOM-mutation updates_ above).
- An assistive technology that turns up later → `accessibilityActiveChanged()` (the view is a `QAccessible::ActivationObserver`) → `request_accessibility_tree()`, if the view hasn’t requested the tree yet. That tree doesn’t take the first responder.
- `on_accessibility_tree_received` → update manager; then `update_accessibility_tree()` drops the cached elements of the nodes that left the tree (invalidating them first) and keeps the rest, so an assistive technology’s handles survive a DOM mutation. For the first tree after each load only, it makes the widget’s NSView the window’s first responder, so AppKit resolves `accessibilityFocusedUIElement` through it (and thus into the `AXWebArea`); posts `@"AXLoadComplete"` (matching what Chromium and Firefox do); and calls `NSAccessibilityHandleFocusChanged()` — so AppKit refreshes its cached focused-UI-element. Any other tree posts no notification at all, and no tree posts `NSAccessibilityLayoutChangedNotification`: VoiceOver answers that one by moving its cursor back to the `AXWebArea`, announcing “frame” — and by setting `AXFocused` there, which takes the focus from whatever element in the page had it.
- `on_accessibility_focus_changed` → update manager, post `NSAccessibilityFocusedUIElementChangedNotification` on the node’s element (`post_accessibility_focus_changed()`)
- `on_live_region_changed` → post `NSAccessibilityAnnouncementRequestedNotification` (`post_accessibility_announcement()`)

`accessibilityFocusedUIElement` returns the focused DOM element when one is focused (e.g., an `<input>` the user clicked into) — and otherwise returns the `AXWebArea` (document root). That matches what Safari and Chrome also do. Returning a leaf (heading, link, etc.) instead causes VoiceOver to interpret the focus as “user already navigated to this element” — which makes it unexpectedly skip its read-from-document-beginning behavior.

## macOS: the overlay and the swizzles

A `WebContentAccessibilityView` (NSView overlay) conforms to `LadybirdAccessibilityViewProtocol`, the protocol through which `LadybirdAccessibilityElement` reaches its host view, and provides the scroll-area container, element cache, coordinate conversion, and search predicate delegation.

The overlay is added as a subview of the QWidget’s underlying NSView (obtained via `QWidget::winId()`). It overrides `isFlipped` to return `YES` so that Cocoa’s coordinate conversion matches CSS’s top-left origin.

### The swizzles on Qt’s Cocoa classes

VoiceOver in a Qt app navigates through `QMacAccessibilityElement` objects (created by Qt’s cocoa bridge) — not through NSViews. Our overlay is an NSView, so VoiceOver cannot discover it through the view hierarchy alone. Three very small, targeted swizzles on `QMacAccessibilityElement` connect Qt’s bridge to the overlay:

1. **`accessibilityRole`**: Returns `@"AXWebArea"` for elements that back a `WebContentView`.
2. **`accessibilityChildren`**: Returns the overlay (which contains the root `LadybirdAccessibilityElement`) as the sole child.
3. **`accessibilityFocusedUIElement`**: Delegates to the overlay’s `accessibilityFocusedUIElement`.

A fourth, on `QNSView`, covers the application’s focused element: AppKit resolves that through the key window’s first responder, which for a Qt window is its `QNSView`, and Qt answers with the element of the widget that has the keyboard focus, never looking inside it. The override on `QNSView`’s `accessibilityFocusedUIElement` (added to that class alone, falling through to `NSView`’s) answers, for a web view, with what its overlay would: the focused DOM element, or the `AXWebArea` — the answer Chrome’s `RenderWidgetHostViewCocoa`, also the first responder, gives from its `accessibilityFocusedUIElement`.

All other accessibility semantics — role mapping, landmark subroles, role descriptions, search predicates, ignored-element handling, hit testing, actions, text markers — are handled by `LadybirdAccessibilityElement`.

A minimal `WebContentViewAccessible` (`QAccessibleWidget` with `Grouping` role, no children) is registered via `QAccessible::installFactory()` so Qt’s bridge creates a `QMacAccessibilityElement` for the swizzles to act on.

### Why swizzles are needed (and alternatives that were tried)

Several swizzle-free approaches were tested:

- **Overlay only** (no swizzles, no `makeFirstResponder`): VoiceOver never discovers the overlay. It stays in the browser UI.
- **`makeFirstResponder` on the overlay**: VoiceOver discovers the overlay and announces “web content”, but enters an infinite re-entry loop (repeating “web content” endlessly). The loop appears to be caused by VoiceOver’s web-content-mode entry logic conflicting with the overlay being the first responder.
- **`makeFirstResponder` without `NSAccessibilityHandleFocusChanged`**: VoiceOver never discovers the overlay.
- **`object_setClass` per-instance subclass injection on the QWidget’s NSView**: Crashes in `QCocoaWindow::safeAreaMargins()` because the class change breaks Qt’s internal NSView state.
- **Swizzling QNSView** (the QWidget’s NSView class) instead of `QMacAccessibilityElement`: Breaks accessibility for the entire application because QNSView is shared by all widgets.

The three swizzles on `QMacAccessibilityElement` are the minimum that works for navigation. They are targeted (only affect elements backing a `WebContentView`), stable (operate within Qt’s existing bridge mechanism), and very small and simple (each is ~5 lines that delegate to the overlay).

### Advantages of the overlay approach

An earlier implementation used a `QAccessibleInterface` subclass (770 lines) that reimplemented all the role mapping, ignored-element handling, text content, actions, and tree navigation that `LadybirdAccessibilityElement` already provided (for the AppKit port, which Ladybird had at the time). 7 runtime swizzles on `QMacAccessibilityElement` patched Qt’s bridge to add `AXWebArea`, landmark subroles, role descriptions, search predicates, focused-element handling, and `ListItem` ignore behavior.

The overlay approach replaced all of that:

| | Earlier approach | Current overlay approach |
|---|---|---|
| macOS Qt a11y code | `AccessibilityInterface` (770 lines) + 7 swizzles (464 lines) = **1,234 lines** | Overlay + four swizzles (**~520 lines**) |
| Shared code | `AccessibilityTreeManager` only | `AccessibilityTreeManager` + `LadybirdAccessibilityElement` (all semantics) |
| Swizzles on `QMacAccessibilityElement` | 7 (role, subrole, roleDescription, parameterizedAttributeNames, attributeValue:forParameter:, focusedUIElement, isIgnored) | 3 (role, children, focusedUIElement), plus 1 on `QNSView` (focusedUIElement) |
| What swizzles do | Reimplement web-content semantics inside the swizzle functions | Delegate to the overlay (which delegates to `LadybirdAccessibilityElement`) |
| Qt-specific role mapping | Full reimplementation in `AccessibilityInterface::map_role()` | None |
| Qt-specific search predicates | DFS traversal reimplemented in swizzled `accessibilityAttributeValue:forParameter:` | None |
| Element cache | `QHash<i64, AccessibilityInterface*>` with manual pruning | `NSMutableDictionary` on the overlay |
| Adding a new toolkit | Would require another full reimplementation | Reuse overlay pattern with new toolkit’s bridge |

The key insight: the earlier approach treated the Qt port as a separate accessibility implementation that happened to share `AccessibilityTreeManager`. The overlay approach treats `LadybirdAccessibilityElement` as the single source of truth for macOS accessibility semantics, with the overlay acting as a thin adapter between Qt’s bridge and the shared elements.

## Linux: QAccessibleInterface with custom Orca scripts

On Linux, we use `QAccessibleInterface` (`UI/Qt/AccessibilityInterface.cpp`) to expose the accessibility tree through Qt’s built-in AT-SPI2 bridge (`QSpiAccessibleBridge`). A custom Orca screen-reader script at `~/.local/share/orca/orca-scripts/Ladybird/` extends Orca’s `web.Script` to close gaps in Qt’s bridge and tune browse-mode behavior for web content.

The implementation is split between C++ (Qt/LibWeb) and Python (Orca):

- **C++ side** decides what the AT-SPI2 tree looks like (which nodes are exposed, what their text is, what children they have, which states and interfaces they advertise).
- **Python side** (the Orca script) sits inside Orca’s process and adjusts Orca’s own behavior where the default behavior is wrong for our tree shape or Qt’s bridge’s quirks.

What works:

- Say All (auto-reading through an entire document)
- Flat review (manually reading word-by-word/line-by-line)
- Object navigation through the accessibility tree
- Structural navigation (`H` key for headings, `K` for links, etc.)
- Table-cell navigation
- Focus ring shown during various forms of navigation
- Actions (press, focus, scroll-into-view) on all interactive elements
- Live-region announcements

### Tree-exposure rules (C++ side, at a glance)

The shape of the AT-SPI2 tree that Orca sees is determined by four overlapping decisions in `UI/Qt/AccessibilityInterface.cpp`:

1. **`is_ignored_role(role, name)`**. Returns true only for `generic` elements with no accessible name: `div`, `span`, unnamed `body`, etc. Ignored elements are not exposed; their children “bubble up” via `collect_unignored_children()` to the nearest non-ignored ancestor. Paragraphs are _not_ ignored — even when they have no name: Orca’s sentence-extension logic during Say All relies on seeing `ROLE_PARAGRAPH` in the tree to stop walking across paragraph boundaries. Without that role present, Orca would merge content across paragraphs up to the nearest heading.

2. **`is_leaf_like_container_role(role)`**. Returns true for roles that Orca reads as whole units: `link`, `button`, `heading`, `img`, `image`, `menuitem`, `tab`, `checkbox`, `radio`, `listitem`. For those, `build_hypertext()` flattens the full descendant text into a single string — so Orca gets readable content in one query, rather than a `See [U+FFFC] for details`-style string that, to expand, would require the AT-SPI2 Hypertext interface (but which the Qt AT-SPI2 bridge doesn’t support).

3. **`interface_cast(QAccessible::TextInterface)`**. Decides which roles advertise a Text interface over AT-SPI2. Text leaves always do. Leaf-like containers do (their flattened text is their content). `paragraph` is explicitly included — Orca’s `is_text_block_element()` heuristic requires `AXObject.supports_text(obj)` to return true for sentence-boundary logic to fire. `list` is explicitly _excluded_ — a list’s text over AT-SPI2 would be `[U+FFFC][U+FFFC][U+FFFC]…` (one marker per `listitem`), and without a Hypertext interface, Orca can’t expand those markers, and skips the entire list during Say All. Leaving the list without a Text interface makes Orca descend into listitem children naturally instead.

4. **`state().focusable`**. Set to true _only_ for roles that are genuinely keyboard-focusable (`button`, `link`, form controls, menu items, tabs, tree items, switches), plus any node that is actually editable or currently focused. Reporting `focusable=true` for every element (some simpler behavior we tried) was found to break Orca’s `is_text_block_element()` heuristic: that heuristic returns false for any focusable object, so paragraphs/headings/list items that claimed to be focusable were excluded from text-block treatment — collapsing sentence boundaries and leading to an unexpected “resume at the top of the document every time” Say-All bug.

Two helpers serve different callers:

- `collect_unignored_children(node)` – returns all non-ignored children, recursing through ignored intermediates. Used by `build_hypertext()` and `find_unignored_parent()` (that is, the internal tree view).
- `collect_exposed_children(node)` – returns what `child()` and `childCount()` advertise to AT-SPI2, and matches similar handling in Firefox.

### Text content: hypertext with flattening

`build_hypertext()` in `AccessibilityInterface.cpp` produces the text that `QAccessibleTextInterface::text(start, end)` returns for a node:

- **Text leaves**: just the leaf’s own text.
- **Leaf-like containers** (listitem, heading, link, button, image, menuitem, tab, checkbox, radio): `flatten_descendant_text()` recursively concatenates every descendant’s text. The node presents one continuous string, with no U+FFFC markers.
- **General containers** (paragraph, section, table cell, etc.): Returns what Orca expects for a paragraph like `<p>Alpha <a>aaa</a> more.</p>`. What Orca sees for that is `Alpha [U+FFFC] more.` — and Orca’s `find_next_caret_in_order` is supposed to expand the [U+FFFC] marker querying the AT-SPI2 Hypertext interface. But because Qt’s bridge doesn’t implement that, if we returned the default expansion None, that’d cause Say All to skip the link entirely and continue with the text after the U+FFFC.

### Focus, and the “accessibility focus target” pseudo-state

Two kinds of “focus” exist in the Linux flow:

- **DOM focus** – the element the keyboard user has focused (via `Tab`, `click`, or JS). Painted as `:focus-visible` by the existing CSS machinery. Orca browse-mode navigation must _not_ move this; the user expects `Enter` to activate whatever _they_ focused with the keyboard, regardless of where Orca is reading.
- **Accessibility focus target** – the element Orca is currently speaking/reviewing. The Linux flow paints a focus ring on this target so the screen reader’s current position is observable visually. DOM focus is unchanged.

The “accessibility focus target” behavior is implemented by a new `Document::set_accessibility_focus_target(Element*)` member on `Libraries/LibWeb/DOM/Document.h`, and a matching pseudo-class match in `Libraries/LibWeb/CSS/SelectorEngine.cpp`: `:focus-visible` now also matches when `document.accessibility_focus_target() == &target.element()`. The existing focus-ring UA stylesheet rule is reused — no new pseudo-class, no new outline style. Style invalidation mirrors `Document::set_target_element()`, so the ring repaints on both the old and new targets.

The target is set by a new `scroll_into_view` accessibility action on the WebContent side:

```
perform_accessibility_action(page_id, node_id, "scroll_into_view")
  —> Element::scroll_into_view(Nearest/Nearest)
  —> Document::set_accessibility_focus_target(element)
```

Nothing about DOM focus changes. The ring appears because the selector now matches. When Orca moves on, the action fires again on the new target and the ring moves with it.

The route that ends up calling that action is:

```
Orca structural nav (H/K/I/L) or Say All iteration
  —> AXEventSynthesizer.scroll_to_center(obj)               [Orca side]
  —> AXText.scroll_substring_to_location(obj)               [added by our monkey patch]
  —> Atspi.Text.ScrollSubstringTo (D-Bus)
  —> AccessibilityInterface::scrollToSubstring              [Qt side]
  —> perform_accessibility_action(..., "scroll_into_view")
```

The monkey patch is necessary because Orca’s stock `scroll_to_center` uses `ScrollSubstringToPoint` — which Qt’s bridge doesn’t route to `QAccessibleTextInterface::scrollToSubstring`. Qt _does_ route `ScrollSubstringTo` (offset-based) — so we patch Orca to also call that (see the _Orca-side monkey patches_ section).

### The Orca script

Embedded as Qt resources and auto-installed on Ladybird startup to `~/.local/share/orca/orca-scripts/Ladybird/` — and only overwritten when the embedded content differs from what’s on disk) — three files:

- **`__init__.py`**: re-exports `Script` so Orca’s script loader can find it.

- **`script.py`**: `Script(web.Script, _QtToolkitScript)`. Overrides `get_toolkit_name()` (returns `"Ladybird"`), `get_utilities()` (returns our `Utilities`), `get_app_key_bindings()` (delegates to `web.Script`), `activate()`, and `on_focused_changed()`.

  The `activate()` override sets the structural navigator’s mode to `NavigationMode.DOCUMENT` on script activation. Without that, `H`/`K`/`L` stay unregistered until the user tabs into a focusable element inside the page — and pressing them on a freshly loaded page would echo the keystroke rather than structurally navigate.

  The `on_focused_changed` override causes browse-mode commands work from the very first document-root Focus event, while also ensuring that typing into the address bar and tabbing through chrome widgets don’t break.

- **`script_utilities.py`** — `Utilities(web.Utilities)`. Class-level overrides: `active_document()`, `get_caret_context()`, nd `_find_first_content_child()`. Also installs several runtime monkey patches at instance-construction time.

### Orca-side monkey patches

Each patch closes a specific gap. All are installed from `Utilities.__init__`, use module-level “patched already?” flags so they only take effect once per Orca session, and wrap `try/except` around both install and run paths so a patch that fails never takes Orca down with it.

1. **Collection fallback** (`_install_collection_fallback_patch`).

   Fixes Qt 6.10’s `Collection::GetMatches` returning empty even when matches exist. Forward-compatible: on Qt 6.11+ the original returns real results and the fallback branch is never taken.

2. **Scroll bridge** (`_install_scroll_patch`).

   Wraps `orca.ax_event_synthesizer.AXEventSynthesizer.scroll_to_center`. Makes the focus ring get painted during structural navigation and Say All.

3. **Say All redirect** (`_install_sayall_patch`).

   Wraps `orca.say_all_presenter.SayAllPresenter.say_all`. Makes Say All pick up where it last left off.

4. **Flat-review zone dedup** (`_install_flat_review_dedup_patch`).

   Wraps `orca.ax_utilities.AXUtilities._get_on_screen_objects`. Prevents Orca for repeating link text in list items.

5. **Flat-review document scope** (`_install_flat_review_document_scope_patch`).

   Wraps `orca.flat_review.Context._get_showing_zones`. Makes Orca’s flat review continue past the bottom of the current viewport.

6. **Flat-review auto-scroll** (`_install_flat_review_scroll_patch`).

   Wraps the cursor-advance methods on `orca.flat_review.Context`. Makes the viewport follow the flat-review cursor the same way it follows Say All.

7. **Hypertext fallback** (`_install_hypertext_fallback_patch`).

   Wraps `orca.ax_hypertext.AXHypertext.find_child_at_offset`, `get_link_start_offset`, `get_link_end_offset`, and `get_character_offset_in_parent`. Makes Say All read link text as expected.

### How the Orca script integrates with Orca

Orca historically loaded per-application scripts from `~/.local/share/orca/orca-scripts/`: the `orca_bin.py` script inserted that directory into `sys.path`, and for each application, [`script_manager`](https://gitlab.gnome.org/GNOME/orca/-/blob/main/src/orca/script_manager.py) tried `importlib.import_module("orca-scripts.<AppName>")` — and, if found, instantiated its `Script` class.

[That mechanism was removed in Orca 49.7](https://gitlab.gnome.org/GNOME/orca/-/commit/5a603529) - on the grounds that customization should go through `orca-customizations.py` instead.

To deal with that Orca change, we still ship our script in the same layout (`~/.local/share/orca/orca-scripts/Ladybird/`) — but re-register it with Orca via a bootstrap block that Ladybird writes into `~/.local/share/orca/orca-customizations.py` on first launch.

That bootstrap adds our `orca-scripts/` directory back to `sys.path` — then monkey-patches `ScriptManager._new_named_script` so that any app whose name matches a subdirectory in `orca-scripts/` loads that user-installed script in preference to a built-in one.

The managed region is delimited by `# --- Ladybird-bootstrap-begin ---`/`# --- Ladybird-bootstrap-end ---` marker lines — so anything the user has in the rest of `orca-customizations.py` is preserved across Ladybird launches.

After that loading occurs, the `Script` class in our `script.py` subclasses one of Orca’s built-in script base classes (`default.Script`, `web.Script`, or a toolkit script).

We use that mechanism in both “sanctioned” and “unsanctioned” ways:

- Sanctioned **subclass overrides** of `Script` (for `on_focused_changed`, etc.) and `Utilities` (for `active_document`, `get_caret_context`, `_find_first_content_child`). Those are documented/supported stable extension points. They’ll keep working as long as Orca preserves its documented script API.

- Unsanctioned **monkey patches** of Orca-internal classes (`AXUtilitiesCollection`, `AXEventSynthesizer`, `SayAllPresenter`, `AXUtilities`). The fact that we can do those is a consequence of Python’s dynamic nature and Orca’s script loader giving us the same address space as Orca itself. It’s _not_ a documented/supported API Orca intentionally exposes.

The two classes of change have different maintenance costs. Subclass overrides move with the script. Monkey patches depend on the names, signatures, and call sites of internal Orca classes — none of which the Orca project makes stability guarantees about.

If Orca renames `AXUtilitiesCollection`, restructures `SayAllPresenter`, or moves `scroll_to_center` out of `AXEventSynthesizer`, our corresponding patch will silently stop applying — and the feature it was working around will silently regress.

Every new Orca release needs the patches re-verified. See _Candidates for upstreaming into Orca itself_ below for which of our patches we believe Orca might accept upstream (and thereby let us delete on our side).

### Qt AT-SPI2 bridge limitations

The accessibility quality we can deliver for Orca users is bounded by `QSpiAccessibleBridge`’s coverage of AT-SPI2. Some gaps are worked around on the Orca side by our script; some can only be fixed by patching Qt itself; some are addressed upstream in Qt 6.11 compared to Qt 6.10; and a few remain as architectural properties of the Orca-plus-Qt combination even after patches.

#### User-visible features vs. what our script recovers

✅ — Our script enables the feature to work as expected.

❌ — Our script cannot recover the feature from the Orca side.

| Feature | | Details |
| ------- | - | ------- |
| Structural navigation (`H`/`K`/`I`/`L`/`B`/`F`/`T`/`R`/—) | ✅ | `_install_collection_fallback_patch` wraps `find_all_with_role` and `find_all_with_role_and_all_states` with a manual DFS fallback when `Collection::GetMatches` returns empty (Qt 6.10 and earlier) |
| `active_document()` for Say All and structural nav | ✅ | `Utilities.active_document()` override tries `super()` first, then cached, then tree-search by role with `is_showing` disambiguation |
| Say All starting in document content on first page load | ✅ | `_install_sayall_patch` redirects `SayAllPresenter.say_all` to the document’s first content child when locus is on chrome |
| Say All resumption after `Esc` | ✅ | Orca keeps locus inside the document during iteration; our tree-exposure rules let Orca’s sentence-extension logic break at paragraph boundaries |
| `get_caret_context()` returning document content instead of window title | ✅ | `Utilities.get_caret_context()` override |
| Focus ring during structural navigation (`H`/`K`/`I`/`L`) | ✅ | `_install_scroll_patch` + the `scroll_into_view` accessibility action + the `:focus-visible` extension described above |
| Flat review not re-reading embedded link text | ✅ | With text-leaf children hidden from AT-SPI2 for every container (matching Firefox’s `EmbeddedObjCollector`), text chunks and inline link zones interleave cleanly in the paragraph’s own zone. |
| Flat review reaching content below the viewport | ✅ | `_install_flat_review_document_scope_patch` expands `_get_showing_zones` so Orca+L keeps going past the last on-screen zone |
| Flat-review cursor auto-scrolling the page | ✅ | `_install_flat_review_scroll_patch` wraps `flat_review.Context.go_next_*`/`go_previous_*` methods — to match the scroll path Say All uses |
| Flat review reading wrapped text as visual lines | ✅ | Per-line `characterRect` plus overrides of `text*Offset` for `LineBoundary` let Orca cluster each paragraph’s chunks and link zones onto their actual visual lines, rather than flattening into one logical line |
| Browse-mode navigation (`H`/`K`/`L`) active from page load | ✅ | `Script.activate()` + `Script.on_focused_changed` set `NavigationMode.DOCUMENT` — so `H`/`K`/`L` work without you needing to manually navigate into a focusable in-page element first |
| Chrome widgets (address bar, tabs) interacting cleanly with structural nav | ✅ | `Script.on_focused_changed` override suspends structural nav for editable chrome, so tabbing through back/forward/tab-strip works |
| `AtkHypertext` / `AtkHyperlink` features — Say All walking into links, link-offset-aware navigation | ✅ | Qt’s bridge exposes no Hypertext/ Hyperlink interfaces. `_install_hypertext_fallback_patch` (re)computes offsets so that Say All can handle links correctly |
| Landmark type distinction | N/A | No patch needed: `xml-roles` object attribute comes from our `QAccessibleAttributesInterface` (C++ side), which works on any Qt 6.8+ |
| Heading-level announcement | N/A | Same: `level` object attribute |
| Orca’s “this is web content” detection | N/A | Same: `tag` object attribute (Orca’s `is_web_element()` checks for it) |
| `AtkDocument` features — document-level locale, MIME metadata | ❌ | Same reason; recoverable via the Qt appendix patch |

#### Qt-bridge bugs, by version

| Qt bridge limitation | Qt 6.10 | Qt 6.11 | Addressed by our Qt patches (appendix)? |
| -------------------- | ------- | ------- | --------------------------------------- |
| `Collection::GetMatches` returns empty | Broken | Fixed | N/A on 6.11; on 6.10, covered by the Collection fallback patch |
| `QSpiDBusCache::GetItems()` returns an empty array | Broken | Broken | Yes — Qt appendix patch 1 |
| `RELATION_EMBEDS` not exposed from window to `WebDocument` child | Missing | Missing | Yes — Qt appendix patch 2 |
| `AtkDocument` interface not exposed on document root | Missing | Missing | Yes — Qt appendix patch 3 (Document portion) |
| `AtkHypertext` / `AtkHyperlink` interfaces not exposed on text containers | Missing | Missing | Worked around from the Orca side via `_install_hypertext_fallback_patch` (enough for Say All link reading and sentence extension). Qt appendix patch 3 is a deeper fix, but it’s blocked by a `libatspi` wrapping bug for full Orca utility |
| `ScrollSubstringToPoint` not routed to `QAccessibleTextInterface::scrollToSubstring` | Broken | Broken | Worked around from the Orca side via `_install_scroll_patch`; a Qt fix would also be welcome |

The only accessibility-related improvement in the Qt 6.11 cycle that materially affects Orca is the `Collection::GetMatches` fix above: 6.11 added a `QSpiMatchRuleMatcher` class (`src/gui/accessible/linux/qspimatchrulematcher.cpp`) that makes `Collection::GetMatches` return real results. On 6.10 the Collection _interface_ was registered but the _method_ returned an empty array — which is why the Orca script carries `_install_collection_fallback_patch`: without it, structural navigation is otherwise entirely dead on 6.10.

#### The AT-SPI2 client-side cache and why `GetItems()` matters

AT-SPI2 defines a `Cache` D-Bus interface with a `GetItems()` method. It has two sides:

- **Server side** (in the application’s process): the AT-SPI2 bridge walks the app’s accessibility tree and returns a bulk snapshot — every object’s D-Bus path, parent, children, supported interfaces, name, role, description, state.

- **Client side** (in Orca’s process): `libatspi` calls `GetItems()` when it first encounters an application, stores the response in a local hash table, and serves subsequent queries for role/name/state/children from that table — **with no D-Bus round-trips**. `AddAccessible` and `RemoveAccessible` signals keep the table in sync as the tree changes.

Firefox and Chromium use `libatk-bridge-2.0` as their AT-SPI2 bridge. Its `GetItems()` returns a fully-populated array — so Orca’s client-side cache works as intended. Qt’s `QSpiDBusCache::GetItems()` returns an _empty_ array. `libatspi` has no cached data; **_every_ query is a D-Bus round-trip** — including for static metadata like role and name.

And that “every query is a D-Bus round-trip” problem is the root cause of the multi-second delay we encounter on first Say All: Orca’s web script builds each line of content by querying role, name, text, states, and attributes on each element — and the Qt architecture, _every_ one of those queries crosses the process boundary. Qt appendix patch 1 addresses that by implementing a real `GetItems()` response.

> [!NOTE]
> Both `libatk-bridge-2.0` and Qt’s `QSpiAccessibleBridge` run inside the application’s process — they are both “in-process” bridges relative to the application. The performance difference is not about _where_ the bridge runs; instead it’s about whether the bridge properly implements `GetItems()`, and thereby populates `libatspi`’s client-side cache in Orca’s process.

#### Limitations that remain even with patches

- **First Say All has a visible startup pause.** Even with `GetItems` populated (Qt patch 1), text and geometry queries (`GetText`, `GetCharacterExtents`, `GetRangeExtents`) are still per-element D-Bus calls, by design; they are too expensive to pre-serialize into the cache reply. Combined with Orca’s own Python and GLib event-loop overhead and `speech-dispatcher` startup, some user-visible delay on the very first Say All will remain. Firefox and Chromium avoid this because their ATK bridge pre-serializes more aggressively.
- **Stale AT-SPI2 proxy references after a tab switch.** Orca’s Python-side caches hold `Atspi.Accessible` proxies keyed by D-Bus object path. When Ladybird switches tabs, our `hideEvent` de-registers the inactive tab’s `QAccessibleInterface` wrappers (so the paths become invalid), and `scrollToSubstring` refuses to operate on non-visible views — but Orca’s Python proxies still point at the old paths.

  Say All on the second tab reads correct content (because `_patched_say_all` looks up the current tab’s document via `active_document()`) — but `scroll_into_view` calls on stale proxies can fail silently, so the focus ring may not appear for the first item after a tab switch. Fixing that would require changes to how Orca manages its proxy cache across tree transitions — not reachable from a Qt-bridge or application-side fix.

#### Orca-side patch lifecycle when Qt 6.11 is the baseline

What should happen to each Orca-side patch once Ladybird’s minimum Qt is 6.11 or later? Short answer: **keep all of them**. Longer answer:

- **`_install_collection_fallback_patch`**: _Keep until Ladybird drops support for Qt < 6.11._ On 6.11+, `Collection::GetMatches` returns real results; the fallback branch is never taken. The wrapper itself stays in the call chain but is dormant. Removing earlier would silently break structural navigation for users still on an older system Qt.
- **`_install_scroll_patch`**: _Keep until Qt adds `ScrollSubstringToPoint` handling._ Qt 6.11’s `atspiadaptor.cpp` still only handles `ScrollSubstringTo`, not `ScrollSubstringToPoint` (verified directly in the 6.11.0 source). Without the extra `scroll_substring_to_location` call, the focus ring during structural nav would stop working on Qt 6.11 too.
- **`_install_sayall_patch`**: _Keep indefinitely._ This patches Orca-side behavior (starting Say All from locus even when locus is on chrome). It is not a Qt-bridge bug; Qt versioning is irrelevant. It stops being needed only if Orca itself changes how it picks the Say All start element.
- **`_install_flat_review_dedup_patch`**: _Keep indefinitely._ This patches Orca-side behavior (flat review zone-per-object without dedup). Qt version is irrelevant.
- **`_install_flat_review_document_scope_patch`**: _Keep indefinitely._ Orca’s default `_get_showing_zones` uses the window rect, which is correct for native toolkit widgets but wrong for web content that scrolls. A Qt-version change won’t affect this. It could become unnecessary if Orca upstream gives web scripts a way to declare a larger review rect.
- **`_install_flat_review_scroll_patch`**: _Keep indefinitely._ Orca’s default flat review doesn’t auto-scroll; Say All does. Closing that gap is Orca-side behavior, not a Qt bug. Could become unnecessary if Orca upstream adopts the same scroll-on-advance behavior for flat review.
- **`_install_hypertext_fallback_patch`**: _Keep until Qt exposes `AtkHypertext`/`AtkHyperlink`_ The fallback is what lets Say All read link text in place. Qt appendix patch 3 addresses the underlying Qt-bridge gap, but a `libatspi` wrapping bug currently blocks full Orca utility even with that patch. So long as Orca relies on `AXHypertext` for hypertext offsets, the fallback stays useful.

The subclass overrides on `Utilities` and `Script` (`active_document`, `get_caret_context`, `_find_first_content_child`, `activate`, `on_focused_changed`) are normal Python OO method overrides on our extension classes, not runtime patches — they stay as long as the extension does.

#### Candidates for upstreaming into Orca itself

Which of the monkey patches would plausibly be accepted upstream by the Orca project, and thereby let us delete the corresponding wrapper on our side? Sorted by priority:

1. **`scroll_to_center` also calls `ScrollSubstringTo`.** Our `_install_scroll_patch` adds a `scroll_substring_to_location` call after the existing `ScrollSubstringToPoint` attempt. The added call is harmless on toolkits that already handle `ScrollSubstringToPoint` — and recovers focus-ring behavior on toolkits (Qt) that don’t. No plausible downside. One-line upstream fix. Upstream source: `orca/ax_event_synthesizer.py::AXEventSynthesizer.scroll_to_center`.

2. **Flat-review auto-scroll.** Our `_install_flat_review_scroll_patch` wraps `flat_review.Context.go_next_*`/`go_previous_*` to scroll the current zone into view after each advance — matching Say All’s existing behavior. Orca-side concern; benefits every toolkit with a scroll-substring action on text interfaces (Firefox and Chromium included). Upstream source: `orca/flat_review.py::Context` advance methods, or `orca/flat_review_presenter.py` equivalents if upstream prefers the Presenter layer.

3. **Say All redirect when `obj` is None.** Our `_install_sayall_patch` makes `SayAllPresenter.say_all` find the active document’s first content child and start from there when called with `obj=None` _and_ locus is outside document content. Benefits any web context where the user invokes Say All without first clicking into the page; happens in Firefox and Chromium too — just less visibly because their AT-SPI2 trees come up faster. Upstream source: `orca/say_all_presenter.py::SayAllPresenter.say_all`.

4. **Flat review zone dedup.** Our `_install_flat_review_dedup_patch` filters zones whose text is contained in an ancestor zone’s text (scoped to leaf-like container ancestors). Orca-side behavior; benefits every browser engine whose leaf-like containers flatten descendant text — Firefox and Chromium have the same architectural setup. Upstream source: `orca/ax_utilities.py::AXUtilities._get_on_screen_objects`.

5. **Flat-review document scope.** Our `_install_flat_review_document_scope_patch` expands `_get_showing_zones` ’ bounding box to cover the web document, not just the viewport. Shape of fix depends on Orca’s intent. Upstream source: `orca/flat_review.py::Context._get_showing_zones`.

6. **Hypertext-offset fallback.** Our `_install_hypertext_fallback_patch` computes offsets from the parent’s text and exposed children when the underlying AT-SPI2 call returns -1/None. Orca could adopt the same fallback unconditionally. Would help any engine on a Qt-bridge-style toolkit — not just Ladybird. Upstream source: `orca/ax_hypertext.py::AXHypertext`.

7. **Collection fallback to manual tree search.** `_install_collection_fallback_patch` wraps `find_all_with_role` and `find_all_with_role_and_all_states`. As Qt 6.11 spreads, the affected case will shrink — but other AT-SPI2 implementations (custom toolkits, embedded environments, older Qt that distros may carry for years) would still benefit. Possible pushback: Orca maintainers may take the position that broken-Collection is a toolkit bug Orca shouldn’t paper over. Upstream source: `orca/ax_utilities_collection.py`.

8. **`active_document()` fallback to tree search.** Same shape of argument and same possible pushback as #7. Tree search is slower than the EMBEDS relation, so the override must be conditional (only fall back if EMBEDS returned nothing). Upstream source: `orca/scripts/web/script_utilities.py::Utilities.active_document`.

Probably not upstreamable:

- **`Script.on_focused_changed()` chrome-widget handling and un-suspend on document focus.** The un-suspend half is structurally similar to what `web.Script` already does — but depends on Ladybird-specific Focus events fired on the document root. The chrome-widget half depends on a clear chrome-vs-document distinction, which is browser-specific and entangled with `web.Script`’s existing focus state machine. Upstream would need a redesign against that state machine.
- **`Script.activate()` setting `NavigationMode.DOCUMENT`.** Needed here only because Ladybird posts a document-root Focus event that `web.Script` short-circuits on. If upstream stopped short-circuiting (or Ladybird stopped relying on the document-root event), the override would be moot.
- **`Utilities.get_caret_context()` fallback.** Possibly upstreamable, but the rationale is entangled with `active_document` and `_find_first_content_child`; untangling for upstream submission would be non-trivial.

The `_install_*_patch` _functions themselves_ (the runtime mechanism that mutates Orca’s classes via `setattr`) are scaffolding that exists only because the underlying fix isn’t upstream yet. Once any of the changes land in Orca itself, the corresponding wrapper becomes a no-op and can be removed.

### Alternative: the private-bridge prototype

Before the current Qt-bridge-plus-Orca-script design was settled on, we tried prototyping a different path: Implement a full ATK bridge ourselves, run it on a private D-Bus instance, and have our Orca script consult that private bus whenever Qt’s built-in bridge let Orca down.

The prototype looked like an attractive way to essentially make us match how every other major Linux browser works — modulo the fact that the other engines don’t need to implement their own private bridges to achieve it. The goal was to work around the three serious gaps in Qt’s bridge on Qt 6.10 that we had run into and that actively harm user experience of web content for Orca users: `Collection::GetMatches` returns empty, `Cache::GetItems` returns empty, and `AtkHypertext`/`AtkHyperlink`/`AtkDocument` aren’t exposed at all. The private bridge seemed like a possible way to close all three gaps at once — because we could use `libatk-bridge-2.0` to populate the cache and route all the interfaces correctly.

#### What the prototype was constituted from

On the Ladybird side, the private-bridge prototype added the following major new chunks of code:

- `PrivateAccessibilityBus` (~280 lines of C++ code). Spawned an isolated `dbus-daemon` for our Orca script to pick up.

- `AtkBridge` (~400 lines of C++ code). `dlopen`ed `libatk-bridge-2.0.so.0` at runtime (matching Firefox’s approach in `accessible/atk/Platform.cpp`), forked an `at-spi2-registryd` child onto the private bus, and ran a dedicated pthread that pumped the bridge’s private `GMainContext`.

- `LadybirdAtkObject` (~1900 lines of C++ code). Per-node GObject/ATK wrapper with dynamic GType registration per interface combination (the pattern Chromium uses in `AXPlatformNodeAuraLinux`). It implemented the full ATK surface Qt doesn’t expose — 11 interfaces: `AtkObject`, `AtkText`, `AtkHypertext`, `AtkHyperlink`, etc.

- `OrcaScripts` (additional ~675 lines of Python code). Connected to the private bus, used it as a fast oracle, and mapped results back to Qt-bridge `Atspi.Accessible` objects. Orca otherwise still navigated Qt-bridge objects normally; the private bus was only ever used for operations the Qt bridge did poorly.

Total additional footprint: ~3500 lines of new C++, Python, and build glue, plus two new runtime dependencies (`at-spi2-core` development headers and `libatk-bridge-2.0` at load time).

#### Why we abandoned the prototype

Four things came up in practice that made the cost-benefit calculation flip:

1. **The bridge needed a registryd on the private bus, not just a daemon** — and the process-lifecycle code we had to implement as as result of that ended up being relatively large and unwieldy.

2. **Private-bus registration didn’t come for free, either** — and relied on an undeclared ABI from a library whose maintainers explicitly don’t consider those symbols stable.

3. **The hoped-for performance win didn’t get delivered:** the very first Say All still had a noticeable startup delay — and ordinary arrow-key reading was already fast enough with the Qt bridge that the prototype didn’t make it noticeably any faster.

4. **We still needed all the same custom runtime-monkey-patching Orca scripts:** Because the private bus in practice could only be used as an _additional oracle_ — not a full replacement — Orca still received events from Qt’s bridge, and still needed the same monkey patches for Say-All-redirect, etc.

So in the end, all the additions — adding ~3500 lines of new code, two extra child processes, dependence on unexported ATK symbols, and no reduction in runtime-monkey-patching by our custom Orca scripts — resulted in neither any real end-user performance benefits, nor any new end-user features beyond what we could _already_ deliver with a far-smaller footprint and far-less-complicated architecture.

## macOS: Browser-engine source code research

The Ladybird accessibility implementation was informed by studying the Blink/Chromium, WebKit/Safari, and Gecko/Firefox accessibility code. On macOS, all three implement the macOS accessibility protocol directly — they create their own Objective-C wrapper objects (not going through a toolkit bridge), giving them full control over role mapping, search predicates, and tree traversal. None of them need “workarounds” for the issues Qt’s bridge creates (see _Qt: shared LadybirdAccessibilityElement on macOS_ above).

The remaining subsections of this section mostly provide macOS-specific details about the platform code for Chromium, Firefox, and WebKit. Information about the Linux platform code for Chromium and Firefox is in other parts of the document.

<details>
<summary><h3>Chromium</h3></summary>
Key files: `ui/accessibility/platform/ax_platform_node_cocoa.h` / `.mm`, `ui/accessibility/platform/one_shot_accessibility_tree_search.h` / `.cc`

**What we adopted:**

1. **IPC serialization model.** Chromium serializes the accessibility tree via Mojo IPC into the browser process. That is the fundamental architecture we adopted.

2. **`OneShotAccessibilityTreeSearch`.** Chromium implements `AXUIElementsForSearchPredicate` via a depth-first tree-search class that skips subtrees based on search criteria. That informed our search-predicate implementation — particularly the concept of “leaf-like” roles whose children aren’t individually navigable.

3. **`AXWebArea` role.** Chromium maps the document role to `@"AXWebArea"` (via `NSAccessibilityWebAreaRole`) — which causes VoiceOver to offer web-specific navigation features (rotor categories, search predicates, text markers). That behavior isn’t documented by Apple; it is inferred from observation.

4. **Custom landmark role descriptions.** Chromium has custom role-description strings for landmarks — rather than relying on `NSAccessibilityRoleDescription`, which doesn’t recognize `AXLandmark*` subrole strings. Landmark subroles are mapped via a static `BuildSubroleMap()` function.

**Where we diverge:**

1. **Base class.** `AXPlatformNodeCocoa` inherits from `NSAccessibilityElement`, not `NSObject`. We tried that but hit property-competition issues (see _“Why NSObject, not NSAccessibilityElement”_ above). Chromium makes `NSAccessibilityElement` work because it has a more elaborate initialization path and implements both the older “informal” protocol and “modern” property API in tandem.

2. **Dual API.** Chromium implements both the older “informal” protocol (`accessibilityAttributeValue:`, `accessibilityAttributeNames`, `accessibilityIsIgnored`) and the “modern” property API (`accessibilityRole`, `accessibilityTitle`, etc.) — with a migration flag to shift between them. We also implement both, but our informal protocol is the source of truth — with modern overrides delegating to it.

3. **Text markers on all elements.** Chromium exposes text-marker parameterized attributes (`AXTextMarkerForPosition`, `AXAttributedStringForTextMarkerRange`, etc.) on all elements — not just the document root. We restrict text markers to the `AXWebArea` element — because we found that exposing them on every element caused VoiceOver to treat structural containers as text content.

4. **No `shouldGroupAccessibilityChildren`.** Chromium doesn’t implement this method at all. We return `NO` on all elements — to prevent VoiceOver from grouping children by screen position, rather than document order.

5. **ListItem not ignored.** Chromium maps `ListItem` to `NSAccessibilityGroupRole` and doesn’t ignore it — it’s a full navigation stop. Chromium’s `OneShotAccessibilityTreeSearch` handles the navigation semantics so that VoiceOver doesn’t double-stop.
</details>

<details>
<summary><h3>WebKit</h3></summary>
Key files: `Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm`, `Source/WebCore/accessibility/mac/AccessibilityObjectMac.mm`, `Source/WebCore/accessibility/AXObjectCache.h`

**What we adopted:**

1. **Coordinate system.** By comparing against Safari’s element bounds in Accessibility Inspector, we confirmed that CSS pixels equal points on macOS — no device-pixel-ratio scaling is needed.

2. **Landmark role descriptions.** Safari provides custom role-description strings for landmarks — confirming that `NSAccessibilityRoleDescription()` doesn’t recognize `AXLandmark*` subrole strings.

**Architecture notes:**

1. **Private API.** WebKit uses Apple’s private `NSAccessibilityRemoteUIElement` Mach-port token-exchange API for cross-process accessibility. We chose not to use that approach.

2. **Search predicates.** `handleUIElementsForSearchPredicateAttribute()` parses VoiceOver’s search dictionary into an `AccessibilitySearchCriteria` struct and delegates to a multi-frame search engine. The search is more sophisticated than ours — it handles cross-frame searches and a wider set of search keys.

3. **Lazy cache.** `AXObjectCache` creates accessibility wrapper objects on demand via `getOrCreate()` and maps them via multiple lookup tables (Node → AXID, RenderObject → AXID). Objects are removed when the DOM node is destroyed.
</details>

<details>
<summary><h3>Firefox</h3></summary>
Key files: `accessible/mac/mozAccessible.h` / `.mm`, `accessible/mac/MOXAccessibleBase.h` / `.mm`, `accessible/mac/MOXSearchInfo.mm`

**What we adopted:**

1. **Same IPC model as Chromium.** Firefox serializes the accessibility tree from the content process to the parent process via IPDL IPC — the de facto standard for multi-process browsers.

2. **Pre-order depth-first traversal.** Firefox’s `Pivot` class (`accessible/base/Pivot.h` / `.cpp`) implements pre-order DFS for search predicates. `Pivot` is a stack-allocated tree searcher that takes a root accessible and traverses with `Next()`/`Prev()` using `PivotRule` subclasses. `MOXSearchInfo` creates specific `RotorRule` subclasses (`RotorRoleRule`, `RotorLinkRule`, `RotorControlRule`, etc. in `accessible/mac/RotorRules.h`) for each VoiceOver search key (`AXHeadingSearchKey`, `AXLinkSearchKey`, etc.).

3. **Search predicate parameters.** Firefox’s `MOXSearchInfo` processes `AXDirection`, `AXStartElement`, and `AXResultsLimit` from the search-predicate dictionary — the same fields we handle.

**Where Firefox aligns with us (and differs from Chromium):**

1. **NSObject base class.** Firefox’s `mozAccessible` inherits from `NSObject` (via `MOXAccessibleBase`), not `NSAccessibilityElement`. That is the same choice we made — suggesting the `NSAccessibilityElement` base-class issues are a known difficulty.

2. **Modern protocol as primary API.** Firefox defines a `MOXAccessibleProtocol` with property-based getters (`moxRole`, `moxSubrole`, `moxTitle`, etc.) and bridges the older informal protocol methods to them. The protocol serves as an internal abstraction layer.

**Where we diverge from Firefox:**

1. **Text markers on all elements.** Like Chromium, Firefox exposes text-marker support on all elements (via `moxTextMarkerDelegate`), not just the root. Our restriction to `AXWebArea` is specific to our implementation.

2. **ListItem as a dedicated class.** Firefox has a `MOXListItemAccessible` subclass with specific overrides (e.g., `moxTitle` returns empty to avoid verbose announcements). ListItems aren’t ignored.

3. **No `shouldGroupAccessibilityChildren`.** Firefox doesn’t implement this, same as Chromium.
</details>

### Key cross-engine observations for Chromium, WebKit, and Firefox on macOS

- All three engines implement NSAccessibility directly on their own wrapper objects. None go through a toolkit bridge.
- All three map the document root to `AXWebArea`.
- All three provide custom role-description strings for landmarks.
- All three implement `AXUIElementsForSearchPredicate` directly.
- Chromium and Firefox both serialize the tree to the UI process; only WebKit does that differently (via private `NSAccessibilityRemoteUIElement` API).
- Firefox uses `NSObject` as the base class (like us); Chromium uses `NSAccessibilityElement`.
- Both Chromium and Firefox expose text markers on all elements; we restrict them to the document root.
- Neither Chromium nor Firefox implements `shouldGroupAccessibilityChildren`.

### Why our macOS implementation diverges from Firefox/Chromium/WebKit implementations

The divergence of Ladybird’s macOS accessibility implementation and that of other browsers isn’t caused by the IPC model — Chromium and Firefox also serialize over IPC; instead, the divergence stems from differences in wrapper-object lifecycle and tree management:

1. **Snapshot vs. live tree.** Our macOS NSAccessibility wrappers are thin shells over a full-tree snapshot that is replaced wholesale on each update. Chromium and Firefox back their wrappers with rich, live object models that support incremental updates and on-demand attribute computation.

2. **Purpose-built traversal.** Chromium’s `OneShotAccessibilityTreeSearch` and Firefox’s `Pivot` are sophisticated tree-traversal engines with predicate matching, subtree skipping, and cross-frame support. Our search predicate is a simpler DFS that required explicit leaf-role and container-descendant handling to avoid double-reading.

3. **Years of iteration.** For macOS, Chromium and Firefox have accumulated extensive VoiceOver interaction debugging over many years. Constraints we discovered through testing (text markers restricted to `AXWebArea`, `shouldGroupAccessibilityChildren` returning `NO`, `NSAccessibilityElement` property competition) are the kind of platform-specific quirks that the other engines must have either solved structurally — or else, because of their architecture — maybe never encountered to begin with.

### AccessKit

[AccessKit](https://github.com/AAccessKit/accesskit) is a Rust library that provides cross-platform accessibility adapters for macOS (NSAccessibility), Linux (AT-SPI2), and Windows (UIA). It handles the platform plumbing — role mapping, element lifecycle, event notifications, coordinate conversion — so applications don’t have to implement each platform’s accessibility protocol directly.

Key files: `platforms/macos/src/node.rs` (role mapping, attributes, actions), `platforms/macos/src/context.rs` (element caching), `platforms/macos/src/event.rs` (notifications), `consumer/src/filters.rs` (tree filtering).

<details><summary><b>What AccessKit handles well:</b></summary>
1. **Role mapping.** ~140 roles including `AXWebArea` for web content, landmark subroles (`AXLandmarkNavigation`, `AXLandmarkMain`, etc.), and custom role descriptions.

2. **`NSAccessibilityElement` base class.** AccessKit successfully uses `NSAccessibilityElement` as its base class (we switched to `NSObject` due to property-competition issues; Firefox also uses `NSObject`).

3. **Incremental tree updates.** `Tree::update_and_process_changes()` supports partial updates, unlike our full-snapshot replacement.

4. **Filtering model.** `FilterResult::Include` / `ExcludeNode` / `ExcludeSubtree` is cleaner than `accessibilityIsIgnored` — `ExcludeNode` promotes children without needing platform-specific ignore swizzles.

5. **Element lifecycle.** HashMap cache with on-demand creation, proper cleanup, and `UIElementDestroyedNotification` on drop.

6. **Focus forwarding.** Patches NSWindow to forward `accessibilityFocusedUIElement` to the content view — solving the same initial-focus problem we solved with `makeFirstResponder`.

7. **Cross-platform.** A single tree representation serves macOS, Linux (AT-SPI2), and Windows (UIA). The Linux adapter would give us AT-SPI2 support without relying on Qt’s bridge.
</details>

<details>
<summary><b>What AccessKit does not provide for web content:</b></summary>
1. **No `AXUIElementsForSearchPredicate`.** Without search predicates, VoiceOver falls back to sibling-only navigation within groups — it cannot do VO+Right/Left linear navigation through web content. That is the most complex part of our implementation, and even if we used AccessKit, we would still need to build that part ourselves.

2. **No `AXTextMarker`.** No cursor-level text navigation. AccessKit has NSRange-based text attributes but not the native marker objects that web content requires for character-by-character navigation and text selection.

3. **No web-content navigation semantics.** AccessKit is designed for native app accessibility, not browser-engine accessibility. The DFS traversal logic (leaf-role skipping, container-descendant skipping, search-key filtering) that makes VoiceOver navigation work smoothly in web content appears not to be part of AccessKit  — and that seems like a critical deficiency.
</details>

**Assessment:** AccessKit would eliminate some of the relatively low-implementation-effort platform plumbing we’re doing — but not the hard parts. The web-specific code — search predicates, text markers, container-descendant skipping, ignored-element semantics for web content — would still need to be implemented on top. So the trade-off would be adding a dependency for what amounts to a cleaner version of the platform-plumbing layer. But even then, with AccessKit, it seems we’d still be lacking the DFS traversal logic (leaf-role skipping, container-descendant skipping, search-key filtering) that makes VoiceOver navigation work smoothly in web content. And that on its own is a critical deficiency that would make AccessKit unsuitable for our needs.

### Future work: search-key filtering on macOS

Our macOS search-predicate implementation returns all non-ignored elements in DFS order without filtering by the `AXSearchKey` parameter. VoiceOver sends search keys like `AXHeadingSearchKey`, `AXLinkSearchKey`, `AXLandmarkSearchKey`, etc., to request elements of a specific type. Currently VoiceOver handles the filtering itself, but implementing search-key filtering on our side would improve performance and correctness.

Chromium’s `OneShotAccessibilityTreeSearch` (`ui/accessibility/platform/one_shot_accessibility_tree_search.h` / `.cc`) uses `PredicateForSearchKey()` to map each VoiceOver search key to a C++ predicate function, then walks the tree returning only matching elements. Firefox’s `MOXSearchInfo` (`accessible/mac/MOXSearchInfo.mm`) does the same — via `PivotRule` subclasses. Both approaches avoid returning the entire flattened tree when VoiceOver only wants headings or links.

## Tests

Four categories of tests cover four different layers: tree-dump tests, Qt AT-SPI2 bridge tests, Orca-integration tests, and macOS NSAccessibility tests.

### Tree-dump tests (`Tests/LibWeb/Accessibility/`)

An added Accessibility test mode in `test-web` follows the same basic pattern as Layout and Text tests:

- Tests are HTML files in `Tests/LibWeb/Accessibility/input/`
- Expectations files are in `Tests/LibWeb/Accessibility/expected/`
- The expectations tree-dump format shows the raw tree structure, including ignored elements and text leaves.
- You can use `test-web --rebaseline` to generate/update the expectations files.

These catch role/name/ancestor regressions observable in the serialized data. They do _not_ catch what our Qt AT-SPI2 bridge emits over D-Bus (role mapping, U+FFFC placement, per-character rects, event timing, action routing, multi-tab lifecycle), nor what the macOS NSAccessibility wrapper exposes at runtime, nor what Orca or VoiceOver output.

### Qt AT-SPI2 bridge tests (`Tests/LibWeb/AccessibilityBridge/tests/`)

Python tests that launch a real Ladybird process on a fixture HTML page, connect as an AT-SPI2 client via `gi.repository.Atspi`, and assert on what the bridge actually exposes. These catch bridge-level regressions we can’t see just with a tree dump

### Orca-integration tests (`Tests/LibWeb/AccessibilityBridge/tests_orca/`)

Python tests that import Orca and our Ladybird script into the test process, instantiate the script against the live Ladybird AT-SPI2 accessible, monkey-patch `speech.speak`, drive Orca commands directly, and assert on what Orca would have spoken.

### macOS NSAccessibility tests (`Tests/LibWeb/AccessibilityBridgeMac/`)

Python tests that launch a Ladybird process on a fixture HTML page, connect as an NSAccessibility client via PyObjC + `AXUIElement`, and assert on what the macOS AppKit accessibility surface actually exposes — and on what VoiceOver would see while walking that surface.

> [!NOTE]
> We have no VoiceOver tests that correspond to our Orca tests — because VoiceOver is a closed binary, and there’s no public hook for capturing synthesized speech without installing a custom TTS driver.

Fixtures are reused from `Tests/LibWeb/AccessibilityBridge/input/` rather than duplicated — since the role/name/structure invariants those fixtures exercise are platform-agnostic.

Running the tests requires the Accessibility (TCC) permission to be granted to the calling shell/IDE in System Settings → Privacy & Security → Accessibility.

## File guide

### `Libraries/LibWeb/CSS/`

`SelectorEngine.cpp`
- Extends the `:focus-visible` pseudo-class match to also match when the element is the document’s _accessibility focus target_ (set by the accessibility `scroll_into_view` action, described under _Linux: QAccessibleInterface with custom Orca scripts_ above). That gives keyboard and screen-reader navigation the same focus-ring behavior without actually moving DOM focus.

### `Libraries/LibWeb/DOM/`

`AccessibilityTreeNode.cpp` / `AccessibilityTreeNode.h`
- Walks the existing GC-managed accessibility tree and produces a flat `Vector<AccessibilityNodeData>`. Extracts role, name, description, bounds, heading level, `aria-live` value, and focus state for each node. For text-leaf nodes, also populates per-character layout data.

`Document.cpp` / `Document.h`
- Builds the accessibility tree and then serializes it. Notifies the page client when the focused element changes — once the UI has requested the tree.
- Has a mechanism for driving Orca focus-ring display movement without moving DOM focus.

### `Libraries/LibWeb/Page/Page.h`
- Has a mechanism for notifying the UI process when focus changes. Also has `accessibility_interested()`, which the first tree request sets, and which gates the focus-change reports and the tree pushes.

### `Libraries/LibWebCommon/`

`WebView/AccessibilityNodeData.cpp` / `WebView/AccessibilityNodeData.h`
- IPC-serializable struct that represents one node in the accessibility tree. Contains id, parent/child IDs, role, name, description, value, bounds, focus/disabled state, heading level, and `aria-live` value — along with the IPC `encode`/`decode` specializations.

`Forward.h`
- Forward declaration for `AccessibilityNodeData`.

`WebView/PageInfo.h`
- Adds `AccessibilityTree` to the `PageInfoType` enum (used by `test-web` for accessibility tree dumps).

### `Libraries/LibWebView/`

`AccessibilityTreeManager.cpp` / `AccessibilityTreeManager.h`
- Platform-agnostic class that caches the accessibility tree in the UI process. Provides node lookup, hit-testing, focused-node tracking, and DFS-ordered text-leaf enumeration. On `update_tree()`, compares old and new trees — to detect `aria-live` region content changes.

`ViewImplementation.cpp` / `ViewImplementation.h`
- Has `request_accessibility_tree()` and `perform_accessibility_action()` methods, plus `on_accessibility_tree_received` and `on_accessibility_focus_changed` callbacks.

`WebContentClient.cpp` / `WebContentClient.h`
- Client-side IPC handlers for `did_get_accessibility_tree` and `did_accessibility_focus_change` messages from WebContent. Dispatches to the `ViewImplementation` callbacks.

### `Services/WebContent/`

`ConnectionFromClient.cpp` / `ConnectionFromClient.h`
- Builds and sends the full tree (and on the first request, sets `accessibility_interested()`), and has `perform_accessibility_action` — which looks up DOM nodes by `UniqueNodeID` and dispatches to the `focus` and `scroll_into_view` action handlers. Also implements the `AccessibilityTree` page-info dump for `test-web`.

`PageClient.cpp` / `PageClient.h`
- Sends focus-change IPC and pushes tree updates when the page changes — once the UI has requested the tree.

`WebContentServer.ipc`
- Has `request_accessibility_tree(page_id)` and `perform_accessibility_action(page_id, node_id, action)` endpoints.

`WebContentClient.ipc`
- Has `did_get_accessibility_tree(page_id, nodes)` and `did_accessibility_focus_change(page_id, node_id)` endpoints.

### `UI/Qt/` (both platforms)

`WebContentView.h` / `WebContentView.cpp`
- Owns the `AccessibilityTreeManager` and element cache. Sets up IPC callbacks, and requests the tree once an assistive technology is present: at load finish if `QAccessible::isActive()`, or through `accessibilityActiveChanged()` for one that turns up later. Posts the document-root accessibility focus event. Rewires `QAccessibleTextInterface::scrollToSubstring` to the `scroll_into_view` accessibility action.

### `UI/Qt/` (macOS-specific files)

`LadybirdAccessibilityElement.h` / `LadybirdAccessibilityElement.mm`
- The main NSAccessibility wrapper. Handles role mapping, ignored-element transparency, search-predicate navigation, hit testing, actions, table-navigation attributes, text-marker navigation, and coordinate conversion. Also exposes the extra attributes VoiceOver uses to anchor its cursor on page load.

`LadybirdAccessibilityViewProtocol.h`
- The protocol the host view implements for `LadybirdAccessibilityElement`: element lookup, screen/view coordinate conversion, action dispatch, page URL, and first-responder state. `WebContentAccessibilityView` conforms to it.

`WebContentViewAccessibility.h` / `WebContentViewAccessibility.mm`
- The NSView overlay that conforms to `LadybirdAccessibilityViewProtocol` and owns the element cache, plus the three swizzles on `QMacAccessibilityElement`. Handles tree-update notifications, focus changes, and live-region announcements. Ensures the shared `LadybirdAccessibilityElement` can answer `AXURL` and `AXFocused` on the `AXWebArea`.

### `UI/Qt/` (Linux files)

`AccessibilityInterface.h` / `AccessibilityInterface.cpp`
- `QAccessibleInterface` implementation. Maps `AccessibilityNodeData` nodes to Qt’s accessibility API. Also contains `WebContentViewAccessible`: a `QAccessibleWidget` that bridges the `WebContentView` widget into Qt’s accessibility tree.

`Application.cpp`
- Enables loading of our custom Orca script, suppresses some log messages that end up flooding the console when Orca’s in use, and fixes a problem that prevents Orca and other AT clients from seeing our accessibility tree.

### `UI/Qt/OrcaScripts/Ladybird/`

Custom Orca screen-reader scripts, embedded as Qt resources and auto-installed to `~/.local/share/orca/orca-scripts/Ladybird/` on startup. A bootstrap block written into `~/.local/share/orca/orca-customizations.py` re-registers that directory with Orca’s script manager (required due to Orca 49.7 having removed the loading mechanism it had previously supported).

`script.py`
- Extends Orca’s `web.Script` with overrides for `activate()` and `on_focused_changed()`.

`script_utilities.py`
- Extends Orca’s `web.Utilities` with overrides for `active_document()`, `get_caret_context()`, and `_find_first_content_child()`. Installs several runtime monkey patches (described in the _Linux: QAccessibleInterface with custom Orca scripts_ section).

`__init__.py`
- `from .script import Script`

## Appendix: Qt AT-SPI2 bridge patches

The patches shown in this section are against Qt 6.11.0 (`src/gui/accessible/linux/`) and complement the “Qt-bridge bugs, by version” section above. They’ve not been upstreamed to the Qt sources; instead they’re included here as a way of having a record of them until they can do get upstreamed (at which point this appendix should be removed).

<details>
<summary><h3>How to use these patches</h3></summary>
Here is a full sequence of instructions creating a Ladybird build linked against Qt 6.11.0 with our patches applied.

1. **Get the Qt 6.11.0 sources.** Either fetch the official source archive and extract it:

   ```sh
   curl -LO https://download.qt.io/official_releases/qt/6.11/6.11.0/single/qt-everywhere-src-6.11.0.tar.xz
   tar xf qt-everywhere-src-6.11.0.tar.xz
   ```

   Or clone qtbase only (sufficient for these patches, since they only touch `src/gui/accessible/linux/`):

   ```sh
   git clone --branch v6.11.0 --depth 1 https://code.qt.io/qt/qtbase.git qtbase-6.11.0-src
   ```

2. **Apply the patches.** Save each fenced `diff` block from this appendix to its own file (e.g. `patch1.diff`, `patch2.diff`, `patch3.diff`); then, from inside the qtbase source tree, run:

   ```sh
   patch -p1 < /path/to/patch1.diff
   patch -p1 < /path/to/patch2.diff
   patch -p1 < /path/to/patch3.diff
   ```

3. **Configure and build Qt.** AT-SPI2 support requires `pkg-config` and the AT-SPI2 development package — `libatspi-dev` on Debian/Ubuntu, `at-spi2-core-devel` on Fedora — to be installed before configuring. Then, from a fresh build directory next to the source tree:

   ```sh
   mkdir build-qt-6.11-patched
   cd build-qt-6.11-patched
   ../qtbase-6.11.0-src/configure -accessibility -feature-accessibility-atspi-bridge -prefix /opt/qt-6.11.0-patched
   cmake --build . --parallel
   cmake --install .
   ```

   The `accessibility` and `accessibility-atspi-bridge` features are on by default if their dependencies are present; passing them explicitly is just to fail loudly if anything is missing.

4. **Build Ladybird against the patched Qt.** Set `CMAKE_PREFIX_PATH` so Ladybird’s `find_package(Qt6 REQUIRED COMPONENTS Core Widgets)` (in `UI/Qt/CMakeLists.txt`) discovers the patched build, then run the normal build entry point:

   ```sh
   CMAKE_PREFIX_PATH=/opt/qt-6.11.0-patched ./Meta/ladybird.py build
   ```

   Equivalent forms:

   ```sh
   # Pointing CMake directly at the Qt6 cmake config dir:
   Qt6_DIR=/opt/qt-6.11.0-patched/lib/cmake/Qt6 ./Meta/ladybird.py build

   # If the patched Qt was installed over an aqtinstall layout at $HOME/Qt/6.11.0:
   CMAKE_PREFIX_PATH=$HOME/Qt/6.11.0 ./Meta/ladybird.py build
   ```

   The environment variable only takes effect on the first configure pass — `Meta/ladybird.py` short-circuits configure if `Build/release/build.ninja` already exists, and CMake then uses the cached Qt path on subsequent builds. To switch an existing build directory from system Qt to the patched Qt, delete it first:

   ```sh
   rm -rf Build/release
   CMAKE_PREFIX_PATH=/opt/qt-6.11.0-patched ./Meta/ladybird.py build
   ```

5. **Verify the link.** Confirm Ladybird picked up the patched Qt rather than the system one:

   ```sh
   ldd Build/release/bin/Ladybird | grep libQt6Gui
   ```

   The output should reference `libQt6Gui.so.6` under your install prefix (`/opt/qt-6.11.0-patched/lib/...` in the example above) — not a system path such as `/usr/lib/...`.
</details>

<details>
<summary><h3>Patch 1: QSpiDBusCache::GetItems populates the AT-SPI2 cache</h3></summary>
Addresses the _“`QSpiDBusCache::GetItems()` returns an empty array”_ limitation. Firefox and Chromium pre-populate the AT-SPI2 cache through `libatk-bridge-2.0`, so that Orca can do its initial tree walk locally; Qt’s stub returns an empty array — forcing Orca to make per-element D-Bus calls for every node it needs to inspect. This patch walks the active `QAccessibleInterface` tree from `qApp` and returns a populated `QSpiAccessibleCacheArray` with path, parent, children, supported interfaces, name, role, description, and state for each accessible.

```
/* Detailed source-code truncated for AI context efficiency. */
```
</details>

<details>
<summary><h3>Patch 2: Expose EMBEDS relation from window to WebDocument child</h3></summary>
Addresses the _“No `RELATION_EMBEDS` from window to `WebDocument` child”_ limitation. Orca’s `Utilities.active_document()` normally walks the AT-SPI2 EMBEDS relation from the active window to its embedded web document; stock Qt doesn’t expose that relation, so `active_document()` returns `None` and Orca falls back to its own tree search (which we also duplicate on the Ladybird side in `script_utilities.py`, for robustness).

This patch extends `AtSpiAdaptor::relationSet()` so that for window-class accessibles (`QAccessible::Window` / `Grouping` / `Pane`) it walks up to five levels of children looking for a `QAccessible::WebDocument` — and if found, appends an `ATSPI_RELATION_EMBEDS` entry pointing at that document’s D-Bus path. That makes `Utilities.active_document()`’s fast path succeed on Qt-based browsers without requiring the Orca-side fallback.

```
/* Detailed source-code truncated for AI context efficiency. */
```
</details>

<details>
<summary><h3>Patch 3: Expose AT-SPI2 Document, Hypertext, and Hyperlink interfaces</h3></summary>
Addresses the _“`AtkDocument` interface not exposed” and “`AtkHypertext` / `AtkHyperlink` interfaces not exposed”_ limitations. Together, those three interfaces let Orca treat a web document as a true hypertext container — with per-link offsets, link enumeration, document-level locale, and MIME attributes — rather than just a tree of text leaves and elements.

Three separable additions in one patch:

- **`org.a11y.atspi.Document`**: minimal stub in `handleMessage()` that answers `GetLocale`, `GetAttributeValue`, and `GetAttributes` queries (the latter reports `DocType` / `MimeType` = `text/html`). `accessibleInterfaces()` advertises this interface on `QAccessible::WebDocument` accessibles so Orca finds it.
- **`org.a11y.atspi.Hypertext`**: new `hypertextInterface()` method answering `GetNLinks`, `GetLink`, and `GetLinkIndex` by walking the accessible’s child list and treating non-`StaticText` children as embedded “links” in hypertext terms. Note that `accessibleInterfaces()` does _not_ advertise Hypertext — see the comment in the diff: `libatspi`’s client wraps `GetLink` results as `Hyperlink` objects, and `GetObject` on those paths returns `Hyperlink` instead of `Accessible`, breaking Orca’s subsequent queries. The method is reachable via direct D-Bus probing by assistive technologies that don’t rely on `libatspi`’s Hypertext wrapping.
- **`org.a11y.atspi.Hyperlink`**: new `hyperlinkInterface()` method answering `NAnchors`, `GetObject`, `GetURI`, `IsValid`, and start/end index queries. Start/end indices are computed by walking siblings and summing `StaticText` character counts and U+FFFC object-replacement-character widths for non-text siblings.

The Document stub is clean and should be straightforward to upstream. Hypertext/Hyperlink is partial for the reason above — the `libatspi` interaction needs to be resolved before this is useful to `libatspi`-based assistive technologies, including Orca.

```
/* Detailed source-code truncated for AI context efficiency. */
```
</details>

---

### Documentation SmartPointers (`Documentation/SmartPointers.md`)

# AK smart pointers

## Introduction

There are three main C++ smart pointer types used in Ladybird. Each type describes the ownership (or lack thereof) of the pointee.

The reason for using these pointers is to make it explicit through code who owns which resources, and how ownership is transferred. They also serve as a guard against memory leaks and use-after-free bugs.

## OwnPtr\<T\> and NonnullOwnPtr\<T\>

`OwnPtr` is used for single-owner objects. An object held in an `OwnPtr` is owned by that `OwnPtr`, and not by anybody else.

This means that the `OwnPtr` is responsible for deleting the pointee when the `OwnPtr` goes out of scope.

These pointers cannot be copied. Transferring ownership is done by moving the pointer.

`NonnullOwnPtr` is a special variant of `OwnPtr` with one additional property: it cannot be null. `NonnullOwnPtr` is suitable as a return type from functions that are guaranteed to never return null, and as an argument type where ownership is transferred, and the argument may not be null. In other words, if `OwnPtr` is "\*", then `NonnullOwnPtr` is "&".

Note: A `NonnullOwnPtr` can be assigned to an `OwnPtr` but not vice versa. To transform an known-non-null `OwnPtr` into a `NonnullOwnPtr`, use `OwnPtr::release_nonnull()`.

### Construction using helper functions

There is a `make<T>()` helper that constructs a new object and returns it wrapped in a `NonnullOwnPtr`. All arguments passed to it are forwarded to `T`'s constructor. If it fails to allocate heap memory for the object, it terminates the program.

```cpp
{
    NonnullOwnPtr<Foo> my_object = make<Foo>();
    my_object->do_stuff();
    // my_object goes out of scope here, and the Foo will be deleted.
}
```

The `try_make<T>()` helper attempts to construct a new object wrapped in an `ErrorOr<NonnullOwnPtr<T>>`. All arguments passed to it are forwarded to `T`'s constructor. In case of allocation failure, an ENOMEM error is returned. This allows the calling code to handle allocation failure as it wishes.

```cpp
auto my_object_or_error = try_make<Foo>();
if (my_object_or_error.is_error()) {
    // handle allocation failure...
}
auto my_object = my_object_or_error.release_value();
my_object->do_stuff();
```

Note: Objects constructed using `try_make<T>()` should only be dereferenced after a null check.

### Manual construction

The helper functions cannot access private constructors, so in some cases, smart pointers need to be created manually. This is done by "adopting" a raw pointer, which moves its ownership to the smart pointer. Dereferencing the raw pointer or calling its destructor afterwards can cause undefined behavior.

Known non-null pointers can be turned into a `NonnullOwnPtr` by the global `adopt_own()` function.

```cpp
NonnullOwnPtr<Foo> my_object = adopt_own(*new Foo);
```

It is safe to immediately dereference this raw pointer, as the normal `new` expression cannot return a null pointer.

Any (possibly null) pointer to `T` can be turned into an `OwnPtr<T>` by the global `adopt_own_if_nonnull()` function.

```cpp
OwnPtr<Foo> my_object = adopt_own_if_nonnull(new (nothrow) Foo);
```

In this case, the *non-throwing* `new` should be used to construct the raw pointer, which returns null if the allocation fails, instead of aborting the program.

**Note:** Always prefer the helper functions to manual construction.

## RefPtr\<T\> and NonnullRefPtr\<T\>

`RefPtr` is used for multiple-owner objects. An object held by a `RefPtr` is owned together by every pointer pointing to that object.

Shared ownership is implemented via reference counting.

`NonnullRefPtr` is a special variant of `RefPtr` with one additional property: it cannot be null. `NonnullRefPtr` is suitable as a return type from functions that are guaranteed to never return null, and as an argument type where the argument may not be null. In other words, if `RefPtr` is "\*", then `NonnullRefPtr` is "&".

Objects can only be held by `RefPtr` if they meet certain criteria. Specifically, they need to implement the functions `ref()` and `unref()`.

To make a class `T` reference-counted, you can simply make it inherit from `RefCounted<T>`. This will add all the necessary pieces to `T`.

```cpp
class Bar : public RefCounted<Bar> {
    ...
};
```

Note: A `NonnullRefPtr` can be assigned to a `RefPtr` but not vice versa. To transform an known-non-null `RefPtr` into a `NonnullRefPtr`, either use `RefPtr::release_nonnull()` or simply dereference the `RefPtr` using its `operator*`.

### Construction using helper functions

There is a `make_ref_counted<T>()` global helper function that constructs a new object and returns it wrapped in a `NonnullRefPtr`. All arguments passed to it are forwarded to `T`'s constructor. If memory cannot be allocated for the object, the program is terminated.

```cpp
NonnullRefPtr<Bar> our_object = make_ref_counted<Bar>();
NonnullRefPtr<Bar> another_owner = our_object;
```


The `try_make_ref_counted<T>()` function constructs an object wrapped in `ErrorOr<NonnullRefPtr<T>>` which may be an error if the allocation does not succeed. This allows the calling code to handle allocation failure as it wishes. All arguments passed to it are forwarded to `T`'s constructor.

```cpp
auto our_object_or_error = try_make_ref_counted<Bar>();
if (our_object_or_error.is_error()) {
    // handle allocation failure...
}
NonnullRefPtr<Bar> our_object = our_object_or_error.release_value();
RefPtr<Bar> another_owner = our_object;
```

In the above examples, the Bar object will only be deleted once both `our_object` and `another_owner` are gone.

### Manual construction

The helper functions cannot access private constructors, so in some cases, objects need to be manually wrapped into smart pointers. When constructing an object that derives from `RefCounted`, the reference count starts out at 1 (since 0 would mean that the object has no owners and should be deleted). The object must therefore be "adopted" by someone who takes responsibility of that 1. The raw pointer must not be used after its ownership is transferred to the smart pointer.

A known non-null raw pointer can be turned into a `NonnullRefPtr` by the global `adopt_ref()` function.

```cpp
NonnullRefPtr<Bar> our_object = adopt_ref(*new Bar);
```

Note: It is safe to immediately dereference this raw pointer, as the normal `new` expression cannot return a null pointer.

Any (possibly null) pointer to a reference-counted object can can be turned into a `RefPtr` by the global `adopt_ref_if_nonnull()` function.

```cpp
RefPtr<Bar> our_object = adopt_ref_if_nonnull(new (nothrow) Bar);
```
In this case, the *non-throwing* `new` should be used to construct the raw pointer, which returns null if the allocation fails, instead of aborting the program.

**Note:** Always prefer the helper functions to manual construction.

## WeakPtr\<T\>

`WeakPtr` is used for objects that somebody else owns. When the pointee of a `WeakPtr` is deleted, the `WeakPtr` will magically become null.

Behind the scenes, this is implemented using the `Weakable` template. If you want to make it possible for a class `T` to be weakly-pointed-to, have it inherit from `Weakable<T>`.

To create a `WeakPtr` to a weakable object, use `make_weak_ptr()`:

```cpp
class Baz : public Weakable<Baz> {
    ....
};

WeakPtr<Baz> a_baz;
{
    NonnullOwnPtr<Baz> my_baz = make<Baz>();
    a_baz = my_baz->make_weak_ptr();
    // a_baz now points to my_baz
}
// a_baz is now null, since my_baz went out of scope.
```

---

### Documentation StringFormatting (`Documentation/StringFormatting.md`)

# String Formatting

Many places in Ladybird allow you to format strings, similar to `printf()`, for example `ByteString::formatted()`
, `StringBuilder::appendff()`, or `dbgln()`. These are checked at compile time to ensure the format string matches the
number of parameters. The syntax is largely based on
the [C++ `std::formatter` syntax](https://en.cppreference.com/w/cpp/utility/format/formatter#Standard_format_specification)
but there are some differences.

For basic usage, any occurrences of `{}` in the format string are replaced with the other arguments, converted to string
form, in order:

```c++
ByteString::formatted("Well, {} my {} friends!", "hello", 42) == "Well, hello my 42 friends!";
```

If you want to include a literal `{` in the output, use `{{`:

```c++
ByteString::formatted("{{ {}", "hello") == "{ hello";
```

You can refer to the arguments by index, if you want to repeat one or change the order:

```c++
ByteString::formatted("{2}{0}{1}", "a", "b", "c") == "cab";
```

To control how the arguments are formatted, add colon after the optional index, and then add format specifier
characters:

```c++
ByteString::formatted("{:.4}", "cool dude") == "cool";
ByteString::formatted("{0:.4}", "cool dude") == "cool";
```

## Format specifiers

In order, the format can contain:

- Fill character and alignment
- Sign
- `#` Hash
- `0` Zero
- Width
- Precision
- Type specifier

Each of these is optional. You can include any combination of them, but they must be in this order.

### Fill and alignment

This is an optional fill character, followed by an alignment. The fill character can be anything apart from `{` or `}`,
and is used to fill any space left when the input has fewer characters than the format requests. By default, it is a
space. (` `)

The alignment characters are:

- `<`: Align left.
- `>`: Align right.
- `^`: Align centered.

### Sign

- `+`: Always display a sign before the number.
- `-`: Display a sign for negative numbers only.
- (space): Display a sign for negative numbers, and a leading space for other numbers.

### Hash

`#` causes an "alternate form" to be used.

For integer types, this adds the number-base prefix after the sign:

- `0b` for binary.
- `0` for octal.
- `0x` for hexadecimal.

### Zero

`0` pads the number with leading zeros.

### Width and Precision

The width defines the minimum number of characters in the output. The precision is a `.` followed by a precision number,
which is used as the precision of floating-point numbers, or a maximum-width for string values.

Both the width and precision can be provided as a replacement field (`{}`, optionally including an argument index) which
allows you to use an integer argument instead of a hard-coded number.

### Type specifiers

| Type      | Effect                | Example output           |
|-----------|-----------------------|--------------------------|
| *nothing* | default format        | Anything! :^)            |
| b         | binary                | `110`, `0b000110`        |
| B         | binary uppercase      | `110`, `0B000110`        |
| d         | decimal               | `42`, `+0000042`         |
| o         | octal                 | `043`                    |
| x         | hexadecimal           | `ff0`, `0x00000ff0`      |
| X         | hexadecimal uppercase | `FF0`, `0X00000FF0`      |
| c         | character             | `a`                      |
| s         | string                | `well, hello friends!`   |
| p         | pointer               | `0xdeadc0de`             |
| f         | float                 | `1.234`, `-inf`          |
| a         | hex float             |                          |
| A         | hex float uppercase   |                          |
| hex-dump  | hexadecimal dump      | `fdfdfdfd`, `3030    00` |

Not all type specifiers are compatible with all input types, of course.

## Formatting custom types

You can provide a custom `AK::Formatter<Foo>` class to format `Foo` values. For the simplest case where you already have
a function that produces a string from your type, that would look like this:

```c++
template<>
struct AK::Formatter<Web::CSS::Selector> : Formatter<StringView> {
    ErrorOr<void> format(FormatBuilder& builder, Web::CSS::Selector const& selector)
    {
        return Formatter<StringView>::format(builder, selector.serialize());
    }
};
```

More advanced formatters that make check for format-specifier flags can be written by referring to the fields
in `StandardFormatter` (which most `Formatter` classes extend).

## Detecting if a type can be formatted

The `AK::HasFormatter<T>` template has a boolean value representing whether `T` can be formatted.

The `FormatIfSupported<T>` makes use of this to return either the formatted value of `T`, or a series of `?`s if the
type cannot be formatted. For example:

```c++
// B has a Formatter defined, but A does not.
ByteString::formatted("{}", FormatIfSupported { A {} }) == "?";
ByteString::formatted("{}", FormatIfSupported { B {} }) == "B";
```

---

### Documentation TabPerformanceMonitor (`Documentation/TabPerformanceMonitor.md`)

# Tab performance monitor

Enable **Show tab performance monitor** in **Settings → Advanced**, or set
`debug.ui.show_tab_performance_monitor` to `true`. It defaults to `false`.
The UI's toolbars display the current tab's statistics inline.
Each tab retains its own CPU history while monitoring is enabled.

`LibWebView::TabPerformanceMonitor` resolves browser ownership and supplies
`TabPerformanceStats` to the frontend. `TabPerformanceAccumulator` performs
counter differencing and windowing independently of the UI and OS APIs.

## Metric definitions

* **CPU:** CPU-time deltas divided by actual monotonic elapsed time. 100% is
  one logical CPU; values above 100% are expected. All live and terminated
  threads in exclusively owned WebContent and WebWorker processes contribute.
  Registered page and embedded-frame ownership identify WebContent owners.
  Worker ownership is resolved transitively, including nested workers and
  shared workers whose owners all resolve to the same tab. Processes shared
  across tabs, unresolved owners and cyclic ownership remain unattributed.
  The first sample establishes a baseline. The sparkline retains ten seconds.
* **MEM:** An estimate of owned process memory, counted once per process.
  macOS uses the kernel's physical-footprint ledger, including compressed
  memory, rather than RSS. Linux uses resident minus shared pages from
  `/proc/PID/statm`, a cheap private-resident approximation that omits
  file-backed private pages and swapped-out memory. These platform estimates
  are not directly comparable. Other platforms report unavailable until a
  collector is implemented. Shared helper/cache allocations and resources
  without a process-level ownership charge are excluded.
* **Download/upload:** HTTP response/request body bytes transferred by curl,
  before response decompression. Disk-cache hits contribute no traffic.
  Headers, TLS/TCP overhead, WebSockets, and helper-initiated background
  requests without an originating page or exclusive worker are excluded.
  A request keeps its originating process/page context when transferred to
  another client. The browser resolves that context to a tab; traffic whose
  original page or worker has already disappeared is left unattributed.
  Byte units are decimal. Rates use a rolling second, with delayed batches
  weighted by their actual sampling interval.
* **FPS:** Accepted compositor backing-store swaps over the last second,
  including compositor-driven animation. This measures frames delivered to
  the frontend, not hardware display scanout or `requestAnimationFrame` calls.
  After a second without a frame the value is `FPS —`. FPS uses neutral text.

CPU, memory and FPS values use neutral text. Labels use 65% opacity and
the sparkline uses a subdued stroke. Network download is blue and upload
orange. Fixed-width columns and tabular digits prevent layout shifts.

## Sampling and overhead

The browser samples owned process counters every 500 ms. RequestServer runs
its own 500 ms timer and **pushes** byte deltas asynchronously to the browser;
there is no UI polling or synchronous traffic-statistics IPC. Completed
requests contribute their final delta before their curl handle is released.
Batches coalesce requests with the same originating process/page.

Disabling the setting destroys both timers, clears histories and pending byte
batches, and disconnects the browser's traffic callback. RequestServer does
not sample curl counters or push traffic messages while disabled. Existing
request setup retains small ownership metadata, and the frame delivery path
has a disabled-state check. Re-enabling establishes fresh CPU and in-flight
request baselines; restarting RequestServer restores an enabled subscription.

Shared helper CPU is currently excluded: the network, image decoder, compiler
and compositor processes do not yet expose per-job CPU accounting. Their
whole-process CPU is never assigned to a tab. Additional explicitly owned
memory or helper CPU can be added to the accounting layer without putting
platform or ownership queries in toolbar widgets.

---

### Documentation Testing (`Documentation/Testing.md`)

# Testing Ladybird

Tests are located in `Tests/`, with a directory for each library.

Every feature or bug fix added to LibWeb should have a corresponding test in `Tests/LibWeb`.
The test should be either a Text, Layout, Ref, or Screenshot test depending on the feature.
Tests of internal C++ code go in their own `TestFoo.cpp` file in `Tests/LibWeb`.

## Running Tests

> [!NOTE]
> To reproduce a CI failure, see the section on [Running with Sanitizers](#running-with-sanitizers).

The easiest way to run tests is to use the `ladybird.py` script. The LibWeb tests are registered with CMake as a test in
`UI/CMakeLists.txt`. Using the built-in test filtering, you can run all tests with `Meta/ladybird.py test` or run
just the LibWeb tests with `Meta/ladybird.py test LibWeb`. The second way is to invoke the `test-web` test runner
directly with `Meta/ladybird.py run test-web`.

A third way is to invoke `ctest` directly. The simplest method is to use the `Release` preset from `CMakePresets.json`:

```sh
cmake --preset Release
cmake --build --preset Release
ctest --preset Release
```

Note that some tests require the `LADYBIRD_SOURCE_DIR` environment variable to be set to the root of the ladybird source tree.

```sh
# /path/to/ladybird repository
export LADYBIRD_SOURCE_DIR=${PWD}
```

The tests can be run via ninja after doing a build.

```sh
cd Build/release
ninja
ninja test
```

To see the stdout/stderr output of failing tests, the recommended way is to set the environment variable [`CTEST_OUTPUT_ON_FAILURE`](https://cmake.org/cmake/help/latest/manual/ctest.1.html#options) to 1.

```sh
CTEST_OUTPUT_ON_FAILURE=1 ninja test

# or, using ctest directly...
ctest --output-on-failure
```

### JavaScript tests

Build `test-js` to build the runtime runner and `js`, and prepare the shared
AST/bytecode snapshot runner. From the build directory, run:

```sh
ninja test-js
./bin/test-js
./bin/test-js --suite runtime -f array
./bin/test-js --suite ast --suite bytecode -f class
./bin/test-js --suite bytecode --rebaseline -f class
```

Set `LADYBIRD_SOURCE_DIR` to the repository root before running the command.
Without `--suite`, the command runs runtime, AST, and bytecode tests, continuing
through suite failures and returning a nonzero status if any suite fails.
CTest registers this as a single `test-js` test.

`-f` / `--filter` accepts repeatable, case-insensitive substring globs across
all selected suites. `--jobs N` controls snapshot parallelism. `--rebaseline`
updates only the selected snapshot expectations; without `--suite`, it updates
both AST and bytecode expectations. It never updates expectations for a script
that exits unsuccessfully.

`-j` / `--json` produces one JSON document with `suites` and `failed_suites`.
`--per-file` includes individual runtime results. When selecting only
`--suite runtime`, both options retain the original runtime JSON format.
`--test262-parser-tests` selects only the runtime suite and retains its JSON
format for the test262 results collector.
Additional runtime options and custom test roots are forwarded to the runtime
runner, `test-js-runtime`; use `--suite runtime --help-runtime` to list its
options.

For a native debugging session, run `test-js-runtime` directly with the
original runtime options.

### Inspecting test-web results

At the end of a run, `test-web` prints the location of its results directory.
Open `index.html` there for the interactive report, or summarize the saved
results from the repository root:

```sh
python3 Meta/test-results.py Build/release/path-to-results
python3 Meta/test-results.py Build/release/path-to-results --details -f your-test
```

The summary lists failed, timed out, and crashed tests with artifact paths and
suggested rerun commands. `--details` also prints text diffs and captured logs,
including helper process logs for the whole run. The optional filter is a
substring of the artifact name; it does not change the run's summary counts.
The reader accepts either a results directory or its `results.js` file and
does not execute the JavaScript or rerun tests. A successful read exits with
status zero even when the saved run contains failures.

Rerun commands are intended for the original build directory. Restore any
custom options from the recorded invocation, such as the test root, timeouts,
or repetition count. The reader uses this checkout's `Tests/LibWeb` to
distinguish query variants from literal `@` characters in filenames; use
`--test-root` for a different source tree. Repeated results omit the `run-N/`
artifact prefix when constructing the filter. No rerun command is suggested
if the corresponding source file cannot be found.

### Running with Sanitizers

CI runs host tests with Address Sanitizer and Undefined Sanitizer instrumentation enabled. These tools catch many
classes of common C++ errors, including memory leaks, out of bounds access to stack and heap allocations, and
signed integer overflow. For more info on the sanitizers, check out the Address Sanitizer [wiki page](https://github.com/google/sanitizers/wiki),
or the Undefined Sanitizer [documentation](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html) from clang.

Note that a sanitizer build will take significantly longer than a non-sanitizer build, and will mess with caches in tools
such as `ccache`. The sanitizers can be enabled with the `-DENABLE_FOO_SANITIZER` set of flags.

The simplest way to enable sanitizers is to use the `Sanitizer` preset.

```sh
cmake --preset Sanitizer
cmake --build --preset Sanitizer
ctest --preset Sanitizer
```

Or from a Lagom build:

To ensure that the test behaves the same way as CI, make sure to set the ASAN_OPTIONS and UBSAN_OPTIONS appropriately.
The Sanitizer test preset already sets these environment variables.

```sh
export ASAN_OPTIONS='strict_string_checks=1:check_initialization_order=1:strict_init_order=1:detect_stack_use_after_return=1:allocator_may_return_null=1'
export UBSAN_OPTIONS='print_stacktrace=1:print_summary=1:halt_on_error=1'
cmake -GNinja -B Build/lagom -DENABLE_ADDRESS_SANITIZER=ON -DENABLE_UNDEFINED_SANITIZER=ON
cd Build/lagom
ninja
CTEST_OUTPUT_ON_FAILURE=1 LADYBIRD_SOURCE_DIR=${PWD}/../.. ninja test
```

### Running the Web Platform Tests

The Web Platform Tests can be run with the `WPT.sh` script. This script can also be used to compare the results of two
test runs.

Example usage:

```sh
# Run the WPT tests then run them again, comparing the results from the two runs
./Meta/WPT.sh run --log expectations.log css
git checkout my-css-change
./Meta/WPT.sh compare --log results.log expectations.log css
```

```sh
# Pull the latest changes from the upstream WPT repository
./Meta/WPT.sh update
# Run all of the Web Platform Tests, outputting the results to results.log
./Meta/WPT.sh run --log results.log
```

### Importing Web Platform Tests

You can import certain Web Platform Tests (WPT) tests into your Ladybird clone (if they're tests of type that can be
imported - and especially if any code changes you're making cause Ladybird to pass any WPT tests it hasn't yet been
passing). Here's how:

```sh
./Meta/WPT.sh import html/dom/aria-attribute-reflection.html
```

That is, you give `./Meta/WPT.sh import` the path part of any `http://wpt.live/` URL for a WPT test you want to import.
It will then download both that test and any of its JavaScript scripts, copy those to the `Tests/LibWeb/<test-type>/input/wpt-import`
directory, run the test, and then in the `Tests/LibWeb/<test-type>/expected/wpt-import` directory, it will create a file
with the expected results from the test.

## Writing tests

Running the following python script to create new test files with correct boilerplate:

```python
./Tests/LibWeb/add_libweb_test.py your-new-test-name test_type
```

The accepted `test_type` values are "Text", "Layout", "Ref", and "Screenshot".

This will create a new test HTML file in `Tests/LibWeb/<test_type>/input` with along with a corresponding expectations
file in the appropriate directory in `Tests/LibWeb/<test_type>/expected`.

After you update/replace the generated boilerplate in your `your-new-test-name.html` test file with your actual test,
you will need to regenerate the corresponding expectations file to match the actual output from your updated test.

For Text or Layout tests, you can "rebaseline" the tests to regenerate the expectation file:

```bash
./Meta/ladybird.py run test-web --rebaseline -f Text/input/your-new-test-name.html
```

For Ref and Screenshot tests, you will need to supply the equivalently rendering HTML manually. Though for Screenshot
tests, you can generate the reference screenshot itself by running Ladybird in headless mode:

```bash
./Meta/ladybird.py run ladybird --headless --test-mode Tests/LibWeb/Screenshot/input/your-new-test-name.html

# This will log something like: "Saved screenshot to: ~/Downloads/screenshot-2025-06-07-08-37-45.png"

mv ~/Downloads/screenshot-2025-06-07-08-37-45.png Tests/LibWeb/Screenshot/images/your-new-test-name.png
```

### Text tests

Text tests are intended to test Web APIs that don't have a visual representation. They are written in JavaScript and
run in a headless browser. Each test has a test function in a script tag that exercises the API and prints expected
results using the `println` function. `println` calls are accumulated into an output test file, which is then
compared to the expected output file by the test runner.

Text tests can be either sync or async. Async tests should use the `done` callback to signal completion.
Async tests are not necessarily run in an async context, they simply require the test function to signal completion
when it is done. If an async context is needed to test the API, the lambda passed to `test` can be async.

### Layout

Layout tests compare the layout tree of a page with an expected one. They are best suited for testing layout code, but
are also used for testing some other features that have an observable effect on the layout. No JavaScript is needed -
once the page loads, the layout tree will be dumped automatically.

### Ref

Reference or "ref" tests compare a screenshot of the test page with one of a reference page. The test passes if the two
are identical. These are ideal for testing visual effects such as background images or shadows. If you're finding it
difficult to recreate the effect in the reference page, (such as for SVG or canvas,) consider using a Screenshot test
instead.

Each Ref test includes a special `<link rel="match" href="../expected/my-test-ref.html" />` tag, which the test runner
uses to locate the reference page. In this way, multiple tests can use the same reference.

### Screenshot

Screenshot tests can be thought of as a subtype of Ref tests, where the reference page is a single `<img>` tag linking
to a screenshot of the expected output. In general, try to avoid using them if a regular Ref test would do, as they are
sensitive to small rendering changes, and won't work on all platforms.

Like Ref tests, they require a `<link rel="match" href="../expected/my-test-ref.html" />` tag to indicate the reference
page to use.

---

### Documentation Troubleshooting (`Documentation/Troubleshooting.md`)

# Troubleshooting

In case of an error, you might find an answer of how to deal with it here.

## Building Ladybird

### CMake fails to configure the build because it's outdated

Ensure your CMake version is >= 3.30 with `cmake --version`. If your system doesn't provide a suitable
version of CMake, you can download a binary release from the [CMake website](https://cmake.org/download).

### GCC is missing or is outdated

Ensure your gcc version is supported by the [build system](BuildInstructionsLadybird.md#build-prerequisites) with
`gcc --version`. Otherwise, install it. If your gcc binary is not called `gcc` you have to specify the names of your
C and C++ compiler when you run cmake, e.g. `cmake ../.. -GNinja -DCMAKE_C_COMPILER=gcc-13 -DCMAKE_CXX_COMPILER=g++-13`.

### Legacy renegotiation is disabled

Ensure your `/etc/ssl/openssl.cnf` file has the following options:

```console
[openssl_init]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
MinProtocol = TLSv1.2
CipherString = DEFAULT@SECLEVEL=1
Options = UnsafeLegacyRenegotiation
```

### “Targets may link only to libraries. CMake is dropping the item” message (when building with the Qt UI on macOS)

When building with the Qt UI on macOS, you may encounter the following message:

```
CMake Warning at /opt/homebrew/Cellar/qt/6.7.0_1/lib/cmake/Qt6/FindWrapOpenGL.cmake:48 (target_link_libraries):
Target "ladybird" requests linking to directory "/usr/X11R6/lib". Targets
may link only to libraries. CMake is dropping the item.
```

…followed by 14-line stack trace, the top of which is this:

```
Build/vcpkg/scripts/buildsystems/vcpkg.cmake:859 (_find_package)
```

…and all of it shown in bright yellow, making you think it must be important and something must need to be fixed. But that’s not the case. Instead, despite that, you’ll be able to build successfully with the Qt UI.

## Running Ladybird

### Race condition on exit when running headless on systems with `llvmpipe`

When running `--headless=text` or `--headless=layout-tree` on a UNIX system with the [llvmpipe](https://docs.mesa3d.org/drivers/llvmpipe.html) software rasterizer
(common default on virtual machines), Ladybird will occasionally terminate with:

```
double free or corruption (!prev)
```

Run Ladybird with `--force-cpu-painting` to make that go away.

Likely explanation ([this thread](https://github.com/LadybirdBrowser/ladybird/issues/5097)):

> suspect […] atexit handlers are registered twice possibly due to llvmpipe and fork/exec interaction issues when starting WebContent process

---

### Documentation WebDriver (`Documentation/WebDriver.md`)

# Investigating websites with WebDriver

Ladybird's WebDriver service can launch a browser, navigate pages, evaluate
JavaScript, and capture screenshots. It also accepts `--resource-map` to
substitute local files for HTTP or HTTPS resources while retaining their URLs.

## Run a complete example

From the repository root, build WebDriver and its browser dependencies:

```sh
cd Build/release
ninja WebDriver
```

Run the following example from that build directory. It uses Python's standard
library, starts WebDriver on loopback, creates a headless session, and loads a
page and script supplied entirely by a resource map. It prints the script's
result and saves `webdriver-example.png` and `webdriver-example.log` in the
build directory. These two files are overwritten on subsequent runs.

```
/* Detailed source-code truncated for AI context efficiency. */
```

Deleting the session closes its browser. Terminating WebDriver cleanly removes
the temporary browser profile it creates by default. The Python context manager
removes the example's resource files; the screenshot and log remain available.
WebDriver launches the browser and its helper processes, so they do not need to
be started separately.

## Adapt the example to a website

Change the navigation URL and the JavaScript passed to `/execute/sync`. Remove
`--resource-map` and its argument to load all resources from the network, or
retain a map containing only the resources you want to instrument. For example:

```json
{
  "substitutions": [
    {
      "url": "https://example.com/assets/app.js",
      "file": "/absolute/path/to/instrumented-app.js",
      "content_type": "text/javascript"
    }
  ]
}
```

Use absolute local paths. Entries support an optional `status_code` (default
200), and `content_type` defaults to a guess from the local filename. URL matching
ignores the query and fragment, so the entry above also replaces
`https://example.com/assets/app.js?v=123`. The map is loaded when RequestServer
starts; create the mapped files before starting the session. Restart the
WebDriver session after changing map entries or replacing a mapped file so
the sandbox can grant access to the new file. Mapped files are read when served,
but browser caching can still affect subsequent loads.

Navigation waits for page loading according to the session's page-load strategy.
For application work that continues afterward, use `/execute/async` with an
application event or promise and call the callback supplied as the last script
argument. Avoid fixed sleeps to decide when a page is ready. The example's short
startup polling interval only waits for the WebDriver listening socket.

To inspect a visible browser, remove `--headless` and set `ladybird:headless` to
`false`. For a browser process waiting for a debugger, add, for example,
`--debug-process WebContent` and allow enough request time to attach. Use
`--force-cpu-painting` when investigating differences involving GPU painting.

For authenticated investigations, `--profile-path /absolute/path/to/profile`
selects a persistent profile. WebDriver otherwise uses its own temporary profile.
See `./bin/WebDriver --help` (or the bundle path above on macOS) for other options.

---

### Documentation WrapperArchitecture (`Documentation/WrapperArchitecture.md`)

# LibWeb wrapper architecture

LibWeb exposes internal C++ implementation objects to JavaScript through a
three-layer wrapper model:

1. `Web::Bindings::Wrappable` is the implementation-side base for objects that
   can be reflected into JavaScript.
2. Wrappers are `JS::HostObject`s that hold the implementation object in their
   wrappable slot. For each interface, the WebIDL generator emits a host class
   table, whose hooks implement the Web-facing object behavior, and a
   `create_*_wrapper()` function in `Web::Bindings`.
3. `Web::Bindings::WrapperWorld` owns wrapper identity for one observable world.
   Wrapping an implementation object means looking up or creating the wrapper in
   the caller's `WrapperWorld`.

This gives LibWeb one wrapper identity per `(implementation object,
WrapperWorld)` pair, while still allowing the same implementation object to be
observed through different realms or isolated worlds.

## World model

Each HTML agent owns one main-world `WrapperWorld`. For windows this means one
cell per similar-origin window agent, not per `Page`: same-agent auxiliary
pages such as same-origin `window.open()` can directly observe each other's DOM
objects and must therefore share main-world wrapper identity. Worker agents
naturally get their own main-world cell. Cross-origin windows in one WebContent
process currently share its `SimilarOriginWindowAgent`, so they also share this
cell; proper agent-cluster separation is future work (including the same-process
COOP/`noopener` popup case noted in `PageClient.cpp`).

Non-main worlds are realm-local wrapper-cache cells. The current concrete types
are `Internal` and `Extension`; each has its own wrapper cache and must not
share main-world slots. Non-main worlds that may preserve wrappers must be
explicitly detached before the embedder drops them. The destructor verifies
this unconditionally, except during `CollectEverything` collection where weak
wrappable references cannot be walked safely. Wrapping, preserving, or cache
insertion through a detached world is likewise rejected by unconditional
`VERIFY`s, including in release builds.

`Wrappable` keeps an inline weak main-world wrapper as a fast-path
representative, but the authoritative cache is the `WrapperWorld` map. This is
important because the same implementation object can be visible through
different realms in the same agent without splitting identity. A main-world
wrapper cache hit from a different main-world cell is treated as an invariant
violation; cross-agent object graphs are not directly shared.

## Realm rule

Generated bindings make one realm decision per IDL member. Instance members
default to the validated receiver realm; static members, constructors, and
namespace members use the caller/current realm; `[NeedsCallerRealm]` is the
single opt-out for instance members whose observable allocation realm is the
caller realm.

The detailed policy, including exception wrapping and current opt-out
categories, lives in
[REALM_POLICY.md](../Meta/Generators/libweb_bindings/REALM_POLICY.md).

`WindowProxy` has one special receiver rule: for main-world proxies,
`Bindings::this_value_realm()` resolves to the active `[[Window]]` realm; for
non-main-world proxies, it stays in the proxy's own realm.

## Realms for DOM developers

Implementation code should not mention realms unless the algorithm is
explicitly binding-adjacent or JS-object-materialization code. Bindings choose
the observable realm for exceptions, return values, callbacks, and ordinary
wrapper creation.

For day-to-day DOM implementation work, realm decisions should fit in this
contract:

> Implementation code never mentions realms. Bindings choose realms. You think
> about global ownership in exactly two situations: (1) a non-`Node` wrappable
> may be observed from several globals before it is wrapped — override
> `relevant_global_impl()` and return the owning `Window`, `Document`'s window,
> or worker global-scope implementation; (2) you mint a `Promise` for later
> resolution — use the promise vending helper for the relevant object or
> settings object. Everything else — exceptions, return values, events,
> callbacks — is realm-free in ordinary implementation code.

Existing implementation-side realm mentions are transitional unless they are
needed for spec-selected JS object materialization, stream/chunk conversion,
structured serialization, module/script plumbing, or callback invocation. New
realm mentions outside `Bindings/`,
`HTML/Scripting/`, and `WebIDL/` should be treated as architecture changes and
must be justified in the lint allowlist.

## Preservation and wrapper state

Any non-default JavaScript state on a platform wrapper preserves that exact
wrapper. This includes:

- ordinary, accessor, and symbol expandos;
- expando creation through inline-cache paths;
- subclass construction state;
- custom prototypes;
- non-default extensibility/frozen/sealed state.

The C++ inline-cache preservation trigger is
`JS::Object::Flag::RequiresSlowAddOwnProperty`: platform wrappers set this flag
so fast expando writes route through the preserving add-own-property path.

Preservation is intentionally append-only for a wrapper. Deleting an expando
does not unpreserve the wrapper; tests for deletion should assert the deleted
property stays deleted after GC, not that the wrapper becomes collectable.

Tests should use `withCollectedWrapper(makeAndMark, reacquire, verify)` from
`Tests/LibWeb/Text/input/include.js` when checking preserved-wrapper behavior.
That helper verifies `internals.wrapperIsPreserved()` before forcing GC, drops
the local wrapper reference, then re-acquires through the implementation object.

## Liveness model

Ordinary wrapper/implementation edges are:

- wrapper to implementation: strong, generated wrapper member;
- implementation to cached wrapper: weak, through `WrapperWorld` and the
  inline main-world representative;
- implementation to preserved wrapper: strong, visited by `Wrappable`.

Pending activity is explicit. Objects such as timers, XHR, WebSocket, and
ResizeObserver take and release `GC::ActivityRoot`s at their state transitions
instead of relying on silent GC virtual predicates.

LibGC finalization clears wrapper cache entries before weak pointers are
observed as live again. Code that depends on weak wrapper caches relies on that
ordering: wrapper finalization removes the cache entry, then weak references can
clear.

## Window and Location cross-origin descriptors

Cross-origin property descriptor maps live on the per-world wrapper, not on the
implementation object. The Window and Location wrappers both keep theirs in a
companion cell in the wrapper's host data slot, created on the first
cross-origin property access and reached through
`HTML::cross_origin_property_descriptor_map()`. Descriptor cache reachability is
ordinary wrapper reachability: cached descriptor values and accessors are traced
through the wrapper, but they are not roots, so a wrapper/realm cycle can still
be collected when nothing outside the cycle reaches it. This removes the need
for world serials or detach-time pruning of implementation-owned maps.

Each world's wrapper has its own descriptor map. A descriptor cached for one
world cannot alias another world's descriptor because it is stored in a
different wrapper object.

## Accepted risks

- Adopted nodes may keep using a wrapper from their original main-world realm
  when that wrapper was already preserved. This is accepted because clearing the
  edge would make wrapper identity depend on navigation/bfcache timing.
- Preservation can over-retain wrappers after state returns to default, such as
  after deleting an expando. This is accepted because append-only preservation is
  simple, deterministic, and avoids a fragile "all wrapper state is default"
  detector.
- A non-`Node` implementation without a relevant-global override falls back to
  the caller's preferred realm. If its unpreserved wrapper dies, a replacement
  wrapper may therefore be allocated in a different realm in the same world.
  Wrapper-world identity remains unchanged; implementations with an override
  derive stable placement from their owning global implementation.
- Cross-origin descriptor cache keys use raw environment-settings-object
  addresses. This is accepted because the key is only used inside a descriptor
  map owned by one live wrapper, and entries are not reused after the wrapper
  dies. The raw addresses are identity tokens for same-lifetime cache lookup,
  not owning references.

## Future implementation representation

The binding layer must not assume implementation objects are permanently
GC-allocated. The expected long-term state is heterogeneous: some implementations
may remain GC cells, some may become C++ refcounted objects, and some may become
Rust-backed handles.

Keep new wrapper work behind the narrow binding surface:

- `wrap()`;
- `impl_from<T>()` / `wrappable_impl()`;
- `preserve_wrapper()`;
- `WrapperWorld` caches;
- the generated root wrapper's implementation member.

Do not add ad-hoc implementation-pointer plumbing or new GC-specific liveness
side channels outside that surface. When non-GC implementation objects arrive,
the migration points are the generated implementation handle, weak
implementation-to-wrapper caches, preserved-wrapper roots, and the current
`ActivityRoot` model.

## Testing wrapper and GC behavior (mandatory rules)

These rules exist because early wrapper work produced ~10 regression tests
that could not fail and two whose checked-in expectations encoded the bug they
were meant to catch. They are hard requirements for any test touching wrapper
identity, preservation, realms, or GC:

1. **Drop and re-acquire.** A wrapper held in a live local across
   `internals.gc()` can never be collected, so the test asserts nothing.
   Create/mutate inside an IIFE (or null the variable), `gc()`, then
   re-acquire through the implementation (`getElementById`, collection
   access) and assert on the re-acquired object. Prefer
   `withCollectedWrapper()` from `Tests/LibWeb/Text/input/include.js`.
   Holding a ref is only correct when the test's point is that a held ref
   pins identity — say so in a comment.
2. **`Function` takes (params..., body).** `Function("x => f(x)")` makes the
   string the function *body* of a zero-argument function. For cross-realm
   calls use `frame.contentWindow.Function("arg", "return arg.item(0)")` and
   verify the result is not `undefined`.
3. **Read every expectation file before committing it.** If the expected
   output contains `false`, `undefined`, or an unchanged "before" value,
   either the test records a failure or it is an intentional negative
   assertion — if the latter, say so in the test.
4. **Prove the test can fail.** For every regression test guarding a fix,
   revert the fix locally, watch the test fail, restore, and record the
   failure text in the change description. A test that passes both with and
   without the fix is a coverage pin — label it as such.
5. **Never mark work done with an unmet acceptance criterion** — record an
   explicit deviation instead. When several test failures share a signature,
   diagnose the shared cause before writing per-site fixes; a green gate is a
   necessary condition, never the goal.

---

### Documentation EditorConfiguration AndroidStudioConfiguration (`Documentation/EditorConfiguration/AndroidStudioConfiguration.md`)

## Android Studio Project Configuration

The Android port of Ladybird has straightforward integration with the Android Studio IDE.

## Prerequisites

Ensure that your system has the following tools available:

- Android Studio Narwhal 2025.1.1 or later
- CMake 3.23 or higher as the default CMake executable
- 20G or more storage space for SDKs + Emulator images + Gradle dependencies + build artifacts

## Opening the project

After opening the ``ladybird`` directory in Android Studio (NOT the UI/Android directory!)
there should be a pop-up in the bottom left indicating that an Android Gradle project was detected
in ``UI/Android``.

In the top left of the screen in the Project view, navigate to ``UI/Android``. Or, click the
highlighted text in the notification for that path. Open the ``settings.gradle.kts`` file. At the
top of the file should be a banner that says ``Code Insight unavailable (related Gradle project not
linked).`` Click the ``Link Gradle project`` text on the right side of the banner. After the IDE
loads the Gradle view to the right of the code window, go back to the banner at the top of the
``settings.gradle.kts`` file and click ``Load Script Configurations`` to finish loading the Gradle
project.

Gradle will index the project, and download all the required plugins. If it complains about no NDK,
follow the instructions in Android Studio to install an appropriate NDK version. If it still
complains about the NDK version, open ``File->Invalidate Caches...`` and click  ``Invalidate and
Restart``.

## Getting the most out of the IDE

See the sections in the [CLionConfiguration](CLionConfiguration.md) for [Excluding Build Artifacts](CLionConfiguration.md#excluding-build-artifacts),
and [Code Generation Settings](CLionConfiguration.md#code-generation-settings).

---

### Documentation EditorConfiguration CLionConfiguration (`Documentation/EditorConfiguration/CLionConfiguration.md`)

## CLion Project Configuration

CLion can integrate with CMake to provide code comprehension features.

After opening the `ladybird` repository in CLion as a new project, the "`Open Project Wizard`" window will open.

Select the `Release` Preset in the `Settings -> Build, Execution and Deployment -> CMake` window and check the `Enable profile` checkbox.
Then select the `Debug` profile and uncheck the `Enable profile` checkbox.

If the build complains that there is no `Default` Toolchain, go to the `Settings -> Build, Execution and Deployment -> Toolchains`
tab and copy the currently defaulted host toolchain and rename it to `Default`. Make sure that the compiler chosen has a version of
supported by the [build system](../BuildInstructionsLadybird.md#build-prerequisites).

## Excluding Build Artifacts

Source files are copied to the `Build` directory during the build, if you do not exclude them from CLion indexing they will show up
in search results. This is often confusing, unintuitive, and can result in you losing changes you have made to files. To exclude
these files navigate to the `Project` tool window, right-click the `Build` folder and select `Mark Directory as | Excluded`.

## Include headers and source files for code insight

To get proper code insight mark the folders `AK` and `Libraries` by right-clicking on them and selecting `Mark Directory as | Project Sources and Headers`.

A symptom of this not being configured correctly is CLion giving a warning for every single file:
> The file does not belong to any project target, code insight features might not work properly.

## Code Generation Settings

To make code generated by CLion match the Ladybird coding style, import the `CLionCodeStyleSettings.xml` from this directory as code style scheme via
`Settings -> Editor -> Code Style -> C/C++ -> Scheme -> Cog icon -> Import Scheme...`

## CMake Error Messages

This section outlines common CMake error messages and provides guidance on how to resolve them.

### Unable to Find Package

You may encounter an error message similar to the following:

```
CMake Error at AK/CMakeLists.txt:60 (find_package):
By not providing "Findsimdutf.cmake" in CMAKE_MODULE_PATH, this project has
requested CMake to find a package configuration file for "simdutf", but
CMake could not locate one.
...
```

This error typically arises when CLion is not configured to use the correct build directory.

**Solution**: Ensure that CLion's build directory is set to the correct build directory for the selected profile.
Navigate to `Settings -> Build, Execution, Deployment -> CMake` and in your selected profile, set the `Build directory` according to the profile:
- Default -> "`Build/release`"
- Debug -> "`Build/debug`"
- Sanitizer -> "`Build/sanitizer`"


## Notes for WSL Users

### Toolchain

If the ladybird directory is on the WSL filesystem you need to configure the CLion toolchain to be WSL.
To set that up go to `File->Settings->Build, Execution, Deployment->Toolchains` and click on the `+` icon, then select WSL. In `Toolset` select the distribution you have the ladybird directory on.

### Terminal

It is possible to set the embedded terminal in CLion to the one that your WSL distribution provides.
This way you can build and run ladybird without leaving the IDE.
Note that following will only help if you don't use an X-window server to access qemu.
It is possible to install qemu natively on Windows and allow WSL to use it instead of installing qemu first on (wsl) linux and then use X server to launch ladybird inside of it.
Check the updated manual [here](../BuildInstructionsLadybird.md#windows).

- Locate the terminal emulator for your linux distribution.
Open CMD with elevated privileges and cd to `C:/Program Files/WindowsApps/`.
The directory is usually hidden and requires additional privileges. You should be able to cd as administrator.
`dir` and look for your distribution in directory names. In case of Ubuntu, it starts with `CanonicalGroupLimited.Ubuntu20.04onWindows_2004.2020.424.0_x64`.
cd to it. The directory should contain the shell executable. In my case it's named `ubuntu2004.exe`.
Copy `absolute/path/to/ubuntu2004.exe`.

- Go to your IDE settings: `File->Settings->Tools->Terminal` and paste the path you just copied to `shell path`. Click OK.

- Close CLion and restart.

The default IDE terminal should now be changed to WSL, and now you can run `CLion/run.sh`.
You may also want to copy `ladybird/Meta/CLion/run.sh` to your project directory and run it from there, so that you don't have to fight with git every time you modify the script.

---

### Documentation EditorConfiguration EmacsConfiguration (`Documentation/EditorConfiguration/EmacsConfiguration.md`)

# Emacs Project Configuration

Emacs can be configured with `lsp-mode` and `clangd` to work well.

### clangd

The official clangd extension can be used for C++ comprehension.

Run cmake (`Meta/ladybird.py run ladybird` or similar) at least once for clangd
to work, as doing so will generate the `Build/release/compile_commands.json`
that is needed by `clangd`.

### lsp-mode

```lisp
(use-package lsp-mode
  :hook ((c++-mode) . lsp-deferred)
  :commands lsp
  :config
  (setq lsp-clients-clangd-args '("-j=4" "-background-index" "--log=error" "--clang-tidy" "--enable-config"))
  ;; Optionally, set the location of clangd -- See notes below for options.
  (setq lsp-clangd-binary-path "/usr/bin/clangd"))

```

### clangd

There are a few different ways to specify which clangd to use:

- By default, without configuration `lsp-mode` will try to find and use your system `clangd`. This is the easiest solution, but your system clangd might be out of date.
- You can manually specify any `clangd` binary with `lsp-clangd-binary-path`, as shown in the use-package example above.
- You can have `lsp-mode` manage your `clangd` installation with emacs' `lsp-install-server`. This will install a `clangd` binary for you.
- You can build the LLVM toolchain, including `clangd`, from Ladybird's repository. This is an advanced option that is not currently documented.

### clang-format

There are multiple packages to handle auto formatting with
`clang-format`, within emacs. Choose what works best for your setup:

- [format-all-mode](https://github.com/lassik/emacs-format-all-the-code)
- [clang-format-plus](https://github.com/SavchenkoValeriy/emacs-clang-format-plus)

Alternatively, this can be done without additional packages, using `lsp-mode`.
You can use the following `.dir-locals.el` file placed in the project root:

```lisp
((c++-mode
  (eval add-hook 'before-save-hook #'lsp-format-buffer nil t)))
```

---

### Documentation EditorConfiguration HelixConfiguration (`Documentation/EditorConfiguration/HelixConfiguration.md`)

# Helix Configuration
Helix comes with support for `clangd` and `clang-format` out of the box! However, you also need to configure the clangd server to not insert headers improperly. To do this, create a `.helix/languages.toml` file in the project root:
```toml
[language-server.ladybird]
command = "clangd"
args = ["--header-insertion=never"]

[[language]]
name = "cpp"
language-servers = ["ladybird"]
```

---

### Documentation EditorConfiguration NvimConfiguration (`Documentation/EditorConfiguration/NvimConfiguration.md`)

# Neovim Project Configuration

There are two documented ways to support clangd in Neovim for the Ladybird
project:

1. With [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) (assumes existing setup)
2. With [coc.nvim](https://github.com/neoclide/coc.nvim) (from scratch, potentially out of date)

For both setups, make sure you ran `./Meta/ladybird.py run ladybird` at least
once.

## With nvim-lspconfig

> Note: This guide assumes Lua is being used, but can easily be adapted to
> VimScript.

If you have an [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig), setup
already, registering clangd is similar to other LSPs:
```lua
require('lspconfig').clangd.setup {
  -- If you have an on_attach function, this is where you'd put it. If not,
  -- you can delete this line.
  -- on_attach = ...,

  -- This is where you'd put capabilities (i.e. if you're using nvim-cmp)
  -- capabilities = ...,

  -- If you have another clangd installation, put it here. Note that we use
  -- clangd version 18 for the Ladybird project.
  -- cmd = { '/path/to/clangd' },
}
```

A base `setup` call should be enough to register the LSP, but if false-negative
LSP errors occur upon opening Neovim, make sure your clangd installation is at
least version 18.

## With coc.nvim

> Note: This guide assumes VimScript is being used, but can easily be adapted to
> Lua. It also starts from scratch.

Neovim can be configured to use the [coc-clangd](https://github.com/clangd/coc-clangd)
plugin to provide code-completion as well as inline
[git blame](https://github.com/f-person/git-blame.nvim) using [vim-plug](https://github.com/junegunn/vim-plug).
Formatting can also be configured with [formatter.nvim](https://github.com/mhartington/formatter.nvim).

### Install vim-plug

See [https://github.com/junegunn/vim-plug](https://github.com/junegunn/vim-plug).

### Install coc.nvim

The config file for neovim is at `~/.config/nvim/init.vim` or if
set `$XDG_CONFIG_HOME/nvim/init.vim`.

Add the Plugin:

```vim
Plug 'neoclide/coc.nvim', { 'branch': 'release' }
```

Run `:PlugInstall` inside nvim.

### Install coc-clangd via CocInstall

```vim
:CocInstall coc-clangd
```

> **Note**: This guide is tested with clangd version 14.0.6 and 15.0.6.

In case you have not installed clangd already install it with
`:CocCommand clangd.install`.

This will install a separate version of clangd just for neovim.

### Configure coc-clangd in coc-settings.json

Use the following settings to ensure that coc-clangd works out of the box.

> **Note**: You might want to adjust the `clangd.fallbackFlags` depending on your build
system and customize the `inlayHints.sep` based on your preference.

```json
{
    "clangd.fallbackFlags": ["-std=c++23"],
    "semanticTokens.enable": true,
    "inlayHint.subSeparator": "︴",
    "inlayHints.enableParameter": true,
    "clangd.inlayHints.sep": "⇝"
}
```

To change the coc-settings.json go to the file `~/.config/nvim/coc-settings.json`
or type `:CocConfig` in the command line.

> **Note**: In case you already had another c++ language server configured in the
`coc-settings.json` you might want to nuke it first and
work towards your desired config by adding the other parts back in to avoid
conflicts.

> **Note**: If you have configured `clangd` as a languageServer in
`coc-settings.json`, you should remove it to avoid running clangd twice!

> **Note**: `clangd.inlayHints.sep` breaks on `clangd 15.0.6`.

### Formatting
For code formatting the formatter plugin can be used.
```vim
Plug 'mhartington/formatter.nvim'
```
#### Configuration
To use the formatter plugin one needs to opt-in to specific formatters. An example lua configuration which uses clang-format for cpp files:
```lua
require("formatter").setup{
    filetype = {
        cpp = {
            require("formatter.filetypes.cpp").clangformat
        }
    }
}
```

### Install git blame (Optional)

```vim
Plug 'f-person/git-blame.nvim'
```

Run `:PlugInstall` inside nvim.

### Configure your init.vim

The config file for neovim is at `~/.config/nvim/init.vim` or if
set `$XDG_CONFIG_HOME/nvim/init.vim`.

The `init.vim` excerpt:

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Documentation EditorConfiguration QtCreatorConfiguration (`Documentation/EditorConfiguration/QtCreatorConfiguration.md`)

# Qt Creator Project Configuration

## Setup

First, make sure you have a working toolchain and can build and run Ladybird. Go [here](../BuildInstructionsLadybird.md) for instructions for setting that up.

* Install [Qt Creator](https://www.qt.io/offline-installers). You don't need the entire Qt setup, just click 'Qt Creator' on the left side, and install that.
* Open Qt Creator, select `File -> New File or Project...`
* Select `Import Existing Project`
* Give it a name (some tools assume lower-case `ladybird`), and navigate to the root of your Ladybird project checkout. Click Next.
* Wait for the file list to generate. This can take a minute or two!
* Ignore the file list, we will overwrite it later. Click Next.
* Set `Add to version control` to `<None>`. Click Finish.
* In your shell, go to your Ladybird project directory, and invoke the `Meta/refresh-ladybird-qtcreator.sh` script to regenerate the `ladybird.files` file. You will also have to do this every time you delete or add a new file to the project.
* Edit the `ladybird.config` file (In Qt Creator, hit ^K or CMD+K on a Mac to open the search dialog, type the name of the file and hit return to open it)
* Add the following `#define`s to the file:
    ```
    #define ENABLE_COMPILETIME_FORMAT_CHECK
    ```
* Edit the `ladybird.cxxflags` file to say `-std=c++23 -fsigned-char -fconcepts -fno-exceptions -fno-semantic-interposition -fPIC`
* Edit the `ladybird.includes` file to list the following lines (adapt to the actual path of your skia folder):
    ```
    ./
    Libraries/
    Services/
    Build/release/
    Build/release/Libraries/
    Build/release/Services/
    Build/release/vcpkg_installed/x64-linux/include/skia/
    AK/
    ```

Finally, search in the options for "BOM" (Text Editor > Behavior > File Encodings > UTF-8 BOM), and switch to "Always delete".

Qt Creator should be set up correctly now, go ahead and explore the project and try making changes. Have fun! :^)

## Auto-Formatting

You can use `clang-format` to help you with the [style guide](../CodingStyle.md). Before you proceed, check that you're actually using the clang-format version specified in the style guide, as some OSes will ship other clang-format versions by default.

- In QtCreator, go to "Help > About Plugins…"
- Find the `Beautifier (experimental)` row (for example, by typing `beau` into the search)
- Put a checkmark into the box
- Restart QtCreator if it asks you
- In QtCreator, go to "Tools > Options…"
- Type "beau" in the search box, go to "Beautifier > Clang Format"
- Select the "customized" style, click "edit"
- Paste the entire content of the file `.clang-format` into the "value" box, and click "OK"
- In the "Beautifier > General" tab, check "Enable auto format on file save"
- Select the tool "ClangFormat" if not already selected, and click "OK"

Note that not the entire project is clang-format-clean (yet), so sometimes you will see large diffs.
Use your own judgement whether you want to include such changes. Generally speaking, if it's a few lines then it's a good idea; if it's the entire file then maybe there's a better way to do it, like doing a separate commit, or just ignoring the clang-format changes.

You may want to read up what `git add -p` does (or `git checkout -p`, to undo).

QtCreator tends to interpret IPC definitions as C++ headers, and then tries to format them. This is not useful. One way to avoid that is telling QtCreator that IPC definitions are not C++ headers.
- In QtCreator, go to "Tools > Options…"
- Type "beau" in the search box, go to "Environment > MIME Types"
- In the little search box, type "plain", and select "text/plain"
- In the "details" section, you should now see the list of Patterns, something like `*.txt;*.asc;*,v`. Extend it in the following way: `*.txt;*.asc;*,v;*.ipc;*.gml`
- Click "OK" to close the dialog.
- Maybe you need to close and open again the IPC files. You can check what QtCreator is doing by right-clicking the filename in the editor tab, and clicking "Properties...". In the third line, you should see `MIME type: text/plain`.

## License template

You may have noticed how Andreas just types `lic` and the [license appears](https://youtu.be/i0J6J1Twwyo?t=346).

In order to so, create a new file anywhere, for example `license-template.creator`, with the standard license:

```
/*
 * Copyright (c) 2024-present, the Ladybird developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */
```

In QtCreator, select the menu "Tools", item "Options", section "C++", tab
"File Naming" (don't ask me why it's here). At the bottom there should be the
option "License template:". Click "Browse…", select your file (i.e.,
`license-template.creator`). Click "OK", and you're done! :)

---

### Documentation EditorConfiguration VSCodeConfiguration (`Documentation/EditorConfiguration/VSCodeConfiguration.md`)

# Visual Studio Code Project Configuration

Visual Studio Code requires some configuration files, and a tailored ``settings.json`` file to understand ladybird.

The WSL Remote extension allows you to use VS Code in Windows while using the normal WSL workflow. This works well, but for code comprehension speed you should put the ladybird directory on your WSL root partition.

The recommended extensions for VS Code include:

- [CMake Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools)
- [clangd](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd)
- [GitLens](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)

## Configuration

Run `./Meta/ladybird.py build` at least once to kick off downloading and building vcpkg dependencies.

The CMake Tools plugin should automatically detect the `CMakePresets.json` at the root of the repository.
Selecting and activating the `default` preset should be enough to get started after the initial build.
You can also use the `Debug` preset to build with debug symbols, or the `Sanitizer` preset to build with ASAN/UBSAN.

For additional settings recommendations, see the [Settings](#settings) section below.

## Code comprehension

Clangd has the best support for modern compilers, especially if configured as noted below. The Microsoft C/C++ tools can work, but may require more configuration.

### clangd

The official clangd extension can be used for C++ comprehension. It is recommended in general, as it is most likely to work on all platforms.

clangd uses ``compile_commands.json`` files to understand the project. CMake will generate these in Build/release.

Run ``./Meta/ladybird.py run ladybird`` at least once to generate the ``compile_commands.json`` file.

#### Known issues

- 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.

### Microsoft C/C++ tools

Note that enabling the extension in the same workspace as the  clangd and clang-format extensions will cause conflicts.
If you choose to use Microsoft C/C++ Tools rather than clangd and clang-format, use the
following ``c_cpp_properties.json`` to circumvent some errors. Even with the configuration in place, the extension will likely still report errors related to types and methods not being found.

<details>
<summary>.vscode/c_cpp_properties.json</summary>

```json
{
    "configurations": [
        {
            "name": "ladybird-gcc",
            "includePath": [
                "${workspaceFolder}",
                "${workspaceFolder}/Build/release/",
                "${workspaceFolder}/Build/release/Libraries",
                "${workspaceFolder}/Build/release/Services",
                "${workspaceFolder}/Libraries",
                "${workspaceFolder}/Services"
            ],
            "defines": [
                "DEBUG"
            ],
            "cStandard": "c17",
            "cppStandard": "c++23",
            "intelliSenseMode": "linux-gcc-x86",
            "compileCommands": "Build/release/compile_commands.json",
            "compilerArgs": [
                "-Wall",
                "-Wextra",
                "-Werror"
            ],
            "browse": {
                "path": [
                    "${workspaceFolder}",
                    "${workspaceFolder}/Build/release/",
                    "${workspaceFolder}/Build/release/Libraries",
                    "${workspaceFolder}/Build/release/Services",
                    "${workspaceFolder}/Libraries",
                    "${workspaceFolder}/Services"
                ],
                "limitSymbolsToIncludedHeaders": true,
                "databaseFilename": "${workspaceFolder}/Build/release/"
            }
        }
    ],
    "version": 4
}
```
</details>

## Formatting

clangd provides code formatting out of the box using the ``clang-format`` engine. ``clang-format`` support is also included with the Microsoft C/C++ tools (see above). The settings below include a key that makes the Microsoft extension use the proper style.

## Settings

These belong in the `.vscode/settings.json` of Ladybird.

```json
{
    // Excluding the generated directories keeps your file view clean and speeds up search.
    "files.exclude": {
        "**/.git": true,
        "Build/**": true,
    },
    "search.exclude": {
        "**/.git": true,
        "Build/**": true,
    },
    // Force clang-format to respect Ladybird's .clang-format style file. This is not necessary if you're not using the Microsoft C++ extension.
    "C_Cpp.clang_format_style": "file",
    // Tab settings
    "editor.tabSize": 4,
    "editor.useTabStops": false,
    // format trailing new lines
    "files.trimFinalNewlines": true,
    "files.insertFinalNewline": true,
    // git commit message length
    "git.inputValidationLength": 72,
    "git.inputValidationSubjectLength": 72,
    // If clangd was obtained from a package manager, its path can be set here.
    "clangd.path": "clangd-18",
    "clangd.arguments": [
        "--header-insertion=never" // See https://github.com/clangd/clangd/issues/1247
    ]
}
```

## Customization

### Custom Tasks

You can create custom tasks (`.vscode/tasks.json`) to quickly compile Ladybird.
The following three example tasks should suffice in most situations, and allow you to specify the build system to use, as well as give you error highlighting.

<details>
<summary>.vscode/tasks.json</summary>

```
/* Detailed source-code truncated for AI context efficiency. */
```

</details>

### Debugging
#### Mac
If you want to run the debugger, first place the content below in `.vscode/launch.json` in the root of the project.

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Attach to WebContent",
            "type": "lldb",
            "request": "attach",
            "program": "${workspaceFolder}/Build/debug/bin/Ladybird.app/Contents/MacOS/WebContent",
        }
    ],
}
```

then run Ladybird with the debug preset and with the `--debug-process WebContent` flag:

```bash
CC=$(brew --prefix llvm)/bin/clang CXX=$(brew --prefix llvm)/bin/clang++ BUILD_PRESET=Debug ./Meta/ladybird.py run ladybird --debug-process WebContent
```

Running Ladybird in this way will pause execution until a debugger is attached. You can then run the debugger by going to the **Run and Debug** menu and selecting the **Attach to WebContent** configuration.

#### Linux
For Linux, the `launch.json` will instead be the file below.

```json
{
  "version": "2.0.0",
  "configurations": [
    {
      "name": "Attach and debug",
      "type": "cppdbg",
      "request": "attach",
      "program": "${workspaceRoot}/Build/debug/libexec/WebContent",
      "MIMode": "gdb",
    },
  ]
}
```

Running Ladybird as follows:

```bash
BUILD_PRESET=Debug Meta/ladybird.py run ladybird --debug-process WebContent
```

Then follow the same steps found in the Mac section. Notice also that you need to have `gdb` (the GNU Debugger) installed.

### License snippet

The following snippet may be useful if you want to quickly generate a license header, put it in `.vscode/ladybird.code-snippets`:
```json
{
    "License": {
        "scope": "cpp,c",
        "prefix": "license",
        "body": [
            "/*",
            " * Copyright (c) $CURRENT_YEAR, ${1:Your Name} <${2:YourName@Email.com}>.",
            " *",
            " * SPDX-License-Identifier: BSD-2-Clause",
            " */"
        ],
        "description": "License header"
    }
}
```

---

### Documentation EditorConfiguration VimConfiguration (`Documentation/EditorConfiguration/VimConfiguration.md`)

# Vim Project Configuration

Vim can be configured to use the [YouCompleteMe](https://github.com/ycm-core/YouCompleteMe)
plugin to provide code-completion.

Once you've installed the YouCompleteMe plugin you should whitelist the
configuration file that is part of the ladybird repository. You can
do this by adding the following option to your `.vimrc` file:

```vim
let g:ycm_extra_conf_globlist = ['~/ladybird/.ycm_extra_conf.py']
```

---

### Documentation HumanInterfaceGuidelines Text (`Documentation/HumanInterfaceGuidelines/Text.md`)

# Guidelines for user interface text in Ladybird

## Capitalization

Ladybird employs two capitalization styles:

- Book title capitalization
- Sentence-style capitalization

### Book title capitalization

In this style, we capitalize the first letter of the first and last word,
as well as all words in between, *except* articles (a, an, the);
the seven coordinating conjunctions (for, and, nor, but, or, yet, so);
and prepositions with up to four letters (at, by, for, with, into, ...)

#### Examples:
- Create New Layer
- Copy URL
- Move to Front
- Save and Exit
- Sort by Name

#### Used for:

- Button text
- Icon labels
- Menu names
- Menu items
- Tab titles
- Window titles
- Tooltips

### Sentence-style capitalization

This style follows basic English sentence capitalization.
We capitalize the first letter of the first word, along with the first letter
of proper names, weekdays, etc.

#### Examples:
- An error occurred
- Use system defaults
- Copy the selected text
- Enable Linux compatibility hacks

#### Used for:

- Check box labels
- Group box labels
- List items
- Messages (as in message boxes)
- Radio button labels
- Status bar text
- Text box labels

## Ellipses

The ellipsis, represented by a series of three periods (...), has two special
functions in the interface:

- Eliding text
- Foreshadowing additional user input

The first occurs programmatically, but the second requires care when setting
text manually.

Control text which implies an action whose effect is incomplete pending further
user input should end in an ellipsis. Opening a new window does not in itself
justify the use of an ellipsis; the dialog must be an intermediate step toward
completing the action.

Ellipses should be used sparingly elsewhere to avoid confusion with elision.

#### Examples:
- Save As...
- Browse...
- Insert Emoji...

---

### Documentation Style RustCSSMigration (`Documentation/Style/RustCSSMigration.md`)

# Rust stylesheet ownership

Downloaded link and import sheets use
`Parser::parse_stylesheet_off_thread`. CSSOM parsing borrows native text synchronously.
Workers receive owned parsing context, never document or interner access.

`ParsedStyleSheet` is immutable and thread-shareable. Its weak parse cache is
keyed by native sheet text and parsing context. Parsed selectors, declarations,
descriptors and URL spelling use shared Rust storage. Parsing and destruction
on a worker must not call C++ or the main-thread `Utf16FlyString` interner.

`NativeRuleList` retains that graph and reserves stable identities without
creating mutable rule or declaration owners. Its visible-rule indexes support
indexed CSSOM access. A sparse map holds exposed rules; order-changing mutation
can materialize a list without recursively materializing descendants.
`RuleRef` traverses both exposed and untouched rules. Keyframes use the same
child list for compilation and CSSOM, with no parallel frame-owner list.

`rule/compilation.rs` binds names on the document thread and publishes directly
into the Rust style engine. Compiled targets retain immutable declarations,
source identities and condition inputs, not CSSOM objects. Cascading borrows
native stylesheet and inline declaration data.
Media evaluation and condition/layer traversal follow imports directly in Rust.
The C++ entry points borrow the media environment once; layer traversal reuses
one UTF-16 prefix buffer across the graph. Rust also merges cascade layer order
and publishes condition gates directly. Compilation and layer publication use
native operations; C adapters exist only for host callers.

`StyleSheetState` and `StyleSheetImport` own document/loading state independently
of the GC-allocated `CSSStyleSheet` and `CSSImportRule` facades. Imports retain
their loading media identity. Font, function, keyframe and property-registration
consumers read shared rule data without creating CSSOM wrappers.

CSSOM facades retain native rules and borrow their payloads where possible.
Live declaration and descriptor handles share mutation state; independent
sheets share immutable data until mutation. Explicit immutable snapshots
remain valid across mutation. C++ property caches needed by CSSOM/resource
consumers are populated directly, without an intermediate cached FFI array.

CSS URL spelling may contain Unicode. URL resolution is a later document-thread
operation, not worker stylesheet parsing. LibURL accepts borrowed ASCII/UTF-16,
and parsed URL components and base URL snapshots stay ASCII. The IDNA boundary
still calls C++/ICU. Rust and AK use the same C allocator for transferable buffers.

The Rust load-and-compile invariant covers worker parsing, imports, media,
namespaces, layers and definition readers. It requires zero C++ worker calls
and zero mutable rule, declaration or descriptor owner allocations for an
untouched sheet. HTML/JS tests cover mutation, sharing, imports and wrapper
identity. Keep those guarantees when simplifying this boundary.

---

### Documentation Style StyleEngine (`Documentation/Style/StyleEngine.md`)

# StyleEngine: differential style resolution

This document describes LibWeb's style engine: its data structures, algorithms, protocols, and memory rules, as implemented. Normative statements ("never", "must") are invariants the implementation upholds and that changes must preserve; they are review tests, not aspirations. Testing and debugging workflows live in [StyleEngineTesting.md](StyleEngineTesting.md).

`StyleEngine` is LibWeb's implementation of CSS selector evaluation, style invalidation, cascade, and computed-value computation. Mutations reach it as typed input deltas (§4.3), and compiled delta routing (§6.3) carries each delta to the transpose entry points that mention that input: every selector program that can change under the delta is reached, and subjects are match-checked before any truth delta is emitted. Selector answers, cascade winners, and computed styles are shared, interned values: elements hold a compact `StyleRecordID` into interned computed-value groups (§9.8), and every consumer of style (layout, paint, hit testing, accessibility, script APIs, and the rest of the inventory in §12) reads through that shared handle.

Correctness rests on the engine's internal fallback chain: incremental evaluation, then exact evaluation of the affected scope, widening to the whole document when required (§10.6). Every path reads the same authoritative live DOM, and the exact cold evaluator is the reference implementation the others are checked against; the machinery that continuously enforces this is described in [StyleEngineTesting.md](StyleEngineTesting.md).

One organizing frame runs through the whole document and is stated in §1.1: StyleEngine is a database, specifically an incremental view maintenance engine over a change stream. Mechanisms are described together with their database-world counterparts, in notes marked *Database counterpart*.

## 1. What the engine does

Conventional style engines do two things per change: conservatively determine which elements *may* be affected, then re-run selector matching, cascade, and computed-value construction for all of them. Work is proportional to the *possible* effects of a change rather than its *actual* semantic effects.

StyleEngine models styling as a memory-bounded incremental computation. DOM structure, element state, stylesheets, CSSOM mutations, cascade topology, and environment values are versioned inputs. Selectors, cascade decisions, and computed values form a shared logical dependency program. A style flush discovers and propagates exact changes through that program and stops as soon as a semantic value is unchanged.

The pipeline:

```text
Style input transaction
    -> tree, state, stylesheet, cascade, and environment deltas
    -> exact selector-truth deltas
    -> exact matched-rule deltas
    -> exact cascade-winner deltas
    -> exact computed-property deltas
    -> layout, paint, animation, and observable-style changes
```

Each arrow is a stopping point. If a stage's output identity is unchanged, the next stage receives nothing.

The cost of a transaction is:

```text
work = normalize + discover + propagate + reconstruct + observe
```

`discover` can dominate even when no semantic output changes, so it is accounted separately and the engine switches to an exact batch plan when a supposedly selective plan does too much discovery.

**The ordinary path is the primary kernel.** Tag, ID, class, attribute, state, compound, and conventional combinator evaluation must be the fastest and smallest path through the engine. `:has()` is a forcing function for the architecture, not the center of the workload: it extends the same selector primitives with reverse-direction existential maintenance, and it must impose no cost on documents that do not use it (zero relational allocations, zero relational fields).

### 1.1 The engine is a database

StyleEngine is a database engine: an incremental view maintenance engine over a change stream. The queries are standing rather than ad-hoc (attached selector programs change only when the program itself is edited), inputs arrive as typed deltas, and the engine's entire job is to keep a set of partially materialized views consistent with the base data for less than the cost of recomputing them. That places it in the family of streaming materialization systems (differential dataflow, DBSP, and the partially stateful Noria line), not general-purpose OLTP stores, and that family's known results, failure modes, and remedies apply here directly.

The canonical mapping, used throughout:

| StyleEngine mechanism | Database counterpart |
| --- | --- |
| Authoritative DOM, CSSOM, browser state (Tier 0) | Base tables, the single source of truth |
| Attached selector and cascade programs | Standing (continuous) queries |
| Ephemeral selector programs (§6.10) | Ad-hoc queries |
| Normalization journal (§4.4) | Changelog (CDC stream) with key-level compaction |
| Style read epoch (§4.2) | MVCC snapshot read view, without copying |
| Style transaction (§4.4, §11) | Transaction: atomic commit, isolated from script |
| Match program (§6.3) | Query plan: predicate evaluation per candidate row |
| Transpose program (§6.3) | Delta rule: the standing query's derivative |
| Delta-routing registry (§6.3) | Trigger dispatch index |
| Impact region (§6.4) | Delta key range; coverage is fail-closed (gaps widen) |
| Feature postings (§6.6) | Secondary (inverted) indexes, non-clustered |
| Rule matching (§6.8) | A relational join |
| Selective-plan cutoff (§6.7) | Cost-based optimizer: index scan versus full scan |
| Incremental discipline (§3) | Incremental view maintenance (fast refresh) |
| Exact batch plan (§3) | Complete refresh of the affected region; the upquery |
| Witnesses, winner groups, match answers | Partially materialized views |
| Tier-3 budget and eviction (§10) | Buffer pool with admission control |
| Cold-batch protocol (§13) | Storage scan API with projection and predicate pushdown |
| Cost model and counters (§15, §16) | Optimizer cost model and its statistics |

Three consequences of the framing carry weight throughout:

1. **Maintain-versus-refresh is a per-delta optimizer decision, not an architectural loyalty.** No mature system promises that incremental maintenance always wins; it prices the delta against the rescan and chooses. §6.7 is that decision for selector plans, and the same decision governs every retained view, fed by §16's counters. When applying a delta costs more than enumerating the affected scope, rescanning is the optimizer's correct output, not a failure of the design.
2. **Every view is partial.** Any materialization may be missing, and a miss is answered by an upquery to base facts through the exact cold evaluator, never by a wrong answer. That is why eviction is always legal (invariant 5), why memory never scales with the product of DOM and selector count (invariant 3), and why the eviction policy matters: a policy that starves the views whose maintenance would pay converts the engine into a full-scan engine with extra bookkeeping.
3. **Propagation stops at unchanged output.** Every stage boundary in the §1 pipeline is a distinctness check: a delta whose output identity is unchanged is consolidated away and the next stage receives nothing. That is streaming consolidation, and it is invariant 1.

Where the analogy stops: there is no durability and no recovery, which is why the journal is a changelog and not a write-ahead log (§4.4); the data model is an ordered tree whose axes (ancestry, sibling order, the flat tree) are join dimensions no relational engine indexes natively; and the query set is fixed at parse time, so plan compilation is a program-change event, not a per-query cost. Importing machinery for problems the engine does not have (locking, recovery logging, general ad-hoc planning) is a misreading of the framing.

Where a mechanism's counterpart has a known failure mode or remedy in the database literature, a short *Database counterpart* note names it; consult that literature before inventing a new remedy.

## 2. Invariants

These hold for every code path. They are not optional optimizations.

1. If a semantic value does not change, no downstream semantic work is emitted.
2. DOM mutations, state changes, CSSOM changes, and environment changes are equally first-class inputs.
3. Core persistent memory is proportional to DOM size, stylesheet program size, and live observable style state, never to the product of DOM size and selector count.
4. Selector matches, reverse dependencies, witnesses, proofs, and indexes are optional materialized views unless explicitly classified as core state (§10.1).
5. Every optional materialization can be discarded at any time without affecting correctness.
6. High fanout uses exact pull evaluation rather than millions of explicit dependency edges.
7. Cold, disconnected, hidden, and unobserved content does not receive per-selector state merely because it exists.
8. Every cache and arena reports exact byte usage and participates in the document budget.
9. Memory pressure reduces retained acceleration state, never correctness.
10. Interning, compaction, and physical relabeling never create semantic style changes.
11. The exact cold evaluator is part of the architecture. Optional incremental state may accelerate it but is never required for correctness.
12. Every selector dependency has a compiled delta-routing entry point. Routing semantic inputs to selector programs is required program state, not an evictable view.
13. Every scoped evaluation carries an impact-region coverage obligation (§6.4). Unknown scope is never treated as empty scope.
14. Style reads use the authoritative live DOM under a mutation-free read epoch. A complete copied DOM is never allocated.
15. Hot transpose and impact-region traversal has bounded evaluator-local access to every required tree relation, with no boundary crossing per relation step.
16. Every broad or degraded path is named, counted, and reachable in tests. Nothing silently degrades to full-document invalidation.

### 2.1 Explicitly rejected as primary architecture

Alternatives with known failure modes, and why they were rejected:

* **Full materialization of every view**: storing every selector state, match, witness, and reverse edge approaches the product of DOM and stylesheet size. The database framing of §1.1 survives this rejection precisely because its views are partial and evictable.
* **Conservative invalidation followed by restyle**: preserves the separation between possible effect and semantic change, and therefore preserves its work amplification.
* **Permanent proof subscriptions**: exact dependency edges are good for hot low-fanout queries, unacceptable as mandatory state for every match and negative result.
* **Rebuilding on CSSOM program change**: dynamic stylesheet insertion and editing are normal page behavior, especially during load; they use incremental query-program operations.
* **A complete computed-style object per element**: repeated style groups, inherited environments, and animation bases share storage through compact handles.

### 2.2 Engineering principles

These are the working principles the invariants compile down to in day-to-day changes. They are review tests, not aspirations.

**Work is mutation-sized; a whole-DOM traversal is a purchase, never a default.** A style node may be visited only when the mutation named it, a compiled route reaches it, or its own output changes. A whole-document pass is one of three things: output (that many styles changed), a purchase (one counted pass that provably beats the per-node work it replaces), or a named, counted correctness floor. The review test for every loop: name what bounds it; if the bound is the document and the output is not, the loop is a bug, even while it is fast. Visit a node at most once per flush across all stages, and never restart an interrupted pass; grow its window instead.

**Represent state data-oriented: dense tables over sparse maps, columns over objects.** A dense integer key (element index, atom, rule) indexes a vector or segmented column, never a hash map; hash maps are for content-keyed intern tables. Every dense per-node structure takes on the retirement story the day it is born, so a reused identity cannot inherit its predecessor's state. An evicted structure reads as unknown, never as empty.

**Choose presence structures by clearing cost.** A bitmap with a companion set-index list when the set lives for a transaction (cleared through the list, not memset); an epoch-stamped seen array when the set clears per candidate; a sorted vector when built once and only queried. A maintained population count is free exact cardinality where a plan cutoff wants one.

**Sort once at a build boundary; inherit order on the hot path.** Convert order labels to dense ordinals at program build and read integers after. A per-subject sort during evaluation is a smell: inherit order from pre-sorted program structures and merge. Sorting the changed set once per transaction is the right shape; sorting anything document-sized falls under the traversal principle.

**Spend document-order coordinates once they are bought.** With preorder positions and subtree ends, a subtree is a slice, ancestry an interval comparison, and skipping a subtree an index jump. The coordinates answer for one named tree relation and the current side only, and they are transaction scratch unless measurement justifies persistence.

## 3. Evaluation disciplines and plans

Two evaluation disciplines share one semantics:

**Incremental**: the normal discipline. Retained derived state (prefix states, retained answers, winner groups) and exact deltas minimize discovery and downstream work.

**Exact evaluation of a scope**: the engine ignores optional derived state and exactly evaluates an affected region with the exact evaluator against authoritative facts. It may inspect every style node in the region but emits only actual selector, rule, cascade, and computed-value changes. This is the correctness path and the universal response to eviction, memory pressure, missing state, or planner uncertainty. An exact batch is still an exact transaction: broad input scope, only changed outputs.

The choice is made per routed region, as a `Plan`:

```text
Selective   { candidates }   drive from an enumerable candidate source
ExactBatch  { nodes }        sweep the region with the exact evaluator
Conservative                 neither is executable; keep the routed region as-is
```

`Plan::Conservative` is not a restyle mode: it keeps the conservatively routed region, whose members are still checked by the match program before any delta is emitted, so precision is lost but correctness is not. There is no separate "recompute and republish" fallback mode; every degraded path narrows to one of the shapes above, and the broad ones are counted.

## 4. Inputs, epochs, and transactions

### 4.1 Terminology

* **Semantic input**: an authoritative fact from DOM, CSSOM, browser state, or environment.
* **Derived value**: a selector result, rule match, cascade winner, computed value, or damage result computed from semantic inputs.
* **Logical program**: the immutable, shared description of selector, activation, cascade, and property dependencies.
* **Materialized view**: a cached physical representation of a derived value or relation.
* **Style transaction**: a batch of input changes applied atomically from the perspective of style observation.
* **Style read epoch**: a mutation-free interval in which style reads the authoritative live DOM and other versioned inputs.
* **Style node**: the engine's compact identity for a DOM element.
* **Style feature atom**: a compact identity for a selector-mentioned tag, ID, class, attribute, value, namespace, or state fact. It does not own duplicate string payload.
* **Match program**: a logical-IR evaluator answering whether a candidate subject matches a selector during one read epoch.
* **Transpose program**: compiled inverse routes that start from a changed selector input and enumerate a safe superset of subjects whose match truth can change.
* **Delta-routing registry**: required program state mapping a typed semantic input key to transpose entry points.
* **Impact region**: the normalized union of subjects, relation ranges, and dependent scopes emitted by transpose programs for a transaction.
* **Impact-region attribution**: the split between regions attributed to a specific rule and program (eligible for exact planning) and unattributed regions (evaluated conservatively).
* **Match-answer identity**: a compact identity for one normalized exact selector answer.

### 4.2 Style read epoch

The epoch is a logical immutability contract, **not** a copied DOM or CSSOM snapshot, and not a reified object: it is upheld by phase rather than by a header. The evaluator runs synchronously at a point where script cannot mutate style-visible inputs, an FFI callback guard rejects reentrant entry into the engine, and identity retirement is deferred (retired `StyleNodeID`s are released for reuse only at a later safe boundary), so nothing an active evaluation can name is destroyed or reused under it. The evaluator queries current tree relations, element features, and document state from their authoritative live objects; nothing walks, clones, flattens, or retains the DOM.

The final live tree is the authoritative new state. The old side is not pinned: the normalization journal's first record per key preserves the pre-transaction value (§4.4), retained derived state supplies old selector truth where resident, and tree/program transactions reconstruct their before side from those sources when planning after commit. Compact mutation records carry only the old-side facts needed to discover everything whose output may have changed.

*Database counterpart:* the isolation is phase-based rather than MVCC: one writer-free interval per flush, with before-images carried in the changelog instead of retained versions.

### 4.3 Typed deltas

Input kinds (`InputKind`, transaction.rs):

```text
TreeRelations       parent/sibling/scope/slot/host relation changes
LocalFeature        tag, ID, class, attribute, namespace fact changes
State               element and document state facts (:hover, :checked, ...)
ElementDeclaration  element-owned declaration blocks
ElementStyleInput   edge-triggered per-element style actions (not retained fact state)
Program             stylesheet program changes
CascadeTopology     layer/order topology changes
Environment         media, viewport, preference, and other environment inputs
```

Animation state is not an input kind: an element whose animation-relevant inputs changed is reached through the kinds above, and animation sampling is an overlay on published style (§9.11), not a change stream into the engine.

Typing matters: treating every change as a generic version bump moves work from mutation time into an unnecessarily broad lazy validation later.

`ElementDeclaration` covers declarations sourced from one style node rather than a stylesheet rule: the `style` attribute, HTML presentational hints, and SVG presentation attributes such as `fill` and `stroke`. The authoritative HTML/SVG mapping produces an immutable old/new declaration block with its language-defined cascade placement; StyleEngine does not reinterpret attribute syntax.

**One mutation can publish several independent typed effects.** Changing an SVG `fill` attribute may change an attribute-selector feature, a presentation declaration, and an `attr()` input. Routing sends each effect to its own consumers; handling one never stands in for the others.

### 4.4 Transaction boundaries and the normalization journal

Script performs many mutations before style is observed. Record them and normalize at the transaction boundary:

* Adding then removing the same class produces no final class delta.
* Inserting then deleting an unobserved rule produces no selector or cascade work.
* Changing a declaration three times retains only the old and final declaration block versions.
* Inserting several sheets during parsing produces one ordered program update.
* Moving a subtree twice retains only its original and final relationships.

Operations whose Web-platform semantics force synchronous observation establish a boundary. Style reads (resolved-style queries, layout-dependent APIs where required) flush all earlier input changes before returning. Normalization never combines changes across a required observation boundary.

**Journal structure.** Pending changes go into a keyed normalization journal:

* DOM/state keys: style-node identity + typed fact or relation.
* Program keys: stable sheet/rule/declaration/attachment/topology identity + edited field.

The first record for a key preserves the pre-transaction value; later records replace only the pending final value; cancellation removes the key. Raw mutation count is therefore not the default work multiplier.

*Database counterpart:* the journal is the changelog, a CDC stream, and not a write-ahead log: it exists for change propagation, not durability or recovery, so WAL disciplines (idempotent replay, checkpointing) do not transfer. Key-level last-writer-wins is log compaction, the retained pre-transaction value is the before-image, and the overflow marker below is compaction of a key range into one coarse record whose replay is a scoped rescan.

For tree and feature mutations, the first record retains the minimum old-side information required by the applicable transpose rules:

* old and new feature atoms;
* old and new parent, adjacent-sibling, tree-scope, flat-tree, slot, host, and pseudo-relation endpoints;
* operator-specific boundary identities for affected sibling or subtree ranges.

A removed style-node identity and any required relation tombstone stay valid until the transaction retires. The journal does **not** copy unchanged descendant fields and does **not** preserve a traversable old DOM. Old-side facts exist to establish a complete impact region, not to reconstruct history.

Resident derived state supplies old selector truth when available. If it was evicted, discard the affected derived fragment, evaluate the final state from the live DOM, and compare with the last committed style output.

**Journal overflow.** The journal is byte-accounted against its own memory category. Its capacity allows four fine-grained entries per connected element and is clamped between 4 MiB and 32 MiB. When growth would cross that capacity, the most numerous reconstructible fact, relation, program, or topology input kind's fine-grained entries are replaced with a typed **complete-scope marker** covering the whole document for that kind, and later records of that kind are dropped outright. Edge-triggered `ElementStyleInput` actions are never marker-coarsened: their reaction and inherited-group bits remain attached to every target and repeated actions on one target are unioned. At flush a marker widens the impact region to the document, and the plan evaluates that region exactly (§3). A pathological script can force broad discovery work but cannot discard an action whose target cannot be reconstructed from final facts. (Environment markers are exempted from disabling the retained-selector fast paths, since environment changes cannot alter selector incidence.)

### 4.5 Mutations during evaluation

During evaluation, DOM and CSSOM topology, element selector state, attached program order, and other style-visible inputs are logically immutable: the evaluator runs synchronously while the document cannot run mutating script, and the FFI boundary's callback guard rejects reentrant entry into the engine, so the phase contract is enforced at the boundary rather than by a per-read lock or version check. Reentrant or concurrently delivered changes (including parser, resource, animation, and environment changes that become ready mid-evaluation) are queued for a later transaction.

The read interval ends only after computed outputs and observer consequences commit atomically. Style node identities, program versions, and environment handles named by an active evaluation cannot be destroyed or reused before retirement; retired identities are released for reuse only at a later safe boundary.

## 5. Style node identity and tree representation

### 5.1 Identity

`StyleNodeID` is a document-local `u32`. A 32-bit identity is sufficient for one document and halves the size of many indexes and handles versus native pointers.

Pseudo-element matching projects declarations from an originating element's match set. A `PseudoElementTarget` carries the pseudo kind without allocating another style node. A first-class pseudo-tree representation may introduce its own identity model when view-transition pseudo-trees need one.

### 5.2 Column inventory

Do not blindly duplicate fields already on DOM elements. Persistent style-side storage is:

* a minimal style-data handle reachable from an element;
* required evaluator-local relation-navigation state;
* directly indexed columns for optional per-element fact metadata;
* compact dense columns for live computed-style publication handles;
* reusable temporary columnar batches for bounded-region evaluation;
* indexes containing only style-relevant facts.

```text
parent                       required, dense
first element child          required, dense
next element sibling         required, dense
previous element sibling     required, dense
depth                        required, dense
tree scope                   conditional, allocated only for multi-scope documents
assigned slot                allocated per segment
shadow host                  allocated per segment
fact metadata header         optional payload in a dense directly indexed column
prefix local-fact identity   optional, Tier 3 segmented column
computed publication handles compact dense columns, Tier 1 for observed content
```

Shadow-relation and prefix-cache columns are allocated per segment only when useful. Fact metadata and computed publication use dense directly indexed columns whose vacant rows carry no external payload; they trade empty slots below the highest used identity for constant-time access. A document with no shadow trees allocates no shadow relationship columns.

### 5.3 Required relation navigation

Transpose programs must traverse inverse selector relations without leaving the evaluator. That is why parent / first-element-child / next-element-sibling / previous-element-sibling are dense required columns rather than optional acceleration.

Concretely: for `.theme .item` and a class mutation on a container, the transpose step for the changed `A` operand is "descendants that can satisfy `B`". That set is not in the `TreeDelta`, so the evaluator must either stream the subtree from the authoritative side or intersect a resident `.item` posting with subtree membership. `StyleNodeID` is not a tree-order label, so membership costs `Hregion` relation steps per candidate. Without resident columns, every one of those steps is a boundary crossing, and the hot path degenerates into reverse cold requests or per-element FFI. Both are forbidden.

Rules:

* Dense `u32`, allocated for every connected style node, charged to **Tier 1**, counted against the mandatory per-node byte budget (§10.5).
* The DOM remains the Web-facing semantic owner, while these columns are the engine's committed evaluation projection. C++ must publish every relation mutation, and the columns must agree with the final live tree at every epoch boundary; the exact evaluator reads this projection rather than fetching the DOM in reverse.
* Not evicted under ordinary memory pressure; Tier 3 is evicted first. Their allocation is required engine state, and a missing committed relation row is an invariant violation rather than a cold-path signal.
* Ownership follows the tree: the subsystem that mutates DOM structure publishes relation-column updates as part of the same `TreeDelta` that reports the mutation, so they cannot drift within a read epoch. Mismatch is an invariant violation, not a recoverable condition.

Tree-scope navigation has the same traversal requirement once multiple scopes are supported, because scope-membership transposition works the same way. Allocate the dense tree-scope column only for documents that need it, and charge it to the conditional byte allowance (§10.5). Flat-tree operations derive from the sparse assigned-slot and shadow-host relations instead of duplicating a third parent column.

### 5.4 Temporary packing

Packing is an execution choice, not snapshot construction. Small or selective work queries the resident columns directly. A sufficiently large transaction compiles its affected regions into a preorder topology charged to Tier-4 scratch; broad matching holds an immutable shared view of the primary fact columns, while transaction-specific pre-images remain bounded overlays. Scratch is generation checked and released after the transaction. Whole-document cold evaluation never allocates a second complete flattened DOM or fact-store copy.

### 5.5 Multiple tree relations

CSS does not operate over one universal tree. The logical input distinguishes:

* DOM ancestry
* shadow-including ancestry
* flat-tree inheritance relationships
* slot assignment
* part exposure
* pseudo-element origin relationships
* view-transition capture and generated pseudo-tree relationships
* style-scope membership

**Every selector operator states which relation it consumes.** This keeps special cases out of the generic parent and descendant operators. A generic descendant walk does not pierce a shadow root or follow slot assignment.

### 5.6 Document order

Build preorder ranges in transaction scratch for the scope being evaluated; retain no per-node tree-order label. Queries depend on relative order and ancestry, never on the numeric representation of an order label, so physical relabeling is invisible to semantic dependencies.

A subtree move emits relationship deltas for its boundary only. Internal relationships remain shared unless the move changes the relevant tree relation, scope, slotting, or inherited context.

## 6. Selectors

### 6.1 Element and document state

State is represented as semantic facts, not bespoke invalidation entry points:

```text
Hovered(element)      Active(element)        Focused(element)
FocusVisible(element) Checked(element)       Disabled(element)
Valid(element)        Target(element)        PlaceholderShown(element)
Open(element)         PopoverOpen(element)
```

A transition publishes old and new fact values. Only selector operators depending on that state receive the delta. Derived states such as `:focus-within` are logical operators over tree relationships; the same machinery supports `:has(:focus)`, `:has(:checked)`, and state nested in selector-list pseudo-classes.

High-frequency states use the same semantics but may be scheduled in a low-latency lane: a hover change must not wait behind compilation of an unrelated large stylesheet when its dependencies can be evaluated independently.

**Visited-link privacy.** Visited state is a routed state fact like any other; a dedicated privacy-taint component in operator, cache, and record identities is **not yet implemented**. When visited-dependent styling is implemented to specification, tainted materializations must not expose visited information through timing diagnostics, developer APIs, or cache keys observable across privacy boundaries, and untainted and tainted results must never share a proof merely because their value bytes match.

### 6.2 Selector logical IR

Selectors compile into a compact logical IR. The IR defines semantic equality independently of how an operator is evaluated. Representative operators:

```text
LocalFeature   And           Or            Not
Parent         Ancestor      NextSibling   FollowingSibling
NthPosition    RelativeExists
ScopeStart     ScopeEnd      Host          Slotted        Part
PseudoElementProjection      StatePredicate
```

### 6.3 Bidirectional compilation and delta routing

Every selector compiles into two views of the same semantics:

```text
match program:     candidate subject -> exact match result
transpose program: changed input     -> possible affected subjects
```

The match program is exact. A transpose program may over-approximate but must **never** omit a subject whose selector truth can change. It starts at each occurrence of the changed input in the selector and traverses inverse combinator relations toward the subject. Resulting subjects are checked by the match program before any selector-truth delta is emitted.

| Match fragment | Changed operand | Transpose step toward possible subjects |
| --- | --- | --- |
| `A B` | `A` | Descendants that can satisfy `B` |
| `A B` | `B` | The changed `B` node |
| `A > B` | `A` | Element children that can satisfy `B` |
| `A > B` | `B` | The changed `B` node |
| `A + B` | `A` | The next element sibling if it can satisfy `B` |
| `A + B` | `B` | The changed `B` node |
| `A ~ B` | `A` | Following element siblings that can satisfy `B` |
| `A ~ B` | `B` | The changed `B` node |

Compound operands transpose to the node whose local fact changed. Structural pseudo-classes add typed child-sequence entry points. `RelativeExists` transposes a possible witness change through the inverse relative-selector axes to possible anchors, then transposes an anchor-truth change through the outer selector to its subjects (§7). Every step names its DOM, shadow-including, flat-tree, slot, part, pseudo, or scope relation explicitly.

*Database counterpart:* the transpose program is the standing query's delta rule, its derivative with respect to one input: given that input's change, it enumerates the output rows that can change. Match-checking every enumerated subject is delta verification, and it is what makes safe over-approximation legal.

**Delta-routing registry.** Required program state mapping each semantic input key to the transpose entry points that mention it:

```text
(input kind, StyleAtomID or relation kind) -> transpose entry-point slice (stable order)
```

It covers tag, ID, class, attribute, namespace, state, tree relation, structural, scope/topology, and activation inputs. Selectors with no selective local atom go into a **typed** always-consulted slice for the input kinds that can affect them, not one global universal bucket. A child-list mutation must not consult programs that depend only on an environment predicate.

Construction and bytes are charged to program compilation and Tier 2. Each input key indexes one insertion-ordered route slice, and all slices are packed into a flat `RouteID` vector; route headers and their variable-length paths live in parallel columnar arenas. The structure is proportional to selector-input incidence, not element count.

The registry is required, not optional: scanning every selector header for every ordinary mutation changes the hot-path asymptotics. Feature postings (Tier 3) may accelerate subject enumeration, but evicting them never changes which transpose entry points run. Program replacement keeps old registry entries alive through old-result removal and installs the new program and registry atomically in the next epoch.

### 6.4 Impact regions

For a normalized transaction `T` and attached program version `P`:

```text
Impact(T, P) = normalize(union(transpose_entry(delta) for delta in T))
```

The result can contain individual style nodes, sibling intervals, subtrees, tree scopes, and dependent flat-tree or shadow scopes. It is a transaction-local execution plan, never a retained element-by-selector relation. Overlapping regions coalesce without changing their named tree relation.

Every selector operator and every non-selector dependency that can request exact scope evaluation has a transpose rule satisfying:

> Between the last committed state and the final live state in the read epoch, every style node whose semantic selector, cascade, computed value, or observer output can differ is contained in `Impact(T, P)` or in an explicitly chained downstream region produced from it.

Coverage is fail-closed: any gap widens the region; unknown scope is never empty scope. Explicit epoch-bound impact certificates are not retained today; the coverage obligation is the same, enforced by widening and verification.

Consequences to implement:

* Inheritance transposes through the **flat** tree.
* Slot reassignment emits both participating light-tree and shadow-tree regions.
* A scope or stylesheet-topology change names every scope whose attached program or cascade order can differ.
* When an operator cannot construct a narrower proven region, it widens to that operator's complete tree scope, and ultimately to the document.

Regions carry attribution: a region attributed to a specific rule and selector program is eligible for exact per-entry planning, while an unattributed region is evaluated conservatively. Any gap (a missing posting, an incomplete retained answer, an unroutable input) surfaces as a typed `Missing`/`Incomplete` lookup whose only legal response is widening, ultimately to the document. A verification gate can additionally assert that a scoped plan publishes nothing it cannot attribute.

The exact batch plan consumes only a complete impact region.

### 6.5 Execution kernels

One selector semantics, three cooperating execution kernels. The boundaries are physical, so operators can move between them later without changing query meaning.

**Local-compound kernel**: the dominant hot path. Evaluates tag names, IDs, classes, attributes, namespaces, element states, and Boolean compound structure. Uses fixed-width interned atom IDs at the C++/Rust boundary, a rightmost-feature dispatch key, and an arena of fixed-width enum nodes with shared operand and literal tables. Candidate tests are ordered cheap/high-rejection first and short-circuit on failure. **A compound with no relational operator allocates no witness.** (Non-relational chains do participate in the retained prefix automaton below when admitted; that state is Tier-3 and evictable, never required.)

Selector feature keys have two sparse directions with independently bounded sizes:

```text
feature atom -> candidate StyleNodeID posting     Tier 3, optional
feature atom -> transpose entry points            Tier 2, required, program-proportional
```

The first avoids scanning DOM facts and may be evicted. The second lets a class, attribute, tag, ID, or state mutation activate only routes mentioning the changed feature. If the posting is evicted, the routed transpose program reads authoritative DOM facts or uses an exact scope batch. No element-by-selector relation is ever required.

**Directional-combinator kernel**: child, descendant, adjacent-sibling, and subsequent-sibling relationships consume the same local compound programs, under operation-specific schedules:

* *Cold initial style*: stream style nodes, gather candidate program offsets from each node's local feature atoms, match relationships right-to-left with direct tree walks.
* *Dynamic selector insertion*: reverse direction: start from the new program's safest rightmost-feature posting and check only those candidates when selective.
* *Mutation to an attached program*: start from changed local truths. Descendant and child effects run as a top-down prefix-state pass over the exact affected scope, warm-started from the retained prefix states of the previous transaction where those survived.
* *Sibling effects*: adjacent-sibling inspects the immediately participating siblings; subsequent-sibling and structural effects use the same automaton's child-sequence machinery and stop at unchanged outputs.

These are schedules over the same logical IR.

**The prefix automaton.** Selector-prefix truths are retained across transactions as a Tier-3 cache: interned prefix states (the "context tokens" entering each node), per-element entering states, per-element terminal match sets, and per-element positional truth bits. Registration is budgeted: an automaton carries at most 32 structural-test truth bits, and a chain that would overflow is refused whole and stays with its exact routes, so refusal degrades to exact matching, never to a wrong answer. The cache's bytes cycle through scratch during a flush and are retained at the end of a patched flush; it is prioritized above the retained answers it maintains (§10.3), and discarding it is always legal; the next flush rebuilds from exact evaluation.

**Relational-query kernel**: `RelativeExists` reuses local compounds and directional traversal to answer an existential question per anchor. Witnesses belong only to this kernel. A document with no relational selector allocates no relational queues, per-node relational fields, or relational cache tables (the witness table is empty and unallocated, and relational routing early-returns on an empty route set).

Selector lists and Boolean functional pseudo-classes compile to branch nodes in the same IR. Specificity, dispatch analysis, and match metadata stay static program data rather than being reconstructed per candidate or scope-dispatch build.

### 6.6 Local feature indexes

Selector posting keys are semantic feature atoms, not strings: tag name, ID, class, attribute name, directionality, part name, and custom state. Namespace, ordinary state, root, heading, and other fixed facts are read from the authoritative row when dispatch reaches a candidate. Every attribute operator drives from the attribute-name posting; exact equality can reject on its value atom before matching, while substring and token operators run their exact text test in the compound evaluator.

Representation: **chunked sorted postings**, sorted by `StyleNodeID`. While the Tier-3 category is admitted, every published selector feature is maintained eagerly, whether or not the current program mentions it. Forgetting an element removes all of its posting memberships; memory pressure evicts the complete posting category, and later selective demand may rebuild its missing keys from authoritative facts.

One key stops retaining a posting when its membership grows beyond the greater of 4096 elements and one quarter of the live elements. The key then reads as missing acceleration, so every consumer takes its exact fallback instead of retaining a near-document-sized secondary index.

*Database counterpart:* secondary inverted indexes, and non-clustered ones: `StyleNodeID` is not a tree-order label, so proving a posting candidate lies in a region is the row lookup a non-clustered index pays when the predicate lives on the clustering dimension.

Because `StyleNodeID` is not a tree-order label, a subtree impact region is *not* a numeric posting interval. For a selective plan, enumerate posting candidates and prove region membership through the named tree relation, charging ancestor/sibling steps explicitly. Those steps walk the required relation columns, so a selective plan costs `Hregion` in-evaluator pointer hops rather than boundary crossings. For a broad region, stream the region in preorder and test local features from authoritative facts instead of building a full posting-to-preorder join.

Transaction-local preorder arrays make repeated relation checks constant time only after their construction cost and `StyleNodeID`-to-preorder mapping bytes are charged to discovery and Tier 4.

### 6.7 Selective-plan cutoff

The plan choice is made once, before enumeration, from candidate cardinality and region size: **the exact batch plan is chosen when the smallest safe candidate source covers more than one quarter of the region** (`SELECTIVE_SHARE_DIVISOR = 4`). A candidate source of 64 or fewer skips the cost model entirely and goes straight to direct membership checks.

The cutoff uses **exact cardinality** of normalized impact ranges. If exact cardinality is unavailable, the batch plan is chosen rather than a smaller scope guessed. Missing Tier-3 acceleration can make the exact batch plan cheaper, but memory accounting does not change the plan after enumeration begins.

### 6.8 Query outputs

```text
SelectorMatches(SelectorProgramID, StyleNodeID, MatchMetadata)
```

`MatchMetadata` contains only what later semantics need (selector-list entry, specificity, scope proximity, pseudo-element target) and is factorized when fields are static for the selector.

Rule matching is a join:

```text
SelectorMatches
AND RuleUsesSelector
AND RuleAttachedToScope
AND ActivationPredicateIsTrue
    -> ActiveRuleMatches
```

This separation lets declaration edits, condition changes, and attachment changes reuse selector truth.

Concrete matches are enumerated into a transaction-local sorted `StyleNodeID` vector and discarded after cascade consumption.

### 6.9 Specificity

Specificity is program metadata with explicit aggregation rules. Each top-level complex selector in a rule's selector list carries its own static specificity. If multiple entries match the same style node, `MatchMetadata` retains the greatest matching entry specificity together with any independently required scope and pseudo metadata. Short-circuiting may skip work only when it cannot hide a later matching entry with a greater cascade contribution.

`:is()` and `:not()` contribute the maximum specificity of their argument list as a static property of the program, regardless of which branch establishes the Boolean result. `:where()` contributes zero. `:has()` uses the corresponding static argument-list rule even though witness discovery is dynamic. Structural and nesting operators use their specification-specific contribution; none of them inherit a generic runtime "specificity of whichever branch matched" rule.

The cold evaluator and the incremental evaluator consume the same static specificity table.

### 6.10 Ephemeral selector consumers

Selectors passed to `querySelector()`, `querySelectorAll()`, `matches()`, and `closest()` are **match-only query programs**. They compile with the same selector semantics and evaluate with a bare match evaluator over the engine's fact store, but they are not attached style programs: no transpose routes, no routing registry entries, no rule identities, no witness reads or writes, no cascade state. Scope roots, shadow boundaries, relative-selector anchoring, and syntax failure are explicit compiler inputs. Result ownership belongs to the DOM API, not StyleEngine.

Two mechanisms make this correct and fast against a live document:

**Settling.** A connected query must see the current DOM generation, which may have pending staged input. Preparing a query commits the pending fact rows into the resident store while stashing their pre-images; the next flush (or normalize) restores the pre-images first, so the style transaction still computes its before/after diff as if the query had never intervened. A query observes current facts; invalidation keeps its native orientation.

**Isolated engines for disconnected roots.** A query rooted in a disconnected tree runs against a small throwaway engine populated from that subtree alone. These engines are cached per root (an LRU keyed by root and mutation versions) so repeated queries over the same detached tree reuse one population, and a compiled query program is cached for the lifetime of its `SelectorQuery` object rather than recompiled per call.

### 6.11 Push/pull division

Push exact deltas only when the transaction or a resident view (witness, winner column) directly names the consumer. Everything else is validated by exact pull evaluation of the affected scope.

When explicit subscriber fanout would cross a hard edge threshold, do not allocate the edge set; pull from the exact affected scope instead.

## 7. Relational selectors and `:has()`

`RelativeExists` is an existential query relating an anchor to a relative-selector witness. Its semantic output is Boolean even when many witnesses exist. **Only transitions between zero and nonzero witnesses can affect selector truth.**

### 7.1 Compilation

A relative selector compiles into operators over: anchor scope; child or descendant traversal; adjacent or following sibling traversal; ordered sibling-descendant combinations; local compounds and selector-list logic. Every traversal operator names its tree relation and scope boundary.

### 7.2 Simple-query grammar

A `:has()` query is **simple** when all of:

* the relative selector contains exactly one axis (a selector-list argument compiles each branch into its own relative query, each classified independently);
* that axis is one of: implicit descendant, child, adjacent sibling, or following sibling;
* that axis is followed by exactly one local compound;
* the compound contains at least one positive indexable driving feature;
* the query contains no structural pseudo-class, nested relational operation, internal combinator, pseudo-element projection, or shadow/scope-crossing construct.

Simple: `:has(.error)`, `:has(> .item:hover)`, `:has(+ .selected)`, `:has(~ .selected)`. Complex: `:has(.group > .item.error)`, `:has(:nth-child(2))`. A list such as `:has(.a, .b)` is two simple queries, each with its own axis and driving feature.

Complex ≠ unsupported. Complex queries use the exact direct relative-selector evaluator and retain no witness.

Selectors Level 4 forbids nesting `:has()` inside `:has()`; reject that syntax at compile time rather than treating it as an incremental-coverage case.

### 7.3 Witness state

Retain **at most one witness per simple positive observed query**. Consequences:

* Adding another witness does not change state and emits nothing.
* Removing a non-retained witness requires no repair.
* Removing the retained witness triggers a replacement search.

A retained witness is a proof cache, never semantic state; evicting it is always legal.

*Database counterpart:* `:has()` truth is an EXISTS aggregate, so only zero-to-nonzero transitions are deltas. The retained witness is a one-row partial materialization of the subquery; losing it is a view miss repaired by an upquery, never a semantic event.

**Bounds.** A retained witness is re-verified on read (liveness, axis membership, and a match re-check), with a fixed step cap on the following-sibling back-walk; any verification failure clears the entry and routes conservatively. The witness table itself is capped (currently 16,384 entries). Limit-crossing growth remains available for its current quota period, and the table is dropped whole at the following boundary. Replacement after a lost witness is the ordinary forward walk of the query's axis; the routing-side cutoff (§6.7) bounds how witness-side discovery drives anchors. A Boolean result is never approximated.

### 7.4 Negative results

A negative `:has()` result must not allocate dependency edges to every element in the searched region, and it allocates nothing at all: a query that finds no witness stores no state. Absence is recomputed on demand, and the false-to-true transition is caught from the witness side: a mutation that could create a witness routes through the relative-selector transpose entry points to its possible anchors (§7.5), so there is no cached negative to invalidate and no stale negative can exist.

### 7.5 Witness creation (false-to-true)

When a local mutation can create a witness, routing activates the relative-selector transpose entry point. For a simple query, possible-anchor discovery is bounded by the single inverse axis:

| Relative axis from anchor | Possible anchors for a newly matching witness |
| --- | --- |
| Child | Its element parent |
| Descendant | Its element ancestors up to the query's scope boundary |
| Adjacent sibling | Its immediately preceding element sibling |
| Following sibling | Its preceding element siblings in the same child sequence |

Each possible anchor is checked exactly, including anchor compound and scope semantics. A false-to-true Boolean transition then enters the outer selector's transpose program, so `.card:has(.error) .button` reaches only possible `.button` subjects in the affected anchor context. A new additional witness for an already-true anchor emits no Boolean delta and never runs outer propagation.

Complex relative selectors use the same principle with multiple inverse axes; their transpose program may widen to a sibling interval, ancestor path, subtree, or complete tree scope, but never omits a possible anchor. Discovery work is charged even when every anchor check rejects.

### 7.6 Non-subject `:has()`

For `.card:has(.error) .button`, the relative query produces a Boolean anchor result that updates the anchor's outgoing top-down selector context. Descendant `.button` matches change only where the context transition changes their rule output. Adding a second `.error` while a witness already exists emits no anchor-truth delta and therefore no descendant selector work.

## 8. Stylesheet program

### 8.1 CSSOM versus semantic program

* CSSOM objects preserve API identity, ownership, indices, and serialization behavior.
* The semantic program is an immutable, content-keyable representation used for computation.

Editing a CSSOM object creates a new semantic version without replacing the wrapper object.

### 8.2 Stable identities

```text
SheetID              stable semantic sheet identity
RuleID               stable semantic rule identity
SelectorProgramID    immutable compiled-selector identity (content-interned:
                     equal compiled selectors share one ID)
DeclarationBlockID   declaration-block version (a fresh identity per edit)
CascadeLayerID       layer identity
ProgramVersion       one document-global program version, bumped per change
```

CSSOM wrapper objects hold their semantic identities on the C++ side (a stylesheet knows its `SheetID`, a rule its `RuleID`). A declaration edit preserves `RuleID` and `SelectorProgramID` while installing a new `DeclarationBlockID`; a selector edit preserves the rule identity but changes the compiled selector program and match relation. Rule contents are updated in place under the rule's single version slot; there is no retained history of prior rule versions, and nothing reads one: evaluation always runs against the current program, with old truth supplied by the journal's before-images and retained derived state (§4.2). Activation state (media, supports, container, sheet disabled) is tracked as current condition booleans per rule and sheet, updated by typed inputs.

### 8.3 Rule version contents

```text
RuleID
SelectorProgramID
DeclarationBlockID
conditions-hold flag
CascadeOrigin
CascadeLayerID
StyleScopeID
StyleSheetOrderToken
NestedRuleOrderToken
```

Selector list entries may carry separate static specificity and matching output identities while sharing a declaration block. Nested group rules form a persistent tree: editing one descendant creates new nodes only along the path to it; unchanged selectors, declarations, and conditions keep their identities.

### 8.4 Attachment

Attachment connects a compiled program to one or more style scopes and is distinct from parsing and compilation. It covers parser-created `style`/`link` elements, dynamically inserted stylesheet nodes, load completion, constructed stylesheets, `adoptedStyleSheets` changes, sheets shared by multiple shadow roots, enabling/disabling, and UA/user/author origins.

One compiled program attaches to multiple scopes without duplicating its logical selector or declaration representation. Scope-specific indexes and materializations stay separate where tree membership requires it.

Selector programs contain process-global `StyleAtomID` values and immutable equal programs are shared across document engines on the StyleEngine thread. Each document retains references to the global atoms it uses; the final document releases the underlying interned-string identity and makes the numeric atom reusable. Entry and rule identities, routing registries, attachments, order tokens, and all result materializations remain document-local, so no document generation, tree scope, or result state crosses the sharing boundary.

The memory lease for a shared selector payload belongs to the process-global selector-program table. Document controllers report only their local program identities and indexes; the shared table reports each immutable payload once and keeps that charge until the payload's final document reference drops.

### 8.5 Source order

Source order composes from per-axis components:

```text
origin              a fixed enum ladder (with importance reversal)
tree context        encapsulation-context depth
layer order         per-scope layer ranks, rebuilt from the ordered layer list
stylesheet order    order-maintenance tokens, one axis per tree scope
nested rule order   order-maintenance tokens, one axis per sheet
```

The two order-maintenance axes use `u64` gap labels: insertion writes one midpoint label, and exhausting a gap relabels the axis. Numeric relabeling changes no semantic version, because comparisons depend on relative order only.

Moving a sheet updates its sheet-order token; rules refer to that token indirectly, so their program nodes are not rewritten. Moving a nested rule updates the smallest enclosing order token.

Reordering affects selector truth only if scoping or attachment changes. Otherwise it is purely a cascade priority delta.

*Database counterpart:* gap-labeled ordering keys (fractional indexing). Comparisons read relative order only, so relabeling is a physical operation with no semantic version change.

### 8.6 CSSOM mutation surface

Every operation maps to a typed program delta. None produces generic document invalidation.

| Mutation | Primary semantic delta |
| --- | --- |
| Attach, detach, or move a `style`/`link` element | Attach, detach, or reorder a sheet program |
| Change text inside a `style` element | Structurally replace the sheet program |
| Finish loading a linked or imported sheet | Attach the compiled program at its semantic position |
| Change `href` on a stylesheet link | Detach the old resource; later attach the new program |
| Change `media` or `disabled` | Update an activation predicate |
| Reorder `adoptedStyleSheets` | Update sheet-order tokens |
| Add or remove an adopted sheet | Attach or detach an existing compiled program |
| `insertRule()` (incl. grouping rules) | Add a rule program at a new order token |
| `deleteRule()` (incl. grouping rules) | Remove a rule program |
| Change `selectorText` | Replace the selector-program edge of one rule |
| `setProperty()`, `removeProperty()`, declaration `cssText` | Replace one declaration block |
| Change a condition rule's text | Replace an activation predicate |
| `replace()` / `replaceSync()` | Structurally replace a sheet program |
| Edit keyframes through CSSOM | Update the named animation program and its consumers |
| Register a custom property | Update the registration input for that property name |
| Change the `style` attribute | Replace one element declaration block |
| Change an HTML presentational-hint attribute | Replace the mapped element declaration block, plus any independent selector/attribute inputs |
| Change an SVG presentation attribute | Replace the mapped element declaration block, plus any independent selector/attribute inputs |

CSSOM collections and wrapper objects reflect API-required mutations synchronously; semantic style work may remain queued until the next required observation. Stable object identities plus immutable program versions let those coexist.

### 8.7 Insertion

1. Parse or receive the immutable stylesheet program.
2. Compile selector, declaration, condition, layer, and scope nodes.
3. Assign a stylesheet order token between neighbors.
4. Attach the program to the applicable style scope.
5. Evaluate only selector-program nodes whose results are not already available in that scope.
6. Gate selector matches through the sheet's activation predicates.
7. Add exact matched-rule deltas to the cascade.

No existing selector truth is invalidated because a sheet was inserted. If the new declarations lose the cascade everywhere, computed styles are unchanged even though the sheet matched many elements.

**Batch planning.** A program transaction is planned as a batch, not a loop over inserted rules. Compile all new selector fragments, then choose selective or exact-batch evaluation once:

* A small number of selective rules drives evaluation from sparse feature postings.
* A large sheet or burst of sheets builds temporary rightmost-feature buckets and runs one columnar pass over the relevant live style nodes, evaluating all new fragments together.

Temporary buckets and candidate arrays are Tier-4 scratch, discarded afterward.

If no style was observed before parser or script mutations completed, compute only the final program and style state. If an observer required an intermediate state, apply later sheets incrementally to that committed state.

**Loading and `@import`.** Network completion order does not determine cascade order. A linked or imported sheet receives its semantic position from document and rule order before or while it loads; when contents arrive, attachment uses that existing token. An `@import` rule is an import program node carrying layer, supports, media, and source-order inputs; load completion attaches the imported program beneath it without rebuilding or renumbering the parent sheet. Several sheets completing before the next observation join one normalized transaction while keeping their independently established positions. If script forces style between completions, each observed completion commits as its own transaction.

Parsing and immutable program compilation may run ahead of attachment, off the main style-evaluation path, subject to CSSOM visibility and loading-order requirements.

### 8.8 Removal

Removing a sheet detaches its rules from the scope. Determine affected matched rules **without** retaining a complete inverse match relation per rule:

* Reject a removed rule immediately when the complete winner-group column proves it won no declaration.
* Otherwise route the detached selector programs from their indexed subject features and evaluate them exactly before retiring them.

Bulk removal is grouped: detaching a sheet or rule subtree evaluates its program-owned selector set as one change and removes its declarations from cascade structures in one operation. Cold content validates against the new program version lazily. Removing a sheet with no winning declarations emits no computed-property deltas.

### 8.9 Rule-level edits

**`insertRule()`** adds one immutable rule version at an order token between neighbors and compiles and evaluates only the new selector and condition fragments. Existing rules are neither renumbered nor rematched.

**`deleteRule()`** removes the rule from its matching elements and repairs cascade winners for the properties it declared. A cold deleted rule is re-evaluated to enumerate its affected elements. Deleting a group rule detaches the persistent subtree rooted at that group; shared descendants stay alive if referenced elsewhere.

**`selectorText`** replacement keeps the `RuleID` and order token, compiles the new selector program, and records one typed input carrying the old and new program identities. Both programs are then evaluated through the retained-answer patch machinery: old-program matches produce removals, new-program matches produce additions, and the cascade-winner comparison stops propagation where the outcome is unchanged, except that an element whose recorded winner came from the edited rule always takes the full cascade reduction, since the fast comparison cannot prove its winner survived. Answer identities are keyed by program, so an element matching both the old and new selector still transits two answer identities; the stop happens at the winner layer, not the match layer.

**Declaration change** does not invalidate selector truth. A resident exact match relation feeds the declaration-block delta directly into the cascade; if absent, exactly reconstruct only the edited rule's matches and charge that selector work to `reconstruct`. Never rematch unrelated rules. Only properties present in the old or new declaration block are winner-change candidates.

Inline style uses the same declaration-block mechanism, with a match relation containing exactly its originating style node. HTML presentational hints and SVG presentation attributes use the same element-owned relation with distinct declaration kinds and their spec-defined cascade placement. They do not masquerade as inline style and do not enter the selector program merely because their source syntax is an attribute.

### 8.10 `replace()` / `replaceSync()`

Replacement reuses rule identities **positionally**: the sheet's style rules are snapshotted, and each incoming style rule reclaims the next prior `RuleID` in order, carrying its old declaration block forward and recompiling its selector. Compiled selector programs are content-interned, so an unchanged selector keeps its `SelectorProgramID` and its match relation for free. A sheet containing any non-style rule (or a rule-kind mismatch mid-list) falls back to whole-sheet detach-and-reattach, which really does swap the rule list; that path still compares selector truths and winners before emitting downstream changes, so correctness never depends on the reuse heuristic finding a correspondence. A content-based structural diff that survives insertions at the top of the sheet is a future idea.

### 8.11 Layers and conditional rules

Cascade layers form their own versioned topology. Layer statements, nested layers, and sheet attachment can change relative layer priority; rules reference layer identities rather than copied numeric ranks. A layer topology change updates property-specific cascade comparisons for participating matched declarations and does **not** invalidate selector truth. Important declarations use the reversed layer ordering through the cascade-priority operator.

Program deltas are typed for: `@media` and sheet media attributes, `@supports`, `@container`, `@scope`, `@starting-style`, `@property`, `@font-face`, `@keyframes`, `@counter-style`, `@font-feature-values`, namespace and layer statements. Each publishes deltas to its semantic consumers: changing a keyframes rule begins at animations referencing its name, not at every element in the scope.

## 9. Cascade and computed values

### 9.1 Match-answer identities

Comparable selector answers are immutable and interned under a compact integer identity. The identity names the normalized `RuleMatch` vector independently of the style node it was evaluated for, so equivalent answers can reuse cascade expansion and prefix-cache results without copying or renormalizing the vector.

These identities are document-local acceleration, not a retained per-element match column. A style ask publishes only the identity of the answer it just produced, and answer caches may carry that identity to their consumers. Program changes reach their candidates through selector dispatch and exact evaluation of the old or new program as appropriate. Do not retain a complete `RuleID -> elements` relation; inverse match sets are optional query-level materializations only.

Scope dispatch versions and encapsulation depths are resolved at matching-context
preparation. Matching and answer materialization borrow the prepared dispatch;
they do not rebuild a program or update a last-scope memo. The prepared scope set
includes host, slotted and part projections. Ancestor requirements are prepared
per topology before a batch, using dense rows for the document topology and
selected primary rows for other topologies. An adaptive retry prepares its
extended facts and primary ancestor summary before matching. Unbounded ancestry
rejects nothing, and secondary scope projections do not use primary ancestry.

Prefix transition rows and memoization belong to each caller's evaluation
context. Rows are prepared against the fact generation and count before
matching. Adaptive retries own separate row domains; a failed attempt drops
its effects. The ordinary child ask reads private rows and completed node
transitions before retained columns, computing missing selector ancestors
and preceding siblings into its own scratch. Completed ordinary transitions
return ordered effects installed at context completion, before retention
compacts IDs. Local-fact cohort IDs are prepared per fact batch before matching;
only preparation mutates their interner. Representative rows belong to the
preparation's fact domain, while contexts keep independent ID columns across
nested adaptive attempts. Appending rows preserves the existing cohorts.
Composite convergence preserves conservative unique cohorts for changed facts.
Local-fact column publication joins the transition effects. Persistent
state, match-set, truth and result interning remains eager during matching;
its overlay and remapping wait until prefix discovery moves into the
evaluation walk. Sparse convergence uses the same private scratch
and ordered effects, keeping retained old-comparison inputs distinct from
new parent and preceding-sibling outputs until convergence completes.

Same-flush prefix answer probes normalize non-prefix matches into a borrowed
slice and hash. Cache entries own that key content and confirm exact equality
on lookup, including hash collisions; keys no longer allocate or retain a
match-answer identity. Ordinary retained-answer reads borrow catalog slices,
reacquiring them after mutable operations rather than cloning their Rc owners.
Prefix contributions and answer-result identities remain eagerly interned for
a later completed-payload and canonical installation step.
Retained answer and cascade-input replacements, exact-cache discoveries,
publication and observation are owned effects. Node and content lookups consult
pending outputs before retained columns, including across an incomplete host
batch. Before-change comparisons continue reading the unchanged old columns.

The catalog's pending reference category pins numeric identities and charges
payloads to BatchScratch. Completed transaction and host-batch boundaries transfer
those references and install columns before retention, prefix release or catalog
sweeping. Input handoff finishes the previous host context before discarding
its publication; transaction entry does the same before committing new inputs.
Those boundaries also finish prefix effects and drop private transition IDs
before their arenas can be reset. Explicit abandonment releases pending
references; incomplete batches keep them until resumption or teardown.
Host consumers read pending publications without requiring their vector to have
been sorted. Deferred-pseudo comparisons borrow the before-change answer from
the old column, which owns it until installation, and the new answer from effects.

Match-program relation answers, sibling cursors, sibling sequences, type ranks
and positional answers live in caller-owned `MatchScratch`. An evaluator borrows
it exclusively; recursive matching cannot mutate another evaluator's scratch.
Sibling geometry is computed only on demand, and readers borrow sequence slices.
Current and old-fact evaluations share current geometry but keep distinct type
ranks and answers. Old-tree evaluation uses separate geometry. Prefix convergence
holds separate old/current scratch while both evaluators are live.

Completed current-fact relational queries return owned witness retain/clear
effects. Matching never reads the retained witness table. Transaction, document
matching and host-batch boundaries install those effects in evaluation order;
a following transaction installs pending host observations before routing reads
the table. Routing still revalidates retained witnesses against current facts;
stale-witness clears join the same ordered effects.
Ephemeral queries and before-side evaluators produce no witness effects.

### 9.2 Cascade priority

A declaration's priority is a comparison program over stable identities:

```text
origin and importance
encapsulation context
element-declaration kind and language-defined placement
layer topology
specificity
scope proximity
source order
```

The element-declaration component distinguishes inline style from HTML and SVG presentational hints; the mapping layer supplies correct origin, specificity behavior, and position relative to stylesheet rules. Components that change globally (layer or stylesheet order) are referenced indirectly, so updating a topology token does not rewrite every declaration.

### 9.3 Provenance and candidate access

For each observed style node, retained cascade state identifies each property's winning source at rule granularity:

```text
color       <- rule 955
display     <- rule 17
background  <- element declaration (inline style)
--theme     <- exact cascade (reconstructed cold)
```

Representation: an **interned winner group** per observed style node (Tier 3, may be absent). Sparse and shared, never one heap object per property per element. Only properties declared by an added, removed, modified, or reprioritized rule are considered.

Candidate access is exact in each mutation direction:

* **Rule insertion** gets concrete matches from evaluation of the inserted selector; each matched node compares only that rule's declarations against its existing winners.
* **Rule deletion** stops immediately when complete winner state proves the rule won nothing; otherwise it evaluates the old selector before retiring its program. A deleted *winner* is repaired by exact cold cascade reconstruction.
* **Declaration editing** preserves the rule's selector relation; reconstruct it exactly if not resident, then apply the declaration delta.
* **Reprioritization** uses complete winner state to reject unaffected rules, then runs an exact affected-scope batch pass. A real broad order change is allowed to cost broad work.

*Database counterpart:* a winner is a top-1 aggregate per (style node, property) under the priority order, and top-1 is the classic non-invertible aggregate: insertions and losing deletions repair in place by comparison, but deleting the current winner cannot be repaired from the aggregate alone. That is exactly why a deleted winner falls back to exact cold cascade reconstruction, the same rescan MIN/MAX maintenance performs under deletion.

The exact cold cascade gathers all active declarations for one node and property and runs the same priority comparison program as the incremental path, depending on no winner caches, tournament nodes, or retained state. It is both the eviction path and the reference implementation.

Winner state, group, provenance and continuation identities remain eagerly
interned during resolution. Canonical identity installation is deferred to a
later step. Per-node winner rows, program versions, flush
stamps, inventory-current flags and rule-to-node postings are owned effects.
The transaction, matching traversal or host batch owns these alongside its
answer effects. A pending-first view supplies same-loop stopping checks,
cohort donors and host computation reads without installing a donor's rows.
Before-change comparisons continue reading unchanged retained state.

Each queued write owns a pending state reference. Installation consumes writes
in production order and transfers accepted references into retained rows;
refused or abandoned writes release their references explicitly. Donor reuse
captures completed state handles and pseudo inventory facts, so later changes
to the producer cannot alter its consumer's output. Input handoff installs the
old context before a new transaction can invalidate its identities. Winner
installation precedes the same boundary admission decision as answer effects.

### 9.4 Winner stopping keys

Propagation stops at the earliest layer that can prove the output unchanged. The stack, from cheapest to most expensive:

1. **Answer-identity stop.** A patched retained answer whose identity is unchanged emits nothing; a cohort memo lets one proven transition answer for every node sharing the same delta shape.
2. **Cascade-input stop.** The compacted match answer (non-winning author rules dropped) is compared; equal inputs cannot change winners.
3. **Winner-key stop.** Per property-group winners carry a `SpecifiedWinnerKey`:

```text
SpecifiedWinnerKey
|-- canonical specified-value identity
|-- CSS-wide cascade operator and its continuation point
`-- animation and transition relevance
```

Declaration identity and source position are excluded, which is what makes theme swaps that produce identical values free downstream. Transition and animation relevance *are* semantic and participate. The winner comparison produces a semantic delta; an empty delta with a valid previous computation stops before value resolution.
4. **Reuse gating.** The winner-key stop may reuse the previous computed result only when the previous computation's recorded inputs still hold: its input record pins the values and parent groups it read, records which inherited custom-property bindings the computation actually consulted, and carries a conservative `read_beyond_the_record` bit covering every dynamic dependency not individually tracked (container, attributes, sibling position). The bit defaults to "incomplete", so an untracked dependency forces resolution rather than a wrong reuse. Live transitions and changed custom-property environments likewise force resolution.
5. **Dependency masks after resolution.** When winners did change, the semantic delta narrows downstream work to the affected computed groups and property words rather than rebuilding the whole style; final record-identity equality then lets unchanged payloads be shared.

Static checks of a declaration's original spelling are prepared with rule and
per-element declaration inputs. Computability after custom-property substitution
is a dynamic evaluation result, memoized in caller-owned scratch by node identity,
winner generation/state, current environment identity and registration generation.
Rust record computation shares that scratch for the transaction; a host
publication call owns a local context. Neither context retains answers into another update.
Synthetic pseudo masks are stored when match answers are interned.

### 9.5 Cascade-wide keywords

`inherit`, `initial`, `unset`, `revert`, and `revert-layer` are cascade **operators**, not eagerly flattened values, so their dependencies on parent style, origin, and layer topology stay explicit.

A winner query is parameterized by its cascade ceiling and exclusions: `revert` resumes below the current origin; `revert-layer` resumes below the current layer in the applicable origin and importance ordering (important declarations use reversed layer order). A topology edit changes the referenced layer-order identity. Winners carrying a revert continuation are excluded from the retained fast paths outright and always evaluate cold, which subsumes repair.

Do not implement these as one-time value substitution; that hides the dependencies needed for incremental repair.

### 9.6 Computed-value dependency graph

The stage consumes changed cascade winners plus non-cascade semantic inputs and evaluates only the affected dependency closure. Static dependency metadata is generated with the property implementation; dynamic edges are discovered while evaluating values.

```text
font-size           -> em-based values
root font-size      -> rem-based values
writing-mode/direction -> logical property mapping
color               -> currentColor consumers
custom property --x -> var(--x) consumers
attribute x         -> attr(x) consumers
container metrics   -> container units and queries
viewport metrics    -> viewport units
font environment    -> font-relative metrics
anchor geometry     -> anchor functions
tree position       -> tree-counting functions
```

Root font inputs are a separate dependency from immediate-parent inheritance.
Before the consumer pass, a reached document element prepares its font and used
line height when the engine can compute them. A missing font is synchronously
refilled at this boundary before publishing the proven inputs. The completed
phases stay in owned scratch and resume at the root's normal record
position; neither remaining properties nor pseudos are required for font readiness.
The root's own remaining phase retains its existing pre-computation document
context. Root pseudos resolve against their originating record's current metrics.
An incremental root otherwise reads current retained document inputs.

A host-materialized root keeps its existing host route without adding descendant
deferral. C++'s root-metric comparison still routes recomputation across fixed-font
intermediaries. Font work already performed is preserved across preparation and
refill; consumers never use an engine-computed root's unproven font inputs.
`rootFontInputsUnprovenFallbacks` counts these unproven reached roots and
`rootFontInputsPrepared` counts successful preparations; neither selects behavior.
Prepared root changes independently invalidate winner/partial-table and pseudo
reuse. Cold, donor, cohort and shared-context keys carry all five actual root
metrics and their viewport-dependence bit. C++ input records carry the same inputs
in their element portion, so a root change cannot be mistaken for only a change
of declarations. Root publication does not clear cohort caches.

Font resolution reads a generation-scoped prepared table. A missing synchronous
result suspends the current canonical element and ends the serial computation
pass. Its owned font-phase table, completed originating record and completed
pseudo prefix survive boundary resolution; the same element resumes before the
unvisited suffix, even with an empty journal. Parent and document contexts are
rebuilt from the unchanged inputs, with no borrowed stack pointers retained.
No full drive or font longhand is restarted. This boundary also applies to the
host's retry after an ancestor has been installed.

A null synchronous resolution is a completed unsupported result, eligible for
existing host handling rather than endless refill. An unloaded web face is
instead retained in the returned font cascade with current fallback metrics.
Metric probes do not start its load; rendering still initiates loading and the
font-display timeline. Load completion changes the font environment and prepares
a new table. Font matching and platform ownership remain in the host.

Diagnostic counters report refill passes, requests, resumed drives, preserved
font longhands and the maximum resident selector-tree depth of a suspended element
(including that element). This depth is a diagnostic location, not the flat-tree
dependency span when the two axes differ. Repeated physical work remains visible in the ordinary drive and
longhand counters; none of these observations controls readiness or completion.

### 9.7 Custom properties

Custom-property environments live on the C++ side (`CustomPropertyData`); the engine interns only an opaque environment identity per record. Environments are immutable and structurally shared:

* Identity zero means the empty environment.
* An environment is its declared name/value map plus a parent pointer: inherited changes are a parent environment plus a small delta, and lookup walks the parent chain.
* Two flattening heuristics bound chain cost: a chain deeper than 32 flattens fully, and a small parent (8 declarations or fewer) is absorbed into its child.
* Environments are interned by parent identity plus declared names and values, so most descendants inherit the same identity and allocate nothing.

Resolution is memoized per environment: one cached resolution per (environment, document, registration generation), where the registration generation is document-wide and bumps on any `@property` change, so a registration edit invalidates environment resolutions wholesale rather than per name. Cycle handling follows the specification's property-replacement algorithm with an active-resolution guard producing the guaranteed-invalid result. The inherited custom-property names a computation actually consults are recorded in its input record and participate in the reuse gate (§9.4).

A separate **parsed-substitution cache** memoizes the parsed value of substitution-bearing declarations per (rule, environment identity, registration generation, property): elements sharing an environment parse each `var()`-bearing declaration once per restyle instead of once per element. It is invalidated by registration-generation change (wholesale), by declaration edits (per rule), and by environment death (swept). It is currently accounted outside the engine's memory controller, a known gap.

Environment lifetimes are reference-driven: a live style record or input record pins its environment (and the parent data it read), and a sweep reclaims environments with no remaining reader. Unbounded history retention is forbidden.

### 9.8 Computed-style records

The engine interns computed style as a 32-bit base record: the tuple of a computed-group set, custom-property environment, fixed metadata, and reconstruction metadata, each itself an interned handle. What an element carries is the 64-bit final record identity: the base record plus a tag bit and generation distinguishing animation overlays, so an animated element's identity changes with its overlay while sharing the base. Sharing comes from hash-consing at publication; the group payloads reuse the existing Rust computed-value group representation, and no second complete style layout exists.

Layout and paint consume the same style handle rather than retaining redundant complete style objects, where lifetime and threading permit.

Engine drives use cached winner locations and borrow their original declaration
spellings through an immutable view of program and element inputs. Canonical
specified-value identities do not replace those spellings. Only substitution
outputs need value ownership in the winner recipe. Cascade order remains the
logical/physical property tie breaker. Recipe capacity is charged to scratch;
program inputs remain stable for the context's lifetime.

Group sets retain their ordered group IDs alongside the contiguous host payload
view, so reconstruction and liveness do not hash payload pointers back to IDs.
Unchanged animation overlays are borrowed through the reuse check; only a new
retained overlay clones their owned contents. Custom-property name text is
shared across environment/store owners.

### 9.9 Inheritance

Inherited values flow through a shared inherited-context identity. Use the inherited-group handle already present in the parent style record and perform a sequential top-down comparison over the required observed subtree. Descendants are **not** given copied inherited payloads or permanent dirty bits. A descendant observed under a new token evaluates only inherited properties it consumes and stops when its resulting inherited or full style identity is unchanged.

Inheritance transposes through the **flat** tree.

### 9.10 Activation and environment predicates

```text
SelectorMatches
AND SheetAttached
AND MediaConditionTrue
AND SupportsConditionTrue
AND ContainerConditionTrue
AND ScopeConditionTrue
    -> ActiveRuleMatches
```

An environment change can toggle already-known matches without re-running selector logic. C++ evaluates media and supports conditions and publishes the resulting per-sheet or per-rule `conditions_hold` flips; container conditions are answered per element during matching instead. Rust retains the document-wide condition-program identity and committed Boolean in required program state. Do not build a persistent inactive-rule match cache; activation evaluates the selector program through normal inputs, reusing a cached selector answer only when one is already available in the current traversal.

Environment inputs: viewport and page size; device pixel ratio; media features and preferences; document URL and target state; font selection and loading state; container sizes and styles; scroll and view timeline state; anchor-positioning inputs. Changes publish typed deltas to actual consumers.

### 9.11 Animations and transitions

Animations and transitions are **sparse time-dependent overlays on a stable base style**. Sampling an animation must not clone or recascade the base computed style.

The animation graph depends on the selected keyframes program, timeline inputs, animation and transition declarations, and base computed values required for interpolation. Changing `@keyframes` updates animations referencing the affected name; unrelated rule changes do not rebuild animation programs. When an animation completes or ceases to affect a property, removing the overlay reveals the already-maintained base value.

`@starting-style` contributes a typed starting-style program used only when the transition model requires it. Its rule and CSSOM mutations update before-change-style and transition-start consumers; it never becomes an unconditional alternate cascade for every element. Define the exact before-change-style lifetime and observation rules alongside the transition implementation.

Who does what today: the host keeps the animation objects, their timelines and the transition step; the engine decides and computes the rest.

* **Plans.** Each tree scope publishes its `@keyframes` (`style_engine_set_tree_scope_animation_keyframes()`). The engine resolves an `animation-name` in the scope of the declaration that named it, decides whether `display: none` stops an animation, and plans the CSS animations of every record it settles, first records included, skipping a definition that has not changed since the host last applied it. The host applies the plan.
* **Samples.** The host publishes a description of every effect it runs: keyframes, easings, composite operations and declarations (`style_engine_set_element_animation_effect_descriptions()`). `rust_sample_animation_effects()` resolves the keyframe declarations, substitutes them against the element, computes the keyframe values in length contexts it builds over the installed record, and composes the overlay, custom properties and compositor keyframe values included. The preparation stays on the overlay, keyed by the effects' versions and, where it substituted against the element, by its custom-property environments.
* **Compositions.** The composition of an element or pseudo-element is laid over the record its winners decide. A row derives the record beneath it, and the composition stays detached and pinned (`DetachedComposition`, which must be released or reattached) until the host installs the new record and samples over it. Children wait for a composition that moves, and pseudo-elements settle against it after the sample.
* **Transitions.** A row whose record declares transitions, or that moves their declarations, owes the host the transition step, which runs against the record held before once the new one is installed (`decide_transition_step()`).

Nothing else computes styles: a row the engine did not settle in its transaction is answered from its record demand (`answer_record_demand()`), and a row whose demand the engine refuses keeps the record it holds.

### 9.12 Style-layout fixed points

The pure style graph contains only dependencies resolvable from inputs held stable by the read epoch. Geometry and layout-dependent features (container queries, anchor functions, tree-counting functions with layout inputs) belong to a separate style-layout fixed-point coordinator and cross an explicit typed boundary that exposes style-to-layout and layout-to-style version changes.

Use the existing feature-specific sequential coordinator; do not build a generic SCC runtime. The general selector and cascade graph remains acyclic within one read epoch. A cycle (style → layout → query input) produces one immutable style result per read epoch; the coordinator advances the layout-input version and schedules the next transaction per the feature's specified iteration and cycle-breaking rules. No partially updated graph is visible to script.

## 10. Memory

Memory is a primary constraint. Time improvements requiring unbounded retained state do not satisfy this design.

### 10.1 Tiers

* **Tier 0: authoritative semantic inputs.** DOM, CSSOM, stylesheet programs, browser state. Reference it; do not duplicate it.
* **Tier 1: minimal live style state.** Compact handles and shared payloads required to answer current observers. Not evictable without first proving the content has no observer.
* **Tier 2: shared semantic IR and intern pools.** Canonical selector, declaration, condition, cascade nodes; routing registry; transpose route programs. Bounded relative to the compact parsed stylesheet program; cannot absorb selector-result state.
* **Tier 3: acceleration materializations.** Indexes, match sets, inverse maps, witnesses, proofs, flattened environments. Strictly budgeted and fully evictable.
* **Tier 4: transaction scratch.** Reusable arenas for flattened relation ranges, delta queues, batch evaluation, comparison. High-water marks monitored; shrinks after unusually large work.

An interned object is not automatically Tier 2. Contexts, summaries, match answers, and proofs stay Tier 3 even when stored in an intern pool.

| Retained object | Tier | Reclamation rule |
| --- | --- | --- |
| DOM, CSSOM, browser state | 0 | Owned by the authoritative subsystem |
| Active read-epoch header and generation | 1 | Reclaim after every reader retires |
| Attached compact semantic rule program | 2 | Reclaim after detachment and epoch retirement |
| Style feature atom mapping | 2 | Reclaim after last program/posting reference and epoch retirement |
| Delta-routing registry and transpose routes | 2 | Reclaim with the selector program and retired epochs |
| Stable rule, attachment, and order nodes | 2 | Reclaim after semantic detachment and epoch retirement |
| Memory controller and aggregate accounting | (uncharged) | Reclaim with the document |
| `StyleNodeID` and DOM-to-style-node mapping | 1 | Reclaim after disconnection and epoch retirement |
| DOM relation-navigation columns | 1 | Reclaim with the style node; not evicted for ordinary pressure |
| `StyleRecordID` on an observed style node | 1 | Reclaim only after the node is provably unobserved |
| Live shared computed-style payload | 1 | Reclaim after all style records and epochs release it |
| Live custom-property environment | 1 | Reclaim after styles, animations, epochs release it |
| Live animation or transition overlay | 1 | Reclaim after the effect and observing epochs release it |
| Cascade winner group | 3 | Evict and reconstruct through exact cold cascade |
| Feature posting | 3 | Evict and read authoritative DOM facts |
| Retained match answers and selector incidences | 3 | Evict and reevaluate their selectors |
| Prefix transition and answer caches | 3 | Discard; rebuilt from exact evaluation |
| Retained witness | 3 | Evict without semantic effect |
| Normalization journal and coarsened scope markers | 4 | Release after the normalized transaction commits |
| Temporary preorder, impact region, batch bucket, delta queue | 4 | Release at transaction end or scratch shrink |

### 10.2 Document budget

Each document has a derived-style memory controller tracking exact bytes by logical operator, physical representation, scope, and tier.

At each flush boundary the controller opens a new admission period for every Tier-3 category and records its starting residency. Admission remains work-conserving while the complete Tier-3 pool has space; there are no per-category partitions.

Owners account exact capacity at arena, slab, vector, bitmap, and table growth boundaries. Growth crossing the global Tier-3 limit remains usable throughout the current evaluation loop. At the loop boundary, a pure decision over final category residency and the quota-period starting residency closes the categories that grew to new entries for later loops in that period. The Rust transaction and the host cold-matching batch each have an explicit end boundary. Exact-versus-pruned answer completion is selected before its batch and remains fixed throughout it. Existing entries may continue changing so they never become stale or partial. At the following flush boundary, an over-limit category is dropped whole; there is no mid-traversal eviction, retry, or rebuild.

```text
Tier3Limit = min(
    DeviceCap,
    BaseAllowance + NodeAllowance * ConnectedElementCount)
```

`ConnectedElementCount` counts connected styleable DOM elements in the live DOM at the read epoch, not pseudo style nodes, arena capacity, or retired generations. Required selector and rule-program storage is charged honestly to Tier 2 but does not enlarge the optional Tier-3 pool; process-shared selector programs therefore need no per-document attribution rule in this budget.

| Configuration | Device cap | Base | Per connected style node |
| --- | ---: | ---: | ---: |
| Browser document | 64 MiB | 1 MiB | 2,048 bytes |

These are a ceiling for the complete set of optional views, not an expectation. Counters report actual capacity against the limit rather than treating the limit as a target. The node coefficient reaches the device cap at 32,256 elements; above that the cap binds and larger documents run progressively colder, which is intended.

Tier 2 is required program state and is tracked without a cap. The cold-interpreted fallback that a cap would require does not exist; required program capacity must therefore never be refused or relabelled as Tier 3.

**Tier 4 peak cap:**

```text
Tier4Limit = min(
    ScratchCap,
    max(4 MiB, Tier3Limit, TransactionNodeAllowance * ConnectedElementCount))
```

The scratch cap is 32 MiB and the transaction node allowance is 768 bytes. The multiplier on `Tier3Limit` is 1, not 2, so transaction scratch cannot dominate the document's total style footprint; the 4 MiB floor matters most on small documents. Tier 4 is a **reported ceiling**, not a refusable cap: scratch charges cannot be refused because the flush must complete. The limit makes an over-limit transaction visible in the pressure report but changes no allocation or planning decision.

Accounting uses one plain-integer ledger per document or shared-program context, mutated through exclusive borrows. Leases share only the accounting lifetime, so destroying an outliving query or shared program still releases its charge. Ledger operations never select admission or refuse required capacity. Peak charged live bytes and peak scratch bytes remain available after temporary charges are released. Required output and node workspace is reserved before computation. Output capacity is reconciled at vector growth; computation scratch capacity is sampled every 256 published elements and reconciled after the loop, before release. Its high-water observation reports accounted capacity at those points, not an allocator-level peak.

Byte accounting happens at arena, slab, vector-capacity, bitmap, and hash-table allocation boundaries: operators update aggregate counters when capacity changes, not on every lookup.

### 10.3 Eviction

Eviction removes physical acceleration state and bumps no semantic versions. A later observer reconstructs from authoritative inputs and remaining logical state.

The policy is benefit-driven with scan resistance built in. Each Tier-3 category accumulates hit/miss observations with exponential decay. At a pressured boundary, categories with no recorded hits are considered first, then the remaining categories by increasing hit ratio, with memory-category order breaking ties. It selects nothing unless the complete selection can cover the actual overage: an all-or-nothing rule that keeps one broad cold pass from flushing a working set that pays.

The budget responds to document and program size. Tier-3 state remains fully evictable while compact live styles are retained.

*Database counterpart:* buffer-pool replacement, with its known trap: without scan resistance and cost awareness, one broad cold pass flushes the working set, and a recency-only policy starves the views whose maintenance would pay, leaving a full-scan engine with extra bookkeeping.

### 10.4 Interning

Intern pools use compact document-local handles and weak reclamation where possible. Apply hash-consing only where lookup and table overhead is likely to beat duplication; very small or unique values are cheaper inline. Prefer adaptive small-vector and interned representations over universal heap allocation.

### 10.5 Per-node metadata budget

Mandatory per-node state is at most **eight 32-bit words**:

```text
StyleNodeID or its DOM mapping       (an existing DOM identity can satisfy this without duplication)
StyleRecordID                        observed content only
parent                               required relation column
first element child                  required relation column
next element sibling                 required relation column
previous element sibling             required relation column
depth                                required relation column
tree scope                           conditional relation column
```

```text
MandatoryNodeBytes(surface) = BaseMandatoryNodeBytes + ConditionalRelationBytes(surface)

BaseMandatoryNodeBytes           <= 28
ConditionalRelationBytes(surface) <= 4
MandatoryNodeBytes(surface)      <= 32
```

The engine asserts the relation-column budget in its own tests. The conditional allowance covers tree-scope identity, allocated only when the document requires it and no authoritative field can be exposed safely without duplication. Depth rejects impossible ancestry checks immediately and bounds the remaining parent walk. Slot, part, pseudo, or future relation navigation must derive a compact identity or replace the physical representation; it cannot raise the 32-byte cap. (`StyleNodeID` is a `NonZeroU32`, so optional relation slots niche-pack into one word.)

Optional context, winner, dependency, and witness handles use dense, bit-packed, or segmented Tier-3 columns according to their access and clearing patterns; they do not consume a reserved mandatory word on every node. Dense optional columns and shared live style payloads are reported separately rather than hidden in the 32-byte relation surface.

**Tier 1 is required state, not free memory.** No duplicate complete computed styles.

### 10.6 Failure behavior

Memory pressure never degrades correctness; it degrades retention. The Tier-3 chain is:

```text
account exact coarse growth and keep it usable for this quota period
    -> at the loop boundary, close grown categories to new retained entries
    -> continue exact evaluation for entries that remain uncached
    -> drop the complete over-limit category at the next flush boundary
```

Independently, a transaction the journal had to coarsen widens its impact region to the document and is evaluated exactly there. Whole-document exact evaluation runs as one pass; its scratch is charged to Tier 4, which is a reported ceiling rather than a refusable cap (§10.2).

Memory pressure can therefore make a later flush slower and colder, but never rejects a synchronous style read as a Web-visible outcome.

## 11. Scheduling

Execution is sequential. Style transactions are processed as dependency-ordered stages over homogeneous delta queues, not recursive pointer-chasing calls. The flush (`take_style_transaction`, flush.rs) runs:

```
/* Detailed source-code truncated for AI context efficiency. */
```

Computed-value construction is C++-side: the reaction batch drives StyleComputer, which builds computed properties and publishes each element's computed groups back to the engine for interning (§13). Layout, paint, animation, and accessibility consequences are produced by the C++ reaction application.

No stage publishes script-observable state before the transaction commits: the emit callback only copies rows, and the FFI callback guard rejects reentry into the engine while it runs. Mutations made while reactions are applied are recorded as new input and become the next transaction of the same style-stabilization epoch; the update loop drains transactions until the document is stable. Old handles stay alive until the stabilization epoch and all its consumers retire.

**Priority** distinguishes latency-sensitive work from throughput work but never changes semantic transaction order:

* Interaction state and required style reads are latency-sensitive.
* Parser and stylesheet-load batches favor throughput.
* Background materialization and hot-query compilation are opportunistic.
* Memory reclamation can cancel speculative materialization.

An interaction transaction may run before uncommitted speculative compilation, but cannot pass an earlier stylesheet attachment that is already semantically committed. A forced style read waits for every preceding committed input delta and ignores later speculative work.

## 12. Consumers and laziness

Style is maintained for connected content, and every consuming subsystem reads it through the shared style handle:

| Consumer | Reads requiring current style |
| --- | --- |
| Layout | Box-tree construction, layout update, intrinsic sizing, fragmentation |
| Paint and compositing | Display-list construction, stacking, clipping, screenshots |
| Input and hit testing | Pointer events, cursor, hit-test visibility, native control behavior |
| Focus | Sequential focus navigation, focusability, focus-visible state |
| Find-in-page and selection | Visibility and generated-content participation |
| Accessibility | Tree creation, role/state updates, generated content, visibility |
| Animations | Base style, interpolation inputs, timeline attachment |
| View transitions | Old/new capture, generated `::view-transition*` pseudo-tree style, lifecycle snapshots |
| Script APIs | Resolved style, geometry APIs, selector APIs where relevant |
| Observers | Resize, intersection, media-query, and other delivery requiring updated style/layout |
| Display locking / `content-visibility` | Activation, skipped-content validation, retained state |
| Browser UI and automation | Printing, capture, WebDriver, DevTools inspection |

Recomputation reaches exactly the elements the reaction batch names, so laziness comes primarily from precision: an element whose inputs did not semantically change receives no reaction. Beyond that, two `display: none` cutoffs bound work in hidden content: the batch path does not propagate style recomputation into a `display: none` element's descendants (they carry only inherited-context reactions and are re-entered when an ancestor becomes visible), and the targeted read path stops its inheritance-chain walk at the topmost `display: none` ancestor. These cutoffs are derived from the current style at traversal time; a broader certificate-of-unobservation mechanism is a known future direction, not something the engine infers today.

Web-platform synchronization holds throughout: a style read flushes all preceding input transactions and returns a value from the resulting committed style state.

## 13. C++/Rust boundary

LibWeb C++ remains authoritative for DOM and CSSOM object identity, mutation semantics, document lifecycle, loading order, style-observation barriers, layout, and paint.

StyleEngine lives in the existing LibWeb Rust crate and is authoritative for selector matching, delta routing, cascade winners, and retained style state:

* the compact logical selector/program representation;
* physical state keyed by document-local StyleEngine IDs;
* the exact cold evaluator and the incremental evaluator;
* selector matching, cascade winner resolution, and reaction planning;
* match-answer identities, winner groups, memory accounting, and instrumentation.

Computed-value **construction** is C++-side: the reaction batch drives StyleComputer, which computes properties and builds computed-value groups, then publishes each element's groups back to the engine, where records are interned and become the shared authoritative representation. So the split is: Rust decides *what* must recompute and *which declarations win*; C++ computes *values*; Rust retains and interns the published result. DOM and CSSOM remain the semantic sources for element and program facts; Rust owns the committed evaluation projection and all retained derived state, and C++ must publish every source mutation needed to keep that projection current.

**Identity allocation.** The engine mints `StyleNodeID` values (C++ requests them through an allocation call that accepts a batch); C++ owns DOM lifecycle and the DOM-to-style-node mapping. Rust treats IDs as opaque dense indexes. Reuse safety comes from two-phase retirement: retired indexes enter a pending pool and are released for reallocation only at a later safe boundary, never while a stale handle can observe them.

The engine likewise mints process-global `StyleAtomID` values for selector-mentioned tag, ID, class, attribute, value, namespace, and state identities, keyed by the exact interned-string identity C++ passes (a `Utf16FlyString`'s one-word raw identity, retained until the last document reference releases it, so identity equality is exact and never hash-approximate). Interning on the engine side is what makes selector-name atoms and fact atoms comparable across shared programs: one process table assigns one sequence, while each document records and releases the atoms it uses. Rust stores only the `u32` atom in selector IR and postings; text the engine must inspect byte-wise (attribute values for broad string operators and language tags) is pushed to engine-side storage on demand and re-pushed if evicted.

**Relation columns** are stored on the Rust side, keyed by `StyleNodeID`, and maintained from the journalled tree deltas within the same transaction that reports the mutation, so transpose traversal, impact-region membership, and sibling-sequence scans run entirely inside the evaluator. Two deliberate exceptions bypass per-node journalling: initial bulk load links the whole arriving tree directly (safe because the root arrival forces whole-document evaluation), and shadow host/root registration is applied directly at registration time.

**Forward transfer.** C++ mutation helpers append element facts to retained input buffers rather than crossing FFI immediately. One flat immutable transaction per flush submits six pointer-free fixed-width row arrays (tree relations, element arrivals, local features, state, element declarations, and element style inputs) plus the arrival custom-state atom arena in a single call. Namespace, language, directionality, heading level, custom states, and slot-ness ride the element-arrival rows. Program, sheet, rule, layer, and topology changes still cross as individual boundary calls as they happen and are journalled engine-side into the same normalization journal.

**There is no reverse fact-fetch protocol.** The engine never asks C++ for a fact it lacks: C++ pushes every fact it owns eagerly as deltas, the engine stages them, and evaluation reads old/new values through a transaction-local fact view over retained pre-images plus the resident fact store. A broad plan holds an immutable shared view of the resident primary columns and allocates only the transaction-specific overlays and indexes it needs; it does not clone the complete fact store or cross the boundary. The one reverse direction that exists is an enumeration callback for flat-tree descendants not represented by an engine-owned child list.

**Computed style crosses as a handle.** The boundary transfers a shared style-group handle, never a copied per-property object. C++ consumers hold `StyleRecordID` and read through the record view; layout and paint consume the same handle rather than materializing a private complete style per element (anonymous layout-derived boxes are the exception and own their values). Records are pinned while any C++ consumer references them and reclaimed per the Tier-1 rules in §10.1.

### 13.1 Module layout

The C++ side of the boundary is `Libraries/LibWeb/CSS/StyleEngineBridge.*` (the FFI surface; the regular boundary calls are one row each in the table of `boundary.rs`) and `Libraries/LibWeb/CSS/StyleEngineInput.*` (input collection and transaction assembly). The engine lives in `Libraries/LibWeb/Rust/src/css/style/`:

```
/* Detailed source-code truncated for AI context efficiency. */
```

## 14. Hard cases the implementation must handle

These defeat the sharing and index assumptions and must work by design, not by luck.

**A new selector has no useful rejecting feature.** A dynamically inserted broad universal/structural selector has no posting that rejects most of the scope. The candidate estimate crosses the selective cutoff, so it runs once through the exact batch evaluator over `Nscope`. If all its declarations lose, the transaction reports broad discovery cost and zero propagation. Existing selector truths are not invalidated; missing candidate state is reconstructed explicitly; no unchanged computed style is republished.

**Many winners change to the same value.** A root toggled from `theme-a` to `theme-b` with 50,000 `.item` descendants, where two descendant rules exchange matches and both declare the same literal canonical `color`. Declaration identity changes on every item, but declaration identity is not in `SpecifiedWinnerKey`; the canonical specified value and the (empty) dynamic read set are unchanged. Expected work shape:

| Stage | Count |
| --- | ---: |
| Normalized input | 1 class edit → 2 local feature deltas |
| Impact subjects | 50,000, plus charged routing and relation discovery |
| Exact selector checks | 100,000 (two selector programs) |
| Selector-truth deltas | 50,000 removals + 50,000 additions |
| Rule-match deltas | 50,000 removals + 50,000 additions |
| Winning declaration identity changes | 50,000 |
| `SpecifiedWinnerKey` changes | 0 |
| Computed `color` evaluations | 0 |
| Computed record identity, layout, paint changes | 0 |

The engine still charges the substantial selector and cascade discovery: this transaction is not free, it is *downstream*-free. If a fixture changes transition semantics or a dynamic read-set input, the appropriate key changes and resolution runs. Byte equality alone is never the stopping rule.

**Slot assignment and flat-tree relations change.** Moving a node between slots changes slot assignment and flat-tree identities even when the DOM parent is unchanged. `::slotted()`, host-context, inheritance, and any operator whose read set includes the affected relation are reevaluated in the shadow scope *and* the participating light-tree scope. Unrelated document-tree selectors keep their results. Never reuse a DOM-tree proof for a flat-tree question.

**Sibling-relative `:has()` churn.** For `.item:has(+ .item.selected)`, changing `selected` has exactly one possible anchor: the immediately preceding sibling. Direct relation evaluation is exact and allocates no summary. `.card:has(~ .panel .error)` can force a large sibling-and-descendant search: run the complex evaluator until the replacement budget is reached, then finish the query family with the exact batch plan. Boolean truth is never approximated.

**Tier-3 state disappears during interaction.** A backgrounded document loses postings, witnesses, and prefix caches. A later state change plus synchronous style read flushes the transaction, then uses DOM facts and the exact cold evaluator for the observed scope. The result must equal a never-evicted run. Returning to the foreground reconstructs nothing eagerly.

**Privacy-sensitive state.** When visited-dependent styling is implemented, its taint component must join operator, cache, and style-record identities, and instrumentation must redact tainted identities and never expose match cardinality to script or developer-facing timing counters. The requirement is recorded here so no cache added in the meantime bakes in an assumption that all results are shareable across a future privacy boundary.

## 15. Cost model

```text
Nscope       style nodes in the exact affected scope
Nlive        observed style nodes in that scope
Naffected    style nodes in the exact mutation-affected region
Nregion      style nodes enumerated by a typed impact-region stream
Ncandidates  candidates activated by one local input delta
Nsiblings    sibling positions whose selector truth may change
Sscope       selector operators attached to that scope
P            candidates in the narrowest usable posting
Qroute       transpose entry points routed from the changed typed input
Csel         exact candidate-check cost for the selector
Rsteps       parent/sibling relation steps performed by conventional combinators
Rimpact      relation positions visited while constructing the impact region
Hregion      relation steps to prove one posting candidate belongs to the region
             (executed against resident relation columns, no boundary crossing)
Aprefix      transaction-local selector-prefix work at one node
Mrule        concrete matches of one rule
Dchanged     declarations affected by a program edit
K            candidates inspected during witness replacement
Edepth       custom-property parent-environment depth
Tsub         token/function work on chosen substitution paths
```

Accepted bounds when no useful optional materialization exists:

| Operation | Discovery and repair bound |
| --- | --- |
| Local feature/state mutation | `O(Qroute + Rimpact + Ncandidates * Csel + Rsteps)`; routing never scans all `Sscope` headers |
| Intersect a posting with a subtree region | Selective `O(P * Hregion)` plus candidate checks, or batch `O(Nregion)` local-fact stream |
| Cold ordinary style pass | One style-node stream plus candidate program probes and `O(sum(Csel + Rsteps))` |
| Conventional ancestor mutation | `O(Naffected * Aprefix)`, stopping at unchanged selector outputs |
| Adjacent-sibling mutation | `O(1)` relation discovery per selector plus compound checks |
| Subsequent-sibling or positional mutation | `O(Nsiblings * Csel)` |
| Create a simple child/adjacent `:has()` witness | `O(Qroute + Csel)` per routed query before outer propagation |
| Create a simple descendant `:has()` witness | `O(Qroute + depth * Csel)` |
| Create a simple following-sibling `:has()` witness | `O(Qroute + preceding_siblings * Csel)` |
| Insert a selector with a rejecting posting | `O(P * (Hregion + Csel))` plus emitted matches, until the batch cutoff |
| Insert a selector with no rejecting index | `O(Nscope * Csel)` |
| Delete a cold rule | `O(Nscope * Csel)` via old-selector evaluation |
| Edit declarations of a cold rule | Same match discovery as deletion, then `O(Mrule * Dchanged)` cascade comparisons |
| Replace a sheet when structural diff fails | Exact batch of the old/new program delta; worst case `O(Nscope * Sscope * Csel)` |
| Replace one retained simple `:has()` witness | Forward walk of the query's axis, `O(K * Csel)` |
| Answer a cold complex `:has()` query | Exact traversal of its anchor region; worst case `O(Nscope * Csel)` per query family |
| Resolve a cold custom property | `O(Edepth + Tsub)` including cycle handling |
| Tier-3 miss for an observed node | Exact selector and cascade evaluation for that scope; worst case `O(Nscope * Sscope * Csel)` |
| Broad source-order or root inherited change | `O(Nlive)` or greater when that many outputs change |

Accepted lower bounds the design does not pretend to beat:

* A new selector may require inspecting every element its indexes cannot reject.
* A source-order change can truly change a property on every matching element.
* A structural mutation can truly change positional results for many siblings.
* A root inherited-value change can truly affect every observed descendant.

The engine's job is to make broad costs correspond to real semantic uncertainty or real output, instrument them, and stop claiming selectivity when a batch plan would be cheaper.

## 16. Instrumentation

The engine maintains a large counter ledger (`instrumentation.rs`), exposed to C++ and through the internals object, alongside a separate C++-side ledger of style-update and invalidation counters. The main families:

* Input deltas by kind; journal cancellations, replacements, and coarsened scope markers.
* Executed selector primitives in five buckets: local-feature tests, state tests, combinator steps, structural tests, relational tests.
* Routing: routed entry points, impact relation steps, region cardinality.
* Candidate enumeration: posting entries enumerated, region-membership checks, exact checks, rejections.
* Matching and stopping: retained-answer patch stops, cascade compaction counts, cold-batch rows evaluated and missing.
* Tier-3 memory decisions: admission closures per category and benefit-guided boundary evictions.
* Bytes by tier and category.
* Style records and interned states created, reused, reclaimed; reaction and recomputation counts on the C++ ledger (the amplification triage numbers).

The doctrine over any future counter: ratios with a zero denominator report the raw numerator and `not applicable`, never an invented zero, and every plan decision made against an estimate should leave behind the observation that would have corrected it.

### 16.1 Exclusive phase clocks and physical work

`internals.styleEngineCounters()` reports cumulative integer microseconds at the
`take_style_transaction` boundary. `transactionMicroseconds` equals the sum of
`commitMicroseconds`, `routingPlanningMicroseconds`, `prepareMicroseconds`,
`matchingCascadeMicroseconds`,
`computationPublicationMicroseconds`, `emitMicroseconds`, and
`transactionRemainderMicroseconds`. Take two snapshots and subtract each field
when measuring an update. Rounded cumulative endpoints preserve the identity even
for short or empty transactions; the whole includes destruction of local scratch.

The fused names describe the current execution: routing performs exact planning
and prefix matching inline; answer patching and completion update winners;
computation interns and installs records immediately. No per-element clocks try
to subdivide that work. Commit includes reclamation and journal application.
Prepare covers reached-scope preparation and completion-batch construction before
matching. Routing-specific dispatch preparation remains within routing/planning;
program and declaration input preparation remains within commit. Standalone host
matching prepares its context in the enclosing C++ caller interval. Remainder
covers final bookkeeping, retained traversal handoff, and cleanup.

The C++ ledger (`internals.getStyleInvalidationCounters()`) has its own identity:
`styleUpdateMicroseconds` equals `styleUpdateSubmissionMicroseconds` plus
`styleUpdateBridgeMicroseconds`, `styleUpdateApplyMicroseconds`, and
`styleUpdateRemainderMicroseconds`. Submission covers update input preparation,
transaction setup, buffered input transfer, and document computation inputs.
Bridge measures the transaction FFI call. Apply covers reaction preparation and
installation for each batch, excluding subsequent transactions. Remainder covers
animation sampling, other coordination, bridge result handling, and cleanup.
The Rust clocks subdivide bridge; **do not add the two ledgers**. Mutation hooks
before `update_style` are outside both clocks and must be measured separately.
Diagnostic transactions still contribute to the Rust ledger, but their returned
bridge durations are not charged to the C++ style-update ledger.

`styleEnginePlanningMicroseconds` remains a compatibility alias for the bridge
clock. `styleEngineTransactionSetupMicroseconds` is a subset of submission, and
`styleRecomputeMicroseconds` is a nested diagnostic. Neither is an extra exclusive
phase. Timing counters never determine which work runs.

Physical-work counts distinguish attempted work from accepted output:

* `reachedStyleNodes` counts every node entering the transaction's answer pass,
  including nodes stopped before publication. `invalidatedStyleNodes` retains its
  existing post-stop meaning. Routing visits are counted separately.
* `coldNodesEvaluated` and `candidateChecks` count cold matching rows and exact
  candidate checks; `styleRecordsInterned` and `styleRecordsReused` retain their
  existing publication meanings.
* `engineFullDrivesStarted` and `enginePartialDrivesStarted` count Rust record
  drives reaching property computation. `enginePhysicalLonghandEvaluations`
  includes every shared-driver phase they execute, even if a later phase bails or
  C++ discards the record. `enginePartialLonghandEvaluations` is its partial-drive
  subset. `engineComputedLonghandEvaluations` still counts accepted output only.
* The C++ ledger's `computedLonghandDrivesStarted` and
  `computedLonghandEvaluations` count the host-driven computation lane. Add its
  evaluations to Rust's physical evaluations for both lanes together.
* `engineDriveCopiedTableSlots` reports drive-boundary slot copies, now zero:
  full and partial drives borrow unchanged slots through a flat base owner.
  `styleFfiCounters()` reports `longhandTableCopiedSlots`,
  `longhandTableCopyRetains` and `longhandTableStorageAllocations` across
  seed, inherited-swap, durable materialization and host boundaries. Copy
  retains include the single base-owner retain. A new unique result is
  materialized once; no durable table retains a delta chain.

These additions use the existing plain-integer document ledgers. The shared
driver counts locally and each Rust phase folds its work immediately, before
bailout checks. They allocate no observer state, format no strings, and perform
no atomic operations per element. Phase clocks use a fixed number of monotonic
clock reads per transaction or C++ reaction batch.

## 17. Failure modes and safeguards

*Database counterpart:* these are the database's own pathologies in this engine's terms: view-size blowup, trigger fanout, buffer-pool thrashing, plan instability under churn, stale statistics. Each safeguard is the standard remedy, stated concretely.

**Materialization explosion.** A selector family may generate unique states for most elements. Refuse the allocation at the Tier-3 limit and use exact cold evaluation. Low sharing is a real failure mode, not something hidden by calling the objects interned.

**Dependency-edge explosion.** Never allocate an unbounded reverse subscriber list. When a direct edge budget would be crossed, retain no list and validate the exact affected scope from shared input identities.

**Match-relation duplication.** Canonicalize match answers while a traversal consumes them. Per-node retained match answers and per-program selector incidences exist only as Tier-3 evictable views (never required state, never a complete `RuleID -> elements` relation), so the duplication is bounded by the budget and disappears under pressure.

**Cache thrashing.** With no promotion logic, repeated cold reconstruction is directly visible in counters. Any later promotion mechanism must use hysteresis, rebuild cost, and a re-promotion penalty.

**Adversarial stylesheet churn.** Program transactions normalize repeated edits and reuse immutable IR. Per-origin and per-scope work queues stop one churning sheet from forcing unrelated selector reevaluation.

**Pathological broad changes.** When a change produces huge output, batch it. Never pretend the output can be avoided and never add unrelated invalidation work.

**Stale lazy state.** Every lazy result records the identities of its semantic inputs or a shared context token. Observation validates those identities. Eviction and physical compaction never modify semantic identities.

**Identifier exhaustion.** Document-local IDs are never reused while stale handles may exist (two-phase retirement). Exhaustion of the 32-bit index space is treated as unreachable for one document; if that assumption ever breaks, the answer is an arena rebuild at a safe boundary, never silent aliasing.

## 18. Verification

The engine's correctness claim (every path produces results identical to full recomputation from authoritative inputs) is enforced by standing machinery, not by convention:

* **The exact cold evaluator is the reference implementation.** It is plain by design, free of the incremental path's caches, and is both the eviction fallback and the oracle every other path is checked against.
* **In-crate unit and differential suites** (`tests.rs`, `differential_tests.rs`) exercise operators directly, including differential comparison of incremental against cold evaluation.
* **Verify modes** re-derive incremental answers through the cold path at runtime and compare, observer-only: verification never mutates engine state, never disables the fast path it checks, and treats an incomplete comparison as a failure.

Commands, workflows, and debugging guidance for all of these are in [StyleEngineTesting.md](StyleEngineTesting.md).

---

### Documentation Style StyleEngineTesting (`Documentation/Style/StyleEngineTesting.md`)

# StyleEngine: testing and debugging

How to test style engine changes, how the verification machinery works, and how to debug. The engine itself is described in [StyleEngine.md](StyleEngine.md).

The correctness bar: **incremental results must be identical to full recomputation from authoritative inputs.** Over-invalidation (recomputing something that did not change) is a performance problem; under-invalidation (missing something that did change) is always a bug.

## 1. The test stack

Run these from fastest to slowest; a style change is not done until all of them pass.

**Rust unit and differential tests.**

```sh
cd Libraries/LibWeb/Rust && cargo test --release -p libweb_rust
```

`tests.rs` drives engine operators directly through the same entry points the bridge uses. `differential_tests.rs` compares incremental evaluation against the exact cold evaluator over generated mutation sequences.

**Web tests.** The style-engine and style-invalidation suites under `Tests/LibWeb/Text/input/css/` assert observable behavior (computed styles after mutations) and, via internals hooks, engine behavior (recompute counts, invalidation reach). The full LibWeb suite is the outer gate:

```sh
./bin/test-web
```

**Verify mode.** `--verify-style` enables every style verification gate for the run (it sets the `LIBWEB_VERIFY_*` environment variables below before spawning WebContent):

```sh
./bin/test-web --verify-style
```

A focused loop for style work:

```sh
./bin/test-web --verify-style -f Text/input/css/style-engine/ -f Text/input/css/style-invalidation/
```

The gates are per-mechanism checks that run at their mechanism's site. Some re-derive incremental results through the exact cold evaluator and compare (`LIBWEB_VERIFY_STYLE_ANSWER_PATCH`, `LIBWEB_VERIFY_SELECTOR_TRUTH_DERIVATION`, `LIBWEB_VERIFY_CASCADE_WINNERS`); others assert structural properties (`LIBWEB_VERIFY_STYLE_PLAN_PROVENANCE`, `LIBWEB_VERIFY_PUBLISHED_STYLE_TRANSACTION`), and two C++-side gates cover the computed closure and the style-diff fast path (`LIBWEB_VERIFY_COMPUTED_CLOSURE`, `LIBWEB_VERIFY_STYLE_DIFF_FAST_PATH`).

`LIBWEB_VERIFY_PREFIX_RELATION` compares every live element's maintained prefix memberships with scalar prefix matching after construction and updates. It also verifies retained membership storage accounting, and runs unconditionally in Rust unit tests.

Verification is **observer-only**: structural checks receive an immutable engine view, while checks that need the exact cold evaluator receive a dedicated verifier capability whose only operations perform comparisons. Neither API exposes cache publication or general engine mutation, and every gate returns unit, so a verifier cannot steer engine behavior. A verifier must never disable or bypass the fast path it is checking, and an incomplete comparison is a failure, not a skip.

## 2. Debugging playbook

**A page styles wrong.**

1. Reduce to a test under `Tests/LibWeb/Text/input/css/style-engine/` if you can; run it under `--verify-style`. If verify mode flags it, the divergence report names the node and the stage (match answer, winner), which usually identifies the responsible mechanism.
2. If verify passes but the page is still wrong, the bug is upstream of the engine (input collection, C++ integration) or downstream (consumers): check that the mutation reached the engine as a typed delta, and that consumers read the published record.
3. The per-mechanism `LIBWEB_VERIFY_*` gates bisect between retained-state mechanisms when the full verify pass is too coarse.

**Performance.** A browser profile shows C++ materialization cost and engine cost side by side. The counters (§16 of StyleEngine.md) are exposed through the internals object (`--expose-internals-object` works on live sites via WebDriver) and answer the first triage question: how many reactions and recomputations did this interaction produce, and how many computed styles changed? A large gap is amplification, and the fix is precision, not micro-optimization. Performance claims are settled by measuring the real workload A/B; counter improvements and synthetic benchmarks are supporting evidence, not the verdict.

**Suspected memory-accounting drift.** Every cache and arena reports exact bytes (invariant 8). Capacity tests assert charges reach zero after eviction.

## 3. Writing tests

* Behavior fixes land with a test that failed before the fix. For engine-internal properties (invalidation reach, recompute counts, retention), assert through the internals hooks rather than timing; timing-based tests are forbidden.
* Structural refactors that claim "no behavior change" are proven by the standard stack, not by new tests that would only assert the refactor's own shape.
* When a test needs the engine to be in a specific retained state (warm caches, retained answers), drive it there through ordinary DOM/CSSOM operations in the test body; tests never reach into engine internals to preload state.

---

--- METRICS ---
- Files Extracted: 44
- Estimated Token Budget: ~140332 tokens
- Canonical Reference: https://codewiki.google/github.com/LadybirdBrowser/ladybird
