/src/serenity/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #pragma once |
8 | | |
9 | | #include <AK/Vector.h> |
10 | | #include <LibWeb/Forward.h> |
11 | | #include <LibWeb/Layout/BlockContainer.h> |
12 | | #include <LibWeb/Layout/FormattingContext.h> |
13 | | #include <LibWeb/Layout/InlineFormattingContext.h> |
14 | | |
15 | | namespace Web::Layout { |
16 | | |
17 | | class LineBuilder; |
18 | | |
19 | | // https://www.w3.org/TR/css-display/#block-formatting-context |
20 | | class BlockFormattingContext : public FormattingContext { |
21 | | public: |
22 | | explicit BlockFormattingContext(LayoutState&, LayoutMode layout_mode, BlockContainer const&, FormattingContext* parent); |
23 | | ~BlockFormattingContext(); |
24 | | |
25 | | virtual void run(AvailableSpace const&) override; |
26 | | virtual CSSPixels automatic_content_width() const override; |
27 | | virtual CSSPixels automatic_content_height() const override; |
28 | | |
29 | 0 | auto const& left_side_floats() const { return m_left_floats; } |
30 | 0 | auto const& right_side_floats() const { return m_right_floats; } |
31 | | |
32 | | bool box_should_avoid_floats_because_it_establishes_fc(Box const&); |
33 | | void compute_width(Box const&, AvailableSpace const&); |
34 | | |
35 | | // https://www.w3.org/TR/css-display/#block-formatting-context-root |
36 | 0 | BlockContainer const& root() const { return static_cast<BlockContainer const&>(context_box()); } |
37 | | |
38 | | virtual void parent_context_did_dimension_child_root_box() override; |
39 | | |
40 | | void resolve_used_height_if_not_treated_as_auto(Box const&, AvailableSpace const&); |
41 | | void resolve_used_height_if_treated_as_auto(Box const&, AvailableSpace const&, FormattingContext const* box_formatting_context = nullptr); |
42 | | |
43 | 0 | void add_absolutely_positioned_box(Box const& box) { m_absolutely_positioned_boxes.append(box); } |
44 | | |
45 | | SpaceUsedAndContainingMarginForFloats space_used_and_containing_margin_for_floats(CSSPixels y) const; |
46 | | [[nodiscard]] SpaceUsedByFloats intrusion_by_floats_into_box(Box const&, CSSPixels y_in_box) const; |
47 | | [[nodiscard]] SpaceUsedByFloats intrusion_by_floats_into_box(LayoutState::UsedValues const&, CSSPixels y_in_box) const; |
48 | | |
49 | | virtual CSSPixels greatest_child_width(Box const&) const override; |
50 | | |
51 | | void layout_floating_box(Box const& child, BlockContainer const& containing_block, AvailableSpace const&, CSSPixels y, LineBuilder* = nullptr); |
52 | | |
53 | | void layout_block_level_box(Box const&, BlockContainer const&, CSSPixels& bottom_of_lowest_margin_box, AvailableSpace const&); |
54 | | |
55 | | void resolve_vertical_box_model_metrics(Box const&); |
56 | | |
57 | | enum class DidIntroduceClearance { |
58 | | Yes, |
59 | | No, |
60 | | }; |
61 | | |
62 | | [[nodiscard]] DidIntroduceClearance clear_floating_boxes(Node const& child_box, Optional<InlineFormattingContext&> inline_formatting_context); |
63 | | |
64 | 0 | void reset_margin_state() { m_margin_state.reset(); } |
65 | | |
66 | | private: |
67 | | CSSPixels compute_auto_height_for_block_level_element(Box const&, AvailableSpace const&); |
68 | | |
69 | | void compute_width_for_floating_box(Box const&, AvailableSpace const&); |
70 | | |
71 | | void compute_width_for_block_level_replaced_element_in_normal_flow(Box const&, AvailableSpace const&); |
72 | | |
73 | | void layout_viewport(AvailableSpace const&); |
74 | | |
75 | | void layout_block_level_children(BlockContainer const&, AvailableSpace const&); |
76 | | void layout_inline_children(BlockContainer const&, AvailableSpace const&); |
77 | | |
78 | | void place_block_level_element_in_normal_flow_horizontally(Box const& child_box, AvailableSpace const&); |
79 | | void place_block_level_element_in_normal_flow_vertically(Box const&, CSSPixels y); |
80 | | |
81 | | void ensure_sizes_correct_for_left_offset_calculation(ListItemBox const&); |
82 | | void layout_list_item_marker(ListItemBox const&, CSSPixels const& left_space_before_list_item_elements_formatted); |
83 | | |
84 | | void measure_scrollable_overflow(Box const&, CSSPixels& bottom_edge, CSSPixels& right_edge) const; |
85 | | |
86 | | enum class FloatSide { |
87 | | Left, |
88 | | Right, |
89 | | }; |
90 | | |
91 | | struct FloatingBox { |
92 | | JS::NonnullGCPtr<Box const> box; |
93 | | |
94 | | LayoutState::UsedValues& used_values; |
95 | | |
96 | | // Offset from left/right edge to the left content edge of `box`. |
97 | | CSSPixels offset_from_edge { 0 }; |
98 | | |
99 | | // Top margin edge of `box`. |
100 | | CSSPixels top_margin_edge { 0 }; |
101 | | |
102 | | // Bottom margin edge of `box`. |
103 | | CSSPixels bottom_margin_edge { 0 }; |
104 | | }; |
105 | | |
106 | | struct FloatSideData { |
107 | | // Floating boxes currently accumulating on this side. |
108 | | Vector<FloatingBox&> current_boxes; |
109 | | |
110 | | // Combined width of boxes currently accumulating on this side. |
111 | | // This is the innermost margin of the innermost floating box. |
112 | | CSSPixels current_width { 0 }; |
113 | | |
114 | | // Highest value of `m_current_width` we've seen. |
115 | | CSSPixels max_width { 0 }; |
116 | | |
117 | | // All floating boxes encountered thus far within this BFC. |
118 | | Vector<NonnullOwnPtr<FloatingBox>> all_boxes; |
119 | | |
120 | | // Current Y offset from BFC root top. |
121 | | CSSPixels y_offset { 0 }; |
122 | | |
123 | | void clear() |
124 | 0 | { |
125 | 0 | current_boxes.clear(); |
126 | 0 | current_width = 0; |
127 | 0 | } |
128 | | }; |
129 | | |
130 | | struct BlockMarginState { |
131 | | CSSPixels current_positive_collapsible_margin; |
132 | | CSSPixels current_negative_collapsible_margin; |
133 | | Function<void(CSSPixels)> block_container_y_position_update_callback; |
134 | | bool box_last_in_flow_child_margin_bottom_collapsed { false }; |
135 | | |
136 | | void add_margin(CSSPixels margin) |
137 | 0 | { |
138 | 0 | if (margin < 0) { |
139 | 0 | current_negative_collapsible_margin = min(margin, current_negative_collapsible_margin); |
140 | 0 | } else { |
141 | 0 | current_positive_collapsible_margin = max(margin, current_positive_collapsible_margin); |
142 | 0 | } |
143 | 0 | } |
144 | | |
145 | | void register_block_container_y_position_update_callback(ESCAPING Function<void(CSSPixels)> callback) |
146 | 0 | { |
147 | 0 | block_container_y_position_update_callback = move(callback); |
148 | 0 | } |
149 | | |
150 | | CSSPixels current_collapsed_margin() const; |
151 | | |
152 | | bool has_block_container_waiting_for_final_y_position() const |
153 | 0 | { |
154 | 0 | return static_cast<bool>(block_container_y_position_update_callback); |
155 | 0 | } |
156 | | |
157 | | void update_block_waiting_for_final_y_position() const |
158 | 0 | { |
159 | 0 | if (block_container_y_position_update_callback) { |
160 | 0 | CSSPixels collapsed_margin = current_collapsed_margin(); |
161 | 0 | block_container_y_position_update_callback(collapsed_margin); |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | | void reset() |
166 | 0 | { |
167 | 0 | block_container_y_position_update_callback = {}; |
168 | 0 | current_negative_collapsible_margin = 0; |
169 | 0 | current_positive_collapsible_margin = 0; |
170 | 0 | } |
171 | | }; |
172 | | |
173 | | Optional<CSSPixels> m_y_offset_of_current_block_container; |
174 | | |
175 | | BlockMarginState m_margin_state; |
176 | | |
177 | | FloatSideData m_left_floats; |
178 | | FloatSideData m_right_floats; |
179 | | |
180 | | Vector<JS::NonnullGCPtr<Box const>> m_absolutely_positioned_boxes; |
181 | | |
182 | | bool m_was_notified_after_parent_dimensioned_my_root_box { false }; |
183 | | }; |
184 | | |
185 | | } |