Skip to content

[css2][css-flow] Define the intrinsic sizing algorithm for block containers #9120

@Loirooriol

Description

@Loirooriol

https://drafts.csswg.org/css2/#float-width

Calculation of the shrink-to-fit width is similar to calculating the width of a table cell using the automatic table layout algorithm. Roughly: calculate the preferred width by formatting the content without breaking lines other than where explicit line breaks occur, and also calculate the preferred minimum width, e.g., by trying all possible line breaks. CSS 2 does not define the exact algorithm.

This is especially tricky in the presence of floats, clearance, and children that establish an independent formatting context.

However, we do seem to have good interoperability (at least in the block-level children case), even if there is no WPT coverage. So I would like to discuss resolving on this interoperable behavior.

Some interesting examples:

The max-content size may be bigger than necessary
<article style="display: inline-block; border: solid">
  <div><!-- Just to prevent the parent from establishing an IFC, otherwise WebKit is different --></div>
  <div style="float: left; width: 50px; height: 30px; background: cyan"></div>
  <div style="float: right; width: 30px; height: 30px; background: yellow"></div>
  <div style="float: left; clear: left; width: 70px; height: 20px; background: magenta"></div>
</article>

It uses a width of 100px, even though 80px would suffice to avoid "breaking lines" between the cyan and the yellow floats.

The max-content size may not be big enough to avoid "breaking lines"
<article style="display: inline-block; border: solid; vertical-align: top">
  <div style="float: left; width: 30px; height: 30px; background: cyan"></div>
  <div style="float: left; width: 30px; height: 30px; background: yellow"></div>
  <div style="overflow: hidden; width: 50px; height: 30px; background: magenta"></div>
</article>
<article style="display: inline-block; border: solid; vertical-align: top">
  <div style="float: left; width: 30px; height: 30px; background: cyan"></div>
  <div style="float: left; width: 30px; height: 30px; background: yellow"></div>
  <div>
    <div style="overflow: hidden; width: 50px; height: 30px; background: magenta"></div>
  </div>
</article>
<article style="display: inline-block; border: solid; vertical-align: top">
  <div style="float: left; width: 30px; height: 30px; background: cyan"></div>
  <div style="float: left; width: 30px; height: 30px; background: yellow"></div>
  <div></div>
  <div style="overflow: hidden; width: 50px; height: 30px; background: magenta"></div>
</article>

Note that the 2nd case just wraps the BFC root inside an intermediate container, and the 3rd case inserts and empty block before it. Both changes reduce the max-content size and then the BFC root no longer fits next to the floats.

If the block container contains block-level children (and is not a table wrapper box?):

In Servo I have tried to implement the behavior that I observed on other browsers, I haven't checked their source code but it seems to be:

For the max-content size,

  1. Let max_size be 0px. This tracks the maximum size seen so far, not including trailing uncleared floats.
  2. Let left_floats be 0px. This tracks the size of the trailing uncleared left floats.
  3. Let right_floats be 0px. This tracks the size of the trailing uncleared right floats.
  4. For each block-level child (including floats, ignoring abspos):
    1. Let clear be the the computed value of the clear CSS property of the child.
    2. If the child continues the same BFC (instead of establishing an independent formatting context),
      1. Set clear := both (this is to avoid adding its size to the floats)
    3. If clear is different than none,
      1. Set max_size := max(max_size, left_floats + right_floats)
    4. If clear is left or both,
      1. Set left_floats := 0px
    5. If clear is right or both,
      1. Set right_floats := 0px
    6. Let size be the outer max-content size of the child, floored by 0px
      1. Bug? Firefox doesn't floor when clear is none and the element doesn't float
      2. Bug? WebKit seems to floor left_floats + right_floats instead of each float individually
    7. If the child floats left,
      1. Set left_floats := left_floats + size
    8. Else, if the child floats right,
      1. Set right_floats := right_floats + size
    9. Else (the child doesn't float),
      1. Set max_size := max(max_size, left_floats + right_floats + size)
      2. Set left_floats := 0px
      3. Set right_floats := 0px
  5. The max-content size is max(max_size, left_floats + right_floats)

The min-content size is just the maximum among the outer min-content sizes of each float or in-flow block-level child.

Firefox flooring bug (?)
<article style="display: inline-block; border: solid; margin-right: 100px">
  <div style="float: right; width: 50px; height: 30px; background: cyan"></div>
  <div style="clear: left; overflow: hidden; width: 100px; height: 30px; background: magenta; margin-right: -500px"></div>
</article>
<article style="display: inline-block; border: solid">
  <div style="float: right; width: 50px; height: 30px; background: cyan"></div>
  <div style="overflow: hidden; width: 100px; height: 30px; background: magenta; margin-right: -500px"></div>
</article>

There is no left float, so clear: left should have no effect. However, removing it causes Firefox to stop flooring the size of the BFC root by 0px before adding it to the floats, and then the container becomes 0px wide.

Firefox Blink/WebKit
WebKit flooring bug (?)
<article style="display: inline-block; border: solid; margin-left: 25px">
  <div style="float: left; width: 50px; height: 50px; background: cyan"></div>
  <div style="float: left; width: 50px; height: 40px; background: yellow; margin-left: -75px"></div>
  <div style="overflow: hidden; width: 50px; height: 50px; background: magenta;"></div>
</article>
<article style="display: inline-block; border: solid;">
  <div style="float: left; width: 50px; height: 50px; background: cyan"></div>
  <div style="float: right; width: 50px; height: 40px; background: yellow; margin-left: -75px"></div>
  <div style="overflow: hidden; width: 50px; height: 50px; background: magenta;"></div>
</article>

WebKit doesn't floor the floats individually, so left_floats + right_floats is 25px instead of 50px. This seems suboptimal since it forces the BFC root to move down.

Firefox/Blink WebKit

If the block container establishes an inline formatting context:

There is less interoperability here, e.g.

<article style="display: inline-block; border: solid">
  .<!-- Just to force the parent to establish an IFC on Blink -->
  <div style="float: left; width: 50px; height: 30px; background: cyan"></div>
  <div style="float: right; width: 30px; height: 30px; background: yellow"></div>
  <div style="float: left; clear: left; width: 70px; height: 20px; background: magenta"></div>
</article>
Firefox Blink/WebKit

I haven't implemented this case yet so I don't have the details, but aligning with Firefox seems more consistent with the block-level children case. Blink and WebKit seem to ignore clear: left.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions