LibWeb: Move selection state from layout tree to paint tree

Where we paint the selection is obviously paint-related information,
so let's keep it in the paint tree.
This commit is contained in:
Andreas Kling 2024-03-18 07:42:38 +01:00
parent d18a5b904d
commit 1987318cc2
Notes: sideshowbarker 2024-07-17 11:29:41 +09:00
9 changed files with 100 additions and 98 deletions

View File

@ -1110,7 +1110,7 @@ void Document::update_layout()
page().client().page_did_layout();
}
m_layout_root->recompute_selection_states();
paintable()->recompute_selection_states();
m_needs_layout = false;
m_layout_update_timer->stop();

View File

@ -25,6 +25,7 @@
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Namespace.h>
#include <LibWeb/Painting/Paintable.h>
#include <LibWeb/Painting/ViewportPaintable.h>
namespace Web::DOM {
@ -97,9 +98,9 @@ void Range::update_associated_selection()
{
if (!m_associated_selection)
return;
if (auto* layout_root = m_associated_selection->document()->layout_node(); layout_root && layout_root->paintable()) {
layout_root->recompute_selection_states();
layout_root->paintable()->set_needs_display();
if (auto* viewport = m_associated_selection->document()->paintable()) {
viewport->recompute_selection_states();
viewport->set_needs_display();
}
// https://w3c.github.io/selection-api/#selectionchange-event

View File

@ -158,17 +158,6 @@ public:
bool children_are_inline() const { return m_children_are_inline; }
void set_children_are_inline(bool value) { m_children_are_inline = value; }
enum class SelectionState {
None, // No selection
Start, // Selection starts in this Node
End, // Selection ends in this Node
StartAndEnd, // Selection starts and ends in this Node
Full, // Selection starts before and ends after this Node
};
SelectionState selection_state() const { return m_selection_state; }
void set_selection_state(SelectionState state) { m_selection_state = state; }
u32 initial_quote_nesting_level() const { return m_initial_quote_nesting_level; }
void set_initial_quote_nesting_level(u32 value) { m_initial_quote_nesting_level = value; }
@ -190,7 +179,6 @@ private:
bool m_anonymous { false };
bool m_has_style { false };
bool m_children_are_inline { false };
SelectionState m_selection_state { SelectionState::None };
bool m_is_flex_item { false };
bool m_is_grid_item { false };

View File

@ -20,77 +20,6 @@ Viewport::Viewport(DOM::Document& document, NonnullRefPtr<CSS::StyleProperties>
Viewport::~Viewport() = default;
JS::GCPtr<Selection::Selection> Viewport::selection() const
{
return const_cast<DOM::Document&>(document()).get_selection();
}
void Viewport::recompute_selection_states()
{
// 1. Start by resetting the selection state of all layout nodes to None.
for_each_in_inclusive_subtree([&](auto& layout_node) {
layout_node.set_selection_state(SelectionState::None);
return IterationDecision::Continue;
});
// 2. If there is no active Selection or selected Range, return.
auto selection = document().get_selection();
if (!selection)
return;
auto range = selection->range();
if (!range)
return;
auto* start_container = range->start_container();
auto* end_container = range->end_container();
// 3. If the selection starts and ends in the same node:
if (start_container == end_container) {
// 1. If the selection starts and ends at the same offset, return.
if (range->start_offset() == range->end_offset()) {
// NOTE: A zero-length selection should not be visible.
return;
}
// 2. If it's a text node, mark it as StartAndEnd and return.
if (is<DOM::Text>(*start_container)) {
if (auto* layout_node = start_container->layout_node()) {
layout_node->set_selection_state(SelectionState::StartAndEnd);
}
return;
}
}
if (start_container == end_container && is<DOM::Text>(*start_container)) {
if (auto* layout_node = start_container->layout_node()) {
layout_node->set_selection_state(SelectionState::StartAndEnd);
}
return;
}
// 4. Mark the selection start node as Start (if text) or Full (if anything else).
if (auto* layout_node = start_container->layout_node()) {
if (is<DOM::Text>(*start_container))
layout_node->set_selection_state(SelectionState::Start);
else
layout_node->set_selection_state(SelectionState::Full);
}
// 5. Mark the selection end node as End (if text) or Full (if anything else).
if (auto* layout_node = end_container->layout_node()) {
if (is<DOM::Text>(*end_container))
layout_node->set_selection_state(SelectionState::End);
else
layout_node->set_selection_state(SelectionState::Full);
}
// 6. Mark the nodes between start node and end node (in tree order) as Full.
for (auto* node = start_container->next_in_pre_order(); node && node != end_container; node = node->next_in_pre_order()) {
if (auto* layout_node = node->layout_node())
layout_node->set_selection_state(SelectionState::Full);
}
}
JS::GCPtr<Painting::Paintable> Viewport::create_paintable() const
{
return Painting::ViewportPaintable::create(*this);

View File

@ -8,7 +8,6 @@
#include <LibWeb/DOM/Document.h>
#include <LibWeb/Layout/BlockContainer.h>
#include <LibWeb/Selection/Selection.h>
namespace Web::Layout {
@ -21,10 +20,6 @@ public:
const DOM::Document& dom_node() const { return static_cast<const DOM::Document&>(*Node::dom_node()); }
JS::GCPtr<Selection::Selection> selection() const;
void recompute_selection_states();
private:
virtual JS::GCPtr<Painting::Paintable> create_paintable() const override;

View File

@ -206,6 +206,17 @@ public:
CSSPixelPoint box_type_agnostic_position() const;
enum class SelectionState : u8 {
None, // No selection
Start, // Selection starts in this Node
End, // Selection ends in this Node
StartAndEnd, // Selection starts and ends in this Node
Full, // Selection starts before and ends after this Node
};
SelectionState selection_state() const { return m_selection_state; }
void set_selection_state(SelectionState state) { m_selection_state = state; }
protected:
explicit Paintable(Layout::Node const&);
@ -219,6 +230,8 @@ private:
OwnPtr<StackingContext> m_stacking_context;
SelectionState m_selection_state { SelectionState::None };
bool m_visible : 1 { false };
bool m_positioned : 1 { false };
bool m_fixed_position : 1 { false };

View File

@ -61,16 +61,16 @@ int PaintableFragment::text_index_at(CSSPixels x) const
CSSPixelRect PaintableFragment::selection_rect(Gfx::Font const& font) const
{
if (layout_node().selection_state() == Layout::Node::SelectionState::None)
if (paintable().selection_state() == Paintable::SelectionState::None)
return {};
if (layout_node().selection_state() == Layout::Node::SelectionState::Full)
if (paintable().selection_state() == Paintable::SelectionState::Full)
return absolute_rect();
if (!is<Layout::TextNode>(layout_node()))
return {};
auto selection = layout_node().root().selection();
auto selection = paintable().document().get_selection();
if (!selection)
return {};
auto range = selection->range();
@ -84,7 +84,7 @@ CSSPixelRect PaintableFragment::selection_rect(Gfx::Font const& font) const
auto& layout_text = verify_cast<Layout::TextNode>(layout_node());
auto text = layout_text.text_for_rendering().bytes_as_string_view().substring_view(m_start, m_length);
if (layout_node().selection_state() == Layout::Node::SelectionState::StartAndEnd) {
if (paintable().selection_state() == Paintable::SelectionState::StartAndEnd) {
// we are in the start/end node (both the same)
if (start_index > range->end_offset())
return {};
@ -105,7 +105,7 @@ CSSPixelRect PaintableFragment::selection_rect(Gfx::Font const& font) const
return rect;
}
if (layout_node().selection_state() == Layout::Node::SelectionState::Start) {
if (paintable().selection_state() == Paintable::SelectionState::Start) {
// we are in the start node
if (end_index < range->start_offset())
return {};
@ -121,7 +121,7 @@ CSSPixelRect PaintableFragment::selection_rect(Gfx::Font const& font) const
return rect;
}
if (layout_node().selection_state() == Layout::Node::SelectionState::End) {
if (paintable().selection_state() == Paintable::SelectionState::End) {
// we are in the end node
if (start_index > range->end_offset())
return {};

View File

@ -4,11 +4,13 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/DOM/Range.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Painting/SVGPaintable.h>
#include <LibWeb/Painting/SVGSVGPaintable.h>
#include <LibWeb/Painting/StackingContext.h>
#include <LibWeb/Painting/ViewportPaintable.h>
#include <LibWeb/Selection/Selection.h>
namespace Web::Painting {
@ -440,4 +442,75 @@ void ViewportPaintable::resolve_paint_only_properties()
});
}
JS::GCPtr<Selection::Selection> ViewportPaintable::selection() const
{
return const_cast<DOM::Document&>(document()).get_selection();
}
void ViewportPaintable::recompute_selection_states()
{
// 1. Start by resetting the selection state of all layout nodes to None.
for_each_in_inclusive_subtree([&](auto& layout_node) {
layout_node.set_selection_state(SelectionState::None);
return TraversalDecision::Continue;
});
// 2. If there is no active Selection or selected Range, return.
auto selection = document().get_selection();
if (!selection)
return;
auto range = selection->range();
if (!range)
return;
auto* start_container = range->start_container();
auto* end_container = range->end_container();
// 3. If the selection starts and ends in the same node:
if (start_container == end_container) {
// 1. If the selection starts and ends at the same offset, return.
if (range->start_offset() == range->end_offset()) {
// NOTE: A zero-length selection should not be visible.
return;
}
// 2. If it's a text node, mark it as StartAndEnd and return.
if (is<DOM::Text>(*start_container)) {
if (auto* paintable = start_container->paintable()) {
paintable->set_selection_state(SelectionState::StartAndEnd);
}
return;
}
}
if (start_container == end_container && is<DOM::Text>(*start_container)) {
if (auto* paintable = start_container->paintable()) {
paintable->set_selection_state(SelectionState::StartAndEnd);
}
return;
}
// 4. Mark the selection start node as Start (if text) or Full (if anything else).
if (auto* paintable = start_container->paintable()) {
if (is<DOM::Text>(*start_container))
paintable->set_selection_state(SelectionState::Start);
else
paintable->set_selection_state(SelectionState::Full);
}
// 5. Mark the selection end node as End (if text) or Full (if anything else).
if (auto* paintable = end_container->paintable()) {
if (is<DOM::Text>(*end_container))
paintable->set_selection_state(SelectionState::End);
else
paintable->set_selection_state(SelectionState::Full);
}
// 6. Mark the nodes between start node and end node (in tree order) as Full.
for (auto* node = start_container->next_in_pre_order(); node && node != end_container; node = node->next_in_pre_order()) {
if (auto* paintable = node->paintable())
paintable->set_selection_state(SelectionState::Full);
}
}
}

View File

@ -30,6 +30,9 @@ public:
void resolve_paint_only_properties();
JS::GCPtr<Selection::Selection> selection() const;
void recompute_selection_states();
private:
void build_stacking_context_tree();