/src/serenity/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp
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 | | #include <AK/TemporaryChange.h> |
8 | | #include <LibWeb/CSS/Length.h> |
9 | | #include <LibWeb/DOM/Node.h> |
10 | | #include <LibWeb/Dump.h> |
11 | | #include <LibWeb/HTML/BrowsingContext.h> |
12 | | #include <LibWeb/Layout/BlockContainer.h> |
13 | | #include <LibWeb/Layout/BlockFormattingContext.h> |
14 | | #include <LibWeb/Layout/Box.h> |
15 | | #include <LibWeb/Layout/InlineFormattingContext.h> |
16 | | #include <LibWeb/Layout/LineBuilder.h> |
17 | | #include <LibWeb/Layout/ListItemBox.h> |
18 | | #include <LibWeb/Layout/ListItemMarkerBox.h> |
19 | | #include <LibWeb/Layout/ReplacedBox.h> |
20 | | #include <LibWeb/Layout/SVGSVGBox.h> |
21 | | #include <LibWeb/Layout/TableWrapper.h> |
22 | | #include <LibWeb/Layout/Viewport.h> |
23 | | |
24 | | namespace Web::Layout { |
25 | | |
26 | | BlockFormattingContext::BlockFormattingContext(LayoutState& state, LayoutMode layout_mode, BlockContainer const& root, FormattingContext* parent) |
27 | 0 | : FormattingContext(Type::Block, layout_mode, state, root, parent) |
28 | 0 | { |
29 | 0 | } |
30 | | |
31 | | BlockFormattingContext::~BlockFormattingContext() |
32 | 0 | { |
33 | 0 | if (!m_was_notified_after_parent_dimensioned_my_root_box) { |
34 | | // HACK: The parent formatting context never notified us after assigning dimensions to our root box. |
35 | | // Pretend that it did anyway, to make sure absolutely positioned children get laid out. |
36 | | // FIXME: Get rid of this hack once parent contexts behave properly. |
37 | 0 | parent_context_did_dimension_child_root_box(); |
38 | 0 | } |
39 | 0 | } |
40 | | |
41 | | CSSPixels BlockFormattingContext::automatic_content_width() const |
42 | 0 | { |
43 | 0 | if (root().children_are_inline()) |
44 | 0 | return m_state.get(root()).content_width(); |
45 | 0 | return greatest_child_width(root()); |
46 | 0 | } |
47 | | |
48 | | CSSPixels BlockFormattingContext::automatic_content_height() const |
49 | 0 | { |
50 | 0 | return compute_auto_height_for_block_formatting_context_root(root()); |
51 | 0 | } |
52 | | |
53 | | static bool margins_collapse_through(Box const& box, LayoutState& state) |
54 | 0 | { |
55 | | // FIXME: A box's own margins collapse if the 'min-height' property is zero, and it has neither top or bottom borders |
56 | | // nor top or bottom padding, and it has a 'height' of either 0 or 'auto', and it does not contain a line box, and |
57 | | // all of its in-flow children's margins (if any) collapse. |
58 | | // https://www.w3.org/TR/CSS22/box.html#collapsing-margins |
59 | | // FIXME: For the purpose of margin collapsing (CSS 2 ยง8.3.1 Collapsing margins), if the block axis is the |
60 | | // ratio-dependent axis, it is not considered to have a computed block-size of auto. |
61 | | // https://www.w3.org/TR/css-sizing-4/#aspect-ratio-margin-collapse |
62 | |
|
63 | 0 | if (box.computed_values().clear() != CSS::Clear::None) |
64 | 0 | return false; |
65 | | |
66 | 0 | return state.get(box).border_box_height() == 0; |
67 | 0 | } |
68 | | |
69 | | void BlockFormattingContext::run(AvailableSpace const& available_space) |
70 | 0 | { |
71 | 0 | if (is<Viewport>(root())) { |
72 | 0 | layout_viewport(available_space); |
73 | 0 | return; |
74 | 0 | } |
75 | | |
76 | 0 | if (root().children_are_inline()) |
77 | 0 | layout_inline_children(root(), available_space); |
78 | 0 | else |
79 | 0 | layout_block_level_children(root(), available_space); |
80 | | |
81 | | // Assign collapsed margin left after children layout of formatting context to the last child box |
82 | 0 | if (m_margin_state.current_collapsed_margin() != 0) { |
83 | 0 | for (auto* child_box = root().last_child_of_type<Box>(); child_box; child_box = child_box->previous_sibling_of_type<Box>()) { |
84 | 0 | if (child_box->is_absolutely_positioned() || child_box->is_floating()) |
85 | 0 | continue; |
86 | 0 | if (margins_collapse_through(*child_box, m_state)) |
87 | 0 | continue; |
88 | 0 | m_state.get_mutable(*child_box).margin_bottom = m_margin_state.current_collapsed_margin(); |
89 | 0 | break; |
90 | 0 | } |
91 | 0 | } |
92 | 0 | } |
93 | | |
94 | | void BlockFormattingContext::parent_context_did_dimension_child_root_box() |
95 | 0 | { |
96 | 0 | m_was_notified_after_parent_dimensioned_my_root_box = true; |
97 | | |
98 | | // Left-side floats: offset_from_edge is from left edge (0) to left content edge of floating_box. |
99 | 0 | for (auto& floating_box : m_left_floats.all_boxes) { |
100 | 0 | auto& box_state = m_state.get_mutable(floating_box->box); |
101 | 0 | box_state.set_content_x(floating_box->offset_from_edge); |
102 | 0 | } |
103 | | |
104 | | // Right-side floats: offset_from_edge is from right edge (float_containing_block_width) to the left content edge of floating_box. |
105 | 0 | for (auto& floating_box : m_right_floats.all_boxes) { |
106 | 0 | auto float_containing_block_width = containing_block_width_for(floating_box->box); |
107 | 0 | auto& box_state = m_state.get_mutable(floating_box->box); |
108 | 0 | box_state.set_content_x(float_containing_block_width - floating_box->offset_from_edge); |
109 | 0 | } |
110 | |
|
111 | 0 | if (m_layout_mode == LayoutMode::Normal) { |
112 | | // We can also layout absolutely positioned boxes within this BFC. |
113 | 0 | for (auto& box : m_absolutely_positioned_boxes) { |
114 | 0 | auto& cb_state = m_state.get(*box->containing_block()); |
115 | 0 | auto available_width = AvailableSize::make_definite(cb_state.content_width() + cb_state.padding_left + cb_state.padding_right); |
116 | 0 | auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom); |
117 | 0 | layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height)); |
118 | 0 | } |
119 | 0 | } |
120 | 0 | } |
121 | | |
122 | | bool BlockFormattingContext::box_should_avoid_floats_because_it_establishes_fc(Box const& box) |
123 | 0 | { |
124 | 0 | if (auto formatting_context_type = formatting_context_type_created_by_box(box); formatting_context_type.has_value()) { |
125 | 0 | if (formatting_context_type.value() == Type::Block) |
126 | 0 | return true; |
127 | 0 | if (formatting_context_type.value() == Type::Flex) |
128 | 0 | return true; |
129 | 0 | if (formatting_context_type.value() == Type::Grid) |
130 | 0 | return true; |
131 | 0 | } |
132 | 0 | return false; |
133 | 0 | } |
134 | | |
135 | | void BlockFormattingContext::compute_width(Box const& box, AvailableSpace const& available_space) |
136 | 0 | { |
137 | 0 | auto remaining_available_space = available_space; |
138 | 0 | if (available_space.width.is_definite() && box_should_avoid_floats_because_it_establishes_fc(box)) { |
139 | | // NOTE: Although CSS 2.2 specification says that only block formatting contexts should avoid floats, |
140 | | // we also do this for flex and grid formatting contexts, because that how other engines behave. |
141 | | // 9.5 Floats |
142 | | // The border box of a table, a block-level replaced element, or an element in the normal flow that establishes a |
143 | | // new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the margin |
144 | | // box of any floats in the same block formatting context as the element itself. If necessary, implementations should |
145 | | // clear the said element by placing it below any preceding floats, but may place it adjacent to such floats if there is |
146 | | // sufficient space. They may even make the border box of said element narrower than defined by section 10.3.3. |
147 | | // CSS2 does not define when a UA may put said element next to the float or by how much said element may |
148 | | // become narrower. |
149 | 0 | auto intrusion = intrusion_by_floats_into_box(box, 0); |
150 | 0 | auto remaining_width = available_space.width.to_px_or_zero() - intrusion.left - intrusion.right; |
151 | 0 | remaining_available_space.width = AvailableSize::make_definite(remaining_width); |
152 | 0 | } |
153 | |
|
154 | 0 | if (box_is_sized_as_replaced_element(box)) { |
155 | | // FIXME: This should not be done *by* ReplacedBox |
156 | 0 | if (is<ReplacedBox>(box)) { |
157 | 0 | auto& replaced = verify_cast<ReplacedBox>(box); |
158 | | // FIXME: This const_cast is gross. |
159 | 0 | const_cast<ReplacedBox&>(replaced).prepare_for_replaced_layout(); |
160 | 0 | } |
161 | 0 | compute_width_for_block_level_replaced_element_in_normal_flow(box, remaining_available_space); |
162 | 0 | if (box.is_floating()) { |
163 | | // 10.3.6 Floating, replaced elements: |
164 | | // https://www.w3.org/TR/CSS22/visudet.html#float-replaced-width |
165 | 0 | return; |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | 0 | if (box.is_floating()) { |
170 | | // 10.3.5 Floating, non-replaced elements: |
171 | | // https://www.w3.org/TR/CSS22/visudet.html#float-width |
172 | 0 | compute_width_for_floating_box(box, available_space); |
173 | 0 | return; |
174 | 0 | } |
175 | | |
176 | 0 | auto const& computed_values = box.computed_values(); |
177 | |
|
178 | 0 | auto width_of_containing_block = remaining_available_space.width.to_px_or_zero(); |
179 | |
|
180 | 0 | auto zero_value = CSS::Length::make_px(0); |
181 | |
|
182 | 0 | auto margin_left = CSS::Length::make_auto(); |
183 | 0 | auto margin_right = CSS::Length::make_auto(); |
184 | 0 | auto const padding_left = computed_values.padding().left().resolved(box, width_of_containing_block).to_px(box); |
185 | 0 | auto const padding_right = computed_values.padding().right().resolved(box, width_of_containing_block).to_px(box); |
186 | |
|
187 | 0 | auto& box_state = m_state.get_mutable(box); |
188 | 0 | box_state.border_left = computed_values.border_left().width; |
189 | 0 | box_state.border_right = computed_values.border_right().width; |
190 | 0 | box_state.padding_left = padding_left; |
191 | 0 | box_state.padding_right = padding_right; |
192 | | |
193 | | // NOTE: If we are calculating the min-content or max-content width of this box, |
194 | | // and the width should be treated as auto, then we can simply return here, |
195 | | // as the preferred width and min/max constraints are irrelevant for intrinsic sizing. |
196 | 0 | if (box_state.width_constraint != SizeConstraint::None) |
197 | 0 | return; |
198 | | |
199 | 0 | auto try_compute_width = [&](CSS::Length const& a_width) { |
200 | 0 | CSS::Length width = a_width; |
201 | 0 | margin_left = computed_values.margin().left().resolved(box, width_of_containing_block); |
202 | 0 | margin_right = computed_values.margin().right().resolved(box, width_of_containing_block); |
203 | 0 | CSSPixels total_px = computed_values.border_left().width + computed_values.border_right().width; |
204 | 0 | for (auto& value : { margin_left, CSS::Length::make_px(padding_left), width, CSS::Length::make_px(padding_right), margin_right }) { |
205 | 0 | total_px += value.to_px(box); |
206 | 0 | } |
207 | |
|
208 | 0 | if (!box.is_inline()) { |
209 | | // 10.3.3 Block-level, non-replaced elements in normal flow |
210 | | // If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero. |
211 | 0 | if (!width.is_auto() && total_px > width_of_containing_block) { |
212 | 0 | if (margin_left.is_auto()) |
213 | 0 | margin_left = zero_value; |
214 | 0 | if (margin_right.is_auto()) |
215 | 0 | margin_right = zero_value; |
216 | 0 | } |
217 | | |
218 | | // 10.3.3 cont'd. |
219 | 0 | auto underflow_px = width_of_containing_block - total_px; |
220 | 0 | if (available_space.width.is_intrinsic_sizing_constraint()) |
221 | 0 | underflow_px = 0; |
222 | |
|
223 | 0 | if (width.is_auto()) { |
224 | 0 | if (margin_left.is_auto()) |
225 | 0 | margin_left = zero_value; |
226 | 0 | if (margin_right.is_auto()) |
227 | 0 | margin_right = zero_value; |
228 | |
|
229 | 0 | if (available_space.width.is_definite()) { |
230 | 0 | if (underflow_px >= 0) { |
231 | 0 | width = CSS::Length::make_px(underflow_px); |
232 | 0 | } else { |
233 | 0 | width = zero_value; |
234 | 0 | margin_right = CSS::Length::make_px(margin_right.to_px(box) + underflow_px); |
235 | 0 | } |
236 | 0 | } |
237 | 0 | } else { |
238 | 0 | if (!margin_left.is_auto() && !margin_right.is_auto()) { |
239 | 0 | margin_right = CSS::Length::make_px(margin_right.to_px(box) + underflow_px); |
240 | 0 | } else if (!margin_left.is_auto() && margin_right.is_auto()) { |
241 | 0 | margin_right = CSS::Length::make_px(underflow_px); |
242 | 0 | } else if (margin_left.is_auto() && !margin_right.is_auto()) { |
243 | 0 | margin_left = CSS::Length::make_px(underflow_px); |
244 | 0 | } else { // margin_left.is_auto() && margin_right.is_auto() |
245 | 0 | auto half_of_the_underflow = CSS::Length::make_px(underflow_px / 2); |
246 | 0 | margin_left = half_of_the_underflow; |
247 | 0 | margin_right = half_of_the_underflow; |
248 | 0 | } |
249 | 0 | } |
250 | 0 | } |
251 | |
|
252 | 0 | return width; |
253 | 0 | }; |
254 | |
|
255 | 0 | auto input_width = [&] { |
256 | 0 | if (box_is_sized_as_replaced_element(box)) { |
257 | | // NOTE: Replaced elements had their width calculated independently above. |
258 | | // We use that width as the input here to ensure that margins get resolved. |
259 | 0 | return CSS::Length::make_px(box_state.content_width()); |
260 | 0 | } |
261 | 0 | if (is<TableWrapper>(box)) |
262 | 0 | return CSS::Length::make_px(compute_table_box_width_inside_table_wrapper(box, remaining_available_space)); |
263 | 0 | if (should_treat_width_as_auto(box, remaining_available_space)) |
264 | 0 | return CSS::Length::make_auto(); |
265 | 0 | return CSS::Length::make_px(calculate_inner_width(box, remaining_available_space.width, computed_values.width())); |
266 | 0 | }(); |
267 | | |
268 | | // 1. The tentative used width is calculated (without 'min-width' and 'max-width') |
269 | 0 | auto used_width = try_compute_width(input_width); |
270 | | |
271 | | // 2. The tentative used width is greater than 'max-width', the rules above are applied again, |
272 | | // but this time using the computed value of 'max-width' as the computed value for 'width'. |
273 | 0 | if (!should_treat_max_width_as_none(box, available_space.width)) { |
274 | 0 | auto max_width = calculate_inner_width(box, remaining_available_space.width, computed_values.max_width()); |
275 | 0 | auto used_width_px = used_width.is_auto() ? CSSPixels { 0 } : used_width.to_px(box); |
276 | 0 | if (used_width_px > max_width) { |
277 | 0 | used_width = try_compute_width(CSS::Length::make_px(max_width)); |
278 | 0 | } |
279 | 0 | } |
280 | | |
281 | | // 3. If the resulting width is smaller than 'min-width', the rules above are applied again, |
282 | | // but this time using the value of 'min-width' as the computed value for 'width'. |
283 | 0 | if (!computed_values.min_width().is_auto()) { |
284 | 0 | auto min_width = calculate_inner_width(box, remaining_available_space.width, computed_values.min_width()); |
285 | 0 | auto used_width_px = used_width.is_auto() ? remaining_available_space.width : AvailableSize::make_definite(used_width.to_px(box)); |
286 | 0 | if (used_width_px < min_width) { |
287 | 0 | used_width = try_compute_width(CSS::Length::make_px(min_width)); |
288 | 0 | } |
289 | 0 | } |
290 | |
|
291 | 0 | if (!box_is_sized_as_replaced_element(box) && !used_width.is_auto()) |
292 | 0 | box_state.set_content_width(used_width.to_px(box)); |
293 | |
|
294 | 0 | box_state.margin_left = margin_left.to_px(box); |
295 | 0 | box_state.margin_right = margin_right.to_px(box); |
296 | 0 | } |
297 | | |
298 | | void BlockFormattingContext::compute_width_for_floating_box(Box const& box, AvailableSpace const& available_space) |
299 | 0 | { |
300 | | // 10.3.5 Floating, non-replaced elements |
301 | 0 | auto& computed_values = box.computed_values(); |
302 | |
|
303 | 0 | auto zero_value = CSS::Length::make_px(0); |
304 | 0 | auto width_of_containing_block = available_space.width.to_px_or_zero(); |
305 | |
|
306 | 0 | auto margin_left = computed_values.margin().left().resolved(box, width_of_containing_block); |
307 | 0 | auto margin_right = computed_values.margin().right().resolved(box, width_of_containing_block); |
308 | | |
309 | | // If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'. |
310 | 0 | if (margin_left.is_auto()) |
311 | 0 | margin_left = zero_value; |
312 | 0 | if (margin_right.is_auto()) |
313 | 0 | margin_right = zero_value; |
314 | |
|
315 | 0 | auto& box_state = m_state.get_mutable(box); |
316 | 0 | box_state.padding_left = computed_values.padding().left().resolved(box, width_of_containing_block).to_px(box); |
317 | 0 | box_state.padding_right = computed_values.padding().right().resolved(box, width_of_containing_block).to_px(box); |
318 | 0 | box_state.margin_left = margin_left.to_px(box); |
319 | 0 | box_state.margin_right = margin_right.to_px(box); |
320 | 0 | box_state.border_left = computed_values.border_left().width; |
321 | 0 | box_state.border_right = computed_values.border_right().width; |
322 | |
|
323 | 0 | auto compute_width = [&](auto width) { |
324 | | // If 'width' is computed as 'auto', the used value is the "shrink-to-fit" width. |
325 | 0 | if (width.is_auto()) { |
326 | 0 | auto result = calculate_shrink_to_fit_widths(box); |
327 | |
|
328 | 0 | if (available_space.width.is_definite()) { |
329 | | // Find the available width: in this case, this is the width of the containing |
330 | | // block minus the used values of 'margin-left', 'border-left-width', 'padding-left', |
331 | | // 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars. |
332 | 0 | auto available_width = available_space.width.to_px_or_zero() |
333 | 0 | - margin_left.to_px(box) - computed_values.border_left().width - box_state.padding_left |
334 | 0 | - box_state.padding_right - computed_values.border_right().width - margin_right.to_px(box); |
335 | | // Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width). |
336 | 0 | width = CSS::Length::make_px(min(max(result.preferred_minimum_width, available_width), result.preferred_width)); |
337 | 0 | } else if (available_space.width.is_indefinite() || available_space.width.is_max_content()) { |
338 | | // Fold the formula for shrink-to-fit width for indefinite and max-content available width. |
339 | 0 | width = CSS::Length::make_px(result.preferred_width); |
340 | 0 | } else { |
341 | | // Fold the formula for shrink-to-fit width for min-content available width. |
342 | 0 | width = CSS::Length::make_px(min(result.preferred_minimum_width, result.preferred_width)); |
343 | 0 | } |
344 | 0 | } |
345 | |
|
346 | 0 | return width; |
347 | 0 | }; |
348 | |
|
349 | 0 | auto input_width = [&] { |
350 | 0 | if (should_treat_width_as_auto(box, available_space)) |
351 | 0 | return CSS::Length::make_auto(); |
352 | 0 | return CSS::Length::make_px(calculate_inner_width(box, available_space.width, computed_values.width())); |
353 | 0 | }(); |
354 | | |
355 | | // 1. The tentative used width is calculated (without 'min-width' and 'max-width') |
356 | 0 | auto width = compute_width(input_width); |
357 | | |
358 | | // 2. The tentative used width is greater than 'max-width', the rules above are applied again, |
359 | | // but this time using the computed value of 'max-width' as the computed value for 'width'. |
360 | 0 | if (!should_treat_max_width_as_none(box, available_space.width)) { |
361 | 0 | auto max_width = calculate_inner_width(box, available_space.width, computed_values.max_width()); |
362 | 0 | if (width.to_px(box) > max_width) |
363 | 0 | width = compute_width(CSS::Length::make_px(max_width)); |
364 | 0 | } |
365 | | |
366 | | // 3. If the resulting width is smaller than 'min-width', the rules above are applied again, |
367 | | // but this time using the value of 'min-width' as the computed value for 'width'. |
368 | 0 | if (!computed_values.min_width().is_auto()) { |
369 | 0 | auto min_width = calculate_inner_width(box, available_space.width, computed_values.min_width()); |
370 | 0 | if (width.to_px(box) < min_width) |
371 | 0 | width = compute_width(CSS::Length::make_px(min_width)); |
372 | 0 | } |
373 | |
|
374 | 0 | box_state.set_content_width(width.to_px(box)); |
375 | 0 | } |
376 | | |
377 | | void BlockFormattingContext::compute_width_for_block_level_replaced_element_in_normal_flow(Box const& box, AvailableSpace const& available_space) |
378 | 0 | { |
379 | | // 10.3.6 Floating, replaced elements |
380 | 0 | auto& computed_values = box.computed_values(); |
381 | |
|
382 | 0 | auto zero_value = CSS::Length::make_px(0); |
383 | 0 | auto width_of_containing_block = available_space.width.to_px_or_zero(); |
384 | | |
385 | | // 10.3.4 Block-level, replaced elements in normal flow |
386 | | // The used value of 'width' is determined as for inline replaced elements. Then the rules for |
387 | | // non-replaced block-level elements are applied to determine the margins. |
388 | 0 | auto margin_left = computed_values.margin().left().resolved(box, width_of_containing_block); |
389 | 0 | auto margin_right = computed_values.margin().right().resolved(box, width_of_containing_block); |
390 | 0 | auto const padding_left = computed_values.padding().left().resolved(box, width_of_containing_block).to_px(box); |
391 | 0 | auto const padding_right = computed_values.padding().right().resolved(box, width_of_containing_block).to_px(box); |
392 | | |
393 | | // If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'. |
394 | 0 | if (margin_left.is_auto()) |
395 | 0 | margin_left = zero_value; |
396 | 0 | if (margin_right.is_auto()) |
397 | 0 | margin_right = zero_value; |
398 | |
|
399 | 0 | auto& box_state = m_state.get_mutable(box); |
400 | 0 | box_state.set_content_width(compute_width_for_replaced_element(box, available_space)); |
401 | 0 | box_state.margin_left = margin_left.to_px(box); |
402 | 0 | box_state.margin_right = margin_right.to_px(box); |
403 | 0 | box_state.border_left = computed_values.border_left().width; |
404 | 0 | box_state.border_right = computed_values.border_right().width; |
405 | 0 | box_state.padding_left = padding_left; |
406 | 0 | box_state.padding_right = padding_right; |
407 | 0 | } |
408 | | |
409 | | void BlockFormattingContext::resolve_used_height_if_not_treated_as_auto(Box const& box, AvailableSpace const& available_space) |
410 | 0 | { |
411 | 0 | if (should_treat_height_as_auto(box, available_space)) { |
412 | 0 | return; |
413 | 0 | } |
414 | | |
415 | 0 | auto const& computed_values = box.computed_values(); |
416 | 0 | auto& box_state = m_state.get_mutable(box); |
417 | |
|
418 | 0 | auto height = calculate_inner_height(box, available_space.height, box.computed_values().height()); |
419 | |
|
420 | 0 | if (!should_treat_max_height_as_none(box, available_space.height)) { |
421 | 0 | if (!computed_values.max_height().is_auto()) { |
422 | 0 | auto max_height = calculate_inner_height(box, available_space.height, computed_values.max_height()); |
423 | 0 | height = min(height, max_height); |
424 | 0 | } |
425 | 0 | } |
426 | 0 | if (!computed_values.min_height().is_auto()) { |
427 | 0 | height = max(height, calculate_inner_height(box, available_space.height, computed_values.min_height())); |
428 | 0 | } |
429 | |
|
430 | 0 | box_state.set_content_height(height); |
431 | 0 | box_state.set_has_definite_height(true); |
432 | 0 | } |
433 | | |
434 | | void BlockFormattingContext::resolve_used_height_if_treated_as_auto(Box const& box, AvailableSpace const& available_space, FormattingContext const* box_formatting_context) |
435 | 0 | { |
436 | 0 | if (!should_treat_height_as_auto(box, available_space)) { |
437 | 0 | return; |
438 | 0 | } |
439 | | |
440 | 0 | auto const& computed_values = box.computed_values(); |
441 | 0 | auto& box_state = m_state.get_mutable(box); |
442 | |
|
443 | 0 | CSSPixels height = 0; |
444 | 0 | if (box_is_sized_as_replaced_element(box)) { |
445 | 0 | height = compute_height_for_replaced_element(box, available_space); |
446 | 0 | } else { |
447 | 0 | if (box_formatting_context) { |
448 | 0 | height = box_formatting_context->automatic_content_height(); |
449 | 0 | } else { |
450 | 0 | height = compute_auto_height_for_block_level_element(box, m_state.get(box).available_inner_space_or_constraints_from(available_space)); |
451 | 0 | } |
452 | 0 | } |
453 | |
|
454 | 0 | if (!should_treat_max_height_as_none(box, available_space.height)) { |
455 | 0 | if (!computed_values.max_height().is_auto()) { |
456 | 0 | auto max_height = calculate_inner_height(box, available_space.height, computed_values.max_height()); |
457 | 0 | height = min(height, max_height); |
458 | 0 | } |
459 | 0 | } |
460 | 0 | if (!computed_values.min_height().is_auto()) { |
461 | 0 | height = max(height, calculate_inner_height(box, available_space.height, computed_values.min_height())); |
462 | 0 | } |
463 | |
|
464 | 0 | if (box.document().in_quirks_mode() |
465 | 0 | && box.dom_node() |
466 | 0 | && box.dom_node()->is_html_html_element() |
467 | 0 | && box.computed_values().height().is_auto()) { |
468 | | // 3.6. The html element fills the viewport quirk |
469 | | // https://quirks.spec.whatwg.org/#the-html-element-fills-the-viewport-quirk |
470 | | // FIXME: Handle vertical writing mode. |
471 | | |
472 | | // 1. Let margins be sum of the used values of the margin-left and margin-right properties of element |
473 | | // if element has a vertical writing mode, otherwise let margins be the sum of the used values of |
474 | | // the margin-top and margin-bottom properties of element. |
475 | 0 | auto margins = box_state.margin_top + box_state.margin_bottom; |
476 | | |
477 | | // 2. Let size be the size of the initial containing block in the block flow direction minus margins. |
478 | 0 | auto size = box_state.containing_block_used_values()->content_height() - margins; |
479 | | |
480 | | // 3. Return the bigger value of size and the normal border box size the element would have |
481 | | // according to the CSS specification. |
482 | 0 | height = max(size, height); |
483 | | |
484 | | // NOTE: The height of the root element when affected by this quirk is considered to be definite. |
485 | 0 | box_state.set_has_definite_height(true); |
486 | 0 | } |
487 | |
|
488 | 0 | box_state.set_content_height(height); |
489 | 0 | } |
490 | | |
491 | | void BlockFormattingContext::layout_inline_children(BlockContainer const& block_container, AvailableSpace const& available_space) |
492 | 0 | { |
493 | 0 | VERIFY(block_container.children_are_inline()); |
494 | | |
495 | 0 | auto& block_container_state = m_state.get_mutable(block_container); |
496 | |
|
497 | 0 | InlineFormattingContext context(m_state, m_layout_mode, block_container, block_container_state, *this); |
498 | 0 | context.run(available_space); |
499 | |
|
500 | 0 | if (!block_container_state.has_definite_width()) { |
501 | | // NOTE: min-width or max-width for boxes with inline children can only be applied after inside layout |
502 | | // is done and width of box content is known |
503 | 0 | auto used_width_px = context.automatic_content_width(); |
504 | | // https://www.w3.org/TR/css-sizing-3/#sizing-values |
505 | | // Percentages are resolved against the width/height, as appropriate, of the boxโs containing block. |
506 | 0 | auto containing_block_width = m_state.get(*block_container.containing_block()).content_width(); |
507 | 0 | auto available_width = AvailableSize::make_definite(containing_block_width); |
508 | 0 | if (!should_treat_max_width_as_none(block_container, available_space.width)) { |
509 | 0 | auto max_width_px = calculate_inner_width(block_container, available_width, block_container.computed_values().max_width()); |
510 | 0 | if (used_width_px > max_width_px) |
511 | 0 | used_width_px = max_width_px; |
512 | 0 | } |
513 | |
|
514 | 0 | auto should_treat_min_width_as_auto = [&] { |
515 | 0 | auto const& available_width = available_space.width; |
516 | 0 | auto const& min_width = block_container.computed_values().min_width(); |
517 | 0 | if (min_width.is_auto()) |
518 | 0 | return true; |
519 | 0 | if (min_width.is_fit_content() && available_width.is_intrinsic_sizing_constraint()) |
520 | 0 | return true; |
521 | 0 | if (min_width.is_max_content() && available_width.is_max_content()) |
522 | 0 | return true; |
523 | 0 | if (min_width.is_min_content() && available_width.is_min_content()) |
524 | 0 | return true; |
525 | 0 | return false; |
526 | 0 | }(); |
527 | 0 | if (!should_treat_min_width_as_auto) { |
528 | 0 | auto min_width_px = calculate_inner_width(block_container, available_width, block_container.computed_values().min_width()); |
529 | 0 | if (used_width_px < min_width_px) |
530 | 0 | used_width_px = min_width_px; |
531 | 0 | } |
532 | 0 | block_container_state.set_content_width(used_width_px); |
533 | 0 | block_container_state.set_content_height(context.automatic_content_height()); |
534 | 0 | } |
535 | 0 | } |
536 | | |
537 | | CSSPixels BlockFormattingContext::compute_auto_height_for_block_level_element(Box const& box, AvailableSpace const& available_space) |
538 | 0 | { |
539 | 0 | if (creates_block_formatting_context(box)) { |
540 | 0 | return compute_auto_height_for_block_formatting_context_root(box); |
541 | 0 | } |
542 | | |
543 | 0 | auto const& box_state = m_state.get(box); |
544 | |
|
545 | 0 | auto display = box.display(); |
546 | 0 | if (display.is_flex_inside()) { |
547 | | // https://drafts.csswg.org/css-flexbox-1/#algo-main-container |
548 | | // NOTE: The automatic block size of a block-level flex container is its max-content size. |
549 | 0 | return calculate_max_content_height(box, available_space.width.to_px_or_zero()); |
550 | 0 | } |
551 | 0 | if (display.is_grid_inside()) { |
552 | | // https://www.w3.org/TR/css-grid-2/#intrinsic-sizes |
553 | | // In both inline and block formatting contexts, the grid containerโs auto block size is its |
554 | | // max-content size. |
555 | 0 | return calculate_max_content_height(box, available_space.width.to_px_or_zero()); |
556 | 0 | } |
557 | 0 | if (display.is_table_inside()) { |
558 | 0 | return calculate_max_content_height(box, available_space.width.to_px_or_zero()); |
559 | 0 | } |
560 | | |
561 | | // https://www.w3.org/TR/CSS22/visudet.html#normal-block |
562 | | // 10.6.3 Block-level non-replaced elements in normal flow when 'overflow' computes to 'visible' |
563 | | |
564 | | // The element's height is the distance from its top content edge to the first applicable of the following: |
565 | | |
566 | | // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines |
567 | 0 | if (box.children_are_inline() && !box_state.line_boxes.is_empty()) |
568 | 0 | return box_state.line_boxes.last().bottom(); |
569 | | |
570 | | // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin does not collapse with the element's bottom margin |
571 | | // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin |
572 | 0 | if (!box.children_are_inline()) { |
573 | 0 | for (auto* child_box = box.last_child_of_type<Box>(); child_box; child_box = child_box->previous_sibling_of_type<Box>()) { |
574 | 0 | if (child_box->is_absolutely_positioned() || child_box->is_floating()) |
575 | 0 | continue; |
576 | | |
577 | | // FIXME: This is hack. If the last child is a list-item marker box, we ignore it for purposes of height calculation. |
578 | | // Perhaps markers should not be considered in-flow(?) Perhaps they should always be the first child of the list-item |
579 | | // box instead of the last child. |
580 | 0 | if (child_box->is_list_item_marker_box()) |
581 | 0 | continue; |
582 | | |
583 | 0 | auto const& child_box_state = m_state.get(*child_box); |
584 | |
|
585 | 0 | if (margins_collapse_through(*child_box, m_state)) |
586 | 0 | continue; |
587 | | |
588 | 0 | auto margin_bottom = m_margin_state.current_collapsed_margin(); |
589 | 0 | if (box_state.padding_bottom == 0 && box_state.border_bottom == 0) { |
590 | 0 | m_margin_state.box_last_in_flow_child_margin_bottom_collapsed = true; |
591 | 0 | margin_bottom = 0; |
592 | 0 | } |
593 | |
|
594 | 0 | return max(CSSPixels(0), child_box_state.offset.y() + child_box_state.content_height() + child_box_state.border_box_bottom() + margin_bottom); |
595 | 0 | } |
596 | 0 | } |
597 | | |
598 | | // 4. zero, otherwise |
599 | 0 | return 0; |
600 | 0 | } |
601 | | |
602 | | void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContainer const& block_container, CSSPixels& bottom_of_lowest_margin_box, AvailableSpace const& available_space) |
603 | 0 | { |
604 | 0 | auto& box_state = m_state.get_mutable(box); |
605 | |
|
606 | 0 | if (box.is_absolutely_positioned()) { |
607 | 0 | box_state.vertical_offset_of_parent_block_container = m_y_offset_of_current_block_container.value(); |
608 | 0 | m_absolutely_positioned_boxes.append(box); |
609 | 0 | return; |
610 | 0 | } |
611 | | |
612 | | // NOTE: ListItemMarkerBoxes are placed by their corresponding ListItemBox. |
613 | 0 | if (is<ListItemMarkerBox>(box)) |
614 | 0 | return; |
615 | | |
616 | 0 | resolve_vertical_box_model_metrics(box); |
617 | |
|
618 | 0 | if (box.is_floating()) { |
619 | 0 | auto const y = m_y_offset_of_current_block_container.value(); |
620 | 0 | auto margin_top = !m_margin_state.has_block_container_waiting_for_final_y_position() ? m_margin_state.current_collapsed_margin() : 0; |
621 | 0 | layout_floating_box(box, block_container, available_space, margin_top + y); |
622 | 0 | bottom_of_lowest_margin_box = max(bottom_of_lowest_margin_box, box_state.offset.y() + box_state.content_height() + box_state.margin_box_bottom()); |
623 | 0 | return; |
624 | 0 | } |
625 | | |
626 | 0 | m_margin_state.add_margin(box_state.margin_top); |
627 | 0 | auto introduce_clearance = clear_floating_boxes(box, {}); |
628 | 0 | if (introduce_clearance == DidIntroduceClearance::Yes) |
629 | 0 | m_margin_state.reset(); |
630 | |
|
631 | 0 | auto const y = m_y_offset_of_current_block_container.value(); |
632 | |
|
633 | 0 | auto box_is_html_element_in_quirks_mode = box.document().in_quirks_mode() |
634 | 0 | && box.dom_node() |
635 | 0 | && box.dom_node()->is_html_html_element() |
636 | 0 | && box.computed_values().height().is_auto(); |
637 | | |
638 | | // NOTE: In quirks mode, the html element's height matches the viewport so it can be treated as definite |
639 | 0 | if (box_state.has_definite_height() || box_is_html_element_in_quirks_mode) { |
640 | 0 | resolve_used_height_if_treated_as_auto(box, available_space); |
641 | 0 | } |
642 | |
|
643 | 0 | auto independent_formatting_context = create_independent_formatting_context_if_needed(m_state, m_layout_mode, box); |
644 | | |
645 | | // NOTE: It is possible to encounter SVGMaskBox nodes while doing layout of formatting context established by <foreignObject> with a mask. |
646 | | // We should skip and let SVGFormattingContext take care of them. |
647 | 0 | if (box.is_svg_mask_box()) |
648 | 0 | return; |
649 | | |
650 | 0 | if (!independent_formatting_context && !is<BlockContainer>(box)) { |
651 | 0 | dbgln("FIXME: Block-level box is not BlockContainer but does not create formatting context: {}", box.debug_description()); |
652 | 0 | return; |
653 | 0 | } |
654 | | |
655 | 0 | m_margin_state.update_block_waiting_for_final_y_position(); |
656 | 0 | CSSPixels margin_top = m_margin_state.current_collapsed_margin(); |
657 | |
|
658 | 0 | if (m_margin_state.has_block_container_waiting_for_final_y_position()) { |
659 | | // If first child margin top will collapse with margin-top of containing block then margin-top of child is 0 |
660 | 0 | margin_top = 0; |
661 | 0 | } |
662 | |
|
663 | 0 | if (independent_formatting_context) { |
664 | | // Margins of elements that establish new formatting contexts do not collapse with their in-flow children |
665 | 0 | m_margin_state.reset(); |
666 | 0 | } |
667 | |
|
668 | 0 | place_block_level_element_in_normal_flow_vertically(box, y + margin_top); |
669 | |
|
670 | 0 | compute_width(box, available_space); |
671 | |
|
672 | 0 | place_block_level_element_in_normal_flow_horizontally(box, available_space); |
673 | |
|
674 | 0 | resolve_used_height_if_not_treated_as_auto(box, available_space); |
675 | | |
676 | | // NOTE: Flex containers with `auto` height are treated as `max-content`, so we can compute their height early. |
677 | 0 | if (box.is_replaced_box() || box.display().is_flex_inside()) { |
678 | 0 | resolve_used_height_if_treated_as_auto(box, available_space); |
679 | 0 | } |
680 | | |
681 | | // Before we insert the children of a list item we need to know the location of the marker. |
682 | | // If we do not do this then left-floating elements inside the list item will push the marker to the right, |
683 | | // in some cases even causing it to overlap with the non-floating content of the list. |
684 | 0 | CSSPixels left_space_before_children_formatted; |
685 | 0 | if (is<ListItemBox>(box)) { |
686 | 0 | auto const& li_box = static_cast<ListItemBox const&>(box); |
687 | | |
688 | | // We need to ensure that our height and width are final before we calculate our left offset. |
689 | | // Otherwise, the y at which we calculate the intrusion by floats might be incorrect. |
690 | 0 | ensure_sizes_correct_for_left_offset_calculation(li_box); |
691 | |
|
692 | 0 | auto const& list_item_state = m_state.get(li_box); |
693 | 0 | auto const& marker_state = m_state.get(*li_box.marker()); |
694 | |
|
695 | 0 | auto offset_y = max(CSSPixels(0), (li_box.marker()->computed_values().line_height() - marker_state.content_height()) / 2); |
696 | 0 | auto space_used_before_children_formatted = intrusion_by_floats_into_box(list_item_state, offset_y); |
697 | |
|
698 | 0 | left_space_before_children_formatted = space_used_before_children_formatted.left; |
699 | 0 | } |
700 | |
|
701 | 0 | if (independent_formatting_context) { |
702 | | // This box establishes a new formatting context. Pass control to it. |
703 | 0 | independent_formatting_context->run(box_state.available_inner_space_or_constraints_from(available_space)); |
704 | 0 | } else { |
705 | | // This box participates in the current block container's flow. |
706 | 0 | if (box.children_are_inline()) { |
707 | 0 | layout_inline_children(verify_cast<BlockContainer>(box), box_state.available_inner_space_or_constraints_from(available_space)); |
708 | 0 | } else { |
709 | 0 | if (box_state.border_top > 0 || box_state.padding_top > 0) { |
710 | | // margin-top of block container can't collapse with it's children if it has non zero border or padding |
711 | 0 | m_margin_state.reset(); |
712 | 0 | } else if (!m_margin_state.has_block_container_waiting_for_final_y_position()) { |
713 | | // margin-top of block container can be updated during children layout hence it's final y position yet to be determined |
714 | 0 | m_margin_state.register_block_container_y_position_update_callback([&, introduce_clearance](CSSPixels margin_top) { |
715 | 0 | if (introduce_clearance == DidIntroduceClearance::No) { |
716 | 0 | place_block_level_element_in_normal_flow_vertically(box, margin_top + y); |
717 | 0 | } |
718 | 0 | }); |
719 | 0 | } |
720 | |
|
721 | 0 | layout_block_level_children(verify_cast<BlockContainer>(box), box_state.available_inner_space_or_constraints_from(available_space)); |
722 | 0 | } |
723 | 0 | } |
724 | | |
725 | | // Tables already set their height during the independent formatting context run. When multi-line text cells are involved, using different |
726 | | // available space here than during the independent formatting context run can result in different line breaks and thus a different height. |
727 | 0 | if (!box.display().is_table_inside()) { |
728 | 0 | resolve_used_height_if_treated_as_auto(box, available_space, independent_formatting_context); |
729 | 0 | } |
730 | |
|
731 | 0 | if (independent_formatting_context || !margins_collapse_through(box, m_state)) { |
732 | 0 | if (!m_margin_state.box_last_in_flow_child_margin_bottom_collapsed) { |
733 | 0 | m_margin_state.reset(); |
734 | 0 | } |
735 | 0 | m_y_offset_of_current_block_container = box_state.offset.y() + box_state.content_height() + box_state.border_box_bottom(); |
736 | 0 | } |
737 | 0 | m_margin_state.box_last_in_flow_child_margin_bottom_collapsed = false; |
738 | |
|
739 | 0 | m_margin_state.add_margin(box_state.margin_bottom); |
740 | 0 | m_margin_state.update_block_waiting_for_final_y_position(); |
741 | |
|
742 | 0 | compute_inset(box); |
743 | | |
744 | | // Now that our children are formatted we place the ListItemBox with the left space we remembered. |
745 | 0 | if (is<ListItemBox>(box)) { |
746 | 0 | layout_list_item_marker(static_cast<ListItemBox const&>(box), left_space_before_children_formatted); |
747 | 0 | } |
748 | |
|
749 | 0 | bottom_of_lowest_margin_box = max(bottom_of_lowest_margin_box, box_state.offset.y() + box_state.content_height() + box_state.margin_box_bottom()); |
750 | |
|
751 | 0 | if (independent_formatting_context) |
752 | 0 | independent_formatting_context->parent_context_did_dimension_child_root_box(); |
753 | 0 | } |
754 | | |
755 | | void BlockFormattingContext::layout_block_level_children(BlockContainer const& block_container, AvailableSpace const& available_space) |
756 | 0 | { |
757 | 0 | VERIFY(!block_container.children_are_inline()); |
758 | | |
759 | 0 | CSSPixels bottom_of_lowest_margin_box = 0; |
760 | |
|
761 | 0 | TemporaryChange<Optional<CSSPixels>> change { m_y_offset_of_current_block_container, CSSPixels(0) }; |
762 | 0 | block_container.for_each_child_of_type<Box>([&](Box& box) { |
763 | 0 | layout_block_level_box(box, block_container, bottom_of_lowest_margin_box, available_space); |
764 | 0 | return IterationDecision::Continue; |
765 | 0 | }); |
766 | |
|
767 | 0 | m_margin_state.block_container_y_position_update_callback = {}; |
768 | |
|
769 | 0 | if (m_layout_mode == LayoutMode::IntrinsicSizing) { |
770 | 0 | auto& block_container_state = m_state.get_mutable(block_container); |
771 | 0 | if (!block_container_state.has_definite_width()) { |
772 | 0 | auto width = greatest_child_width(block_container); |
773 | 0 | auto const& computed_values = block_container.computed_values(); |
774 | | // NOTE: Min and max constraints are not applied to a box that is being sized as intrinsic because |
775 | | // according to css-sizing-3 spec: |
776 | | // The min-content size of a box in each axis is the size it would have if it was a float given an |
777 | | // auto size in that axis (and no minimum or maximum size in that axis) and if its containing block |
778 | | // was zero-sized in that axis. |
779 | 0 | if (block_container_state.width_constraint == SizeConstraint::None) { |
780 | 0 | if (!should_treat_max_width_as_none(block_container, available_space.width)) { |
781 | 0 | auto max_width = calculate_inner_width(block_container, available_space.width, |
782 | 0 | computed_values.max_width()); |
783 | 0 | width = min(width, max_width); |
784 | 0 | } |
785 | 0 | if (!computed_values.min_width().is_auto()) { |
786 | 0 | auto min_width = calculate_inner_width(block_container, available_space.width, |
787 | 0 | computed_values.min_width()); |
788 | 0 | width = max(width, min_width); |
789 | 0 | } |
790 | 0 | } |
791 | 0 | block_container_state.set_content_width(width); |
792 | 0 | block_container_state.set_content_height(bottom_of_lowest_margin_box); |
793 | 0 | } |
794 | 0 | } |
795 | 0 | } |
796 | | |
797 | | void BlockFormattingContext::resolve_vertical_box_model_metrics(Box const& box) |
798 | 0 | { |
799 | 0 | auto& box_state = m_state.get_mutable(box); |
800 | 0 | auto const& computed_values = box.computed_values(); |
801 | 0 | auto width_of_containing_block = containing_block_width_for(box); |
802 | |
|
803 | 0 | box_state.margin_top = computed_values.margin().top().to_px(box, width_of_containing_block); |
804 | 0 | box_state.margin_bottom = computed_values.margin().bottom().to_px(box, width_of_containing_block); |
805 | 0 | box_state.border_top = computed_values.border_top().width; |
806 | 0 | box_state.border_bottom = computed_values.border_bottom().width; |
807 | 0 | box_state.padding_top = computed_values.padding().top().to_px(box, width_of_containing_block); |
808 | 0 | box_state.padding_bottom = computed_values.padding().bottom().to_px(box, width_of_containing_block); |
809 | 0 | } |
810 | | |
811 | | CSSPixels BlockFormattingContext::BlockMarginState::current_collapsed_margin() const |
812 | 0 | { |
813 | 0 | return current_positive_collapsible_margin + current_negative_collapsible_margin; |
814 | 0 | } |
815 | | |
816 | | BlockFormattingContext::DidIntroduceClearance BlockFormattingContext::clear_floating_boxes(Node const& child_box, Optional<InlineFormattingContext&> inline_formatting_context) |
817 | 0 | { |
818 | 0 | auto const& computed_values = child_box.computed_values(); |
819 | 0 | auto result = DidIntroduceClearance::No; |
820 | |
|
821 | 0 | auto clear_floating_boxes = [&](FloatSideData& float_side) { |
822 | 0 | if (!float_side.current_boxes.is_empty()) { |
823 | | // NOTE: Floating boxes are globally relevant within this BFC, *but* their offset coordinates |
824 | | // are relative to their containing block. |
825 | | // This means that we have to first convert to a root-space Y coordinate before clearing, |
826 | | // and then convert back to a local Y coordinate when assigning the cleared offset to |
827 | | // the `child_box` layout state. |
828 | | |
829 | | // First, find the lowest margin box edge on this float side and calculate the Y offset just below it. |
830 | 0 | CSSPixels clearance_y_in_root = 0; |
831 | 0 | for (auto const& floating_box : float_side.current_boxes) { |
832 | 0 | auto floating_box_rect_in_root = margin_box_rect_in_ancestor_coordinate_space(floating_box.used_values, root()); |
833 | 0 | clearance_y_in_root = max(clearance_y_in_root, floating_box_rect_in_root.bottom()); |
834 | 0 | } |
835 | | |
836 | | // Then, convert the clearance Y to a coordinate relative to the containing block of `child_box`. |
837 | 0 | CSSPixels clearance_y_in_containing_block = clearance_y_in_root; |
838 | 0 | for (auto* containing_block = child_box.containing_block(); containing_block && containing_block != &root(); containing_block = containing_block->containing_block()) |
839 | 0 | clearance_y_in_containing_block -= m_state.get(*containing_block).offset.y(); |
840 | |
|
841 | 0 | if (inline_formatting_context.has_value()) { |
842 | 0 | if (clearance_y_in_containing_block > inline_formatting_context->vertical_float_clearance()) { |
843 | 0 | result = DidIntroduceClearance::Yes; |
844 | 0 | inline_formatting_context->set_vertical_float_clearance(clearance_y_in_containing_block); |
845 | 0 | } |
846 | 0 | } else if (clearance_y_in_containing_block > m_y_offset_of_current_block_container.value()) { |
847 | 0 | result = DidIntroduceClearance::Yes; |
848 | 0 | m_y_offset_of_current_block_container = clearance_y_in_containing_block; |
849 | 0 | } |
850 | |
|
851 | 0 | float_side.clear(); |
852 | 0 | } |
853 | 0 | }; |
854 | |
|
855 | 0 | if (computed_values.clear() == CSS::Clear::Left || computed_values.clear() == CSS::Clear::Both) |
856 | 0 | clear_floating_boxes(m_left_floats); |
857 | 0 | if (computed_values.clear() == CSS::Clear::Right || computed_values.clear() == CSS::Clear::Both) |
858 | 0 | clear_floating_boxes(m_right_floats); |
859 | |
|
860 | 0 | return result; |
861 | 0 | } |
862 | | |
863 | | void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically(Box const& child_box, CSSPixels y) |
864 | 0 | { |
865 | 0 | auto& box_state = m_state.get_mutable(child_box); |
866 | 0 | y += box_state.border_box_top(); |
867 | 0 | box_state.set_content_offset(CSSPixelPoint { box_state.offset.x(), y }); |
868 | 0 | } |
869 | | |
870 | | // Returns whether the given box has the given ancestor on the path to root, ignoring the anonymous blocks. |
871 | | static bool box_has_ancestor_in_non_anonymous_containing_block_chain(Box const* box, Box const& ancestor, Box const& root) |
872 | 0 | { |
873 | 0 | Box const* current_ancestor = box ? box->non_anonymous_containing_block() : &root; |
874 | 0 | while (current_ancestor != &root) { |
875 | 0 | if (current_ancestor == &ancestor) |
876 | 0 | return true; |
877 | 0 | current_ancestor = current_ancestor->non_anonymous_containing_block(); |
878 | 0 | } |
879 | 0 | return false; |
880 | 0 | } |
881 | | |
882 | | void BlockFormattingContext::place_block_level_element_in_normal_flow_horizontally(Box const& child_box, AvailableSpace const& available_space) |
883 | 0 | { |
884 | 0 | auto& box_state = m_state.get_mutable(child_box); |
885 | |
|
886 | 0 | CSSPixels x = 0; |
887 | 0 | CSSPixels available_width_within_containing_block = available_space.width.to_px_or_zero(); |
888 | |
|
889 | 0 | if ((!m_left_floats.current_boxes.is_empty() || !m_right_floats.current_boxes.is_empty()) |
890 | 0 | && creates_block_formatting_context(child_box)) { |
891 | 0 | auto box_in_root_rect = content_box_rect_in_ancestor_coordinate_space(box_state, root()); |
892 | 0 | auto space_and_containing_margin = space_used_and_containing_margin_for_floats(box_in_root_rect.y()); |
893 | 0 | available_width_within_containing_block -= space_and_containing_margin.left_used_space + space_and_containing_margin.right_used_space; |
894 | 0 | auto const& containing_box_state = m_state.get(*child_box.containing_block()); |
895 | 0 | if (box_has_ancestor_in_non_anonymous_containing_block_chain(space_and_containing_margin.matching_left_float_box, *child_box.non_anonymous_containing_block(), root())) |
896 | 0 | x = space_and_containing_margin.left_used_space; |
897 | 0 | else |
898 | | // If the floating box doesn't share a containing block with the child box, the child box margin should overlap with the width of the floating box. |
899 | 0 | x = max(space_and_containing_margin.left_used_space - containing_box_state.margin_left, 0); |
900 | 0 | } |
901 | |
|
902 | 0 | if (child_box.containing_block()->computed_values().text_align() == CSS::TextAlign::LibwebCenter) { |
903 | 0 | x += (available_width_within_containing_block / 2) - box_state.content_width() / 2; |
904 | 0 | } else if (child_box.containing_block()->computed_values().text_align() == CSS::TextAlign::LibwebRight) { |
905 | | // Subtracting the left margin here because left and right margins need to be swapped when aligning to the right |
906 | 0 | x += available_width_within_containing_block - box_state.content_width() - box_state.margin_box_left(); |
907 | 0 | } else { |
908 | 0 | x += box_state.margin_box_left(); |
909 | 0 | } |
910 | |
|
911 | 0 | box_state.set_content_offset({ x, box_state.offset.y() }); |
912 | 0 | } |
913 | | |
914 | | void BlockFormattingContext::layout_viewport(AvailableSpace const& available_space) |
915 | 0 | { |
916 | | // NOTE: If we are laying out a standalone SVG document, we give it some special treatment: |
917 | | // The root <svg> container gets the same size as the viewport, |
918 | | // and we call directly into the SVG layout code from here. |
919 | 0 | if (root().first_child() && root().first_child()->is_svg_svg_box()) { |
920 | 0 | auto const& svg_root = verify_cast<SVGSVGBox>(*root().first_child()); |
921 | 0 | auto content_height = m_state.get(*svg_root.containing_block()).content_height(); |
922 | 0 | m_state.get_mutable(svg_root).set_content_height(content_height); |
923 | 0 | auto svg_formatting_context = create_independent_formatting_context_if_needed(m_state, m_layout_mode, svg_root); |
924 | 0 | svg_formatting_context->run(available_space); |
925 | 0 | } else { |
926 | 0 | if (root().children_are_inline()) |
927 | 0 | layout_inline_children(root(), available_space); |
928 | 0 | else |
929 | 0 | layout_block_level_children(root(), available_space); |
930 | 0 | } |
931 | 0 | } |
932 | | |
933 | | void BlockFormattingContext::layout_floating_box(Box const& box, BlockContainer const&, AvailableSpace const& available_space, CSSPixels y, LineBuilder* line_builder) |
934 | 0 | { |
935 | 0 | VERIFY(box.is_floating()); |
936 | | |
937 | 0 | auto& box_state = m_state.get_mutable(box); |
938 | 0 | auto const& computed_values = box.computed_values(); |
939 | |
|
940 | 0 | resolve_vertical_box_model_metrics(box); |
941 | |
|
942 | 0 | compute_width(box, available_space); |
943 | |
|
944 | 0 | resolve_used_height_if_not_treated_as_auto(box, available_space); |
945 | | |
946 | | // NOTE: Flex containers with `auto` height are treated as `max-content`, so we can compute their height early. |
947 | 0 | if (box.is_replaced_box() || box.display().is_flex_inside()) { |
948 | 0 | resolve_used_height_if_treated_as_auto(box, available_space); |
949 | 0 | } |
950 | |
|
951 | 0 | auto independent_formatting_context = layout_inside(box, m_layout_mode, box_state.available_inner_space_or_constraints_from(available_space)); |
952 | 0 | resolve_used_height_if_treated_as_auto(box, available_space, independent_formatting_context); |
953 | | |
954 | | // First we place the box normally (to get the right y coordinate.) |
955 | | // If we have a LineBuilder, we're in the middle of inline layout, otherwise this is block layout. |
956 | 0 | if (line_builder) { |
957 | 0 | auto y = max(line_builder->y_for_float_to_be_inserted_here(box), line_builder->inline_formatting_context().vertical_float_clearance()); |
958 | 0 | box_state.set_content_y(y + box_state.margin_box_top()); |
959 | 0 | } else { |
960 | 0 | place_block_level_element_in_normal_flow_vertically(box, y + box_state.margin_top); |
961 | 0 | place_block_level_element_in_normal_flow_horizontally(box, available_space); |
962 | 0 | } |
963 | | |
964 | | // Then we float it to the left or right. |
965 | 0 | auto float_box = [&](FloatSide side, FloatSideData& side_data, FloatSideData& other_side_data) { |
966 | 0 | CSSPixels offset_from_edge = 0; |
967 | 0 | auto float_to_edge = [&] { |
968 | 0 | if (side == FloatSide::Left) |
969 | 0 | offset_from_edge = box_state.margin_box_left(); |
970 | 0 | else |
971 | 0 | offset_from_edge = box_state.content_width() + box_state.margin_box_right(); |
972 | 0 | }; |
973 | |
|
974 | 0 | auto box_in_root_rect = content_box_rect_in_ancestor_coordinate_space(box_state, root()); |
975 | 0 | CSSPixels y_in_root = box_in_root_rect.y(); |
976 | 0 | CSSPixels y = box_state.offset.y(); |
977 | |
|
978 | 0 | if (side_data.current_boxes.is_empty()) { |
979 | | // This is the first floating box on this side. Go all the way to the edge. |
980 | 0 | float_to_edge(); |
981 | 0 | side_data.y_offset = 0; |
982 | 0 | } else { |
983 | | |
984 | | // NOTE: If we're in inline layout, the LineBuilder has already provided the right Y offset. |
985 | | // In block layout, we adjust by the side's current Y offset here. |
986 | 0 | if (!line_builder) |
987 | 0 | y_in_root += side_data.y_offset; |
988 | |
|
989 | 0 | bool did_touch_preceding_float = false; |
990 | 0 | bool did_place_next_to_preceding_float = false; |
991 | | |
992 | | // Walk all currently tracked floats on the side we're floating towards. |
993 | | // We're looking for the innermost preceding float that intersects vertically with `box`. |
994 | 0 | for (auto& preceding_float : side_data.current_boxes.in_reverse()) { |
995 | 0 | auto const preceding_float_rect = margin_box_rect_in_ancestor_coordinate_space(preceding_float.used_values, root()); |
996 | 0 | if (!preceding_float_rect.contains_vertically(y_in_root)) |
997 | 0 | continue; |
998 | | // We found a preceding float that intersects vertically with the current float. |
999 | | // Now we need to find out if there's enough inline-axis space to stack them next to each other. |
1000 | 0 | CSSPixels tentative_offset_from_edge = 0; |
1001 | 0 | bool fits_next_to_preceding_float = false; |
1002 | 0 | if (side == FloatSide::Left) { |
1003 | 0 | tentative_offset_from_edge = max(preceding_float.offset_from_edge + preceding_float.used_values.content_width() + preceding_float.used_values.margin_box_right(), 0) + box_state.margin_box_left(); |
1004 | 0 | if (available_space.width.is_definite()) { |
1005 | 0 | fits_next_to_preceding_float = (tentative_offset_from_edge + box_state.content_width() + box_state.margin_box_right()) <= available_space.width.to_px_or_zero(); |
1006 | 0 | } else if (available_space.width.is_max_content() || available_space.width.is_indefinite()) { |
1007 | 0 | fits_next_to_preceding_float = true; |
1008 | 0 | } |
1009 | 0 | } else { |
1010 | 0 | tentative_offset_from_edge = preceding_float.offset_from_edge + preceding_float.used_values.margin_box_left() + box_state.margin_box_right() + box_state.content_width(); |
1011 | 0 | fits_next_to_preceding_float = tentative_offset_from_edge >= 0; |
1012 | 0 | } |
1013 | 0 | did_touch_preceding_float = true; |
1014 | 0 | if (!fits_next_to_preceding_float) |
1015 | 0 | break; |
1016 | 0 | offset_from_edge = tentative_offset_from_edge; |
1017 | 0 | did_place_next_to_preceding_float = true; |
1018 | 0 | break; |
1019 | 0 | } |
1020 | |
|
1021 | 0 | auto has_clearance = false; |
1022 | 0 | if (side == FloatSide::Left) { |
1023 | 0 | has_clearance = computed_values.clear() == CSS::Clear::Left || computed_values.clear() == CSS::Clear::Both; |
1024 | 0 | } else if (side == FloatSide::Right) { |
1025 | 0 | has_clearance = computed_values.clear() == CSS::Clear::Right || computed_values.clear() == CSS::Clear::Both; |
1026 | 0 | } |
1027 | |
|
1028 | 0 | if (!did_touch_preceding_float || !did_place_next_to_preceding_float || has_clearance) { |
1029 | | // One of three things happened: |
1030 | | // - This box does not touch another floating box. |
1031 | | // - We ran out of horizontal space on this "float line", and need to break. |
1032 | | // - This box has clearance. |
1033 | | // Either way, we float this box all the way to the edge. |
1034 | 0 | float_to_edge(); |
1035 | 0 | CSSPixels lowest_margin_edge = 0; |
1036 | 0 | for (auto const& current : side_data.current_boxes) { |
1037 | 0 | lowest_margin_edge = max(lowest_margin_edge, current.used_values.margin_box_height()); |
1038 | 0 | } |
1039 | |
|
1040 | 0 | side_data.y_offset += lowest_margin_edge; |
1041 | | |
1042 | | // Also, forget all previous boxes floated to this side while since they're no longer relevant. |
1043 | 0 | side_data.clear(); |
1044 | 0 | } |
1045 | 0 | } |
1046 | | |
1047 | | // NOTE: If we're in inline layout, the LineBuilder has already provided the right Y offset. |
1048 | | // In block layout, we adjust by the side's current Y offset here. |
1049 | | // FIXME: It's annoying that we have different behavior for inline vs block here. |
1050 | | // Find a way to unify the behavior so we don't need to branch here. |
1051 | |
|
1052 | 0 | if (!line_builder) |
1053 | 0 | y += side_data.y_offset; |
1054 | |
|
1055 | 0 | auto top_margin_edge = y - box_state.margin_box_top(); |
1056 | 0 | side_data.all_boxes.append(adopt_own(*new FloatingBox { |
1057 | 0 | .box = box, |
1058 | 0 | .used_values = box_state, |
1059 | 0 | .offset_from_edge = offset_from_edge, |
1060 | 0 | .top_margin_edge = top_margin_edge, |
1061 | 0 | .bottom_margin_edge = y + box_state.content_height() + box_state.margin_box_bottom(), |
1062 | 0 | })); |
1063 | 0 | side_data.current_boxes.append(*side_data.all_boxes.last()); |
1064 | |
|
1065 | 0 | if (side == FloatSide::Left) { |
1066 | 0 | side_data.current_width = offset_from_edge + box_state.content_width() + box_state.margin_box_right(); |
1067 | 0 | } else { |
1068 | 0 | side_data.current_width = offset_from_edge + box_state.margin_box_left(); |
1069 | 0 | } |
1070 | 0 | side_data.max_width = max(side_data.current_width, side_data.max_width); |
1071 | | |
1072 | | // NOTE: We don't set the X position here, that happens later, once we know the root block width. |
1073 | | // See parent_context_did_dimension_child_root_box() for that logic. |
1074 | 0 | box_state.set_content_y(y); |
1075 | | |
1076 | | // If the new box was inserted below the bottom of the opposite side, |
1077 | | // we reset the other side back to its edge. |
1078 | 0 | if (top_margin_edge > other_side_data.y_offset) |
1079 | 0 | other_side_data.clear(); |
1080 | 0 | }; |
1081 | | |
1082 | | // Next, float to the left and/or right |
1083 | 0 | if (box.computed_values().float_() == CSS::Float::Left) { |
1084 | 0 | float_box(FloatSide::Left, m_left_floats, m_right_floats); |
1085 | 0 | } else if (box.computed_values().float_() == CSS::Float::Right) { |
1086 | 0 | float_box(FloatSide::Right, m_right_floats, m_left_floats); |
1087 | 0 | } |
1088 | |
|
1089 | 0 | m_state.get_mutable(root()).add_floating_descendant(box); |
1090 | |
|
1091 | 0 | if (line_builder) |
1092 | 0 | line_builder->recalculate_available_space(); |
1093 | |
|
1094 | 0 | compute_inset(box); |
1095 | |
|
1096 | 0 | if (independent_formatting_context) |
1097 | 0 | independent_formatting_context->parent_context_did_dimension_child_root_box(); |
1098 | 0 | } |
1099 | | |
1100 | | void BlockFormattingContext::ensure_sizes_correct_for_left_offset_calculation(ListItemBox const& list_item_box) |
1101 | 0 | { |
1102 | 0 | if (!list_item_box.marker()) |
1103 | 0 | return; |
1104 | | |
1105 | 0 | auto& marker = *list_item_box.marker(); |
1106 | 0 | auto& marker_state = m_state.get_mutable(marker); |
1107 | |
|
1108 | 0 | CSSPixels image_width = 0; |
1109 | 0 | CSSPixels image_height = 0; |
1110 | 0 | if (auto const* list_style_image = marker.list_style_image()) { |
1111 | 0 | image_width = list_style_image->natural_width().value_or(0); |
1112 | 0 | image_height = list_style_image->natural_height().value_or(0); |
1113 | 0 | } |
1114 | |
|
1115 | 0 | auto default_marker_width = max(4, marker.first_available_font().pixel_size_rounded_up() - 4); |
1116 | |
|
1117 | 0 | auto marker_text = marker.text().value_or(""); |
1118 | 0 | if (marker_text.is_empty()) { |
1119 | 0 | marker_state.set_content_width(image_width + default_marker_width); |
1120 | 0 | } else { |
1121 | 0 | auto text_width = marker.first_available_font().width(marker_text); |
1122 | 0 | marker_state.set_content_width(image_width + CSSPixels::nearest_value_for(text_width)); |
1123 | 0 | } |
1124 | |
|
1125 | 0 | marker_state.set_content_height(max(image_height, marker.first_available_font().pixel_size_rounded_up() + 1)); |
1126 | 0 | } |
1127 | | |
1128 | | void BlockFormattingContext::layout_list_item_marker(ListItemBox const& list_item_box, CSSPixels const& left_space_before_list_item_elements_formatted) |
1129 | 0 | { |
1130 | 0 | if (!list_item_box.marker()) |
1131 | 0 | return; |
1132 | | |
1133 | 0 | auto& marker = *list_item_box.marker(); |
1134 | 0 | auto& marker_state = m_state.get_mutable(marker); |
1135 | 0 | auto& list_item_state = m_state.get_mutable(list_item_box); |
1136 | |
|
1137 | 0 | auto default_marker_width = max(4, marker.first_available_font().pixel_size_rounded_up() - 4); |
1138 | 0 | auto final_marker_width = marker_state.content_width() + default_marker_width; |
1139 | |
|
1140 | 0 | if (marker.list_style_position() == CSS::ListStylePosition::Inside) { |
1141 | 0 | list_item_state.set_content_offset({ final_marker_width, list_item_state.offset.y() }); |
1142 | 0 | list_item_state.set_content_width(list_item_state.content_width() - final_marker_width); |
1143 | 0 | } |
1144 | |
|
1145 | 0 | auto offset_y = max(CSSPixels(0), (marker.computed_values().line_height() - marker_state.content_height()) / 2); |
1146 | |
|
1147 | 0 | marker_state.set_content_offset({ left_space_before_list_item_elements_formatted - final_marker_width, offset_y }); |
1148 | |
|
1149 | 0 | if (marker_state.content_height() > list_item_state.content_height()) |
1150 | 0 | list_item_state.set_content_height(marker_state.content_height()); |
1151 | 0 | } |
1152 | | |
1153 | | BlockFormattingContext::SpaceUsedAndContainingMarginForFloats BlockFormattingContext::space_used_and_containing_margin_for_floats(CSSPixels y) const |
1154 | 0 | { |
1155 | 0 | SpaceUsedAndContainingMarginForFloats space_and_containing_margin; |
1156 | |
|
1157 | 0 | for (auto const& floating_box_ptr : m_left_floats.all_boxes.in_reverse()) { |
1158 | 0 | auto const& floating_box = *floating_box_ptr; |
1159 | | // NOTE: The floating box is *not* in the final horizontal position yet, but the size and vertical position is valid. |
1160 | 0 | auto rect = margin_box_rect_in_ancestor_coordinate_space(floating_box.used_values, root()); |
1161 | 0 | if (rect.contains_vertically(y)) { |
1162 | 0 | CSSPixels offset_from_containing_block_chain_margins_between_here_and_root = 0; |
1163 | 0 | for (auto const* containing_block = floating_box.used_values.containing_block_used_values(); containing_block && &containing_block->node() != &root(); containing_block = containing_block->containing_block_used_values()) { |
1164 | 0 | offset_from_containing_block_chain_margins_between_here_and_root += containing_block->margin_box_left(); |
1165 | 0 | } |
1166 | 0 | space_and_containing_margin.left_used_space = floating_box.offset_from_edge |
1167 | 0 | + floating_box.used_values.content_width() |
1168 | 0 | + floating_box.used_values.margin_box_right(); |
1169 | 0 | space_and_containing_margin.left_total_containing_margin = offset_from_containing_block_chain_margins_between_here_and_root; |
1170 | 0 | space_and_containing_margin.matching_left_float_box = floating_box.box; |
1171 | 0 | break; |
1172 | 0 | } |
1173 | 0 | } |
1174 | |
|
1175 | 0 | for (auto const& floating_box_ptr : m_right_floats.all_boxes.in_reverse()) { |
1176 | 0 | auto const& floating_box = *floating_box_ptr; |
1177 | | // NOTE: The floating box is *not* in the final horizontal position yet, but the size and vertical position is valid. |
1178 | 0 | auto rect = margin_box_rect_in_ancestor_coordinate_space(floating_box.used_values, root()); |
1179 | 0 | if (rect.contains_vertically(y)) { |
1180 | 0 | CSSPixels offset_from_containing_block_chain_margins_between_here_and_root = 0; |
1181 | 0 | for (auto const* containing_block = floating_box.used_values.containing_block_used_values(); containing_block && &containing_block->node() != &root(); containing_block = containing_block->containing_block_used_values()) { |
1182 | 0 | offset_from_containing_block_chain_margins_between_here_and_root += containing_block->margin_box_right(); |
1183 | 0 | } |
1184 | 0 | space_and_containing_margin.right_used_space = floating_box.offset_from_edge |
1185 | 0 | + floating_box.used_values.margin_box_left(); |
1186 | 0 | space_and_containing_margin.right_total_containing_margin = offset_from_containing_block_chain_margins_between_here_and_root; |
1187 | 0 | break; |
1188 | 0 | } |
1189 | 0 | } |
1190 | |
|
1191 | 0 | return space_and_containing_margin; |
1192 | 0 | } |
1193 | | |
1194 | | FormattingContext::SpaceUsedByFloats BlockFormattingContext::intrusion_by_floats_into_box(Box const& box, CSSPixels y_in_box) const |
1195 | 0 | { |
1196 | 0 | return intrusion_by_floats_into_box(m_state.get(box), y_in_box); |
1197 | 0 | } |
1198 | | |
1199 | | FormattingContext::SpaceUsedByFloats BlockFormattingContext::intrusion_by_floats_into_box(LayoutState::UsedValues const& box_used_values, CSSPixels y_in_box) const |
1200 | 0 | { |
1201 | | // NOTE: Floats are relative to the BFC root box, not necessarily the containing block of this IFC. |
1202 | 0 | auto box_in_root_rect = content_box_rect_in_ancestor_coordinate_space(box_used_values, root()); |
1203 | 0 | CSSPixels y_in_root = box_in_root_rect.y() + y_in_box; |
1204 | 0 | auto space_and_containing_margin = space_used_and_containing_margin_for_floats(y_in_root); |
1205 | 0 | auto left_side_floats_limit_to_right = space_and_containing_margin.left_total_containing_margin + space_and_containing_margin.left_used_space; |
1206 | 0 | auto right_side_floats_limit_to_right = space_and_containing_margin.right_used_space + space_and_containing_margin.right_total_containing_margin; |
1207 | |
|
1208 | 0 | auto left_intrusion = max(CSSPixels(0), left_side_floats_limit_to_right - max(CSSPixels(0), box_in_root_rect.x())); |
1209 | |
|
1210 | 0 | CSSPixels offset_from_containing_block_chain_margins_between_here_and_root = 0; |
1211 | 0 | for (auto const* containing_block = &box_used_values; containing_block && &containing_block->node() != &root(); containing_block = containing_block->containing_block_used_values()) { |
1212 | 0 | offset_from_containing_block_chain_margins_between_here_and_root = max(offset_from_containing_block_chain_margins_between_here_and_root, containing_block->margin_box_right()); |
1213 | 0 | } |
1214 | 0 | auto right_intrusion = max(CSSPixels(0), right_side_floats_limit_to_right - offset_from_containing_block_chain_margins_between_here_and_root); |
1215 | |
|
1216 | 0 | return { left_intrusion, right_intrusion }; |
1217 | 0 | } |
1218 | | |
1219 | | CSSPixels BlockFormattingContext::greatest_child_width(Box const& box) const |
1220 | 0 | { |
1221 | | // Similar to FormattingContext::greatest_child_width() |
1222 | | // but this one takes floats into account! |
1223 | 0 | CSSPixels max_width = m_left_floats.max_width + m_right_floats.max_width; |
1224 | 0 | if (box.children_are_inline()) { |
1225 | 0 | for (auto const& line_box : m_state.get(verify_cast<BlockContainer>(box)).line_boxes) { |
1226 | 0 | CSSPixels width_here = line_box.width(); |
1227 | 0 | CSSPixels extra_width_from_left_floats = 0; |
1228 | 0 | for (auto& left_float : m_left_floats.all_boxes) { |
1229 | | // NOTE: Floats directly affect the automatic size of their containing block, but only indirectly anything above in the tree. |
1230 | 0 | if (left_float->box->containing_block() != &box) |
1231 | 0 | continue; |
1232 | 0 | if (line_box.baseline() >= left_float->top_margin_edge || line_box.baseline() <= left_float->bottom_margin_edge) { |
1233 | 0 | extra_width_from_left_floats = max(extra_width_from_left_floats, left_float->offset_from_edge + left_float->used_values.content_width() + left_float->used_values.margin_box_right()); |
1234 | 0 | } |
1235 | 0 | } |
1236 | 0 | CSSPixels extra_width_from_right_floats = 0; |
1237 | 0 | for (auto& right_float : m_right_floats.all_boxes) { |
1238 | | // NOTE: Floats directly affect the automatic size of their containing block, but only indirectly anything above in the tree. |
1239 | 0 | if (right_float->box->containing_block() != &box) |
1240 | 0 | continue; |
1241 | 0 | if (line_box.baseline() >= right_float->top_margin_edge || line_box.baseline() <= right_float->bottom_margin_edge) { |
1242 | 0 | extra_width_from_right_floats = max(extra_width_from_right_floats, right_float->offset_from_edge + right_float->used_values.margin_box_left()); |
1243 | 0 | } |
1244 | 0 | } |
1245 | 0 | width_here += extra_width_from_left_floats + extra_width_from_right_floats; |
1246 | 0 | max_width = max(max_width, width_here); |
1247 | 0 | } |
1248 | 0 | } else { |
1249 | 0 | box.for_each_child_of_type<Box>([&](Box const& child) { |
1250 | 0 | if (!child.is_absolutely_positioned()) |
1251 | 0 | max_width = max(max_width, m_state.get(child).margin_box_width()); |
1252 | 0 | return IterationDecision::Continue; |
1253 | 0 | }); |
1254 | 0 | } |
1255 | 0 | return max_width; |
1256 | 0 | } |
1257 | | |
1258 | | } |