Repository navigation
feat(core): apply box-sizing and position: relative - #36
Conversation
…stale block rects - A first block styled only with a background or border lost its block-start fragment, which carries the decoration, and painted nothing. - Canvas gradients painted nothing anywhere: the fill paint's alpha was 0 and a shader is multiplied by it. - _blockDecorations was not cleared when line layout re-ran, so a details toggle kept painting the block's previous rect.
…33) box-sizing was never parsed. Add ComputedStyle.boxSizing (not inherited) and apply border-box to width / min-width / max-width, including %, on in-flow canvas blocks. min-width:% was also unread on this path and now applies. The painted background ignored width and always spanned the containing block, so a correct line width alone would not have changed what the box looks like. The rect now follows the resolved width. Borders still do not reserve layout space; they only widen the painted box.
position and its offsets were never parsed. A post-layout pass moves each relative element's fragments and block decoration, including those of relative ancestors, leaving sibling layout and box height as they were. Moved fragments stay on their original line, so hit-testing, character lookup, getBoxesForCharRange and text painting now check them separately. Flex and grid items use Transform.translate. While any content is moved the box clips its painting to its own bounds. The sanitizer doesn't filter CSS properties, so without this untrusted HTML could draw over the host app. Offsets take lengths only; % is not supported.
The docs called both "parsed, not applied", but neither was parsed. Record what now applies and the limits, and pin both in the CSS execution guard.
|
| Fixture | Budget (ms) | Median (ms) | P95 (ms) | |
|---|---|---|---|---|
| ❌ | simple_paragraph |
8 | 15 | 22 |
| ❌ | mixed_inline |
10 | 13 | 15 |
| ❌ | float_layout |
12 | 17 | 21 |
| ❌ | table_20_rows |
14 | 36 | 62 |
| ✅ | cjk_ruby |
14 | 9 | 10 |
| ❌ | large_article |
16 | 44 | 76 |
One or more fixtures exceeded the 16 ms budget.
Flutter
3.41.5· ubuntu-22.04
Informational, does not block merge: CI runners use software
rendering and are 2-3x slower than the devices the budgets are
calibrated for. Compare against the base branch; if a fixture
regressed relative to it, profile with:
flutter test benchmark/layout_regression.dart --reporter expandedand check _performLineLayout / _buildCharacterMapping for
any new O(N²) or O(N log N) paths introduced in this PR.
…gins Block margins now show more, since the background follows width.
|
| Fixture | Budget (ms) | Median (ms) | P95 (ms) | |
|---|---|---|---|---|
| ❌ | simple_paragraph |
8 | 22 | 27 |
| ❌ | mixed_inline |
10 | 18 | 20 |
| ❌ | float_layout |
12 | 21 | 27 |
| ❌ | table_20_rows |
14 | 49 | 78 |
| ✅ | cjk_ruby |
14 | 14 | 20 |
| ❌ | large_article |
16 | 53 | 84 |
One or more fixtures exceeded the 16 ms budget.
Flutter
3.41.5· ubuntu-22.04
Informational, does not block merge: CI runners use software
rendering and are 2-3x slower than the devices the budgets are
calibrated for. Compare against the base branch; if a fixture
regressed relative to it, profile with:
flutter test benchmark/layout_regression.dart --reporter expandedand check _performLineLayout / _buildCharacterMapping for
any new O(N²) or O(N log N) paths introduced in this PR.
- Clip escapes: flex/grid items and moved block backgrounds (empty blocks, or a parent whose child cancels its offset) could paint over the host app. The clip only looked at moved fragments. - Moved content now paints in its own clipped pass, above normal content, so one moved span no longer crops unrelated overflow such as a box-shadow. - Double drawing: fragments the line breaker replaced were moved and drawn too, and list markers were drawn twice. Only line fragments are drawn now. - Drift: a fragment line layout doesn't re-place gained the shift on every relayout. Shifts are undone before line layout. - em offsets used the root font size, not the element's. - Shifts are collected walking down the tree. Building a nested box re-parents shared nodes, so a walk up missed relative ancestors of a float's text. - An outer inline background followed a relative child off its line. - Flex and grid items ignored the writing direction, and HyperViewer dropped textDirection in sync mode.
The root barrel re-exports the new HyperBoxSizing, so root must require a core that has it.
|
| Fixture | Budget (ms) | Median (ms) | P95 (ms) | |
|---|---|---|---|---|
| ❌ | simple_paragraph |
8 | 17 | 26 |
| ❌ | mixed_inline |
10 | 15 | 16 |
| ❌ | float_layout |
12 | 16 | 21 |
| ❌ | table_20_rows |
14 | 34 | 45 |
| ✅ | cjk_ruby |
14 | 9 | 10 |
| ❌ | large_article |
16 | 40 | 79 |
One or more fixtures exceeded the 16 ms budget.
Flutter
3.41.5· ubuntu-22.04
Informational, does not block merge: CI runners use software
rendering and are 2-3x slower than the devices the budgets are
calibrated for. Compare against the base branch; if a fixture
regressed relative to it, profile with:
flutter test benchmark/layout_regression.dart --reporter expandedand check _performLineLayout / _buildCharacterMapping for
any new O(N²) or O(N log N) paths introduced in this PR.
Closes #33, closes #34.
What changes
box-sizing:border-boxapplies towidth/min-width/max-width, including%, on in-flow blocks.position: relative:top/right/bottom/left(lengths) move blocks, inline spans and flex/grid items. Sibling layout and box height stay as they were, and hit-testing and selection follow the moved text.Rendering changes to review
These affect existing documents:
width. They used to span the whole container.min-width: %now applies on blocks.Bugs fixed along the way (first commit):
A first block styled only with a background or border painted nothing.
Gradients on canvas blocks and inline spans painted nothing: the fill paint's alpha was 0.
_blockDecorationswasn't cleared when line layout re-ran, so a<details>toggle left the old rect painted.HyperViewer(textDirection:)and an ambient RTLDirectionalitynow apply in sync mode. They were dropped there, while virtualized and paged modes honoured them.Decisions
z-index).doc/LIMITATIONS.md.HyperBoxSizing. Nothing is published yet.Testing
test/box_sizing_test.dart,test/position_relative_test.dart,test/first_block_decoration_test.dart. They measure fragment geometry, raw pixels and, through a recording canvas, how many times text is drawn.devtools_uiChrome-only test wasn't run.main(platform differences), so I compared against a baseline generated locally frommain: no differences. Thegolden.ymlrun on this PR is the authoritative check.Not proven by a test: per-line paint culling doesn't take effect in a widget test, because
RenderHyperBoxis a repaint boundary and its canvas clip is unbounded. The separate paint pass for moved text is tested; its culling isn't.