Commit Graph

27 Commits

Author SHA1 Message Date
Jelle Raaijmakers
d5173fe6ca LibWeb: Inflate range rect by font ascenders/descenders
Chrome and Firefox inflate this rect to accommodate for the font's
ascenders and descenders, while the absolute rect for the fragment
remains unaffected. This fixes ascenders/descenders in text being
clipped when selecting text.
2026-02-17 10:51:48 +01:00
Jelle Raaijmakers
819bfa11ec LibWeb: Simplify primary offset translation in ::range_rect()
No functional changes.
2026-02-17 10:51:48 +01:00
Jelle Raaijmakers
8b32456473 LibWeb: Use explicit CSSPixels(float) constructor in ::range_rect()
No functional changes.
2026-02-17 10:51:48 +01:00
Jelle Raaijmakers
f831534589 LibWeb: Add explanatory comment to ::range_rect() on trailing whitespace 2026-02-17 10:51:48 +01:00
Jelle Raaijmakers
d2ad917fbc LibWeb: Simplify PaintableFragment::absolute_rect()
We can pass in offset() immediately. No functional changes.
2026-02-17 10:51:48 +01:00
Jelle Raaijmakers
8654e2caf4 LibWeb: Apply letter-spacing for selection rects and grapheme bounds
Fixes the selection rect on the title text of https://modern-css.com/
being misaligned with the actual characters.
2026-02-17 10:51:48 +01:00
Aliaksandr Kalenik
b41ed92505 LibWeb: Remove Document.h include from Layout/Viewport.h
Move the inline dom_node() method to Viewport.cpp so the header no
longer needs the full Document definition. Add explicit includes to
files that relied on the transitive dependency.
2026-02-08 18:51:13 +01:00
Jelle Raaijmakers
98b4feb406 LibWeb: Apply CSS ::selection styling to text and form controls
Previously, text selection always used the system highlight color. This
implements support for the ::selection pseudo-element's background-color
and color properties.

For form controls like <input> and <textarea>, the selection style is
looked up on the shadow host element, since the actual text lives inside
their shadow DOM.

The text painting logic has been refactored to split fragments into
styled spans (before selection, selected, after selection) so that each
portion can be rendered with its appropriate colors, taking care not to
allocate in 99%+ of fragment rendering cases.
2026-02-06 10:47:50 +00:00
Jelle Raaijmakers
3d777a8d04 LibWeb: Simplify PaintableFragment::range_rect()
Some of the logic in ::range_rect() was duplicated, and we did not
optimally utilize the early returns. Rearrange the code so we do the
expensive font metrics stuff as late as possible.
2026-02-06 10:47:50 +00:00
Jelle Raaijmakers
69fa144539 LibWeb: Keep track of trailing whitespace for wrapped lines
When rendering selections, we want to extend the selection rect for
wrapped lines to show that there is whitespace present. We don't
actually store this whitespace in the fragments; it's purely a visual
clue.

This reflects how both Chrome and Firefox deal with selection ranges
over wrapped lines.
2026-02-06 10:47:50 +00:00
Jelle Raaijmakers
a048355e66 LibWeb: Prevent overlapping selection rects
We were always extending selection rects by 1, which caused some
unnecessary overlap between adjacent selections. This also happened
vertically between lines, because we were ceil()ing instead of rounding.
This makes the selection rects look much nicer.
2026-02-06 10:47:50 +00:00
Jelle Raaijmakers
54d95fc766 LibWeb: Unify selection and cursor rects for painting
Instead of calculating the cursor rect separately, reuse
PaintableFragment::range_rect() and check for the selection state of
'None' where appropriate.
2025-12-08 20:12:23 +01:00
Jelle Raaijmakers
9e9db9a9dd LibWeb: Store correct text offsets in PaintableFragment
Previously, we were collapsing whitespace in Layout::TextNode and then
passed the resulting string for further processing through ChunkIterator
-> InlineLevelIterator -> InlineFormattingContext -> LineBuilder ->
LineBoxFragment -> PaintableFragment. Our painting tree is where we deal
with things like range offsets into the underlying text nodes, but since
we modified the original string, the offsets were wrong.

This changes the way we generate fragments:

  * Layout::TextNode no longer collapses whitespace as part of its
    stored "text for rendering", but moves this logic to ChunkIterator
    which splits up this text into separate views whenever whitespace
    needs to be collapsed.

  * Layout::LineBox now only extends the last fragment if its end offset
    is equal to the new fragment's start offset. Otherwise, there's a
    gap caused by collapsing whitespace and we need to generate a
    separate fragment for that in order to have a correct start offset.

Some tests need new baselines because of the fixed start offsets.

Fixes #566.
2025-09-12 15:34:09 -04:00
Jelle Raaijmakers
d1076c1e6e LibWeb: Remove text storage from TextPaintable
Instead of copying the layout node's text, retrieve the text from the
layout node directly.
2025-09-12 15:34:09 -04:00
Jelle Raaijmakers
518c048eb4 LibWeb+WebContent: Rename Document::focused_element to ::focused_area
And make it a DOM::Node, not DOM::Element. This makes everything flow
much better, such as spec texts that explicitly mention "focused area"
as the fact that we don't necessarily need to traverse a tree of
elements, since a Node can be focusable as well.

