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

[css-overflow][css-contain][css-sizing] overflow: auto incompatible with size containment and container queries #7875

Open
Loirooriol opened this issue Oct 12, 2022 · 6 comments

Comments

@Loirooriol
Copy link
Contributor

Loirooriol commented Oct 12, 2022

Context

Elements with overflow: auto only get a scrollbar when the contents overflow. Classic scrollbars take some space, so this affects sizing this way:

When the box is intrinsically sized, this reserved space is added to the size of its contents. It is otherwise subtracted from space alotted to the content area. To the extent that the presence of scrollbars can affect sizing, UAs must start with the assumption that no scrollbars are needed, and recalculate sizes if it turns out they are.

For example:

<style>
div { overflow: auto; background: cyan; vertical-align: top; }
div::before { content: ""; display: block; height: 200px; width: 200px; }
</style>
<div style="display: inline-block; height: 100px; width: 100px"></div>
<div style="display: inline-grid; grid-template: 100px / 100px"></div>

Note that only Blink has this behavior, Gecko (bug 764076) and WebKit don't: in the intrinsic case, the content area (without the scrollbar) is 100px tall indeed, but is less than 100px wide.

Tip: to force classical scrollbars if your UA uses overlay ones, in Firefox go to about:config and create a number pref ui.useOverlayScrollbars set to 0. In Blink/WebKit add this CSS:

