@react-x11/components 0.14.1 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/code-editor/node.d.ts +13 -0
  2. package/dist/code-editor/node.d.ts.map +1 -1
  3. package/dist/code-editor/node.js +23 -5
  4. package/dist/code-editor/node.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +171 -26
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +616 -216
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +25 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +307 -19
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/content.d.ts +11 -6
  14. package/dist/html/css/content.d.ts.map +1 -1
  15. package/dist/html/css/content.js +171 -12
  16. package/dist/html/css/content.js.map +1 -1
  17. package/dist/html/css/parse.d.ts.map +1 -1
  18. package/dist/html/css/parse.js +62 -9
  19. package/dist/html/css/parse.js.map +1 -1
  20. package/dist/html/css/shapes.d.ts +18 -0
  21. package/dist/html/css/shapes.d.ts.map +1 -1
  22. package/dist/html/css/shapes.js +21 -1
  23. package/dist/html/css/shapes.js.map +1 -1
  24. package/dist/html/css/style.d.ts +42 -5
  25. package/dist/html/css/style.d.ts.map +1 -1
  26. package/dist/html/css/style.js +74 -12
  27. package/dist/html/css/style.js.map +1 -1
  28. package/dist/html/css/ua.js +27 -8
  29. package/dist/html/css/ua.js.map +1 -1
  30. package/dist/html/css/values.d.ts +5 -0
  31. package/dist/html/css/values.d.ts.map +1 -1
  32. package/dist/html/css/values.js +18 -9
  33. package/dist/html/css/values.js.map +1 -1
  34. package/dist/html/index.d.ts.map +1 -1
  35. package/dist/html/index.js +7 -0
  36. package/dist/html/index.js.map +1 -1
  37. package/dist/html/layout/block.d.ts +29 -9
  38. package/dist/html/layout/block.d.ts.map +1 -1
  39. package/dist/html/layout/block.js +141 -52
  40. package/dist/html/layout/block.js.map +1 -1
  41. package/dist/html/layout/boxes.d.ts +29 -2
  42. package/dist/html/layout/boxes.d.ts.map +1 -1
  43. package/dist/html/layout/boxes.js +172 -23
  44. package/dist/html/layout/boxes.js.map +1 -1
  45. package/dist/html/layout/css-grid.d.ts.map +1 -1
  46. package/dist/html/layout/css-grid.js +11 -9
  47. package/dist/html/layout/css-grid.js.map +1 -1
  48. package/dist/html/layout/flex.d.ts.map +1 -1
  49. package/dist/html/layout/flex.js +1759 -238
  50. package/dist/html/layout/flex.js.map +1 -1
  51. package/dist/html/layout/inline.d.ts +15 -3
  52. package/dist/html/layout/inline.d.ts.map +1 -1
  53. package/dist/html/layout/inline.js +64 -19
  54. package/dist/html/layout/inline.js.map +1 -1
  55. package/dist/html/layout/table.d.ts +27 -1
  56. package/dist/html/layout/table.d.ts.map +1 -1
  57. package/dist/html/layout/table.js +886 -324
  58. package/dist/html/layout/table.js.map +1 -1
  59. package/dist/html/node.d.ts +35 -4
  60. package/dist/html/node.d.ts.map +1 -1
  61. package/dist/html/node.js +122 -26
  62. package/dist/html/node.js.map +1 -1
  63. package/dist/html/paint.d.ts.map +1 -1
  64. package/dist/html/paint.js +558 -178
  65. package/dist/html/paint.js.map +1 -1
  66. package/dist/html/resources.js +14 -1
  67. package/dist/html/resources.js.map +1 -1
  68. package/dist/html/svg.d.ts +23 -7
  69. package/dist/html/svg.d.ts.map +1 -1
  70. package/dist/html/svg.js +264 -59
  71. package/dist/html/svg.js.map +1 -1
  72. package/dist/html/widgets.d.ts.map +1 -1
  73. package/dist/html/widgets.js +57 -4
  74. package/dist/html/widgets.js.map +1 -1
  75. package/package.json +3 -3
  76. package/src/code-editor/node.ts +24 -5
  77. package/src/html/css/cascade.ts +697 -221
  78. package/src/html/css/color.ts +345 -18
  79. package/src/html/css/content.ts +173 -15
  80. package/src/html/css/parse.ts +60 -8
  81. package/src/html/css/shapes.ts +35 -1
  82. package/src/html/css/style.ts +122 -17
  83. package/src/html/css/ua.ts +27 -8
  84. package/src/html/css/values.ts +23 -9
  85. package/src/html/index.ts +7 -0
  86. package/src/html/layout/block.ts +149 -58
  87. package/src/html/layout/boxes.ts +211 -27
  88. package/src/html/layout/css-grid.ts +12 -9
  89. package/src/html/layout/flex.ts +1975 -231
  90. package/src/html/layout/inline.ts +70 -16
  91. package/src/html/layout/table.ts +961 -332
  92. package/src/html/node.ts +155 -27
  93. package/src/html/paint.ts +692 -234
  94. package/src/html/resources.ts +13 -1
  95. package/src/html/svg.ts +299 -56
  96. package/src/html/widgets.ts +61 -4
