From ac6b3c989d688e23c3245542b0c4c865f0f7c3e1 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Fri, 29 Dec 2023 06:10:32 +0100 Subject: [PATCH] LibWeb: Apply scroll boxes offsets after painting commands recording With this change, instead of applying scroll offsets during the recording of the painting command list, we do the following: 1. Collect all boxes with scrollable overflow into a PaintContext, each with an id and the total amount of scrolling offset accumulated from ancestor scrollable boxes. 2. During the recording phase assign a corresponding scroll_frame_id to each command that paints content within a scrollable box. 3. Before executing the recorded commands, translate each command that has a scroll_frame_id by the accumulated scroll offset. This approach has following advantages: - Implementing nested scrollables becomes much simpler, as the recording phase only requires the correct assignment of the nearest scrollable's scroll_frame_id, while the accumulated offset from ancestors is applied subsequently. - The recording of painting commands is not tied to a specific offset within scrollable boxes, which means in the future, it will be possible to update the scrolling offset and repaint without the need to re-record painting commands. --- ...-box-with-nested-stacking-context-ref.html | 28 +++++ ...able-box-with-nested-stacking-context.html | 46 +++++++ Userland/Libraries/LibGfx/TextLayout.h | 10 ++ .../Libraries/LibWeb/Painting/PaintContext.h | 11 +- .../LibWeb/Painting/PaintableBox.cpp | 20 +-- .../Libraries/LibWeb/Painting/PaintableBox.h | 2 +- .../LibWeb/Painting/RecordingPainter.cpp | 50 +++++++- .../LibWeb/Painting/RecordingPainter.h | 117 +++++++++++++++++- .../LibWeb/Painting/StackingContext.cpp | 19 ++- .../LibWeb/Painting/ViewportPaintable.cpp | 18 +++ .../LibWeb/Painting/ViewportPaintable.h | 2 + Userland/Services/WebContent/PageClient.cpp | 8 ++ 12 files changed, 307 insertions(+), 24 deletions(-) create mode 100644 Tests/LibWeb/Ref/reference/scrollable-box-with-nested-stacking-context-ref.html create mode 100644 Tests/LibWeb/Ref/scrollable-box-with-nested-stacking-context.html diff --git a/Tests/LibWeb/Ref/reference/scrollable-box-with-nested-stacking-context-ref.html b/Tests/LibWeb/Ref/reference/scrollable-box-with-nested-stacking-context-ref.html new file mode 100644 index 0000000000..5d6e2c48d3 --- /dev/null +++ b/Tests/LibWeb/Ref/reference/scrollable-box-with-nested-stacking-context-ref.html @@ -0,0 +1,28 @@ + +
+
+
+
+
diff --git a/Tests/LibWeb/Ref/scrollable-box-with-nested-stacking-context.html b/Tests/LibWeb/Ref/scrollable-box-with-nested-stacking-context.html new file mode 100644 index 0000000000..fc03414561 --- /dev/null +++ b/Tests/LibWeb/Ref/scrollable-box-with-nested-stacking-context.html @@ -0,0 +1,46 @@ + + +
+
+ Lots of scrollable content here! +
box
+
+
box
+
+ diff --git a/Userland/Libraries/LibGfx/TextLayout.h b/Userland/Libraries/LibGfx/TextLayout.h index 32dbd46921..b002ddcd35 100644 --- a/Userland/Libraries/LibGfx/TextLayout.h +++ b/Userland/Libraries/LibGfx/TextLayout.h @@ -80,12 +80,22 @@ struct DrawGlyph { FloatPoint position; u32 code_point; NonnullRefPtr font; + + void translate_by(FloatPoint const& delta) + { + position.translate_by(delta); + } }; struct DrawEmoji { FloatPoint position; Gfx::Bitmap const* emoji; NonnullRefPtr font; + + void translate_by(FloatPoint const& delta) + { + position.translate_by(delta); + } }; using DrawGlyphOrEmoji = Variant; diff --git a/Userland/Libraries/LibWeb/Painting/PaintContext.h b/Userland/Libraries/LibWeb/Painting/PaintContext.h index 81cfeec9dc..3cc8ba1767 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintContext.h +++ b/Userland/Libraries/LibWeb/Painting/PaintContext.h @@ -72,11 +72,14 @@ public: double device_pixels_per_css_pixel() const { return m_device_pixels_per_css_pixel; } - CSSPixelPoint scroll_offset() const { return m_scroll_offset; } - void translate_scroll_offset_by(CSSPixelPoint offset) { m_scroll_offset.translate_by(offset); } - 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; @@ -85,9 +88,9 @@ private: bool m_should_show_line_box_borders { false }; bool m_should_paint_overlay { true }; bool m_focus { false }; - CSSPixelPoint m_scroll_offset; Gfx::AffineTransform m_svg_transform; u32 m_next_corner_clipper_id { 0 }; + HashMap m_scroll_frames; }; } diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index b160082ec0..1ee868c4aa 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -410,16 +410,17 @@ Optional PaintableBox::calculate_overflow_clipped_rect() const void PaintableBox::apply_scroll_offset(PaintContext& context, PaintPhase) const { - auto scroll_offset = -this->scroll_offset(); - context.translate_scroll_offset_by(scroll_offset); - context.recording_painter().translate({ context.enclosing_device_pixels(scroll_offset.x()), context.enclosing_device_pixels(scroll_offset.y()) }); + if (context.scroll_frames().contains(this)) { + context.recording_painter().save(); + context.recording_painter().set_scroll_frame_id(context.scroll_frames().get(this)->id); + } } void PaintableBox::reset_scroll_offset(PaintContext& context, PaintPhase) const { - auto scroll_offset = this->scroll_offset(); - context.translate_scroll_offset_by(scroll_offset); - context.recording_painter().translate({ context.enclosing_device_pixels(scroll_offset.x()), context.enclosing_device_pixels(scroll_offset.y()) }); + if (context.scroll_frames().contains(this)) { + context.recording_painter().restore(); + } } void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase phase) const @@ -445,10 +446,7 @@ void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase ph if (!m_clipping_overflow) { context.recording_painter().save(); - auto scroll_offset = context.scroll_offset(); - context.recording_painter().translate({ -context.enclosing_device_pixels(scroll_offset.x()), -context.enclosing_device_pixels(scroll_offset.y()) }); context.recording_painter().add_clip_rect(context.enclosing_device_rect(*clip_rect).to_type()); - context.recording_painter().translate({ context.enclosing_device_pixels(scroll_offset.x()), context.enclosing_device_pixels(scroll_offset.y()) }); m_clipping_overflow = true; } @@ -826,10 +824,12 @@ Optional PaintableWithLines::hit_test(CSSPixelPoint position, Hit return {}; } -PaintableBox const* PaintableBox::nearest_scrollable_ancestor() const +PaintableBox const* PaintableBox::nearest_scrollable_ancestor_within_stacking_context() const { auto* ancestor = parent(); while (ancestor) { + if (ancestor->stacking_context_rooted_here()) + return nullptr; if (ancestor->is_paintable_box() && static_cast(ancestor)->has_scrollable_overflow()) return static_cast(ancestor); ancestor = ancestor->parent(); diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index adf447e34e..c30ab28175 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -194,7 +194,7 @@ 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; } - PaintableBox const* nearest_scrollable_ancestor() const; + PaintableBox const* nearest_scrollable_ancestor_within_stacking_context() const; protected: explicit PaintableBox(Layout::Box const&); diff --git a/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp b/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp index a891220ca6..e389f45f30 100644 --- a/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp +++ b/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp @@ -9,11 +9,31 @@ namespace Web::Painting { +void DrawGlyphRun::translate_by(Gfx::IntPoint const& offset) +{ + for (auto& glyph : glyph_run) { + glyph.visit([&](auto& glyph) { + glyph.translate_by(offset.to_type()); + }); + } + rect.translate_by(offset); +} + Gfx::IntRect PaintOuterBoxShadow::bounding_rect() const { return get_outer_box_shadow_bounding_rect(outer_box_shadow_params); } +void PaintOuterBoxShadow::translate_by(Gfx::IntPoint const& offset) +{ + outer_box_shadow_params.device_content_rect.translate_by(offset.to_type()); +} + +void PaintInnerBoxShadow::translate_by(Gfx::IntPoint const& offset) +{ + outer_box_shadow_params.device_content_rect.translate_by(offset.to_type()); +} + Gfx::IntRect SampleUnderCorners::bounding_rect() const { return border_rect; @@ -303,8 +323,8 @@ void RecordingPainter::push_stacking_context(PushStackingContextParams params) void RecordingPainter::pop_stacking_context() { - push_command(PopStackingContext {}); m_state_stack.take_last(); + push_command(PopStackingContext {}); } void RecordingPainter::paint_frame(Gfx::IntRect rect, Palette palette, Gfx::FrameStyle style) @@ -405,11 +425,27 @@ static Optional command_bounding_rectangle(PaintingCommand const& }); } +void RecordingPainter::apply_scroll_offsets(Vector const& offsets_by_frame_id) +{ + for (auto& command_with_scroll_id : m_painting_commands) { + if (command_with_scroll_id.scroll_frame_id.has_value()) { + auto const& scroll_frame_id = command_with_scroll_id.scroll_frame_id.value(); + auto const& scroll_offset = offsets_by_frame_id[scroll_frame_id]; + command_with_scroll_id.command.visit( + [&](auto& command) { + if constexpr (requires { command.translate_by(scroll_offset); }) + command.translate_by(scroll_offset); + }); + } + } +} + void RecordingPainter::execute(PaintingCommandExecutor& executor) { if (executor.needs_prepare_glyphs_texture()) { HashMap> unique_glyphs; - for (auto& command : m_painting_commands) { + for (auto& command_with_scroll_id : m_painting_commands) { + auto& command = command_with_scroll_id.command; if (command.has()) { for (auto const& glyph_or_emoji : command.get().glyph_run) { if (glyph_or_emoji.has()) { @@ -424,7 +460,8 @@ void RecordingPainter::execute(PaintingCommandExecutor& executor) if (executor.needs_update_immutable_bitmap_texture_cache()) { HashMap immutable_bitmaps; - for (auto const& command : m_painting_commands) { + for (auto const& command_with_scroll_id : m_painting_commands) { + auto& command = command_with_scroll_id.command; if (command.has()) { auto const& immutable_bitmap = command.get().bitmap; immutable_bitmaps.set(immutable_bitmap->id(), immutable_bitmap.ptr()); @@ -436,7 +473,8 @@ void RecordingPainter::execute(PaintingCommandExecutor& executor) HashTable skipped_sample_corner_commands; size_t next_command_index = 0; while (next_command_index < m_painting_commands.size()) { - auto& command = m_painting_commands[next_command_index++]; + auto& command_with_scroll_id = m_painting_commands[next_command_index++]; + auto& command = command_with_scroll_id.command; auto bounding_rect = command_bounding_rectangle(command); if (bounding_rect.has_value() && (bounding_rect->is_empty() || executor.would_be_fully_clipped_by_painter(*bounding_rect))) { if (command.has()) { @@ -555,9 +593,9 @@ void RecordingPainter::execute(PaintingCommandExecutor& executor) if (result == CommandResult::SkipStackingContext) { auto stacking_context_nesting_level = 1; while (next_command_index < m_painting_commands.size()) { - if (m_painting_commands[next_command_index].has()) { + if (m_painting_commands[next_command_index].command.has()) { stacking_context_nesting_level++; - } else if (m_painting_commands[next_command_index].has()) { + } else if (m_painting_commands[next_command_index].command.has()) { stacking_context_nesting_level--; } diff --git a/Userland/Libraries/LibWeb/Painting/RecordingPainter.h b/Userland/Libraries/LibWeb/Painting/RecordingPainter.h index cadff7287e..9052b25db8 100644 --- a/Userland/Libraries/LibWeb/Painting/RecordingPainter.h +++ b/Userland/Libraries/LibWeb/Painting/RecordingPainter.h @@ -47,6 +47,8 @@ struct DrawGlyphRun { Gfx::IntRect rect; [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset); }; struct DrawText { @@ -59,6 +61,7 @@ struct DrawText { Optional> font {}; [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } }; struct FillRect { @@ -66,6 +69,7 @@ struct FillRect { Color color; [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } }; struct DrawScaledBitmap { @@ -75,6 +79,7 @@ struct DrawScaledBitmap { Gfx::Painter::ScalingMode scaling_mode; [[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; } + void translate_by(Gfx::IntPoint const& offset) { dst_rect.translate_by(offset); } }; struct DrawScaledImmutableBitmap { @@ -84,6 +89,7 @@ struct DrawScaledImmutableBitmap { Gfx::Painter::ScalingMode scaling_mode; [[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; } + void translate_by(Gfx::IntPoint const& offset) { dst_rect.translate_by(offset); } }; struct SetClipRect { @@ -116,6 +122,11 @@ struct PushStackingContext { CSS::ImageRendering image_rendering; StackingContextTransform transform; Optional mask = {}; + + void translate_by(Gfx::IntPoint const& offset) + { + post_transform_translation.translate_by(offset); + } }; struct PopStackingContext { }; @@ -125,16 +136,24 @@ struct PaintLinearGradient { LinearGradientData linear_gradient_data; [[nodiscard]] Gfx::IntRect bounding_rect() const { return gradient_rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + gradient_rect.translate_by(offset); + } }; struct PaintOuterBoxShadow { PaintOuterBoxShadowParams outer_box_shadow_params; [[nodiscard]] Gfx::IntRect bounding_rect() const; + void translate_by(Gfx::IntPoint const& offset); }; struct PaintInnerBoxShadow { PaintOuterBoxShadowParams outer_box_shadow_params; + + void translate_by(Gfx::IntPoint const& offset); }; struct PaintTextShadow { @@ -147,6 +166,7 @@ struct PaintTextShadow { Gfx::IntPoint draw_location; [[nodiscard]] Gfx::IntRect bounding_rect() const { return { draw_location, shadow_bounding_rect.size() }; } + void translate_by(Gfx::IntPoint const& offset) { draw_location.translate_by(offset); } }; struct FillRectWithRoundedCorners { @@ -158,6 +178,7 @@ struct FillRectWithRoundedCorners { Gfx::AntiAliasingPainter::CornerRadius bottom_right_radius; [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } }; struct FillPathUsingColor { @@ -168,6 +189,12 @@ struct FillPathUsingColor { Gfx::FloatPoint aa_translation; [[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + path_bounding_rect.translate_by(offset); + aa_translation.translate_by(offset.to_type()); + } }; struct FillPathUsingPaintStyle { @@ -179,6 +206,12 @@ struct FillPathUsingPaintStyle { Gfx::FloatPoint aa_translation; [[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + path_bounding_rect.translate_by(offset); + aa_translation.translate_by(offset.to_type()); + } }; struct StrokePathUsingColor { @@ -189,6 +222,12 @@ struct StrokePathUsingColor { Gfx::FloatPoint aa_translation; [[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + path_bounding_rect.translate_by(offset); + aa_translation.translate_by(offset.to_type()); + } }; struct StrokePathUsingPaintStyle { @@ -200,6 +239,12 @@ struct StrokePathUsingPaintStyle { Gfx::FloatPoint aa_translation; [[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + path_bounding_rect.translate_by(offset); + aa_translation.translate_by(offset.to_type()); + } }; struct DrawEllipse { @@ -208,6 +253,11 @@ struct DrawEllipse { int thickness; [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + rect.translate_by(offset); + } }; struct FillEllipse { @@ -216,6 +266,11 @@ struct FillEllipse { Gfx::AntiAliasingPainter::BlendMode blend_mode; [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + rect.translate_by(offset); + } }; struct DrawLine { @@ -225,6 +280,12 @@ struct DrawLine { int thickness; Gfx::Painter::LineStyle style; Color alternate_color; + + void translate_by(Gfx::IntPoint const& offset) + { + from.translate_by(offset); + to.translate_by(offset); + } }; struct DrawSignedDistanceField { @@ -234,6 +295,11 @@ struct DrawSignedDistanceField { float smoothing; [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + rect.translate_by(offset); + } }; struct PaintFrame { @@ -242,6 +308,8 @@ struct PaintFrame { Gfx::FrameStyle style; [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } }; struct ApplyBackdropFilter { @@ -250,6 +318,11 @@ struct ApplyBackdropFilter { CSS::ResolvedBackdropFilter backdrop_filter; [[nodiscard]] Gfx::IntRect bounding_rect() const { return backdrop_region; } + + void translate_by(Gfx::IntPoint const& offset) + { + backdrop_region.translate_by(offset); + } }; struct DrawRect { @@ -258,6 +331,8 @@ struct DrawRect { bool rough; [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } }; struct PaintRadialGradient { @@ -267,6 +342,8 @@ struct PaintRadialGradient { Gfx::IntSize size; [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } }; struct PaintConicGradient { @@ -275,6 +352,8 @@ struct PaintConicGradient { Gfx::IntPoint position; [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } }; struct DrawTriangleWave { @@ -283,6 +362,12 @@ struct DrawTriangleWave { Color color; int amplitude; int thickness; + + void translate_by(Gfx::IntPoint const& offset) + { + p1.translate_by(offset); + p2.translate_by(offset); + } }; struct SampleUnderCorners { @@ -292,6 +377,11 @@ struct SampleUnderCorners { CornerClip corner_clip; [[nodiscard]] Gfx::IntRect bounding_rect() const; + + void translate_by(Gfx::IntPoint const& offset) + { + border_rect.translate_by(offset); + } }; struct BlitCornerClipping { @@ -299,6 +389,11 @@ struct BlitCornerClipping { Gfx::IntRect border_rect; [[nodiscard]] Gfx::IntRect bounding_rect() const; + + void translate_by(Gfx::IntPoint const& offset) + { + border_rect.translate_by(offset); + } }; struct PaintBorders { @@ -307,6 +402,11 @@ struct PaintBorders { BordersDataDevicePixels borders_data; [[nodiscard]] Gfx::IntRect bounding_rect() const { return border_rect.to_type(); } + + void translate_by(Gfx::IntPoint const& offset) + { + border_rect.translate_by(offset.to_type()); + } }; using PaintingCommand = Variant< @@ -457,6 +557,11 @@ public: void set_font(Gfx::Font const& font); + void set_scroll_frame_id(i32 id) + { + state().scroll_frame_id = id; + } + void save(); void restore(); @@ -497,20 +602,28 @@ public: m_state_stack.append(State()); } + void apply_scroll_offsets(Vector const& offsets_by_frame_id); + private: struct State { Gfx::AffineTransform translation; Optional clip_rect; + Optional scroll_frame_id; }; State& state() { return m_state_stack.last(); } State const& state() const { return m_state_stack.last(); } void push_command(PaintingCommand command) { - m_painting_commands.append(command); + m_painting_commands.append({ state().scroll_frame_id, command }); } - Vector m_painting_commands; + struct PaintingCommandWithScrollFrame { + Optional scroll_frame_id; + PaintingCommand command; + }; + + Vector m_painting_commands; Vector m_state_stack; }; diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp index 138037f906..f9acba66be 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp @@ -184,8 +184,16 @@ 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_box().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); } @@ -229,7 +237,7 @@ void StackingContext::paint_internal(PaintContext& context) const // Apply scroll offset of nearest scrollable ancestor before painting the positioned descendant. PaintableBox const* nearest_scrollable_ancestor = nullptr; if (paintable.is_paintable_box()) - nearest_scrollable_ancestor = static_cast(paintable).nearest_scrollable_ancestor(); + nearest_scrollable_ancestor = static_cast(paintable).nearest_scrollable_ancestor_within_stacking_context(); if (nearest_scrollable_ancestor) nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground); @@ -265,6 +273,12 @@ void StackingContext::paint_internal(PaintContext& context) const for (auto* child : m_children) { if (!child->paintable_box().is_positioned()) continue; + + PaintableBox const* nearest_scrollable_ancestor = child->paintable_box().nearest_scrollable_ancestor_within_stacking_context(); + + if (nearest_scrollable_ancestor) + nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground); + auto containing_block = child->paintable_box().containing_block(); auto const* containing_block_paintable = containing_block ? containing_block->paintable() : nullptr; if (containing_block_paintable) @@ -273,6 +287,9 @@ void StackingContext::paint_internal(PaintContext& context) const 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_box(), context, PaintPhase::Outline); diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp index 364d5203e8..8f35d4d4f9 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp @@ -57,4 +57,22 @@ void ViewportPaintable::paint_all_phases(PaintContext& context) stacking_context()->paint(context); } +void ViewportPaintable::collect_scroll_frames(PaintContext& context) const +{ + i32 next_id = 0; + 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(); + while (ancestor) { + if (ancestor->is_paintable_box() && static_cast(ancestor)->has_scrollable_overflow()) + offset.translate_by(static_cast(ancestor)->scroll_offset()); + ancestor = ancestor->parent(); + } + 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 1d1d9731eb..b0f5e8e943 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h @@ -20,6 +20,8 @@ public: void paint_all_phases(PaintContext&); void build_stacking_context_tree_if_needed(); + void collect_scroll_frames(PaintContext&) const; + private: void build_stacking_context_tree(); diff --git a/Userland/Services/WebContent/PageClient.cpp b/Userland/Services/WebContent/PageClient.cpp index b6ec6d3527..d19a8b64bb 100644 --- a/Userland/Services/WebContent/PageClient.cpp +++ b/Userland/Services/WebContent/PageClient.cpp @@ -188,8 +188,16 @@ void PageClient::paint(Web::DevicePixelRect const& content_rect, Gfx::Bitmap& ta context.set_should_paint_overlay(paint_options.paint_overlay == Web::PaintOptions::PaintOverlay::Yes); context.set_device_viewport_rect(content_rect); context.set_has_focus(m_has_focus); + + document->paintable()->collect_scroll_frames(context); document->paintable()->paint_all_phases(context); + 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(); + recording_painter.apply_scroll_offsets(scroll_offsets_by_frame_id); + if (s_use_gpu_painter) { #ifdef HAS_ACCELERATED_GRAPHICS Web::Painting::PaintingCommandExecutorGPU painting_command_executor(*m_accelerated_graphics_context, target);