1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-06-25 05:12:07 +00:00

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)
This commit is contained in:
Aliaksandr Kalenik 2022-11-12 00:07:43 +03:00 committed by Andreas Kling
parent c1401b37c4
commit f3d57e1157
10 changed files with 55 additions and 40 deletions

View file

@ -86,12 +86,8 @@ public:
virtual void paint(PaintContext&, PaintPhase) const { } virtual void paint(PaintContext&, PaintPhase) const { }
enum class ShouldClipOverflow { virtual void before_children_paint(PaintContext&, PaintPhase) const { }
No, virtual void after_children_paint(PaintContext&, PaintPhase) const { }
Yes
};
virtual void before_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const { }
virtual void after_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const { }
virtual Optional<HitTestResult> hit_test(Gfx::FloatPoint const&, HitTestType) const; virtual Optional<HitTestResult> hit_test(Gfx::FloatPoint const&, HitTestType) const;

View file

@ -310,34 +310,53 @@ BorderRadiiData PaintableBox::normalized_border_radii_data(ShrinkRadiiForBorders
return border_radius_data; return border_radius_data;
} }
void PaintableBox::before_children_paint(PaintContext& context, PaintPhase phase, ShouldClipOverflow should_clip_overflow) const Optional<Gfx::IntRect> 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<int>());
} else {
m_clip_rect = absolute_padding_box_rect().to_rounded<int>();
}
}
}
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)) if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::Foreground))
return; return;
if (should_clip_overflow == ShouldClipOverflow::No)
return;
// FIXME: Support more overflow variations. // FIXME: Support more overflow variations.
auto clip_rect = absolute_padding_box_rect().to_rounded<int>(); auto clip_rect = this->clip_rect();
auto overflow_x = computed_values().overflow_x(); auto overflow_x = computed_values().overflow_x();
auto overflow_y = computed_values().overflow_y(); auto overflow_y = computed_values().overflow_y();
auto clip_overflow = [&] { auto clip_overflow = [&] {
if (!m_clipping_overflow) { if (!m_clipping_overflow) {
context.painter().save(); context.painter().save();
context.painter().add_clip_rect(clip_rect); context.painter().add_clip_rect(*clip_rect);
m_clipping_overflow = true; m_clipping_overflow = true;
} }
}; };
if (overflow_x == CSS::Overflow::Hidden && overflow_y == CSS::Overflow::Hidden) { if (clip_rect.has_value()) {
clip_overflow(); clip_overflow();
} }
if (overflow_y == CSS::Overflow::Hidden || overflow_x == CSS::Overflow::Hidden) { if (overflow_y == CSS::Overflow::Hidden || overflow_x == CSS::Overflow::Hidden) {
auto border_radii_data = normalized_border_radii_data(ShrinkRadiiForBorders::Yes); auto border_radii_data = normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
if (border_radii_data.has_any_radius()) { 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<int>(), border_radii_data, CornerClip::Outside, BorderRadiusCornerClipper::UseCachedBitmap::No);
if (corner_clipper.is_error()) { if (corner_clipper.is_error()) {
dbgln("Failed to create overflow border-radius corner clipper: {}", corner_clipper.error()); dbgln("Failed to create overflow border-radius corner clipper: {}", corner_clipper.error());
return; 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)) if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::Foreground))
return; return;
if (should_clip_overflow == ShouldClipOverflow::No)
return;
// FIXME: Support more overflow variations. // FIXME: Support more overflow variations.
if (m_clipping_overflow) { if (m_clipping_overflow) {
context.painter().restore(); context.painter().restore();

View file

@ -96,6 +96,8 @@ public:
return m_overflow_data->scrollable_overflow_rect; return m_overflow_data->scrollable_overflow_rect;
} }
Optional<Gfx::IntRect> clip_rect() const;
void set_overflow_data(Optional<OverflowData> data) { m_overflow_data = move(data); } void set_overflow_data(Optional<OverflowData> data) { m_overflow_data = move(data); }
void set_containing_line_box_fragment(Optional<Layout::LineBoxFragmentCoordinate>); void set_containing_line_box_fragment(Optional<Layout::LineBoxFragmentCoordinate>);
@ -110,8 +112,8 @@ public:
DOM::Document const& document() const { return layout_box().document(); } DOM::Document const& document() const { return layout_box().document(); }
DOM::Document& document() { return layout_box().document(); } DOM::Document& document() { return layout_box().document(); }
virtual void before_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; virtual void before_children_paint(PaintContext&, PaintPhase) const override;
virtual void after_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; virtual void after_children_paint(PaintContext&, PaintPhase) const override;
virtual Optional<HitTestResult> hit_test(Gfx::FloatPoint const&, HitTestType) const override; virtual Optional<HitTestResult> hit_test(Gfx::FloatPoint const&, HitTestType) const override;
@ -153,6 +155,8 @@ private:
Optional<Gfx::FloatRect> mutable m_absolute_rect; Optional<Gfx::FloatRect> mutable m_absolute_rect;
Optional<Gfx::FloatRect> mutable m_absolute_paint_rect; Optional<Gfx::FloatRect> mutable m_absolute_paint_rect;
Optional<Gfx::IntRect> mutable m_clip_rect;
mutable bool m_clipping_overflow { false }; mutable bool m_clipping_overflow { false };
Optional<BorderRadiusCornerClipper> mutable m_overflow_corner_radius_clipper; Optional<BorderRadiusCornerClipper> mutable m_overflow_corner_radius_clipper;
}; };

