2024-01-12 21:25:05 +01:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <LibWeb/DOM/Range.h>
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
#include <LibWeb/HTML/FormAssociatedElement.h>
|
|
|
|
#include <LibWeb/HTML/HTMLInputElement.h>
|
|
|
|
#include <LibWeb/HTML/HTMLTextAreaElement.h>
|
2024-01-12 21:25:05 +01:00
|
|
|
#include <LibWeb/Layout/Viewport.h>
|
|
|
|
#include <LibWeb/Painting/PaintableBox.h>
|
2024-03-18 10:25:57 +01:00
|
|
|
#include <LibWeb/Painting/TextPaintable.h>
|
2024-01-12 21:25:05 +01:00
|
|
|
|
|
|
|
namespace Web::Painting {
|
|
|
|
|
|
|
|
PaintableFragment::PaintableFragment(Layout::LineBoxFragment const& fragment)
|
|
|
|
: m_layout_node(fragment.layout_node())
|
|
|
|
, m_offset(fragment.offset())
|
|
|
|
, m_size(fragment.size())
|
|
|
|
, m_baseline(fragment.baseline())
|
|
|
|
, m_start(fragment.start())
|
|
|
|
, m_length(fragment.length())
|
|
|
|
, m_glyph_run(fragment.glyph_run())
|
2024-10-29 14:29:00 +00:00
|
|
|
, m_writing_mode(fragment.writing_mode())
|
2024-01-12 21:25:05 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
CSSPixelRect const PaintableFragment::absolute_rect() const
|
|
|
|
{
|
|
|
|
CSSPixelRect rect { {}, size() };
|
2024-01-27 14:31:47 +01:00
|
|
|
auto const* containing_block = paintable().containing_block();
|
2024-03-01 15:30:44 +01:00
|
|
|
if (containing_block)
|
|
|
|
rect.set_location(containing_block->absolute_position());
|
2024-01-12 21:25:05 +01:00
|
|
|
rect.translate_by(offset());
|
|
|
|
return rect;
|
|
|
|
}
|
|
|
|
|
2024-11-02 20:09:07 +00:00
|
|
|
int PaintableFragment::text_index_at(CSSPixelPoint position) const
|
2024-01-12 21:25:05 +01:00
|
|
|
{
|
2024-03-18 10:25:57 +01:00
|
|
|
if (!is<TextPaintable>(paintable()))
|
2024-01-12 21:25:05 +01:00
|
|
|
return 0;
|
|
|
|
|
2024-11-02 20:09:07 +00:00
|
|
|
CSSPixels relative_inline_offset = [&]() {
|
|
|
|
switch (orientation()) {
|
|
|
|
case Gfx::Orientation::Horizontal:
|
|
|
|
return position.x() - absolute_rect().x();
|
|
|
|
case Gfx::Orientation::Vertical:
|
|
|
|
return position.y() - absolute_rect().y();
|
|
|
|
default:
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
}
|
|
|
|
}();
|
|
|
|
|
|
|
|
if (relative_inline_offset < 0)
|
2024-01-12 21:25:05 +01:00
|
|
|
return 0;
|
|
|
|
|
2024-09-06 15:10:18 +02:00
|
|
|
auto const& glyphs = m_glyph_run->glyphs();
|
|
|
|
for (size_t i = 0; i < glyphs.size(); ++i) {
|
|
|
|
auto glyph_position = CSSPixels::nearest_value_for(glyphs[i].position.x());
|
|
|
|
if (i + 1 < glyphs.size()) {
|
|
|
|
auto next_glyph_position = CSSPixels::nearest_value_for(glyphs[i + 1].position.x());
|
2024-11-02 20:09:07 +00:00
|
|
|
if (relative_inline_offset >= glyph_position && relative_inline_offset < next_glyph_position)
|
2024-09-06 15:10:18 +02:00
|
|
|
return m_start + i;
|
|
|
|
} else {
|
2024-11-02 20:09:07 +00:00
|
|
|
if (relative_inline_offset >= glyph_position)
|
2024-09-06 15:10:18 +02:00
|
|
|
return m_start + i;
|
|
|
|
}
|
2024-01-12 21:25:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return m_start + m_length;
|
|
|
|
}
|
2024-12-05 17:54:05 +00:00
|
|
|
|
|
|
|
CSSPixelRect PaintableFragment::range_rect(size_t start_offset, size_t end_offset) const
|
2024-01-12 21:25:05 +01:00
|
|
|
{
|
2024-03-18 07:42:38 +01:00
|
|
|
if (paintable().selection_state() == Paintable::SelectionState::None)
|
2024-01-12 21:25:05 +01:00
|
|
|
return {};
|
|
|
|
|
2024-03-18 07:42:38 +01:00
|
|
|
if (paintable().selection_state() == Paintable::SelectionState::Full)
|
2024-01-12 21:25:05 +01:00
|
|
|
return absolute_rect();
|
|
|
|
|
|
|
|
// FIXME: m_start and m_length should be unsigned and then we won't need these casts.
|
|
|
|
auto const start_index = static_cast<unsigned>(m_start);
|
|
|
|
auto const end_index = static_cast<unsigned>(m_start) + static_cast<unsigned>(m_length);
|
|
|
|
|
2024-12-05 17:54:05 +00:00
|
|
|
auto const& font = glyph_run() ? glyph_run()->font() : layout_node().first_available_font();
|
2024-03-18 10:25:57 +01:00
|
|
|
auto text = string_view();
|
2024-01-12 21:25:05 +01:00
|
|
|
|
2024-03-18 07:42:38 +01:00
|
|
|
if (paintable().selection_state() == Paintable::SelectionState::StartAndEnd) {
|
2024-01-12 21:25:05 +01:00
|
|
|
// we are in the start/end node (both the same)
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
if (start_index > end_offset)
|
2024-01-12 21:25:05 +01:00
|
|
|
return {};
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
if (end_index < start_offset)
|
2024-01-12 21:25:05 +01:00
|
|
|
return {};
|
|
|
|
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
if (start_offset == end_offset)
|
2024-01-12 21:25:05 +01:00
|
|
|
return {};
|
|
|
|
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
auto selection_start_in_this_fragment = max(0, start_offset - m_start);
|
|
|
|
auto selection_end_in_this_fragment = min(m_length, end_offset - m_start);
|
2024-01-12 21:25:05 +01:00
|
|
|
auto pixel_distance_to_first_selected_character = CSSPixels::nearest_value_for(font.width(text.substring_view(0, selection_start_in_this_fragment)));
|
|
|
|
auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1;
|
|
|
|
|
|
|
|
auto rect = absolute_rect();
|
2024-11-02 20:09:07 +00:00
|
|
|
switch (orientation()) {
|
|
|
|
case Gfx::Orientation::Horizontal:
|
|
|
|
rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
|
|
|
|
rect.set_width(pixel_width_of_selection);
|
|
|
|
break;
|
|
|
|
case Gfx::Orientation::Vertical:
|
|
|
|
rect.set_y(rect.y() + pixel_distance_to_first_selected_character);
|
|
|
|
rect.set_height(pixel_width_of_selection);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
}
|
2024-01-12 21:25:05 +01:00
|
|
|
|
|
|
|
return rect;
|
|
|
|
}
|
2024-03-18 07:42:38 +01:00
|
|
|
if (paintable().selection_state() == Paintable::SelectionState::Start) {
|
2024-01-12 21:25:05 +01:00
|
|
|
// we are in the start node
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
if (end_index < start_offset)
|
2024-01-12 21:25:05 +01:00
|
|
|
return {};
|
|
|
|
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
auto selection_start_in_this_fragment = max(0, start_offset - m_start);
|
2024-01-12 21:25:05 +01:00
|
|
|
auto selection_end_in_this_fragment = m_length;
|
|
|
|
auto pixel_distance_to_first_selected_character = CSSPixels::nearest_value_for(font.width(text.substring_view(0, selection_start_in_this_fragment)));
|
|
|
|
auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1;
|
|
|
|
|
|
|
|
auto rect = absolute_rect();
|
2024-11-02 20:09:07 +00:00
|
|
|
switch (orientation()) {
|
|
|
|
case Gfx::Orientation::Horizontal:
|
|
|
|
rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
|
|
|
|
rect.set_width(pixel_width_of_selection);
|
|
|
|
break;
|
|
|
|
case Gfx::Orientation::Vertical:
|
|
|
|
rect.set_y(rect.y() + pixel_distance_to_first_selected_character);
|
|
|
|
rect.set_height(pixel_width_of_selection);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
}
|
2024-01-12 21:25:05 +01:00
|
|
|
|
|
|
|
return rect;
|
|
|
|
}
|
2024-03-18 07:42:38 +01:00
|
|
|
if (paintable().selection_state() == Paintable::SelectionState::End) {
|
2024-01-12 21:25:05 +01:00
|
|
|
// we are in the end node
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
if (start_index > end_offset)
|
2024-01-12 21:25:05 +01:00
|
|
|
return {};
|
|
|
|
|
|
|
|
auto selection_start_in_this_fragment = 0;
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
auto selection_end_in_this_fragment = min(end_offset - m_start, m_length);
|
2024-01-12 21:25:05 +01:00
|
|
|
auto pixel_distance_to_first_selected_character = CSSPixels::nearest_value_for(font.width(text.substring_view(0, selection_start_in_this_fragment)));
|
|
|
|
auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1;
|
|
|
|
|
|
|
|
auto rect = absolute_rect();
|
2024-11-02 20:09:07 +00:00
|
|
|
switch (orientation()) {
|
|
|
|
case Gfx::Orientation::Horizontal:
|
|
|
|
rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
|
|
|
|
rect.set_width(pixel_width_of_selection);
|
|
|
|
break;
|
|
|
|
case Gfx::Orientation::Vertical:
|
|
|
|
rect.set_y(rect.y() + pixel_distance_to_first_selected_character);
|
|
|
|
rect.set_height(pixel_width_of_selection);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
}
|
2024-01-12 21:25:05 +01:00
|
|
|
|
|
|
|
return rect;
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2024-10-29 14:29:00 +00:00
|
|
|
Gfx::Orientation PaintableFragment::orientation() const
|
|
|
|
{
|
|
|
|
switch (m_writing_mode) {
|
|
|
|
case CSS::WritingMode::HorizontalTb:
|
|
|
|
return Gfx::Orientation::Horizontal;
|
|
|
|
case CSS::WritingMode::VerticalRl:
|
|
|
|
case CSS::WritingMode::VerticalLr:
|
|
|
|
case CSS::WritingMode::SidewaysRl:
|
|
|
|
case CSS::WritingMode::SidewaysLr:
|
|
|
|
return Gfx::Orientation::Vertical;
|
|
|
|
default:
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-05 17:54:05 +00:00
|
|
|
CSSPixelRect PaintableFragment::selection_rect() const
|
2024-09-16 19:38:14 +08:00
|
|
|
{
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
if (auto const* focused_element = paintable().document().focused_element(); focused_element && is<HTML::FormAssociatedTextControlElement>(*focused_element)) {
|
|
|
|
HTML::FormAssociatedTextControlElement const* text_control_element = nullptr;
|
|
|
|
if (is<HTML::HTMLInputElement>(*focused_element)) {
|
2024-10-31 01:14:55 +01:00
|
|
|
text_control_element = static_cast<HTML::HTMLInputElement const*>(focused_element);
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
} else if (is<HTML::HTMLTextAreaElement>(*focused_element)) {
|
2024-10-31 01:14:55 +01:00
|
|
|
text_control_element = static_cast<HTML::HTMLTextAreaElement const*>(focused_element);
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
} else {
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
}
|
|
|
|
auto selection_start = text_control_element->selection_start();
|
|
|
|
auto selection_end = text_control_element->selection_end();
|
2024-12-27 17:03:16 +00:00
|
|
|
return range_rect(selection_start, selection_end);
|
LibWeb: Separate text control input events handling from contenteditable
This input event handling change is intended to address the following
design issues:
- Having `DOM::Position` is unnecessary complexity when `Selection`
exists because caret position could be described by the selection
object with a collapsed state. Before this change, we had to
synchronize those whenever one of them was modified, and there were
already bugs caused by that, i.e., caret position was not changed when
selection offset was modified from the JS side.
- Selection API exposes selection offset within `<textarea>` and
`<input>`, which is not supposed to happen. These objects should
manage their selection state by themselves and have selection offset
even when they are not displayed.
- `EventHandler` looks only at `DOM::Text` owned by `DOM::Position`
while doing text manipulations. It works fine for `<input>` and
`<textarea>`, but `contenteditable` needs to consider all text
descendant text nodes; i.e., if the cursor is moved outside of
`DOM::Text`, we need to look for an adjacent text node to move the
cursor there.
With this change, `EventHandler` no longer does direct manipulations on
caret position or text content, but instead delegates them to the active
`InputEventsTarget`, which could be either
`FormAssociatedTextControlElement` (for `<input>` and `<textarea>`) or
`EditingHostManager` (for `contenteditable`). The `Selection` object is
used to manage both selection and caret position for `contenteditable`,
and text control elements manage their own selection state that is not
exposed by Selection API.
This change improves text editing on Discord, as now we don't have to
refocus the `contenteditable` element after character input. The problem
was that selection manipulations from the JS side were not propagated
to `DOM::Position`.
I expect this change to make future correctness improvements for
`contenteditable` (and `designMode`) easier, as now it's decoupled from
`<input>` and `<textarea>` and separated from `EventHandler`, which is
quite a busy file.
2024-10-23 21:26:58 +02:00
|
|
|
}
|
2024-09-16 19:38:14 +08:00
|
|
|
auto selection = paintable().document().get_selection();
|
|
|
|
if (!selection)
|
|
|
|
return {};
|
|
|
|
auto range = selection->range();
|
|
|
|
if (!range)
|
|
|
|
return {};
|
|
|
|
|
2024-12-05 17:54:05 +00:00
|
|
|
return range_rect(range->start_offset(), range->end_offset());
|
2024-09-16 19:38:14 +08:00
|
|
|
}
|
|
|
|
|
2024-03-18 10:25:57 +01:00
|
|
|
StringView PaintableFragment::string_view() const
|
|
|
|
{
|
|
|
|
if (!is<TextPaintable>(paintable()))
|
|
|
|
return {};
|
|
|
|
return static_cast<TextPaintable const&>(paintable()).text_for_rendering().bytes_as_string_view().substring_view(m_start, m_length);
|
|
|
|
}
|
|
|
|
|
2024-01-12 21:25:05 +01:00
|
|
|
}
|