From f3d57e11571b0f2fc1ca5923a9cfd43db390f92c Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Sat, 12 Nov 2022 00:07:43 +0300 Subject: [PATCH] LibWeb: Clip hidden overflow by absolute rect of containing block Since handling overflow: hidden in PaintableBox::before_children_paint while following paint traversal order can't result in correctly computed clip rectangle for elements that create their own stacking context (because before_children_paint is called only for parent but overflow: hidden can be set somewhere deeper but not in direct ancestor), here introduced new function PaintableBox::clip_rect() that computes clip rectangle by looking into containing block. should_clip_overflow flag that disables clip for absolutely positioned elements in before_children_paint and after_children_paint is removed because after changing clip rectangle to be computed from not parent but containing block it is not needed anymore (absolutely positioned item is clipped if it's containing block has hidden overflow) --- .../Libraries/LibWeb/Painting/Paintable.h | 8 +--- .../LibWeb/Painting/PaintableBox.cpp | 40 +++++++++++++------ .../Libraries/LibWeb/Painting/PaintableBox.h | 8 +++- .../LibWeb/Painting/SVGGraphicsPaintable.cpp | 4 +- .../LibWeb/Painting/SVGGraphicsPaintable.h | 2 +- .../LibWeb/Painting/SVGPaintable.cpp | 8 ++-- .../Libraries/LibWeb/Painting/SVGPaintable.h | 4 +- .../LibWeb/Painting/SVGSVGPaintable.cpp | 8 ++-- .../LibWeb/Painting/SVGSVGPaintable.h | 4 +- .../LibWeb/Painting/StackingContext.cpp | 9 ++--- 10 files changed, 55 insertions(+), 40 deletions(-) diff --git a/Userland/Libraries/LibWeb/Painting/Paintable.h b/Userland/Libraries/LibWeb/Painting/Paintable.h index c9e2b964aa9..5bdb06e0dd0 100644 --- a/Userland/Libraries/LibWeb/Painting/Paintable.h +++ b/Userland/Libraries/LibWeb/Painting/Paintable.h @@ -86,12 +86,8 @@ public: virtual void paint(PaintContext&, PaintPhase) const { } - enum class ShouldClipOverflow { - No, - Yes - }; - virtual void before_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const { } - virtual void after_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const { } + virtual void before_children_paint(PaintContext&, PaintPhase) const { } + virtual void after_children_paint(PaintContext&, PaintPhase) const { } virtual Optional hit_test(Gfx::FloatPoint const&, HitTestType) const; diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 3107927bb67..b6f0804f781 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -310,34 +310,53 @@ BorderRadiiData PaintableBox::normalized_border_radii_data(ShrinkRadiiForBorders return border_radius_data; } -void PaintableBox::before_children_paint(PaintContext& context, PaintPhase phase, ShouldClipOverflow should_clip_overflow) const +Optional PaintableBox::clip_rect() const +{ + if (!m_clip_rect.has_value()) { + if (containing_block() && containing_block()->paint_box()) + m_clip_rect = containing_block()->paint_box()->clip_rect(); + + auto overflow_x = computed_values().overflow_x(); + auto overflow_y = computed_values().overflow_y(); + + if (overflow_x == CSS::Overflow::Hidden && overflow_y == CSS::Overflow::Hidden) { + if (m_clip_rect.has_value()) { + m_clip_rect->intersect(absolute_padding_box_rect().to_rounded()); + } else { + m_clip_rect = absolute_padding_box_rect().to_rounded(); + } + } + } + + return m_clip_rect; +} + +void PaintableBox::before_children_paint(PaintContext& context, PaintPhase phase) const { if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::Foreground)) return; - if (should_clip_overflow == ShouldClipOverflow::No) - return; - // FIXME: Support more overflow variations. - auto clip_rect = absolute_padding_box_rect().to_rounded(); + auto clip_rect = this->clip_rect(); auto overflow_x = computed_values().overflow_x(); auto overflow_y = computed_values().overflow_y(); auto clip_overflow = [&] { if (!m_clipping_overflow) { context.painter().save(); - context.painter().add_clip_rect(clip_rect); + context.painter().add_clip_rect(*clip_rect); m_clipping_overflow = true; } }; - if (overflow_x == CSS::Overflow::Hidden && overflow_y == CSS::Overflow::Hidden) { + if (clip_rect.has_value()) { clip_overflow(); } + if (overflow_y == CSS::Overflow::Hidden || overflow_x == CSS::Overflow::Hidden) { auto border_radii_data = normalized_border_radii_data(ShrinkRadiiForBorders::Yes); if (border_radii_data.has_any_radius()) { - auto corner_clipper = BorderRadiusCornerClipper::create(clip_rect, border_radii_data, CornerClip::Outside, BorderRadiusCornerClipper::UseCachedBitmap::No); + auto corner_clipper = BorderRadiusCornerClipper::create(absolute_padding_box_rect().to_rounded(), border_radii_data, CornerClip::Outside, BorderRadiusCornerClipper::UseCachedBitmap::No); if (corner_clipper.is_error()) { dbgln("Failed to create overflow border-radius corner clipper: {}", corner_clipper.error()); return; @@ -349,14 +368,11 @@ void PaintableBox::before_children_paint(PaintContext& context, PaintPhase phase } } -void PaintableBox::after_children_paint(PaintContext& context, PaintPhase phase, ShouldClipOverflow should_clip_overflow) const +void PaintableBox::after_children_paint(PaintContext& context, PaintPhase phase) const { if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::Foreground)) return; - if (should_clip_overflow == ShouldClipOverflow::No) - return; - // FIXME: Support more overflow variations. if (m_clipping_overflow) { context.painter().restore(); diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index 603f9ff7b3e..345c0d6c9d1 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -96,6 +96,8 @@ public: return m_overflow_data->scrollable_overflow_rect; } + Optional clip_rect() const; + void set_overflow_data(Optional data) { m_overflow_data = move(data); } void set_containing_line_box_fragment(Optional); @@ -110,8 +112,8 @@ public: DOM::Document const& document() const { return layout_box().document(); } DOM::Document& document() { return layout_box().document(); } - virtual void before_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; - virtual void after_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; + virtual void before_children_paint(PaintContext&, PaintPhase) const override; + virtual void after_children_paint(PaintContext&, PaintPhase) const override; virtual Optional hit_test(Gfx::FloatPoint const&, HitTestType) const override; @@ -153,6 +155,8 @@ private: Optional mutable m_absolute_rect; Optional mutable m_absolute_paint_rect; + Optional mutable m_clip_rect; + mutable bool m_clipping_overflow { false }; Optional mutable m_overflow_corner_radius_clipper; }; diff --git a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp index 8f3990bd142..52e5e06c795 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp @@ -19,9 +19,9 @@ Layout::SVGGraphicsBox const& SVGGraphicsPaintable::layout_box() const return static_cast(layout_node()); } -void SVGGraphicsPaintable::before_children_paint(PaintContext& context, PaintPhase phase, ShouldClipOverflow should_clip_overflow) const +void SVGGraphicsPaintable::before_children_paint(PaintContext& context, PaintPhase phase) const { - SVGPaintable::before_children_paint(context, phase, should_clip_overflow); + SVGPaintable::before_children_paint(context, phase); if (phase != PaintPhase::Foreground) return; diff --git a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h index 39877db81b6..56b1a2e4880 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h @@ -13,7 +13,7 @@ namespace Web::Painting { class SVGGraphicsPaintable : public SVGPaintable { public: - virtual void before_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; + virtual void before_children_paint(PaintContext&, PaintPhase) const override; Layout::SVGGraphicsBox const& layout_box() const; diff --git a/Userland/Libraries/LibWeb/Painting/SVGPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGPaintable.cpp index 84910de3067..e1dae922978 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGPaintable.cpp @@ -20,17 +20,17 @@ Layout::SVGBox const& SVGPaintable::layout_box() const return static_cast(layout_node()); } -void SVGPaintable::before_children_paint(PaintContext& context, PaintPhase phase, ShouldClipOverflow should_clip_overflow) const +void SVGPaintable::before_children_paint(PaintContext& context, PaintPhase phase) const { - PaintableBox::before_children_paint(context, phase, should_clip_overflow); + PaintableBox::before_children_paint(context, phase); if (phase != PaintPhase::Foreground) return; context.svg_context().save(); } -void SVGPaintable::after_children_paint(PaintContext& context, PaintPhase phase, ShouldClipOverflow should_clip_overflow) const +void SVGPaintable::after_children_paint(PaintContext& context, PaintPhase phase) const { - PaintableBox::after_children_paint(context, phase, should_clip_overflow); + PaintableBox::after_children_paint(context, phase); if (phase != PaintPhase::Foreground) return; context.svg_context().restore(); diff --git a/Userland/Libraries/LibWeb/Painting/SVGPaintable.h b/Userland/Libraries/LibWeb/Painting/SVGPaintable.h index a4c3b905732..b1783e16eb3 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/SVGPaintable.h @@ -13,8 +13,8 @@ namespace Web::Painting { class SVGPaintable : public PaintableBox { public: - virtual void before_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; - virtual void after_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; + virtual void before_children_paint(PaintContext&, PaintPhase) const override; + virtual void after_children_paint(PaintContext&, PaintPhase) const override; Layout::SVGBox const& layout_box() const; diff --git a/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.cpp index 3ca3f691671..b5216b14182 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.cpp @@ -24,7 +24,7 @@ Layout::SVGSVGBox const& SVGSVGPaintable::layout_box() const return static_cast(layout_node()); } -void SVGSVGPaintable::before_children_paint(PaintContext& context, PaintPhase phase, ShouldClipOverflow should_clip_overflow) const +void SVGSVGPaintable::before_children_paint(PaintContext& context, PaintPhase phase) const { if (phase != PaintPhase::Foreground) return; @@ -32,12 +32,12 @@ void SVGSVGPaintable::before_children_paint(PaintContext& context, PaintPhase ph if (!context.has_svg_context()) context.set_svg_context(SVGContext(absolute_rect())); - PaintableBox::before_children_paint(context, phase, should_clip_overflow); + PaintableBox::before_children_paint(context, phase); } -void SVGSVGPaintable::after_children_paint(PaintContext& context, PaintPhase phase, ShouldClipOverflow should_clip_overflow) const +void SVGSVGPaintable::after_children_paint(PaintContext& context, PaintPhase phase) const { - PaintableBox::after_children_paint(context, phase, should_clip_overflow); + PaintableBox::after_children_paint(context, phase); if (phase != PaintPhase::Foreground) return; context.clear_svg_context(); diff --git a/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.h b/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.h index 9c207c3e181..443747abaab 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.h @@ -15,8 +15,8 @@ class SVGSVGPaintable : public PaintableBox { public: static NonnullRefPtr create(Layout::SVGSVGBox const&); - virtual void before_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; - virtual void after_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; + virtual void before_children_paint(PaintContext&, PaintPhase) const override; + virtual void after_children_paint(PaintContext&, PaintPhase) const override; Layout::SVGSVGBox const& layout_box() const; diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp index 2e994815ad4..77ab796bfe3 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp @@ -72,7 +72,7 @@ static PaintPhase to_paint_phase(StackingContext::StackingContextPaintPhase phas void StackingContext::paint_descendants(PaintContext& context, Layout::Node const& box, StackingContextPaintPhase phase) const { if (auto* paintable = box.paintable()) - paintable->before_children_paint(context, to_paint_phase(phase), Paintable::ShouldClipOverflow::Yes); + paintable->before_children_paint(context, to_paint_phase(phase)); box.for_each_child([&](auto& child) { // If `child` establishes its own stacking context, skip over it. @@ -125,7 +125,7 @@ void StackingContext::paint_descendants(PaintContext& context, Layout::Node cons }); if (auto* paintable = box.paintable()) - paintable->after_children_paint(context, to_paint_phase(phase), Paintable::ShouldClipOverflow::Yes); + paintable->after_children_paint(context, to_paint_phase(phase)); } void StackingContext::paint_internal(PaintContext& context) const @@ -137,13 +137,12 @@ void StackingContext::paint_internal(PaintContext& context) const auto paint_child = [&](auto* child) { auto parent = child->m_box.parent(); - auto should_clip_overflow = child->m_box.is_absolutely_positioned() ? Paintable::ShouldClipOverflow::No : Paintable::ShouldClipOverflow::Yes; auto* paintable = parent ? parent->paintable() : nullptr; if (paintable) - paintable->before_children_paint(context, PaintPhase::Foreground, should_clip_overflow); + paintable->before_children_paint(context, PaintPhase::Foreground); child->paint(context); if (paintable) - paintable->after_children_paint(context, PaintPhase::Foreground, should_clip_overflow); + paintable->after_children_paint(context, PaintPhase::Foreground); }; // Draw positioned descendants with negative z-indices (step 3)