w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.42k stars 648 forks source link

[cssom-view] Spec for offsetTop/Left does not match impls when offsetParent is `position:static` `<body>` element #10549

Open nicoburns opened 1 month ago

nicoburns commented 1 month ago

Context

This issue was discovered when trying to run unrelated WPT tests (css-flexbox tests) in Servo. It turns out that many of them depend on this non-standard behaviour. For example https://wpt.live/css/css-flexbox/align-content-horiz-001b.html

Specification

The spec says:

offsetParent

The offsetParent attribute must return the result of running these steps:

...

Return the nearest ancestor element of the element for which at least one of the following is true and terminate this algorithm if such an ancestor is found:

The computed value of the position property is not static.

It is the HTML body element.

...

offsetTop/offsetLeft

The offsetTop attribute must return the result of running these steps:

If the element is the HTML body element or does not have any associated CSS layout box return zero and terminate this algorithm.

If the offsetParent of the element is null return the y-coordinate of the top border edge of the first CSS layout box associated with the element, relative to the initial containing block origin, ignoring any transforms that apply to the element and its ancestors, and terminate this algorithm.

Return the result of subtracting the y-coordinate of the top padding edge of the first CSS layout box associated with the offsetParent of the element from the y-coordinate of the top border edge of the first CSS layout box associated with the element, relative to the initial containing block origin, ignoring any transforms that apply to the element and its ancestors.

Test Case

<!doctype html>
<html>
  <body id="body" onload="recompute()">
    <div id="target" style="height: 40px; width: 40px; background: red;"></div>
    <div id="output" style="text-wrap: nowrap"></div>
    <button onclick="recompute()">recompute</button>
    <script type="text/javascript">
      function recompute() {
        let targetNode = document.getElementById("target");
        let outputNode = document.getElementById("output");
        outputNode.innerHTML = `
          offsetParent: ${targetNode.offsetParent.id}<br />
          offsetTop: ${targetNode.offsetTop}<br />
          offsetLeft: ${targetNode.offsetLeft}<br />
        `;
      }
    </script>
  </body>
</html>

Per the spec, the offsetParent for #target ought to be the <body> element. And therefore the offsetTop and offsetLeft both ought to be 0. However, actual browsers (tested Chrome 126.0.6478.127, Firefox 127.0.2, Safari 17.5) return 8 for both properties.

The actual observed behaviour seems to be that while browsers are correctly returning the <body> element as the offsetParent, they are actually computing the offsetLeft/offsetTop offsets relative to the border-edge of the <html> element rather than the padding-edge of the <body> element in this case.

(note that this is only the case if the <body> is position: static. If the <body> is position: relative then all browsers return 0 as per the spec).

Implications

This issue affects a number of of WPT tests and means that browsers that conform to both the offsetTop/offsetLeft spec and the css-flexbox spec cannot pass those tests.

Either the spec or browser behaviour ought to be updated.

