Ok, I will explain the oddity in the `nsColumnSetFrame` with a further reduced testcase by dropping the surrounding `div` around `dev.float-L` which causes an assertion failure (see my explanation in a comment on phabricator). So the testcase would be:
<div class="multicol-b">
<div class="step"></div>
<div class="float-L"></div>
</div>
* 1px=60 logical unit. Both `.step` and `.float-L` have 1px height, thus it's totally 2px=120. And we need at least 40 BSize to place it in 3 columns (120 = 40 * 3).
* At the beginning of the log, it's the outer multicol `.multicol-a` relowing the child #0 with `availBSize=1699`, `float-R` consumes 26px=1560, while the inner multicol has 1px border, which consumes 2*1px=120. So the `availableContentBSize` for the inner one only remains `1699 - 1560 - 120 = 19`. But because of the lack of a `std::min` in initialization, it tries to reflow `40`.
* see the inline comments in the log
// outer reflows child #0
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #0 0x7fe0ef551be8: availBSize=1699
[00000003] nsBlockFrame:0x7fe0ef551be8 Reflow
// .float-R reflows
[00000004] nsBlockFrame:0x7fe0ef551d38 Reflow
[00000004] nsBlockFrame:0x7fe0ef551d38 Reflow done
// inner reflows
[Child 8618: Main Thread]: D/ColumnSet ChooseColumnStrategy: numColumns=3, colISize=4000, expectedISizeLeftOver=0, colBSize=40, colGap=0
// at this point, we only have 19 available, why try `mColMaxBSize=40`? To exceed 19 is pointless
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Doing column reflow pass: mLastBalanceBSize=40, mColMaxBSize=40, RTL=0, mBalanceColCount=3, mColISize=4000, mColGap=0
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #0 0x7fe0ef551df0: availBSize=40
[00000004] nsBlockFrame:0x7fe0ef551df0 Reflow
[00000005] nsBlockFrame:0x7fe0ef551f40 Reflow
[00000005] nsBlockFrame:0x7fe0ef551f40 Reflow done
[00000004] nsBlockFrame:0x7fe0ef551df0 Reflow done
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowed child #0 0x7fe0ef551df0: status=[Complete=N,NIF=Y,Truncated=N,Break=N,FirstLetter=N], desiredSize=(4000,40), CarriedOutBEndMargin=0 (ignored)
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Next childOrigin.iCoord=4060
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #1 0x7fe0ef552398: availBSize=40
[00000004] nsBlockFrame:0x7fe0ef552398 Reflow
[00000005] nsBlockFrame:0x7fe0ef552450 Reflow
[00000005] nsBlockFrame:0x7fe0ef552450 Reflow done
[00000005] nsBlockFrame:0x7fe0ef551ff8 Reflow
[00000005] nsBlockFrame:0x7fe0ef551ff8 Reflow done
[00000004] nsBlockFrame 0x7fe0ef552398 SplitFloat 0x7fe0ef551ff8 -> 0x7fe0ef5522d0
[00000004] nsBlockFrame:0x7fe0ef552398 Reflow done
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowed child #1 0x7fe0ef552398: status=[Complete=O,NIF=Y,Truncated=N,Break=N,FirstLetter=N], desiredSize=(4000,40), CarriedOutBEndMargin=0 (ignored)
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Next childOrigin.iCoord=8060
// Why inconsistent height? Both child #0 and #1 have 40, #2 only has 19?
// That's because https://github.com/mozilla/gecko-dev/blob/master/layout/generic/nsColumnSetFrame.cpp#L712
// the last column is unbounded, thus it takes all avail size, which is 19, which is **smaller** than the previous columns.
// Now we take a look at float splitting state. 60+60 should be split into 3 column, thus (40)+(20+20)+(40),
// So there is already a split for 60 -> 20 + 40.
// An unfortunate consequence of the oddity here is that the 40 is further split into 40 -> 19 + 21, then
// 21 is pushed to child#2 as a pushed-float, see splitting `->` below.
// After this reflow, we end up with split float `1ff8`, `22d0`, `2790`
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #2 0x7fe0ef552558: availBSize=19
[00000004] nsBlockFrame:0x7fe0ef552558 Reflow
[00000005] nsBlockFrame:0x7fe0ef5522d0 Reflow
[00000005] nsBlockFrame:0x7fe0ef5522d0 Reflow done
-> [00000004] nsBlockFrame 0x7fe0ef552558 SplitFloat 0x7fe0ef5522d0 -> 0x7fe0ef552790
[00000004] nsBlockFrame:0x7fe0ef552558 Reflow done
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowed child #2 0x7fe0ef552558: status=[Complete=O,NIF=Y,Truncated=N,Break=N,FirstLetter=N], desiredSize=(4000,19), CarriedOutBEndMargin=0 (ignored)
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Done column reflow pass: Infeasible :(
// Now it ends with an infeasible state, we arrive at https://github.com/mozilla/gecko-dev/blob/master/layout/generic/nsColumnSetFrame.cpp#L1111
// Then we reflow at 19 since it's the available size. But we shouldn't have tried 40 in the first place
[Child 8618: Main Thread]: D/ColumnSet FindBestBalanceBSize: KnownInfeasibleBSize=40, KnownFeasibleBSize=40
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Doing column reflow pass: mLastBalanceBSize=40, mColMaxBSize=19, RTL=0, mBalanceColCount=3, mColISize=4000, mColGap=0
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #0 0x7fe0ef551df0: availBSize=19
[00000004] nsBlockFrame:0x7fe0ef551df0 Reflow
[00000005] nsBlockFrame:0x7fe0ef551f40 Reflow
[00000005] nsBlockFrame:0x7fe0ef551f40 Reflow done
[00000004] nsBlockFrame:0x7fe0ef551df0 Reflow done
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowed child #0 0x7fe0ef551df0: status=[Complete=N,NIF=Y,Truncated=N,Break=N,FirstLetter=N], desiredSize=(4000,19), CarriedOutBEndMargin=0 (ignored)
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Next childOrigin.iCoord=4060
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #1 0x7fe0ef552398: availBSize=19
[00000004] nsBlockFrame:0x7fe0ef552398 Reflow
[00000005] nsBlockFrame:0x7fe0ef552450 Reflow
[00000005] nsBlockFrame:0x7fe0ef552450 Reflow done
[00000004] nsBlockFrame:0x7fe0ef552398 Reflow done
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowed child #1 0x7fe0ef552398: status=[Complete=N,NIF=Y,Truncated=N,Break=N,FirstLetter=N], desiredSize=(4000,19), CarriedOutBEndMargin=0 (ignored)
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Next childOrigin.iCoord=8060
// first 2 children only consume 19+19=38 height, thus the first split float `1ff8` is entirely overflow in child#1,
// child#2 drains it, but one of the split float (`2790`) in its next-in-flow chain is still in child#2's pushed-float list.
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #2 0x7fe0ef552558: availBSize=19
[00000004] nsBlockFrame:0x7fe0ef552558 Reflow
[00000004] nsBlockFrame:0x7fe0ef552558 DrainOverflowLines 0x7fe0ef551ff8 <- this one, overflow from child #1
[00000004] nsBlockFrame:0x7fe0ef552558 DrainOverflowLines: nif=0x7fe0ef5522d0
[00000004] nsBlockFrame:0x7fe0ef552558 DrainOverflowLines: nif=0x7fe0ef552790 <- this one, the third split float still in pushed float list of child#2, which violates assertion.
Assertion failure: mFloats.ContainsFrame(nif), at /home/lily/supplement/firefox-my-dev/layout/generic/nsBlockFrame.cpp:4756
* Conclusion: we should not try a size that is greater than `availableContentBSize` in the first place, it's a wasted computation at best. It will also make some previously overflow-but-not-drained frames hanging around to do bad things.
Bug 1209952 Comment 13 Edit History
Note: The actual edited comment in the bug view page will always show the original commenter’s name and original timestamp.
Ok, I will explain the oddity in the `nsColumnSetFrame` with a further reduced testcase by dropping the surrounding `div` around `dev.float-L` which causes an assertion failure (see my explanation in a comment on phabricator). So the testcase would be:
<div class="multicol-b">
<div class="step"></div>
<div class="float-L"></div>
</div>
* 1px=60 logical unit. Both `.step` and `.float-L` have 1px height, thus it's totally 2px=120. And we need at least 40 BSize to place it in 3 columns (120 = 40 * 3).
* At the beginning of the log, it's the outer multicol `.multicol-a` relowing the child #0 with `availBSize=1699`, `float-R` consumes 26px=1560, while the inner multicol has 1px border, which consumes 2*1px=120. So the `availableContentBSize` for the inner one only remains `1699 - 1560 - 120 = 19`. But because of the lack of a `std::min` in initialization, it tries to reflow `40`.
* see the inline comments in the log
// outer reflows child #0
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #0 0x7fe0ef551be8: availBSize=1699
[00000003] nsBlockFrame:0x7fe0ef551be8 Reflow
// .float-R reflows
[00000004] nsBlockFrame:0x7fe0ef551d38 Reflow
[00000004] nsBlockFrame:0x7fe0ef551d38 Reflow done
// inner reflows
[Child 8618: Main Thread]: D/ColumnSet ChooseColumnStrategy: numColumns=3, colISize=4000, expectedISizeLeftOver=0, colBSize=40, colGap=0
// at this point, we only have 19 available, why try `mColMaxBSize=40`? To exceed 19 is pointless
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Doing column reflow pass: mLastBalanceBSize=40, mColMaxBSize=40, RTL=0, mBalanceColCount=3, mColISize=4000, mColGap=0
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #0 0x7fe0ef551df0: availBSize=40
[00000004] nsBlockFrame:0x7fe0ef551df0 Reflow
[00000005] nsBlockFrame:0x7fe0ef551f40 Reflow
[00000005] nsBlockFrame:0x7fe0ef551f40 Reflow done
[00000004] nsBlockFrame:0x7fe0ef551df0 Reflow done
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowed child #0 0x7fe0ef551df0: status=[Complete=N,NIF=Y,Truncated=N,Break=N,FirstLetter=N], desiredSize=(4000,40), CarriedOutBEndMargin=0 (ignored)
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Next childOrigin.iCoord=4060
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #1 0x7fe0ef552398: availBSize=40
[00000004] nsBlockFrame:0x7fe0ef552398 Reflow
[00000005] nsBlockFrame:0x7fe0ef552450 Reflow
[00000005] nsBlockFrame:0x7fe0ef552450 Reflow done
[00000005] nsBlockFrame:0x7fe0ef551ff8 Reflow
[00000005] nsBlockFrame:0x7fe0ef551ff8 Reflow done
[00000004] nsBlockFrame 0x7fe0ef552398 SplitFloat 0x7fe0ef551ff8 -> 0x7fe0ef5522d0
[00000004] nsBlockFrame:0x7fe0ef552398 Reflow done
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowed child #1 0x7fe0ef552398: status=[Complete=O,NIF=Y,Truncated=N,Break=N,FirstLetter=N], desiredSize=(4000,40), CarriedOutBEndMargin=0 (ignored)
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Next childOrigin.iCoord=8060
// Why inconsistent height? Both child #0 and #1 have 40, #2 only has 19?
// That's because https://github.com/mozilla/gecko-dev/blob/master/layout/generic/nsColumnSetFrame.cpp#L712
// the last column is unbounded, thus it takes all avail size, which is 19, which is **smaller** than the previous columns.
// Now we take a look at float splitting state. 60+60 should be split into 3 column, thus (40)+(20+20)+(40),
// So there is already a split for 60 -> 20 + 40.
// An unfortunate consequence of the oddity here is that the 40 is further split into 40 -> 19 + 21, then
// 21 is pushed to child#2 as a pushed-float, see splitting `->` below.
// After this reflow, we end up with split float `1ff8`, `22d0`, `2790`
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #2 0x7fe0ef552558: availBSize=19
[00000004] nsBlockFrame:0x7fe0ef552558 Reflow
[00000005] nsBlockFrame:0x7fe0ef5522d0 Reflow
[00000005] nsBlockFrame:0x7fe0ef5522d0 Reflow done
-> [00000004] nsBlockFrame 0x7fe0ef552558 SplitFloat 0x7fe0ef5522d0 -> 0x7fe0ef552790
[00000004] nsBlockFrame:0x7fe0ef552558 Reflow done
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowed child #2 0x7fe0ef552558: status=[Complete=O,NIF=Y,Truncated=N,Break=N,FirstLetter=N], desiredSize=(4000,19), CarriedOutBEndMargin=0 (ignored)
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Done column reflow pass: Infeasible :(
// Now it ends with an infeasible state, we arrive at https://github.com/mozilla/gecko-dev/blob/master/layout/generic/nsColumnSetFrame.cpp#L1111
// Then we reflow at 19 since it's the available size. But we shouldn't have tried 40 in the first place
[Child 8618: Main Thread]: D/ColumnSet FindBestBalanceBSize: KnownInfeasibleBSize=40, KnownFeasibleBSize=40
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Doing column reflow pass: mLastBalanceBSize=40, mColMaxBSize=19, RTL=0, mBalanceColCount=3, mColISize=4000, mColGap=0
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #0 0x7fe0ef551df0: availBSize=19
[00000004] nsBlockFrame:0x7fe0ef551df0 Reflow
[00000005] nsBlockFrame:0x7fe0ef551f40 Reflow
[00000005] nsBlockFrame:0x7fe0ef551f40 Reflow done
[00000004] nsBlockFrame:0x7fe0ef551df0 Reflow done
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowed child #0 0x7fe0ef551df0: status=[Complete=N,NIF=Y,Truncated=N,Break=N,FirstLetter=N], desiredSize=(4000,19), CarriedOutBEndMargin=0 (ignored)
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Next childOrigin.iCoord=4060
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #1 0x7fe0ef552398: availBSize=19
[00000004] nsBlockFrame:0x7fe0ef552398 Reflow
[00000005] nsBlockFrame:0x7fe0ef552450 Reflow
[00000005] nsBlockFrame:0x7fe0ef552450 Reflow done
[00000004] nsBlockFrame:0x7fe0ef552398 Reflow done
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowed child #1 0x7fe0ef552398: status=[Complete=N,NIF=Y,Truncated=N,Break=N,FirstLetter=N], desiredSize=(4000,19), CarriedOutBEndMargin=0 (ignored)
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Next childOrigin.iCoord=8060
// first 2 children only consume 19+19=38 height, thus the first split float `1ff8` is entirely overflow in child#1,
// child#2 drains it, but one of the split float (`2790`) in its next-in-flow chain is still in child#2's pushed-float list.
[Child 8618: Main Thread]: D/ColumnSet ReflowChildren: Reflowing child #2 0x7fe0ef552558: availBSize=19
[00000004] nsBlockFrame:0x7fe0ef552558 Reflow
[00000004] nsBlockFrame:0x7fe0ef552558 DrainOverflowLines 0x7fe0ef551ff8 <- this one, overflow from child #1
[00000004] nsBlockFrame:0x7fe0ef552558 DrainOverflowLines: nif=0x7fe0ef5522d0
[00000004] nsBlockFrame:0x7fe0ef552558 DrainOverflowLines: nif=0x7fe0ef552790 <- this one, the third split float still in pushed float list of child#2, which violates assertion.
Assertion failure: mFloats.ContainsFrame(nif), at layout/generic/nsBlockFrame.cpp:4756
* Conclusion: we should not try a size that is greater than `availableContentBSize` in the first place, it's a wasted computation at best. It will also make some previously overflow-but-not-drained frames hanging around to do bad things.