From bf25568703d11482f7fb37d804994bbee6db2424 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Wed, 12 Jul 2023 18:55:23 +0200 Subject: [PATCH] LibWeb: Bring measuring of scrollable overflow closer to spec Importantly, we now only consider overflow from descendants with explicltly visible overflow, and only from descendants that have the measured box as their containing block. Also, we now measure scrollable overflow for all boxes, not just scroll containers. This will allow us to fix a long-standing paint problem in the next commit. --- Userland/Libraries/LibWeb/Dump.cpp | 4 +- .../Libraries/LibWeb/Layout/LayoutState.cpp | 96 ++++++++++++------- .../Libraries/LibWeb/Painting/PaintableBox.h | 9 +- Userland/Services/WebContent/PageHost.cpp | 2 +- 4 files changed, 67 insertions(+), 44 deletions(-) diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp index 64641fba48..14ba654afa 100644 --- a/Userland/Libraries/LibWeb/Dump.cpp +++ b/Userland/Libraries/LibWeb/Dump.cpp @@ -825,8 +825,8 @@ void dump_tree(StringBuilder& builder, Painting::Paintable const& paintable, boo auto const& paintable_box = static_cast(paintable); builder.appendff(" {}", paintable_box.absolute_border_box_rect()); - if (paintable_box.has_overflow()) { - builder.appendff(" overflow: {}", paintable_box.scrollable_overflow_rect().value()); + if (paintable_box.has_scrollable_overflow()) { + builder.appendff(" overflow: {}", paintable_box.scrollable_overflow_rect()); } } builder.append("\n"sv); diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp index fc1d772016..c02b01d084 100644 --- a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp +++ b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace Web::Layout { @@ -64,36 +65,70 @@ LayoutState::UsedValues const& LayoutState::get(NodeWithStyleAndBoxModelMetrics return *new_used_values_ptr; } -static void measure_scrollable_overflow(LayoutState const& state, Box const& box, CSSPixels& bottom_edge, CSSPixels& right_edge) +// https://www.w3.org/TR/css-overflow-3/#scrollable-overflow +static CSSPixelRect measure_scrollable_overflow(Box const& box) { - auto const* maybe_box_state = state.used_values_per_layout_node.get(&box).value_or(nullptr); - if (!maybe_box_state) - return; - auto const& box_state = *maybe_box_state; - auto scroll_container_border_box = CSSPixelRect { - box_state.offset.translated(-box_state.border_box_left(), -box_state.border_box_top()), - { box_state.border_box_width(), box_state.border_box_height() } - }; + if (!box.paintable_box()) + return {}; - bottom_edge = max(bottom_edge, scroll_container_border_box.bottom()); - right_edge = max(right_edge, scroll_container_border_box.right()); + auto& paintable_box = const_cast(*box.paintable_box()); - if (box.children_are_inline()) { - if (!box_state.line_boxes.is_empty()) { - bottom_edge = max(bottom_edge, scroll_container_border_box.y() + box_state.line_boxes.last().bottom()); - for (auto& line_box : box_state.line_boxes) { - if (line_box.fragments().is_empty()) - continue; - right_edge = max(right_edge, scroll_container_border_box.x() + line_box.fragments().last().width()); - } + if (paintable_box.scrollable_overflow_rect().has_value()) + return paintable_box.scrollable_overflow_rect().value(); + + // The scrollable overflow area is the union of: + + // - The scroll container’s own padding box. + auto scrollable_overflow_rect = paintable_box.absolute_padding_box_rect(); + + // - All line boxes directly contained by the scroll container. + if (box.is_block_container() && box.children_are_inline()) { + auto const& line_boxes = verify_cast(*box.paintable_box()).line_boxes(); + for (auto const& line_box : line_boxes) { + scrollable_overflow_rect = scrollable_overflow_rect.united(line_box.absolute_rect()); } - } else { - // FIXME: Only check boxes for whom `box` is the containing block. - box.for_each_child_of_type([&](Box const& child) { - measure_scrollable_overflow(state, child, bottom_edge, right_edge); + } + + // - The border boxes of all boxes for which it is the containing block + // and whose border boxes are positioned not wholly in the negative scrollable overflow region, + // FIXME: accounting for transforms by projecting each box onto the plane of the element that establishes its 3D rendering context. [CSS3-TRANSFORMS] + if (!box.children_are_inline()) { + box.for_each_child_of_type([&box, &scrollable_overflow_rect](Box const& child) { + if (!child.paintable_box()) + return IterationDecision::Continue; + + auto child_border_box = child.paintable_box()->absolute_border_box_rect(); + // NOTE: Here we check that the child is not wholly in the negative scrollable overflow region. + if (child_border_box.bottom() > 0 && child_border_box.right() > 0) + scrollable_overflow_rect = scrollable_overflow_rect.united(child_border_box); + + // - The scrollable overflow areas of all of the above boxes + // (including zero-area boxes and accounting for transforms as described above), + // provided they themselves have overflow: visible (i.e. do not themselves trap the overflow) + // and that scrollable overflow is not already clipped (e.g. by the clip property or the contain property). + if (is(box) || child.computed_values().overflow_x() == CSS::Overflow::Visible || child.computed_values().overflow_y() == CSS::Overflow::Visible) { + auto child_scrollable_overflow = measure_scrollable_overflow(child); + if (is(box) || child.computed_values().overflow_x() == CSS::Overflow::Visible) + scrollable_overflow_rect.unite_horizontally(child_scrollable_overflow); + if (is(box) || child.computed_values().overflow_y() == CSS::Overflow::Visible) + scrollable_overflow_rect.unite_vertically(child_scrollable_overflow); + } + return IterationDecision::Continue; }); } + + // FIXME: - The margin areas of grid item and flex item boxes for which the box establishes a containing block. + + // FIXME: - Additional padding added to the end-side of the scrollable overflow rectangle as necessary + // to enable a scroll position that satisfies the requirements of place-content: end alignment. + + paintable_box.set_overflow_data(Painting::PaintableBox::OverflowData { + .scrollable_overflow_rect = scrollable_overflow_rect, + .has_scrollable_overflow = !paintable_box.absolute_padding_box_rect().contains(scrollable_overflow_rect), + }); + + return scrollable_overflow_rect; } void LayoutState::commit() @@ -158,20 +193,7 @@ void LayoutState::commit() if (!used_values.node().is_box()) continue; auto const& box = static_cast(used_values.node()); - if (!box.is_scroll_container()) - continue; - CSSPixels bottom_edge = 0; - CSSPixels right_edge = 0; - measure_scrollable_overflow(*this, box, bottom_edge, right_edge); - - auto padding_box = box.paintable_box()->absolute_padding_box_rect(); - - if (bottom_edge > padding_box.height() || right_edge > padding_box.width()) { - Painting::PaintableBox::OverflowData overflow_data; - overflow_data.scrollable_overflow_rect = padding_box; - overflow_data.scrollable_overflow_rect.set_size(right_edge, bottom_edge); - const_cast(*box.paintable_box()).set_overflow_data(overflow_data); - } + measure_scrollable_overflow(box); } for (auto* text_node : text_nodes) diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index 7ec51ac61e..8b388c8945 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -31,7 +31,8 @@ public: struct OverflowData { CSSPixelRect scrollable_overflow_rect; - CSSPixelPoint scroll_offset; + bool has_scrollable_overflow { false }; + CSSPixelPoint scroll_offset {}; }; CSSPixelRect absolute_rect() const; @@ -95,9 +96,9 @@ public: CSSPixels absolute_y() const { return absolute_rect().y(); } CSSPixelPoint absolute_position() const { return absolute_rect().location(); } - bool has_overflow() const { return m_overflow_data.has_value(); } + [[nodiscard]] bool has_scrollable_overflow() const { return m_overflow_data->has_scrollable_overflow; } - Optional scrollable_overflow_rect() const + [[nodiscard]] Optional scrollable_overflow_rect() const { if (!m_overflow_data.has_value()) return {}; @@ -106,7 +107,7 @@ public: Optional calculate_overflow_clipped_rect() const; - void set_overflow_data(Optional data) { m_overflow_data = move(data); } + void set_overflow_data(OverflowData data) { m_overflow_data = move(data); } void set_containing_line_box_fragment(Optional); StackingContext* stacking_context() { return m_stacking_context; } diff --git a/Userland/Services/WebContent/PageHost.cpp b/Userland/Services/WebContent/PageHost.cpp index 8520a49fab..7b9958b748 100644 --- a/Userland/Services/WebContent/PageHost.cpp +++ b/Userland/Services/WebContent/PageHost.cpp @@ -168,7 +168,7 @@ void PageHost::page_did_layout() { auto* layout_root = this->layout_root(); VERIFY(layout_root); - if (layout_root->paintable_box()->has_overflow()) + if (layout_root->paintable_box()->has_scrollable_overflow()) m_content_size = page().enclosing_device_rect(layout_root->paintable_box()->scrollable_overflow_rect().value()).size(); else m_content_size = page().enclosing_device_rect(layout_root->paintable_box()->absolute_rect()).size();