diff --git a/Libraries/LibWeb/Painting/ClipFrame.h b/Libraries/LibWeb/Painting/ClipFrame.h index 48187da810d..299d1134cbb 100644 --- a/Libraries/LibWeb/Painting/ClipFrame.h +++ b/Libraries/LibWeb/Painting/ClipFrame.h @@ -18,7 +18,7 @@ struct ClipRectWithScrollFrame { RefPtr enclosing_scroll_frame; }; -struct ClipFrame : public RefCounted { +struct ClipFrame : public AtomicRefCounted { Vector const& clip_rects() const { return m_clip_rects; } void add_clip_rect(CSSPixelRect rect, BorderRadiiData radii, RefPtr enclosing_scroll_frame); diff --git a/Libraries/LibWeb/Painting/DisplayList.cpp b/Libraries/LibWeb/Painting/DisplayList.cpp index 11895b5cdf3..36b5ee5586d 100644 --- a/Libraries/LibWeb/Painting/DisplayList.cpp +++ b/Libraries/LibWeb/Painting/DisplayList.cpp @@ -1,16 +1,17 @@ /* - * Copyright (c) 2024, Aliaksandr Kalenik + * Copyright (c) 2024-2025, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include namespace Web::Painting { -void DisplayList::append(Command&& command, Optional scroll_frame_id) +void DisplayList::append(Command&& command, Optional scroll_frame_id, RefPtr clip_frame) { - m_commands.append({ scroll_frame_id, move(command) }); + m_commands.append({ scroll_frame_id, clip_frame, move(command) }); } String DisplayList::dump() const @@ -56,6 +57,35 @@ void DisplayListPlayer::execute(DisplayList& display_list, ScrollStateSnapshot c } } +void DisplayListPlayer::apply_clip_frame(ClipFrame const& clip_frame, DevicePixelConverter const& device_pixel_converter) +{ + auto const& clip_rects = clip_frame.clip_rects(); + if (clip_rects.is_empty()) + return; + + save({}); + for (auto const& clip_rect : clip_rects) { + auto css_rect = clip_rect.rect; + if (auto scroll_frame = clip_rect.enclosing_scroll_frame) { + css_rect.translate_by(scroll_frame->cumulative_offset()); + } + auto device_rect = device_pixel_converter.rounded_device_rect(css_rect).to_type(); + auto corner_radii = clip_rect.corner_radii.as_corners(device_pixel_converter); + if (corner_radii.has_any_radius()) { + add_rounded_rect_clip({ .corner_radii = corner_radii, .border_rect = device_rect, .corner_clip = CornerClip::Outside }); + } else { + add_clip_rect({ .rect = device_rect }); + } + } +} + +void DisplayListPlayer::remove_clip_frame(ClipFrame const& clip_frame) +{ + if (clip_frame.clip_rects().is_empty()) + return; + restore({}); +} + void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnapshot const& scroll_state, RefPtr surface) { if (surface) @@ -68,11 +98,36 @@ void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnaps auto const& commands = display_list.commands(); auto device_pixels_per_css_pixel = display_list.device_pixels_per_css_pixel(); + DevicePixelConverter device_pixel_converter { device_pixels_per_css_pixel }; + VERIFY(!m_surfaces.is_empty()); + Vector> clip_frames_stack; + clip_frames_stack.append({}); for (size_t command_index = 0; command_index < commands.size(); command_index++) { - auto scroll_frame_id = commands[command_index].scroll_frame_id; - auto command = commands[command_index].command; + auto [scroll_frame_id, clip_frame, command] = commands[command_index]; + + if (clip_frames_stack.last() != clip_frame) { + if (auto clip_frame = clip_frames_stack.take_last()) { + remove_clip_frame(*clip_frame); + } + clip_frames_stack.append(clip_frame); + if (clip_frame) { + apply_clip_frame(*clip_frame, device_pixel_converter); + } + } + + // After entering a new stacking context, we keep the outer clip frame applied. + // This is necessary when the stacking context has a CSS transform, and all + // nested ClipFrames aggregate clip rectangles only up to the stacking context + // node. + if (command.has()) { + clip_frames_stack.append({}); + } else if (command.has()) { + if (auto clip_frame = clip_frames_stack.take_last()) { + remove_clip_frame(*clip_frame); + } + } if (command.has()) { auto& paint_scroll_bar = command.get(); @@ -159,6 +214,12 @@ void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnaps // clang-format on } + while (!clip_frames_stack.is_empty()) { + if (auto clip_frame = clip_frames_stack.take_last()) { + remove_clip_frame(*clip_frame); + } + } + if (surface) flush(); } diff --git a/Libraries/LibWeb/Painting/DisplayList.h b/Libraries/LibWeb/Painting/DisplayList.h index 66f219b84e8..6c4c12f451f 100644 --- a/Libraries/LibWeb/Painting/DisplayList.h +++ b/Libraries/LibWeb/Painting/DisplayList.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Aliaksandr Kalenik + * Copyright (c) 2024-2025, Aliaksandr Kalenik * Copyright (c) 2025, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -74,6 +75,9 @@ private: virtual void apply_mask_bitmap(ApplyMaskBitmap const&) = 0; virtual bool would_be_fully_clipped_by_painter(Gfx::IntRect) const = 0; + void apply_clip_frame(ClipFrame const&, DevicePixelConverter const&); + void remove_clip_frame(ClipFrame const&); + Vector, 1> m_surfaces; }; @@ -84,10 +88,11 @@ public: return adopt_ref(*new DisplayList()); } - void append(Command&& command, Optional scroll_frame_id); + void append(Command&& command, Optional scroll_frame_id, RefPtr); struct CommandListItem { Optional scroll_frame_id; + RefPtr clip_frame; Command command; }; diff --git a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp index b1ecc1b636f..acc1de2ac6f 100644 --- a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp +++ b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp @@ -18,12 +18,15 @@ DisplayListRecorder::DisplayListRecorder(DisplayList& command_list) DisplayListRecorder::~DisplayListRecorder() = default; -#define APPEND(...) \ - do { \ - Optional _scroll_frame_id; \ - if (!m_scroll_frame_id_stack.is_empty()) \ - _scroll_frame_id = m_scroll_frame_id_stack.last(); \ - m_command_list.append(__VA_ARGS__, _scroll_frame_id); \ +#define APPEND(...) \ + do { \ + Optional _scroll_frame_id; \ + if (!m_scroll_frame_id_stack.is_empty()) \ + _scroll_frame_id = m_scroll_frame_id_stack.last(); \ + RefPtr _clip_frame; \ + if (!m_clip_frame_stack.is_empty()) \ + _clip_frame = m_clip_frame_stack.last(); \ + m_command_list.append(__VA_ARGS__, _scroll_frame_id, _clip_frame); \ } while (false) void DisplayListRecorder::paint_nested_display_list(RefPtr display_list, ScrollStateSnapshot&& scroll_state_snapshot, Gfx::IntRect rect) @@ -311,6 +314,16 @@ void DisplayListRecorder::pop_scroll_frame_id() (void)m_scroll_frame_id_stack.take_last(); } +void DisplayListRecorder::push_clip_frame(RefPtr clip_frame) +{ + m_clip_frame_stack.append(clip_frame); +} + +void DisplayListRecorder::pop_clip_frame() +{ + (void)m_clip_frame_stack.take_last(); +} + void DisplayListRecorder::push_stacking_context(PushStackingContextParams params) { APPEND(PushStackingContext { @@ -323,11 +336,13 @@ void DisplayListRecorder::push_stacking_context(PushStackingContextParams params .matrix = params.transform.matrix, }, .clip_path = params.clip_path }); + m_clip_frame_stack.append({}); } void DisplayListRecorder::pop_stacking_context() { APPEND(PopStackingContext {}); + (void)m_clip_frame_stack.take_last(); } void DisplayListRecorder::apply_backdrop_filter(Gfx::IntRect const& backdrop_region, BorderRadiiData const& border_radii_data, Gfx::Filter const& backdrop_filter) diff --git a/Libraries/LibWeb/Painting/DisplayListRecorder.h b/Libraries/LibWeb/Painting/DisplayListRecorder.h index 033368a3c2e..9443fcc4b53 100644 --- a/Libraries/LibWeb/Painting/DisplayListRecorder.h +++ b/Libraries/LibWeb/Painting/DisplayListRecorder.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -111,6 +112,9 @@ public: void push_scroll_frame_id(Optional id); void pop_scroll_frame_id(); + void push_clip_frame(RefPtr); + void pop_clip_frame(); + void save(); void save_layer(); void restore(); @@ -161,6 +165,7 @@ public: private: Vector> m_scroll_frame_id_stack; + Vector> m_clip_frame_stack; DisplayList& m_command_list; }; diff --git a/Libraries/LibWeb/Painting/PaintableBox.cpp b/Libraries/LibWeb/Painting/PaintableBox.cpp index 1f18a33f3ed..79ea44952d7 100644 --- a/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -327,7 +327,7 @@ void PaintableBox::before_paint(PaintContext& context, PaintPhase phase) const return; if (first_is_one_of(phase, PaintPhase::Background, PaintPhase::Foreground) && own_clip_frame()) { - apply_clip(context, own_clip_frame()); + context.display_list_recorder().push_clip_frame(own_clip_frame()); } else if (!has_css_transform()) { apply_clip_overflow_rect(context, phase); } @@ -341,7 +341,7 @@ void PaintableBox::after_paint(PaintContext& context, PaintPhase phase) const reset_scroll_offset(context); if (first_is_one_of(phase, PaintPhase::Background, PaintPhase::Foreground) && own_clip_frame()) { - restore_clip(context, own_clip_frame()); + context.display_list_recorder().pop_clip_frame(); } else if (!has_css_transform()) { clear_clip_overflow_rect(context, phase); } @@ -647,39 +647,6 @@ Optional PaintableBox::clip_rect_for_hit_testing() const return {}; } -void PaintableBox::apply_clip(PaintContext& context, RefPtr const& from_clip_frame) -{ - auto const& clip_rects = from_clip_frame->clip_rects(); - if (clip_rects.is_empty()) - return; - - auto& display_list_recorder = context.display_list_recorder(); - display_list_recorder.save(); - 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.push_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.device_pixel_converter()); - 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.pop_scroll_frame_id(); - } -} - -void PaintableBox::restore_clip(PaintContext& context, RefPtr const& from_clip_frame) -{ - auto const& clip_rects = from_clip_frame->clip_rects(); - if (clip_rects.is_empty()) - return; - - context.display_list_recorder().restore(); -} - void PaintableBox::apply_scroll_offset(PaintContext& context) const { if (scroll_frame_id().has_value()) { @@ -702,7 +669,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; - apply_clip(context, enclosing_clip_frame()); + context.display_list_recorder().push_clip_frame(enclosing_clip_frame()); } void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase phase) const @@ -713,7 +680,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; - restore_clip(context, enclosing_clip_frame()); + context.display_list_recorder().pop_clip_frame(); } void paint_cursor_if_needed(PaintContext& context, TextPaintable const& paintable, PaintableFragment const& fragment) diff --git a/Libraries/LibWeb/Painting/PaintableBox.h b/Libraries/LibWeb/Painting/PaintableBox.h index 477329bb6e1..f62e2a364d2 100644 --- a/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Libraries/LibWeb/Painting/PaintableBox.h @@ -305,9 +305,6 @@ private: bool scrollbar_contains_mouse_position(ScrollDirection, CSSPixelPoint); void scroll_to_mouse_position(CSSPixelPoint); - static void apply_clip(PaintContext&, RefPtr const&); - static void restore_clip(PaintContext&, RefPtr const&); - OwnPtr m_stacking_context; Optional m_overflow_data; diff --git a/Tests/LibWeb/Text/expected/display_list/simple-overflow-hidden.txt b/Tests/LibWeb/Text/expected/display_list/simple-overflow-hidden.txt index 06526511e7a..2d3e6a491a7 100644 --- a/Tests/LibWeb/Text/expected/display_list/simple-overflow-hidden.txt +++ b/Tests/LibWeb/Text/expected/display_list/simple-overflow-hidden.txt @@ -1,27 +1,9 @@ SaveLayer PushStackingContext PushStackingContext -Save -AddClipRect rect=[10,10 200x100] -Restore FillPathUsingColor -Save -AddClipRect rect=[10,10 200x100] -Restore -Save -AddClipRect rect=[10,10 200x100] FillRect rect=[10,10 300x150] color=rgb(240, 128, 128) -Restore -Save -AddClipRect rect=[10,10 200x100] -Restore -Save -AddClipRect rect=[10,10 200x100] DrawGlyphRun rect=[10,10 38x18] translation=[10,23.796875] color=rgb(0, 0, 0) scale=1 -Restore -Save -AddClipRect rect=[10,10 200x100] -Restore PopStackingContext PopStackingContext Restore