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();