::-webkit-scrollbar { background-color: #eff0f1; width: 12px; height: 12px; }
::-webkit-scrollbar-thumb { background: #898c8d; }

Incompatibility with size containment

In short, with overflow: auto the contents can affect either the inner or the outer size of the element. This is a problem with size containment (when paired with layout containment), which should allow this optimization:

When the style or contents of a descendant of the containment box is changed, calculating what part of the DOM tree is "dirtied" and might need to be re-laid out can stop at the containment box.

This is not true if the element is sized intrinsically: a change in the descendants may trigger or untrigger a scrollbar, affecting the outer size, and potentially the ancestors.

Blink and WebKit assume it's fine to apply the optimization... with clear wrong results. See this testcase:

The testcase above won't work in Firefox due to bug 1488878, but the same problem can be seen with contain-intrinsic-size. CSS Sizing tries to define some interaction between contain-intrinsic-size and overflow: auto, but it has some problems (#7867, #7868) and I think the behavior should be the usual one, just with a custom intrinsic size. Here is the testcase with contain-intrinsic-size:

Circularity with container queries

So far, we have seen that we shouldn't allow the contents of an element with size containment to affect its outer size. Could we maybe say that the scrollbar should shrink the inner size to preserve the outer size, even when sizing intrinsically? No, because that would create a circularity with container queries, because of https://w3c.github.io/csswg-drafts/css-contain-3/#width

The width container feature queries the width of the query container’s content box.

I have checked how Blink handles the circularity. It turns out it queries the size of the content area plus the scrollbar. Oddly, this happens even with overflow: scroll or when sizing intrinsically with overflow: auto. Testcase

  • 1st is extrinsic overflow: scroll. It matches inline-size = 100px despite the content area being narrower.
  • 2nd is intrinsic overflow: scroll. It matches inline-size > 100px despite the content area being 100px.
  • 3rd is extrinsic overflow: auto. It matches inline-size = 100px despite the content area being narrower when there is a scrollbar.
  • 4th is intrinsic overflow: auto. It matches inline-size = 100px when there is no scrollbar, and matches inline-size > 100px when there is a scrollbar despite the content area being 100px. Thus it would be circular, but due to the wrong optimization described above, the circularity is only apparent when forcing layout.

Well, I guess that does the trick to avoid circularities in practice, but all of this seems very buggy and hacky.

Possible solution

I'm leaning towards simply saying that size containment forces overflow: auto to behave as overflow: scroll.

Or maybe something like scrollbar-gutter: stable but without adding the gutter for overflow: hidden, and affecting both the inline and block axes.

CC @frivoal

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-overflow][css-contain][css-sizing] overflow: auto incompatible with size containment and container queries, and agreed to the following:

  • RESOLVED: for overflow:auto and size containment, 1st phase sizes without scrollbars, 2nd pass adding scrollbars doesn't change the size (b/c it is fixed). Clarify
The full IRC log of that discussion <fantasai> Topic: [css-overflow][css-contain][css-sizing] overflow: auto incompatible with size containment and container queries
<fantasai> github: https://github.com//issues/7875
<fantasai> oriol: When have overflow auto, only scrollbars when overflowing
<fantasai> oriol: but that can affect sizing
<fantasai> oriol: According to spec, there are 2 ways of handling these
<fantasai> oriol: one behavior, if element is intrinsically sized
<fantasai> oriol: then inner size of element will be intrinsic size, and scrollbar added onto that
<fantasai> oriol: this makes the element bigger
<fantasai> oriol: other behavior is when element is extrinsically sized
<fantasai> oriol: inner size is reduced to make room for scrollbar without affecitng outer size
<fantasai> oriol: Blink has implemented
<fantasai> oriol: firefox and webkit only do this in inline axis
<fantasai> oriol: Having said this, behavior is problematic in different ways related to containment
<fantasai> oriol: examples in the issue
<fantasai> oriol: There's an optimization that browsers can apply
<fantasai> oriol: when descednant changes size, we don't have to compute size of size-contained element and its ancestors
<florian> q+
<fantasai> oriol: but if this element has overflow:auto and intrinsically sized, then adding the scrollbar can cause the outer size to grow
<fantasai> oriol: so we cannot apply this optimization
<fantasai> oriol: and then browsers are broken because they assume they can apply the optimization, and it looks bad
<astearns> ack florian
<fantasai> oriol: can get scrollbars floating outside the element, or have sudden changes in size ...
<fantasai> florian: I think they're broken, but for a different reason
<fantasai> florian: i think spec is correct
<fantasai> florian: because contain:size is defined to work in 2 phases
<fantasai> florian: in the first phase, you size as if empty
<fantasai> florian: not going to get scrollbars
<fantasai> florian: but then you fix that size
<fantasai> florian: then you're no longer intrinsically sizing
<fantasai> florian: so when you're adding content into the fixed size, you are no longer intrinsically sized
<fantasai> florian: at that point, you need ot add the scrollbars inside
<iank_> i agree with florian
<fantasai> florian: I think that's the bug in the implementations
<fantasai> florian: so Chrome is putting the bottom scrollbar outside, as if intrinsically sizign, but we're not
<fantasai> oriol: I guess this argument would apply even without size containment, in the normal case you have 'width: max-content' then you could say the same
<fantasai> oriol: during first phase you do intrinsic size, and then element gets is size computed, and then you do final sizing
<vmpstr> fantasai: is the example with max-content with or without size containment? if it doesn't have it, we don't fix the size so we get weird results with scrollbars if you have normal element with normal sizing
<iank_> q+
<fantasai> oriol: In grid layout, if grid container has width:max-content, then first you run track sizing algo with indefinite size
<fantasai> oriol: and then when you know this size you fix the grid container and lay out again
<fantasai> oriol: regardless of size containment or not
<fantasai> oriol: so maybe the behavior of scrollbars added on top, reduce the inner size, maybe this wording may need to be different or maybe I didn't explain it well
<fantasai> oriol: but the same argument would apply in both contained an non-contained one
<astearns> ack iank_
<fantasai> iank_: I think this is two problem
<fantasai> iank_: I agree with florian, initialy size it without scrollbars present and then it will overlay content if you overflow in that direction
<fantasai> iank_: I think that's straightforward, should get a resolution
<fantasai> iank_: more general problem of sizing things in the presence of auto scrollbars
<fantasai> iank_: is complicated
<fantasai> iank_: we have our layout engine a little state machine that will go through and basically add scrollbars [missed]
<fantasai> iank_: still bugs trying to get rid of
<fantasai> iank_: but that's a separate issue
<fantasai> florian: might be complications in various layout modes, I think for contain case the spec is clear
<fantasai> florian: about the 2 phases
<fantasai> oriol: so final outer size does not depend on whether it gets scrollbars or not?
<fantasai> florian: correct
<fantasai> oriol: then we have circularity problem with Container Queries
<fantasai> astearns: If you want to present an additional problem, that should be in the issue or separate issue
<fantasai> astearns: we do need to get to next item
<fantasai> [discussion of resolution to take]
<fantasai> florian: I think the spec specifies this correctly
<fantasai> florian: could be clearer, but it's there
<fantasai> oriol: I think it needs to be clarified with interaction with spec prose in css-overflow
<fantasai> oriol: Because overflow says to recalculate sizes
<fantasai> astearns: so one proposal is to clarify that when things are initially sized under size containment, they're sized without scrollbars
<fantasai> florian: that's clear
<fantasai> florian: it's the other part that's less clear
<fantasai> florian: once you add the content, size containment says you keep the size fixed
<fantasai> florian: and just because you have scrollbars, doesn't change that fact
<iank_> i'm fine with that.
<fantasai> astearns: Proposal is to clarify that in 1st phase size without scrollbars, and in 2nd pass scrollbars don't change the size
<fantasai> RESOLVED: for overflow:auto and size containment, 1st phase sizes without scrollbars, 2nd pass adding scrollbars doesn't change the size (b/c it is fixed). Clarify

@Loirooriol
Copy link
Contributor Author

@frivoal I didn't think about this during the call, what about overflow: scroll?

<style>
div { overflow: scroll; contain: size; background: cyan; vertical-align: top; }
div::before { content: ""; display: block; height: 200px; width: 200px; }
</style>
<div style="display: inline-grid; grid-template: 100px / 100px"></div>

Should it also end up with an inner size smaller than 100px? Overlapping the content seems suboptimal and in this case there is no reason to do that.

@atanassov atanassov added this to View transitions in October 26 meeting Oct 25, 2022
@atanassov atanassov moved this from View transitions to Contain in October 26 meeting Oct 25, 2022
@atanassov atanassov added this to Agenda+ in November 30 2022 Nov 16, 2022
@Loirooriol
Copy link
Contributor Author

There's also the problem that content-visibility: auto and contain-intrinsic-size: auto are supposed to keep a stable outer size regardless of whether the element gets size containment. However, if the outer size can vary depending on overflowing contents in the non-contained case, but not in the contain-case, then there is a problem.

Also, the last remembered size records an inner size which doesn't include scrollbars. With the last resolution it's a problem if the size of the scrollbars will be subtracted from that. So the resolution should either be amended, or ResizeObserver should provide a way to track content size plus scrollbars.

@fantasai fantasai added this to By Theme in Agenda Scratchpad Dec 6, 2022
@mstensho
Copy link

mstensho commented Dec 15, 2022

Here's a test that doesn't require grid layout (to replace this testcase), and still illustrates the problem in Blink.

<!DOCTYPE html>
<style>
  .size { width: 200px; height: 200px; }
</style>
<button onclick="document.getElementById('contents').classList.toggle('size')">Change contents</button>
<button onclick="document.body.style.display = 'none'; document.body.offsetLeft; document.body.style.display = ''">Full layout</button>
<div style="width:fit-content; border:solid;">
  <div style="padding:50px; overflow:auto; contain:layout size;">
    <div id="contents"></div>
  </div>
</div>

@fantasai fantasai removed the css-overflow-3 Current Work label Feb 1, 2023
@astearns astearns added this to Overflow in March 2023 VF2F Mar 9, 2023
@astearns astearns moved this from Overflow to Wednesday - Mar 22 in March 2023 VF2F Mar 11, 2023
@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-overflow][css-contain][css-sizing] `overflow: auto` incompatible with size containment and container queries.

