Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cssom-view] Let offsetWidth / offsetHeight report actual size? #4541

Open
hugoholgersson opened this issue Nov 26, 2019 · 8 comments
Open

Comments

@hugoholgersson
Copy link

<a href="#" id="a">
<div style="width: 40px; height: 40px; background-color: green;"></div>
</a>
<script>
document.write("This link has size: " + a.offsetWidth + "x" + a.offsetHeight + ".");
</script>

Firefox, Edge 42, IE 11: 40x40
Chrome, Safari: 0x0

According to current spec [1], I guess Chrome is correct. However, from a developer perspective, returning the actual size, as Firefox does, is way more useful and intuitive.

[1] Return the size of "the first CSS layout box associated with the element".

Is Firefox's behavior spec-able?

@tabatkins
Copy link
Member

So the issue here is that the a is suffering from "block inside inline" splitting; rather than having a box that surrounds the div, it has two boxes that are siblings of the div's box, and both just contain some whitespace (collapsed away, so 0x0).

(And then, separately, the div is still linkable, because it's inside the a in the DOM, unrelated to the CSS box tree.)

I'm not sure how Firefox/etc got the idea that the div's box was valid to return. Was there a change to this text at some point? I'd have to history-dive.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [cssom-view] Let offsetWidth / offsetHeight report actual size?.

The full IRC log of that discussion <dael> Topic: [cssom-view] Let offsetWidth / offsetHeight report actual size?
<dael> github: https://github.com//issues/4541
<dael> TabAtkins: I'm confused on this issue. Thanks for digging up issues fantasai . According to the eample script and I verified Chrome. FF it repots offset of A is 40x40 but that's the div child. Chrome doesn't. It has 2 boxes surrounding hte div and b/c whitespace is collapsed the boxes are 0x0.
<dael> TabAtkins: Haven't done spec diving to see if it allows A to report div as size. I think Chrome and Wk is correct here and we close no change
<dael> AmeliaBR: Do you have link to spec that says block inside inline isn't counted as part of inline dimensions
<dael> TabAtkins: Yu return size of first layout box. A element starts with inline that has whtiespace only
<dael> AmeliaBR: Entire A includes div but there's a 0 width
<dael> TabAtkins: Not quite. In box tree div is not child of A. A produces 2 siblings but they're children of A parent.
<astearns> spec link: https://drafts.csswg.org/cssom-view-1/#extensions-to-the-htmlelement-interface
<dael> dbaron: I think there are multi explinations for difference and worth checking which. Possible it's due to FF treating block as inside the inline. Another poss if difference if there's a box generated for that little piece or not
<dael> TabAtkins: Yeah, and couldn't tell that without diving more
<dael> dbaron: Could check if there's a difference if you put the letter in there
<dael> TabAtkins: Yeah.
<dael> TabAtkins: Letter in there still considers div part of A but the height is a little higher
<dael> TabAtkins: FF makes height a littler higher. In Chrome it's still 0x0. Appears consistent. Chrome generates the empty box, FF considers it a part
<dael> dbaron: Not sure how it's higher b/c not sure what box it's dealing with
<dael> astearns: Seems like nice to be consistent here
<dael> dbaron: Nevermind.
<TabAtkins> With <a>foo<div></div></a>, you get a "foo" with the box below it, so the height grows in Firefox.
<dael> dbaron: Code for getOffsetRect does go through all fragments
<dael> TabAtkins: Rather then get first?
<dael> dbaron: Yeah. At least Geck code goes through all
<dael> TabAtkins: Sounds like Chrome doesn't
<dael> dbaron: Maybe diff is do you go through all fragments
<dael> AmeliaBR: Ran quick test using issue example + character before div and that way we can tell if it's about collapsing whitespace and in that case if there's a character FF gives width of div when giving offset of A element. It's not the first inline, but entire thing. Chrome gives size of first inline
<dael> dbaron: Another test is letter before and after div
<TabAtkins> before and after: http://software.hixie.ch/utilities/js/live-dom-viewer/saved/7825 Chrome still reports the height of just one line, so it's not iterating all the fragments
<dael> florian: Another thing confusing is Chrome behavior which is simple about first. For FF with most all frag are next to in this case. But it's not clear all fragments will be next to each other. If it's multi-col they're not adjacent.
<dael> dbaron: I think ti's smallest rect that encloses all
<dael> florian: So multi-col you get unrelated stuff, but when printing?
<dael> dbaron: API doesn't work in printing. DOn't have access
<dael> iank_: I think we had this convo with client height in multi-col. Two things you can do, union of all clients or stack them all. Impl stack. CSS Regions make it funky. Need to be consistent.
<dael> emilio: Chrome does take height in getBoundingClientRect
<dael> TabAtkins: It should b/c div height influences boundary. But this is just client rect
<dael> florian: Not working in printing, saying when we print there is no script so it's not there? B/c that's not true. THing that renders to PDF runs scripts so you can ask this question in an engine that's fragmenting to multiple pieces not next ot each other. Easy for Chrome example, not sure what it does if try and stick together
<dael> astearns: Top of hour. I think way forward is write test cases and then come to agreement what success for those cases should be and get that into spec
<florian> s/THing/UAs/
<dael> astearns: Sound okay to everyone?
<dael> florian: I would suggest including something like Prince in test cases to get an engine that prints and runs scripts
<TabAtkins> As far as I can tell, the test cases are all consistently showing that Chrome returns the dimensions of the first fragment, and Firefox gives a bounding rect of all the fragments (including the blocks splitting the inline).
<dael> astearns: Fair enough.

@tabatkins
Copy link
Member

@dbaron suggested several additional tests to make sure of exactly what behavior was being shown.

  1. initial case, a with whitespace before and after the div: http://software.hixie.ch/utilities/js/live-dom-viewer/saved/7826

  2. no whitespace inside the a at all: http://software.hixie.ch/utilities/js/live-dom-viewer/saved/7827

  3. text inside the a, before and after the div: http://software.hixie.ch/utilities/js/live-dom-viewer/saved/7828

Results:

case 1case 2case 3
Chrome0x00x07x17
Firefox40x4040x4040x75

So yeah, this consistently shows that Chrome is returning the size of the first layout box (always something preceding the div, possibly empty, generated even when there's no ws because there's still an empty layout box there), while Firefox is giving the bounding box of all the fragments.

I haven't tested across multicols, but I think interop is spotty there anyway, so I'm not sure how useful such a test would be, considering we already see the break in these tests.

@dbaron
Copy link
Member

dbaron commented Mar 18, 2020

There was some additional relevant IRC conversation at https://log.csswg.org/irc.w3.org/css/2020-03-18/#e1297586
# | dholbert  TabAtkins, RE unioning vs. "first layout box" - note that Chrome does  seem to union for a scenario of an inline element that's fragmented via  explicit breaks: https://jsfiddle.net/dholbert/hpxo4g2r/ | 10:04:30 PDT
# | TabAtkins dholbert: Yeah, that's all still within one layout box. | 10:06:28 PDT
# | TabAtkins (we were throwing around "fragment" casually, but the spec doesn't say fragment, it's about layout boxes) | 10:06:43 PDT
# | dholbert hmm, https://drafts.csswg.org/cssom-view/#css-layout-box Issue 1:"The terms CSS layout box and SVG layout box are not currently defined by CSS or SVG." :D | 10:08:21 PDT
# | dholbert maybe that's part of the problem | 10:08:41 PDT
# | TabAtkins lol | 10:09:24 PDT
# | fantasai "fragment" is a CSS3 term, CSS2 just called them boxes... | 10:09:26 PDT
# | TabAtkins  yeah, css2 mixed "box" and "fragment". But if we read CSSOM-View  literally, as referring to boxes, then that a-with-breaks is one box,  and the a-with-div is three sibling boxes.

@astearns astearns removed the Agenda+ label Mar 18, 2020
@Loirooriol
Copy link
Contributor

TabAtkins: Not quite. In box tree div is not child of A

Actually it should, in #1477 (comment) we resolved

In a block-in-inline split, the block is inside the inline in the box tree, and is a sibling of the two fragments of the inline in the fragment tree

@tabatkins
Copy link
Member

oh dang, well then, that changes things

@frivoal
Copy link
Collaborator

frivoal commented Mar 19, 2020

I haven't tested across multicols, but I think interop is spotty there anyway, so I'm not sure how useful such a test would be, considering we already see the break in these tests.

Here's how I view it: If we go with chrome's behavior, how this behaves in multicol or paged scenario is not interesting, because it is sufficiently well defined: as you go with the first piece of element, and it's not terribly interesting to know what happens to the other ones, since they're not considered.

If we are inclined to go with the firefox behavior, it becomes essential: as soon as you say you have to take into account all the pieces, you have to say what that means, and it is not trivial in all cases. Testing gives part of the answer here, as browsers that have this behavior must already have some answer to that question, but that does not necessarily mean it's the right answer.

Maybe I'm missing something, but I see 3 different kinds of situation where this question is interesting, and may lead to different answers:

  1. the inline-broken-by-a-block situation: there are multiple pieces, but they're all adjacent to each other
  2. the multicol situation (also true for something like regions): there are multiple pieces, and they're not (necessarily) adjacent to each other, but they're all in the same coordinate space
  3. the paged media situation: there are multiple pieces, and not only are they not (necessarily) adjacent to each other, they're not even in the same coordinate space

Some answers to "how do you account for multiple pieces" work in all of these situations, some don't. Some are more useful than others.

a. measure the first one only (Chrome behavior):
This gives an easy and well defined answer in all situations. Whether that answer is useful is questionable.

b. use the maximum inline size, and the sum of the block sizes:
Would give the expected answer in the inline-broken-by-a-block situation, and a similarly easy to compute answer in all other situations. Whether the answer in the other situations is useful or not is debatable, and probably depends on what you're trying to do.

c. measure the size of the bounding box of all fragments:
Would give the same answer as (b) in the inline-broken-by-a-block situation, but a different one in the multicol and regions situations. Whether that answer is more useful, and probably depends on what you're trying to do. Notably, this approach does not have a well defined answer in the paginated scenario, as the bounding box of things that are on different pages isn't knowable unless you know where the pages are placed relative to each other, which isn't defined.

d. measure the size of the bounding box of all the pieces that fit in the first thing-that-establishes-a-coordinate-space (i.e. the first page), ignore the rest:
Same as (c) as long as you don't paginate, closer to (a) (well defined but of questionable usefulness) if you do.

e. ???

My inclination would be to go for a (and to use the same logic for getBoundingClientRect() and any other similar API), and to introduce additional fragment/box aware APIs, for two reasons:
(1) This gives a well defined answer in all cases
(2) Because it gives a fairly unhelpful answer in many situations, It is quickly apparent to authors that if you want a correct answer to your problem, you have to be aware that fragmentation is a thing, use the API that give you access to that, and make a reasoned decision about how you want to handle them based on what you're trying to achieve.

(b) and (d) also satisfy the first criteria, but because they appear to work in more situations (inline-broken-by-a-block for b and d, as well as multicol / regions for d) but not all situations, they trick authors into thinking they got the right tool for the job, while giving broken results in less commonly tested situations, which I think signals a bad and fragile API.

Interestingly, prince, which supports javascript and pagination, does not support offsetWidth/offsetHeight/getBoundingClientRect(), but does have an API that lets you iterate through boxes/fragments/pieces (called getPrinceBoxes()).


(note: I'm using the word "piece" here, to walk around the fact that it is poorly defined whether we're talking about boxes or fragments here, as mentioned in #4541 (comment))

@frivoal
Copy link
Collaborator

frivoal commented Mar 19, 2020

TabAtkins: Not quite. In box tree div is not child of A

Actually it should, in #1477 (comment) we resolved

In a block-in-inline split, the block is inside the inline in the box tree, and is a sibling of the two fragments of the inline in the fragment tree

oh dang, well then, that changes things

Does it? Boxes don't have a size, fragments do, so it shifts the problem one definition further, but doesn't really make a difference to the end result, does it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants