From 76d153630717946f17d7bc4b2ebadf8d045f80d5 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 8 Feb 2024 17:30:07 +0100 Subject: [PATCH] LibWeb: Optimize scroll offset and clip state recalculation In this commit we have optimized the handling of scroll offsets and clip rectangles to improve performance. Previously, the process involved multiple full traversals of the paintable tree before each repaint, which was highly inefficient, especially on pages with a large number of paintables. The steps were: 1. Traverse the paintable tree to identify all boxes with scrollable or clipped overflow. 2. Gather the accumulated scroll offset or clip rectangle for each box. 3. Perform another traversal to apply the corresponding scroll offset and clip rectangle to each paintable. To address this, we've adopted a new strategy that separates the assignment of the scroll/clip frame from the refresh of accumulated scroll offsets and clip rectangles, thus reducing the workload: 1. Post-relayout: Identify all boxes with overflow and link each paintable to the state of its containing scroll/clip frame. 2. Pre-repaint: Update the clip rectangle and scroll offset only in the previously identified boxes. This adjustment ensures that the costly tree traversals are only necessary after a relayout, substantially decreasing the amount of work required before each repaint. --- Userland/Libraries/LibWeb/DOM/Document.cpp | 5 + Userland/Libraries/LibWeb/HTML/Navigable.cpp | 19 +-- .../LibWeb/Painting/InlinePaintable.cpp | 37 ++++-- .../LibWeb/Painting/InlinePaintable.h | 13 +- .../LibWeb/Painting/PaintableBox.cpp | 60 ++++++--- .../Libraries/LibWeb/Painting/PaintableBox.h | 29 +++-- .../LibWeb/Painting/ViewportPaintable.cpp | 116 ++++++++++-------- .../LibWeb/Painting/ViewportPaintable.h | 14 ++- 8 files changed, 189 insertions(+), 104 deletions(-) diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 64247f96dd..747183156c 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -1064,6 +1064,11 @@ void Document::update_layout() set_needs_to_resolve_paint_only_properties(); if (navigable()->is_traversable()) { + // NOTE: The assignment of scroll frames only needs to occur for traversables because they take care of all + // nested navigable documents. + paintable()->assign_scroll_frames(); + paintable()->assign_clip_frames(); + page().client().page_did_layout(); } diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.cpp b/Userland/Libraries/LibWeb/HTML/Navigable.cpp index 502c3e37fe..6288908de3 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Userland/Libraries/LibWeb/HTML/Navigable.cpp @@ -2108,21 +2108,24 @@ void Navigable::paint(Painting::RecordingPainter& recording_painter, PaintConfig document->update_paint_and_hit_testing_properties_if_needed(); - HashMap scroll_frames; + auto& viewport_paintable = *document->paintable(); + + // NOTE: We only need to refresh the scroll state for traversables because they are responsible + // for tracking the state of all nested navigables. if (is_traversable()) { - document->paintable()->assign_scroll_frame_ids(scroll_frames); - document->paintable()->assign_clip_rectangles(); + viewport_paintable.refresh_scroll_state(); + viewport_paintable.refresh_clip_state(); } - document->paintable()->paint_all_phases(context); + viewport_paintable.paint_all_phases(context); // FIXME: Support scrollable frames inside iframes. if (is_traversable()) { Vector scroll_offsets_by_frame_id; - 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; + scroll_offsets_by_frame_id.resize(viewport_paintable.scroll_state.size()); + for (auto [_, scrollable_frame] : viewport_paintable.scroll_state) { + 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/InlinePaintable.cpp b/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp index 7b477dc34b..0de7ec5e91 100644 --- a/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp @@ -27,23 +27,44 @@ Layout::InlineNode const& InlinePaintable::layout_node() const return static_cast(Paintable::layout_node()); } +Optional InlinePaintable::scroll_frame_id() const +{ + if (m_enclosing_scroll_frame) + return m_enclosing_scroll_frame->id; + return {}; +} + +Optional InlinePaintable::enclosing_scroll_frame_offset() const +{ + if (m_enclosing_scroll_frame) + return m_enclosing_scroll_frame->offset; + return {}; +} + +Optional InlinePaintable::clip_rect() const +{ + if (m_enclosing_clip_frame) + return m_enclosing_clip_frame->rect; + return {}; +} + void InlinePaintable::before_paint(PaintContext& context, PaintPhase) const { - if (m_scroll_frame_id.has_value()) { + if (scroll_frame_id().has_value()) { context.recording_painter().save(); - context.recording_painter().set_scroll_frame_id(m_scroll_frame_id.value()); + context.recording_painter().set_scroll_frame_id(scroll_frame_id().value()); } - if (m_clip_rect.has_value()) { + if (clip_rect().has_value()) { context.recording_painter().save(); - context.recording_painter().add_clip_rect(context.enclosing_device_rect(*m_clip_rect).to_type()); + context.recording_painter().add_clip_rect(context.enclosing_device_rect(*clip_rect()).to_type()); } } void InlinePaintable::after_paint(PaintContext& context, PaintPhase) const { - if (m_clip_rect.has_value()) + if (clip_rect().has_value()) context.recording_painter().restore(); - if (m_scroll_frame_id.has_value()) + if (scroll_frame_id().has_value()) context.recording_painter().restore(); } @@ -186,8 +207,8 @@ Optional InlinePaintable::hit_test(CSSPixelPoint position, HitTes return {}; auto position_adjusted_by_scroll_offset = position; - if (m_enclosing_scroll_frame_offset.has_value()) - position_adjusted_by_scroll_offset.translate_by(-m_enclosing_scroll_frame_offset.value()); + if (enclosing_scroll_frame_offset().has_value()) + position_adjusted_by_scroll_offset.translate_by(-enclosing_scroll_frame_offset().value()); for (auto& fragment : m_fragments) { if (fragment.paintable().stacking_context()) diff --git a/Userland/Libraries/LibWeb/Painting/InlinePaintable.h b/Userland/Libraries/LibWeb/Painting/InlinePaintable.h index d80ed3efa8..ac65a752f7 100644 --- a/Userland/Libraries/LibWeb/Painting/InlinePaintable.h +++ b/Userland/Libraries/LibWeb/Painting/InlinePaintable.h @@ -37,9 +37,12 @@ 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_enclosing_scroll_frame_offset(CSSPixelPoint offset) { m_enclosing_scroll_frame_offset = offset; } - void set_clip_rect(Optional rect) { m_clip_rect = rect; } + 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; } + + Optional scroll_frame_id() const; + Optional enclosing_scroll_frame_offset() const; + Optional clip_rect() const; private: InlinePaintable(Layout::InlineNode const&); @@ -47,9 +50,9 @@ private: template void for_each_fragment(Callback) const; - Optional m_scroll_frame_id; - Optional m_enclosing_scroll_frame_offset; Optional m_clip_rect; + RefPtr m_enclosing_scroll_frame; + RefPtr m_enclosing_clip_frame; Vector m_box_shadow_data; Vector m_fragments; diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index eeb6b7e330..9e0557a898 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -187,6 +187,34 @@ Optional PaintableBox::get_clip_rect() const return {}; } +Optional PaintableBox::scroll_frame_id() const +{ + if (m_enclosing_scroll_frame) + return m_enclosing_scroll_frame->id; + return {}; +} + +Optional PaintableBox::enclosing_scroll_frame_offset() const +{ + if (m_enclosing_scroll_frame) + return m_enclosing_scroll_frame->offset; + return {}; +} + +Optional PaintableBox::clip_rect() const +{ + if (m_enclosing_clip_frame) + return m_enclosing_clip_frame->rect; + return {}; +} + +Optional PaintableBox::corner_clip_radii() const +{ + if (m_enclosing_clip_frame) + return m_enclosing_clip_frame->corner_clip_radii; + return {}; +} + void PaintableBox::before_paint(PaintContext& context, [[maybe_unused]] PaintPhase phase) const { if (!is_visible()) @@ -378,15 +406,15 @@ BorderRadiiData PaintableBox::normalized_border_radii_data(ShrinkRadiiForBorders void PaintableBox::apply_scroll_offset(PaintContext& context, PaintPhase) const { - if (m_scroll_frame_id.has_value()) { + if (scroll_frame_id().has_value()) { context.recording_painter().save(); - context.recording_painter().set_scroll_frame_id(m_scroll_frame_id.value()); + context.recording_painter().set_scroll_frame_id(scroll_frame_id().value()); } } void PaintableBox::reset_scroll_offset(PaintContext& context, PaintPhase) const { - if (m_scroll_frame_id.has_value()) + if (scroll_frame_id().has_value()) context.recording_painter().restore(); } @@ -395,8 +423,8 @@ void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase ph if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::Foreground, PaintPhase::Outline)) return; - if (m_clip_rect.has_value()) { - auto overflow_clip_rect = m_clip_rect.value(); + if (clip_rect().has_value()) { + auto overflow_clip_rect = 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()) { @@ -410,12 +438,12 @@ void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase ph m_clipping_overflow = true; 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()) { + if (corner_clip_radii().has_value()) { VERIFY(!m_corner_clipper_id.has_value()); m_corner_clipper_id = context.allocate_corner_clipper_id(); - auto corner_radii = m_corner_clip_radii->as_corners(context); + auto corner_radii = corner_clip_radii()->as_corners(context); if (corner_radii.has_any_radius()) - context.recording_painter().sample_under_corners(*m_corner_clipper_id, m_corner_clip_radii->as_corners(context), context.rounded_device_rect(overflow_clip_rect).to_type(), CornerClip::Outside); + context.recording_painter().sample_under_corners(*m_corner_clipper_id, corner_clip_radii()->as_corners(context), context.rounded_device_rect(overflow_clip_rect).to_type(), CornerClip::Outside); } } } @@ -427,11 +455,11 @@ void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase ph if (m_clipping_overflow) { m_clipping_overflow = false; - if (m_corner_clip_radii.has_value()) { + if (corner_clip_radii().has_value()) { VERIFY(m_corner_clipper_id.has_value()); - auto corner_radii = m_corner_clip_radii->as_corners(context); + auto corner_radii = corner_clip_radii()->as_corners(context); if (corner_radii.has_any_radius()) - context.recording_painter().blit_corner_clipping(*m_corner_clipper_id, context.rounded_device_rect(*m_clip_rect).to_type()); + context.recording_painter().blit_corner_clipping(*m_corner_clipper_id, context.rounded_device_rect(*clip_rect()).to_type()); m_corner_clipper_id = {}; } context.recording_painter().restore(); @@ -600,6 +628,10 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const Optional corner_clip_id; auto clip_box = absolute_padding_box_rect(); + if (get_clip_rect().has_value()) { + clip_box.intersect(get_clip_rect().value()); + should_clip_overflow = true; + } if (enclosing_scroll_frame_offset().has_value()) clip_box.translate_by(enclosing_scroll_frame_offset().value()); if (should_clip_overflow) { @@ -687,9 +719,9 @@ Optional PaintableBox::hit_test(CSSPixelPoint position, HitTestTy if (layout_box().is_viewport()) { auto& viewport_paintable = const_cast(static_cast(*this)); viewport_paintable.build_stacking_context_tree_if_needed(); - HashMap scroll_frames; - viewport_paintable.assign_scroll_frame_ids(scroll_frames); - viewport_paintable.assign_clip_rectangles(); + viewport_paintable.document().update_paint_and_hit_testing_properties_if_needed(); + viewport_paintable.refresh_scroll_state(); + viewport_paintable.refresh_clip_state(); return stacking_context()->hit_test(position, type); } diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index 4eeb5f95c7..d38dba7a32 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -14,6 +14,16 @@ namespace Web::Painting { +struct ScrollFrame : public RefCounted { + i32 id { -1 }; + CSSPixelPoint offset; +}; + +struct ClipFrame : public RefCounted { + CSSPixelRect rect; + Optional corner_clip_radii; +}; + class PaintableBox : public Paintable { JS_CELL(PaintableBox, Paintable); @@ -193,14 +203,13 @@ public: 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_enclosing_scroll_frame_offset(CSSPixelPoint offset) { m_enclosing_scroll_frame_offset = offset; } - void set_corner_clip_radii(BorderRadiiData const& corner_radii) { m_corner_clip_radii = corner_radii; } + 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; } - Optional scroll_frame_id() const { return m_scroll_frame_id; } - Optional enclosing_scroll_frame_offset() const { return m_enclosing_scroll_frame_offset; } - Optional clip_rect() const { return m_clip_rect; } + Optional scroll_frame_id() const; + Optional enclosing_scroll_frame_offset() const; + Optional clip_rect() const; + Optional corner_clip_radii() const; protected: explicit PaintableBox(Layout::Box const&); @@ -227,10 +236,8 @@ private: mutable bool m_clipping_overflow { false }; mutable Optional m_corner_clipper_id; - Optional m_clip_rect; - Optional m_scroll_frame_id; - Optional m_enclosing_scroll_frame_offset; - Optional m_corner_clip_radii; + RefPtr m_enclosing_scroll_frame; + RefPtr m_enclosing_clip_frame; Optional m_override_borders_data; Optional m_table_cell_coordinates; diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp index f2a8f449fb..5ee40397ca 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp @@ -58,37 +58,28 @@ void ViewportPaintable::paint_all_phases(PaintContext& context) stacking_context()->paint(context); } -void ViewportPaintable::assign_scroll_frame_ids(HashMap& scroll_frames) const +void ViewportPaintable::assign_scroll_frames() { - i32 next_id = 0; - // Collect scroll frames with their offsets (accumulated offset for nested scroll frames). + int 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.containing_block(); - while (ancestor) { - 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 }); + auto scroll_frame = adopt_ref(*new ScrollFrame()); + scroll_frame->id = next_id++; + scroll_state.set(&paintable_box, move(scroll_frame)); } 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 (auto scroll_frame = scroll_state.get(&block_paintable_box); scroll_frame.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); - const_cast(paintable_box).set_enclosing_scroll_frame_offset(scroll_frame_id->offset); + const_cast(paintable_box).set_enclosing_scroll_frame(scroll_frame.value()); } 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); - const_cast(inline_paintable).set_enclosing_scroll_frame_offset(scroll_frame_id->offset); + const_cast(inline_paintable).set_enclosing_scroll_frame(scroll_frame.value()); } break; } @@ -97,19 +88,64 @@ void ViewportPaintable::assign_scroll_frame_ids(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(); + auto has_hidden_overflow = overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible; + if (has_hidden_overflow || paintable_box.get_clip_rect().has_value()) { + auto clip_frame = adopt_ref(*new ClipFrame()); + clip_state.set(&paintable_box, move(clip_frame)); + } + return TraversalDecision::Continue; + }); + + 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 clip_frame = clip_state.get(&block_paintable_box); clip_frame.has_value()) { + if (paintable.is_paintable_box()) { + auto const& paintable_box = static_cast(paintable); + const_cast(paintable_box).set_enclosing_clip_frame(clip_frame.value()); + } else if (paintable.is_inline_paintable()) { + auto const& inline_paintable = static_cast(paintable); + const_cast(inline_paintable).set_enclosing_clip_frame(clip_frame.value()); + } + break; + } + } + return TraversalDecision::Continue; + }); +} + +void ViewportPaintable::refresh_scroll_state() +{ + for (auto& it : scroll_state) { + auto const& paintable_box = *it.key; + auto& scroll_frame = *it.value; + CSSPixelPoint offset; + for (auto const* block = &paintable_box.layout_box(); block; block = block->containing_block()) { + auto const& block_paintable_box = *block->paintable_box(); + offset.translate_by(block_paintable_box.scroll_offset()); + } + scroll_frame.offset = -offset; + } +} + +void ViewportPaintable::refresh_clip_state() +{ + for (auto& it : clip_state) { + auto const& paintable_box = *it.key; + auto& clip_frame = *it.value; 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()) { + for (auto const* 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(); @@ -120,39 +156,15 @@ void ViewportPaintable::assign_clip_rectangles() } 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); - if (border_radii_data.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(border_radii_data); - } - clip_rect = *containing_block_clip_rect; - break; - } + auto border_radii_data = paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes); + if (border_radii_data.has_any_radius()) { + // FIXME: Border radii of all boxes in containing block chain should be taken into account. + clip_frame.corner_clip_radii = border_radii_data; } - 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; - } - } - return TraversalDecision::Continue; - }); + clip_frame.rect = *clip_rect; + } } static Painting::BorderRadiiData normalize_border_radii_data(Layout::Node const& node, CSSPixelRect const& rect, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius) diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h index a246dcd969..f75aab3602 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h @@ -20,12 +20,14 @@ public: void paint_all_phases(PaintContext&); void build_stacking_context_tree_if_needed(); - struct ScrollFrame { - i32 id { -1 }; - CSSPixelPoint offset; - }; - void assign_scroll_frame_ids(HashMap&) const; - void assign_clip_rectangles(); + HashMap> scroll_state; + void assign_scroll_frames(); + void refresh_scroll_state(); + + HashMap> clip_state; + void assign_clip_frames(); + void refresh_clip_state(); + void resolve_paint_only_properties(); private: