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: