Skip to content

Incorrect free space distribution in flex layout #419

@DRKV333

Description

@DRKV333

The free space distribution algorithm in litehtml doesn't exactly match the one outlined in the spec, which leads to incorrect rendering in certain situations. I have multiple examples here, where litehtml doesn't work correctly, each of which is a different deviation from the spec, but I though it would be simpler to just make one issue for all of them.

When growing, the initial sum of flex grow factors is used, rather than the sum of unfrozen items

Step 4.c says: (emphasis mine)

If using the flex grow factor
Find the ratio of the item’s flex grow factor to the sum of the flex grow factors of all unfrozen items on the line. Set the item’s target main size to its flex base size plus a fraction of the remaining free space proportional to the ratio.

But litehtml always uses the initial sum, which means when an item is clamped to its max size, other items grow less than they should. This in itself is fairly easy to fix tough.

Example

<!DOCTYPE html>
<head>
    <style>
        .flexbox {
            display: flex;
            flex-direction: row;
            justify-content: space-around;
            width: 500px;
            background-color: blue;
        }
        .item1 {
            flex-grow: 10;
            width: 10px;
            max-width: 15px;
            background-color: red;
        }
        .item2 {
            flex-grow: 1;
            width: 10px;
            background-color: green;
        }
    </style>
</head>
<html>
    <div class="flexbox">
        <span class="item1"></span>
        <span class="item2">hello</span>
        <span class="item2">world</span>
    </div>
</html>

Expected:
Image

Actual:
Image

Used flex factor is determined from flex base sizes, rather than hypothetical main sizes

Step 1 says that to determined whether the items need to shrink or grow, one should "sum the outer hypothetical main sizes of all items on the line" and compare that to the container's main size.
However litehtml instead compares the container's main size with the sum of the items' flex base size. (The difference between the two is mostly that the hypothetical main size is clamped between the min and max main sizes.)
Take an example where a flex item has a small base size, but large min size, which combined with the base size of some other item overflows the container. The correct behavior is to clamp the first item to its min size and shrink the other one. Litehtml instead will try to grow both items.

Example

<!DOCTYPE html>
<head>
    <style>
        .flexbox {
            display: flex;
            flex-direction: row;
            justify-content: space-around;
            width: 500px;
            height: 20px;
            background-color: blue;
        }
        .item1 {
            flex-grow: 0;
            flex-shrink: 1;
            width: 10px;
            min-width: 400px;
            height: 10px;
            background-color: red;
        }
        .item2 {
            flex-grow: 0;
            flex-shrink: 1;
            width: 200px;
            height: 10px;
            background-color: green;
        }
    </style>
</head>
<html>
    <div class="flexbox">
        <span class="item1">hello</span>
        <span class="item2">world</span>
    </div>
</html>

Expected:
Image

Actual:
Image

Flex factor sums less than one are handled incorrectly

The algorithm in the spec handles flex factor sums less then one in the remaining free space calculation in step 4.b.
Litehtml instead only checks for this once at the start of the algorithm. This leads to incorrect behavior if the sum flex factor becomes less than one due to an item getting frozen.
The example is from here: https://stackoverflow.com/questions/76291155/understanding-flex-shrink-when-the-value-is-less-than-1
The hypothetical main sizes overflow the container, so shrinking happens. The first item gets clamped to its min size. The remaining second item has a flex shrink factor less than one, so it shouldn't shrink all the way down to fit the container, it should overflow. In litehtml there is no overflow.

Details

<!DOCTYPE html>
<head>
    <style>
        .flexbox {
            display: flex;
            flex-direction: row;
            justify-content: space-around;
            width: 220px;
            height: 20px;
            background-color: blue;
        }
        .item1 {
            flex-shrink: 1;
            width: 200px;
            min-width: 100px;
            height: 10px;
            background-color: red;
        }
        .item2 {
            flex-shrink: .25;
            width: 200px;
            min-width: 100px;
            height: 10px;
            background-color: green;
        }
    </style>
</head>
<html>
    <div class="flexbox">
        <span class="item1">hello</span>
        <span class="item2">world</span>
    </div>
</html>

Expected:
Image

Actual:
Image

To handle this correctly requires taking the initial free space calculated in step 3, which also might not be entirely right here, since litehtml doesn't do step 2, where inflexible items are frozen.

Min/max violations are not always handled correctly

The way min/max violations are handled is different between litehtml and the spec. I don't fully understand why the spec does it that particular way, but I did find at least one problem with the way litehtml works. When growing items, their min size is not considered. They only get clamped sometime later, when they are rendered, causing an overflow in this example.

Details

<!DOCTYPE html>
<head>
    <style>
        .flexbox {
            display: flex;
            flex-direction: row;
            justify-content: space-around;
            width: 500px;
            height: 20px;
            background-color: blue;
        }
        .item1 {
            flex-grow: 10;
            width: 10px;
            height: 10px;
            background-color: red;
        }
        .item2 {
            flex-grow: 1;
            width: 10px;
            min-width: 100px;
            height: 10px;
            background-color: green;
        }
    </style>
</head>
<html>
    <div class="flexbox">
        <span class="item1">hello</span>
        <span class="item2">world</span>
    </div>
</html>

Expected:
Image

Actual:
Image

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