diff --git a/Tests/LibWeb/Ref/overflow-hidden-5.html b/Tests/LibWeb/Ref/overflow-hidden-5.html
new file mode 100644
index 0000000000..a49c4962bf
--- /dev/null
+++ b/Tests/LibWeb/Ref/overflow-hidden-5.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
diff --git a/Tests/LibWeb/Ref/reference/abspos-escapes-scroll-container-ref.html b/Tests/LibWeb/Ref/reference/abspos-escapes-scroll-container-ref.html
new file mode 100644
index 0000000000..032b5f2cf0
--- /dev/null
+++ b/Tests/LibWeb/Ref/reference/abspos-escapes-scroll-container-ref.html
@@ -0,0 +1,16 @@
+
+
+
+
Abspos Element
diff --git a/Tests/LibWeb/Ref/reference/overflow-hidden-3-ref.html b/Tests/LibWeb/Ref/reference/overflow-hidden-3-ref.html
new file mode 100644
index 0000000000..f3ac3a9477
--- /dev/null
+++ b/Tests/LibWeb/Ref/reference/overflow-hidden-3-ref.html
@@ -0,0 +1,9 @@
+
+
+
diff --git a/Tests/LibWeb/Ref/reference/overflow-hidden-4-ref.html b/Tests/LibWeb/Ref/reference/overflow-hidden-4-ref.html
new file mode 100644
index 0000000000..d14ab71dd0
--- /dev/null
+++ b/Tests/LibWeb/Ref/reference/overflow-hidden-4-ref.html
@@ -0,0 +1,12 @@
+
+
+
diff --git a/Tests/LibWeb/Ref/reference/overflow-hidden-5-ref.html b/Tests/LibWeb/Ref/reference/overflow-hidden-5-ref.html
new file mode 100644
index 0000000000..85b0337064
--- /dev/null
+++ b/Tests/LibWeb/Ref/reference/overflow-hidden-5-ref.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.cpp b/Userland/Libraries/LibWeb/HTML/Navigable.cpp
index 9d52a400c0..7f66f140ee 100644
--- a/Userland/Libraries/LibWeb/HTML/Navigable.cpp
+++ b/Userland/Libraries/LibWeb/HTML/Navigable.cpp
@@ -2082,8 +2082,10 @@ void Navigable::paint(Painting::RecordingPainter& recording_painter, PaintConfig
context.set_should_paint_overlay(config.paint_overlay);
context.set_has_focus(config.has_focus);
+ HashMap
scroll_frames;
if (is_traversable()) {
- document->paintable()->collect_scroll_frames(context);
+ document->paintable()->assign_scroll_frame_ids(scroll_frames);
+ document->paintable()->assign_clip_rectangles(context);
}
document->paintable()->paint_all_phases(context);
@@ -2091,11 +2093,11 @@ void Navigable::paint(Painting::RecordingPainter& recording_painter, PaintConfig
// FIXME: Support scrollable frames inside iframes.
if (is_traversable()) {
Vector scroll_offsets_by_frame_id;
- scroll_offsets_by_frame_id.resize(context.scroll_frames().size());
- for (auto [_, scrollable_frame] : context.scroll_frames())
- scroll_offsets_by_frame_id[scrollable_frame.id] = context.rounded_device_point(
- scrollable_frame.offset)
- .to_type();
+ scroll_offsets_by_frame_id.resize(scroll_frames.size());
+ for (auto [_, scrollable_frame] : scroll_frames) {
+ auto scroll_offset = context.rounded_device_point(scrollable_frame.offset).to_type();
+ scroll_offsets_by_frame_id[scrollable_frame.id] = scroll_offset;
+ }
recording_painter.apply_scroll_offsets(scroll_offsets_by_frame_id);
}
}
diff --git a/Userland/Libraries/LibWeb/Painting/BorderRadiiData.cpp b/Userland/Libraries/LibWeb/Painting/BorderRadiiData.cpp
index b05a873148..b18dcfb51c 100644
--- a/Userland/Libraries/LibWeb/Painting/BorderRadiiData.cpp
+++ b/Userland/Libraries/LibWeb/Painting/BorderRadiiData.cpp
@@ -10,7 +10,7 @@
namespace Web::Painting {
-Gfx::AntiAliasingPainter::CornerRadius BorderRadiusData::as_corner(PaintContext& context) const
+Gfx::AntiAliasingPainter::CornerRadius BorderRadiusData::as_corner(PaintContext const& context) const
{
return Gfx::AntiAliasingPainter::CornerRadius {
context.floored_device_pixels(horizontal_radius).value(),
diff --git a/Userland/Libraries/LibWeb/Painting/BorderRadiiData.h b/Userland/Libraries/LibWeb/Painting/BorderRadiiData.h
index 432320afa0..8ebd81d26d 100644
--- a/Userland/Libraries/LibWeb/Painting/BorderRadiiData.h
+++ b/Userland/Libraries/LibWeb/Painting/BorderRadiiData.h
@@ -17,7 +17,7 @@ struct BorderRadiusData {
CSSPixels horizontal_radius { 0 };
CSSPixels vertical_radius { 0 };
- Gfx::AntiAliasingPainter::CornerRadius as_corner(PaintContext& context) const;
+ Gfx::AntiAliasingPainter::CornerRadius as_corner(PaintContext const& context) const;
inline operator bool() const
{
@@ -71,7 +71,7 @@ struct BorderRadiiData {
shrink(-top, -right, -bottom, -left);
}
- inline CornerRadii as_corners(PaintContext& context) const
+ inline CornerRadii as_corners(PaintContext const& context) const
{
return CornerRadii {
top_left.as_corner(context),
diff --git a/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp b/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp
index c30cef76e6..fbce8a88da 100644
--- a/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp
+++ b/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp
@@ -27,6 +27,26 @@ Layout::InlineNode const& InlinePaintable::layout_node() const
return static_cast(Paintable::layout_node());
}
+void InlinePaintable::before_paint(PaintContext& context, PaintPhase) const
+{
+ if (m_scroll_frame_id.has_value()) {
+ context.recording_painter().save();
+ context.recording_painter().set_scroll_frame_id(m_scroll_frame_id.value());
+ }
+ if (m_clip_rect.has_value()) {
+ context.recording_painter().save();
+ context.recording_painter().add_clip_rect(context.enclosing_device_rect(*m_clip_rect).to_type());
+ }
+}
+
+void InlinePaintable::after_paint(PaintContext& context, PaintPhase) const
+{
+ if (m_clip_rect.has_value())
+ context.recording_painter().restore();
+ if (m_scroll_frame_id.has_value())
+ context.recording_painter().restore();
+}
+
void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const
{
auto& painter = context.recording_painter();
diff --git a/Userland/Libraries/LibWeb/Painting/InlinePaintable.h b/Userland/Libraries/LibWeb/Painting/InlinePaintable.h
index 3fe802e293..378031957f 100644
--- a/Userland/Libraries/LibWeb/Painting/InlinePaintable.h
+++ b/Userland/Libraries/LibWeb/Painting/InlinePaintable.h
@@ -20,6 +20,9 @@ public:
virtual void paint(PaintContext&, PaintPhase) const override;
+ virtual void before_paint(PaintContext& context, PaintPhase) const override;
+ virtual void after_paint(PaintContext& context, PaintPhase) const override;
+
Layout::InlineNode const& layout_node() const;
auto const& box_model() const { return layout_node().box_model(); }
@@ -34,12 +37,18 @@ 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; }
+ void set_scroll_frame_id(int id) { m_scroll_frame_id = id; }
+ void set_clip_rect(Optional rect) { m_clip_rect = rect; }
+
private:
InlinePaintable(Layout::InlineNode const&);
template
void for_each_fragment(Callback) const;
+ Optional m_scroll_frame_id;
+ Optional m_clip_rect;
+
Vector m_box_shadow_data;
Vector m_fragments;
};
diff --git a/Userland/Libraries/LibWeb/Painting/PaintContext.h b/Userland/Libraries/LibWeb/Painting/PaintContext.h
index 3cc8ba1767..c0beef1ac3 100644
--- a/Userland/Libraries/LibWeb/Painting/PaintContext.h
+++ b/Userland/Libraries/LibWeb/Painting/PaintContext.h
@@ -74,12 +74,6 @@ public:
u32 allocate_corner_clipper_id() { return m_next_corner_clipper_id++; }
- struct ScrollFrame {
- i32 id { -1 };
- CSSPixelPoint offset;
- };
- HashMap& scroll_frames() { return m_scroll_frames; }
-
private:
Painting::RecordingPainter& m_recording_painter;
Palette m_palette;
@@ -90,7 +84,6 @@ private:
bool m_focus { false };
Gfx::AffineTransform m_svg_transform;
u32 m_next_corner_clipper_id { 0 };
- HashMap m_scroll_frames;
};
}
diff --git a/Userland/Libraries/LibWeb/Painting/Paintable.cpp b/Userland/Libraries/LibWeb/Painting/Paintable.cpp
index 452fa2c449..899b934316 100644
--- a/Userland/Libraries/LibWeb/Painting/Paintable.cpp
+++ b/Userland/Libraries/LibWeb/Painting/Paintable.cpp
@@ -148,19 +148,6 @@ void Paintable::set_needs_display() const
});
}
-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;
-}
-
CSSPixelPoint Paintable::box_type_agnostic_position() const
{
if (is_paintable_box())
diff --git a/Userland/Libraries/LibWeb/Painting/Paintable.h b/Userland/Libraries/LibWeb/Painting/Paintable.h
index 41810b17b7..fd7c8449e9 100644
--- a/Userland/Libraries/LibWeb/Painting/Paintable.h
+++ b/Userland/Libraries/LibWeb/Painting/Paintable.h
@@ -186,8 +186,6 @@ public:
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;
-
CSSPixelPoint box_type_agnostic_position() const;
protected:
diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp
index 4664c47e13..3847717b70 100644
--- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp
+++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp
@@ -121,6 +121,26 @@ CSSPixelRect PaintableBox::compute_absolute_rect() const
return rect;
}
+CSSPixelRect PaintableBox::compute_absolute_padding_rect_with_css_transform_applied() const
+{
+ CSSPixelRect rect { offset(), content_size() };
+ for (auto const* block = containing_block(); block; block = block->containing_block()) {
+ auto offset = block->paintable_box()->offset();
+ auto affine_transform = Gfx::extract_2d_affine_transform(block->paintable_box()->transform());
+ offset.translate_by(affine_transform.translation().to_type());
+ rect.translate_by(offset);
+ }
+ auto affine_transform = Gfx::extract_2d_affine_transform(transform());
+ rect.translate_by(affine_transform.translation().to_type());
+
+ CSSPixelRect padding_rect;
+ padding_rect.set_x(rect.x() - box_model().padding.left);
+ padding_rect.set_width(content_width() + box_model().padding.left + box_model().padding.right);
+ padding_rect.set_y(rect.y() - box_model().padding.top);
+ padding_rect.set_height(content_height() + box_model().padding.top + box_model().padding.bottom);
+ return padding_rect;
+}
+
CSSPixelRect PaintableBox::absolute_rect() const
{
if (!m_absolute_rect.has_value())
@@ -171,11 +191,8 @@ void PaintableBox::before_paint(PaintContext& context, [[maybe_unused]] PaintPha
if (!is_visible())
return;
- auto clip_rect = get_clip_rect();
- if (clip_rect.has_value()) {
- context.recording_painter().save();
- context.recording_painter().add_clip_rect(clip_rect->to_type());
- }
+ apply_scroll_offset(context, phase);
+ apply_clip_overflow_rect(context, phase);
}
void PaintableBox::after_paint(PaintContext& context, [[maybe_unused]] PaintPhase phase) const
@@ -183,8 +200,8 @@ void PaintableBox::after_paint(PaintContext& context, [[maybe_unused]] PaintPhas
if (!is_visible())
return;
- if (get_clip_rect().has_value())
- context.recording_painter().restore();
+ clear_clip_overflow_rect(context, phase);
+ reset_scroll_offset(context, phase);
}
void PaintableBox::paint(PaintContext& context, PaintPhase phase) const
@@ -358,110 +375,63 @@ BorderRadiiData PaintableBox::normalized_border_radii_data(ShrinkRadiiForBorders
return border_radii_data;
}
-Optional PaintableBox::calculate_overflow_clipped_rect() const
-{
- if (layout_node().is_viewport()) {
- return {};
- }
-
- if (!m_clip_rect.has_value()) {
- // NOTE: stacking context should not be crossed while aggregating rectangle to
- // clip `overflow: hidden` because intersecting rectangles with different
- // transforms doesn't make sense
- // TODO: figure out if there are cases when stacking context should be
- // crossed to calculate correct clip rect
- if (!stacking_context() && containing_block() && containing_block()->paintable_box()) {
- m_clip_rect = containing_block()->paintable_box()->calculate_overflow_clipped_rect();
- }
-
- auto overflow_x = computed_values().overflow_x();
- auto overflow_y = computed_values().overflow_y();
-
- if (overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible) {
- if (m_clip_rect.has_value()) {
- m_clip_rect->intersect(absolute_padding_box_rect());
- } else {
- m_clip_rect = absolute_padding_box_rect();
- }
- }
- }
-
- return m_clip_rect;
-}
-
void PaintableBox::apply_scroll_offset(PaintContext& context, PaintPhase) const
{
- if (context.scroll_frames().contains(this)) {
+ if (m_scroll_frame_id.has_value()) {
context.recording_painter().save();
- context.recording_painter().set_scroll_frame_id(context.scroll_frames().get(this)->id);
+ context.recording_painter().set_scroll_frame_id(m_scroll_frame_id.value());
}
}
void PaintableBox::reset_scroll_offset(PaintContext& context, PaintPhase) const
{
- if (context.scroll_frames().contains(this)) {
+ if (m_scroll_frame_id.has_value())
context.recording_painter().restore();
- }
}
void PaintableBox::apply_clip_overflow_rect(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, PaintPhase::Outline))
return;
- // FIXME: Support more overflow variations.
- auto clip_rect = this->calculate_overflow_clipped_rect();
- auto overflow_x = computed_values().overflow_x();
- auto overflow_y = computed_values().overflow_y();
+ if (m_clip_rect.has_value()) {
+ auto overflow_clip_rect = m_clip_rect.value();
+ for (auto const* ancestor = &this->layout_box(); ancestor; ancestor = ancestor->containing_block()) {
+ auto affine_transform = Gfx::extract_2d_affine_transform(ancestor->paintable_box()->transform());
+ if (!affine_transform.is_identity()) {
+ // NOTE: Because the painting command executor applies CSS transform of the nearest stacking context
+ // and the m_clip_rect is determined considering CSS transforms, here transform of the nearest
+ // stacking context need to be compensated.
+ // This adjustment ensures the transform is accounted for just once.
+ overflow_clip_rect.translate_by(-affine_transform.translation().to_type());
+ break;
+ }
+ }
- auto css_clip_property = get_clip_rect();
- if (css_clip_property.has_value()) {
- if (clip_rect.has_value())
- clip_rect->intersect(css_clip_property.value());
- else
- clip_rect = css_clip_property.value();
- }
-
- if (!clip_rect.has_value())
- return;
-
- if (!m_clipping_overflow) {
- context.recording_painter().save();
- context.recording_painter().add_clip_rect(context.enclosing_device_rect(*clip_rect).to_type());
m_clipping_overflow = true;
- }
-
- if (!clip_rect->is_empty() && overflow_y == CSS::Overflow::Hidden && overflow_x == CSS::Overflow::Hidden) {
- auto border_radii_data = normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
- CornerRadii corner_radii {
- .top_left = border_radii_data.top_left.as_corner(context),
- .top_right = border_radii_data.top_right.as_corner(context),
- .bottom_right = border_radii_data.bottom_right.as_corner(context),
- .bottom_left = border_radii_data.bottom_left.as_corner(context)
- };
- if (corner_radii.has_any_radius()) {
+ context.recording_painter().save();
+ context.recording_painter().add_clip_rect(context.enclosing_device_rect(overflow_clip_rect).to_type());
+ if (m_corner_clip_radii.has_value()) {
VERIFY(!m_corner_clipper_id.has_value());
m_corner_clipper_id = context.allocate_corner_clipper_id();
- context.recording_painter().sample_under_corners(*m_corner_clipper_id, corner_radii, context.rounded_device_rect(*clip_rect).to_type(), CornerClip::Outside);
+ context.recording_painter().sample_under_corners(*m_corner_clipper_id, *m_corner_clip_radii, context.rounded_device_rect(overflow_clip_rect).to_type(), CornerClip::Outside);
}
}
}
void PaintableBox::clear_clip_overflow_rect(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, PaintPhase::Outline))
return;
- // FIXME: Support more overflow variations.
if (m_clipping_overflow) {
- context.recording_painter().restore();
m_clipping_overflow = false;
- }
- if (m_corner_clipper_id.has_value()) {
- VERIFY(m_corner_clipper_id.has_value());
- auto clip_rect = this->calculate_overflow_clipped_rect();
- context.recording_painter().blit_corner_clipping(*m_corner_clipper_id, context.rounded_device_rect(*clip_rect).to_type());
- m_corner_clipper_id = {};
+ if (m_corner_clip_radii.has_value()) {
+ VERIFY(m_corner_clipper_id.has_value());
+ context.recording_painter().blit_corner_clipping(*m_corner_clipper_id, context.rounded_device_rect(*m_clip_rect).to_type());
+ m_corner_clipper_id = {};
+ }
+ context.recording_painter().restore();
}
}
diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h
index f040d5ae6e..3c668b3b9a 100644
--- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h
+++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h
@@ -117,8 +117,6 @@ public:
return m_overflow_data->scrollable_overflow_rect;
}
- Optional calculate_overflow_clipped_rect() const;
-
void set_overflow_data(OverflowData data) { m_overflow_data = move(data); }
DOM::Node const* dom_node() const { return layout_box().dom_node(); }
@@ -191,11 +189,17 @@ public:
void set_transform_origin(CSSPixelPoint transform_origin) { m_transform_origin = transform_origin; }
CSSPixelPoint const& transform_origin() const { return m_transform_origin; }
-protected:
- explicit PaintableBox(Layout::Box const&);
+ CSSPixelRect compute_absolute_padding_rect_with_css_transform_applied() const;
Optional get_clip_rect() const;
+ void set_clip_rect(Optional rect) { m_clip_rect = rect; }
+ void set_scroll_frame_id(int id) { m_scroll_frame_id = id; }
+ void set_corner_clip_radii(CornerRadii const& corner_radii) { m_corner_clip_radii = corner_radii; }
+
+protected:
+ explicit PaintableBox(Layout::Box const&);
+
virtual void paint_border(PaintContext&) const;
virtual void paint_backdrop_filter(PaintContext&) const;
virtual void paint_background(PaintContext&) const;
@@ -215,11 +219,13 @@ private:
Optional mutable m_absolute_rect;
Optional mutable m_absolute_paint_rect;
- Optional mutable m_clip_rect;
-
mutable bool m_clipping_overflow { false };
mutable Optional m_corner_clipper_id;
+ Optional m_clip_rect;
+ Optional m_scroll_frame_id;
+ Optional m_corner_clip_radii;
+
Optional m_override_borders_data;
Optional m_table_cell_coordinates;
diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp
index a0573583c0..005db43616 100644
--- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp
+++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp
@@ -88,9 +88,7 @@ void StackingContext::paint_node_as_stacking_context(Paintable const& paintable,
void StackingContext::paint_descendants(PaintContext& context, Paintable const& paintable, StackingContextPaintPhase phase)
{
- paintable.apply_scroll_offset(context, to_paint_phase(phase));
paintable.before_children_paint(context, to_paint_phase(phase));
- paintable.apply_clip_overflow_rect(context, to_paint_phase(phase));
paintable.for_each_child([&context, phase](auto& child) {
auto* stacking_context = child.stacking_context();
@@ -167,9 +165,7 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const&
}
});
- paintable.clear_clip_overflow_rect(context, to_paint_phase(phase));
paintable.after_children_paint(context, to_paint_phase(phase));
- paintable.reset_scroll_offset(context, to_paint_phase(phase));
}
void StackingContext::paint_child(PaintContext& context, StackingContext const& child)
@@ -178,16 +174,8 @@ void StackingContext::paint_child(PaintContext& context, StackingContext const&
if (parent_paintable)
parent_paintable->before_children_paint(context, PaintPhase::Foreground);
- 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);
-
child.paint(context);
- if (nearest_scrollable_ancestor)
- nearest_scrollable_ancestor->reset_scroll_offset(context, PaintPhase::Foreground);
-
if (parent_paintable)
parent_paintable->after_children_paint(context, PaintPhase::Foreground);
}
@@ -227,21 +215,12 @@ void StackingContext::paint_internal(PaintContext& context) const
: TraversalDecision::Continue;
}
- // Apply scroll offset of nearest scrollable ancestor before painting the positioned descendant.
- 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);
-
// At this point, `paintable_box` is a positioned descendant with z-index: auto.
// FIXME: This is basically duplicating logic found elsewhere in this same function. Find a way to make this more elegant.
auto exit_decision = TraversalDecision::Continue;
auto* parent_paintable = paintable.parent();
if (parent_paintable)
parent_paintable->before_children_paint(context, PaintPhase::Foreground);
- auto containing_block = paintable.containing_block();
- 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()) {
paint_child(context, *child);
exit_decision = TraversalDecision::SkipChildrenAndContinue;
@@ -250,11 +229,6 @@ void StackingContext::paint_internal(PaintContext& context) const
}
if (parent_paintable)
parent_paintable->after_children_paint(context, PaintPhase::Foreground);
- if (containing_block_paintable)
- containing_block_paintable->clear_clip_overflow_rect(context, PaintPhase::Foreground);
-
- if (nearest_scrollable_ancestor)
- nearest_scrollable_ancestor->reset_scroll_offset(context, PaintPhase::Foreground);
return exit_decision;
});
@@ -263,22 +237,8 @@ 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().nearest_scrollable_ancestor_within_stacking_context();
-
- if (nearest_scrollable_ancestor)
- nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground);
-
- 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().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);
-
- if (nearest_scrollable_ancestor)
- nearest_scrollable_ancestor->reset_scroll_offset(context, PaintPhase::Foreground);
}
paint_node(paintable(), context, PaintPhase::Outline);
diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp
index 436b745bd2..f1010c222f 100644
--- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp
+++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp
@@ -56,19 +56,97 @@ void ViewportPaintable::paint_all_phases(PaintContext& context)
stacking_context()->paint(context);
}
-void ViewportPaintable::collect_scroll_frames(PaintContext& context) const
+void ViewportPaintable::assign_scroll_frame_ids(HashMap& scroll_frames) const
{
i32 next_id = 0;
+ // Collect scroll frames with their offsets (accumulated offset for nested scroll frames).
for_each_in_subtree_of_type([&](auto const& paintable_box) {
if (paintable_box.has_scrollable_overflow()) {
auto offset = paintable_box.scroll_offset();
- auto ancestor = paintable_box.parent();
+ auto ancestor = paintable_box.containing_block();
while (ancestor) {
- if (ancestor->is_paintable_box() && static_cast(ancestor)->has_scrollable_overflow())
- offset.translate_by(static_cast(ancestor)->scroll_offset());
- ancestor = ancestor->parent();
+ if (ancestor->paintable()->is_paintable_box() && static_cast(ancestor->paintable())->has_scrollable_overflow())
+ offset.translate_by(static_cast(ancestor->paintable())->scroll_offset());
+ ancestor = ancestor->containing_block();
+ }
+ scroll_frames.set(&paintable_box, { .id = next_id++, .offset = -offset });
+ }
+ return TraversalDecision::Continue;
+ });
+
+ // Assign scroll frame id to all paintables contained in a scroll frame.
+ for_each_in_subtree([&](auto const& paintable) {
+ for (auto block = paintable.containing_block(); block; block = block->containing_block()) {
+ auto const& block_paintable_box = *block->paintable_box();
+ if (auto scroll_frame_id = scroll_frames.get(&block_paintable_box); scroll_frame_id.has_value()) {
+ if (paintable.is_paintable_box()) {
+ auto const& paintable_box = static_cast(paintable);
+ const_cast(paintable_box).set_scroll_frame_id(scroll_frame_id->id);
+ } else if (paintable.is_inline_paintable()) {
+ auto const& inline_paintable = static_cast(paintable);
+ const_cast(inline_paintable).set_scroll_frame_id(scroll_frame_id->id);
+ }
+ break;
+ }
+ }
+ return TraversalDecision::Continue;
+ });
+}
+
+void ViewportPaintable::assign_clip_rectangles(PaintContext const& context)
+{
+ HashMap clip_rects;
+ // Calculate clip rects for all boxes that either have hidden overflow or a CSS clip property.
+ for_each_in_subtree_of_type([&](auto const& paintable_box) {
+ auto overflow_x = paintable_box.computed_values().overflow_x();
+ auto overflow_y = paintable_box.computed_values().overflow_y();
+ // Start from CSS clip property if it exists.
+ Optional clip_rect = paintable_box.get_clip_rect();
+ // FIXME: Support overflow clip in one direction only.
+ if (overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible) {
+ auto overflow_clip_rect = paintable_box.compute_absolute_padding_rect_with_css_transform_applied();
+ for (auto block = &paintable_box.layout_box(); !block->is_viewport(); block = block->containing_block()) {
+ auto const& block_paintable_box = *block->paintable_box();
+ auto block_overflow_x = block_paintable_box.computed_values().overflow_x();
+ auto block_overflow_y = block_paintable_box.computed_values().overflow_y();
+ if (block_overflow_x != CSS::Overflow::Visible && block_overflow_y != CSS::Overflow::Visible)
+ overflow_clip_rect.intersect(block_paintable_box.compute_absolute_padding_rect_with_css_transform_applied());
+ if (auto css_clip_property_rect = block->paintable_box()->get_clip_rect(); css_clip_property_rect.has_value())
+ overflow_clip_rect.intersect(css_clip_property_rect.value());
+ }
+ clip_rect = overflow_clip_rect;
+ }
+ if (clip_rect.has_value())
+ clip_rects.set(&paintable_box, *clip_rect);
+ return TraversalDecision::Continue;
+ });
+
+ // Assign clip rects to all paintable boxes contained by a box with a hidden overflow or a CSS clip property.
+ for_each_in_subtree_of_type([&](auto const& paintable_box) {
+ Optional clip_rect = paintable_box.get_clip_rect();
+ for (auto block = paintable_box.containing_block(); block; block = block->containing_block()) {
+ if (auto containing_block_clip_rect = clip_rects.get(block->paintable()); containing_block_clip_rect.has_value()) {
+ auto border_radii_data = block->paintable_box()->normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
+ CornerRadii corner_radii = border_radii_data.as_corners(context);
+ if (corner_radii.has_any_radius()) {
+ // FIXME: Border radii of all boxes in containing block chain should be taken into account instead of just the closest one.
+ const_cast(paintable_box).set_corner_clip_radii(corner_radii);
+ }
+ clip_rect = *containing_block_clip_rect;
+ break;
+ }
+ }
+ const_cast(paintable_box).set_clip_rect(clip_rect);
+ return TraversalDecision::Continue;
+ });
+
+ // Assign clip rects to all inline paintables contained by a box with hidden overflow or a CSS clip property.
+ for_each_in_subtree_of_type([&](auto const& paintable_box) {
+ for (auto block = paintable_box.containing_block(); block; block = block->containing_block()) {
+ if (auto clip_rect = clip_rects.get(block->paintable()); clip_rect.has_value()) {
+ const_cast(paintable_box).set_clip_rect(clip_rect);
+ break;
}
- context.scroll_frames().set(&paintable_box, { .id = next_id++, .offset = -offset });
}
return TraversalDecision::Continue;
});
diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h
index b0f5e8e943..96b0f30c28 100644
--- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h
+++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h
@@ -20,7 +20,12 @@ public:
void paint_all_phases(PaintContext&);
void build_stacking_context_tree_if_needed();
- void collect_scroll_frames(PaintContext&) const;
+ struct ScrollFrame {
+ i32 id { -1 };
+ CSSPixelPoint offset;
+ };
+ void assign_scroll_frame_ids(HashMap&) const;
+ void assign_clip_rectangles(PaintContext const&);
private:
void build_stacking_context_tree();