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();