From 1987318cc25ff01de339e9f5a419212b78d37212 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 18 Mar 2024 07:42:38 +0100 Subject: [PATCH] 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. --- Userland/Libraries/LibWeb/DOM/Document.cpp | 2 +- Userland/Libraries/LibWeb/DOM/Range.cpp | 7 +- Userland/Libraries/LibWeb/Layout/Node.h | 12 --- Userland/Libraries/LibWeb/Layout/Viewport.cpp | 71 ------------------ Userland/Libraries/LibWeb/Layout/Viewport.h | 5 -- .../Libraries/LibWeb/Painting/Paintable.h | 13 ++++ .../LibWeb/Painting/PaintableFragment.cpp | 12 +-- .../LibWeb/Painting/ViewportPaintable.cpp | 73 +++++++++++++++++++ .../LibWeb/Painting/ViewportPaintable.h | 3 + 9 files changed, 100 insertions(+), 98 deletions(-) diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 6af8fe3eccb..aa8c4b9e158 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -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(); diff --git a/Userland/Libraries/LibWeb/DOM/Range.cpp b/Userland/Libraries/LibWeb/DOM/Range.cpp index fdfd78e688b..ad20a68faca 100644 --- a/Userland/Libraries/LibWeb/DOM/Range.cpp +++ b/Userland/Libraries/LibWeb/DOM/Range.cpp @@ -25,6 +25,7 @@ #include #include #include +#include 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 diff --git a/Userland/Libraries/LibWeb/Layout/Node.h b/Userland/Libraries/LibWeb/Layout/Node.h index fe6c37bc7d2..3d5a5e7a9f7 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.h +++ b/Userland/Libraries/LibWeb/Layout/Node.h @@ -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 }; diff --git a/Userland/Libraries/LibWeb/Layout/Viewport.cpp b/Userland/Libraries/LibWeb/Layout/Viewport.cpp index 9db6562cbaf..3825840b046 100644 --- a/Userland/Libraries/LibWeb/Layout/Viewport.cpp +++ b/Userland/Libraries/LibWeb/Layout/Viewport.cpp @@ -20,77 +20,6 @@ Viewport::Viewport(DOM::Document& document, NonnullRefPtr Viewport::~Viewport() = default; -JS::GCPtr Viewport::selection() const -{ - return const_cast(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(*start_container)) { - if (auto* layout_node = start_container->layout_node()) { - layout_node->set_selection_state(SelectionState::StartAndEnd); - } - return; - } - } - - if (start_container == end_container && is(*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(*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(*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 Viewport::create_paintable() const { return Painting::ViewportPaintable::create(*this); diff --git a/Userland/Libraries/LibWeb/Layout/Viewport.h b/Userland/Libraries/LibWeb/Layout/Viewport.h index cf9f450764c..dc235d3d3a1 100644 --- a/Userland/Libraries/LibWeb/Layout/Viewport.h +++ b/Userland/Libraries/LibWeb/Layout/Viewport.h @@ -8,7 +8,6 @@ #include #include -#include namespace Web::Layout { @@ -21,10 +20,6 @@ public: const DOM::Document& dom_node() const { return static_cast(*Node::dom_node()); } - JS::GCPtr selection() const; - - void recompute_selection_states(); - private: virtual JS::GCPtr create_paintable() const override; diff --git a/Userland/Libraries/LibWeb/Painting/Paintable.h b/Userland/Libraries/LibWeb/Painting/Paintable.h index d773e45be3d..f62cf1dea35 100644 --- a/Userland/Libraries/LibWeb/Painting/Paintable.h +++ b/Userland/Libraries/LibWeb/Painting/Paintable.h @@ -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 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 }; diff --git a/Userland/Libraries/LibWeb/Painting/PaintableFragment.cpp b/Userland/Libraries/LibWeb/Painting/PaintableFragment.cpp index 9cfe0ecf5d1..7d989d25572 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableFragment.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableFragment.cpp @@ -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_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_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 {}; diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp index a848d9a380d..d6e102c3014 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp @@ -4,11 +4,13 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include #include #include +#include namespace Web::Painting { @@ -440,4 +442,75 @@ void ViewportPaintable::resolve_paint_only_properties() }); } +JS::GCPtr ViewportPaintable::selection() const +{ + return const_cast(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(*start_container)) { + if (auto* paintable = start_container->paintable()) { + paintable->set_selection_state(SelectionState::StartAndEnd); + } + return; + } + } + + if (start_container == end_container && is(*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(*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(*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); + } +} + } diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h index f75aab36022..7aac4fd71cc 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h @@ -30,6 +30,9 @@ public: void resolve_paint_only_properties(); + JS::GCPtr selection() const; + void recompute_selection_states(); + private: void build_stacking_context_tree();