summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2022-03-11 00:03:28 +0100
committerAndreas Kling <kling@serenityos.org>2022-03-11 00:21:49 +0100
commit5779a910e5186fbf6bf2d26203312271073dcfce (patch)
tree7f1aae3a61dd35c8dfc391ca0dcc64be7a38a59a /Userland
parentba606d90570f2a57cc094c19cf4fd35a43e22102 (diff)
downloadserenity-5779a910e5186fbf6bf2d26203312271073dcfce.zip
LibWeb: Move hit testing to the painting tree
Diffstat (limited to 'Userland')
-rw-r--r--Userland/Libraries/LibWeb/Forward.h1
-rw-r--r--Userland/Libraries/LibWeb/Layout/BlockContainer.cpp25
-rw-r--r--Userland/Libraries/LibWeb/Layout/BlockContainer.h2
-rw-r--r--Userland/Libraries/LibWeb/Layout/Box.cpp14
-rw-r--r--Userland/Libraries/LibWeb/Layout/Box.h1
-rw-r--r--Userland/Libraries/LibWeb/Layout/InitialContainingBlock.cpp5
-rw-r--r--Userland/Libraries/LibWeb/Layout/InitialContainingBlock.h2
-rw-r--r--Userland/Libraries/LibWeb/Layout/Node.cpp5
-rw-r--r--Userland/Libraries/LibWeb/Layout/Node.h86
-rw-r--r--Userland/Libraries/LibWeb/Page/EventHandler.cpp42
-rw-r--r--Userland/Libraries/LibWeb/Page/EventHandler.h3
-rw-r--r--Userland/Libraries/LibWeb/Painting/Paintable.cpp5
-rw-r--r--Userland/Libraries/LibWeb/Painting/Paintable.h29
-rw-r--r--Userland/Libraries/LibWeb/Painting/PaintableBox.cpp113
-rw-r--r--Userland/Libraries/LibWeb/Painting/PaintableBox.h9
-rw-r--r--Userland/Libraries/LibWeb/Painting/StackingContext.cpp14
-rw-r--r--Userland/Libraries/LibWeb/Painting/StackingContext.h11
-rw-r--r--Userland/Services/WebContent/ConnectionFromClient.cpp1
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>