Stop converting between CSS and device pixels as part of rendering - the
display list should be as simple as possible, so convert to DevicePixels
once when constructing the display list.
These IPC methods should be expanded in the future to allow WebContent
to specify what UI elements should be kept/removed, for example, the
navigation UI.
Instead of using a custom paintable to draw the controls for video and
audio elements, we build them out of plain old HTML elements within a
shadow root.
This required a few hacks in the previous commits in order to allow a
replaced element to host children within a shadow root, but it's
fairly self-contained.
A big benefit is that we can drive all the UI updates off of plain old
DOM events (except the play button overlay on videos, which uses the
video element representation), so we can test our media and input event
handling more thoroughly. :^)
The control bar visibility is now more similar to how other browsers
handle it. It will show upon hovering over the element, but if the
cursor is kept still for more than a second, it will hide again. While
dragging, the controls remain visible, and will then hide after the
mouse button is released.
The icons have been redesigned from scratch, and the mute icon now
visualizes the volume level along with indicating the mute state.
present() now snapshots the PaintingSurface into an ImmutableBitmap
and publishes it to the ExternalContentSource, so the rendering thread
never touches the live GPU surface — eliminating the data race
described in the ExternalContentSource commit (problem 1).
Canvas elements are registered with Page and presented once per frame
from the event loop, rather than on every individual draw call in
CRC2D::did_draw(). A dirty flag on HTMLCanvasElement ensures the
snapshot is only taken when content has actually changed, and makes
the present() call in CanvasPaintable::paint() a no-op when the
surface has already been snapshotted for the current frame.
We were previously not allowing the user to select text when the clicked
position represented a click-focusable area. This included text within
dialogs (as the dialog element is click-focusable) and labels (as the
associated input element would be considered click-focusable).
We now no longer consider associated input elements when clicking on a
label. This is handled separately already by the label's activation
behavior steps, so there is no loss of functionality here. In those
steps, though, we now no longer propagate the click event to the input
element if a selection was made during the click. This matches the
behavior of Firefox and Chrome.
With label elements no longer considered here, we can then enter the
character selection mode when click-focusable areas are clicked.
We were using a separately fired timer for auto scrolling ticks, but it
makes more sense to tie this into the rendering steps of which
`::run_the_scroll_steps()` is a part. Should fix the flaky
`Text/input/viewport-auto-scroll.html` test.
Fixes#7939.
Remove includes from Node.h that are only needed for forward
declarations (AccessibilityTreeNode.h, XMLSerializer.h,
JsonObjectSerializer.h). Extract StyleInvalidationReason and
FragmentSerializationMode enums into standalone lightweight
headers so downstream headers (CSSStyleSheet.h, CSSStyleProperties.h,
HTMLParser.h) can include just the enum they need instead of all of
Node.h. Replace Node.h with forward declarations in headers that only
use Node by pointer/reference.
This breaks the circular dependency between Node.h and
AccessibilityTreeNode.h, reducing AccessibilityTreeNode.h's
recompilation footprint from ~1399 to ~25 files.
Remove 11 heavy includes from Document.h that were only needed for
pointer/reference types (already forward-declared in Forward.h), and
extract the nested ViewportClient interface to a standalone header.
This reduces Document.h's recompilation cascade from ~1228 files to
~717 files (42% reduction). Headers like BrowsingContext.h that were
previously transitively included see even larger improvements (from
~1228 down to ~73 dependents).
When the mouse is dragged from inside a scrollable container to outside
of it, we now automatically scroll the container so the selection can be
extended. Scroll speed scales with the distance past the scrollport
edge, capped at a maximum. Edges close to the viewport boundary get a
wider activation zone so the speed ramp works predictably even when the
mouse has limited room to move.
The logic is encapsulated in AutoScrollHandler, which EventHandler
creates lazily on mouse selection start.
When editing or changing the selection inside an <input> or <textarea>,
we should scroll the container so the cursor is always visible. Note
that currently the cursor might still become invisible at the end of the
container since we do not reserve enough space for it to be made
visible.
When triple clicking on text, we should select the entire paragraph, or
entire line in <input>s and <textarea>s. If the mouse button is held
down and the user starts dragging, the selection expands with additional
paragraphs or lines.
This expands on the work of Kai Wildberger (PR #7681) but was adjusted
for the work that happened previously to support double click + drag
moves and includes triple click support for our Qt UI.
Co-authored-by: Kai Wildberger <kiawildberger@gmail.com>
In EventHandler, we now keep track of a mouse selection mode which is
either None, Character or Word. By double clicking a word and
immediately dragging, you can now extend the selection word by word
instead of by character.
If our UI informed the page of a DPI change, we would store the new
device pixel ratio and leave it at that. It would take a layout/style
update (e.g. by clicking the page) to actually render the page using the
new DPI. This is very visible on macOS when moving the Ladybird window
from a 1x resolution monitor to a HiDPI 2x monitor.
We now instantly update the backing stores and mark media queries for
reevaluation. Moving the Ladybird window on macOS now immediately
updates the page when dragging it to a HiDPI monitor.
This patch introduces a cookie cache in the WebContent process to reduce
blocking IPC calls when JS accesses document.cookie. The UI process now
maintains a cookie version counter per-domain in shared memory. When JS
reads document.cookie, we check whether we have a valid cached cookie by
comparing the current shared version to the last used version. If they
match, the cached cookie is returned without IPC.
This optimization is based on Chromium's shared versioning, in which it
was observed that 87% of document.cookie accesses were redundant. See:
https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html
Note that this cache only supports document.cookie, not HTTP Cookie
headers. HTTP cookies are attached to requests with varying URLs and
paths. The cookies that match the document URL might not match the
request URL, which we wouldn't know from WebContent. So attaching the
cached document cookie would be incorrect.
On https://twinings.co.uk, we see approximately 600 document.cookie
requests while the page loads. This patch reduces the time spent in
the document.cookie getter from ~45ms to 2-3ms.
The AccumulatedVisualContext tree already handles the visual viewport
transform for hit testing (via transform_point_for_hit_test) and offset
computation (via inverse_transform_point). Passing viewport_position
(which has map_to_layout_viewport already applied) to these functions
caused a double-inversion during pinch-to-zoom.
compute_mouse_event_offset() only inverted the target element's own CSS
transform, ignoring ancestor transforms. This caused incorrect offsetX/
offsetY values when elements were nested inside transformed parents.
Use AccumulatedVisualContext::inverse_transform_point() to invert the
full ancestor transform chain instead.
Previously, click handling for labels was handled in layout and
painting code. This change implements activation_behavior on
HTMLLabelElement, which clicks and focuses the element.
Add webdriver_key_to_key_code() in Internals.cpp to properly translate
WebDriver special key codes (0xE000-0xE05D) to KeyCode values with
appropriate modifiers. This ensures keys like Enter, Backspace, and
arrow keys are handled correctly when sent via Internals::send_text().
In EventHandler::handle_keydown(), strip Mod_Keypad when determining
Enter key behavior since it only indicates key location (numpad vs
standard keyboard), not a behavior change. The modifier is still passed
through to KeyboardEvent for the location property.
This gains us 656 WPT subtest passes in `editing`.
When inserting a line break in a contenteditable with preformatted
white-space (pre, pre-line, pre-wrap), insert a newline character (\n)
instead of a <br> element. Use <br> only for padding at end of line to
ensure the cursor can be placed on the new line.
We had skipped some steps in the spec and were:
* Always broadcasting an old value of null, instead of what it
actually was previously.
* Still broadcasting a storage event even if the value had
not changed in storage compared to the last value.
Fix both issues by returning what the old value is in the setter and
implementing the missing logic.
When delegating mouse events to iframes, coordinate transformations
were not accounting for scroll offsets. This caused clicks inside
iframes to be incorrectly positioned when the parent page was scrolled.
- Add WindowManagement to PolicyControlledFeature enum
- Add screen_count() virtual method to PageClient
- Store all screen rects in WebContent::PageClient, derive both
screen_rect() and screen_count() from stored data
- Implement screen_count() overrides in SVGPageClient and PageHost
- Replace FIXME stub in Screen.cpp with spec-compliant implementation
Add support for WPT test variants, which allow a single test file to be
run multiple times with different URL query parameters. Tests declare
variants using `<meta name="variant" content="?param=value">` tags.
When test-web encounters a test with variants, it expands that test into
multiple runs, each with its own expectation file using the naming
convention `testname@variant.txt` (e.g., `test@run_type=uri.txt`).
Implementation details:
- WebContent observes variant meta tags and communicates them to the
test runner via a new `did_receive_test_variant_metadata` IPC call
- test-web dynamically expands tests with variants during execution,
waking idle views after each test completion to pick up new work
- Use index-based test tracking to avoid dangling references when the
test vector grows during variant expansion
- Introduce TestRunContext to group test run state, and store a static
pointer to it for signal handler access
This enables proper testing of WPT tests that use variants, such as the
html5lib parsing tests (which test uri, write, and write_single modes)
and the editing/bold tests (which split across multiple ranges).
Propagate the request initiator type (e.g., "xmlhttprequest", "fetch",
"script", "stylesheet") from LibWeb through the IPC layer to DevTools.
This enables Firefox DevTools to correctly identify XHR/fetch requests
and display appropriate cause types in the Network panel's "Initiator"
column.
This adds support for viewing request payloads (POST data) and response
bodies in the Firefox DevTools network panel.
Request bodies are captured when network requests start and passed
through IPC to the NetworkEventActor, which returns them via the
getRequestPostData protocol method.
Response bodies are streamed via a new IPC message as data is received,
accumulated in NetworkEventActor (with a 10MB size limit to prevent
memory issues), and returned via getResponseContent. Text content is
returned as UTF-8, while binary content (images, etc.) is base64.
Hook ResourceLoader to emit network request lifecycle events through
IPC to the UI process, where FrameActor creates NetworkEventActor
instances that serialize requests using Firefox's Remote Debug Protocol.
The Network panel now shows requests with method, URL, status, MIME
type, size, and timing information. Several features remain stubbed
(POST data, response content, cause detection) marked with FIXMEs.
Add ElementResizeAction to Page (maybe there's a better place). It's
just a mousemove delegate that updates styles on the target element.
Add ChromeMetrics for zoom-invariant chrome like scrollbar thumb
thickness, resize gripper size, paddings, etc. It's not user-stylable
but separates basic concerns in a way that a visually gifted
designer unlike myself can adjust to taste.
These values are pre-divided by zoom factor so that PaintableBox can
continue using device_pixels_per_css_pixel calls as normal.
The adjusted metrics are computed on demand from Page multiple times
per paint cycle, which is not ideal but avoids lifetime management and
atomics. Maybe someone with more surety about the painting flow control
can improve this, but it won't be a huge win. If profiling shows
this slowing paints, then Ladybird is in good shape.
Update PaintableBox to draw the resize gripper and deconflict
the scrollbars. Set apropriate cursors for scrollbars and gripper in
mousemove. We override EventHandler's cursor handling because nothing
should ever come between a man and his resize gripper.
Chrome metrics use the CSSPixels class. This is good because it's
broadly compatible but bad because they're actually different units
when zoom is not 1.0. If that's a problem, we could make a new type
or just use double.
Model "viewport focus" with Document::focused_area == nullptr.
Focus.cpp:
When a blur occurs, remove the document entry from the old chain
before running the focusing steps. This ensures the document atop the
new chain is not discarded. Focus::run_focus_update_steps will then
set focused_area to nullptr, indicating viewport focus.
EventHandler.cpp:
Split hit testing in handle_mousedown. Use an exact hit test for
focus/blur decisions, and a subsequent cursor hit test for
selection/caret.
A version of this was added in a610639119
and reverted in 70671b4c11. The bugs
there (confusing scroll-to-position and scroll-by-delta, and not having
an execution context in some cases) have been fixed in this version.
This adds visit_edges(Cell::Visitor&) methods to various helper structs
that contain GC pointers, and makes sure they are called from owning
GC-heap-allocated objects as needed.
These were found by our Clang plugin after expanding its capabilities.
The added rules will be enforced by CI going forward.
Fixes crashing introduced in a610639 when `scroll_viewport_by_delta()`
is called from `EventHandler::handle_mousewheel()` and there's no
running execution context to grab current realm from to allocate a
promise.