diff options
author | Andreas Kling <kling@serenityos.org> | 2022-03-11 00:03:28 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-03-11 00:21:49 +0100 |
commit | 5779a910e5186fbf6bf2d26203312271073dcfce (patch) | |
tree | 7f1aae3a61dd35c8dfc391ca0dcc64be7a38a59a /Userland | |
parent | ba606d90570f2a57cc094c19cf4fd35a43e22102 (diff) | |
download | serenity-5779a910e5186fbf6bf2d26203312271073dcfce.zip |
LibWeb: Move hit testing to the painting tree
Diffstat (limited to 'Userland')
18 files changed, 196 insertions, 172 deletions
diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 730437e732..7f537ace26 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -271,6 +271,7 @@ class LabelablePaintable; class Paintable; class PaintableBox; class PaintableWithLines; +class StackingContext; class TextPaintable; } diff --git a/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp b/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp index 4871d6e477..4723d8bb09 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp +++ b/Userland/Libraries/LibWeb/Layout/BlockContainer.cpp @@ -29,31 +29,6 @@ BlockContainer::~BlockContainer() { } -HitTestResult BlockContainer::hit_test(const Gfx::IntPoint& position, HitTestType type) const -{ - if (!children_are_inline()) - return Box::hit_test(position, type); - - HitTestResult last_good_candidate; - for (auto& line_box : paint_box()->line_boxes()) { - for (auto& fragment : line_box.fragments()) { - if (is<Box>(fragment.layout_node()) && verify_cast<Box>(fragment.layout_node()).paint_box()->stacking_context()) - continue; - if (enclosing_int_rect(fragment.absolute_rect()).contains(position)) { - if (is<BlockContainer>(fragment.layout_node())) - return verify_cast<BlockContainer>(fragment.layout_node()).hit_test(position, type); - return { fragment.layout_node().paintable(), fragment.text_index_at(position.x()) }; - } - if (fragment.absolute_rect().top() <= position.y()) - last_good_candidate = { fragment.layout_node().paintable(), fragment.text_index_at(position.x()) }; - } - } - - if (type == HitTestType::TextCursor && last_good_candidate.paintable) - return last_good_candidate; - return { paint_box()->absolute_border_box_rect().contains(position.x(), position.y()) ? paintable() : nullptr }; -} - bool BlockContainer::is_scrollable() const { // FIXME: Support horizontal scroll as well (overflow-x) diff --git a/Userland/Libraries/LibWeb/Layout/BlockContainer.h b/Userland/Libraries/LibWeb/Layout/BlockContainer.h index 162f40f12e..fbff185595 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockContainer.h +++ b/Userland/Libraries/LibWeb/Layout/BlockContainer.h @@ -18,8 +18,6 @@ public: BlockContainer(DOM::Document&, DOM::Node*, CSS::ComputedValues); virtual ~BlockContainer() override; - virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override; - BlockContainer* previous_sibling() { return verify_cast<BlockContainer>(Node::previous_sibling()); } const BlockContainer* previous_sibling() const { return verify_cast<BlockContainer>(Node::previous_sibling()); } BlockContainer* next_sibling() { return verify_cast<BlockContainer>(Node::next_sibling()); } diff --git a/Userland/Libraries/LibWeb/Layout/Box.cpp b/Userland/Libraries/LibWeb/Layout/Box.cpp index 5ee1e745e0..2c502ceb4e 100644 --- a/Userland/Libraries/LibWeb/Layout/Box.cpp +++ b/Userland/Libraries/LibWeb/Layout/Box.cpp @@ -57,20 +57,6 @@ bool Box::is_out_of_flow(FormattingContext const& formatting_context) const return false; } -HitTestResult Box::hit_test(const Gfx::IntPoint& position, HitTestType type) const -{ - // FIXME: It would be nice if we could confidently skip over hit testing - // parts of the layout tree, but currently we can't just check - // m_rect.contains() since inline text rects can't be trusted.. - HitTestResult result { paint_box()->absolute_border_box_rect().contains(position.x(), position.y()) ? paint_box() : nullptr }; - for_each_child_in_paint_order([&](auto& child) { - auto child_result = child.hit_test(position, type); - if (child_result.paintable) - result = child_result; - }); - return result; -} - void Box::set_needs_display() { if (!is_inline()) { diff --git a/Userland/Libraries/LibWeb/Layout/Box.h b/Userland/Libraries/LibWeb/Layout/Box.h index 7a13b9a4df..21df281137 100644 --- a/Userland/Libraries/LibWeb/Layout/Box.h +++ b/Userland/Libraries/LibWeb/Layout/Box.h @@ -24,7 +24,6 @@ public: bool is_out_of_flow(FormattingContext const&) const; - virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override; virtual void set_needs_display() override; bool is_body() const; diff --git a/Userland/Libraries/LibWeb/Layout/InitialContainingBlock.cpp b/Userland/Libraries/LibWeb/Layout/InitialContainingBlock.cpp index 5a4adb37a4..73a6714211 100644 --- a/Userland/Libraries/LibWeb/Layout/InitialContainingBlock.cpp +++ b/Userland/Libraries/LibWeb/Layout/InitialContainingBlock.cpp @@ -47,11 +47,6 @@ void InitialContainingBlock::paint_all_phases(PaintContext& context) paint_box()->stacking_context()->paint(context); } -HitTestResult InitialContainingBlock::hit_test(const Gfx::IntPoint& position, HitTestType type) const -{ - return paint_box()->stacking_context()->hit_test(position, type); -} - void InitialContainingBlock::recompute_selection_states() { SelectionState state = SelectionState::None; diff --git a/Userland/Libraries/LibWeb/Layout/InitialContainingBlock.h b/Userland/Libraries/LibWeb/Layout/InitialContainingBlock.h index c95267e377..db360f656d 100644 --- a/Userland/Libraries/LibWeb/Layout/InitialContainingBlock.h +++ b/Userland/Libraries/LibWeb/Layout/InitialContainingBlock.h @@ -20,8 +20,6 @@ public: void paint_all_phases(PaintContext&); - virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const override; - const LayoutRange& selection() const { return m_selection; } void set_selection(const LayoutRange&); void set_selection_end(const LayoutPosition&); diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp index ec0ec6a724..6cf6fc16bb 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.cpp +++ b/Userland/Libraries/LibWeb/Layout/Node.cpp @@ -80,11 +80,6 @@ bool Node::establishes_stacking_context() const return computed_values().opacity() < 1.0f; } -HitTestResult Node::hit_test(Gfx::IntPoint const&, HitTestType) const -{ - VERIFY_NOT_REACHED(); -} - HTML::BrowsingContext const& Node::browsing_context() const { VERIFY(document().browsing_context()); diff --git a/Userland/Libraries/LibWeb/Layout/Node.h b/Userland/Libraries/LibWeb/Layout/Node.h index eb1b9f1012..35429c2d8b 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.h +++ b/Userland/Libraries/LibWeb/Layout/Node.h @@ -26,30 +26,10 @@ enum class LayoutMode { OnlyRequiredLineBreaks, }; -struct HitTestResult { - RefPtr<Painting::Paintable> paintable; - int index_in_node { 0 }; - - enum InternalPosition { - None, - Before, - Inside, - After, - }; - InternalPosition internal_position { None }; -}; - -enum class HitTestType { - Exact, // Exact matches only - TextCursor, // Clicking past the right/bottom edge of text will still hit the text -}; - class Node : public TreeNode<Node> { public: virtual ~Node(); - virtual HitTestResult hit_test(const Gfx::IntPoint&, HitTestType) const; - bool is_anonymous() const { return !m_dom_node; } const DOM::Node* dom_node() const { return m_dom_node; } DOM::Node* dom_node() { return m_dom_node; } @@ -144,72 +124,6 @@ public: SelectionState selection_state() const { return m_selection_state; } void set_selection_state(SelectionState state) { m_selection_state = state; } - template<typename Callback> - void for_each_child_in_paint_order(Callback callback) const - { - // Element traversal using the order defined in https://www.w3.org/TR/CSS2/zindex.html#painting-order. - // Note: Some steps are skipped because they are not relevant to node traversal. - - // 3. Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order - // (most negative first) then tree order. - // FIXME: This does not retrieve elements in the z-index order. - for_each_child([&](auto& child) { - if (!child.is_positioned() || !is<Box>(child)) - return; - - auto& box_child = verify_cast<Box>(child); - auto* stacking_context = box_child.paint_box()->stacking_context(); - if (stacking_context && box_child.computed_values().z_index().has_value() && box_child.computed_values().z_index().value() < 0) - callback(child); - }); - - // 4. For all its in-flow, non-positioned, block-level descendants in tree order: If the element is a block, list-item, - // or other block equivalent: - for_each_child([&](auto& child) { - if (is<Box>(child) && verify_cast<Box>(child).paint_box()->stacking_context()) - return; - if (!child.is_positioned()) - callback(child); - }); - - // 5. All non-positioned floating descendants, in tree order. For each one of these, treat the element as if it created - // a new stacking context, but any positioned descendants and descendants which actually create a new stacking context - // should be considered part of the parent stacking context, not this new one. - for_each_child([&](auto& child) { - if (is<Box>(child) && verify_cast<Box>(child).paint_box()->stacking_context()) - return; - if (child.is_positioned()) - callback(child); - }); - - // 8. All positioned descendants with 'z-index: auto' or 'z-index: 0', in tree order. For those with 'z-index: auto', treat - // the element as if it created a new stacking context, but any positioned descendants and descendants which actually - // create a new stacking context should be considered part of the parent stacking context, not this new one. For those - // with 'z-index: 0', treat the stacking context generated atomically. - for_each_child([&](auto& child) { - if (!child.is_positioned() || !is<Box>(child)) - return; - - auto& box_child = verify_cast<Box>(child); - auto* stacking_context = box_child.paint_box()->stacking_context(); - if (stacking_context && box_child.computed_values().z_index().has_value() && box_child.computed_values().z_index().value() == 0) - callback(child); - }); - - // 9. Stacking contexts formed by positioned descendants with z-indices greater than or equal to 1 in z-index order - // (smallest first) then tree order. - // FIXME: This does not retrieve elements in the z-index order. - for_each_child([&](auto& child) { - if (!child.is_positioned() || !is<Box>(child)) - return; - - auto& box_child = verify_cast<Box>(child); - auto* stacking_context = box_child.paint_box()->stacking_context(); - if (stacking_context && box_child.computed_values().z_index().has_value() && box_child.computed_values().z_index().value() > 0) - callback(child); - }); - } - protected: Node(DOM::Document&, DOM::Node*); diff --git a/Userland/Libraries/LibWeb/Page/EventHandler.cpp b/Userland/Libraries/LibWeb/Page/EventHandler.cpp index e14a1c30f0..c5047bd3d0 100644 --- a/Userland/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Userland/Libraries/LibWeb/Page/EventHandler.cpp @@ -113,9 +113,23 @@ Layout::InitialContainingBlock* EventHandler::layout_root() return m_browsing_context.active_document()->layout_node(); } +Painting::PaintableBox* EventHandler::paint_root() +{ + if (!m_browsing_context.active_document()) + return nullptr; + return const_cast<Painting::PaintableBox*>(m_browsing_context.active_document()->paint_box()); +} + +Painting::PaintableBox const* EventHandler::paint_root() const +{ + if (!m_browsing_context.active_document()) + return nullptr; + return const_cast<Painting::PaintableBox*>(m_browsing_context.active_document()->paint_box()); +} + bool EventHandler::handle_mousewheel(const Gfx::IntPoint& position, unsigned int buttons, unsigned int modifiers, int wheel_delta_x, int wheel_delta_y) { - if (!layout_root()) + if (!paint_root()) return false; if (modifiers & KeyModifier::Mod_Shift) @@ -123,7 +137,7 @@ bool EventHandler::handle_mousewheel(const Gfx::IntPoint& position, unsigned int // FIXME: Support wheel events in nested browsing contexts. - auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact); + auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact); if (result.paintable && result.paintable->handle_mousewheel({}, position, buttons, modifiers, wheel_delta_x, wheel_delta_y)) return true; @@ -137,7 +151,7 @@ bool EventHandler::handle_mousewheel(const Gfx::IntPoint& position, unsigned int bool EventHandler::handle_mouseup(const Gfx::IntPoint& position, unsigned button, unsigned modifiers) { - if (!layout_root()) + if (!paint_root()) return false; if (m_mouse_event_tracking_layout_node) { @@ -147,15 +161,15 @@ bool EventHandler::handle_mouseup(const Gfx::IntPoint& position, unsigned button bool handled_event = false; - auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact); + auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact); if (result.paintable && result.paintable->wants_mouse_events()) { result.paintable->handle_mouseup({}, position, button, modifiers); // Things may have changed as a consequence of Layout::Node::handle_mouseup(). Hit test again. - if (!layout_root()) + if (!paint_root()) return true; - result = layout_root()->hit_test(position, Layout::HitTestType::Exact); + result = paint_root()->hit_test(position, Painting::HitTestType::Exact); } if (result.paintable && result.paintable->layout_node().dom_node()) { @@ -181,7 +195,7 @@ bool EventHandler::handle_mouseup(const Gfx::IntPoint& position, unsigned button bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned button, unsigned modifiers) { - if (!layout_root()) + if (!paint_root()) return false; if (m_mouse_event_tracking_layout_node) { @@ -194,7 +208,7 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt { // TODO: Allow selecting element behind if one on top has pointer-events set to none. - auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact); + auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact); if (!result.paintable) return false; @@ -229,7 +243,7 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt } // NOTE: Dispatching an event may have disturbed the world. - if (!layout_root() || layout_root() != node->document().layout_node()) + if (!paint_root() || paint_root() != node->document().paint_box()) return true; if (button == GUI::MouseButton::Secondary && is<HTML::HTMLImageElement>(*node)) { @@ -268,7 +282,7 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt } } else { if (button == GUI::MouseButton::Primary) { - auto result = layout_root()->hit_test(position, Layout::HitTestType::TextCursor); + auto result = paint_root()->hit_test(position, Painting::HitTestType::TextCursor); if (result.paintable && result.paintable->layout_node().dom_node()) { // See if we want to focus something. @@ -299,7 +313,7 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned buttons, unsigned modifiers) { - if (!layout_root()) + if (!paint_root()) return false; if (m_mouse_event_tracking_layout_node) { @@ -312,7 +326,7 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt bool hovered_node_changed = false; bool is_hovering_link = false; Gfx::StandardCursor hovered_node_cursor = Gfx::StandardCursor::None; - auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact); + auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact); const HTML::HTMLAnchorElement* hovered_link_element = nullptr; if (result.paintable) { if (result.paintable->wants_mouse_events()) { @@ -361,11 +375,11 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt auto offset = compute_mouse_event_offset(position, result.paintable->layout_node()); node->dispatch_event(UIEvents::MouseEvent::create(UIEvents::EventNames::mousemove, offset.x(), offset.y(), position.x(), position.y())); // NOTE: Dispatching an event may have disturbed the world. - if (!layout_root() || layout_root() != node->document().layout_node()) + if (!paint_root() || paint_root() != node->document().paint_box()) return true; } if (m_in_mouse_selection) { - auto hit = layout_root()->hit_test(position, Layout::HitTestType::TextCursor); + auto hit = paint_root()->hit_test(position, Painting::HitTestType::TextCursor); if (hit.paintable && hit.paintable->layout_node().dom_node()) { m_browsing_context.set_cursor_position(DOM::Position(*hit.paintable->layout_node().dom_node(), result.index_in_node)); layout_root()->set_selection_end({ hit.paintable->layout_node(), hit.index_in_node }); diff --git a/Userland/Libraries/LibWeb/Page/EventHandler.h b/Userland/Libraries/LibWeb/Page/EventHandler.h index 2b4204c871..c2244f9b46 100644 --- a/Userland/Libraries/LibWeb/Page/EventHandler.h +++ b/Userland/Libraries/LibWeb/Page/EventHandler.h @@ -41,6 +41,9 @@ private: Layout::InitialContainingBlock* layout_root(); const Layout::InitialContainingBlock* layout_root() const; + Painting::PaintableBox* paint_root(); + Painting::PaintableBox const* paint_root() const; + HTML::BrowsingContext& m_browsing_context; bool m_in_mouse_selection { false }; diff --git a/Userland/Libraries/LibWeb/Painting/Paintable.cpp b/Userland/Libraries/LibWeb/Painting/Paintable.cpp index 88b384dcdc..008e871def 100644 --- a/Userland/Libraries/LibWeb/Painting/Paintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/Paintable.cpp @@ -38,4 +38,9 @@ bool Paintable::handle_mousewheel(Badge<EventHandler>, Gfx::IntPoint const&, uns return false; } +HitTestResult Paintable::hit_test(Gfx::IntPoint const&, HitTestType) const +{ + VERIFY_NOT_REACHED(); +} + } diff --git a/Userland/Libraries/LibWeb/Painting/Paintable.h b/Userland/Libraries/LibWeb/Painting/Paintable.h index ecb9a66759..ea81a8d867 100644 --- a/Userland/Libraries/LibWeb/Painting/Paintable.h +++ b/Userland/Libraries/LibWeb/Painting/Paintable.h @@ -10,10 +10,35 @@ #include <LibWeb/Layout/Box.h> #include <LibWeb/Layout/LineBox.h> #include <LibWeb/Layout/TextNode.h> -#include <LibWeb/Painting/StackingContext.h> namespace Web::Painting { +enum class PaintPhase { + Background, + Border, + Foreground, + FocusOutline, + Overlay, +}; + +struct HitTestResult { + RefPtr<Painting::Paintable> paintable; + int index_in_node { 0 }; + + enum InternalPosition { + None, + Before, + Inside, + After, + }; + InternalPosition internal_position { None }; +}; + +enum class HitTestType { + Exact, // Exact matches only + TextCursor, // Clicking past the right/bottom edge of text will still hit the text +}; + class Paintable : public RefCounted<Paintable> { AK_MAKE_NONMOVABLE(Paintable); AK_MAKE_NONCOPYABLE(Paintable); @@ -25,6 +50,8 @@ public: virtual void before_children_paint(PaintContext&, PaintPhase) const { } virtual void after_children_paint(PaintContext&, PaintPhase) const { } + virtual HitTestResult hit_test(Gfx::IntPoint const&, HitTestType) const; + virtual bool wants_mouse_events() const { return false; } virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers); virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers); diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 3315a654cd..b0ce44c761 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -10,6 +10,7 @@ #include <LibWeb/Painting/BackgroundPainting.h> #include <LibWeb/Painting/PaintableBox.h> #include <LibWeb/Painting/ShadowPainting.h> +#include <LibWeb/Painting/StackingContext.h> namespace Web::Painting { @@ -307,4 +308,116 @@ Layout::BlockContainer& PaintableWithLines::layout_box() return static_cast<Layout::BlockContainer&>(PaintableBox::layout_box()); } +void PaintableBox::set_stacking_context(NonnullOwnPtr<StackingContext> stacking_context) +{ + m_stacking_context = move(stacking_context); +} + +template<typename Callback> +void PaintableBox::for_each_child_in_paint_order(Callback callback) const +{ + // Element traversal using the order defined in https://www.w3.org/TR/CSS2/zindex.html#painting-order. + // Note: Some steps are skipped because they are not relevant to node traversal. + + // 3. Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order + // (most negative first) then tree order. + // FIXME: This does not retrieve elements in the z-index order. + layout_box().for_each_child([&](auto& child) { + if (!child.is_positioned() || !is<Layout::Box>(child)) + return; + + auto& box_child = verify_cast<Layout::Box>(child); + auto* stacking_context = box_child.paint_box()->stacking_context(); + if (stacking_context && box_child.computed_values().z_index().has_value() && box_child.computed_values().z_index().value() < 0) + callback(child); + }); + + // 4. For all its in-flow, non-positioned, block-level descendants in tree order: If the element is a block, list-item, + // or other block equivalent: + layout_box().for_each_child([&](auto& child) { + if (is<Layout::Box>(child) && verify_cast<Layout::Box>(child).paint_box()->stacking_context()) + return; + if (!child.is_positioned()) + callback(child); + }); + + // 5. All non-positioned floating descendants, in tree order. For each one of these, treat the element as if it created + // a new stacking context, but any positioned descendants and descendants which actually create a new stacking context + // should be considered part of the parent stacking context, not this new one. + layout_box().for_each_child([&](auto& child) { + if (is<Layout::Box>(child) && verify_cast<Layout::Box>(child).paint_box()->stacking_context()) + return; + if (child.is_positioned()) + callback(child); + }); + + // 8. All positioned descendants with 'z-index: auto' or 'z-index: 0', in tree order. For those with 'z-index: auto', treat + // the element as if it created a new stacking context, but any positioned descendants and descendants which actually + // create a new stacking context should be considered part of the parent stacking context, not this new one. For those + // with 'z-index: 0', treat the stacking context generated atomically. + layout_box().for_each_child([&](auto& child) { + if (!child.is_positioned() || !is<Layout::Box>(child)) + return; + + auto& box_child = verify_cast<Layout::Box>(child); + auto* stacking_context = box_child.paint_box()->stacking_context(); + if (stacking_context && box_child.computed_values().z_index().has_value() && box_child.computed_values().z_index().value() == 0) + callback(child); + }); + + // 9. Stacking contexts formed by positioned descendants with z-indices greater than or equal to 1 in z-index order + // (smallest first) then tree order. + // FIXME: This does not retrieve elements in the z-index order. + layout_box().for_each_child([&](auto& child) { + if (!child.is_positioned() || !is<Layout::Box>(child)) + return; + + auto& box_child = verify_cast<Layout::Box>(child); + auto* stacking_context = box_child.paint_box()->stacking_context(); + if (stacking_context && box_child.computed_values().z_index().has_value() && box_child.computed_values().z_index().value() > 0) + callback(child); + }); +} + +HitTestResult PaintableBox::hit_test(Gfx::IntPoint const& position, HitTestType type) const +{ + if (layout_box().is_initial_containing_block_box()) + return stacking_context()->hit_test(position, type); + + HitTestResult result { absolute_border_box_rect().contains(position.x(), position.y()) ? this : nullptr }; + for_each_child_in_paint_order([&](auto& child) { + if (child.paintable()) { + auto child_result = child.paintable()->hit_test(position, type); + if (child_result.paintable) + result = child_result; + } + }); + return result; +} + +HitTestResult PaintableWithLines::hit_test(const Gfx::IntPoint& position, HitTestType type) const +{ + if (!layout_box().children_are_inline()) + return PaintableBox::hit_test(position, type); + + HitTestResult last_good_candidate; + for (auto& line_box : m_line_boxes) { + for (auto& fragment : line_box.fragments()) { + if (is<Layout::Box>(fragment.layout_node()) && static_cast<Layout::Box const&>(fragment.layout_node()).paint_box()->stacking_context()) + continue; + if (enclosing_int_rect(fragment.absolute_rect()).contains(position)) { + if (is<Layout::BlockContainer>(fragment.layout_node()) && fragment.layout_node().paintable()) + return fragment.layout_node().paintable()->hit_test(position, type); + return { fragment.layout_node().paintable(), fragment.text_index_at(position.x()) }; + } + if (fragment.absolute_rect().top() <= position.y()) + last_good_candidate = { fragment.layout_node().paintable(), fragment.text_index_at(position.x()) }; + } + } + + if (type == HitTestType::TextCursor && last_good_candidate.paintable) + return last_good_candidate; + return { absolute_border_box_rect().contains(position.x(), position.y()) ? this : nullptr }; +} + } diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index 43dc3ef0b8..95545196dc 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -103,7 +103,7 @@ public: StackingContext* stacking_context() { return m_stacking_context; } StackingContext const* stacking_context() const { return m_stacking_context; } - void set_stacking_context(NonnullOwnPtr<Painting::StackingContext> context) { m_stacking_context = move(context); } + void set_stacking_context(NonnullOwnPtr<Painting::StackingContext>); StackingContext* enclosing_stacking_context(); DOM::Node const* dom_node() const { return layout_box().dom_node(); } @@ -115,6 +115,8 @@ public: virtual void before_children_paint(PaintContext&, PaintPhase) const override; virtual void after_children_paint(PaintContext&, PaintPhase) const override; + virtual HitTestResult hit_test(Gfx::IntPoint const&, HitTestType) const override; + protected: explicit PaintableBox(Layout::Box const&); @@ -123,6 +125,9 @@ protected: virtual void paint_box_shadow(PaintContext&) const; private: + template<typename Callback> + void for_each_child_in_paint_order(Callback) const; + Painting::BorderRadiusData normalized_border_radius_data() const; OwnPtr<Painting::StackingContext> m_stacking_context; @@ -157,6 +162,8 @@ public: virtual bool wants_mouse_events() const override { return false; } virtual bool handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y) override; + virtual HitTestResult hit_test(Gfx::IntPoint const&, HitTestType) const override; + private: PaintableWithLines(Layout::BlockContainer const&); diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp index ae2f106aa6..fb0dc9d51c 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp @@ -157,7 +157,7 @@ void StackingContext::paint(PaintContext& context) const } } -Layout::HitTestResult StackingContext::hit_test(const Gfx::IntPoint& position, Layout::HitTestType type) const +HitTestResult StackingContext::hit_test(Gfx::IntPoint const& position, HitTestType type) const { // NOTE: Hit testing basically happens in reverse painting order. // https://www.w3.org/TR/CSS22/visuren.html#z-index @@ -172,11 +172,11 @@ Layout::HitTestResult StackingContext::hit_test(const Gfx::IntPoint& position, L return result; } - Layout::HitTestResult result; + HitTestResult result; // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0. m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) { if (box.is_positioned() && !box.paint_box()->stacking_context()) { - result = box.hit_test(position, type); + result = box.paint_box()->hit_test(position, type); if (result.paintable) return IterationDecision::Break; } @@ -187,7 +187,7 @@ Layout::HitTestResult StackingContext::hit_test(const Gfx::IntPoint& position, L // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks. if (m_box.children_are_inline() && is<Layout::BlockContainer>(m_box)) { - auto result = m_box.hit_test(position, type); + auto result = m_box.paint_box()->hit_test(position, type); if (result.paintable) return result; } @@ -195,7 +195,7 @@ Layout::HitTestResult StackingContext::hit_test(const Gfx::IntPoint& position, L // 4. the non-positioned floats. m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) { if (box.is_floating()) { - result = box.hit_test(position, type); + result = box.paint_box()->hit_test(position, type); if (result.paintable) return IterationDecision::Break; } @@ -206,7 +206,7 @@ Layout::HitTestResult StackingContext::hit_test(const Gfx::IntPoint& position, L if (!m_box.children_are_inline()) { m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) { if (!box.is_absolutely_positioned() && !box.is_floating()) { - result = box.hit_test(position, type); + result = box.paint_box()->hit_test(position, type); if (result.paintable) return IterationDecision::Break; } @@ -228,7 +228,7 @@ Layout::HitTestResult StackingContext::hit_test(const Gfx::IntPoint& position, L // 1. the background and borders of the element forming the stacking context. if (m_box.paint_box()->absolute_border_box_rect().contains(position.to_type<float>())) { - return Layout::HitTestResult { + return HitTestResult { .paintable = m_box.paintable(), }; } diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.h b/Userland/Libraries/LibWeb/Painting/StackingContext.h index ebbc384387..0851a93984 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.h +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.h @@ -8,17 +8,10 @@ #include <AK/Vector.h> #include <LibWeb/Layout/Node.h> +#include <LibWeb/Painting/Paintable.h> namespace Web::Painting { -enum class PaintPhase { - Background, - Border, - Foreground, - FocusOutline, - Overlay, -}; - class StackingContext { public: StackingContext(Layout::Box&, StackingContext* parent); @@ -36,7 +29,7 @@ public: void paint_descendants(PaintContext&, Layout::Node&, StackingContextPaintPhase) const; void paint(PaintContext&) const; - Layout::HitTestResult hit_test(Gfx::IntPoint const&, Layout::HitTestType) const; + HitTestResult hit_test(Gfx::IntPoint const&, HitTestType) const; void dump(int indent = 0) const; diff --git a/Userland/Services/WebContent/ConnectionFromClient.cpp b/Userland/Services/WebContent/ConnectionFromClient.cpp index 3243e15ef2..943e212ca5 100644 --- a/Userland/Services/WebContent/ConnectionFromClient.cpp +++ b/Userland/Services/WebContent/ConnectionFromClient.cpp @@ -26,6 +26,7 @@ #include <LibWeb/Loader/ContentFilter.h> #include <LibWeb/Loader/ResourceLoader.h> #include <LibWeb/Painting/PaintableBox.h> +#include <LibWeb/Painting/StackingContext.h> #include <WebContent/ConnectionFromClient.h> #include <WebContent/PageHost.h> #include <WebContent/WebContentClientEndpoint.h> |