diff --git a/Tests/LibWeb/Ref/inline-stacking-context.html b/Tests/LibWeb/Ref/inline-stacking-context.html new file mode 100644 index 0000000000..d676c6f9a5 --- /dev/null +++ b/Tests/LibWeb/Ref/inline-stacking-context.html @@ -0,0 +1,16 @@ +hello
diff --git a/Tests/LibWeb/Ref/reference/inline-stacking-context-ref.html b/Tests/LibWeb/Ref/reference/inline-stacking-context-ref.html new file mode 100644 index 0000000000..d9a54c5337 --- /dev/null +++ b/Tests/LibWeb/Ref/reference/inline-stacking-context-ref.html @@ -0,0 +1,17 @@ +
hello
diff --git a/Tests/LibWeb/Text/expected/hit_testing/inline-stacking-context.txt b/Tests/LibWeb/Text/expected/hit_testing/inline-stacking-context.txt new file mode 100644 index 0000000000..4a5ee1f9d6 --- /dev/null +++ b/Tests/LibWeb/Text/expected/hit_testing/inline-stacking-context.txt @@ -0,0 +1 @@ +hello true diff --git a/Tests/LibWeb/Text/input/hit_testing/inline-stacking-context.html b/Tests/LibWeb/Text/input/hit_testing/inline-stacking-context.html new file mode 100644 index 0000000000..5d748e81b8 --- /dev/null +++ b/Tests/LibWeb/Text/input/hit_testing/inline-stacking-context.html @@ -0,0 +1,23 @@ +hello
+ + diff --git a/Userland/Libraries/LibWeb/Painting/InlinePaintable.h b/Userland/Libraries/LibWeb/Painting/InlinePaintable.h index 6718bed6c5..20b46bb183 100644 --- a/Userland/Libraries/LibWeb/Painting/InlinePaintable.h +++ b/Userland/Libraries/LibWeb/Painting/InlinePaintable.h @@ -24,6 +24,8 @@ public: CSSPixelRect bounding_rect() const; + virtual bool is_inline_paintable() const override { return true; } + void mark_contained_fragments(); private: diff --git a/Userland/Libraries/LibWeb/Painting/Paintable.cpp b/Userland/Libraries/LibWeb/Painting/Paintable.cpp index 3b4ab8fc90..2294cd674c 100644 --- a/Userland/Libraries/LibWeb/Painting/Paintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/Paintable.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace Web::Painting { @@ -17,6 +18,10 @@ Paintable::Paintable(Layout::Node const& layout_node) { } +Paintable::~Paintable() +{ +} + void Paintable::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); @@ -28,6 +33,11 @@ void Paintable::visit_edges(Cell::Visitor& visitor) visitor.visit(m_containing_block.value()); } +bool Paintable::is_visible() const +{ + return computed_values().visibility() == CSS::Visibility::Visible && computed_values().opacity() != 0; +} + bool Paintable::is_positioned() const { if (layout_node().is_grid_item() && computed_values().z_index().has_value()) { @@ -88,11 +98,37 @@ Optional Paintable::hit_test(CSSPixelPoint, HitTestType) const return {}; } -StackingContext const* Paintable::stacking_context_rooted_here() const +StackingContext* Paintable::enclosing_stacking_context() { - if (!is(*this)) - return nullptr; - return static_cast(*this).stacking_context(); + for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) { + if (auto* stacking_context = ancestor->stacking_context()) + return const_cast(stacking_context); + } + // We should always reach the viewport's stacking context. + VERIFY_NOT_REACHED(); +} + +void Paintable::set_stacking_context(NonnullOwnPtr stacking_context) +{ + m_stacking_context = move(stacking_context); +} + +void Paintable::invalidate_stacking_context() +{ + m_stacking_context = nullptr; +} + +PaintableBox const* Paintable::nearest_scrollable_ancestor_within_stacking_context() const +{ + auto* ancestor = parent(); + while (ancestor) { + if (ancestor->stacking_context()) + return nullptr; + if (ancestor->is_paintable_box() && static_cast(ancestor)->has_scrollable_overflow()) + return static_cast(ancestor); + ancestor = ancestor->parent(); + } + return nullptr; } } diff --git a/Userland/Libraries/LibWeb/Painting/Paintable.h b/Userland/Libraries/LibWeb/Painting/Paintable.h index 592102b4c2..44c3171f93 100644 --- a/Userland/Libraries/LibWeb/Painting/Paintable.h +++ b/Userland/Libraries/LibWeb/Painting/Paintable.h @@ -54,8 +54,9 @@ class Paintable JS_CELL(Paintable, Cell); public: - virtual ~Paintable() = default; + virtual ~Paintable(); + [[nodiscard]] bool is_visible() const; [[nodiscard]] bool is_positioned() const; [[nodiscard]] bool is_fixed_position() const { return layout_node().is_fixed_position(); } [[nodiscard]] bool is_absolutely_positioned() const { return layout_node().is_absolutely_positioned(); } @@ -109,6 +110,13 @@ public: return TraversalDecision::Continue; } + StackingContext* stacking_context() { return m_stacking_context; } + StackingContext const* stacking_context() const { return m_stacking_context; } + void set_stacking_context(NonnullOwnPtr); + StackingContext* enclosing_stacking_context(); + + void invalidate_stacking_context(); + virtual void before_paint(PaintContext&, PaintPhase) const { } virtual void after_paint(PaintContext&, PaintPhase) const { } @@ -171,8 +179,12 @@ public: [[nodiscard]] virtual bool is_paintable_box() const { return false; } [[nodiscard]] virtual bool is_paintable_with_lines() const { return false; } + [[nodiscard]] virtual bool is_inline_paintable() const { return false; } - StackingContext const* stacking_context_rooted_here() const; + DOM::Document const& document() const { return layout_node().document(); } + DOM::Document& document() { return layout_node().document(); } + + PaintableBox const* nearest_scrollable_ancestor_within_stacking_context() const; protected: explicit Paintable(Layout::Node const&); @@ -184,6 +196,8 @@ private: JS::NonnullGCPtr m_layout_node; JS::NonnullGCPtr m_browsing_context; Optional> mutable m_containing_block; + + OwnPtr m_stacking_context; }; inline DOM::Node* HitTestResult::dom_node() diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 3218d6ef6d..33afe77276 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -40,16 +40,6 @@ PaintableBox::~PaintableBox() { } -bool PaintableBox::is_visible() const -{ - return computed_values().visibility() == CSS::Visibility::Visible && computed_values().opacity() != 0; -} - -void PaintableBox::invalidate_stacking_context() -{ - m_stacking_context = nullptr; -} - PaintableWithLines::PaintableWithLines(Layout::BlockContainer const& layout_box) : PaintableBox(layout_box) { @@ -165,16 +155,6 @@ CSSPixelRect PaintableBox::absolute_paint_rect() const return *m_absolute_paint_rect; } -StackingContext* PaintableBox::enclosing_stacking_context() -{ - for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) { - if (auto* stacking_context = ancestor->stacking_context_rooted_here()) - return const_cast(stacking_context); - } - // We should always reach the viewport's stacking context. - VERIFY_NOT_REACHED(); -} - Optional PaintableBox::get_clip_rect() const { auto clip = computed_values().clip(); @@ -748,11 +728,6 @@ Layout::BlockContainer& PaintableWithLines::layout_box() return static_cast(PaintableBox::layout_box()); } -void PaintableBox::set_stacking_context(NonnullOwnPtr stacking_context) -{ - m_stacking_context = move(stacking_context); -} - Optional PaintableBox::hit_test(CSSPixelPoint position, HitTestType type) const { if (!is_visible()) @@ -828,17 +803,4 @@ Optional PaintableWithLines::hit_test(CSSPixelPoint position, Hit return {}; } -PaintableBox const* PaintableBox::nearest_scrollable_ancestor_within_stacking_context() const -{ - auto* ancestor = parent(); - while (ancestor) { - if (ancestor->stacking_context_rooted_here()) - return nullptr; - if (ancestor->is_paintable_box() && static_cast(ancestor)->has_scrollable_overflow()) - return static_cast(ancestor); - ancestor = ancestor->parent(); - } - return nullptr; -} - } diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index 85a5d8f861..4972d258f9 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -25,8 +25,6 @@ public: virtual void paint(PaintContext&, PaintPhase) const override; - [[nodiscard]] bool is_visible() const; - virtual Optional get_masking_area() const { return {}; } virtual Optional get_mask_type() const { return {}; } virtual RefPtr calculate_mask(PaintContext&, CSSPixelRect const&) const { return {}; } @@ -122,17 +120,9 @@ public: void set_overflow_data(OverflowData data) { m_overflow_data = move(data); } - StackingContext* stacking_context() { return m_stacking_context; } - StackingContext const* stacking_context() const { return m_stacking_context; } - void set_stacking_context(NonnullOwnPtr); - StackingContext* enclosing_stacking_context(); - DOM::Node const* dom_node() const { return layout_box().dom_node(); } DOM::Node* dom_node() { return layout_box().dom_node(); } - DOM::Document const& document() const { return layout_box().document(); } - DOM::Document& document() { return layout_box().document(); } - virtual void apply_scroll_offset(PaintContext&, PaintPhase) const override; virtual void reset_scroll_offset(PaintContext&, PaintPhase) const override; @@ -143,8 +133,6 @@ public: virtual bool handle_mousewheel(Badge, CSSPixelPoint, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y) override; - void invalidate_stacking_context(); - enum class ConflictingElementKind { Cell, Row, @@ -194,8 +182,6 @@ public: void set_box_shadow_data(Vector box_shadow_data) { m_box_shadow_data = move(box_shadow_data); } Vector const& box_shadow_data() const { return m_box_shadow_data; } - PaintableBox const* nearest_scrollable_ancestor_within_stacking_context() const; - protected: explicit PaintableBox(Layout::Box const&); @@ -217,8 +203,6 @@ private: CSSPixelPoint m_offset; CSSPixelSize m_content_size; - OwnPtr m_stacking_context; - Optional mutable m_absolute_rect; Optional mutable m_absolute_paint_rect; diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp index 521611ebd8..56ef8b1c19 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp @@ -31,9 +31,9 @@ static void paint_node(Paintable const& paintable, PaintContext& context, PaintP paintable.after_paint(context, phase); } -StackingContext::StackingContext(PaintableBox& paintable_box, StackingContext* parent, size_t index_in_tree_order) - : m_paintable_box(paintable_box) - , m_transform(combine_transformations(paintable_box.computed_values().transformations())) +StackingContext::StackingContext(Paintable& paintable, StackingContext* parent, size_t index_in_tree_order) + : m_paintable(paintable) + , m_transform(combine_transformations(paintable.computed_values().transformations())) , m_transform_origin(compute_transform_origin()) , m_parent(parent) , m_index_in_tree_order(index_in_tree_order) @@ -46,8 +46,8 @@ StackingContext::StackingContext(PaintableBox& paintable_box, StackingContext* p void StackingContext::sort() { quick_sort(m_children, [](auto& a, auto& b) { - auto a_z_index = a->paintable_box().computed_values().z_index().value_or(0); - auto b_z_index = b->paintable_box().computed_values().z_index().value_or(0); + auto a_z_index = a->paintable().computed_values().z_index().value_or(0); + auto b_z_index = b->paintable().computed_values().z_index().value_or(0); if (a_z_index == b_z_index) return a->m_index_in_tree_order < b->m_index_in_tree_order; return a_z_index < b_z_index; @@ -95,7 +95,7 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const& paintable.apply_clip_overflow_rect(context, to_paint_phase(phase)); paintable.for_each_child([&context, phase](auto& child) { - auto* stacking_context = child.stacking_context_rooted_here(); + auto* stacking_context = child.stacking_context(); auto const& z_index = child.computed_values().z_index(); // NOTE: Grid specification https://www.w3.org/TR/css-grid-2/#z-order says that grid items should be treated @@ -176,11 +176,11 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const& void StackingContext::paint_child(PaintContext& context, StackingContext const& child) { - auto parent_paintable = child.paintable_box().parent(); + auto parent_paintable = child.paintable().parent(); if (parent_paintable) parent_paintable->before_children_paint(context, PaintPhase::Foreground); - PaintableBox const* nearest_scrollable_ancestor = child.paintable_box().nearest_scrollable_ancestor_within_stacking_context(); + PaintableBox const* nearest_scrollable_ancestor = child.paintable().nearest_scrollable_ancestor_within_stacking_context(); if (nearest_scrollable_ancestor) nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground); @@ -198,41 +198,39 @@ void StackingContext::paint_internal(PaintContext& context) const { // For a more elaborate description of the algorithm, see CSS 2.1 Appendix E // Draw the background and borders for the context root (steps 1, 2) - paint_node(paintable_box(), context, PaintPhase::Background); - paint_node(paintable_box(), context, PaintPhase::Border); + paint_node(paintable(), context, PaintPhase::Background); + paint_node(paintable(), context, PaintPhase::Border); // Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order // (most negative first) then tree order. (step 3) // NOTE: This doesn't check if a descendant is positioned as modern CSS allows for alternative methods to establish stacking contexts. for (auto* child : m_children) { - if (child->paintable_box().computed_values().z_index().has_value() && child->paintable_box().computed_values().z_index().value() < 0) + if (child->paintable().computed_values().z_index().has_value() && child->paintable().computed_values().z_index().value() < 0) paint_child(context, *child); } // Draw the background and borders for block-level children (step 4) - paint_descendants(context, paintable_box(), StackingContextPaintPhase::BackgroundAndBorders); + paint_descendants(context, paintable(), StackingContextPaintPhase::BackgroundAndBorders); // Draw the non-positioned floats (step 5) - paint_descendants(context, paintable_box(), StackingContextPaintPhase::Floats); + paint_descendants(context, paintable(), StackingContextPaintPhase::Floats); // Draw inline content, replaced content, etc. (steps 6, 7) - paint_descendants(context, paintable_box(), StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced); - paint_node(paintable_box(), context, PaintPhase::Foreground); - paint_descendants(context, paintable_box(), StackingContextPaintPhase::Foreground); + paint_descendants(context, paintable(), StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced); + paint_node(paintable(), context, PaintPhase::Foreground); + paint_descendants(context, paintable(), StackingContextPaintPhase::Foreground); // Draw positioned descendants with z-index `0` or `auto` in tree order. (step 8) // FIXME: There's more to this step that we have yet to understand and implement. - paintable_box().for_each_in_subtree([&context](Paintable const& paintable) { + paintable().for_each_in_subtree([&context](Paintable const& paintable) { auto const& z_index = paintable.computed_values().z_index(); if (!paintable.is_positioned() || (z_index.has_value() && z_index.value() != 0)) { - return paintable.stacking_context_rooted_here() + return paintable.stacking_context() ? TraversalDecision::SkipChildrenAndContinue : TraversalDecision::Continue; } // Apply scroll offset of nearest scrollable ancestor before painting the positioned descendant. - PaintableBox const* nearest_scrollable_ancestor = nullptr; - if (paintable.is_paintable_box()) - nearest_scrollable_ancestor = static_cast(paintable).nearest_scrollable_ancestor_within_stacking_context(); + PaintableBox const* nearest_scrollable_ancestor = paintable.nearest_scrollable_ancestor_within_stacking_context(); if (nearest_scrollable_ancestor) nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground); @@ -246,7 +244,7 @@ void StackingContext::paint_internal(PaintContext& context) const auto* containing_block_paintable = containing_block ? containing_block->paintable() : nullptr; if (containing_block_paintable) containing_block_paintable->apply_clip_overflow_rect(context, PaintPhase::Foreground); - if (auto* child = paintable.stacking_context_rooted_here()) { + if (auto* child = paintable.stacking_context()) { paint_child(context, *child); exit_decision = TraversalDecision::SkipChildrenAndContinue; } else { @@ -267,16 +265,16 @@ void StackingContext::paint_internal(PaintContext& context) const // (smallest first) then tree order. (Step 9) // NOTE: This doesn't check if a descendant is positioned as modern CSS allows for alternative methods to establish stacking contexts. for (auto* child : m_children) { - PaintableBox const* nearest_scrollable_ancestor = child->paintable_box().nearest_scrollable_ancestor_within_stacking_context(); + PaintableBox const* nearest_scrollable_ancestor = child->paintable().nearest_scrollable_ancestor_within_stacking_context(); if (nearest_scrollable_ancestor) nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground); - auto containing_block = child->paintable_box().containing_block(); + auto containing_block = child->paintable().containing_block(); auto const* containing_block_paintable = containing_block ? containing_block->paintable() : nullptr; if (containing_block_paintable) containing_block_paintable->apply_clip_overflow_rect(context, PaintPhase::Foreground); - if (child->paintable_box().computed_values().z_index().has_value() && child->paintable_box().computed_values().z_index().value() >= 1) + if (child->paintable().computed_values().z_index().has_value() && child->paintable().computed_values().z_index().value() >= 1) paint_child(context, *child); if (containing_block_paintable) containing_block_paintable->clear_clip_overflow_rect(context, PaintPhase::Foreground); @@ -285,20 +283,27 @@ void StackingContext::paint_internal(PaintContext& context) const nearest_scrollable_ancestor->reset_scroll_offset(context, PaintPhase::Foreground); } - paint_node(paintable_box(), context, PaintPhase::Outline); + paint_node(paintable(), context, PaintPhase::Outline); if (context.should_paint_overlay()) { - paint_node(paintable_box(), context, PaintPhase::Overlay); - paint_descendants(context, paintable_box(), StackingContextPaintPhase::FocusAndOverlay); + paint_node(paintable(), context, PaintPhase::Overlay); + paint_descendants(context, paintable(), StackingContextPaintPhase::FocusAndOverlay); } } Gfx::FloatMatrix4x4 StackingContext::combine_transformations(Vector const& transformations) const { - auto matrix = Gfx::FloatMatrix4x4::identity(); + // https://drafts.csswg.org/css-transforms-1/#WD20171130 says: + // "No transform on non-replaced inline boxes, table-column boxes, and table-column-group boxes." + // and https://www.w3.org/TR/css-transforms-2/ does not say anything about what to do with inline boxes. - for (auto const& transform : transformations) - matrix = matrix * transform.to_matrix(paintable_box()); + auto matrix = Gfx::FloatMatrix4x4::identity(); + if (paintable().is_paintable_box()) { + for (auto const& transform : transformations) + matrix = matrix * transform.to_matrix(paintable_box()); + + return matrix; + } return matrix; } @@ -321,35 +326,46 @@ static Gfx::FloatMatrix4x4 matrix_with_scaled_translation(Gfx::FloatMatrix4x4 ma void StackingContext::paint(PaintContext& context) const { - auto opacity = paintable_box().computed_values().opacity(); + auto opacity = paintable().computed_values().opacity(); if (opacity == 0.0f) return; RecordingPainterStateSaver saver(context.recording_painter()); auto to_device_pixels_scale = float(context.device_pixels_per_css_pixel()); + Gfx::IntRect source_paintable_rect; + if (paintable().is_paintable_box()) { + source_paintable_rect = context.enclosing_device_rect(paintable_box().absolute_paint_rect()).to_type(); + } else if (paintable().is_inline()) { + source_paintable_rect = context.enclosing_device_rect(inline_paintable().bounding_rect()).to_type(); + } else { + VERIFY_NOT_REACHED(); + } + RecordingPainter::PushStackingContextParams push_stacking_context_params { .opacity = opacity, - .is_fixed_position = paintable_box().is_fixed_position(), - .source_paintable_rect = context.enclosing_device_rect(paintable_box().absolute_paint_rect()).to_type(), - .image_rendering = paintable_box().computed_values().image_rendering(), + .is_fixed_position = paintable().is_fixed_position(), + .source_paintable_rect = source_paintable_rect, + .image_rendering = paintable().computed_values().image_rendering(), .transform = { .origin = transform_origin().scaled(to_device_pixels_scale), .matrix = matrix_with_scaled_translation(transform_matrix(), to_device_pixels_scale), }, }; - if (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) { - if (masking_area->is_empty()) - return; - auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area); - if (mask_bitmap) { - auto source_paintable_rect = context.enclosing_device_rect(*masking_area).to_type(); - push_stacking_context_params.source_paintable_rect = source_paintable_rect; - push_stacking_context_params.mask = StackingContextMask { - .mask_bitmap = mask_bitmap.release_nonnull(), - .mask_kind = *paintable_box().get_mask_type() - }; + if (paintable().is_paintable_box()) { + if (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) { + if (masking_area->is_empty()) + return; + auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area); + if (mask_bitmap) { + auto source_paintable_rect = context.enclosing_device_rect(*masking_area).to_type(); + push_stacking_context_params.source_paintable_rect = source_paintable_rect; + push_stacking_context_params.mask = StackingContextMask { + .mask_bitmap = mask_bitmap.release_nonnull(), + .mask_kind = *paintable_box().get_mask_type() + }; + } } } @@ -360,39 +376,40 @@ void StackingContext::paint(PaintContext& context) const Gfx::FloatPoint StackingContext::compute_transform_origin() const { - auto style_value = paintable_box().computed_values().transform_origin(); + if (!paintable().is_paintable_box()) + return {}; + + auto style_value = paintable().computed_values().transform_origin(); // FIXME: respect transform-box property auto reference_box = paintable_box().absolute_border_box_rect(); - auto x = reference_box.left() + style_value.x.to_px(paintable_box().layout_node(), reference_box.width()); - auto y = reference_box.top() + style_value.y.to_px(paintable_box().layout_node(), reference_box.height()); + auto x = reference_box.left() + style_value.x.to_px(paintable().layout_node(), reference_box.width()); + auto y = reference_box.top() + style_value.y.to_px(paintable().layout_node(), reference_box.height()); return { x.to_float(), y.to_float() }; } -template -static TraversalDecision for_each_in_inclusive_subtree_of_type_within_same_stacking_context_in_reverse(Paintable const& paintable, Callback callback) +template +static TraversalDecision for_each_in_inclusive_subtree_within_same_stacking_context_in_reverse(Paintable const& paintable, Callback callback) { - if (paintable.stacking_context_rooted_here()) { + if (paintable.stacking_context()) { // Note: Include the stacking context (so we can hit test it), but don't recurse into it. - if (auto decision = callback(static_cast(paintable)); decision != TraversalDecision::Continue) + if (auto decision = callback(paintable); decision != TraversalDecision::Continue) return decision; return TraversalDecision::SkipChildrenAndContinue; } for (auto* child = paintable.last_child(); child; child = child->previous_sibling()) { - if (for_each_in_inclusive_subtree_of_type_within_same_stacking_context_in_reverse(*child, callback) == TraversalDecision::Break) + if (for_each_in_inclusive_subtree_within_same_stacking_context_in_reverse(*child, callback) == TraversalDecision::Break) return TraversalDecision::Break; } - if (is(paintable)) { - if (auto decision = callback(static_cast(paintable)); decision != TraversalDecision::Continue) - return decision; - } + if (auto decision = callback(paintable); decision != TraversalDecision::Continue) + return decision; return TraversalDecision::Continue; } -template -static TraversalDecision for_each_in_subtree_of_type_within_same_stacking_context_in_reverse(Paintable const& paintable, Callback callback) +template +static TraversalDecision for_each_in_subtree_within_same_stacking_context_in_reverse(Paintable const& paintable, Callback callback) { for (auto* child = paintable.last_child(); child; child = child->previous_sibling()) { - if (for_each_in_inclusive_subtree_of_type_within_same_stacking_context_in_reverse(*child, callback) == TraversalDecision::Break) + if (for_each_in_inclusive_subtree_within_same_stacking_context_in_reverse(*child, callback) == TraversalDecision::Break) return TraversalDecision::Break; } return TraversalDecision::Continue; @@ -400,7 +417,7 @@ static TraversalDecision for_each_in_subtree_of_type_within_same_stacking_contex Optional StackingContext::hit_test(CSSPixelPoint position, HitTestType type) const { - if (!paintable_box().is_visible()) + if (!paintable().is_visible()) return {}; auto transform_origin = this->transform_origin().to_type(); @@ -411,15 +428,17 @@ Optional StackingContext::hit_test(CSSPixelPoint position, HitTes }; auto transformed_position = affine_transform_matrix().inverse().value_or({}).map(offset_position).to_type() + transform_origin; - if (paintable_box().is_fixed_position()) { - auto scroll_offset = paintable_box().document().navigable()->viewport_scroll_offset(); + if (paintable().is_fixed_position()) { + auto scroll_offset = paintable().document().navigable()->viewport_scroll_offset(); transformed_position.translate_by(-scroll_offset); } // FIXME: Support more overflow variations. - if (paintable_box().computed_values().overflow_x() == CSS::Overflow::Hidden && paintable_box().computed_values().overflow_y() == CSS::Overflow::Hidden) { - if (!paintable_box().absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y())) - return {}; + if (paintable().computed_values().overflow_x() == CSS::Overflow::Hidden && paintable().computed_values().overflow_y() == CSS::Overflow::Hidden) { + if (paintable().is_paintable_box()) { + if (!paintable_box().absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y())) + return {}; + } } // NOTE: Hit testing basically happens in reverse painting order. @@ -429,7 +448,7 @@ Optional StackingContext::hit_test(CSSPixelPoint position, HitTes // NOTE: Hit testing follows reverse painting order, that's why the conditions here are reversed. for (ssize_t i = m_children.size() - 1; i >= 0; --i) { auto const& child = *m_children[i]; - if (child.paintable_box().computed_values().z_index().value_or(0) <= 0) + if (child.paintable().computed_values().z_index().value_or(0) <= 0) break; auto result = child.hit_test(transformed_position, type); if (result.has_value() && result->paintable->visible_for_hit_testing()) @@ -438,7 +457,12 @@ Optional StackingContext::hit_test(CSSPixelPoint position, HitTes // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0. Optional result; - for_each_in_subtree_of_type_within_same_stacking_context_in_reverse(paintable_box(), [&](PaintableBox const& paintable_box) { + for_each_in_subtree_within_same_stacking_context_in_reverse(paintable(), [&](Paintable const& paintable) { + if (!paintable.is_paintable_box()) + return TraversalDecision::Continue; + + auto const& paintable_box = verify_cast(paintable); + // FIXME: Support more overflow variations. if (paintable_box.computed_values().overflow_x() == CSS::Overflow::Hidden && paintable_box.computed_values().overflow_y() == CSS::Overflow::Hidden) { if (!paintable_box.absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y())) @@ -470,14 +494,19 @@ Optional StackingContext::hit_test(CSSPixelPoint position, HitTes return result; // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks. - if (paintable_box().layout_box().children_are_inline() && is(paintable_box().layout_box())) { + if (paintable().layout_node().children_are_inline() && is(paintable().layout_node())) { auto result = paintable_box().hit_test(transformed_position, type); if (result.has_value() && result->paintable->visible_for_hit_testing()) return result; } // 4. the non-positioned floats. - for_each_in_subtree_of_type_within_same_stacking_context_in_reverse(paintable_box(), [&](PaintableBox const& paintable_box) { + for_each_in_subtree_within_same_stacking_context_in_reverse(paintable(), [&](Paintable const& paintable) { + if (!paintable.is_paintable_box()) + return TraversalDecision::Continue; + + auto const& paintable_box = verify_cast(paintable); + // FIXME: Support more overflow variations. if (paintable_box.computed_values().overflow_x() == CSS::Overflow::Hidden && paintable_box.computed_values().overflow_y() == CSS::Overflow::Hidden) { if (!paintable_box.absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y())) @@ -496,8 +525,13 @@ Optional StackingContext::hit_test(CSSPixelPoint position, HitTes return result; // 3. the in-flow, non-inline-level, non-positioned descendants. - if (!paintable_box().layout_box().children_are_inline()) { - for_each_in_subtree_of_type_within_same_stacking_context_in_reverse(paintable_box(), [&](PaintableBox const& paintable_box) { + if (!paintable().layout_node().children_are_inline()) { + for_each_in_subtree_within_same_stacking_context_in_reverse(paintable(), [&](Paintable const& paintable) { + if (!paintable.is_paintable_box()) + return TraversalDecision::Continue; + + auto const& paintable_box = verify_cast(paintable); + // FIXME: Support more overflow variations. if (paintable_box.computed_values().overflow_x() == CSS::Overflow::Hidden && paintable_box.computed_values().overflow_y() == CSS::Overflow::Hidden) { if (!paintable_box.absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y())) @@ -520,7 +554,7 @@ Optional StackingContext::hit_test(CSSPixelPoint position, HitTes // NOTE: Hit testing follows reverse painting order, that's why the conditions here are reversed. for (ssize_t i = m_children.size() - 1; i >= 0; --i) { auto const& child = *m_children[i]; - if (child.paintable_box().computed_values().z_index().value_or(0) >= 0) + if (child.paintable().computed_values().z_index().value_or(0) >= 0) break; auto result = child.hit_test(transformed_position, type); if (result.has_value() && result->paintable->visible_for_hit_testing()) @@ -528,10 +562,12 @@ Optional StackingContext::hit_test(CSSPixelPoint position, HitTes } // 1. the background and borders of the element forming the stacking context. - if (paintable_box().absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y())) { - return HitTestResult { - .paintable = const_cast(paintable_box()), - }; + if (paintable().is_paintable_box()) { + if (paintable_box().absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y())) { + return HitTestResult { + .paintable = const_cast(paintable_box()), + }; + } } return {}; @@ -542,9 +578,18 @@ void StackingContext::dump(int indent) const StringBuilder builder; for (int i = 0; i < indent; ++i) builder.append(' '); - builder.appendff("SC for {} {} [children: {}] (z-index: ", paintable_box().layout_box().debug_description(), paintable_box().absolute_rect(), m_children.size()); - if (paintable_box().computed_values().z_index().has_value()) - builder.appendff("{}", paintable_box().computed_values().z_index().value()); + CSSPixelRect rect; + if (paintable().is_paintable_box()) { + rect = paintable_box().absolute_rect(); + } else if (paintable().is_inline_paintable()) { + rect = inline_paintable().bounding_rect(); + } else { + VERIFY_NOT_REACHED(); + } + builder.appendff("SC for {} {} [children: {}] (z-index: ", paintable().layout_node().debug_description(), rect, m_children.size()); + + if (paintable().computed_values().z_index().has_value()) + builder.appendff("{}", paintable().computed_values().z_index().value()); else builder.append("auto"sv); builder.append(')'); diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.h b/Userland/Libraries/LibWeb/Painting/StackingContext.h index 52a9736c62..86d4113c66 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.h +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.h @@ -8,18 +8,21 @@ #include #include +#include #include namespace Web::Painting { class StackingContext { public: - StackingContext(PaintableBox&, StackingContext* parent, size_t index_in_tree_order); + StackingContext(Paintable&, StackingContext* parent, size_t index_in_tree_order); StackingContext* parent() { return m_parent; } StackingContext const* parent() const { return m_parent; } - PaintableBox const& paintable_box() const { return *m_paintable_box; } + Paintable const& paintable() const { return *m_paintable; } + PaintableBox const& paintable_box() const { return verify_cast(*m_paintable); } + InlinePaintable const& inline_paintable() const { return verify_cast(*m_paintable); } enum class StackingContextPaintPhase { BackgroundAndBorders, @@ -42,7 +45,7 @@ public: void sort(); private: - JS::NonnullGCPtr m_paintable_box; + JS::NonnullGCPtr m_paintable; Gfx::FloatMatrix4x4 m_transform; Gfx::FloatPoint m_transform_origin; StackingContext* const m_parent { nullptr }; diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp index 8f35d4d4f9..436b745bd2 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp @@ -34,16 +34,15 @@ void ViewportPaintable::build_stacking_context_tree() set_stacking_context(make(*this, nullptr, 0)); size_t index_in_tree_order = 1; - for_each_in_subtree_of_type([&](PaintableBox const& paintable) { - auto& paintable_box = const_cast(paintable); - paintable_box.invalidate_stacking_context(); - if (!paintable_box.layout_box().establishes_stacking_context()) { - VERIFY(!paintable_box.stacking_context()); + for_each_in_subtree([&](Paintable const& paintable) { + const_cast(paintable).invalidate_stacking_context(); + if (!paintable.layout_node().establishes_stacking_context()) { + VERIFY(!paintable.stacking_context()); return TraversalDecision::Continue; } - auto* parent_context = paintable_box.enclosing_stacking_context(); + auto* parent_context = const_cast(paintable).enclosing_stacking_context(); VERIFY(parent_context); - paintable_box.set_stacking_context(make(paintable_box, parent_context, index_in_tree_order++)); + const_cast(paintable).set_stacking_context(make(const_cast(paintable), parent_context, index_in_tree_order++)); return TraversalDecision::Continue; });