In css-flexbox, the following tests use data-offset-x/data-offset-y assertions and are potentially affected (but will only be affected if these are used with an offsetParent that corresponds to the <body>:

percentage-padding-001.html:1
inline-flex.html:3
column-reverse-gap.html:3
intrinsic-size/col-wrap-018.html:1
intrinsic-size/col-wrap-019.html:3
relayout-align-items.html:14
align-items-baseline-vert-rl-column-horz-flexbox-item.html:2
align-items-baseline-vert-rl-column-horz-table-item.html:2
flex-flow-013.html:32
align-items-baseline-vert-lr-column-horz-table-item.html:2
inline-flexbox-wrap-vertically-width-calculation.html:64
intrinsic-size/col-wrap-004.html:1
align-items-baseline-vert-lr-column-horz-flexbox-item.html:2
intrinsic-size/col-wrap-005.html:1
align-items-baseline-column-vert-rl-items.html:2
align-content-wrap-003.html:120
align-items-baseline-column-vert-lr-items.html:2
multiline-min-max.html:56
align-items-baseline-vert-lr-column-horz-items.html:2
align-content-wmvert-001.html:54
align-items-baseline-column-vert-lr-table-item.html:2
position-relative-percentage-top-001.html:1
flexbox_justifycontent-rtl-001.html:12
align-content-vert-001a.html:54
flex-factor-less-than-one.html:3
align-content-wrap-005.html:8
flexitem-no-margin-collapsing.html:2
align-items-baseline-column-vert-lr-flexbox-item.html:2
align-content-horiz-001a.html:54
percentage-heights-000.html:18
fieldset-as-container-justify-center.tentative.html:1
align-items-baseline-column-vert-rl-table-item.html:2
align-content-horiz-002.html:54
align-content-horiz-001b.html:54
flexbox_justifycontent-rtl-002.html:12
justify-content-007.html:1
abspos/position-absolute-001.html:2
abspos/flex-abspos-staticpos-justify-content-vertWM-001.html:24
align-content-vert-001b.html:54
align-items-baseline-vert-rl-column-horz-grid-item.html:2
abspos/flex-abspos-staticpos-align-self-rtl-004.html:24
abspos/position-absolute-012.html:2
abspos/flex-abspos-staticpos-justify-content-007.html:24
abspos/flex-abspos-staticpos-justify-content-002.html:24
abspos/flex-abspos-staticpos-align-self-vertWM-002.html:28
abspos/flex-abspos-staticpos-align-self-002.html:24
overflow-auto-003.html:1
abspos/position-absolute-002.html:39
abspos/flex-abspos-staticpos-justify-content-004.html:24
abspos/flex-abspos-staticpos-justify-content-008.html:24
abspos/flex-abspos-staticpos-justify-content-vertWM-002.html:24
abspos/flex-abspos-staticpos-align-self-003.html:24
align-content-wrap-001.html:13
abspos/flex-abspos-staticpos-align-self-001.html:24
abspos/flex-abspos-staticpos-align-self-vertWM-004.html:24
abspos/flex-abspos-staticpos-justify-content-rtl-002.html:24
abspos/flex-abspos-staticpos-justify-content-005.html:24
abspos/flex-abspos-staticpos-align-self-005.html:24
abspos/flex-abspos-staticpos-justify-content-rtl-001.html:24
abspos/position-absolute-015.html:1
abspos/flex-abspos-staticpos-align-self-rtl-001.html:24
abspos/flex-abspos-staticpos-align-self-006.html:24
justify-content-006.html:1
abspos/flex-abspos-staticpos-align-self-rtl-002.html:24
abspos/flex-abspos-staticpos-align-self-008.html:24
abspos/flex-abspos-staticpos-align-self-007.html:24
abspos/flex-abspos-staticpos-align-self-004.html:24
abspos/flex-abspos-staticpos-align-self-vertWM-003.html:24
abspos/flex-abspos-staticpos-align-content-001.html:24
abspos/position-absolute-003.html:16
abspos/flex-abspos-staticpos-justify-content-003.html:24
abspos/position-absolute-013.html:2
abspos/flex-abspos-staticpos-align-self-rtl-003.html:24
abspos/flex-abspos-staticpos-justify-content-001.html:24
abspos/flex-abspos-staticpos-align-self-vertWM-001.html:28
abspos/flex-abspos-staticpos-justify-content-006.html:24
box-sizing-001.html:2
align-self-014.html:2
flexbox_justifycontent-center-overflow.html:3
align-items-baseline-vert-lr-column-horz-grid-item.html:2
alignment/flex-align-baseline-overflow-001.html:12
alignment/flex-align-baseline-multicol-001.html:12
align-content-vert-002.html:54
alignment/flex-align-baseline-grid-001.html:12
alignment/flex-align-baseline-line-clamp-001.tentative.html:24
alignment/flex-align-baseline-002.html:6
alignment/flex-align-baseline-fieldset-002.html:4
alignment/flex-align-baseline-fieldset-003.html:4
alignment/flex-align-baseline-003.html:6
alignment/flex-align-baseline-004.html:6
alignment/flex-align-baseline-line-clamp-002.tentative.html:24
alignment/flex-align-baseline-flex-003.html:16
alignment/flex-align-baseline-007.html:3
alignment/flex-align-baseline-flex-001.html:48
alignment/flex-align-baseline-fieldset-001.html:4
alignment/flex-align-baseline-table-003.html:4
alignment/flex-align-baseline-line-clamp-003.tentative.html:24
alignment/flex-align-baseline-table-001.html:8
alignment/flex-align-baseline-grid-003.html:12
alignment/flex-align-baseline-005.html:3
alignment/flex-align-baseline-table-002.html:4
alignment/flex-align-baseline-flex-004.html:16
alignment/flex-align-baseline-006.html:3
alignment/flex-align-baseline-flex-002.html:48
alignment/flex-align-baseline-multicol-003.html:12
alignment/flex-align-baseline-overflow-003.html:12
align-items-baseline-column-vert-rl-flexbox-item.html:2
alignment/flex-align-baseline-001.html:6
alignment/flex-align-baseline-overflow-002.html:12
alignment/flex-align-baseline-multicol-002.html:12
alignment/flex-align-baseline-grid-002.html:12
flexbox-justify-content-wmvert-003.html:4
align-items-baseline-column-vert-lr-grid-item.html:2
align-items-baseline-vert-rl-column-horz-items.html:2
flexbox-justify-content-wmvert-002.html:4
align-items-baseline-column-vert-rl-grid-item.html:2

Questions

nicoburns commented 1 month ago

Of particular note: this affects the https://wpt.live/css/cssom-view/offsetTopLeft-inline.html test

nicoburns commented 1 month ago

As it looks like this is going to be discussed, I thought I would add my expectations from an web author's perspective: