From 0bf82f748f44c961eb03c76a97620783fef69a32 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Fri, 26 Jan 2024 21:47:26 +0100 Subject: [PATCH] LibWeb: Move clip rect calculation to happen before painting With this change, clip rectangles for boxes with hidden overflow or the clip property are no longer calculated during the recording of painting commands. Instead, it has moved to the "pre-paint" phase, along with the assignment of scrolling offsets, and works in the following way: 1. The paintable tree is traversed to collect all paintable boxes that have hidden overflow or use the CSS clip property. For each of these boxes, the "final" clip rectangle is calculated by intersecting clip rectangles in the containing block chain for a box. 2. The paintable tree is traversed another time, and a clip rectangle is assigned for each paintable box contained by a node with hidden overflow or the clip property. This way, clipping becomes much easier during the painting commands recording phase, as it only concerns the use of already assigned clip rectangles. The same approach is applied to handle scrolling offsets. Also, clip rectangle calculation is now implemented more correctly, as we no longer stop at the stacking context boundary while intersecting clip rectangles in the containing block chain. Fixes: https://github.com/SerenityOS/serenity/issues/22932 https://github.com/SerenityOS/serenity/issues/22883 https://github.com/SerenityOS/serenity/issues/22679 https://github.com/SerenityOS/serenity/issues/22534 --- .../Ref/abspos-escapes-scroll-container.html | 29 ++++ Tests/LibWeb/Ref/overflow-hidden-3.html | 23 +++ Tests/LibWeb/Ref/overflow-hidden-4.html | 28 ++++ Tests/LibWeb/Ref/overflow-hidden-5.html | 30 ++++ .../abspos-escapes-scroll-container-ref.html | 16 +++ .../Ref/reference/overflow-hidden-3-ref.html | 9 ++ .../Ref/reference/overflow-hidden-4-ref.html | 12 ++ .../Ref/reference/overflow-hidden-5-ref.html | 24 ++++ Userland/Libraries/LibWeb/HTML/Navigable.cpp | 14 +- .../LibWeb/Painting/BorderRadiiData.cpp | 2 +- .../LibWeb/Painting/BorderRadiiData.h | 4 +- .../LibWeb/Painting/InlinePaintable.cpp | 20 +++ .../LibWeb/Painting/InlinePaintable.h | 9 ++ .../Libraries/LibWeb/Painting/PaintContext.h | 7 - .../Libraries/LibWeb/Painting/Paintable.cpp | 13 -- .../Libraries/LibWeb/Painting/Paintable.h | 2 - .../LibWeb/Painting/PaintableBox.cpp | 134 +++++++----------- .../Libraries/LibWeb/Painting/PaintableBox.h | 18 ++- .../LibWeb/Painting/StackingContext.cpp | 40 ------ .../LibWeb/Painting/ViewportPaintable.cpp | 90 +++++++++++- .../LibWeb/Painting/ViewportPaintable.h | 7 +- 21 files changed, 365 insertions(+), 166 deletions(-) create mode 100644 Tests/LibWeb/Ref/abspos-escapes-scroll-container.html create mode 100644 Tests/LibWeb/Ref/overflow-hidden-3.html create mode 100644 Tests/LibWeb/Ref/overflow-hidden-4.html create mode 100644 Tests/LibWeb/Ref/overflow-hidden-5.html create mode 100644 Tests/LibWeb/Ref/reference/abspos-escapes-scroll-container-ref.html create mode 100644 Tests/LibWeb/Ref/reference/overflow-hidden-3-ref.html create mode 100644 Tests/LibWeb/Ref/reference/overflow-hidden-4-ref.html create mode 100644 Tests/LibWeb/Ref/reference/overflow-hidden-5-ref.html diff --git a/Tests/LibWeb/Ref/abspos-escapes-scroll-container.html b/Tests/LibWeb/Ref/abspos-escapes-scroll-container.html new file mode 100644 index 0000000000..34a373b62a --- /dev/null +++ b/Tests/LibWeb/Ref/abspos-escapes-scroll-container.html @@ -0,0 +1,29 @@ + + + +
+
Content that makes the container scrollable...
+
+ Abspos Element +
+
+ diff --git a/Tests/LibWeb/Ref/overflow-hidden-3.html b/Tests/LibWeb/Ref/overflow-hidden-3.html new file mode 100644 index 0000000000..909760669a --- /dev/null +++ b/Tests/LibWeb/Ref/overflow-hidden-3.html @@ -0,0 +1,23 @@ + + +
diff --git a/Tests/LibWeb/Ref/overflow-hidden-4.html b/Tests/LibWeb/Ref/overflow-hidden-4.html new file mode 100644 index 0000000000..433ce19638 --- /dev/null +++ b/Tests/LibWeb/Ref/overflow-hidden-4.html @@ -0,0 +1,28 @@ + + +
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();