@@ -30,6 +30,7 @@ import {
30
30
  CONTAIN_LAYOUT,
31
31
  CONTAIN_PAINT,
32
32
  CONTAIN_SIZE,
33
+ INLINE_BEFORE_ABSOLUTE,
33
34
  scrolls,
34
35
  } from '../css/style.js';
35
36
  import { about, linearOf, matrixOf, transformed } from '../css/transform.js';
@@ -71,6 +72,7 @@ import type {
71
72
  } from './boxes.js';
72
73
  import { FloatContext } from './floats.js';
73
74
  import {
75
+ emptyLinePoint,
74
76
  faceExtentOf,
75
77
  isButton,
76
78
  joinsBefore,
@@ -83,7 +85,7 @@ import {
83
85
  import type { FontsLike, InlineOptions, InlineResult } from './inline.js';
84
86
  import type { TextRun } from '../../richtext/index.js';
85
87
  import { layoutFlex } from './flex.js';
86
- import { finishCaptions, layoutTable } from './table.js';
88
+ import { finishCaptions, givenHeight, layoutTable } from './table.js';
87
89
  import { collapseEdges } from './collapse.js';
88
90
  import {
89
91
  clipsFor,
@@ -132,7 +134,8 @@ export interface LayoutContext {
132
134
  * its padding, its margins and its width limits are of (CSS 2.1 8.3,
133
135
  * 8.4, 10.4). Left out, `width` is both — a box probed at the room there
134
136
  * is for it. For a table cell it is the width of its row, which its
135
- * padding's percentages are of.
137
+ * padding's percentages are of, and it says the cell is laid out in its
138
+ * columns: at `width`, whatever its limits say.
136
139
  */
137
140
  layoutSubtree(box: Box, width: number, containing?: number): void;
138
141
  /** How many flex boxes' Yoga passes are running, one inside the last's
@@ -843,13 +846,19 @@ function layoutChildren(
843
846
  marked = null;
844
847
  }
845
848
  if (child.outOfFlow) {
846
- placeStatic(
847
- child,
848
- box,
849
- contentLeft,
850
- contentWidth,
851
- y + marginOf(pendingMargin),
852
- );
849
+ const top = y + marginOf(pendingMargin);
850
+ if (INLINE_BEFORE_ABSOLUTE.has(child.style)) {
851
+ // one that was inline-level is on a line of its own, beside the
852
+ // floats there, where the line's alignment puts what it holds
853
+ const band = floats.bandAt(
854
+ top,
855
+ 1,
856
+ contentLeft,
857
+ contentLeft + contentWidth,
858
+ );
859
+ const at = emptyLinePoint(box.style, band.left, band.right);
860
+ placeStatic(child, box, at, 0, top);
861
+ } else placeStatic(child, box, contentLeft, contentWidth, top);
853
862
  ctx.positioned.push({
854
863
  box: child,
855
864
  containing: containingBlockFor(child) ?? box,
@@ -1670,43 +1679,65 @@ export function measureIntrinsicWidth(
1670
1679
  available: number,
1671
1680
  content = false,
1672
1681
  ): number {
1673
- ctx.layoutSubtree(box, available);
1682
+ // not through `ctx.layoutSubtree`, which is this and one frame more on
1683
+ // the stack for every level of a document that measures what it holds
1684
+ // (`layoutTable`)
1685
+ layoutSubtree(box, ctx, available, undefined, true);
1674
1686
  const specified = box.style.width;
1675
1687
  if (!content && specified !== AUTO && Number.isFinite(box.width)) {
1676
1688
  return box.width;
1677
1689
  }
1678
- return intrinsicWidth(box) + box.horizontalExtra;
1690
+ return (
1691
+ intrinsicWidth(box, undefined, available === MIN_CONTENT_PROBE) +
1692
+ box.horizontalExtra
1693
+ );
1679
1694
  }
1680
1695
 
1681
1696
  /**
1682
1697
  * Lay a box and everything under it out at a width, at the origin — what
1683
- * `LayoutContext.layoutSubtree` hands to `flex.ts` and `table.ts`, and what
1684
- * the shrink-to-fit probe uses. A replaced box is sized rather than laid out,
1685
- * because there is nothing inside it to lay out.
1698
+ * `LayoutContext.layoutSubtree` hands to `flex.ts` and `css-grid.ts`, and
1699
+ * what the shrink-to-fit probe and a table's cells use. A replaced box is
1700
+ * sized rather than laid out, because there is nothing inside it to lay out.
1686
1701
  */
1687
- function layoutSubtree(
1702
+ export function layoutSubtree(
1688
1703
  box: Box,
1689
1704
  ctx: LayoutContext,
1690
1705
  width: number,
1691
1706
  /** Its containing block's width, where `width` is the box's own. */
1692
- containing = width,
1707
+ containing?: number,
1708
+ /** Whether this lays it out to measure it (`measureIntrinsicWidth`). */
1709
+ measuring = false,
1693
1710
  ): void {
1694
- resolveEdges(box, Number.isFinite(containing) ? containing : 0);
1711
+ const base = containing ?? width;
1712
+ resolveEdges(box, Number.isFinite(base) ? base : 0);
1695
1713
  if (box.kind === 'replaced') {
1696
1714
  sizeReplaced(box, width);
1697
1715
  box.x = 0;
1698
1716
  box.y = 0;
1699
1717
  return;
1700
1718
  }
1701
- // within its limits, a percentage among them of its containing block's
1719
+ // Within its limits, a percentage among them of its containing block's
1702
1720
  // width and not of its own: a flex item `max-width: 50%` that the flex
1703
- // layout made half its row was laid out at a quarter of it. A table
1704
- // cell's is of nothing, and ignored: what a percentage limit does to a
1705
- // cell it does where its column is sized (CSS Tables 3, 3.8.2), and a
1706
- // cell is laid out in the width its columns came to.
1707
- const borderBox = Number.isFinite(width)
1708
- ? clampWidth(box, width, box.kind === 'table-cell' ? NaN : containing)
1709
- : Infinity;
1721
+ // layout made half its row was laid out at a quarter of it. An
1722
+ // intrinsic limit is among them, as it is in a block's flow: without it,
1723
+ // a flex or grid item `min-width: max-content` that its layout made as
1724
+ // wide as its content was laid out at a smaller `max-width`, where the
1725
+ // minimum wins (CSS Sizing 3, 3.1). Not where the box is laid out to be
1726
+ // measured, since that is what an intrinsic limit is measured from.
1727
+ //
1728
+ // A table cell laid out in its columns, which hand it its row's width,
1729
+ // is as wide as they are (CSS Tables 3, 3.10.2): its limits are theirs
1730
+ // to weigh (3.8.2), and have been. Clamped again, `td { max-width:
1731
+ // 50px }` in a column 200 wide set its text in 50 of it. Probed for
1732
+ // what it asks of them, at none, it keeps within its length limits as
1733
+ // any box does; a percentage one is of nothing and does not hold it.
1734
+ let borderBox = Infinity;
1735
+ if (Number.isFinite(width)) {
1736
+ if (box.kind !== 'table-cell') {
1737
+ borderBox = clampWidth(box, width, base, measuring ? undefined : ctx);
1738
+ } else if (containing !== undefined) borderBox = width;
1739
+ else borderBox = clampWidth(box, width, NaN);
1740
+ }
1710
1741
  layoutInternals(box, ctx, borderBox, 0, 0);
1711
1742
  }
1712
1743
 
@@ -2110,12 +2141,12 @@ function layoutMarker(box: Box, marker: Marker, ctx: LayoutContext): void {
2110
2141
  const style = box.style;
2111
2142
  if (marker.image) {
2112
2143
  // an image: its bottom on the first line's baseline, as an inline
2113
- // image's is, and the gap a bullet has before the content
2144
+ // image's is, and its own gap before the content (`MARKER_IMAGE_GAP`)
2114
2145
  const first = firstLineIn(box);
2115
2146
  marker.y = first
2116
2147
  ? first.y + first.baseline - marker.image.height
2117
2148
  : box.contentY;
2118
- const gap = Math.round(style.fontSize * 0.4);
2149
+ const { gap } = marker.image;
2119
2150
  // at the start of the line, which is its right in a right-to-left item
2120
2151
  marker.x =
2121
2152
  style.direction === 'rtl'
@@ -2150,9 +2181,13 @@ function layoutMarker(box: Box, marker: Marker, ctx: LayoutContext): void {
2150
2181
  });
2151
2182
  marker.layout = layout;
2152
2183
  // a space between it and the content — the one its style's suffix ends
2153
- // in, or its own text's — in its own face: a line measures without it
2154
- const gap =
2155
- marker.flush && !/\s$/.test(marker.text) ? 0 : markerGap(fonts, face);
2184
+ // in, or its own text's — in its own face: a line measures without the
2185
+ // spaces it ends in, and with a no-break space, which `content: "1.\a0"`
2186
+ // writes and which was a second gap on top of its own
2187
+ const trailing = marker.flush
2188
+ ? (/[ \t\n\r\f]*$/.exec(marker.text)?.[0].length ?? 0)
2189
+ : 1;
2190
+ const gap = trailing ? trailing * markerGap(fonts, face) : 0;
2156
2191
  // The marker sits on the first line of the item's *content*, which is not
2157
2192
  // always the item's own: an `<li>` holding a paragraph, or one holding text
2158
2193
  // and a nested list, has its inline content in an anonymous block. Looking
@@ -2341,13 +2376,16 @@ export const FLEXED_HEIGHT = new WeakMap<Box, number>();
2341
2376
  export const USED_HEIGHT = new WeakMap<Box, number>();
2342
2377
 
2343
2378
  /**
2344
- * A table a grid stretches across its area, whose `auto` width is then the
2345
- * one it is laid out at. `normal` stretches any grid item but a replaced
2346
- * one (CSS Grid 1, 6.2), a table among them, where in a block's flow a
2347
- * table's `auto` width is its columns' (CSS 2.1 17.5.2; Blink leaves tables
2348
- * out of the boxes a block stretches, `space_utils.cc`, and stretches them
2349
- * in a grid, `grid_item.cc`). Set by `css-grid.ts` for the item's own
2350
- * layout alone, and never while its widths are measured.
2379
+ * A table a grid or a flex layout gives its width, whose `auto` width is
2380
+ * then the one it is laid out at. `normal` stretches any grid item but a
2381
+ * replaced one across its area (CSS Grid 1, 6.2), and a flex item across a
2382
+ * column's line (CSS Flexbox 9.4, step 11), a table among them; and a flex
2383
+ * item along a row is the size the line flexed it to (9.7) — where in a
2384
+ * block's flow a table's `auto` width is its columns' (CSS 2.1 17.5.2;
2385
+ * Blink leaves tables out of the boxes a block stretches, `space_utils.cc`,
2386
+ * and stretches them in a grid, `grid_item.cc`, and a flex box). Set by
2387
+ * `css-grid.ts` and `flex.ts` for the item's own layout alone, and never
2388
+ * while its widths are measured.
2351
2389
  */
2352
2390
  export const STRETCHED_ACROSS = new WeakSet<Box>();
2353
2391
 
@@ -2539,6 +2577,14 @@ function finishHeight(box: Box, contentHeight: number): void {
2539
2577
  if (box.kind === 'table-cell') {
2540
2578
  CELL_CONTENT.set(box, contentHeight + box.verticalExtra);
2541
2579
  }
2580
+ // A table a flex or grid layout gave a height is as tall as that, or as
2581
+ // its rows where they need more, which `layoutTable` has shared it out
2582
+ // among — whatever height it has of its own, which the flex layout
2583
+ // started from (`layoutTable`)
2584
+ if (box.kind === 'table' && Number.isFinite(givenHeight(box))) {
2585
+ box.height = contentHeight + box.verticalExtra;
2586
+ return;
2587
+ }
2542
2588
  const set = specifiedHeight(box);
2543
2589
  // at least zero: a `calc()` may come to less
2544
2590
  const specified = set === null ? null : Math.max(0, set);
@@ -2608,7 +2654,9 @@ export function clampHeight(box: Box, height: number): number {
2608
2654
  out,
2609
2655
  min + (box.style.boxSizing === 'border-box' ? 0 : box.verticalExtra),
2610
2656
  );
2611
- return Math.max(0, out);
2657
+ // no shorter than its padding and borders, as `clampWidth` is no
2658
+ // narrower
2659
+ return Math.max(box.verticalExtra, out);
2612
2660
  }
2613
2661
 
2614
2662
  /** Place a block-level box horizontally, honouring `margin: auto`. */
@@ -2828,7 +2876,10 @@ export function clampWidth(
2828
2876
  keywordWidth(box, ctx, style.minWidthKeyword, containingWidth, room),
2829
2877
  );
2830
2878
  }
2831
- return Math.max(0, out);
2879
+ // and no narrower than its padding and borders: a `border-box` length
2880
+ // less than them leaves its content box none wide, and not less (CSS
2881
+ // Sizing 3, 3.3)
2882
+ return Math.max(box.horizontalExtra, out);
2832
2883
  }
2833
2884
 
2834
2885
  /** An intrinsic `min-width` or `max-width` in the room a box's margins
@@ -2870,10 +2921,8 @@ export function contentSizedWidth(
2870
2921
  // the content's sizes, whatever width the box has of its own: a
2871
2922
  // `min-width: max-content` beside a `width` is its content's widest,
2872
2923
  // and a probe of the box at no width answered the probe's width
2873
- let probed = false;
2874
2924
  if (box.intrinsicMaxContent < 0) {
2875
2925
  box.intrinsicMaxContent = measureIntrinsicWidth(box, ctx, Infinity, true);
2876
- probed = true;
2877
2926
  }
2878
2927
  if (size !== 'max-content' && box.intrinsicMinContent < 0) {
2879
2928
  box.intrinsicMinContent = measureIntrinsicWidth(
@@ -2882,12 +2931,19 @@ export function contentSizedWidth(
2882
2931
  MIN_CONTENT_PROBE,
2883
2932
  true,
2884
2933
  );
2885
- probed = true;
2886
2934
  }
2887
- if (probed) resolveEdges(box, percentBase);
2888
- const max = box.intrinsicMaxContent;
2935
+ // The probes resolved a percentage of the box's padding or border
2936
+ // against nothing, which is how the sizes are kept, whatever the width
2937
+ // around the box: what one comes to against `percentBase` goes on top.
2938
+ // Left out, `width: max-content; padding-left: 10%` was as wide as its
2939
+ // content, the padding inside it.
2940
+ resolveEdges(box, 0);
2941
+ const bare = box.horizontalExtra;
2942
+ resolveEdges(box, Number.isFinite(percentBase) ? percentBase : 0);
2943
+ const extra = box.horizontalExtra - bare;
2944
+ const max = box.intrinsicMaxContent + extra;
2889
2945
  if (size === 'max-content') return max;
2890
- const min = box.intrinsicMinContent;
2946
+ const min = box.intrinsicMinContent + extra;
2891
2947
  if (size === 'min-content') return min;
2892
2948
  return Math.min(
2893
2949
  max,
@@ -3039,6 +3095,10 @@ const PREFERRED = new WeakMap<Box, number>();
3039
3095
  * or is indented, a block with widths of its own.
3040
3096
  */
3041
3097
  export function exactMinContent(box: Box, fonts: FontsLike): number | null {
3098
+ // a flex row's is its items' side by side, a grid's its columns', and a
3099
+ // replaced element has no words: read as its widest word, a row of two
3100
+ // in a flex column's item was as narrow as the wider of them
3101
+ if (box.kind !== 'block' && box.kind !== 'table-cell') return null;
3042
3102
  if (hasWidths(box.style) || contained(box, CONTAIN_WIDTH)) return null;
3043
3103
  const inner = exactWords(box, fonts);
3044
3104
  return inner === null ? null : inner + box.horizontalExtra;
@@ -3172,8 +3232,17 @@ function wordBound(box: Box, fonts: FontsLike, measured: boolean): number {
3172
3232
  * `seen.cut` is set where a line it measured was cut short by
3173
3233
  * `text-overflow`, in a box with a width to cut it at: the line is wider
3174
3234
  * than it was drawn, and the answer is of what was drawn.
3235
+ *
3236
+ * `narrowest` says the box was laid out by the min-content probe
3237
+ * (`MIN_CONTENT_PROBE`), and the answer is its min-content width: the
3238
+ * probe lays a box out at no width, but one with a least width of its own
3239
+ * at that, and nothing in the layout tells the two apart.
3175
3240
  */
3176
- export function intrinsicWidth(box: Box, seen?: { cut: boolean }): number {
3241
+ export function intrinsicWidth(
3242
+ box: Box,
3243
+ seen?: { cut: boolean },
3244
+ narrowest = false,
3245
+ ): number {
3177
3246
  // under size containment, as though it held nothing: the size
3178
3247
  // `contain-intrinsic-size` gives it, or none (CSS Containment 2, 3.2)
3179
3248
  if (contained(box, CONTAIN_WIDTH)) {
@@ -3207,11 +3276,17 @@ export function intrinsicWidth(box: Box, seen?: { cut: boolean }): number {
3207
3276
  }
3208
3277
  let widest = 0;
3209
3278
  // A row of flex items is as wide as all of them side by side, and gaps
3210
- // between them; any other box is as wide as the widest thing in it
3279
+ // between them; any other box is as wide as the widest thing in it. So
3280
+ // is a row that wraps, at its narrowest: each of its items may be a line
3281
+ // of its own, and it is as wide as the widest of them (CSS Flexbox
3282
+ // 9.9.1). Summed, a wrapping row of three 30px items made a
3283
+ // `width: min-content` box around it 90 wide, the items side by side,
3284
+ // where Chrome has 30, one under another
3211
3285
  const display = box.style.display;
3212
3286
  const row =
3213
3287
  (display === 'flex' || display === 'inline-flex') &&
3214
3288
  box.style.flexDirection.startsWith('row');
3289
+ const wraps = row && narrowest && box.style.flexWrap !== 'nowrap';
3215
3290
  let total = 0;
3216
3291
  let items = 0;
3217
3292
  const lines = box.lines;
@@ -3279,7 +3354,8 @@ export function intrinsicWidth(box: Box, seen?: { cut: boolean }): number {
3279
3354
  contribution = Math.max(contribution, stated + margins);
3280
3355
  }
3281
3356
  } else {
3282
- let inner = intrinsicWidth(child, seen) + child.horizontalExtra;
3357
+ let inner =
3358
+ intrinsicWidth(child, seen, narrowest) + child.horizontalExtra;
3283
3359
  if (typeof style.maxWidth === 'number') {
3284
3360
  inner = Math.min(inner, style.maxWidth + contentExtra(child));
3285
3361
  }
@@ -3291,10 +3367,10 @@ export function intrinsicWidth(box: Box, seen?: { cut: boolean }): number {
3291
3367
  across = Math.max(across, contribution);
3292
3368
  continue;
3293
3369
  }
3294
- if (row) {
3370
+ if (row && !wraps) {
3295
3371
  total += contribution;
3296
3372
  items += 1;
3297
- } else if (!sideBySide) widest = Math.max(widest, contribution);
3373
+ } else if (wraps || !sideBySide) widest = Math.max(widest, contribution);
3298
3374
  else if (child.isFloat) {
3299
3375
  const clear = style.clear;
3300
3376
  if (clear !== 'none') {
@@ -3426,8 +3502,17 @@ function sizeReplaced(
3426
3502
  const ih = own.missing & 2 ? null : own.height;
3427
3503
  // `aspect-ratio` over its own, unless written `auto` and it has one
3428
3504
  const aspect = style.aspectRatio;
3429
- const ratio =
3430
- aspect && !(aspect.auto && own.ratio > 0) ? aspect.ratio : own.ratio;
3505
+ const pages = aspect !== null && !(aspect.auto && own.ratio > 0);
3506
+ const ratio = pages ? aspect.ratio : own.ratio;
3507
+ // A ratio of the page's is of the box `box-sizing` names (CSS Sizing 4,
3508
+ // 5.1): one axis's content size to the other's goes through the border
3509
+ // box there. Written with `auto`, it is of the content box, as the
3510
+ // replaced element's own is, whichever it is.
3511
+ const outer = pages && !aspect.auto && style.boxSizing === 'border-box';
3512
+ const ox = outer ? box.horizontalExtra : 0;
3513
+ const oy = outer ? box.verticalExtra : 0;
3514
+ const widthOf = (h: number): number => Math.max(0, (h + oy) * ratio - ox);
3515
+ const heightOf = (w: number): number => Math.max(0, (w + ox) / ratio - oy);
3431
3516
  // the width a block would have here: what a ratio with no size fills, and
3432
3517
  // an `hr`, whose whole appearance is its border across the line
3433
3518
  const room = (): number => {
@@ -3443,16 +3528,16 @@ function sizeReplaced(
3443
3528
  // which moves both axes together
3444
3529
  if (iw !== null) {
3445
3530
  w = iw;
3446
- h = ih ?? iw / ratio;
3531
+ h = ih ?? heightOf(iw);
3447
3532
  } else if (ih !== null) {
3448
3533
  h = ih;
3449
- w = ih * ratio;
3534
+ w = widthOf(ih);
3450
3535
  } else {
3451
3536
  // a ratio and no size: CSS 2.1 leaves it undefined and suggests the
3452
3537
  // width a block would have, which is what browsers do — or, where the
3453
3538
  // containing block waits on this box, the default object size
3454
3539
  w = Number.isFinite(containingWidth) ? room() : own.width;
3455
- h = w / ratio;
3540
+ h = heightOf(w);
3456
3541
  }
3457
3542
  [w, h] = constrained(w, h, minW, maxW, minH, maxH);
3458
3543
  } else {
@@ -3461,12 +3546,12 @@ function sizeReplaced(
3461
3546
  // again" for a limit)
3462
3547
  const usedHeight = height === null ? null : clamp(height, minH, maxH);
3463
3548
  if (width !== null) w = width;
3464
- else if (usedHeight !== null && ratio > 0) w = usedHeight * ratio;
3549
+ else if (usedHeight !== null && ratio > 0) w = widthOf(usedHeight);
3465
3550
  else if (box.replaced === 'hr') w = room();
3466
3551
  else w = own.width;
3467
3552
  w = clamp(w, minW, maxW);
3468
3553
  if (usedHeight !== null) h = usedHeight;
3469
- else if (ratio > 0) h = clamp(w / ratio, minH, maxH);
3554
+ else if (ratio > 0) h = clamp(heightOf(w), minH, maxH);
3470
3555
  else h = clamp(own.height, minH, maxH);
3471
3556
  }
3472
3557
  box.width = w + box.horizontalExtra;
@@ -3815,10 +3900,16 @@ function layoutPositioned(box: Box, containing: Box, ctx: LayoutContext): void {
3815
3900
  0,
3816
3901
  cbHeight - top - bottom - box.marginTop - box.marginBottom,
3817
3902
  );
3818
- // — a box with a ratio has its height from its width
3903
+ // — a box with a ratio has its height from its width, and a table is
3904
+ // as tall as its rows, as it is as wide as its columns between two
3905
+ // offsets: `normal` stretches neither (Blink: "Replaced/tables don't
3906
+ // stretch in abspos", `out_of_flow_layout_part.cc`), and a table taken
3907
+ // to the height its offsets leave was that tall around rows that were
3908
+ // not
3819
3909
  const stretches =
3820
3910
  style.height === AUTO &&
3821
3911
  (box.kind !== 'replaced' || control) &&
3912
+ box.kind !== 'table' &&
3822
3913
  !boxRatio(box);
3823
3914
  // both offsets and no height: the box fills what they leave, its
3824
3915
  // `auto` margins nothing (10.6.4, rule 5) — unless `min-height` or