The full IRC log of that discussion <emeyer> oriol: We had a resolution but I’m not sure if that was the right solution
<emeyer> …Problem is that with overflow:auto, if the browser is using classical scrollbars, those take up space
<emeyer> …This can happen in two ways
<emeyer> …Browser may behave a bit different, if the element is sized intrinsically, the scrollbar size is added on top of content size
<emeyer> …If the size is explicit, then outer size of the element is preserved and scrollbars go inside
<emeyer> …We resolved the address the problem we don’t want, having to check to see if ancestors have to change size
<emeyer> …We always want to stop at element with size containment
<emeyer> …So we resolved to have scrollbars shrink content size
<emeyer> …Question with this is, first, this addresses the auto case, but what about the overflow:scroll case?
<emeyer> …Second, even if we preserve outer size of the element, there are features that depend on content size, like container queries and contain-intrinsic-size:auto
<emeyer> …By letting auto scrollbars affect content size, we have instability with container queries and contain-intrinsic-size:auto
<emeyer> …Maybe it would be simpler to say that if an element has size containment or c-i-s:auto, then overflow:auto would need to computer to overflow:scroll
<emeyer> …By forcing the element to consistently get scrollbars, we’d avoid problems, might not look as good
<florian> q+
<emeyer> …Open to ideas, but finding a solution that covers all problems simultaneously is tricky
<iank_> q+
<Rossen_> ack florian
<emeyer> florian: I think your suggestion of getting stable scrollbars does help
<emeyer> …I support that
<emeyer> …Maybe we could do indirectoin through scroillbar-gutter properties
<emeyer> oriol: Those properties have the stable keyword, but I think it only affects on axis
<emeyer> florian: Yeah, maybe that won’t work
<miriam> q+
<emeyer> …Containment is magic enough already; I think we could make it do strange things like this
<emeyer> iank_: I do worry about web compatbility
<emeyer> …People throw overflow:auto on random elements
<emeyer> …Also, the optimization mentioned in the spec is on a best-case scenario
<emeyer> …You aren’t required to use it for whatever reason
<Rossen_> ack iank_
<emeyer> florian: Ian, did you mean we should let the outer size with overflow:auto change and that regains consistency at the expense of optimization?
<emeyer> iank_: Want to think about it a little more, but I would be fine with that
<emeyer> …It’s a little bit of a hairy area
<Rossen_> ack miriam
<emeyer> miriam: Confused about the loop you mentioned of scrollbars getting added due to content
<emeyer> …That seems like the same loop we had to work through to get container queries at all, how is this new/different?
<emeyer> oriol: You can make contents change size with container queries
<emeyer> iank_: This falls into the you-always-move-forward pool
<emeyer> …We start assuming no scrollbars, then add if needed
<emeyer> oriol: It’s not like browsers are freezing, but if you start forcing things with JS or such, operations that should not affect things visually can change things
<emeyer> …I have some examples in the issue of things looking broken
<emeyer> iank_: To me, those examples are browser bugs
<emeyer> oriol: Then we need to define the expected behavior
<emeyer> miriam: I thought we did
<emeyer> Rossen: Is is the case that when we start with no scrollbars, you can only add scrollbars in the normal case, excluding container queries?
<emeyer> s/Is is/Is it/
<emeyer> …Is the proposal to make changing the scrollbar state once per layout a defined behavior?
<emeyer> oriol: Yeah, I want to avoid circularities
<emeyer> …Mia was saying we can already avoid them, so maybe I missed something
<emeyer> …Reading the specification, it seems there’s a circularity
<emeyer> …Was proposing a way to keep the content from affecting the content size of the container
<miriam> note and example here discuss this exact example: https://www.w3.org/TR/css-contain-3/#containment-inline-size
<emeyer> …If there’s another solution that only take a look at size at one point and not others, that may be fine
<emeyer> iank_: We said you’re performing a layout and engines will try to optimize, but can skip them
<emeyer> …You start with no scrollbars, then add a scrollbar or two if needed, then finish
<emeyer> …So you only add scrollbars during layout
<emeyer> …It’s tricky to get the optimization correct
<emeyer> …This circularity can arise with regular content, so this isn’t particularly new
<emeyer> Rossen: Is there a clarification or solution we need to record here, or is there an added step that seems reasonable?
<emeyer> miriam: Not sure; I think this is covered and I see this exact case mentioned in the spec, so I need to know what’s unclear and in need of clarification
<emeyer> oriol: Even if we use this approach on whether query styles apply or not, there’s a problem with contain-intrinsic-size:auto which remembers the content size
<emeyer> …We have some inconsistency here, and I think the other cases are addressed in other ways, but I don’t see this c-i-s problem addressed
<emeyer> miriam: It’s likely we do need to clarify here
<emeyer> Rossen: Let’s the conversation back to the issue and work out a solution there
<emeyer> s/Let’s the/Let’s take the/

@frivoal
Copy link
Collaborator

frivoal commented May 30, 2024

Summarizing earlier discussions:

We have the resolution we took in #7875 (comment)

for overflow:auto and size containment, 1st phase sizes without scrollbars, 2nd pass adding scrollbars doesn't change the size (b/c it is fixed).

The first phase sizes without scrollbars because when size-containment applies, you first size as if empty.

If we extend the same logic to overflow: scroll from first principles, you'd get a 1st phase sized with scrollbars, since they're there even if empty.

That solves the "first problem" discussed earlier.

For the second one, the instability of overflow:auto with contain-intrinsic-size:auto, so far I'm only seeing two proposal:

  • contain-intrinsic-size:auto causes overflow:auto to compute to overflow:scroll
  • with contain-intrinsic-size:auto and overflow:auto, the scrollbar gutters getting reserved regardless of whether there is actual overflow, but except the scrollbars don't get painted unless there's overflow. (this could be explained in terms of the scrollbar-gutter property, but it would need to gain a new value, as currently stable only works in the inline axis.)

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

No branches or pull requests

6 participants