diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 707c5a4e23b..f1107f71c4f 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -554,6 +554,7 @@ set(SOURCES Painting/CanvasPaintable.cpp Painting/Command.cpp Painting/CheckBoxPaintable.cpp + Painting/ClipFrame.cpp Painting/ClippableAndScrollable.cpp Painting/DisplayList.cpp Painting/DisplayListPlayerSkia.cpp diff --git a/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp b/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp index e5b05404f2a..920f23cc6f9 100644 --- a/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp @@ -162,15 +162,6 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet clip_shrink.right = context.rounded_device_pixels(border_right.width); } - CSSPixelPoint enclosing_scroll_offset; - if (is(layout_node.paintable())) { - auto const& paintable_box = static_cast(*layout_node.paintable()); - enclosing_scroll_offset = paintable_box.enclosing_scroll_frame_offset(); - } else if (is(layout_node.paintable())) { - auto const& inline_paintable = static_cast(*layout_node.paintable()); - enclosing_scroll_offset = inline_paintable.enclosing_scroll_frame_offset(); - } - // Note: Background layers are ordered front-to-back, so we paint them in reverse for (auto& layer : resolved_background.layers.in_reverse()) { DisplayListRecorderStateSaver state { display_list_recorder }; @@ -179,7 +170,7 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet auto clip_box = get_box(layer.clip, border_box, layout_node); CSSPixelRect const& css_clip_rect = clip_box.rect; - auto clip_rect = context.rounded_device_rect(css_clip_rect.translated(enclosing_scroll_offset)); + auto clip_rect = context.rounded_device_rect(css_clip_rect); display_list_recorder.add_clip_rect(clip_rect.to_type()); ScopedCornerRadiusClip corner_clip { context, context.rounded_device_rect(css_clip_rect), clip_box.radii }; diff --git a/Userland/Libraries/LibWeb/Painting/ClipFrame.cpp b/Userland/Libraries/LibWeb/Painting/ClipFrame.cpp new file mode 100644 index 00000000000..cf3f70a4420 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/ClipFrame.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace Web::Painting { + +void ClipFrame::add_clip_rect(CSSPixelRect rect, BorderRadiiData radii, RefPtr enclosing_scroll_frame) +{ + for (auto& existing_clip : m_clip_rects) { + if (rect == existing_clip.rect && enclosing_scroll_frame == existing_clip.enclosing_scroll_frame) { + existing_clip.corner_radii.union_max_radii(radii); + return; + } + } + m_clip_rects.append({ rect, radii, move(enclosing_scroll_frame) }); +} + +void ClipFrame::clear_rects() +{ + m_clip_rects.clear_with_capacity(); +} + +CSSPixelRect ClipFrame::clip_rect_for_hit_testing() const +{ + VERIFY(!m_clip_rects.is_empty()); + auto rect = m_clip_rects[0].rect; + if (m_clip_rects[0].enclosing_scroll_frame) { + rect.translate_by(m_clip_rects[0].enclosing_scroll_frame->offset); + } + for (size_t i = 1; i < m_clip_rects.size(); ++i) { + auto clip_rect = m_clip_rects[i].rect; + if (m_clip_rects[i].enclosing_scroll_frame) { + clip_rect.translate_by(m_clip_rects[i].enclosing_scroll_frame->offset); + } + rect.intersect(clip_rect); + } + return rect; +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/ClipFrame.h b/Userland/Libraries/LibWeb/Painting/ClipFrame.h index 38aa3a5e3f2..41f6c392c83 100644 --- a/Userland/Libraries/LibWeb/Painting/ClipFrame.h +++ b/Userland/Libraries/LibWeb/Painting/ClipFrame.h @@ -7,35 +7,26 @@ #pragma once #include +#include #include namespace Web::Painting { -struct BorderRadiiClip { +struct ClipRectWithScrollFrame { CSSPixelRect rect; - BorderRadiiData radii; + BorderRadiiData corner_radii; + RefPtr enclosing_scroll_frame; }; struct ClipFrame : public RefCounted { - Vector const& border_radii_clips() const { return m_border_radii_clips; } - void add_border_radii_clip(BorderRadiiClip border_radii_clip) - { - for (auto& existing_clip : m_border_radii_clips) { - if (border_radii_clip.rect == existing_clip.rect) { - existing_clip.radii.union_max_radii(border_radii_clip.radii); - return; - } - } - m_border_radii_clips.append(border_radii_clip); - } - void clear_border_radii_clips() { m_border_radii_clips.clear(); } + Vector const& clip_rects() const { return m_clip_rects; } + void add_clip_rect(CSSPixelRect rect, BorderRadiiData radii, RefPtr enclosing_scroll_frame); + void clear_rects(); - CSSPixelRect rect() const { return m_rect; } - void set_rect(CSSPixelRect rect) { m_rect = rect; } + CSSPixelRect clip_rect_for_hit_testing() const; private: - CSSPixelRect m_rect; - Vector m_border_radii_clips; + Vector m_clip_rects; }; } diff --git a/Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.cpp b/Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.cpp index bde018f1195..5791cac282e 100644 --- a/Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.cpp +++ b/Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.cpp @@ -5,6 +5,7 @@ */ #include +#include namespace Web::Painting { @@ -22,18 +23,49 @@ CSSPixelPoint ClippableAndScrollable::enclosing_scroll_frame_offset() const return {}; } -Optional ClippableAndScrollable::clip_rect() const +Optional ClippableAndScrollable::clip_rect_for_hit_testing() const { if (m_enclosing_clip_frame) - return m_enclosing_clip_frame->rect(); + return m_enclosing_clip_frame->clip_rect_for_hit_testing(); return {}; } -Span ClippableAndScrollable::border_radii_clips() const +void ClippableAndScrollable::apply_clip(PaintContext& context) const { - if (m_enclosing_clip_frame) - return m_enclosing_clip_frame->border_radii_clips(); - return {}; + if (!m_enclosing_clip_frame) + return; + auto const& clip_rects = m_enclosing_clip_frame->clip_rects(); + if (clip_rects.is_empty()) + return; + + auto& display_list_recorder = context.display_list_recorder(); + display_list_recorder.save(); + auto saved_scroll_frame_id = display_list_recorder.scroll_frame_id(); + for (auto const& clip_rect : clip_rects) { + Optional clip_scroll_frame_id; + if (clip_rect.enclosing_scroll_frame) + clip_scroll_frame_id = clip_rect.enclosing_scroll_frame->id; + display_list_recorder.set_scroll_frame_id(clip_scroll_frame_id); + auto rect = context.rounded_device_rect(clip_rect.rect).to_type(); + auto corner_radii = clip_rect.corner_radii.as_corners(context); + if (corner_radii.has_any_radius()) { + display_list_recorder.add_rounded_rect_clip(corner_radii, rect, CornerClip::Outside); + } else { + display_list_recorder.add_clip_rect(rect); + } + } + display_list_recorder.set_scroll_frame_id(saved_scroll_frame_id); +} + +void ClippableAndScrollable::restore_clip(PaintContext& context) const +{ + if (!m_enclosing_clip_frame) + return; + auto const& clip_rects = m_enclosing_clip_frame->clip_rects(); + if (clip_rects.is_empty()) + return; + + context.display_list_recorder().restore(); } } diff --git a/Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.h b/Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.h index 667c90b5e5e..a87468dee3f 100644 --- a/Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.h +++ b/Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.h @@ -7,14 +7,10 @@ #pragma once #include +#include namespace Web::Painting { -struct ScrollFrame : public RefCounted { - i32 id { -1 }; - CSSPixelPoint offset; -}; - class ClippableAndScrollable { public: virtual ~ClippableAndScrollable() = default; @@ -22,10 +18,13 @@ public: void set_enclosing_scroll_frame(RefPtr scroll_frame) { m_enclosing_scroll_frame = scroll_frame; } void set_enclosing_clip_frame(RefPtr clip_frame) { m_enclosing_clip_frame = clip_frame; } + [[nodiscard]] RefPtr enclosing_scroll_frame() const { return m_enclosing_scroll_frame; } [[nodiscard]] Optional scroll_frame_id() const; [[nodiscard]] CSSPixelPoint enclosing_scroll_frame_offset() const; - [[nodiscard]] Optional clip_rect() const; - [[nodiscard]] Span border_radii_clips() const; + [[nodiscard]] Optional clip_rect_for_hit_testing() const; + + void apply_clip(PaintContext&) const; + void restore_clip(PaintContext&) const; Gfx::AffineTransform const& combined_css_transform() const { return m_combined_css_transform; } void set_combined_css_transform(Gfx::AffineTransform const& transform) { m_combined_css_transform = transform; } diff --git a/Userland/Libraries/LibWeb/Painting/Command.h b/Userland/Libraries/LibWeb/Painting/Command.h index 192a296290f..92019fccd67 100644 --- a/Userland/Libraries/LibWeb/Painting/Command.h +++ b/Userland/Libraries/LibWeb/Painting/Command.h @@ -95,6 +95,8 @@ struct Restore { }; struct AddClipRect { Gfx::IntRect rect; + + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } }; struct StackingContextTransform { diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h index 8af41429e30..91120d2df66 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h +++ b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h @@ -103,11 +103,16 @@ public: void translate(int dx, int dy); void translate(Gfx::IntPoint delta); - void set_scroll_frame_id(i32 id) + void set_scroll_frame_id(Optional id) { state().scroll_frame_id = id; } + Optional scroll_frame_id() const + { + return state().scroll_frame_id; + } + void save(); void restore(); diff --git a/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp b/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp index 3cdbd8fe7c1..823d6acc5ea 100644 --- a/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp @@ -32,22 +32,20 @@ Layout::InlineNode const& InlinePaintable::layout_node() const void InlinePaintable::before_paint(PaintContext& context, PaintPhase) const { + apply_clip(context); + if (scroll_frame_id().has_value()) { context.display_list_recorder().save(); context.display_list_recorder().set_scroll_frame_id(scroll_frame_id().value()); } - if (clip_rect().has_value()) { - context.display_list_recorder().save(); - context.display_list_recorder().add_clip_rect(context.enclosing_device_rect(*clip_rect()).to_type()); - } } void InlinePaintable::after_paint(PaintContext& context, PaintPhase) const { - if (clip_rect().has_value()) - context.display_list_recorder().restore(); if (scroll_frame_id().has_value()) context.display_list_recorder().restore(); + + restore_clip(context); } void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const @@ -189,7 +187,7 @@ void InlinePaintable::for_each_fragment(Callback callback) const TraversalDecision InlinePaintable::hit_test(CSSPixelPoint position, HitTestType type, Function const& callback) const { - if (clip_rect().has_value() && !clip_rect().value().contains(position)) + if (clip_rect_for_hit_testing().has_value() && !clip_rect_for_hit_testing().value().contains(position)) return TraversalDecision::Continue; auto position_adjusted_by_scroll_offset = position; diff --git a/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.cpp b/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.cpp index f54ad3b35f7..56dd0a869fb 100644 --- a/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.cpp @@ -40,7 +40,7 @@ void NestedBrowsingContextPaintable::paint(PaintContext& context, PaintPhase pha if (phase == PaintPhase::Foreground) { auto absolute_rect = this->absolute_rect(); - auto clip_rect = context.rounded_device_rect(absolute_rect.translated(enclosing_scroll_frame_offset())); + auto clip_rect = context.rounded_device_rect(absolute_rect); ScopedCornerRadiusClip corner_clip { context, clip_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; auto const* hosted_document = layout_box().dom_node().content_document_without_origin_check(); diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 3e7cc968ea4..2e521aba5a3 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -158,19 +158,6 @@ CSSPixelRect PaintableBox::compute_absolute_rect() const return rect; } -CSSPixelRect PaintableBox::compute_absolute_padding_rect_with_scroll_offset_applied() const -{ - auto rect = absolute_rect(); - rect.translate_by(enclosing_scroll_frame_offset()); - - 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()) @@ -503,19 +490,7 @@ void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase ph if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::TableCollapsedBorder, PaintPhase::Foreground, PaintPhase::Outline)) return; - if (clip_rect().has_value()) { - auto overflow_clip_rect = clip_rect().value(); - context.display_list_recorder().save(); - context.display_list_recorder().add_clip_rect(context.enclosing_device_rect(overflow_clip_rect).to_type()); - auto const& border_radii_clips = this->border_radii_clips(); - for (size_t corner_clip_index = 0; corner_clip_index < border_radii_clips.size(); ++corner_clip_index) { - auto const& corner_clip = border_radii_clips[corner_clip_index]; - auto corners = corner_clip.radii.as_corners(context); - if (!corners.has_any_radius()) - continue; - context.display_list_recorder().add_rounded_rect_clip(corner_clip.radii.as_corners(context), context.rounded_device_rect(corner_clip.rect).to_type(), CornerClip::Outside); - } - } + apply_clip(context); } void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase phase) const @@ -523,9 +498,7 @@ void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase ph if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::TableCollapsedBorder, PaintPhase::Foreground, PaintPhase::Outline)) return; - if (clip_rect().has_value()) { - context.display_list_recorder().restore(); - } + restore_clip(context); } void paint_cursor_if_needed(PaintContext& context, TextPaintable const& paintable, PaintableFragment const& fragment) @@ -694,9 +667,7 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const if (should_clip_overflow) { context.display_list_recorder().save(); // FIXME: Handle overflow-x and overflow-y being different values. - auto clip_box_with_enclosing_scroll_frame_offset = clip_box; - clip_box_with_enclosing_scroll_frame_offset.translate_by(enclosing_scroll_frame_offset()); - context.display_list_recorder().add_clip_rect(context.rounded_device_rect(clip_box_with_enclosing_scroll_frame_offset).to_type()); + context.display_list_recorder().add_clip_rect(context.rounded_device_rect(clip_box).to_type()); auto border_radii = normalized_border_radii_data(ShrinkRadiiForBorders::Yes); CornerRadii corner_radii { @@ -833,7 +804,7 @@ TraversalDecision PaintableBox::hit_test_scrollbars(CSSPixelPoint position, Func TraversalDecision PaintableBox::hit_test(CSSPixelPoint position, HitTestType type, Function const& callback) const { - if (clip_rect().has_value() && !clip_rect()->contains(position)) + if (clip_rect_for_hit_testing().has_value() && !clip_rect_for_hit_testing()->contains(position)) return TraversalDecision::Continue; auto position_adjusted_by_scroll_offset = position; @@ -886,7 +857,7 @@ Optional PaintableBox::hit_test(CSSPixelPoint position, HitTestTy TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestType type, Function const& callback) const { - if (clip_rect().has_value() && !clip_rect()->contains(position)) + if (clip_rect_for_hit_testing().has_value() && !clip_rect_for_hit_testing()->contains(position)) return TraversalDecision::Continue; auto position_adjusted_by_scroll_offset = position; diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index 331cd77ca29..bdaed44287a 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -202,8 +202,6 @@ public: void set_outline_offset(CSSPixels outline_offset) { m_outline_offset = outline_offset; } CSSPixels outline_offset() const { return m_outline_offset; } - CSSPixelRect compute_absolute_padding_rect_with_scroll_offset_applied() const; - Optional get_clip_rect() const; bool is_viewport() const { return layout_box().is_viewport(); } diff --git a/Userland/Libraries/LibWeb/Painting/ScrollFrame.h b/Userland/Libraries/LibWeb/Painting/ScrollFrame.h new file mode 100644 index 00000000000..293a7570ac8 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/ScrollFrame.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::Painting { + +struct ScrollFrame : public RefCounted { + i32 id { -1 }; + CSSPixelPoint offset; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp index 4a312f53e3e..f2757fa0e3f 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp @@ -165,21 +165,18 @@ void ViewportPaintable::refresh_clip_state() for (auto& it : clip_state) { auto const& paintable_box = *it.key; auto& clip_frame = *it.value; + + clip_frame.clear_rects(); + 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(); + if (auto clip_rect = paintable_box.get_clip_rect(); clip_rect.has_value()) { + clip_frame.add_clip_rect(clip_rect.value(), {}, paintable_box.enclosing_scroll_frame()); + } - auto add_border_radii_clip = [&](auto rect, auto border_radii_data) { - if (border_radii_data.has_any_radius()) { - BorderRadiiClip border_radii_clip { .rect = rect, .radii = border_radii_data }; - clip_frame.add_border_radii_clip(border_radii_clip); - } - }; - clip_frame.clear_border_radii_clips(); if (overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible) { - auto overflow_clip_rect = paintable_box.compute_absolute_padding_rect_with_scroll_offset_applied(); - add_border_radii_clip(overflow_clip_rect, paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes)); + auto overflow_clip_rect = paintable_box.absolute_padding_box_rect(); + clip_frame.add_clip_rect(overflow_clip_rect, paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes), paintable_box.enclosing_scroll_frame()); for (auto const* block = &paintable_box.layout_box(); !block->is_viewport(); block = block->containing_block()) { if (block->has_css_transform()) { break; @@ -188,17 +185,14 @@ void ViewportPaintable::refresh_clip_state() 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) { - auto rect = block_paintable_box.compute_absolute_padding_rect_with_scroll_offset_applied(); - overflow_clip_rect.intersect(rect); - add_border_radii_clip(rect, block_paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes)); + auto rect = block_paintable_box.absolute_padding_box_rect(); + clip_frame.add_clip_rect(rect, block_paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes), block_paintable_box.enclosing_scroll_frame()); + } + if (auto css_clip_property_rect = block->paintable_box()->get_clip_rect(); css_clip_property_rect.has_value()) { + clip_frame.add_clip_rect(css_clip_property_rect.value(), {}, block_paintable_box.enclosing_scroll_frame()); } - 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; } - - clip_frame.set_rect(*clip_rect); } }