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-contain-3] Ancestor Layout Loops with Single-Axis Containment #6426

Closed
mirisuzanne opened this issue Jul 6, 2021 · 22 comments
Closed

Comments

@mirisuzanne
Copy link
Contributor

In #1031 we identified the need for, and some of the issues with, single-axis containment. Since then Chrome has implemented the basic feature behind a flag – and we've had a chance to explore some of the issues in more depth. We wanted to start a new thread here to provide a collated summary of the issues, and how they relate.

In order for inline-size containment to work, the container has to maintain the same inline-size no matter what happens to the contents inside it – and no matter how much the block-size changes. These are cases where that behavior is difficult to define. We have potential solutions to each individual issue here. But the overarching concern is that all these solutions need to be applied on arbitrary ancestors of the contained element, making their impact expensive for browser engines, and unpredictable for authors.

Hopefully we can attempt some of these solutions in the Chrome prototype, in order to see them in practice.

Cross-axis overflow and classic scrollbars

Demo: https://codepen.io/miriamsuzanne/pen/yLbNgMx (requires classic scrollbars enabled)

If any ancestor has overflow on the container's block axis, then additional block-size of the contents can trigger a scrollbar on the cross-axis. Classic scrollbars for block-scrolling take up space on the inline-axis, leaving less space for the container.

This is likely to a very common scenario, impacting the majority of sites that uses inline-size containment, since the viewport itself uses auto-scrollbars.

This could be resolved by applying overflow-gutter: stable to the nearest ancestor with overflow: auto on the container's block axis? However, overflow-gutter is currently limited to a single axis (block-scrolling), and might need to handle inline-scrolling as well, in case the scroller has a writing mode orthogonal to the container.

%-Padding percentages in Orthogonal Writing Modes

Demo: https://codepen.io/miriamsuzanne/pen/PomqWVE

When padding is applied to the block axis in percentages, those values resolve against the inline-size of the parent. By restricting single-axis containment to the inline axis, we eliminate the majority of these issues. However, it's still possible to apply orthogonal writing modes to any arbitrary ancestor with block-padding that would respond to the (now cross-axis) block size of the container.

  • This applies to any ancestor with percentage-based block padding orthogonal to the container
  • It doesn't matter what elements switch the writing modes, so long as the container & padding are orthogonal

This could be resolved by "zeroing out" any percentage-padding applied to any ancestor on the containers block-axis?

Auto-sized BFCs effected by floats

Demo: https://codepen.io/miriamsuzanne/pen/mdmJRxW

The auto-sized Block Formatting Context (created by overflow:hidden) finds space for itself among various floated elements. However, as the contents expand vertically, the BFC has to contend with additional floated elements, and resizes. Note that:

  • The float/s can be "siblings" or even "cousins" of the BFC
  • The BFC can be the container, or any ancestor of the container

This could be resolved by adding clear: both to all auto-sized ancestor BFCs?

Aspect ratios (and Orthogonal Writing Modes?)

Demo: https://codepen.io/miriamsuzanne/pen/abWOJYJ

