Open nt1m opened 1 year ago
https://drafts.csswg.org/css-contain-2/#example-6932a400
the
counter()
andcounters()
value of the content property is not itself scoped, and can refer to counters established outside of the subtree
So the ::before
can see the outer instance of the counter instead of 0
.
That said, there are nested .list-item
children that will use counter-increment
and thus create a new instance of the counter. Where does this happen? See discussion in #5175
The example in the spec says
As counter-increment is scoped to an element’s subtree, the first use of it within the subtree acts as if the named counter were set to 0 at the scoping element
So with that interpretation the ::before
of the .list-item
wrapper should see 0
indeed.
[0] A1
[1] B1
[2] B2
[2] A2
[3] A3
However I think that's wrong, the new counter should be instantiated on the element that tries to modify it (which is what all browsers do in simple cases).
So then I agree with Firefox:
[1] A1
[1] B1
[2] B2
[2] A2
[3] A3
In fact, when using counters()
, Blink behaves like Firefox
[1] A1
[1.1] B1
[1.2] B2
[2] A2
[3] A3
Agree with:
However I think that's wrong, the new counter should be instantiated on the element that tries to modify it (which is what all browsers do in simple cases).
New Blink implementation behaves like Firefox in this case for counter().
Also, these two tests need spec clarification:
https://github.com/web-platform-tests/wpt/blob/master/css/css-contain/contain-style-counters-004.html and https://github.com/web-platform-tests/wpt/blob/master/css/css-contain/contain-style-counters-005.html
for the 004 the question is - why we don't have 13 as the result, as it doesn't seem different from the spec example: https://drafts.csswg.org/css-contain-2/#example-6932a400. (btw, removing <span>
s will produce 13 in Firefox, but they should not change the result, since we still can leave the style containment scope, even the s are there? Or it has to do something with the first counter-increment acting like it was set to 0 on scope element?).
for the 005 the question is - since we have counters(), shouldn't it access counters established outside the style containment scope? E.g. the first div with class increment will create a counter, that is inherited by the first div with class contain. Inside this style containment div the first div with class increment will create another counter. So, when later using counters() on ::before of that div, shouldn't counters also leave the style containment scope and produce 11 as a result? Basically, it's the same as the same example https://drafts.csswg.org/css-contain-2/#example-6932a400?
I do think 004 should produce 13, which is what I get on Blink if I use counters(counter-of-span, ".")
.
And also agree for 005, on Blink I get both counters if the outer instance is instantiated on the <body>
. I don't think that instantiating it in a previous sibling instead of the parent should affect the outcome.
To me that essentially means we take all the elements inside the subtree excluding the scoping element, and put that in its own document. Meaning the counter should always be "0" where we read the counter, since the counter is never incremented (since the element that increments it is outside of that "document").
From an authoring perspective, this mental model makes sense to me...
But I think this gets to the fundamental question of, what is style containment? Does it only prevent content within the contained subtree from affecting content outside it, or does it also prevent styling from outside the subtree from affecting content inside it?
For example, what happens with timeline names? If an ancestor of the contained element declares a named timeline, can content inside the subtree reference it? Counters should be consistent with that.
What are the use cases for style containment, and which mental model are we targetting here?
See prior discussion in #2483. It was decided that the model would be: contents inside containment can read counters from outside of the containment, but can't modify them (attempting to do so instantiates a new counter).
Does it only prevent content within the contained subtree from affecting content outside it, or does it also prevent styling from outside the subtree from affecting content inside it?
The former. @tabatkins said "the read doesn't cause alteration so it should work as normal". I think this is also consistent with e.g. size containment, which prevents the contents from affecting the size of the element, but not the other way round.
If an ancestor of the contained element declares a named timeline, can content inside the subtree reference it?
I would guess so, for the same reason
What are the use cases for style containment
Container queries, see #6213.
The CSS Working Group just discussed [css-contain] Style containment for counters
, and agreed to the following:
RESOLVED: fix the example and re-affirm the one-way containment of counters
RESOLVED: Add a clarifying note about the coutner function and review the WPT tests
I was a little confused about the end of the discussion (and @dbaron's proposal), specifically:
…If you were using the counters() function, and you incremented both outside and inside the subtree, the difference is that one would give you 3.1 and the other would give you 4
I tried with
<style>
div::before {
counter-increment: foo;
content: counters(foo, ".");
}
</style>
<div>
<div style="contain: style">
<div></div>
<div></div>
</div>
<div>
<div></div>
<div></div>
</div>
</div>
And I see, on Chrome and Firefox:
1
1
2
3
2
3
4
That's what I'd expect. But it seemed like the discussion was about this being something like 1.1 1.2 inside the style containment, or did I misunderstand?
That's probably because implementations are buggy? You can see new counter instances in this simpler case:
<style>
div::before {
content: counters(foo, ".", decimal);
}
</style>
<div style="counter-increment: foo">
<div style="contain: style">
<div style="counter-increment: foo"></div>
</div>
</div>
1
1
1.1
Rendering of Chrome's handling of @vmpstr's code, with a little bit of styling to show off the nesting and containment better:
Yeah, this is just buggy, it reflects neither the current spec nor dbaron's proposal. It looks like it does "two-way" containment, so the stuff inside the containment can't see the outside counters at all.
The current spec should render as:
1
1.1 (the style-contained element)
1.2
1.3
2
3
4
The elements inside the scope can see the foo counter, but aren't allowed to modify it, so they create a nested foo
counter instead.
Dbarons' proposal should render as:
1
2 (the style-contained element)
3
4
2
3
4
The elements inside the scope instead see a copy of the counters coming from the style-scoped element. They're thus allowed to modify the foo
counter as normal, but since they're only modifying a copy, the elements outside of the scope don't see their modifications.
And for completeness, without any containment at all it should look like this (and impls do indeed match this):
1
2 (the element that used to be style-contained)
3
4
5
6
7
So we see the differences here. Under both the current spec and dbaron's proposal, the elements following the containment act identically, pretending the contained subtree doesn't exist. In dbaron's proposal, however, the stuff inside the subtree also acts identically to how it worked without containment.
(Edited to fix the numbering since Oriol pointed out the increment was happening on the ::before, not on the div.)
@tabatkins Your expected behavior should actually be for this CSS:
div { counter-increment: foo }
div::before { content: counters(foo, ".") }
However, the testcase was incrementing on the pseudo-element, not the element itself, and the pseudo-element is in the subtree, so I think it should actually produce
1 (body > div::before)
1.1 (body > div > :first-child::before)
1.2 (body > div > :first-child > :first-child::before)
1.3 (body > div > :first-child > :last-child::before)
2 (body > div > :last-child::before)
3 (body > div > :last-child > :first-child::before)
4 (body > div > :last-child > :last-child::before)
Ohhhh, I didn't notice the increment was on the ::before. That does indeed change things, yeah, so that explains the first thing I listed as buggy. False alarm, it's correct. I'll edit.
Okay, example fixed and made more comprehensive - I just reused the example we're talking about here, but with the counter-increment on the div itself so I could also illustrate the "scoped to subtree" aspect.
Can you guys have a look at https://github.com/web-platform-tests/wpt/pull/41799 and https://github.com/web-platform-tests/wpt/pull/41820 for a review that it's ok?
Mm, actually, due to #5477, I think the example in the spec should produce
1
2
2.1
2.1
3
4
5
that's because the 2nd item inside the containment will not inherit the nested counter from the previous sibling, because the parent element already has a counter with the same name.
But I already added #5477 to the agenda to revert that resolution, so it may be fine to leave the example as-is?
Oooh, I didn't pay proper attention to #5477. Yeah, let's deal with it there.
The fact that both Chrome and Firefox render the case I had incorrectly but consistent with each other makes me a little worried about "just fixing the implementation" as it might cause rendering changes on existing sites that rely on this behavior.
I don't know if this is necessarily the same problem as #5477. Another way to get to 2.1 / 2.2 rather than 2.1 / 2.1 in the example would be to say something like:
The counter-increment and counter-set properties must be scoped to the element’s sub-tree and create a new counter at the root of the scoped sub-tree when a counter with the same name is inherited from outside.
But that's a problem if there is some counters()
beforehand, because at first it could just read the outer counter, but if later we create a new instance, then it would be affecting counters()
retroactively.
I'm not sure I'd call that "retroactive", as my perception of the ordering is that first you resolve all the counter-increment, counter-set, counter reset properties everywhere, and then you figure out what the counter() or counter(s) functions give you. But point taken, that behavior would not be overly intuitive.
State of this issue: the edits we resolved on are in place, but once the edits for #5477 are landed and once #5175 is resolved and edited in, we need to do a round of review all all related tests, and possibly write a few more.
While looking at: https://github.com/web-platform-tests/wpt/blob/efde03a7b7/css/css-contain/counter-scoping-004.html
It seemed to me that this WPT was incorrect, and that Blink & WebKit's behaviors were correct here.
Since
::before
(where we read the counter) is inside the style containment tree (it's a tree abiding pseudo element),counter-increment
on the scoping element shouldn't have any effect on the counter inside tree.I believe that is the intention of the spec as well:
https://drafts.csswg.org/css-contain-2/#property-scoped-to-a-sub-tree
To me that essentially means we take all the elements inside the subtree excluding the scoping element, and put that in its own document. Meaning the counter should always be "0" where we read the counter, since the counter is never incremented (since the element that increments it is outside of that "document").
@emilio Can you describe what your interpretation was?