Serenity Operating System
at master 1395 lines 68 kB view raw
1/* 2 * Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibWeb/Dump.h> 8#include <LibWeb/Layout/BlockFormattingContext.h> 9#include <LibWeb/Layout/Box.h> 10#include <LibWeb/Layout/FlexFormattingContext.h> 11#include <LibWeb/Layout/FormattingContext.h> 12#include <LibWeb/Layout/GridFormattingContext.h> 13#include <LibWeb/Layout/ReplacedBox.h> 14#include <LibWeb/Layout/SVGFormattingContext.h> 15#include <LibWeb/Layout/SVGSVGBox.h> 16#include <LibWeb/Layout/TableBox.h> 17#include <LibWeb/Layout/TableCellBox.h> 18#include <LibWeb/Layout/TableFormattingContext.h> 19#include <LibWeb/Layout/Viewport.h> 20 21namespace Web::Layout { 22 23FormattingContext::FormattingContext(Type type, LayoutState& state, Box const& context_box, FormattingContext* parent) 24 : m_type(type) 25 , m_parent(parent) 26 , m_context_box(context_box) 27 , m_state(state) 28{ 29} 30 31FormattingContext::~FormattingContext() = default; 32 33// https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context 34bool FormattingContext::creates_block_formatting_context(Box const& box) 35{ 36 // NOTE: Replaced elements never create a BFC. 37 if (box.is_replaced_box()) 38 return false; 39 40 // display: table 41 if (box.display().is_table_inside()) { 42 return false; 43 } 44 45 // display: flex 46 if (box.display().is_flex_inside()) { 47 return false; 48 } 49 50 // display: grid 51 if (box.display().is_grid_inside()) { 52 return false; 53 } 54 55 // NOTE: This function uses MDN as a reference, not because it's authoritative, 56 // but because they've gathered all the conditions in one convenient location. 57 58 // The root element of the document (<html>). 59 if (box.is_root_element()) 60 return true; 61 62 // Floats (elements where float isn't none). 63 if (box.is_floating()) 64 return true; 65 66 // Absolutely positioned elements (elements where position is absolute or fixed). 67 if (box.is_absolutely_positioned()) 68 return true; 69 70 // Inline-blocks (elements with display: inline-block). 71 if (box.display().is_inline_block()) 72 return true; 73 74 // Table cells (elements with display: table-cell, which is the default for HTML table cells). 75 if (box.display().is_table_cell()) 76 return true; 77 78 // Table captions (elements with display: table-caption, which is the default for HTML table captions). 79 if (box.display().is_table_caption()) 80 return true; 81 82 // FIXME: Anonymous table cells implicitly created by the elements with display: table, table-row, table-row-group, table-header-group, table-footer-group 83 // (which is the default for HTML tables, table rows, table bodies, table headers, and table footers, respectively), or inline-table. 84 85 // Block elements where overflow has a value other than visible and clip. 86 CSS::Overflow overflow_x = box.computed_values().overflow_x(); 87 if ((overflow_x != CSS::Overflow::Visible) && (overflow_x != CSS::Overflow::Clip)) 88 return true; 89 CSS::Overflow overflow_y = box.computed_values().overflow_y(); 90 if ((overflow_y != CSS::Overflow::Visible) && (overflow_y != CSS::Overflow::Clip)) 91 return true; 92 93 // display: flow-root. 94 if (box.display().is_flow_root_inside()) 95 return true; 96 97 // FIXME: Elements with contain: layout, content, or paint. 98 99 if (box.parent()) { 100 auto parent_display = box.parent()->display(); 101 102 // Flex items (direct children of the element with display: flex or inline-flex) if they are neither flex nor grid nor table containers themselves. 103 if (parent_display.is_flex_inside()) 104 return true; 105 // Grid items (direct children of the element with display: grid or inline-grid) if they are neither flex nor grid nor table containers themselves. 106 if (parent_display.is_grid_inside()) 107 return true; 108 } 109 110 // FIXME: Multicol containers (elements where column-count or column-width isn't auto, including elements with column-count: 1). 111 112 // FIXME: column-span: all should always create a new formatting context, even when the column-span: all element isn't contained by a multicol container (Spec change, Chrome bug). 113 114 return false; 115} 116 117OwnPtr<FormattingContext> FormattingContext::create_independent_formatting_context_if_needed(LayoutState& state, Box const& child_box) 118{ 119 if (child_box.is_replaced_box() && !child_box.can_have_children()) { 120 // NOTE: This is a bit strange. 121 // Basically, we create a pretend formatting context for replaced elements that does nothing. 122 // This allows other formatting contexts to treat them like elements that actually need inside layout 123 // without having separate code to handle replaced elements. 124 // FIXME: Find a better abstraction for this. 125 struct ReplacedFormattingContext : public FormattingContext { 126 ReplacedFormattingContext(LayoutState& state, Box const& box) 127 : FormattingContext(Type::Block, state, box) 128 { 129 } 130 virtual CSSPixels automatic_content_height() const override { return 0; }; 131 virtual void run(Box const&, LayoutMode, AvailableSpace const&) override { } 132 }; 133 return make<ReplacedFormattingContext>(state, child_box); 134 } 135 136 if (!child_box.can_have_children()) 137 return {}; 138 139 auto child_display = child_box.display(); 140 141 if (is<SVGSVGBox>(child_box)) 142 return make<SVGFormattingContext>(state, child_box, this); 143 144 if (child_display.is_flex_inside()) 145 return make<FlexFormattingContext>(state, child_box, this); 146 147 if (creates_block_formatting_context(child_box)) 148 return make<BlockFormattingContext>(state, verify_cast<BlockContainer>(child_box), this); 149 150 if (child_display.is_table_inside()) 151 return make<TableFormattingContext>(state, verify_cast<TableBox>(child_box), this); 152 153 if (child_display.is_grid_inside()) { 154 return make<GridFormattingContext>(state, child_box, this); 155 } 156 157 VERIFY(is_block_formatting_context()); 158 if (child_box.children_are_inline()) 159 return {}; 160 161 // The child box is a block container that doesn't create its own BFC. 162 // It will be formatted by this BFC. 163 if (!child_display.is_flow_inside()) { 164 dbgln("FIXME: Child box doesn't create BFC, but inside is also not flow! display={}", MUST(child_display.to_string())); 165 // HACK: Instead of crashing, create a dummy formatting context that does nothing. 166 // FIXME: Remove this once it's no longer needed. It currently swallows problem with standalone 167 // table-related boxes that don't get fixed up by CSS anonymous table box generation. 168 struct DummyFormattingContext : public FormattingContext { 169 DummyFormattingContext(LayoutState& state, Box const& box) 170 : FormattingContext(Type::Block, state, box) 171 { 172 } 173 virtual CSSPixels automatic_content_height() const override { return 0; }; 174 virtual void run(Box const&, LayoutMode, AvailableSpace const&) override { } 175 }; 176 return make<DummyFormattingContext>(state, child_box); 177 } 178 VERIFY(child_box.is_block_container()); 179 VERIFY(child_display.is_flow_inside()); 180 return {}; 181} 182 183OwnPtr<FormattingContext> FormattingContext::layout_inside(Box const& child_box, LayoutMode layout_mode, AvailableSpace const& available_space) 184{ 185 { 186 // OPTIMIZATION: If we're doing intrinsic sizing and `child_box` has definite size in both axes, 187 // we don't need to layout its insides. The size is resolvable without learning 188 // the metrics of whatever's inside the box. 189 auto const& used_values = m_state.get(child_box); 190 if (layout_mode == LayoutMode::IntrinsicSizing 191 && used_values.width_constraint == SizeConstraint::None 192 && used_values.height_constraint == SizeConstraint::None 193 && used_values.has_definite_width() 194 && used_values.has_definite_height()) { 195 return nullptr; 196 } 197 } 198 199 if (!child_box.can_have_children()) 200 return {}; 201 202 auto independent_formatting_context = create_independent_formatting_context_if_needed(m_state, child_box); 203 if (independent_formatting_context) 204 independent_formatting_context->run(child_box, layout_mode, available_space); 205 else 206 run(child_box, layout_mode, available_space); 207 208 return independent_formatting_context; 209} 210 211CSSPixels FormattingContext::greatest_child_width(Box const& box) 212{ 213 CSSPixels max_width = 0; 214 if (box.children_are_inline()) { 215 for (auto& line_box : m_state.get(box).line_boxes) { 216 max_width = max(max_width, line_box.width()); 217 } 218 } else { 219 box.for_each_child_of_type<Box>([&](Box const& child) { 220 if (!child.is_absolutely_positioned()) 221 max_width = max(max_width, m_state.get(child).margin_box_width()); 222 }); 223 } 224 return max_width; 225} 226 227FormattingContext::ShrinkToFitResult FormattingContext::calculate_shrink_to_fit_widths(Box const& box) 228{ 229 return { 230 .preferred_width = calculate_max_content_width(box), 231 .preferred_minimum_width = calculate_min_content_width(box), 232 }; 233} 234 235static CSSPixelSize solve_replaced_size_constraint(LayoutState const& state, CSSPixels w, CSSPixels h, ReplacedBox const& box) 236{ 237 // 10.4 Minimum and maximum widths: 'min-width' and 'max-width' 238 239 auto const& containing_block = *box.containing_block(); 240 auto const& containing_block_state = state.get(containing_block); 241 auto width_of_containing_block = CSS::Length::make_px(containing_block_state.content_width()); 242 auto height_of_containing_block = CSS::Length::make_px(containing_block_state.content_height()); 243 244 CSSPixels specified_min_width = box.computed_values().min_width().is_auto() ? 0 : box.computed_values().min_width().resolved(box, width_of_containing_block).to_px(box); 245 CSSPixels specified_max_width = box.computed_values().max_width().is_none() ? w : box.computed_values().max_width().resolved(box, width_of_containing_block).to_px(box); 246 CSSPixels specified_min_height = box.computed_values().min_height().is_auto() ? 0 : box.computed_values().min_height().resolved(box, height_of_containing_block).to_px(box); 247 CSSPixels specified_max_height = box.computed_values().max_height().is_none() ? h : box.computed_values().max_height().resolved(box, height_of_containing_block).to_px(box); 248 249 auto min_width = min(specified_min_width, specified_max_width); 250 auto max_width = max(specified_min_width, specified_max_width); 251 auto min_height = min(specified_min_height, specified_max_height); 252 auto max_height = max(specified_min_height, specified_max_height); 253 254 if (w > max_width) 255 return { w, max(max_width * (h / w), min_height) }; 256 if (w < min_width) 257 return { max_width, min(min_width * (h / w), max_height) }; 258 if (h > max_height) 259 return { max(max_height * (w / h), min_width), max_height }; 260 if (h < min_height) 261 return { min(min_height * (w / h), max_width), min_height }; 262 if ((w > max_width && h > max_height) && (max_width / w < max_height / h)) 263 return { max_width, max(min_height, max_width * (h / w)) }; 264 if ((w > max_width && h > max_height) && (max_width / w > max_height / h)) 265 return { max(min_width, max_height * (w / h)), max_height }; 266 if ((w < min_width && h < min_height) && (min_width / w < min_height / h)) 267 return { min(max_width, min_height * (w / h)), min_height }; 268 if ((w < min_width && h < min_height) && (min_width / w > min_height / h)) 269 return { min_width, min(max_height, min_width * (h / w)) }; 270 if (w < min_width && h > max_height) 271 return { min_width, max_height }; 272 if (w > max_width && h < min_height) 273 return { max_width, min_height }; 274 return { w, h }; 275} 276 277// https://www.w3.org/TR/CSS22/visudet.html#root-height 278CSSPixels FormattingContext::compute_auto_height_for_block_formatting_context_root(Box const& root) const 279{ 280 // 10.6.7 'Auto' heights for block formatting context roots 281 Optional<CSSPixels> top; 282 Optional<CSSPixels> bottom; 283 284 if (root.children_are_inline()) { 285 // If it only has inline-level children, the height is the distance between 286 // the top content edge and the bottom of the bottommost line box. 287 auto const& line_boxes = m_state.get(root).line_boxes; 288 top = 0; 289 if (!line_boxes.is_empty()) 290 bottom = line_boxes.last().bottom(); 291 } else { 292 // If it has block-level children, the height is the distance between 293 // the top margin-edge of the topmost block-level child box 294 // and the bottom margin-edge of the bottommost block-level child box. 295 root.for_each_child_of_type<Box>([&](Layout::Box& child_box) { 296 // Absolutely positioned children are ignored, 297 // and relatively positioned boxes are considered without their offset. 298 // Note that the child box may be an anonymous block box. 299 if (child_box.is_absolutely_positioned()) 300 return IterationDecision::Continue; 301 302 // FIXME: This doesn't look right. 303 if ((root.computed_values().overflow_y() == CSS::Overflow::Visible) && child_box.is_floating()) 304 return IterationDecision::Continue; 305 306 auto const& child_box_state = m_state.get(child_box); 307 308 CSSPixels child_box_top = child_box_state.offset.y() - child_box_state.margin_box_top(); 309 CSSPixels child_box_bottom = child_box_state.offset.y() + child_box_state.content_height() + child_box_state.margin_box_bottom(); 310 311 if (!top.has_value() || child_box_top < top.value()) 312 top = child_box_top; 313 314 if (!bottom.has_value() || child_box_bottom > bottom.value()) 315 bottom = child_box_bottom; 316 317 return IterationDecision::Continue; 318 }); 319 } 320 321 // In addition, if the element has any floating descendants 322 // whose bottom margin edge is below the element's bottom content edge, 323 // then the height is increased to include those edges. 324 for (auto* floating_box : m_state.get(root).floating_descendants()) { 325 // NOTE: Floating box coordinates are relative to their own containing block, 326 // which may or may not be the BFC root. 327 auto margin_box = margin_box_rect_in_ancestor_coordinate_space(*floating_box, root, m_state); 328 CSSPixels floating_box_bottom_margin_edge = margin_box.bottom() + 1; 329 if (!bottom.has_value() || floating_box_bottom_margin_edge > bottom.value()) 330 bottom = floating_box_bottom_margin_edge; 331 } 332 333 return max(CSSPixels(0.0f), bottom.value_or(0) - top.value_or(0)); 334} 335 336// 10.3.2 Inline, replaced elements, https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-width 337CSSPixels FormattingContext::tentative_width_for_replaced_element(LayoutState const& state, ReplacedBox const& box, CSS::Size const& computed_width, AvailableSpace const& available_space) 338{ 339 // Treat percentages of indefinite containing block widths as 0 (the initial width). 340 if (computed_width.is_percentage() && !state.get(*box.containing_block()).has_definite_width()) 341 return 0; 342 343 auto height_of_containing_block = CSS::Length::make_px(containing_block_height_for(box, state)); 344 auto computed_height = should_treat_height_as_auto(box, available_space) ? CSS::Size::make_auto() : box.computed_values().height(); 345 346 CSSPixels used_width = computed_width.resolved(box, CSS::Length::make_px(available_space.width.to_px())).to_px(box); 347 348 // If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width, 349 // then that intrinsic width is the used value of 'width'. 350 if (computed_height.is_auto() && computed_width.is_auto() && box.has_intrinsic_width()) 351 return box.intrinsic_width().value(); 352 353 // If 'height' and 'width' both have computed values of 'auto' and the element has no intrinsic width, 354 // but does have an intrinsic height and intrinsic ratio; 355 // or if 'width' has a computed value of 'auto', 356 // 'height' has some other computed value, and the element does have an intrinsic ratio; then the used value of 'width' is: 357 // 358 // (used height) * (intrinsic ratio) 359 if ((computed_height.is_auto() && computed_width.is_auto() && !box.has_intrinsic_width() && box.has_intrinsic_height() && box.has_intrinsic_aspect_ratio()) 360 || (computed_width.is_auto() && !computed_height.is_auto() && box.has_intrinsic_aspect_ratio())) { 361 return compute_height_for_replaced_element(state, box, available_space) * box.intrinsic_aspect_ratio().value(); 362 } 363 364 // If 'height' and 'width' both have computed values of 'auto' and the element has an intrinsic ratio but no intrinsic height or width, 365 // then the used value of 'width' is undefined in CSS 2.2. However, it is suggested that, if the containing block's width does not itself 366 // depend on the replaced element's width, then the used value of 'width' is calculated from the constraint equation used for block-level, 367 // non-replaced elements in normal flow. 368 369 // Otherwise, if 'width' has a computed value of 'auto', and the element has an intrinsic width, then that intrinsic width is the used value of 'width'. 370 if (computed_width.is_auto() && box.has_intrinsic_width()) 371 return box.intrinsic_width().value(); 372 373 // Otherwise, if 'width' has a computed value of 'auto', but none of the conditions above are met, then the used value of 'width' becomes 300px. 374 // If 300px is too wide to fit the device, UAs should use the width of the largest rectangle that has a 2:1 ratio and fits the device instead. 375 if (computed_width.is_auto()) 376 return 300; 377 378 return used_width; 379} 380 381void FormattingContext::compute_width_for_absolutely_positioned_element(Box const& box, AvailableSpace const& available_space) 382{ 383 if (is<ReplacedBox>(box)) 384 compute_width_for_absolutely_positioned_replaced_element(verify_cast<ReplacedBox>(box), available_space); 385 else 386 compute_width_for_absolutely_positioned_non_replaced_element(box, available_space); 387} 388 389void FormattingContext::compute_height_for_absolutely_positioned_element(Box const& box, AvailableSpace const& available_space, BeforeOrAfterInsideLayout before_or_after_inside_layout) 390{ 391 if (is<ReplacedBox>(box)) 392 compute_height_for_absolutely_positioned_replaced_element(static_cast<ReplacedBox const&>(box), available_space, before_or_after_inside_layout); 393 else 394 compute_height_for_absolutely_positioned_non_replaced_element(box, available_space, before_or_after_inside_layout); 395} 396 397CSSPixels FormattingContext::compute_width_for_replaced_element(LayoutState const& state, ReplacedBox const& box, AvailableSpace const& available_space) 398{ 399 // 10.3.4 Block-level, replaced elements in normal flow... 400 // 10.3.2 Inline, replaced elements 401 402 auto zero_value = CSS::Length::make_px(0); 403 auto width_of_containing_block_as_length = CSS::Length::make_px(available_space.width.to_px()); 404 405 auto margin_left = box.computed_values().margin().left().resolved(box, width_of_containing_block_as_length).resolved(box); 406 auto margin_right = box.computed_values().margin().right().resolved(box, width_of_containing_block_as_length).resolved(box); 407 408 // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'. 409 if (margin_left.is_auto()) 410 margin_left = zero_value; 411 if (margin_right.is_auto()) 412 margin_right = zero_value; 413 414 auto computed_width = should_treat_width_as_auto(box, available_space) ? CSS::Size::make_auto() : box.computed_values().width(); 415 416 // 1. The tentative used width is calculated (without 'min-width' and 'max-width') 417 auto used_width = tentative_width_for_replaced_element(state, box, computed_width, available_space); 418 419 // 2. The tentative used width is greater than 'max-width', the rules above are applied again, 420 // but this time using the computed value of 'max-width' as the computed value for 'width'. 421 auto computed_max_width = box.computed_values().max_width(); 422 if (!computed_max_width.is_none()) { 423 if (used_width > computed_max_width.resolved(box, width_of_containing_block_as_length).to_px(box)) { 424 used_width = tentative_width_for_replaced_element(state, box, computed_max_width, available_space); 425 } 426 } 427 428 // 3. If the resulting width is smaller than 'min-width', the rules above are applied again, 429 // but this time using the value of 'min-width' as the computed value for 'width'. 430 auto computed_min_width = box.computed_values().min_width(); 431 if (!computed_min_width.is_auto()) { 432 if (used_width < computed_min_width.resolved(box, width_of_containing_block_as_length).to_px(box)) { 433 used_width = tentative_width_for_replaced_element(state, box, computed_min_width, available_space); 434 } 435 } 436 437 return used_width; 438} 439 440// 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block' replaced elements in normal flow and floating replaced elements 441// https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-height 442CSSPixels FormattingContext::tentative_height_for_replaced_element(LayoutState const& state, ReplacedBox const& box, CSS::Size const& computed_height, AvailableSpace const& available_space) 443{ 444 // If 'height' and 'width' both have computed values of 'auto' and the element also has 445 // an intrinsic height, then that intrinsic height is the used value of 'height'. 446 if (should_treat_width_as_auto(box, available_space) && should_treat_height_as_auto(box, available_space) && box.has_intrinsic_height()) 447 return box.intrinsic_height().value(); 448 449 // Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic ratio then the used value of 'height' is: 450 // 451 // (used width) / (intrinsic ratio) 452 if (computed_height.is_auto() && box.has_intrinsic_aspect_ratio()) 453 return compute_width_for_replaced_element(state, box, available_space) / box.intrinsic_aspect_ratio().value(); 454 455 // Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic height, then that intrinsic height is the used value of 'height'. 456 if (computed_height.is_auto() && box.has_intrinsic_height()) 457 return box.intrinsic_height().value(); 458 459 // Otherwise, if 'height' has a computed value of 'auto', but none of the conditions above are met, 460 // then the used value of 'height' must be set to the height of the largest rectangle that has a 2:1 ratio, has a height not greater than 150px, 461 // and has a width not greater than the device width. 462 if (computed_height.is_auto()) 463 return 150; 464 465 return computed_height.resolved(box, CSS::Length::make_px(available_space.height.to_px())).to_px(box); 466} 467 468CSSPixels FormattingContext::compute_height_for_replaced_element(LayoutState const& state, ReplacedBox const& box, AvailableSpace const& available_space) 469{ 470 // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 471 // 'inline-block' replaced elements in normal flow and floating replaced elements 472 473 auto width_of_containing_block_as_length = CSS::Length::make_px(available_space.width.to_px()); 474 auto height_of_containing_block_as_length = CSS::Length::make_px(available_space.height.to_px()); 475 auto computed_width = should_treat_width_as_auto(box, available_space) ? CSS::Size::make_auto() : box.computed_values().width(); 476 auto computed_height = should_treat_height_as_auto(box, available_space) ? CSS::Size::make_auto() : box.computed_values().height(); 477 478 CSSPixels used_height = tentative_height_for_replaced_element(state, box, computed_height, available_space); 479 480 if (computed_width.is_auto() && computed_height.is_auto() && box.has_intrinsic_aspect_ratio()) { 481 CSSPixels w = tentative_width_for_replaced_element(state, box, computed_width, available_space); 482 CSSPixels h = used_height; 483 used_height = solve_replaced_size_constraint(state, w, h, box).height(); 484 } 485 486 return used_height; 487} 488 489void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_element(Box const& box, AvailableSpace const& available_space) 490{ 491 auto width_of_containing_block = available_space.width.to_px(); 492 auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); 493 auto& computed_values = box.computed_values(); 494 auto zero_value = CSS::Length::make_px(0); 495 496 auto margin_left = CSS::Length::make_auto(); 497 auto margin_right = CSS::Length::make_auto(); 498 auto const border_left = computed_values.border_left().width; 499 auto const border_right = computed_values.border_right().width; 500 auto const padding_left = computed_values.padding().left().resolved(box, width_of_containing_block_as_length).to_px(box); 501 auto const padding_right = computed_values.padding().right().resolved(box, width_of_containing_block_as_length).to_px(box); 502 503 auto try_compute_width = [&](auto const& a_width) { 504 margin_left = computed_values.margin().left().resolved(box, width_of_containing_block_as_length).resolved(box); 505 margin_right = computed_values.margin().right().resolved(box, width_of_containing_block_as_length).resolved(box); 506 507 auto left = computed_values.inset().left().resolved(box, width_of_containing_block_as_length).resolved(box); 508 auto right = computed_values.inset().right().resolved(box, width_of_containing_block_as_length).resolved(box); 509 auto width = a_width; 510 511 auto solve_for_left = [&] { 512 return CSS::Length::make_px(width_of_containing_block - margin_left.to_px(box) - border_left - padding_left - width.to_px(box) - padding_right - border_right - margin_right.to_px(box) - right.to_px(box)); 513 }; 514 515 auto solve_for_width = [&] { 516 return CSS::Length::make_px(width_of_containing_block - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left - padding_right - border_right - margin_right.to_px(box) - right.to_px(box)); 517 }; 518 519 auto solve_for_right = [&] { 520 return CSS::Length::make_px(width_of_containing_block - left.to_px(box) - margin_left.to_px(box) - border_left - padding_left - width.to_px(box) - padding_right - border_right - margin_right.to_px(box)); 521 }; 522 523 // If all three of 'left', 'width', and 'right' are 'auto': 524 if (left.is_auto() && width.is_auto() && right.is_auto()) { 525 // First set any 'auto' values for 'margin-left' and 'margin-right' to 0. 526 if (margin_left.is_auto()) 527 margin_left = CSS::Length::make_px(0); 528 if (margin_right.is_auto()) 529 margin_right = CSS::Length::make_px(0); 530 // Then, if the 'direction' property of the element establishing the static-position containing block 531 // is 'ltr' set 'left' to the static position and apply rule number three below; 532 // otherwise, set 'right' to the static position and apply rule number one below. 533 // FIXME: This is very hackish. 534 left = CSS::Length::make_px(0); 535 goto Rule3; 536 } 537 538 if (!left.is_auto() && !width.is_auto() && !right.is_auto()) { 539 // FIXME: This should be solved in a more complicated way. 540 return width; 541 } 542 543 if (margin_left.is_auto()) 544 margin_left = CSS::Length::make_px(0); 545 if (margin_right.is_auto()) 546 margin_right = CSS::Length::make_px(0); 547 548 // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', 549 // then the width is shrink-to-fit. Then solve for 'left' 550 if (left.is_auto() && width.is_auto() && !right.is_auto()) { 551 auto result = calculate_shrink_to_fit_widths(box); 552 auto available_width = solve_for_width(); 553 width = CSS::Length::make_px(min(max(result.preferred_minimum_width, available_width.to_px(box)), result.preferred_width)); 554 left = solve_for_left(); 555 } 556 557 // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', 558 // then if the 'direction' property of the element establishing 559 // the static-position containing block is 'ltr' set 'left' 560 // to the static position, otherwise set 'right' to the static position. 561 // Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr'). 562 else if (left.is_auto() && right.is_auto() && !width.is_auto()) { 563 // FIXME: Check direction 564 // FIXME: Use the static-position containing block 565 left = zero_value; 566 right = solve_for_right(); 567 } 568 569 // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', 570 // then the width is shrink-to-fit. Then solve for 'right' 571 else if (width.is_auto() && right.is_auto() && !left.is_auto()) { 572 Rule3: 573 auto result = calculate_shrink_to_fit_widths(box); 574 auto available_width = solve_for_width(); 575 width = CSS::Length::make_px(min(max(result.preferred_minimum_width, available_width.to_px(box)), result.preferred_width)); 576 right = solve_for_right(); 577 } 578 579 // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left' 580 else if (left.is_auto() && !width.is_auto() && !right.is_auto()) { 581 left = solve_for_left(); 582 } 583 584 // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width' 585 else if (width.is_auto() && !left.is_auto() && !right.is_auto()) { 586 width = solve_for_width(); 587 } 588 589 // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right' 590 else if (right.is_auto() && !left.is_auto() && !width.is_auto()) { 591 right = solve_for_right(); 592 } 593 594 return width; 595 }; 596 597 // 1. The tentative used width is calculated (without 'min-width' and 'max-width') 598 auto used_width = try_compute_width(calculate_inner_width(box, available_space.width, computed_values.width())); 599 600 // 2. The tentative used width is greater than 'max-width', the rules above are applied again, 601 // but this time using the computed value of 'max-width' as the computed value for 'width'. 602 if (!computed_values.max_width().is_none()) { 603 auto max_width = calculate_inner_width(box, available_space.width, computed_values.max_width()); 604 if (used_width.to_px(box) > max_width.to_px(box)) { 605 used_width = try_compute_width(max_width); 606 } 607 } 608 609 // 3. If the resulting width is smaller than 'min-width', the rules above are applied again, 610 // but this time using the value of 'min-width' as the computed value for 'width'. 611 if (!computed_values.min_width().is_auto()) { 612 auto min_width = calculate_inner_width(box, available_space.width, computed_values.min_width()); 613 if (used_width.to_px(box) < min_width.to_px(box)) { 614 used_width = try_compute_width(min_width); 615 } 616 } 617 618 auto& box_state = m_state.get_mutable(box); 619 box_state.set_content_width(used_width.to_px(box)); 620 621 box_state.margin_left = margin_left.to_px(box); 622 box_state.margin_right = margin_right.to_px(box); 623 box_state.border_left = border_left; 624 box_state.border_right = border_right; 625 box_state.padding_left = padding_left; 626 box_state.padding_right = padding_right; 627} 628 629void FormattingContext::compute_width_for_absolutely_positioned_replaced_element(ReplacedBox const& box, AvailableSpace const& available_space) 630{ 631 // 10.3.8 Absolutely positioned, replaced elements 632 // The used value of 'width' is determined as for inline replaced elements. 633 // FIXME: This const_cast is gross. 634 const_cast<ReplacedBox&>(box).prepare_for_replaced_layout(); 635 m_state.get_mutable(box).set_content_width(compute_width_for_replaced_element(m_state, box, available_space)); 636} 637 638// https://drafts.csswg.org/css-position-3/#abs-non-replaced-height 639void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_element(Box const& box, AvailableSpace const& available_space, BeforeOrAfterInsideLayout before_or_after_inside_layout) 640{ 641 // 5.3. The Height Of Absolutely Positioned, Non-Replaced Elements 642 643 // For absolutely positioned elements, the used values of the vertical dimensions must satisfy this constraint: 644 // top + margin-top + border-top-width + padding-top + height + padding-bottom + border-bottom-width + margin-bottom + bottom = height of containing block 645 646 // NOTE: This function is called twice: both before and after inside layout. 647 // In the before pass, if it turns out we need the automatic height of the box, we abort these steps. 648 // This allows the box to retain an indefinite height from the perspective of inside layout. 649 650 auto margin_top = box.computed_values().margin().top(); 651 auto margin_bottom = box.computed_values().margin().bottom(); 652 auto top = box.computed_values().inset().top(); 653 auto bottom = box.computed_values().inset().bottom(); 654 auto height = box.computed_values().height(); 655 656 auto width_of_containing_block = containing_block_width_for(box); 657 auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); 658 auto height_of_containing_block = available_space.height.to_px(); 659 auto height_of_containing_block_as_length = CSS::Length::make_px(height_of_containing_block); 660 661 auto solve_for = [&](CSS::Length length) { 662 return CSS::Length::make_px( 663 height_of_containing_block 664 - top.resolved(box, height_of_containing_block_as_length).to_px(box) 665 - margin_top.resolved(box, width_of_containing_block_as_length).to_px(box) 666 - box.computed_values().border_top().width 667 - box.computed_values().padding().top().resolved(box, width_of_containing_block_as_length).to_px(box) 668 - height.resolved(box, height_of_containing_block_as_length).to_px(box) 669 - box.computed_values().padding().bottom().resolved(box, width_of_containing_block_as_length).to_px(box) 670 - box.computed_values().border_bottom().width 671 - margin_bottom.resolved(box, width_of_containing_block_as_length).to_px(box) 672 - bottom.resolved(box, height_of_containing_block_as_length).to_px(box) 673 + length.to_px(box)); 674 }; 675 676 auto solve_for_top = [&] { 677 top = solve_for(top.resolved(box, height_of_containing_block_as_length)); 678 }; 679 680 auto solve_for_bottom = [&] { 681 bottom = solve_for(bottom.resolved(box, height_of_containing_block_as_length)); 682 }; 683 684 auto solve_for_height = [&] { 685 height = CSS::Size::make_length(solve_for(height.resolved(box, height_of_containing_block_as_length))); 686 }; 687 688 auto solve_for_margin_top = [&] { 689 margin_top = solve_for(margin_top.resolved(box, width_of_containing_block_as_length)); 690 }; 691 692 auto solve_for_margin_bottom = [&] { 693 margin_bottom = solve_for(margin_bottom.resolved(box, width_of_containing_block_as_length)); 694 }; 695 696 auto solve_for_margin_top_and_margin_bottom = [&] { 697 auto remainder = solve_for(CSS::Length::make_px(margin_top.resolved(box, width_of_containing_block_as_length).to_px(box) + margin_bottom.resolved(box, width_of_containing_block_as_length).to_px(box))).to_px(box); 698 margin_top = CSS::Length::make_px(remainder / 2); 699 margin_bottom = CSS::Length::make_px(remainder / 2); 700 }; 701 702 // If all three of top, height, and bottom are auto: 703 if (top.is_auto() && height.is_auto() && bottom.is_auto()) { 704 // (If we haven't done inside layout yet, we can't compute the auto height.) 705 if (before_or_after_inside_layout == BeforeOrAfterInsideLayout::Before) 706 return; 707 708 // First set any auto values for margin-top and margin-bottom to 0, 709 if (margin_top.is_auto()) 710 margin_top = CSS::Length::make_px(0); 711 if (margin_bottom.is_auto()) 712 margin_bottom = CSS::Length::make_px(0); 713 714 // then set top to the static position, 715 auto static_position = calculate_static_position(box); 716 top = CSS::Length::make_px(static_position.y()); 717 718 // and finally apply rule number three below. 719 height = CSS::Size::make_px(compute_auto_height_for_block_formatting_context_root(box)); 720 solve_for_bottom(); 721 } 722 723 // If none of the three are auto: 724 else if (!top.is_auto() && !height.is_auto() && !bottom.is_auto()) { 725 // If both margin-top and margin-bottom are auto, 726 if (margin_top.is_auto() && margin_bottom.is_auto()) { 727 // solve the equation under the extra constraint that the two margins get equal values. 728 solve_for_margin_top_and_margin_bottom(); 729 } 730 731 // If one of margin-top or margin-bottom is auto, 732 else if (margin_top.is_auto() || margin_bottom.is_auto()) { 733 // solve the equation for that value. 734 if (margin_top.is_auto()) 735 solve_for_margin_top(); 736 else 737 solve_for_margin_bottom(); 738 } 739 740 // If the values are over-constrained, 741 else { 742 // ignore the value for bottom and solve for that value. 743 solve_for_bottom(); 744 } 745 } 746 747 // Otherwise, 748 else { 749 // set auto values for margin-top and margin-bottom to 0, 750 if (margin_top.is_auto()) 751 margin_top = CSS::Length::make_px(0); 752 if (margin_bottom.is_auto()) 753 margin_bottom = CSS::Length::make_px(0); 754 755 // and pick one of the following six rules that apply. 756 757 // 1. If top and height are auto and bottom is not auto, 758 if (top.is_auto() && height.is_auto() && !bottom.is_auto()) { 759 // (If we haven't done inside layout yet, we can't compute the auto height.) 760 if (before_or_after_inside_layout == BeforeOrAfterInsideLayout::Before) 761 return; 762 763 // then the height is based on the Auto heights for block formatting context roots, 764 height = CSS::Size::make_px(compute_auto_height_for_block_formatting_context_root(box)); 765 766 // and solve for top. 767 solve_for_top(); 768 } 769 770 // 2. If top and bottom are auto and height is not auto, 771 else if (top.is_auto() && bottom.is_auto() && !height.is_auto()) { 772 // then set top to the static position, 773 top = CSS::Length::make_px(calculate_static_position(box).y()); 774 775 // then solve for bottom. 776 solve_for_bottom(); 777 } 778 779 // 3. If height and bottom are auto and top is not auto, 780 else if (height.is_auto() && bottom.is_auto() && !top.is_auto()) { 781 // (If we haven't done inside layout yet, we can't compute the auto height.) 782 if (before_or_after_inside_layout == BeforeOrAfterInsideLayout::Before) 783 return; 784 785 // then the height is based on the Auto heights for block formatting context roots, 786 height = CSS::Size::make_px(compute_auto_height_for_block_formatting_context_root(box)); 787 788 // and solve for bottom. 789 solve_for_bottom(); 790 } 791 792 // 4. If top is auto, height and bottom are not auto, 793 else if (top.is_auto() && !height.is_auto() && !bottom.is_auto()) { 794 // then solve for top. 795 solve_for_top(); 796 } 797 798 // 5. If height is auto, top and bottom are not auto, 799 else if (height.is_auto() && !top.is_auto() && !bottom.is_auto()) { 800 // then solve for height. 801 solve_for_height(); 802 } 803 804 // 6. If bottom is auto, top and height are not auto, 805 else if (bottom.is_auto() && !top.is_auto() && !height.is_auto()) { 806 // then solve for bottom. 807 solve_for_bottom(); 808 } 809 } 810 811 auto used_height = height.resolved(box, height_of_containing_block_as_length).to_px(box); 812 auto const& computed_min_height = box.computed_values().min_height(); 813 auto const& computed_max_height = box.computed_values().max_height(); 814 815 if (!computed_max_height.is_none()) 816 used_height = min(used_height, computed_max_height.resolved(box, height_of_containing_block_as_length).resolved(box).to_px(box)); 817 if (!computed_min_height.is_auto()) 818 used_height = max(used_height, computed_min_height.resolved(box, height_of_containing_block_as_length).resolved(box).to_px(box)); 819 820 // NOTE: The following is not directly part of any spec, but this is where we resolve 821 // the final used values for vertical margin/border/padding. 822 823 auto& box_state = m_state.get_mutable(box); 824 box_state.margin_top = margin_top.resolved(box, width_of_containing_block_as_length).to_px(box); 825 box_state.margin_bottom = margin_bottom.resolved(box, width_of_containing_block_as_length).to_px(box); 826 box_state.border_top = box.computed_values().border_top().width; 827 box_state.border_bottom = box.computed_values().border_bottom().width; 828 box_state.padding_top = box.computed_values().padding().top().resolved(box, width_of_containing_block_as_length).to_px(box); 829 box_state.padding_bottom = box.computed_values().padding().bottom().resolved(box, width_of_containing_block_as_length).to_px(box); 830 831 // And here is where we assign the box's content height. 832 box_state.set_content_height(used_height); 833} 834 835// NOTE: This is different from content_box_rect_in_ancestor_coordinate_space() as this does *not* follow the containing block chain up, but rather the parent() chain. 836static CSSPixelRect content_box_rect_in_static_position_ancestor_coordinate_space(Box const& box, Box const& ancestor_box, LayoutState const& state) 837{ 838 auto rect = content_box_rect(box, state); 839 if (&box == &ancestor_box) 840 return rect; 841 for (auto const* current = box.parent(); current; current = current->parent()) { 842 if (current == &ancestor_box) 843 return rect; 844 auto const& current_state = state.get(static_cast<Box const&>(*current)); 845 rect.translate_by(current_state.offset); 846 } 847 // If we get here, ancestor_box was not an ancestor of `box`! 848 VERIFY_NOT_REACHED(); 849} 850 851// https://www.w3.org/TR/css-position-3/#staticpos-rect 852CSSPixelPoint FormattingContext::calculate_static_position(Box const& box) const 853{ 854 // NOTE: This is very ad-hoc. 855 // The purpose of this function is to calculate the approximate position that `box` 856 // would have had if it were position:static. 857 858 CSSPixels x = 0.0f; 859 CSSPixels y = 0.0f; 860 861 VERIFY(box.parent()); 862 if (box.parent()->children_are_inline()) { 863 // We're an abspos box with inline siblings. This is gonna get messy! 864 if (auto* sibling = box.previous_sibling()) { 865 // Hard case: there's a previous sibling. This means there's already inline content 866 // preceding the hypothetical static position of `box` within its containing block. 867 // If we had been position:static, that inline content would have been wrapped in 868 // anonymous block box, so now we get to imagine what the world might have looked like 869 // in that scenario.. 870 // Basically, we find its last associated line box fragment and place `box` under it. 871 // FIXME: I'm 100% sure this can be smarter, better and faster. 872 LineBoxFragment const* last_fragment = nullptr; 873 auto& cb_state = m_state.get(*sibling->containing_block()); 874 for (auto& line_box : cb_state.line_boxes) { 875 for (auto& fragment : line_box.fragments()) { 876 if (&fragment.layout_node() == sibling) 877 last_fragment = &fragment; 878 } 879 } 880 if (last_fragment) { 881 y = (last_fragment->offset().y() + last_fragment->height()).value(); 882 } 883 } else { 884 // Easy case: no previous sibling, we're at the top of the containing block. 885 } 886 } else { 887 x = m_state.get(box).margin_box_left(); 888 // We're among block siblings, Y can be calculated easily. 889 y = m_state.get(box).margin_box_top(); 890 } 891 auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block(), m_state); 892 return offset_to_static_parent.location().translated(x, y); 893} 894 895void FormattingContext::layout_absolutely_positioned_element(Box const& box, AvailableSpace const& available_space) 896{ 897 auto& containing_block_state = m_state.get_mutable(*box.containing_block()); 898 auto& box_state = m_state.get_mutable(box); 899 900 auto width_of_containing_block = available_space.width.to_px(); 901 auto height_of_containing_block = available_space.height.to_px(); 902 auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); 903 auto height_of_containing_block_as_length = CSS::Length::make_px(height_of_containing_block); 904 905 auto specified_width = box.computed_values().width().resolved(box, width_of_containing_block_as_length).resolved(box); 906 907 compute_width_for_absolutely_positioned_element(box, available_space); 908 909 // NOTE: We compute height before *and* after doing inside layout. 910 // This is done so that inside layout can resolve percentage heights. 911 // In some situations, e.g with non-auto top & bottom values, the height can be determined early. 912 compute_height_for_absolutely_positioned_element(box, available_space, BeforeOrAfterInsideLayout::Before); 913 914 auto independent_formatting_context = layout_inside(box, LayoutMode::Normal, box_state.available_inner_space_or_constraints_from(available_space)); 915 916 compute_height_for_absolutely_positioned_element(box, available_space, BeforeOrAfterInsideLayout::After); 917 918 box_state.margin_left = box.computed_values().margin().left().resolved(box, width_of_containing_block_as_length).to_px(box); 919 box_state.margin_top = box.computed_values().margin().top().resolved(box, width_of_containing_block_as_length).to_px(box); 920 box_state.margin_right = box.computed_values().margin().right().resolved(box, width_of_containing_block_as_length).to_px(box); 921 box_state.margin_bottom = box.computed_values().margin().bottom().resolved(box, width_of_containing_block_as_length).to_px(box); 922 923 box_state.border_left = box.computed_values().border_left().width; 924 box_state.border_right = box.computed_values().border_right().width; 925 box_state.border_top = box.computed_values().border_top().width; 926 box_state.border_bottom = box.computed_values().border_bottom().width; 927 928 auto const& computed_left = box.computed_values().inset().left(); 929 auto const& computed_right = box.computed_values().inset().right(); 930 auto const& computed_top = box.computed_values().inset().top(); 931 auto const& computed_bottom = box.computed_values().inset().bottom(); 932 933 box_state.inset_left = computed_left.resolved(box, width_of_containing_block_as_length).to_px(box); 934 box_state.inset_top = computed_top.resolved(box, height_of_containing_block_as_length).to_px(box); 935 box_state.inset_right = computed_right.resolved(box, width_of_containing_block_as_length).to_px(box); 936 box_state.inset_bottom = computed_bottom.resolved(box, height_of_containing_block_as_length).to_px(box); 937 938 if (computed_left.is_auto() && box.computed_values().width().is_auto() && computed_right.is_auto()) { 939 if (box.computed_values().margin().left().is_auto()) 940 box_state.margin_left = 0; 941 if (box.computed_values().margin().right().is_auto()) 942 box_state.margin_right = 0; 943 } 944 945 auto static_position = calculate_static_position(box); 946 947 CSSPixelPoint used_offset; 948 949 if (!computed_left.is_auto()) { 950 CSSPixels x_offset = box_state.inset_left 951 + box_state.border_box_left(); 952 used_offset.set_x(x_offset + box_state.margin_left); 953 } else if (!computed_right.is_auto()) { 954 CSSPixels x_offset = CSSPixels(0) 955 - box_state.inset_right 956 - box_state.border_box_right(); 957 used_offset.set_x(width_of_containing_block + x_offset - box_state.content_width() - box_state.margin_right); 958 } else { 959 // NOTE: static position is content box position so border_box and margin should not be added 960 used_offset.set_x(static_position.x()); 961 } 962 963 if (!computed_top.is_auto()) { 964 CSSPixels y_offset = box_state.inset_top 965 + box_state.border_box_top(); 966 used_offset.set_y(y_offset + box_state.margin_top); 967 } else if (!computed_bottom.is_auto()) { 968 CSSPixels y_offset = CSSPixels(0) 969 - box_state.inset_bottom 970 - box_state.border_box_bottom(); 971 used_offset.set_y(height_of_containing_block + y_offset - box_state.content_height() - box_state.margin_bottom); 972 } else { 973 // NOTE: static position is content box position so border_box and margin should not be added 974 used_offset.set_y(static_position.y()); 975 } 976 977 // NOTE: Absolutely positioned boxes are relative to the *padding edge* of the containing block. 978 used_offset.translate_by(-containing_block_state.padding_left, -containing_block_state.padding_top); 979 980 box_state.set_content_offset(used_offset); 981 982 if (independent_formatting_context) 983 independent_formatting_context->parent_context_did_dimension_child_root_box(); 984} 985 986void FormattingContext::compute_height_for_absolutely_positioned_replaced_element(ReplacedBox const& box, AvailableSpace const& available_space, BeforeOrAfterInsideLayout) 987{ 988 // 10.6.5 Absolutely positioned, replaced elements 989 // The used value of 'height' is determined as for inline replaced elements. 990 m_state.get_mutable(box).set_content_height(compute_height_for_replaced_element(m_state, box, available_space)); 991} 992 993// https://www.w3.org/TR/css-position-3/#relpos-insets 994void FormattingContext::compute_inset(Box const& box) 995{ 996 if (box.computed_values().position() != CSS::Position::Relative) 997 return; 998 999 auto resolve_two_opposing_insets = [&](CSS::LengthPercentage const& computed_start, CSS::LengthPercentage const& computed_end, CSSPixels& used_start, CSSPixels& used_end, CSSPixels reference_for_percentage) { 1000 auto resolved_first = computed_start.resolved(box, CSS::Length::make_px(reference_for_percentage)).resolved(box); 1001 auto resolved_second = computed_end.resolved(box, CSS::Length::make_px(reference_for_percentage)).resolved(box); 1002 1003 if (resolved_first.is_auto() && resolved_second.is_auto()) { 1004 // If opposing inset properties in an axis both compute to auto (their initial values), 1005 // their used values are zero (i.e., the boxes stay in their original position in that axis). 1006 used_start = 0; 1007 used_end = 0; 1008 } else if (resolved_first.is_auto() || resolved_second.is_auto()) { 1009 // If only one is auto, its used value becomes the negation of the other, and the box is shifted by the specified amount. 1010 if (resolved_first.is_auto()) { 1011 used_end = resolved_second.to_px(box); 1012 used_start = -used_end; 1013 } else { 1014 used_start = resolved_first.to_px(box); 1015 used_end = -used_start; 1016 } 1017 } else { 1018 // If neither is auto, the position is over-constrained; (with respect to the writing mode of its containing block) 1019 // the computed end side value is ignored, and its used value becomes the negation of the start side. 1020 used_start = resolved_first.to_px(box); 1021 used_end = -used_start; 1022 } 1023 }; 1024 1025 auto& box_state = m_state.get_mutable(box); 1026 auto const& computed_values = box.computed_values(); 1027 1028 // FIXME: Respect the containing block's writing-mode. 1029 resolve_two_opposing_insets(computed_values.inset().left(), computed_values.inset().right(), box_state.inset_left, box_state.inset_right, containing_block_width_for(box)); 1030 resolve_two_opposing_insets(computed_values.inset().top(), computed_values.inset().bottom(), box_state.inset_top, box_state.inset_bottom, containing_block_height_for(box)); 1031} 1032 1033// https://drafts.csswg.org/css-sizing-3/#fit-content-size 1034CSSPixels FormattingContext::calculate_fit_content_width(Layout::Box const& box, AvailableSpace const& available_space) const 1035{ 1036 // If the available space in a given axis is definite, 1037 // equal to clamp(min-content size, stretch-fit size, max-content size) 1038 // (i.e. max(min-content size, min(max-content size, stretch-fit size))). 1039 if (available_space.width.is_definite()) { 1040 return max(calculate_min_content_width(box), 1041 min(calculate_stretch_fit_width(box, available_space.width), 1042 calculate_max_content_width(box))); 1043 } 1044 1045 // When sizing under a min-content constraint, equal to the min-content size. 1046 if (available_space.width.is_min_content()) 1047 return calculate_min_content_width(box); 1048 1049 // Otherwise, equal to the max-content size in that axis. 1050 return calculate_max_content_width(box); 1051} 1052 1053// https://drafts.csswg.org/css-sizing-3/#fit-content-size 1054CSSPixels FormattingContext::calculate_fit_content_height(Layout::Box const& box, AvailableSpace const& available_space) const 1055{ 1056 // If the available space in a given axis is definite, 1057 // equal to clamp(min-content size, stretch-fit size, max-content size) 1058 // (i.e. max(min-content size, min(max-content size, stretch-fit size))). 1059 if (available_space.height.is_definite()) { 1060 return max(calculate_min_content_height(box, available_space.width), 1061 min(calculate_stretch_fit_height(box, available_space.height), 1062 calculate_max_content_height(box, available_space.width))); 1063 } 1064 1065 // When sizing under a min-content constraint, equal to the min-content size. 1066 if (available_space.height.is_min_content()) 1067 return calculate_min_content_height(box, available_space.width); 1068 1069 // Otherwise, equal to the max-content size in that axis. 1070 return calculate_max_content_height(box, available_space.width); 1071} 1072 1073CSSPixels FormattingContext::calculate_min_content_width(Layout::Box const& box) const 1074{ 1075 if (box.has_intrinsic_width()) 1076 return *box.intrinsic_width(); 1077 1078 auto& root_state = m_state.m_root; 1079 1080 auto& cache = *root_state.intrinsic_sizes.ensure(&box, [] { return adopt_own(*new LayoutState::IntrinsicSizes); }); 1081 if (cache.min_content_width.has_value()) 1082 return *cache.min_content_width; 1083 1084 LayoutState throwaway_state(&m_state); 1085 1086 auto& box_state = throwaway_state.get_mutable(box); 1087 box_state.width_constraint = SizeConstraint::MinContent; 1088 1089 auto context = const_cast<FormattingContext*>(this)->create_independent_formatting_context_if_needed(throwaway_state, box); 1090 VERIFY(context); 1091 1092 auto available_width = AvailableSize::make_min_content(); 1093 auto available_height = AvailableSize::make_indefinite(); 1094 context->run(box, LayoutMode::IntrinsicSizing, AvailableSpace(available_width, available_height)); 1095 1096 if (context->type() == FormattingContext::Type::Flex) { 1097 cache.min_content_width = box_state.content_width(); 1098 } else { 1099 cache.min_content_width = context->greatest_child_width(box).value(); 1100 } 1101 1102 if (!isfinite(cache.min_content_width->value())) { 1103 // HACK: If layout calculates a non-finite result, something went wrong. Force it to zero and log a little whine. 1104 dbgln("FIXME: Calculated non-finite min-content width for {}", box.debug_description()); 1105 cache.min_content_width = 0; 1106 } 1107 1108 return *cache.min_content_width; 1109} 1110 1111CSSPixels FormattingContext::calculate_max_content_width(Layout::Box const& box) const 1112{ 1113 if (box.has_intrinsic_width()) 1114 return *box.intrinsic_width(); 1115 1116 auto& root_state = m_state.m_root; 1117 1118 auto& cache = *root_state.intrinsic_sizes.ensure(&box, [] { return adopt_own(*new LayoutState::IntrinsicSizes); }); 1119 if (cache.max_content_width.has_value()) 1120 return *cache.max_content_width; 1121 1122 LayoutState throwaway_state(&m_state); 1123 1124 auto& box_state = throwaway_state.get_mutable(box); 1125 box_state.width_constraint = SizeConstraint::MaxContent; 1126 1127 auto context = const_cast<FormattingContext*>(this)->create_independent_formatting_context_if_needed(throwaway_state, box); 1128 VERIFY(context); 1129 1130 auto available_width = AvailableSize::make_max_content(); 1131 auto available_height = AvailableSize::make_indefinite(); 1132 context->run(box, LayoutMode::IntrinsicSizing, AvailableSpace(available_width, available_height)); 1133 1134 if (context->type() == FormattingContext::Type::Flex) { 1135 cache.max_content_width = box_state.content_width(); 1136 } else { 1137 cache.max_content_width = context->greatest_child_width(box).value(); 1138 } 1139 1140 if (!isfinite(cache.max_content_width->value())) { 1141 // HACK: If layout calculates a non-finite result, something went wrong. Force it to zero and log a little whine. 1142 dbgln("FIXME: Calculated non-finite max-content width for {}", box.debug_description()); 1143 cache.max_content_width = 0; 1144 } 1145 1146 return *cache.max_content_width; 1147} 1148 1149// https://www.w3.org/TR/css-sizing-3/#min-content-block-size 1150CSSPixels FormattingContext::calculate_min_content_height(Layout::Box const& box, AvailableSize const& available_width) const 1151{ 1152 // For block containers, tables, and inline boxes, this is equivalent to the max-content block size. 1153 if (box.is_block_container() || box.is_table()) 1154 return calculate_max_content_height(box, available_width); 1155 1156 if (box.has_intrinsic_height()) 1157 return *box.intrinsic_height(); 1158 1159 bool is_cacheable = available_width.is_definite() || available_width.is_intrinsic_sizing_constraint(); 1160 Optional<CSSPixels>* cache_slot = nullptr; 1161 if (is_cacheable) { 1162 auto& root_state = m_state.m_root; 1163 auto& cache = *root_state.intrinsic_sizes.ensure(&box, [] { return adopt_own(*new LayoutState::IntrinsicSizes); }); 1164 if (available_width.is_definite()) { 1165 cache_slot = &cache.min_content_height_with_definite_available_width.ensure(available_width.to_px()); 1166 } else if (available_width.is_min_content()) { 1167 cache_slot = &cache.min_content_height_with_min_content_available_width; 1168 } else if (available_width.is_max_content()) { 1169 cache_slot = &cache.min_content_height_with_max_content_available_width; 1170 } 1171 } 1172 1173 if (cache_slot && cache_slot->has_value()) 1174 return cache_slot->value(); 1175 1176 LayoutState throwaway_state(&m_state); 1177 1178 auto& box_state = throwaway_state.get_mutable(box); 1179 box_state.height_constraint = SizeConstraint::MinContent; 1180 1181 auto context = const_cast<FormattingContext*>(this)->create_independent_formatting_context_if_needed(throwaway_state, box); 1182 VERIFY(context); 1183 1184 context->run(box, LayoutMode::IntrinsicSizing, AvailableSpace(available_width, AvailableSize::make_min_content())); 1185 1186 auto min_content_height = context->automatic_content_height(); 1187 if (!isfinite(min_content_height.value())) { 1188 // HACK: If layout calculates a non-finite result, something went wrong. Force it to zero and log a little whine. 1189 dbgln("FIXME: Calculated non-finite min-content height for {}", box.debug_description()); 1190 min_content_height = 0; 1191 } 1192 1193 if (cache_slot) { 1194 *cache_slot = min_content_height; 1195 } 1196 return min_content_height; 1197} 1198 1199CSSPixels FormattingContext::calculate_max_content_height(Layout::Box const& box, AvailableSize const& available_width) const 1200{ 1201 if (box.has_intrinsic_height()) 1202 return *box.intrinsic_height(); 1203 1204 bool is_cacheable = available_width.is_definite() || available_width.is_intrinsic_sizing_constraint(); 1205 Optional<CSSPixels>* cache_slot = nullptr; 1206 if (is_cacheable) { 1207 auto& root_state = m_state.m_root; 1208 auto& cache = *root_state.intrinsic_sizes.ensure(&box, [] { return adopt_own(*new LayoutState::IntrinsicSizes); }); 1209 if (available_width.is_definite()) { 1210 cache_slot = &cache.max_content_height_with_definite_available_width.ensure(available_width.to_px()); 1211 } else if (available_width.is_min_content()) { 1212 cache_slot = &cache.max_content_height_with_min_content_available_width; 1213 } else if (available_width.is_max_content()) { 1214 cache_slot = &cache.max_content_height_with_max_content_available_width; 1215 } 1216 } 1217 1218 if (cache_slot && cache_slot->has_value()) 1219 return cache_slot->value(); 1220 1221 LayoutState throwaway_state(&m_state); 1222 1223 auto& box_state = throwaway_state.get_mutable(box); 1224 box_state.height_constraint = SizeConstraint::MaxContent; 1225 1226 auto context = const_cast<FormattingContext*>(this)->create_independent_formatting_context_if_needed(throwaway_state, box); 1227 VERIFY(context); 1228 1229 context->run(box, LayoutMode::IntrinsicSizing, AvailableSpace(available_width, AvailableSize::make_max_content())); 1230 1231 auto max_content_height = context->automatic_content_height(); 1232 1233 if (!isfinite(max_content_height.value())) { 1234 // HACK: If layout calculates a non-finite result, something went wrong. Force it to zero and log a little whine. 1235 dbgln("FIXME: Calculated non-finite max-content height for {}", box.debug_description()); 1236 max_content_height = 0; 1237 } 1238 1239 if (cache_slot) { 1240 *cache_slot = max_content_height; 1241 } 1242 1243 return max_content_height; 1244} 1245 1246CSS::Length FormattingContext::calculate_inner_width(Layout::Box const& box, AvailableSize const& available_width, CSS::Size const& width) const 1247{ 1248 auto width_of_containing_block = available_width.to_px(); 1249 auto width_of_containing_block_as_length_for_resolve = CSS::Length::make_px(width_of_containing_block); 1250 if (width.is_auto()) { 1251 return width.resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); 1252 } 1253 1254 if (!available_width.is_definite()) 1255 width_of_containing_block_as_length_for_resolve = CSS::Length::make_px(0); 1256 1257 auto& computed_values = box.computed_values(); 1258 if (computed_values.box_sizing() == CSS::BoxSizing::BorderBox) { 1259 auto const padding_left = computed_values.padding().left().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); 1260 auto const padding_right = computed_values.padding().right().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); 1261 1262 auto inner_width = width.resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box).to_px(box) 1263 - computed_values.border_left().width 1264 - padding_left.to_px(box) 1265 - computed_values.border_right().width 1266 - padding_right.to_px(box); 1267 return CSS::Length::make_px(max(inner_width, 0)); 1268 } 1269 1270 return width.resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); 1271} 1272 1273CSS::Length FormattingContext::calculate_inner_height(Layout::Box const& box, AvailableSize const& available_height, CSS::Size const& height) const 1274{ 1275 auto height_of_containing_block = available_height.to_px(); 1276 auto height_of_containing_block_as_length_for_resolve = CSS::Length::make_px(height_of_containing_block); 1277 if (height.is_auto()) { 1278 return height.resolved(box, height_of_containing_block_as_length_for_resolve).resolved(box); 1279 } 1280 1281 if (!available_height.is_definite()) 1282 height_of_containing_block_as_length_for_resolve = CSS::Length::make_px(0); 1283 1284 auto& computed_values = box.computed_values(); 1285 if (computed_values.box_sizing() == CSS::BoxSizing::BorderBox) { 1286 auto width_of_containing_block = CSS::Length::make_px(containing_block_width_for(box)); 1287 1288 auto const padding_top = computed_values.padding().top().resolved(box, width_of_containing_block).resolved(box); 1289 auto const padding_bottom = computed_values.padding().bottom().resolved(box, width_of_containing_block).resolved(box); 1290 1291 auto inner_height = height.resolved(box, height_of_containing_block_as_length_for_resolve).resolved(box).to_px(box) 1292 - computed_values.border_top().width 1293 - padding_top.to_px(box) 1294 - computed_values.border_bottom().width 1295 - padding_bottom.to_px(box); 1296 return CSS::Length::make_px(max(inner_height, 0)); 1297 } 1298 1299 return height.resolved(box, height_of_containing_block_as_length_for_resolve).resolved(box); 1300} 1301 1302CSSPixels FormattingContext::containing_block_width_for(Box const& box, LayoutState const& state) 1303{ 1304 auto& containing_block_state = state.get(*box.containing_block()); 1305 auto& box_state = state.get(box); 1306 1307 switch (box_state.width_constraint) { 1308 case SizeConstraint::MinContent: 1309 return 0; 1310 case SizeConstraint::MaxContent: 1311 return INFINITY; 1312 case SizeConstraint::None: 1313 return containing_block_state.content_width(); 1314 } 1315 VERIFY_NOT_REACHED(); 1316} 1317 1318CSSPixels FormattingContext::containing_block_height_for(Box const& box, LayoutState const& state) 1319{ 1320 auto& containing_block_state = state.get(*box.containing_block()); 1321 auto& box_state = state.get(box); 1322 1323 switch (box_state.height_constraint) { 1324 case SizeConstraint::MinContent: 1325 return 0; 1326 case SizeConstraint::MaxContent: 1327 return INFINITY; 1328 case SizeConstraint::None: 1329 return containing_block_state.content_height(); 1330 } 1331 VERIFY_NOT_REACHED(); 1332} 1333 1334// https://drafts.csswg.org/css-sizing-3/#stretch-fit-size 1335CSSPixels FormattingContext::calculate_stretch_fit_width(Box const& box, AvailableSize const& available_width) const 1336{ 1337 // The size a box would take if its outer size filled the available space in the given axis; 1338 // in other words, the stretch fit into the available space, if that is definite. 1339 // Undefined if the available space is indefinite. 1340 auto const& box_state = m_state.get(box); 1341 return available_width.to_px() 1342 - box_state.margin_left 1343 - box_state.margin_right 1344 - box_state.padding_left 1345 - box_state.padding_right 1346 - box_state.border_left 1347 - box_state.border_right; 1348} 1349 1350// https://drafts.csswg.org/css-sizing-3/#stretch-fit-size 1351CSSPixels FormattingContext::calculate_stretch_fit_height(Box const& box, AvailableSize const& available_height) const 1352{ 1353 // The size a box would take if its outer size filled the available space in the given axis; 1354 // in other words, the stretch fit into the available space, if that is definite. 1355 // Undefined if the available space is indefinite. 1356 auto const& box_state = m_state.get(box); 1357 return available_height.to_px() 1358 - box_state.margin_top 1359 - box_state.margin_bottom 1360 - box_state.padding_top 1361 - box_state.padding_bottom 1362 - box_state.border_top 1363 - box_state.border_bottom; 1364} 1365 1366bool FormattingContext::should_treat_width_as_auto(Box const& box, AvailableSpace const& available_space) 1367{ 1368 return box.computed_values().width().is_auto() 1369 || (box.computed_values().width().contains_percentage() && !available_space.width.is_definite()); 1370} 1371 1372bool FormattingContext::should_treat_height_as_auto(Box const& box, AvailableSpace const& available_space) 1373{ 1374 return box.computed_values().height().is_auto() 1375 || (box.computed_values().height().contains_percentage() && !available_space.height.is_definite()); 1376} 1377 1378bool FormattingContext::can_skip_is_anonymous_text_run(Box& box) 1379{ 1380 if (box.is_anonymous() && !box.is_generated() && !box.first_child_of_type<BlockContainer>()) { 1381 bool contains_only_white_space = true; 1382 box.for_each_in_subtree([&](auto const& node) { 1383 if (!is<TextNode>(node) || !static_cast<TextNode const&>(node).dom_node().data().is_whitespace()) { 1384 contains_only_white_space = false; 1385 return IterationDecision::Break; 1386 } 1387 return IterationDecision::Continue; 1388 }); 1389 if (contains_only_white_space) 1390 return true; 1391 } 1392 return false; 1393} 1394 1395}