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.
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.
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.
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.
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.
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.
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.
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.