Repository navigation
fix(layout): honour box-sizing: content-box - #26
leshchenko1979 wants to merge 1 commit into
Conversation
Macawls
left a comment
There was a problem hiding this comment.
Thanks, the bug is real and the e2e tests reproduce it on main. The fix is incomplete though: the conversion only happens in computeNode, but the parent's flex pass still sizes items from Style.Width / Style.Height directly (flex basis and cross size in layout/flex.go, around lines 424, 459, 702 and 765). A content-box element works as a root or a block child, but not as a flex item. Most elements here are flex items.
Repro on this branch:
<div style="display:flex;flex-direction:row">
<div style="width:200px;padding:0 50px;box-sizing:content-box;background:red;height:40px"></div>
<div style="width:50px;height:40px;background:blue"></div>
</div><rect x="0" y="0" width="300" height="40" fill="#ff0000"/>
<rect x="200" y="0" width="50" height="40" fill="#0000ff"/>
The red box draws 300px wide, but the parent allocated 200px, so the blue sibling overlaps it. The same applies on the main axis of a column container with vertical padding. Could you apply the content-box conversion wherever an item's declared size enters the flex algorithm (or resolve it once into the style before layout), and add a test with a content-box item next to a sibling in a row?
box-sizing was parsed and stored on ComputedStyle, but nothing in the layout
package ever read it: layout.Style had no field for it and computeNode always
subtracted padding and border from the declared size, i.e. the engine was
unconditionally border-box.
<div style="width:200px;padding:0 50px;box-sizing:content-box">
Ogre -> 200
Chromium -> 300
layout.Style gains ContentBox, filled from cs.BoxSizing, and borderBoxSize()
does the conversion once: the declared content size plus the padding and
border it excludes, which is the border-box model the engine uses internally.
It has to happen at every sizing site, not only in computeNode — the flex
pass reads Style.Width/Height directly for the base size, the cross size and
the fallback estimate, and computeAbsolute and both aspect-ratio paths
resolve the declared size themselves. Without that, a padded content-box item
measured 300px but occupied a 200px slot and its sibling was laid on top of
it. Under content-box the ratio describes the content box, so both ratio
paths strip the padding and border before applying it and add them back.
ContentBox is a bool rather than an enum on purpose: the zero value has to
mean the engine default (border-box, which matches Satori), and an enum would
silently make content-box the implicit default for every Style{} literal in
the codebase and in downstream users.
Verified against Chromium 151, same markup:
border-box 200 + 0 50 padding -> 200 (unchanged)
content-box 200 + 0 50 padding -> 300 (was 200)
content-box 100 + 25 0 padding -> 150 (was 100)
flex sibling after a padded item -> x 300 (was 200, overlapping)
right-anchored absolute content-box -> x 280 (was 300)
aspect-ratio 2, width 300 -> h 100 (was 150)
Pixel diff against Chromium for the content-box case dropped from 1.09% to
0.00%.
Red tests, one per site, before the fix:
sib: got (200.0, ...), want (300.0, ...)
sib: got (0.0, 100.0, ...), want (0.0, 150.0, ...)
abs: got (300.0, ...), want (280.0, ...)
child: got (..., 300.0, 150.0), want (..., 300.0, 100.0)
Fixes Macawls#23
Refs Macawls#26
7dc7eb5 to
efd1d5d
Compare
|
You were right: the conversion was in I audited every place that resolves a declared size and found five, not one:
Verified against Chromium 151, same markup:
Red tests, one per site, before the fix: Two notes on scope, so you can judge them rather than discover them:
Branch rebuilt on current |
box-sizing was parsed and stored on ComputedStyle, but nothing in the layout
package ever read it: layout.Style had no field for it and computeNode always
subtracted padding and border from the declared size, i.e. the engine was
unconditionally border-box.
<div style="width:200px;padding:0 50px;box-sizing:content-box">
Ogre -> 200
Chromium -> 300
layout.Style gains ContentBox, filled from cs.BoxSizing, and borderBoxSize()
does the conversion once: the declared content size plus the padding and
border it excludes, which is the border-box model the engine uses internally.
It has to happen at every sizing site, not only in computeNode — the flex
pass reads Style.Width/Height directly for the base size, the cross size and
the fallback estimate, and computeAbsolute and both aspect-ratio paths
resolve the declared size themselves. Without that, a padded content-box item
measured 300px but occupied a 200px slot and its sibling was laid on top of
it. Under content-box the ratio describes the content box, so both ratio
paths strip the padding and border before applying it and add them back.
ContentBox is a bool rather than an enum on purpose: the zero value has to
mean the engine default (border-box, which matches Satori), and an enum would
silently make content-box the implicit default for every Style{} literal in
the codebase and in downstream users.
Verified against Chromium 151, same markup:
border-box 200 + 0 50 padding -> 200 (unchanged)
content-box 200 + 0 50 padding -> 300 (was 200)
content-box 100 + 25 0 padding -> 150 (was 100)
flex sibling after a padded item -> x 300 (was 200, overlapping)
right-anchored absolute content-box -> x 280 (was 300)
aspect-ratio 2, width 300 -> h 100 (was 150)
Pixel diff against Chromium for the content-box case dropped from 1.09% to
0.00%.
Red tests, one per site, before the fix:
sib: got (200.0, ...), want (300.0, ...)
sib: got (0.0, 100.0, ...), want (0.0, 150.0, ...)
abs: got (300.0, ...), want (280.0, ...)
child: got (..., 300.0, 150.0), want (..., 300.0, 100.0)
Fixes Macawls#23
Refs Macawls#26
Problem
box-sizing: content-boxis parsed and stored onComputedStyle, but the layout package never reads it:layout.Stylehas no field for it andcomputeNodealways subtracts padding and border from the declared size. The engine is unconditionallyborder-box.Reproduced on
main(32f3fad):Cause
style.BoxSizingexists and is populated, butgrep -r BoxSizing layout/returns nothing — there is no consumer.layout.Stylehas no corresponding field, somapStylehad nothing to map it to, andcomputeNode(layout/flex.go:71) always did:Fix
layout.StylegainsContentBox bool, filled fromcs.BoxSizing == style.BoxSizingContentBoxinmapStyle.computeNodeadds padding and border back on top of the declared width and height whenContentBoxis set. The engine keepsborder-boxas its internal model and the conversion happens exactly once, at the point where the declared size enters layout.ContentBoxis a bool, not an enum, on purpose: the zero value must mean the engine default (border-box, Satori-compatible), and an enum would silently makecontent-boxthe implicit default for everyStyle{}literal in the codebase and for downstream users of the package.Verification
Against Chromium, same markup, 1280×720:
border-box,width:200,padding:0 50pxcontent-box,width:200,padding:0 50pxcontent-box,height:100,padding:25px 0Pixel diff against Chromium for the
content-boxcase: 1.09% → 0.00%.The default case (
box-sizingnot declared) is deliberately left asborder-box— that is the documented Satori-compatible behaviour and changing it would break the existing golden tests.Tests added:
layout/layout_test.go— content-box adds padding, adds border, works without padding; border-box keeps the declared width.ogre_box_sizing_e2e_test.go— through the publicRender: horizontal and vertical conversion.go build ./...,go vet ./...andgo test ./...pass.Fixes #23