From 863416e3acdf3628227b249b4ecd6333a5e46eda Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 10 Sep 2024 19:02:03 +0200 Subject: [PATCH] LibWeb: Make FC of containing block responsible for abspos layout Before this change, a formatting context was responsible for layout of absolutely positioned boxes whose FC root box was their parent (either directly or indirectly). This only worked correctly when the containing block of the absolutely positioned child did not escape the FC root. This is because the width and height of an absolutely positioned box are resolved based on the size of its containing block, so we needed to ensure that the containing block's layout was completed before laying out an absolutely positioned box. With this change, the layout of absolutely positioned boxes is delayed until the FC responsible for the containing block's layout is complete. This has affected the way we calculate the static position. It is no longer possible to ask the FC for a box's static position, as this FC's state might be gone by the time the layout for absolutely positioned elements occurs. Instead, the "static position rectangle" (a concept from the spec) is saved in the layout state, along with information on how to align the box within this rectangle when its width and height are resolved. --- .../abspos-box-with-replaced-element.txt | 10 +-- ...d-static-position-with-justify-content.txt | 12 +-- ...spos-flex-child-with-percentage-height.txt | 37 +++++++++ .../abspos-with-grid-container-as-parent.txt | 59 ++++++++++++++ ...pos-flex-child-with-percentage-height.html | 26 ++++++ .../abspos-with-grid-container-as-parent.html | 32 ++++++++ Userland/Libraries/LibWeb/DOM/Document.cpp | 20 +++++ .../LibWeb/Layout/BlockFormattingContext.cpp | 7 +- .../LibWeb/Layout/BlockFormattingContext.h | 4 - Userland/Libraries/LibWeb/Layout/Box.cpp | 8 +- Userland/Libraries/LibWeb/Layout/Box.h | 7 ++ .../LibWeb/Layout/FlexFormattingContext.cpp | 79 +++++++------------ .../LibWeb/Layout/FlexFormattingContext.h | 2 +- .../LibWeb/Layout/FormattingContext.cpp | 20 ++--- .../LibWeb/Layout/FormattingContext.h | 2 +- .../LibWeb/Layout/GridFormattingContext.cpp | 13 ++- .../LibWeb/Layout/InlineFormattingContext.cpp | 7 +- .../Libraries/LibWeb/Layout/LayoutState.h | 40 ++++++++++ 18 files changed, 299 insertions(+), 86 deletions(-) create mode 100644 Tests/LibWeb/Layout/expected/flex/abspos-flex-child-with-percentage-height.txt create mode 100644 Tests/LibWeb/Layout/expected/grid/abspos-with-grid-container-as-parent.txt create mode 100644 Tests/LibWeb/Layout/input/flex/abspos-flex-child-with-percentage-height.html create mode 100644 Tests/LibWeb/Layout/input/grid/abspos-with-grid-container-as-parent.html diff --git a/Tests/LibWeb/Layout/expected/abspos-box-with-replaced-element.txt b/Tests/LibWeb/Layout/expected/abspos-box-with-replaced-element.txt index 76806becf02..1471d95229e 100644 --- a/Tests/LibWeb/Layout/expected/abspos-box-with-replaced-element.txt +++ b/Tests/LibWeb/Layout/expected/abspos-box-with-replaced-element.txt @@ -1,12 +1,12 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline BlockContainer at (1,1) content-size 798x0 [BFC] children: not-inline BlockContainer at (10,10) content-size 500x100 positioned [BFC] children: not-inline - BlockContainer at (261,11) content-size 248x28.46875 positioned [BFC] children: inline - frag 0 from ImageBox start: 0, length: 0, rect: [262,12 248x26.46875] baseline: 28.46875 - ImageBox at (262,12) content-size 248x26.46875 children: not-inline + BlockContainer at (261,12) content-size 248x28.46875 positioned [BFC] children: inline + frag 0 from ImageBox start: 0, length: 0, rect: [262,13 248x26.46875] baseline: 28.46875 + ImageBox at (262,13) content-size 248x26.46875 children: not-inline ViewportPaintable (Viewport<#document>) [0,0 800x600] PaintableWithLines (BlockContainer) [0,0 800x2] overflow: [9,9 502x102] PaintableWithLines (BlockContainer) [9,9 502x102] - PaintableWithLines (BlockContainer
.image-container) [260,10 250x30.46875] overflow: [261,11 249x28.46875] - ImagePaintable (ImageBox) [261,11 250x28.46875] + PaintableWithLines (BlockContainer
.image-container) [260,11 250x30.46875] overflow: [261,12 249x28.46875] + ImagePaintable (ImageBox) [261,12 250x28.46875] diff --git a/Tests/LibWeb/Layout/expected/flex/abspos-flex-child-static-position-with-justify-content.txt b/Tests/LibWeb/Layout/expected/flex/abspos-flex-child-static-position-with-justify-content.txt index 9123ce5e909..9aa4843cfc3 100644 --- a/Tests/LibWeb/Layout/expected/flex/abspos-flex-child-static-position-with-justify-content.txt +++ b/Tests/LibWeb/Layout/expected/flex/abspos-flex-child-static-position-with-justify-content.txt @@ -207,8 +207,8 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline BlockContainer <(anonymous)> at (10,1808) content-size 780x0 children: inline TextNode <#text> Box at (11,1809) content-size 300x60 flex-container(column) [FFC] children: not-inline - BlockContainer
at (12,1810) content-size 150x50 positioned [BFC] children: inline - frag 0 from TextNode start: 0, length: 5, rect: [12,1810 37.109375x17] baseline: 13.296875 + BlockContainer
at (12,1818) content-size 150x50 positioned [BFC] children: inline + frag 0 from TextNode start: 0, length: 5, rect: [12,1818 37.109375x17] baseline: 13.296875 "right" TextNode <#text> BlockContainer <(anonymous)> at (10,1870) content-size 780x0 children: inline @@ -277,8 +277,8 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline BlockContainer <(anonymous)> at (10,2428) content-size 780x0 children: inline TextNode <#text> Box at (11,2429) content-size 300x60 flex-container(column-reverse) [FFC] children: not-inline - BlockContainer
at (12,2430) content-size 150x50 positioned [BFC] children: inline - frag 0 from TextNode start: 0, length: 5, rect: [12,2430 37.109375x17] baseline: 13.296875 + BlockContainer
at (12,2438) content-size 150x50 positioned [BFC] children: inline + frag 0 from TextNode start: 0, length: 5, rect: [12,2438 37.109375x17] baseline: 13.296875 "right" TextNode <#text> BlockContainer <(anonymous)> at (10,2490) content-size 780x0 children: inline @@ -405,7 +405,7 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600] overflow: [0,0 800x2500] TextPaintable (TextNode<#text>) PaintableWithLines (BlockContainer(anonymous)) [10,1808 780x0] PaintableBox (Box
.column.outer.right) [10,1808 302x62] - PaintableWithLines (BlockContainer
) [11,1809 152x52] + PaintableWithLines (BlockContainer
) [11,1817 152x52] TextPaintable (TextNode<#text>) PaintableWithLines (BlockContainer(anonymous)) [10,1870 780x0] PaintableBox (Box
.column.reverse.outer.start) [10,1870 302x62] @@ -445,6 +445,6 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600] overflow: [0,0 800x2500] TextPaintable (TextNode<#text>) PaintableWithLines (BlockContainer(anonymous)) [10,2428 780x0] PaintableBox (Box
.column.reverse.outer.right) [10,2428 302x62] - PaintableWithLines (BlockContainer
) [11,2429 152x52] + PaintableWithLines (BlockContainer
) [11,2437 152x52] TextPaintable (TextNode<#text>) PaintableWithLines (BlockContainer(anonymous)) [10,2490 780x0] diff --git a/Tests/LibWeb/Layout/expected/flex/abspos-flex-child-with-percentage-height.txt b/Tests/LibWeb/Layout/expected/flex/abspos-flex-child-with-percentage-height.txt new file mode 100644 index 00000000000..2fde36f68c0 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/flex/abspos-flex-child-with-percentage-height.txt @@ -0,0 +1,37 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x16 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x0 children: inline + BlockContainer at (8,8) content-size 200x300 positioned [BFC] children: not-inline + BlockContainer <(anonymous)> at (8,8) content-size 200x0 children: inline + TextNode <#text> + Box at (8,8) content-size 200x0 flex-container(row) [FFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (83,8) content-size 50x150 positioned [BFC] children: not-inline + BlockContainer <(anonymous)> at (83,8) content-size 50x0 children: inline + TextNode <#text> + BlockContainer at (83,8) content-size 50x0 children: not-inline + BlockContainer <(anonymous)> at (83,8) content-size 50x0 children: inline + TextNode <#text> + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer <(anonymous)> at (8,8) content-size 200x0 children: inline + TextNode <#text> + BlockContainer
at (8,8) content-size 300x300 children: not-inline + BlockContainer <(anonymous)> at (8,308) content-size 200x0 children: inline + TextNode <#text> + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x16] + PaintableWithLines (BlockContainer) [8,8 784x0] + PaintableWithLines (BlockContainer
#containing-block) [8,8 200x300] overflow: [8,8 300x300] + PaintableWithLines (BlockContainer(anonymous)) [8,8 200x0] + PaintableBox (Box
#inner-flex) [8,8 200x0] overflow: [83,8 50x150] + PaintableWithLines (BlockContainer
) [83,8 50x150] + PaintableWithLines (BlockContainer(anonymous)) [83,8 50x0] + PaintableWithLines (BlockContainer) [83,8 50x0] + PaintableWithLines (BlockContainer(anonymous)) [83,8 50x0] + PaintableWithLines (BlockContainer(anonymous)) [8,8 200x0] + PaintableWithLines (BlockContainer
) [8,8 300x300] + PaintableWithLines (BlockContainer(anonymous)) [8,308 200x0] diff --git a/Tests/LibWeb/Layout/expected/grid/abspos-with-grid-container-as-parent.txt b/Tests/LibWeb/Layout/expected/grid/abspos-with-grid-container-as-parent.txt new file mode 100644 index 00000000000..68b12a5bf00 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/grid/abspos-with-grid-container-as-parent.txt @@ -0,0 +1,59 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x0 [BFC] children: not-inline + BlockContainer at (8,8) content-size 400x275 positioned [BFC] children: not-inline + BlockContainer at (8,8) content-size 100x100 children: inline + frag 0 from TextNode start: 0, length: 5, rect: [8,8 36.84375x17] baseline: 13.296875 + "hello" + TextNode <#text> + BlockContainer <(anonymous)> at (8,108) content-size 400x0 children: inline + TextNode <#text> + BlockContainer at (8,108) content-size 400x75 [BFC] children: inline + TextNode <#text> + BlockContainer at (8,8) content-size 60.890625x25 positioned [BFC] children: inline + frag 0 from TextNode start: 0, length: 8, rect: [8,8 60.890625x17] baseline: 13.296875 + "top left" + TextNode <#text> + TextNode <#text> + BlockContainer at (336.25,8) content-size 71.75x25 positioned [BFC] children: inline + frag 0 from TextNode start: 0, length: 9, rect: [336.25,8 71.75x17] baseline: 13.296875 + "top right" + TextNode <#text> + TextNode <#text> + BlockContainer at (8,258) content-size 90.359375x25 positioned [BFC] children: inline + frag 0 from TextNode start: 0, length: 11, rect: [8,258 90.359375x17] baseline: 13.296875 + "bottom left" + TextNode <#text> + TextNode <#text> + BlockContainer at (306.78125,258) content-size 101.21875x25 positioned [BFC] children: inline + frag 0 from TextNode start: 0, length: 12, rect: [306.78125,258 101.21875x17] baseline: 13.296875 + "bottom right" + TextNode <#text> + TextNode <#text> + BlockContainer <(anonymous)> at (8,183) content-size 400x0 children: inline + TextNode <#text> + BlockContainer at (8,183) content-size 100x100 children: inline + frag 0 from TextNode start: 0, length: 5, rect: [8,183 36.84375x17] baseline: 13.296875 + "hello" + TextNode <#text> + BlockContainer <(anonymous)> at (8,283) content-size 400x0 children: inline + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x0] overflow: [8,8 400x275] + PaintableWithLines (BlockContainer) [8,8 400x275] + PaintableWithLines (BlockContainer
#fill) [8,8 100x100] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer(anonymous)) [8,108 400x0] + PaintableWithLines (BlockContainer
#grid) [8,108 400x75] + PaintableWithLines (BlockContainer
#grid-item-abspos) [8,8 60.890625x25] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer
#grid-item-abspos) [336.25,8 71.75x25] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer
#grid-item-abspos) [8,258 90.359375x25] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer
#grid-item-abspos) [306.78125,258 101.21875x25] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer(anonymous)) [8,183 400x0] + PaintableWithLines (BlockContainer
#fill) [8,183 100x100] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer(anonymous)) [8,283 400x0] diff --git a/Tests/LibWeb/Layout/input/flex/abspos-flex-child-with-percentage-height.html b/Tests/LibWeb/Layout/input/flex/abspos-flex-child-with-percentage-height.html new file mode 100644 index 00000000000..8b77cc451f2 --- /dev/null +++ b/Tests/LibWeb/Layout/input/flex/abspos-flex-child-with-percentage-height.html @@ -0,0 +1,26 @@ + + +
+
+
+ +
+
+
+
diff --git a/Tests/LibWeb/Layout/input/grid/abspos-with-grid-container-as-parent.html b/Tests/LibWeb/Layout/input/grid/abspos-with-grid-container-as-parent.html new file mode 100644 index 00000000000..78fbdc60966 --- /dev/null +++ b/Tests/LibWeb/Layout/input/grid/abspos-with-grid-container-as-parent.html @@ -0,0 +1,32 @@ + +
hello
+
+
top left
+
top right
+
bottom left
+
bottom right
+
+
hello
diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 1d0a6785c9f..d3c63bbd2f8 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -1131,6 +1131,26 @@ void Document::update_layout() } } + // Assign each box that establishes a formatting context a list of absolutely positioned children it should take care of during layout + m_layout_root->for_each_in_inclusive_subtree([&](auto& child) { + if (!child.is_absolutely_positioned()) + return TraversalDecision::Continue; + if (auto* containing_block = child.containing_block()) { + auto* closest_box_that_establishes_formatting_context = containing_block; + while (closest_box_that_establishes_formatting_context) { + if (closest_box_that_establishes_formatting_context == m_layout_root) + break; + if (Layout::FormattingContext::formatting_context_type_created_by_box(*closest_box_that_establishes_formatting_context).has_value()) { + break; + } + closest_box_that_establishes_formatting_context = closest_box_that_establishes_formatting_context->containing_block(); + } + VERIFY(closest_box_that_establishes_formatting_context); + closest_box_that_establishes_formatting_context->add_contained_abspos_child(child); + } + return TraversalDecision::Continue; + }); + Layout::LayoutState layout_state; { diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp index 485c17d2998..6dccbf6358c 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp @@ -110,8 +110,9 @@ void BlockFormattingContext::parent_context_did_dimension_child_root_box() if (m_layout_mode == LayoutMode::Normal) { // We can also layout absolutely positioned boxes within this BFC. - for (auto& box : m_absolutely_positioned_boxes) { - auto& cb_state = m_state.get(*box->containing_block()); + for (auto& child : root().contained_abspos_children()) { + auto& box = verify_cast(*child); + auto& cb_state = m_state.get(*box.containing_block()); auto available_width = AvailableSize::make_definite(cb_state.content_width() + cb_state.padding_left + cb_state.padding_right); auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom); layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height)); @@ -585,7 +586,7 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain if (box.is_absolutely_positioned()) { box_state.vertical_offset_of_parent_block_container = m_y_offset_of_current_block_container.value(); - m_absolutely_positioned_boxes.append(box); + box_state.set_static_position_rect(calculate_static_position_rect(box)); return; } diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h index 09d3a1a0a71..0b183451252 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h @@ -39,8 +39,6 @@ public: void compute_height(Box const&, AvailableSpace const&); - void add_absolutely_positioned_box(Box const& box) { m_absolutely_positioned_boxes.append(box); } - SpaceUsedAndContainingMarginForFloats space_used_and_containing_margin_for_floats(CSSPixels y) const; [[nodiscard]] SpaceUsedByFloats intrusion_by_floats_into_box(Box const&, CSSPixels y_in_box) const; [[nodiscard]] SpaceUsedByFloats intrusion_by_floats_into_box(LayoutState::UsedValues const&, CSSPixels y_in_box) const; @@ -170,8 +168,6 @@ private: FloatSideData m_left_floats; FloatSideData m_right_floats; - Vector> m_absolutely_positioned_boxes; - bool m_was_notified_after_parent_dimensioned_my_root_box { false }; }; diff --git a/Userland/Libraries/LibWeb/Layout/Box.cpp b/Userland/Libraries/LibWeb/Layout/Box.cpp index b1aeb1a691b..844cd423f38 100644 --- a/Userland/Libraries/LibWeb/Layout/Box.cpp +++ b/Userland/Libraries/LibWeb/Layout/Box.cpp @@ -28,7 +28,13 @@ Box::~Box() { } -// https://www.w3.org/TR/css-overflow-3/#overflow-control +void Box::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_contained_abspos_children); +} + +// https://www.w37.org/TR/css-overflow-3/#overflow-control static bool overflow_value_makes_box_a_scroll_container(CSS::Overflow overflow) { switch (overflow) { diff --git a/Userland/Libraries/LibWeb/Layout/Box.h b/Userland/Libraries/LibWeb/Layout/Box.h index 7bfedc0d963..7278505de70 100644 --- a/Userland/Libraries/LibWeb/Layout/Box.h +++ b/Userland/Libraries/LibWeb/Layout/Box.h @@ -54,6 +54,11 @@ public: bool is_user_scrollable() const; + void add_contained_abspos_child(JS::NonnullGCPtr child) { m_contained_abspos_children.append(child); } + Vector> const& contained_abspos_children() const { return m_contained_abspos_children; } + + virtual void visit_edges(Cell::Visitor&) override; + protected: Box(DOM::Document&, DOM::Node*, NonnullRefPtr); Box(DOM::Document&, DOM::Node*, NonnullOwnPtr); @@ -64,6 +69,8 @@ private: Optional m_natural_width; Optional m_natural_height; Optional m_natural_aspect_ratio; + + Vector> m_contained_abspos_children; }; template<> diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp index 5f9e163caa1..c22f13d69e5 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp @@ -186,13 +186,18 @@ void FlexFormattingContext::parent_context_did_dimension_child_root_box() flex_container().for_each_child_of_type([&](Layout::Box& box) { if (box.is_absolutely_positioned()) { - auto& cb_state = m_state.get(*box.containing_block()); - auto available_width = AvailableSize::make_definite(cb_state.content_width() + cb_state.padding_left + cb_state.padding_right); - auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom); - layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height)); + m_state.get_mutable(box).set_static_position_rect(calculate_static_position_rect(box)); } return IterationDecision::Continue; }); + + for (auto& child : flex_container().contained_abspos_children()) { + auto& box = verify_cast(*child); + auto& cb_state = m_state.get(*box.containing_block()); + auto available_width = AvailableSize::make_definite(cb_state.content_width() + cb_state.padding_left + cb_state.padding_right); + auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom); + layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height)); + } } // https://www.w3.org/TR/css-flexbox-1/#flex-direction-property @@ -2129,35 +2134,12 @@ void FlexFormattingContext::handle_align_content_stretch() } // https://drafts.csswg.org/css-flexbox-1/#abspos-items -CSSPixelPoint FlexFormattingContext::calculate_static_position(Box const& box) const +StaticPositionRect FlexFormattingContext::calculate_static_position_rect(Box const& box) const { // The cross-axis edges of the static-position rectangle of an absolutely-positioned child // of a flex container are the content edges of the flex container. - CSSPixels cross_offset = 0; - CSSPixels half_line_size = inner_cross_size(m_flex_container_state) / 2; - - auto cross_to_px = [&](CSS::LengthPercentage const& length_percentage) -> CSSPixels { - return length_percentage.to_px(box, m_flex_container_state.content_width()); - }; - - auto main_to_px = [&](CSS::LengthPercentage const& length_percentage) -> CSSPixels { - return length_percentage.to_px(box, m_flex_container_state.content_width()); - }; - - auto const& box_state = m_state.get(box); - CSSPixels cross_margin_before = is_row_layout() ? cross_to_px(box.computed_values().margin().top()) : cross_to_px(box.computed_values().margin().left()); - CSSPixels cross_margin_after = is_row_layout() ? cross_to_px(box.computed_values().margin().bottom()) : cross_to_px(box.computed_values().margin().right()); - CSSPixels cross_border_before = is_row_layout() ? box.computed_values().border_top().width : box.computed_values().border_left().width; - CSSPixels cross_border_after = is_row_layout() ? box.computed_values().border_bottom().width : box.computed_values().border_right().width; - CSSPixels cross_padding_before = is_row_layout() ? cross_to_px(box.computed_values().padding().top()) : cross_to_px(box.computed_values().padding().left()); - CSSPixels cross_padding_after = is_row_layout() ? cross_to_px(box.computed_values().padding().bottom()) : cross_to_px(box.computed_values().padding().right()); - CSSPixels main_margin_before = is_row_layout() ? main_to_px(box.computed_values().margin().left()) : main_to_px(box.computed_values().margin().top()); - CSSPixels main_margin_after = is_row_layout() ? main_to_px(box.computed_values().margin().right()) : main_to_px(box.computed_values().margin().bottom()); - CSSPixels main_border_before = is_row_layout() ? box.computed_values().border_left().width : box.computed_values().border_top().width; - CSSPixels main_border_after = is_row_layout() ? box.computed_values().border_right().width : box.computed_values().border_bottom().width; - CSSPixels main_padding_before = is_row_layout() ? main_to_px(box.computed_values().padding().left()) : main_to_px(box.computed_values().padding().top()); - CSSPixels main_padding_after = is_row_layout() ? main_to_px(box.computed_values().padding().right()) : main_to_px(box.computed_values().padding().bottom()); + StaticPositionRect::Alignment cross_axis_alignment = StaticPositionRect::Alignment::Start; switch (alignment_for_item(box)) { case CSS::AlignItems::Baseline: // FIXME: Implement this @@ -2167,67 +2149,64 @@ CSSPixelPoint FlexFormattingContext::calculate_static_position(Box const& box) c case CSS::AlignItems::SelfStart: case CSS::AlignItems::Stretch: case CSS::AlignItems::Normal: - cross_offset = -half_line_size; + cross_axis_alignment = StaticPositionRect::Alignment::Start; break; case CSS::AlignItems::End: case CSS::AlignItems::SelfEnd: case CSS::AlignItems::FlexEnd: - cross_offset = half_line_size - inner_cross_size(box_state) - (cross_margin_before + cross_margin_after) - (cross_border_before + cross_border_after) - (cross_padding_before + cross_padding_after); + cross_axis_alignment = StaticPositionRect::Alignment::End; break; case CSS::AlignItems::Center: - cross_offset = -((inner_cross_size(box_state) + cross_margin_after + cross_margin_before + cross_border_before + cross_border_after + cross_padding_before + cross_padding_after) / 2); + cross_axis_alignment = StaticPositionRect::Alignment::Center; break; default: break; } - cross_offset += inner_cross_size(m_flex_container_state) / 2; - // The main-axis edges of the static-position rectangle are where the margin edges of the child // would be positioned if it were the sole flex item in the flex container, // assuming both the child and the flex container were fixed-size boxes of their used size. // (For this purpose, auto margins are treated as zero. - bool pack_from_end = true; - CSSPixels main_offset = 0; + StaticPositionRect::Alignment main_axis_alignment = StaticPositionRect::Alignment::Start; switch (flex_container().computed_values().justify_content()) { case CSS::JustifyContent::Start: case CSS::JustifyContent::Left: - pack_from_end = false; + main_axis_alignment = StaticPositionRect::Alignment::Start; break; case CSS::JustifyContent::Stretch: case CSS::JustifyContent::Normal: case CSS::JustifyContent::FlexStart: case CSS::JustifyContent::SpaceBetween: - pack_from_end = is_direction_reverse(); + main_axis_alignment = is_direction_reverse() ? StaticPositionRect::Alignment::End : StaticPositionRect::Alignment::Start; break; case CSS::JustifyContent::End: - pack_from_end = true; + main_axis_alignment = StaticPositionRect::Alignment::End; break; case CSS::JustifyContent::Right: - pack_from_end = is_row_layout(); + main_axis_alignment = StaticPositionRect::Alignment::End; break; case CSS::JustifyContent::FlexEnd: - pack_from_end = !is_direction_reverse(); + main_axis_alignment = !is_direction_reverse() ? StaticPositionRect::Alignment::End : StaticPositionRect::Alignment::Start; break; case CSS::JustifyContent::Center: case CSS::JustifyContent::SpaceAround: case CSS::JustifyContent::SpaceEvenly: - pack_from_end = false; - main_offset = (inner_main_size(m_flex_container_state) - inner_main_size(box_state) - main_margin_before - main_margin_after - main_border_before - main_border_after - main_padding_before - main_padding_after) / 2; + main_axis_alignment = StaticPositionRect::Alignment::Center; break; } - if (pack_from_end) - main_offset += inner_main_size(m_flex_container_state) - inner_main_size(box_state) - main_margin_before - main_margin_after - main_border_before - main_border_after - main_padding_before - main_padding_after; - - auto static_position_offset = is_row_layout() ? CSSPixelPoint { main_offset, cross_offset } : CSSPixelPoint { cross_offset, main_offset }; - auto absolute_position_of_flex_container = absolute_content_rect(flex_container()).location(); auto absolute_position_of_abspos_containing_block = absolute_content_rect(*box.containing_block()).location(); - auto diff = absolute_position_of_flex_container - absolute_position_of_abspos_containing_block; - return static_position_offset + diff; + auto flex_container_width = is_row_layout() ? inner_main_size(m_flex_container_state) : inner_cross_size(m_flex_container_state); + auto flex_container_height = is_row_layout() ? inner_cross_size(m_flex_container_state) : inner_main_size(m_flex_container_state); + + StaticPositionRect static_position_rect; + static_position_rect.rect = { absolute_position_of_flex_container - absolute_position_of_abspos_containing_block, { flex_container_width, flex_container_height } }; + static_position_rect.horizontal_alignment = is_row_layout() ? main_axis_alignment : cross_axis_alignment; + static_position_rect.vertical_alignment = is_row_layout() ? cross_axis_alignment : main_axis_alignment; + return static_position_rect; } double FlexFormattingContext::FlexLine::sum_of_flex_factor_of_unfrozen_items() const diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h index 041e6353ad2..24a77e833b8 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h @@ -24,7 +24,7 @@ public: Box const& flex_container() const { return context_box(); } - virtual CSSPixelPoint calculate_static_position(Box const&) const override; + virtual StaticPositionRect calculate_static_position_rect(Box const&) const override; private: [[nodiscard]] bool should_treat_main_size_as_auto(Box const&) const; diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp index 6b9eb1d145b..0b485b46239 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -716,7 +716,7 @@ void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_ele width = CSS::Length::make_px(content_width); m_state.get_mutable(box).set_content_width(content_width); - auto static_position = calculate_static_position(box); + auto static_position = m_state.get(box).static_position(); left = static_position.x(); right = solve_for_right(); @@ -774,7 +774,7 @@ void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_ele // Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr'). else if (computed_left.is_auto() && computed_right.is_auto() && !width.is_auto()) { // FIXME: Check direction - auto static_position = calculate_static_position(box); + auto static_position = m_state.get(box).static_position(); left = static_position.x(); right = solve_for_right(); } @@ -863,7 +863,7 @@ void FormattingContext::compute_width_for_absolutely_positioned_replaced_element auto margin_left = computed_values.margin().left(); auto right = computed_values.inset().right(); auto margin_right = computed_values.margin().right(); - auto static_position = calculate_static_position(box); + auto static_position = m_state.get(box).static_position(); auto to_px = [&](const CSS::LengthPercentage& l) { return l.to_px(box, width_of_containing_block); @@ -1034,7 +1034,7 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el auto constrained_height = apply_min_max_height_constraints(height); m_state.get_mutable(box).set_content_height(constrained_height.to_px(box)); - auto static_position = calculate_static_position(box); + auto static_position = m_state.get(box).static_position(); top = CSS::Length::make_px(static_position.y()); solve_for_bottom(); @@ -1089,7 +1089,7 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el // 2. If top and bottom are auto and height is not auto, else if (top.is_auto() && bottom.is_auto() && !height.is_auto()) { // then set top to the static position, - top = CSS::Length::make_px(calculate_static_position(box).y()); + top = CSS::Length::make_px(m_state.get(box).static_position().y()); // then solve for bottom. solve_for_bottom(); @@ -1176,7 +1176,7 @@ CSSPixelRect FormattingContext::content_box_rect_in_static_position_ancestor_coo } // https://www.w3.org/TR/css-position-3/#staticpos-rect -CSSPixelPoint FormattingContext::calculate_static_position(Box const& box) const +StaticPositionRect FormattingContext::calculate_static_position_rect(Box const& box) const { // NOTE: This is very ad-hoc. // The purpose of this function is to calculate the approximate position that `box` @@ -1216,7 +1216,9 @@ CSSPixelPoint FormattingContext::calculate_static_position(Box const& box) const y = box_state.vertical_offset_of_parent_block_container; } auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block()); - return offset_to_static_parent.location().translated(x, y); + StaticPositionRect static_position_rect; + static_position_rect.rect = { offset_to_static_parent.location().translated(x, y), { 0, 0 } }; + return static_position_rect; } void FormattingContext::layout_absolutely_positioned_element(Box const& box, AvailableSpace const& available_space) @@ -1259,7 +1261,7 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box, Ava CSSPixelPoint used_offset; - auto static_position = calculate_static_position(box); + auto static_position = m_state.get(box).static_position(); if (box.computed_values().inset().top().is_auto() && box.computed_values().inset().bottom().is_auto()) { used_offset.set_y(static_position.y()); @@ -1300,7 +1302,7 @@ void FormattingContext::compute_height_for_absolutely_positioned_replaced_elemen auto margin_top = computed_values.margin().top(); auto bottom = computed_values.inset().bottom(); auto margin_bottom = computed_values.margin().bottom(); - auto static_position = calculate_static_position(box); + auto static_position = m_state.get(box).static_position(); auto to_px = [&](const CSS::LengthPercentage& l) { return l.to_px(box, height_of_containing_block); diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.h b/Userland/Libraries/LibWeb/Layout/FormattingContext.h index cf64a952d16..a95d15ad692 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.h @@ -107,7 +107,7 @@ public: [[nodiscard]] CSSPixels calculate_stretch_fit_width(Box const&, AvailableSize const&) const; [[nodiscard]] CSSPixels calculate_stretch_fit_height(Box const&, AvailableSize const&) const; - virtual CSSPixelPoint calculate_static_position(Box const&) const; + virtual StaticPositionRect calculate_static_position_rect(Box const&) const; bool can_skip_is_anonymous_text_run(Box&); void compute_inset(NodeWithStyleAndBoxModelMetrics const&); diff --git a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp index 09102dec19d..3ce8a998377 100644 --- a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -2010,13 +2010,18 @@ void GridFormattingContext::parent_context_did_dimension_child_root_box() grid_container().for_each_child_of_type([&](Layout::Box& box) { if (box.is_absolutely_positioned()) { - auto& cb_state = m_state.get(*box.containing_block()); - auto available_width = AvailableSize::make_definite(cb_state.content_width() + cb_state.padding_left + cb_state.padding_right); - auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom); - layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height)); + m_state.get_mutable(box).set_static_position_rect(calculate_static_position_rect(box)); } return IterationDecision::Continue; }); + + for (auto& child : grid_container().contained_abspos_children()) { + auto& box = verify_cast(*child); + auto& cb_state = m_state.get(*box.containing_block()); + auto available_width = AvailableSize::make_definite(cb_state.content_width() + cb_state.padding_left + cb_state.padding_right); + auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom); + layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height)); + } } void GridFormattingContext::determine_intrinsic_size_of_grid_container(AvailableSpace const& available_space) diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp index d681fe3bd5a..2fcc4153d72 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -305,8 +305,11 @@ void InlineFormattingContext::generate_line_boxes() break; } case InlineLevelIterator::Item::Type::AbsolutelyPositionedElement: - if (is(*item.node)) - parent().add_absolutely_positioned_box(static_cast(*item.node)); + if (is(*item.node)) { + auto const& box = static_cast(*item.node); + auto& box_state = m_state.get_mutable(box); + box_state.set_static_position_rect(calculate_static_position_rect(box)); + } break; case InlineLevelIterator::Item::Type::FloatingElement: diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.h b/Userland/Libraries/LibWeb/Layout/LayoutState.h index 0bab0f50ef3..80d2db7475c 100644 --- a/Userland/Libraries/LibWeb/Layout/LayoutState.h +++ b/Userland/Libraries/LibWeb/Layout/LayoutState.h @@ -25,6 +25,35 @@ enum class SizeConstraint { class AvailableSize; class AvailableSpace; +// https://www.w3.org/TR/css-position-3/#static-position-rectangle +struct StaticPositionRect { + enum class Alignment { + Start, + Center, + End, + }; + + CSSPixelRect rect; + Alignment horizontal_alignment { Alignment::Start }; + Alignment vertical_alignment { Alignment::Start }; + + CSSPixelPoint aligned_position_for_box_with_size(CSSPixelSize const& size) const + { + CSSPixelPoint position = rect.location(); + if (horizontal_alignment == Alignment::Center) + position.set_x(position.x() + (rect.width() - size.width()) / 2); + else if (horizontal_alignment == Alignment::End) + position.set_x(position.x() + rect.width() - size.width()); + + if (vertical_alignment == Alignment::Center) + position.set_y(position.y() + (rect.height() - size.height()) / 2); + else if (vertical_alignment == Alignment::End) + position.set_y(position.y() + rect.height() - size.height()); + + return position; + } +}; + struct LayoutState { LayoutState() : m_root(*this) @@ -145,6 +174,15 @@ struct LayoutState { void set_grid_template_rows(RefPtr used_values_for_grid_template_rows) { m_grid_template_rows = move(used_values_for_grid_template_rows); } auto const& grid_template_rows() const { return m_grid_template_rows; } + void set_static_position_rect(StaticPositionRect const& static_position_rect) { m_static_position_rect = static_position_rect; } + CSSPixelPoint static_position() const + { + CSSPixelSize size; + size.set_width(content_width() + padding_left + padding_right + border_left + border_right + margin_left + margin_right); + size.set_height(content_height() + padding_top + padding_bottom + border_top + border_bottom + margin_top + margin_bottom); + return m_static_position_rect->aligned_position_for_box_with_size(size); + } + private: AvailableSize available_width_inside() const; AvailableSize available_height_inside() const; @@ -175,6 +213,8 @@ struct LayoutState { RefPtr m_grid_template_columns; RefPtr m_grid_template_rows; + + Optional m_static_position_rect; }; // Commits the used values produced by layout and builds a paintable tree.