w3c / csswg-drafts

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

[cssom][css-break] getComputedStyle and fragmentation #6513

Open frivoal opened 3 years ago

frivoal commented 3 years ago

I could not find a defintion that states how getComputedStyle() is supposed to work for fragmented elements.

getComputedStyle() returns the resolved value. In some cases, that corresponds to the computed value, and in some cases that corresponds to the used value. Even when an element is fragmented, the computed value is clearly a per-element thing, but the way the used value is calculated from the computed value as part of layout and can, in some cases, differ per fragment. In such cases, I could not find any spec that says what getComputedStyle() is supposed to return.

For many properties, the used value is equal to the computed value anyway, so for them there's no problem, but that's not always true. Most prominently, what about the width and height of fragmented block elements: should the used value of the height property be the height of the first fragment, or the sum of the height of all fragments (or something else)? Should the used value of the width be the that of the first fragment, or the sum, or the average? Should potentially orthogonal writing-modes make a difference? Should a different display type make a difference? More properties may have answers that vary per fragment, maybe due to percentages (see https://github.com/w3c/csswg-drafts/issues/6512) or to box-decoration-break.

I think the only answer we can consistently apply to all properties and all display types, regardless of writing-mode, box-decoration-break, etc, is to return the used value on the first fragment. We could special-case some situations to get sums or averages or other computations to derive one value from several, but that seems like a lot corner-case handling for no clear use-case.

In terms of current implementations, as far as I can tell Firefox does just return the values of the first fragment in all cases. Chrome and Safari seem to have special cased block-size (and whichever of width and height it maps to) to return a sum of all fragments.

If there's no compat constraint, I'd suggest standardizing on the simpler behavior of only taking into account the first fragment. If we do want to special case some properties, we should document them.

emilio commented 3 years ago

I think the WebKit implementation is just an artifact of how fragmentation works on WebKit. cc @bfgeek, is LayoutNG different in this case?

bfgeek commented 3 years ago

@emilio @frivoal Its different, but there is a (slightly) larger larger question around what non-fragment aware geometry APIs should return in general. E.g. what does clientWidth/clientHeight return?

We are currently building the capability to have different inline-sized fragments. An answer which may make sense is:

gCS(elem).width == std::max(fragments_inline_sizes);
gCS(elem).height == sum_fragments_block_sizes;

Similar for clientWidth/clientHeight/scrollWidth/scrollHeight/etc.

emilio commented 3 years ago

gCS(elem).width/height doesn't return the used width for non-replaced inline elements, so I think it doesn't matter for that specific case, fwiw.

I think doing first-fragment is the easiest. Lots of these APIs weren't thought with fragmentation in mind and trying to retroactively support it is probably not a great idea / use of our time.

APIs like ResizeObserver / getClientRects / are meant to provide per-fragment sizes, so I don't think we need new APIs even.

frivoal commented 3 years ago

In general, I agree with @emilio, and we should have fragment-aware APIs to properly investigate fragment specific questions, and return something simple here, since we cannot do it perfectly anyway. But we might not be able to return the value of the first fragment in all cases, due to compat concerns. For instance, I suspect (though I don't know for sure) that for padding-bottom, there might be an expectation to return the value of the last fragment, not the first one. But this would have to be investigated on a property by property basis.

How about we spec that the value returned should be based on the first fragment, except in explicitly listed cases where we define exactly what happens, and we add to that list as we discover good reasons to make exceptions.

FremyCompany commented 2 years ago

Maybe properties that relate to the 'end' directions (in LTR: bottom and right) should be last-fragment, and all other properties should be the first fragment?

css-meeting-bot commented 2 years ago

The CSS Working Group just discussed [cssom][css-break] getComputedStyle and fragmentation.

The full IRC log of that discussion <dael> Topic: [cssom][css-break] getComputedStyle and fragmentation
<dael> github: https://github.com/w3c/csswg-drafts/issues/6513
<dael> florian: A while ago looking at def of gCS and it does not mention fragmentation. problem b/c element frag gives multiple answer to value of property. What to do?
<dael> florian: GH suggestion which is roughly okay with commentors is other APIs are fragment aware so people who want per frag should use those. This API we should do simpliest with tweak for compat
<dael> florian: Simpliest is return first fragment. Might need to tweak based on historic impl. Assuming hoz writing mode, height of the block chrome returns sum, though FF returns first. Probably not compat porblem there, but might have it elsewhere
<dael> florian: Suggestion is doc that gCS returns based on first fragment and we document compat issues sep
<dael> florian: Suggestion from fremy to make it a little smarter and answer on last for other writing modes. I think I would suggest not taking the suggestion. This will not be smart enough to describe everything so you need frag away APIs
<dael> florian: If compat dictates smarter this could be one of the things, but would rather not if we can keep it simple
<dael> TabAtkins: I think I agree. Don't think we can get smarter without being complex. Going with simple answer of first frag is easier
<dael> iank_: Not wild about first fragment. Larger set of problems about client width and height and how they interact
<Rossen_> q?
<dael> iank_: In a lot of libraries they compute to witdh and height and used interchangably. Someone using this naively they would get unexpected results when printing. If you sum as I suggest in the issue you get broadly expected
<dael> iank_: I don't expect a large compat issue, but it's a little unintuitive
<dael> florian: Can we say unless documented we go with first fragment but with block dimensions we sum up. And maybe for padding, border, margin we take last for inline-end and block-end. Maybe that's enough of an excpetion?
<dael> iank_: What properties return funcy used? width height padding border margin?
<dael> florian: More. Offsets as well
<dael> florian: width height margin padding, top left bottom right
<Rossen_> q+
<dael> florian: Another complication is we're baking selectors that let you style different fragments differntly and then any property could have a different answer. Not exposed, but might be coming
<dael> iank_: With that we would start not to return...you can't really...a lot will break if you set different margin-top per fragment. Skeptical we'll need to do something for that case
<dael> florian: Even if we don't get that. for margin-bottom return the first, last, depend on box-decoration-break? If compat dictates we should take that
<dael> iank_: Rathr then applying general rule would be good to go through cases individually and come up with answers. I suspect I agree with amrgina nd padding for first/last fragment but width and height we want different.
<dael> iank_: It would be better to do case by case
<astearns> ack Rossen_
<dael> Rossen_: I'm more closely aligned with iank_ then the simplicity. Generally I'm all for simplier and more definitive answer. I believe there will be unfortunate compat breakage we'll see for apps building pagination-ish presentations
<florian> q+
<dael> Rossen_: I've seen a number of apps using multicol to drive book-like experiences. For them, back in the time this is why we into getClientRects and go down patch of fragment aware things. Zooming out, not being able ot know where a box starts which is what you will run into if you only give results on first frag which could give you a result under the 2nd, that would be unfortunate
<dael> Rossen_: I'm closer aligned with iank_ 's point
<astearns> ack florian
<dael> florian: Strongly agree we need something that's fragment aware. But gCS isn't fragment aware. As to breaking interop, we don't have interop.
<dael> florian: Another thing I forgot to mention is we don't need to get to fancy APIs to get into troble. We could sum heights, but what to do with width when frag have different width? Or an inline that breaks 2 lines? Block that's across 2 pages? No answer in the spec
<dael> florian: We can go property by property as iank_ said. Having done that for height there's no interop
<dael> astearns: Not that we break interop but that we break compat. There are likely sites that rely on Blink's behavior. Risk breaking them if we spec differently
<dael> florian: But if it's engine specific should we also not change FF?
<dael> Rossen_: If they don't have bugs they're either not used for these presentations or they've coded to adapt. I want to udnerscore the point about compat vs interop. I'd be more concerned about compat at this point
<dael> astearns: If concerned about compat and there isn't anything that we can spec as safe, perhaps we explicitly say gCS is not frag aware, different enginres return different and we don't spec what to do
<dael> florian: Not terribly helpful
<dael> astearns: It's not. but going through what to do with every layout property with interesting frag when we can't spec something interop, is that a wild goose chase?
<dael> Rossen_: Sounds more academic than applicable. Do we have use cases for alignment? These properties have existed for quite some time so people work around it
<dael> florian: Confsed how it can be true simultaniously that this is sufficently used that we can't change but unused that we don't have to answer. If people aren't using this spec simple or say undefined. If it is used would be good to haev a known behavior for interop
<dael> smfr: Might have legacy content on each browser relying on the behavior
<dael> Rossen_: epub viewers are built against 1 engine and for these apllications they have very specific code with very specific behavior for the engine. Whatever that engine does is what they'll get
<dael> florian: Not so sure. Paginators are frequent cross-engine.
<dael> iank_: Specific example, a lot of people run chrome in headless to generate pdfs. A lot of people doing that
<dael> iank_: To my previous point, only properties we'd use layout dependent is margin, padding, width, height, and the insets
<dael> florian: Also borders. Sizes don't change but box-decoration-break might or might not have
<dael> florian: But yes, it's those
<chrishtr> q+
<dael> iank_: I wonder if we can go through these and pick what is reasonable. for example, for the edge type things, border padding margin and insets I'll agree picking first or last will be pretty compat. not huge burden on engines to sum on block and take max on inline
<dael> iank_: I think blink and wk do that already. Would be interested to hear for gecko
<dael> florian: Not particuarly hard. But in viviostyle there is a measure of lazy rendering for a many 1000 book and having a blocking call that forces you to layout isn't great. Maybe no way around
<dael> iank_: I can come up with examples that will render incorrect due to that
<dael> florian: Yeah. For block maybe no way around. For inline maybe we can just do first?
<dael> iank_: Yeah. I'd like this consistent with client height. A bit of precedence with inline element fragmentation. might be wrong there
<astearns> ack chrishtr
<dael> chrishtr: No strong opinion, but maybe take this offline for specific proposals?
<dael> chrishtr: There is #6588 on the agenda which we need for progress
<dael> astearns: Okay, will go back to GH on this issue and get a list of properties we want to consider fully
smfr commented 2 years ago

In general I think we should discourage use of getComputedStyle for asking questions about geometry.

frivoal commented 2 years ago

In general I think we should discourage use of getComputedStyle for asking questions about geometry.

Agreed, and it seemed to me that returning a not-too-smart but simple answer (like "always first fragment") is kind of going in that direction, but on the call, we did seem to want to try a bit harder and get something roughly sensible, for the sake of people who do geometry with it.

The growing proposal seems to be:

(for non-replaced inlines, it's already defined to return the computed value, not used value, so don't try to do any math on that.)

For everything else, it doesn't seem to make a difference until we introduce the ability to style differently per fragment. Except arguably, we already have the ability to style different fragment differently, though in a limited and quirky fashion :(

<p><span id=nowwhat>a sufficiently long piece of text that takes up more than one line and wraps</span>
p::first-line { font-weight: 800; }
p { font-weight: 100;}
console.log(getComputedStyle(document.getElementById("nowwhat")).fontWeight);

But ::first-line and first-letter are plenty quirky, so I'd be tempted to not generalize from them, and to the extend that something needs to be specified for them, I'd go with a specific rule that covers them and them only (possibly hosted in css-pseudo), and not try to address them via something generic about fragmentation.

frivoal commented 2 years ago

Or maybe it's just block-end on the last fragment, and inline-end stays on the first for margins / borders / paddings / offsets

emilio commented 2 years ago

Borders already return computed values, iirc, so I'd rather not make them return used values now.