View file

@ -19,9 +19,9 @@ Layout::SVGGraphicsBox const& SVGGraphicsPaintable::layout_box() const
return static_cast<Layout::SVGGraphicsBox const&>(layout_node()); return static_cast<Layout::SVGGraphicsBox const&>(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) if (phase != PaintPhase::Foreground)
return; return;

View file

@ -13,7 +13,7 @@ namespace Web::Painting {
class SVGGraphicsPaintable : public SVGPaintable { class SVGGraphicsPaintable : public SVGPaintable {
public: 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; Layout::SVGGraphicsBox const& layout_box() const;

View file

@ -20,17 +20,17 @@ Layout::SVGBox const& SVGPaintable::layout_box() const
return static_cast<Layout::SVGBox const&>(layout_node()); return static_cast<Layout::SVGBox const&>(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) if (phase != PaintPhase::Foreground)
return; return;
context.svg_context().save(); 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) if (phase != PaintPhase::Foreground)
return; return;
context.svg_context().restore(); context.svg_context().restore();

View file

@ -13,8 +13,8 @@ namespace Web::Painting {
class SVGPaintable : public PaintableBox { class SVGPaintable : public PaintableBox {
public: public:
virtual void before_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; virtual void before_children_paint(PaintContext&, PaintPhase) const override;
virtual void after_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; virtual void after_children_paint(PaintContext&, PaintPhase) const override;
Layout::SVGBox const& layout_box() const; Layout::SVGBox const& layout_box() const;

View file

@ -24,7 +24,7 @@ Layout::SVGSVGBox const& SVGSVGPaintable::layout_box() const
return static_cast<Layout::SVGSVGBox const&>(layout_node()); return static_cast<Layout::SVGSVGBox const&>(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) if (phase != PaintPhase::Foreground)
return; return;
@ -32,12 +32,12 @@ void SVGSVGPaintable::before_children_paint(PaintContext& context, PaintPhase ph
if (!context.has_svg_context()) if (!context.has_svg_context())
context.set_svg_context(SVGContext(absolute_rect())); 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) if (phase != PaintPhase::Foreground)
return; return;
context.clear_svg_context(); context.clear_svg_context();

View file

@ -15,8 +15,8 @@ class SVGSVGPaintable : public PaintableBox {
public: public:
static NonnullRefPtr<SVGSVGPaintable> create(Layout::SVGSVGBox const&); static NonnullRefPtr<SVGSVGPaintable> create(Layout::SVGSVGBox const&);
virtual void before_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; virtual void before_children_paint(PaintContext&, PaintPhase) const override;
virtual void after_children_paint(PaintContext&, PaintPhase, ShouldClipOverflow) const override; virtual void after_children_paint(PaintContext&, PaintPhase) const override;
Layout::SVGSVGBox const& layout_box() const; Layout::SVGSVGBox const& layout_box() const;

View file

@ -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 void StackingContext::paint_descendants(PaintContext& context, Layout::Node const& box, StackingContextPaintPhase phase) const
{ {
if (auto* paintable = box.paintable()) 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) { box.for_each_child([&](auto& child) {
// If `child` establishes its own stacking context, skip over it. // 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()) 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 void StackingContext::paint_internal(PaintContext& context) const
@ -137,13 +137,12 @@ void StackingContext::paint_internal(PaintContext& context) const
auto paint_child = [&](auto* child) { auto paint_child = [&](auto* child) {
auto parent = child->m_box.parent(); 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; auto* paintable = parent ? parent->paintable() : nullptr;
if (paintable) if (paintable)
paintable->before_children_paint(context, PaintPhase::Foreground, should_clip_overflow); paintable->before_children_paint(context, PaintPhase::Foreground);
child->paint(context); child->paint(context);
if (paintable) 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) // Draw positioned descendants with negative z-indices (step 3)