Eventually this will need to be a struct with a separate "focused area"
and "DOM anchor", but this change will make it easier to achieve that.
2025-08-26 10:25:59 +02:00
Timothy Flynn
1f88e6819a LibWeb: Ensure hit testing is grapheme aware
Previously, clicking in the middle of a multi-code point grapheme would
place the cursor at a code unit index somewhere in the middle of the
grapheme. This was not only visually misleading, but the user could then
start typing and insert characters in the middle of the cluster. This
also made text select pretty wonky.

The main issue was that we were treating the glyph index in a glyph run
as a code unit index. We must instead map that glyph index back to a
code unit index with help from LibGfx (via harfbuzz).

The distance computation used here was also a bit off, especially for
the last glyph in a glyph run. We essentially want the cursor to end
up on whichever edge of the clicked glyph it is closest to. The result
of the distance computation limited us to the left edge of the last
glyph. Instead, we can use the same edge tracking we use for form-
associated elements to handle this for us.
2025-08-22 14:06:46 +02:00
Timothy Flynn
97548f48de LibWeb: Port rendered text to UTF-16
This migrates TextNode::text_for_rendering() to Utf16String and deals
with the fallout. As a consequence, this effectively reverts commit
3df83dade8.

The layout test expecation file updates are because we now express text
lengths and offsets in UTF-16 code units.

The selection-over-multiple-code-units expectation file update actually
represents a correctness fix. Our result now matches Firefox.
2025-07-25 18:16:22 +02:00
Timothy Flynn
9582895759 AK+LibJS+LibWeb+LibRegex: Replace AK::Utf16Data with AK::Utf16String 2025-07-18 12:45:38 -04:00
Aliaksandr Kalenik
5874b7a76f LibWeb: Skip update_associated_selection() when there's no selection
This change fixes at least two issues:
- `update_associated_selection()` is responsible for selectionchange
  dispatch, and we were incorrectly dispatching this event on ranges
  that were not associated with a selection.
- `Range::get_client_rects()` was using `update_associated_selection()`
  to refresh the selection state in the paintable tree for the current
  range, but since a range might not be associated with a selection,
  this could make the painted selection reflect the state of an
  arbitrary range instead of the actual selection range.

Fixes a bug on Discord where any text typed into the message input would
get selected.
2025-07-03 22:16:39 +02:00
Jelle Raaijmakers
7beecaa43d LibWeb: Rename PaintableFragment::m_start and ::m_length
Make it extra clear that we're dealing with byte offsets here. No
functional changes.
2025-06-13 15:08:26 +02:00
Jelle Raaijmakers
3df83dade8 LibWeb: Treat DOM::Range offsets as UTF-16 code unit offsets
We generated `PaintableFragment`s with a start and length represented in
UTF-8 byte offsets, but failed to consider that the offsets in a
`DOM::Range` are actually expressed in UTF-16 code units.

This is a bit of a mess: almost all web specs use UTF-16 code units as
the unit for indexing into text nodes, but we almost exclusively use
UTF-8 in our code base. Arguably the best thing would for us to use
UTF-16 everywhere as well: it prevents these mismatches in our
implementations for the price of a bit more memory usage - and even that
could potentially be optimized for.

But for now, try to do the correct thing and lazily allocate UTF-16 data
in a `PaintableFragment` whenever we need to index into it or if we're
asked to determine the code unit offset of a pixel position.
2025-06-13 15:08:26 +02:00
Jelle Raaijmakers
dbbdf2cebc LibWeb: Improve caret positioning behavior inside fragments
When clicking on a glyph or starting a selection on it, we would use the
glyph's offset/index as the position which represents the left side of
the glyph, or the position between the glyph and the glyph before it.

Instead of looking at which glyph is under the mouse pointer, look at
which glyph boundary is closer. Now, if you click to the right of a
glyph (but still on that glyph), it correctly selects the next glyph's
offset as the position.
2025-06-13 15:08:26 +02:00
Jelle Raaijmakers
9126507dc6 LibWeb: Make fragment start/length size_t instead of int
These must always be unsigned. No functional changes.
2025-06-13 15:08:26 +02:00
Tim Ledbetter
db24440403 LibWeb: Allow keyboard input to alter email inputs
Previously, the`HTMLInputElement.selectinStart` and
`HTMLInputElement.selectionEnd` IDL setters, and the
`setRangeText()` IDL method were used when updating an input's value
on keyboard input. These methods can't be used for this purpose,
since selection doesn't apply to email type inputs. Therefore, this
change introduces internal-use only methods that don't check whether
selection applies to the given input.
2024-12-30 11:04:16 +01:00
Sam Atkins
e457252c97 LibWeb/Painting: Use GlyphRun font for measuring selection rectangle
We incorrectly used the first available font to measure this before,
which may or may not be the correct font for this text.
2024-12-06 02:57:34 +01:00
Aliaksandr Kalenik
d7caa426a0 LibWeb: Delete m_selected flag from Paintable
This was redundant when Paintable already has `m_selection_state` that
could be none.
2024-11-14 19:48:43 +01:00
Timothy Flynn
93712b24bf Everywhere: Hoist the Libraries folder to the top-level 2024-11-10 12:50:45 +01:00