Since aspect-ratio implementations are inconsistent, and may have existing recursion issues (see #6419), I'm not confident that I know what issues here are specific to inline containment. Still it seems like something to keep an eye on - especially when orthogonal writing modes come into play.

This could be resolved in by removing the impact of the preferred aspect ratio? Authors who want to maintain a strict aspect ratio can still do that by applying size containment and defining overflow behavior.

@lilles
Copy link
Member

lilles commented Aug 25, 2021

I don't understand how the cross-axis overflow case is any different for single-axis containment and container queries than what is currently the issue with overflow:auto and contain:size. Regarding maintaining the same inline-size, we cannot do that even for contain:size, I think.

In this case, the inline size of the #contain element depends on its #spanner content via the scrollbar it triggers:

<!doctype html>
<style>
  #scroller { width: 400px; height: 400px; overflow: auto; }
  #contain { contain: size; border: 2px solid green; }
  #spanner { height: 2000px; border: 2px solid red; }
</style>
<div id="scroller">
  <div id="contain">
    <div id="spanner">Spanner</div>
  </div>
</div>

@lilles
Copy link
Member

lilles commented Aug 26, 2021

@mstensho

@mirisuzanne
Copy link
Contributor Author

mirisuzanne commented Aug 26, 2021

@lilles That's because size containment doesn't also imply layout containment, right? If I add layout containment to your example container (#contain), there is no scrollbar. Since we're requiring both size and layout containment for container queries, I think we only have to worry about the single-axis case, which happens even with layout containment?


EDIT: I'm wrong, and Rune is right.

@bfgeek
Copy link

bfgeek commented Oct 12, 2021

So for all(?) of these cases roughly fall into this pattern:

  1. Parent performs layout/intrinsic-sizes with some set of constraints.
  2. Parent inspects the result of the layout/intrinsic-sizes (e.g. reads the block-size of the resulting layout).
  3. Parent based in (2) updates the constraints (changing available-size to avoid floats).

For this case the parent layout algorithms always walk forward. They don't go backwards. The easiest to look at here is the float case: https://codepen.io/miriamsuzanne/pen/mdmJRxW

E.g. if we see that a fragment has a smaller block-size in area 2 we don't suddenly go backwards to try and get it to fit in area 1.

This is somewhat implied by a lot of algorithms, for the float case we discussed it previously here:
#2452

@mirisuzanne
Copy link
Contributor Author

mirisuzanne commented Oct 15, 2021

@emilio @anttijk (sorry if I'm mentioning the wrong folks - feel free to correct me) I'm curious how this solution (Ian's comment above) looks for WebKit and Gecko? This is being proposed for contain: inline-size specifically, I think, with block-size containment likely deferred indefinitely.

@emilio
Copy link
Collaborator

emilio commented Oct 15, 2021

@dholbert is a bit more familiar with containment and can probably page this stuff in faster, but happy to take a look if he can't :)

@anttijk
Copy link

anttijk commented Nov 3, 2021

It sounds bit like there simply isn't such a thing as single-axis containment in CSS.

Having a confusing and arbitrary seeming set of rules affect layout of the entire parent chain seems like opposite of "containment".

It is not obvious to me that that it is a requirement for a useful container query feature though. Maybe the focus should be in ensuring the algorithm makes forward progress and arrives in stable (though not necessarily entirely self-consistent) end state?

@dholbert
Copy link
Member

dholbert commented Nov 3, 2021

I'm curious how this solution (Ian's comment above) looks for WebKit and Gecko? This is being proposed for contain: inline-size specifically

So if I'm understand correctly, the proposed solution is just to let the following behavior sort of naturally fall out (with no explicit fixup/band-aids needed):
(1) During layout, we arrive at a box for a contain:inline-size container-query-targeted element (let's call this element Foo), with a particular inline-size provided by its parent (due to Foo being e.g. an auto-width block)
(2) At this point in layout, Foo can resolve styles for its subtree (based on the container query); then it proceeds to lay its subtree out, figures out its own block-size, and reports that up to its parent.
(3) The parent now changes its mind about the inline-size that it provided before (e.g. due to now needing to show a scrollbar, or realizing that floats will intersect badly, etc) and tells Foo to lay itself out again with a new inline size.
(4) Foo re-resolves its container-query-dependent styles and lays out its subtree again. It happens to now be small enough that you could imagine that the parent might reconsider its decision (e.g. no scrollbar needed after all, or it looks like it could move to an earlier location amidst floats, etc). But the parent doesn't actually get that opportunity.
(5) So: at the end of this layout, we've got a somewhat-inconsistent-looking layout (where e.g. a scrollbar is visible even though there's no overflow, or where Foo is placed further down than it looks like it would need to be amidst floats, etc)

I think this seems ~fine, though I suspect we may have to go through this same dance on every incremental layout for any change that dirties any nearby boxes.

@bfgeek
Copy link

bfgeek commented Nov 3, 2021

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-contain-3] Ancestor Layout Loops with Single-Axis Containment, and agreed to the following:

  • RESOLVED: Accept iank_'s proposal to guarantee full layout algorithm is always forward moving
The full IRC log of that discussion <dael_> Topic: [css-contain-3] Ancestor Layout Loops with Single-Axis Containment
<dael_> github: https://github.com//issues/6426
<dael_> miriam: It turns out this is hard. We knew that
<dael_> miriam: I tried to document 4 known instances where content changing size on 1 axis, generally block, has impact on available size for inline. So if we try and contain inline only get a loop
<dael_> miriam: Generally have a pattern where outer ancestor, element contained on inline axis, inside of that contents responding to container query
<dael_> miriam: With those three nested bits, a few of the issues are orthogonal writing modes. 2 other cases that are common. Ancestor is overflow scroll ad the contents can trigger scrollbar on ancestor which changes inline space available
<dael_> miriam: Another is floats as they get taller or shorter how they stack might change
<dael_> miriam: In my first document I wrote out the brute force solutions. They're not ideal for users. iank_'s idea is better
<dael_> iank_: I need to study one of these a bit more but they all fall into effectively we layout a child once with given space, read child height, and based on that give it a new available space.
<iank_> https://www.software.hixie.ch/utilities/js/live-dom-viewer/?saved=9775
<dael_> iank_: Fundamentally need to make sure we never walk backwards. This is true today
<dael_> iank_: I pasted in irc an example ^
<dael_> iank_: Orange box and inside it has a child with an a-r. Conisder width first, get height where we can't fix. Then consider second of 50px. All engines walk forwards like that.
<dael_> iank_: We don't try and say now that I'm this height maybe i could fit back here after all
<dael_> florian: Do you mean this is hte end of hte line principle we don't need the pieces miriam highlighted or that it solves them all?
<Rossen_> q?
<florian> q+
<fantasai> me q-
<dael_> iank_: I think we state it and it solves them all. Orthogonal. We layout with one set of contstraints, layout, adjust. We move forward.
<fantasai> s/me q-//
<Rossen_> ack florian
<dael_> florian: I'm a bit concerned this is different than float. We should avoid making css accidently stateful
<dael_> florian: Float, ancestors are not effected by float in the same way
<dael_> florian: Roughly I feel the way miriam proposed is annoyingly specific but need to do it to avoid vague or stateful
<dael_> iank_: I don't think it's stateful. We have a bug on orthogonal, but with that case to compute intrinsic size for the orthogonal child we need to lay it out with some available size.
<dael_> iank_: We layout with that size. may effect size of the parent. Now when we do layout pass we give orthogonal child different constraints and it lays out differently
<dael_> florian: That sounds true
<dael_> iank_: If you can come up with a case you think is stateful happy to access
<dael_> iank_: Orthogonal is hard b/c engines have a lot of bugs. miriam example shows a bug
<dael_> florian: For scrollbar and padding do you think we can get away without hatchet miriam proposed?
<dael_> iank_: I think so. A-r is fine, floats is fine, I think classic scrollbars is fine as long as you add them in a particular way. We'll slowly add scrollbars to get to a state. I think it should be okay
<dael_> florian: I thought if you didn't have miriam approach you would get a different result if you start on a 800 px window different result then if you start bigger and sthrink
<dael_> iank_: No, shouldn't be an issue. That's a browser issue and should be fixed.
<dael_> iank_: If there is an ambig set of steps where we need more specific order we should do that
<dael_> iank_: When forming a new context in relation to floats we had a specific order on how people should walk through. Pretty much everything else falls into that
<dael_> florian: If we go that way we can have a spec that's less magic but we need to be very precise so it's stable and interop
<dael_> iank_: yes
<dael_> florian: Can someone implementing list the steps? Undefined would be bad
<dael_> iank_: Depends what you mean by define
<dael_> iank_: We don't have a spec for block layout but we have resolved how floats behave.
<dael_> iank_: I can describe how this works for orthogonal children, I think that's well defined
<dael_> florian: My ask would be for the 4 cases miriam highlighted which I think are specific enough, could you either point to the spec language that make them non-problematic or propose sufficient spec text?
<dael_> iank_: I can work with miriam on that
<dael_> Rossen_: Sounds like we're forming consensus around going forward with reasoning captured in the issue by iank_. Additional exercise to figure out where changes have to land
<dael_> Rossen_: Anything else to add here? I see people on this issue chimed in
<dael_> florian: Great if we can go there. The things proposed are cludges we thought unavoidable. If they're avoidable, great
<dael_> Rossen_: In terms of solution anything else to add here?
<dael_> dholbert: I think your example with a-r is a good case for something that behaves like this without needing container queries. Good extension of that
<dael_> iank_: I think I can create examples as well with time
<dael_> Rossen_: We call them test cases; please add them :)
<dael_> Rossen_: Let's resolve on path forward. And it'll be iank_ for you to figure out wehre edits should go
<dael_> Rossen_: Prop: Accept iank_'s proposal to guarantee full layout algorithm is always forward moving
<dael_> florian: Sort of important to get to this in a timely manner. This discussion has been going on for years in GH. If there is a solution, great, but I'm worried we will force other browsers to reverse engineer crhome or do something else if we don't write it
<dael_> chrishtr: resolution to go with iank_ proposal?
<dael_> iank_: write it down at least for the examples. Part of the problem is large parts of some algos are undefined. Can spac how things should walk forward
<dael_> chrishtr: So parts of algo that intersect need to be written
<dael_> iank_: Part of issue is float not well defined. List of side effects. most recent for float layout is in a GH issue but not in spec. Pointing to the various places we've resolved
<dael_> iank_: For intrinsic size we have it written
<dael_> florian: I don't think iank_ proposal is not self contained enough. It's an approach we need to look into more details.
<dael_> Rossen_: yes. We don't have specificals
<dael_> florian: We agree to try and solve that way. And we go that way and we come back to resolve on details
<dael_> Rossen_: Have proposed path forward. Don't have details about specifics, but approach makes sense. You've made your point and people agree.
<dael_> fantasai: We had previously declined to publish fpwd because of this issue. If we get to a point we're okay with the issue we should publish fpwd
<dael_> previous proposal: Accept iank_'s proposal to guarantee full layout algorithm is always forward moving
<dael_> Rossen_: That's the proposed resolution. Obj?
<dael_> RESOLVED: Accept iank_'s proposal to guarantee full layout algorithm is always forward moving

@frivoal
Copy link
Collaborator

frivoal commented Nov 3, 2021

a somewhat-inconsistent-looking layout (where e.g. a scrollbar is visible even though there's no overflow

That's unfortunate-but-ok as long as it's somewhat rare. The opposite (i.e. having overflowing content but no room to put the scrollbar) would likely not.

@fantasai
Copy link
Collaborator

fantasai commented Nov 4, 2021

Action items here are:

  1. Edit in @bfgeek's proposal once he writes it out.
  2. Get @dbaron's sign-off that resulting spec is a acceptable for FPWD.
  3. Resolve in CSSWG and publish FPWD.

@chrishtr
Copy link
Contributor

chrishtr commented Dec 3, 2021

There is now a draft commit; however there is one more commit coming that moves the part about avoiding infinite cycles to a non-normative note.

Based on some offline discussions with several people, I think the TL;DR summary of what it's trying to say is:
a. The existing algorithms should not have infinite loops, and if they do it's a flaw in the existing algorithms that needs to be patched.
b. Container query elements are no different than cases that can already be encountered with combinations of floats, scrollbars, images, etc.
c. Therefore container query with single-axis containment elements don't represent anything "new".

Ideally, point (a) would be proved by algorithmic analysis, but unfortunately there is no algorithmic spec for block layout, floats etc; just a set of described "effects" of the layouts. I think it's too much to ask to require going through a very long and difficult task of writing it all out as an algorithm and proving that it halts and has a certain runtime performance. Instead, we should make our best effort to find and resolve problems we find, and guide our work with plenty of tests and implementation experience.

Detailed version of my reasoning:

  1. Any element in the DOM has a certain means of determining its sizing based on its contents and external constraints.
  2. In particular, for any choice of inline available size, an element has exactly one block size that it would prefer.
  3. Therefore, while the relationship between inline available size and block size can vary greatly, it is always a function F mapping inline available size to preferred block size. (*)
  4. For all layout algorithms to eventually halt, they must be able to deal with elements with an arbitrary F.
  5. A container-query element is no different: it always chooses a given block sizing based on its available inline sizing, and this relationship can be graphed in the same way, and therefore has an F.
  6. Therefore, container queries will halt, as long as existing layout algorithms have those properties and are correctly implemented.
  7. Ideally, someone would go and write down an exact algorithm for block layout with floats, vertical writing modes, scrollbars, and so on.
  8. In the meantime, container queries do not make the situation worse, except to the extent they raise the prominence of currently-obscure/not-costly algorithmic or implementation bugs.
  9. While it's a good idea to invest in point 7 (define algorithms) incrementally over time, it doesn't make sense to block container queries on writing down and verifying these algorithms.
  10. The best way forward to minimize risk from point 9 is a deep and clear experience with the layout algorithms, good implementation architectures, comprehensive and thorough testing, and interop.
  11. Experience with past features tells us that there will be errors found after shipping in algorithm design and implementation, and experience also shows us we'll be able to figure out a solution to them.

(*) Examples: If you graph it in 2D with inline size in the X axis and block size in the Y, the relationship for an img would be a straight line up and to the right; a div with text under it would be a piecewise linear curve that bends downward to the right. With sufficient cleverness, you can probably generate cases where it starts going up and then down, and then up again, by combining text and images and floats and flexboxes in various ways (though I don't have an actual example at hand).

@tabatkins
Copy link
Member

I just committed 8574e93, which rewords and expands on the note about inline-size cycles, hopefully capturing @bfgeek's comments and intention. Mind reviewing, @bfgeek and/or @dbaron?

@dbaron
Copy link
Member

dbaron commented Dec 3, 2021

8574e93 Mostly looks good to me; I left a few comments on the commit about the examples.

@fantasai
Copy link
Collaborator

fantasai commented Dec 3, 2021

OK, @tabatkins and I made some revisions to this section to a) simplify the note and b) improve the example. I also ported in the size containment exceptions from css-contain-2 and tightened up some of the normative wording. The resulting text can be viewed at https://drafts.csswg.org/css-contain-3/#containment-inline-size
CC @dbaron @bfgeek @chrishtr

@chrishtr
Copy link
Contributor

chrishtr commented Dec 3, 2021

New text LGTM.

@mirisuzanne
Copy link
Contributor Author

I think the spec changes above should address both this issue and #1031, pending a resolution to approve this approach.

@chrishtr
Copy link
Contributor

chrishtr commented Dec 3, 2021

@mirisuzanne when you said "should address", you meant it in the "did address" sense, right? Meaning you think the spec text as curently written is enough to resolve #1031 and #6426?

@mirisuzanne
Copy link
Contributor Author

@chrishtr yes

@bfgeek
Copy link

bfgeek commented Dec 3, 2021

Yup seems good.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed Ancestor Layout Loops, and agreed to the following:

  • RESOLVED: Accept draft text
  • RESOLVED: FPWD Contain 3
The full IRC log of that discussion <fantasai> Topic: Ancestor Layout Loops
<drott> thanks for taking that issue before the hour
<fantasai> github: https://github.com//issues/6426
<fantasai> miriam: This has been the main blocking issue for Container Queries
<fantasai> miriam: defining exactly how single-axis containment should work
<fantasai> miriam: I think at this point narrowing that down to the [audio cut]
<fantasai> miriam: inline axis
<fantasai> miriam: potential worried about changes to block size impacting available inline size, creating a loop
<fantasai> miriam: iank proposed that these are not new issues, and we have existing solutions in CSS, and showed that some of these problems already exist
<fantasai> miriam: we wrote a draft spec
<fantasai> fantasai: Importantly, both IanK and dbaron signed off on the spec, so from my perspective it must be good enough for FPWD
<fantasai> florian: dbaron reviewed earlier version, did he re-review the updated version?
<fantasai> dbaron: Don't hold up on me
<fantasai> dbaron: I'll get to it
<chrishtr> (the updated version is basically the same as the previous version)
<drott> (I am out for the next half hour, possibly slightly longer)
<fantasai> florian: As far as I'm concerned, if dbaron's OK with it OK with me
<fantasai> astearns: So are we proposing to resolve on the draft text?
<fantasai> miriam: yes
<fantasai> fantasai: And also to publish FPWD
<fantasai> astearns: Anyone with concerns about resolving to accept the draft text?
<fantasai> astearns: anyone need more time to review?
<fantasai> RESOLVED: Accept draft text
<fantasai> astearns: since this was the last big issue that we wanted to solve before FPWD, next is proposed FPWD of CSS Contain 3
<fantasai> +!
<fantasai> +1
<fantasai> jensimmons: +1 to FPWD, keep seeing situations where not having a WD of this spec is creating problems
<chrishtr> +1
<fantasai> astearns: anyone with the opposite view?
<fantasai> florian: I was pushing back against this one until this issue was addressed, because unclear if we should publish FPWD without it, but with that cleared I'm good now
<fantasai> RESOLVED: FPWD Contain 3
<chrishtr> congratulations everyone, especially miriam!
<chrishtr> This is an awesome feature, and a huge milestone for the web.
<fantasai> <astearns> +1, very happy to get into FPWD

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