Skip to content

Commit 8d1fa06

Browse files
byung-woomoz-wptsync-bot
authored andcommitted
Bug 1721351 [wpt PR 29718] - Support relative selector to update the :has tests, a=testonly
Automatic update from web-platform-tests Support relative selector to update the :has tests Support the relative selector grammar starting with combinator. - https://www.w3.org/TR/selectors-4/#typedef-relative-selector To simplify matching operation, some relation types are added. - kRelativeDescendant : Leftmost descendant combinator - kRelativeChild : Leftmost child combinator - kRelativeDirectAdjacent : Leftmost next-sibling combinator - kRelativeIndirectAdjacent : Leftmost subsequent-sibling combinator The ':scope' dependency in <relative-selector> definition creates too much confusion especially with ':has' as the CSSWG issue describes. - w3c/csswg-drafts#6399 1. ':scope' behavior in ':has' argument is different with usual ':scope' behavior. 2. Explicit ':scope' in a ':has' argument can create performance issues or increase complexity when the ':scope' is not leftmost or compounded with other simple selectors. 3. Absolutizing a relative selector with ':scope' doesn't make sense when the ':has' argument already has explicit ':scope' (e.g. ':has(~ .a :scope .b)' -> ':has(:scope ~ .a :scope .b)' To skip those complexity and ambiguity, this CL removed some logic related with the 'explicit :scope in :has argument', and added TODO comment to handle it later separately. As suggested in the CSSWG issue, this CL always absolutize the <relative-selector> with a dummy pseudo class. - kPseudoRelativeLeftmost The added pseudo class represents any elements that is at the relative position that matches with the leftmost combinator of the relative selector. This CL also includes tentative tests for some cases involving the ':scope' inside ':has' to show the result of the suggestion. By removing the ':scope' dependency from the relative selector, most of the ':scope' inside ':has' will be meaningless. (It will not match or can be changed more simple/efficient expression) Change-Id: I1e0ccf0c190d04b9636d86cb15e1bbb175b7cc30 Bug: 669058 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2972189 Reviewed-by: Rune Lillesveen <futhark@chromium.org> Commit-Queue: Byungwoo Lee <blee@igalia.com> Cr-Commit-Position: refs/heads/master@{#908421} -- wpt-commits: 5800374cbcad335de936e9a30868b2e5e5340a9d wpt-pr: 29718
1 parent d63bd43 commit 8d1fa06

File tree

4 files changed

+110
-143
lines changed

4 files changed

+110
-143
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>:has pseudo class behavior with explicit ':scope' in its argument</title>
4+
<link rel="author" title="Byungwoo Lee" href="mailto:blee@igalia.com">
5+
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
9+
<main>
10+
<div id=d01 class="a">
11+
<div id=scope1 class="b">
12+
<div id=d02 class="c">
13+
<div id=d03 class="c">
14+
<div id=d04 class="d"></div>
15+
</div>
16+
</div>
17+
<div id=d05 class="e"></div>
18+
</div>
19+
</div>
20+
<div id=d06>
21+
<div id=scope2 class="b">
22+
<div id=d07 class="c">
23+
<div id=d08 class="c">
24+
<div id=d09></div>
25+
</div>
26+
</div>
27+
</div>
28+
</div>
29+
</div>
30+
31+
<script>
32+
function formatElements(elements) {
33+
return elements.map(e => e.id).sort().join();
34+
}
35+
36+
// Test that |selector| returns the given elements in the given scope element
37+
function test_selector_all(scope, selector, expected) {
38+
test(function() {
39+
let actual = Array.from(scope.querySelectorAll(selector));
40+
assert_equals(formatElements(actual), formatElements(expected));
41+
}, `${selector} matches expected elements on ${scope.id}`);
42+
}
43+
44+
// Test that |selector1| and |selector2| returns same elements in the given scope element
45+
function compare_selector_all(scope, selector1, selector2) {
46+
test(function() {
47+
let result1 = Array.from(scope.querySelectorAll(selector1));
48+
let result2 = Array.from(scope.querySelectorAll(selector2));
49+
assert_equals(formatElements(result1), formatElements(result2));
50+
}, `${selector1} and ${selector2} returns same elements on ${scope.id}`);
51+
}
52+
53+
// descendants of a scope element cannot have the scope element as its descendant
54+
test_selector_all(scope1, ':has(:scope)', []);
55+
test_selector_all(scope1, ':has(:scope .c)', []);
56+
test_selector_all(scope1, ':has(.a :scope)', []);
57+
58+
// there can be more simple and efficient alternative for a ':scope' in ':has'
59+
test_selector_all(scope1, '.a:has(:scope) .c', [d02, d03]);
60+
compare_selector_all(scope1, '.a:has(:scope) .c', ':is(.a :scope .c)');
61+
test_selector_all(scope2, '.a:has(:scope) .c', []);
62+
compare_selector_all(scope2, '.a:has(:scope) .c', ':is(.a :scope .c)');
63+
test_selector_all(scope1, '.c:has(:is(:scope .d))', [d02, d03]);
64+
compare_selector_all(scope1, '.c:has(:is(:scope .d))', ':scope .c:has(.d)');
65+
compare_selector_all(scope1, '.c:has(:is(:scope .d))', '.c:has(.d)');
66+
test_selector_all(scope2, '.c:has(:is(:scope .d))', []);
67+
compare_selector_all(scope2, '.c:has(:is(:scope .d))', ':scope .c:has(.d)');
68+
compare_selector_all(scope2, '.c:has(:is(:scope .d))', '.c:has(.d)');
69+
</script>

testing/web-platform/tests/css/selectors/has-basic.html

+2-5
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,11 @@
7474
test_selector_all(
7575
':has(.sibling:has(.descendant) ~ .target) ~ .parent > .descendant',
7676
[g, i, j]);
77-
test_selector_all(':has(:scope .target)', [a, b, f, h]);
78-
test_selector_all(':has(:scope > .parent)', [a]);
77+
test_selector_all(':has(> .parent)', [a]);
7978
test_selector_all(':has(> .target)', [b, f, h]);
80-
test_selector_all(':has(:scope > .target)', [b, f, h]);
79+
test_selector_all(':has(> .parent, > .target)', [a, b, f, h]);
8180
test_selector_all(':has(+ #h)', [f]);
82-
test_selector_all(':has(:scope + #h)', [f]);
8381
test_selector_all('.parent:has(~ #h)', [b, f]);
84-
test_selector_all('.parent:has(:scope ~ #h)', [b, f]);
8582
test_selector('.sibling:has(.descendant)', c);
8683
test_closest(k, '.ancestor:has(.descendant)', h);
8784
test_matches(h, ':has(.target ~ .sibling .descendant)', true);

testing/web-platform/tests/css/selectors/has-relative-argument.html

+39-125
Original file line numberDiff line numberDiff line change
@@ -138,139 +138,53 @@
138138
}, `${selector} matches expected elements`);
139139
}
140140

141-
test_selector_all('.x:has(:scope .a)', [d02, d06, d07, d09, d12]);
142-
test_selector_all('.x:has(:scope .a > .b)', [d09]);
143-
test_selector_all('.x:has(:scope .a .b)', [d09, d12]);
144-
test_selector_all('.x:has(:scope .a + .b)', [d12]);
145-
test_selector_all('.x:has(:scope .a ~ .b)', [d02, d12]);
146-
test_selector_all(':has(.x:scope .a)', [d02, d06, d07, d09, d12]);
147-
test_selector_all(':has(.x:scope .a > .b)', [d09]);
148-
test_selector_all(':has(.x:scope .a .b)', [d09, d12]);
149-
test_selector_all(':has(.x:scope .a + .b)', [d12]);
150-
test_selector_all(':has(.x:scope .a ~ .b)', [d02, d12]);
151-
test_selector_all(':has(:scope.x .a)', [d02, d06, d07, d09, d12]);
152-
test_selector_all(':has(:scope.x .a > .b)', [d09]);
153-
test_selector_all(':has(:scope.x .a .b)', [d09, d12]);
154-
test_selector_all(':has(:scope.x .a + .b)', [d12]);
155-
test_selector_all(':has(:scope.x .a ~ .b)', [d02, d12]);
141+
test_selector_all('.x:has(.a)', [d02, d06, d07, d09, d12]);
142+
test_selector_all('.x:has(.a > .b)', [d09]);
143+
test_selector_all('.x:has(.a .b)', [d09, d12]);
144+
test_selector_all('.x:has(.a + .b)', [d12]);
145+
test_selector_all('.x:has(.a ~ .b)', [d02, d12]);
156146

157-
test_selector_all('.x:has(:scope > .a)', [d02, d07, d09, d12]);
158-
test_selector_all('.x:has(:scope > .a > .b)', [d09]);
159-
test_selector_all('.x:has(:scope > .a .b)', [d09, d12]);
160-
test_selector_all('.x:has(:scope > .a + .b)', [d12]);
161-
test_selector_all('.x:has(:scope > .a ~ .b)', [d02, d12]);
162-
test_selector_all(':has(.x:scope > .a)', [d02, d07, d09, d12]);
163-
test_selector_all(':has(.x:scope > .a > .b)', [d09]);
164-
test_selector_all(':has(.x:scope > .a .b)', [d09, d12]);
165-
test_selector_all(':has(.x:scope > .a + .b)', [d12]);
166-
test_selector_all(':has(.x:scope > .a ~ .b)', [d02, d12]);
167-
test_selector_all(':has(:scope.x > .a)', [d02, d07, d09, d12]);
168-
test_selector_all(':has(:scope.x > .a > .b)', [d09]);
169-
test_selector_all(':has(:scope.x > .a .b)', [d09, d12]);
170-
test_selector_all(':has(:scope.x > .a + .b)', [d12]);
171-
test_selector_all(':has(:scope.x > .a ~ .b)', [d02, d12]);
147+
test_selector_all('.x:has(> .a)', [d02, d07, d09, d12]);
148+
test_selector_all('.x:has(> .a > .b)', [d09]);
149+
test_selector_all('.x:has(> .a .b)', [d09, d12]);
150+
test_selector_all('.x:has(> .a + .b)', [d12]);
151+
test_selector_all('.x:has(> .a ~ .b)', [d02, d12]);
172152

173-
test_selector_all('.x:has(:scope + .a)', [d19, d21, d24, d28, d32, d37, d40, d46]);
174-
test_selector_all('.x:has(:scope + .a > .b)', [d21]);
175-
test_selector_all('.x:has(:scope + .a .b)', [d21, d24]);
176-
test_selector_all('.x:has(:scope + .a + .b)', [d28, d32, d37]);
177-
test_selector_all('.x:has(:scope + .a ~ .b)', [d19, d21, d24, d28, d32, d37, d40]);
178-
test_selector_all(':has(.x:scope + .a)', [d19, d21, d24, d28, d32, d37, d40, d46]);
179-
test_selector_all(':has(.x:scope + .a > .b)', [d21]);
180-
test_selector_all(':has(.x:scope + .a .b)', [d21, d24]);
181-
test_selector_all(':has(.x:scope + .a + .b)', [d28, d32, d37]);
182-
test_selector_all(':has(.x:scope + .a ~ .b)', [d19, d21, d24, d28, d32, d37, d40]);
183-
test_selector_all(':has(:scope.x + .a)', [d19, d21, d24, d28, d32, d37, d40, d46]);
184-
test_selector_all(':has(:scope.x + .a > .b)', [d21]);
185-
test_selector_all(':has(:scope.x + .a .b)', [d21, d24]);
186-
test_selector_all(':has(:scope.x + .a + .b)', [d28, d32, d37]);
187-
test_selector_all(':has(:scope.x + .a ~ .b)', [d19, d21, d24, d28, d32, d37, d40]);
153+
test_selector_all('.x:has(+ .a)', [d19, d21, d24, d28, d32, d37, d40, d46]);
154+
test_selector_all('.x:has(+ .a > .b)', [d21]);
155+
test_selector_all('.x:has(+ .a .b)', [d21, d24]);
156+
test_selector_all('.x:has(+ .a + .b)', [d28, d32, d37]);
157+
test_selector_all('.x:has(+ .a ~ .b)', [d19, d21, d24, d28, d32, d37, d40]);
188158

189-
test_selector_all('.x:has(:scope ~ .a)', [d18, d19, d21, d24, d28, d32, d37, d40, d46]);
190-
test_selector_all('.x:has(:scope ~ .a > .b)', [d18, d19, d21]);
191-
test_selector_all('.x:has(:scope ~ .a .b)', [d18, d19, d21, d24]);
192-
test_selector_all('.x:has(:scope ~ .a + .b)', [d18, d19, d21, d24, d28, d32, d37]);
193-
test_selector_all('.x:has(:scope ~ .a + .b > .c)', [d18, d19, d21, d24, d28]);
194-
test_selector_all('.x:has(:scope ~ .a + .b .c)', [d18, d19, d21, d24, d28, d32]);
195-
test_selector_all(':has(.x:scope ~ .a)', [d18, d19, d21, d24, d28, d32, d37, d40, d46]);
196-
test_selector_all(':has(.x:scope ~ .a > .b)', [d18, d19, d21]);
197-
test_selector_all(':has(.x:scope ~ .a .b)', [d18, d19, d21, d24]);
198-
test_selector_all(':has(.x:scope ~ .a + .b)', [d18, d19, d21, d24, d28, d32, d37]);
199-
test_selector_all(':has(.x:scope ~ .a + .b > .c)', [d18, d19, d21, d24, d28]);
200-
test_selector_all(':has(.x:scope ~ .a + .b .c)', [d18, d19, d21, d24, d28, d32]);
201-
test_selector_all(':has(:scope.x ~ .a)', [d18, d19, d21, d24, d28, d32, d37, d40, d46]);
202-
test_selector_all(':has(:scope.x ~ .a > .b)', [d18, d19, d21]);
203-
test_selector_all(':has(:scope.x ~ .a .b)', [d18, d19, d21, d24]);
204-
test_selector_all(':has(:scope.x ~ .a + .b)', [d18, d19, d21, d24, d28, d32, d37]);
205-
test_selector_all(':has(:scope.x ~ .a + .b > .c)', [d18, d19, d21, d24, d28]);
206-
test_selector_all(':has(:scope.x ~ .a + .b .c)', [d18, d19, d21, d24, d28, d32]);
159+
test_selector_all('.x:has(~ .a)', [d18, d19, d21, d24, d28, d32, d37, d40, d46]);
160+
test_selector_all('.x:has(~ .a > .b)', [d18, d19, d21]);
161+
test_selector_all('.x:has(~ .a .b)', [d18, d19, d21, d24]);
162+
test_selector_all('.x:has(~ .a + .b)', [d18, d19, d21, d24, d28, d32, d37]);
163+
test_selector_all('.x:has(~ .a + .b > .c)', [d18, d19, d21, d24, d28]);
164+
test_selector_all('.x:has(~ .a + .b .c)', [d18, d19, d21, d24, d28, d32]);
207165

208166
test_selector_all('.x:has(.d .e)', [d48, d49, d50]);
209167
test_selector_all('.x:has(.d .e) .f', [d54]);
210-
test_selector_all('.x:has(:scope .d .e)', [d48, d49, d50]);
211-
test_selector_all('.x:has(:scope .d .e) .f', [d54]);
212-
test_selector_all('.x:has(:scope > .d)', [d49, d50]);
213-
test_selector_all('.x:has(:scope > .d) .f', [d54]);
214-
test_selector_all('.x:has(:scope ~ .d ~ .e)', [d48, d55, d56]);
215-
test_selector_all('.x:has(:scope ~ .d ~ .e) ~ .f', [d60]);
216-
test_selector_all('.x:has(:scope + .d ~ .e)', [d55, d56]);
217-
test_selector_all('.x:has(:scope + .d ~ .e) ~ .f', [d60]);
218-
test_selector_all(':has(.x:scope .d .e)', [d48, d49, d50]);
219-
test_selector_all(':has(.x:scope .d .e) .f', [d54]);
220-
test_selector_all(':has(.x:scope > .d)', [d49, d50]);
221-
test_selector_all(':has(.x:scope > .d) .f', [d54]);
222-
test_selector_all(':has(.x:scope ~ .d ~ .e)', [d48, d55, d56]);
223-
test_selector_all(':has(.x:scope ~ .d ~ .e) ~ .f', [d60]);
224-
test_selector_all(':has(.x:scope + .d ~ .e)', [d55, d56]);
225-
test_selector_all(':has(.x:scope + .d ~ .e) ~ .f', [d60]);
226-
test_selector_all(':has(:scope.x .d .e)', [d48, d49, d50]);
227-
test_selector_all(':has(:scope.x .d .e) .f', [d54]);
228-
test_selector_all(':has(:scope.x > .d)', [d49, d50]);
229-
test_selector_all(':has(:scope.x > .d) .f', [d54]);
230-
test_selector_all(':has(:scope.x ~ .d ~ .e)', [d48, d55, d56]);
231-
test_selector_all(':has(:scope.x ~ .d ~ .e) ~ .f', [d60]);
232-
test_selector_all(':has(:scope.x + .d ~ .e)', [d55, d56]);
233-
test_selector_all(':has(:scope.x + .d ~ .e) ~ .f', [d60]);
168+
test_selector_all('.x:has(> .d)', [d49, d50]);
169+
test_selector_all('.x:has(> .d) .f', [d54]);
170+
test_selector_all('.x:has(~ .d ~ .e)', [d48, d55, d56]);
171+
test_selector_all('.x:has(~ .d ~ .e) ~ .f', [d60]);
172+
test_selector_all('.x:has(+ .d ~ .e)', [d55, d56]);
173+
test_selector_all('.x:has(+ .d ~ .e) ~ .f', [d60]);
234174

235-
test_selector_all('.y:has(:scope > .g .h)', [d63, d71])
236-
test_selector_all('.y:has(:scope .g .h)', [d63, d68, d71])
237-
test_selector_all('.y:has(:scope > .g .h) .i', [d67, d75])
238-
test_selector_all('.y:has(:scope .g .h) .i', [d67, d75])
239-
test_selector_all('.x:has(:scope + .y:has(:scope > .g .h) .i)', [d62, d70])
240-
test_selector_all('.x:has(:scope + .y:has(:scope .g .h) .i)', [d62, d63, d70])
241-
test_selector_all('.x:has(:scope + .y:has(:scope > .g .h) .i) ~ .j', [d77, d80])
242-
test_selector_all('.x:has(:scope + .y:has(:scope .g .h) .i) ~ .j', [d77, d80])
243-
test_selector_all('.x:has(:scope ~ .y:has(:scope > .g .h) .i)', [d61, d62, d69, d70])
244-
test_selector_all('.x:has(:scope ~ .y:has(:scope .g .h) .i)', [d61, d62, d63, d69, d70])
245-
test_selector_all(':has(.y:scope > .g .h)', [d63, d71])
246-
test_selector_all(':has(.y:scope .g .h)', [d63, d68, d71])
247-
test_selector_all(':has(.y:scope > .g .h) .i', [d67, d75])
248-
test_selector_all(':has(.y:scope .g .h) .i', [d67, d75])
249-
test_selector_all(':has(.x:scope + :has(.y:scope > .g .h) .i)', [d62, d70])
250-
test_selector_all(':has(.x:scope + :has(.y:scope .g .h) .i)', [d62, d63, d70])
251-
test_selector_all(':has(.x:scope + :has(.y:scope > .g .h) .i) ~ .j', [d77, d80])
252-
test_selector_all(':has(.x:scope + :has(.y:scope .g .h) .i) ~ .j', [d77, d80])
253-
test_selector_all(':has(.x:scope ~ :has(.y:scope > .g .h) .i)', [d61, d62, d69, d70])
254-
test_selector_all(':has(.x:scope ~ :has(.y:scope .g .h) .i)', [d61, d62, d63, d69, d70])
255-
test_selector_all(':has(:scope.y > .g .h)', [d63, d71])
256-
test_selector_all(':has(:scope.y .g .h)', [d63, d68, d71])
257-
test_selector_all(':has(:scope.y > .g .h) .i', [d67, d75])
258-
test_selector_all(':has(:scope.y .g .h) .i', [d67, d75])
259-
test_selector_all(':has(:scope.x + :has(:scope.y > .g .h) .i)', [d62, d70])
260-
test_selector_all(':has(:scope.x + :has(:scope.y .g .h) .i)', [d62, d63, d70])
261-
test_selector_all(':has(:scope.x + :has(:scope.y > .g .h) .i) ~ .j', [d77, d80])
262-
test_selector_all(':has(:scope.x + :has(:scope.y .g .h) .i) ~ .j', [d77, d80])
263-
test_selector_all(':has(:scope.x ~ :has(:scope.y > .g .h) .i)', [d61, d62, d69, d70])
264-
test_selector_all(':has(:scope.x ~ :has(:scope.y .g .h) .i)', [d61, d62, d63, d69, d70])
175+
test_selector_all('.y:has(> .g .h)', [d63, d71])
176+
test_selector_all('.y:has(.g .h)', [d63, d68, d71])
177+
test_selector_all('.y:has(> .g .h) .i', [d67, d75])
178+
test_selector_all('.y:has(.g .h) .i', [d67, d75])
179+
test_selector_all('.x:has(+ .y:has(> .g .h) .i)', [d62, d70])
180+
test_selector_all('.x:has(+ .y:has(.g .h) .i)', [d62, d63, d70])
181+
test_selector_all('.x:has(+ .y:has(> .g .h) .i) ~ .j', [d77, d80])
182+
test_selector_all('.x:has(+ .y:has(.g .h) .i) ~ .j', [d77, d80])
183+
test_selector_all('.x:has(~ .y:has(> .g .h) .i)', [d61, d62, d69, d70])
184+
test_selector_all('.x:has(~ .y:has(.g .h) .i)', [d61, d62, d63, d69, d70])
265185

266-
test_selector_all('.x:has(.d :scope .e)', [d51, d52])
267-
test_selector_all(':has(.d .x:scope .e)', [d51, d52])
268-
test_selector_all(':has(.d :scope.x .e)', [d51, d52])
186+
test_selector_all('.d .x:has(.e)', [d51, d52])
269187

270-
test_selector_all('.x:has(.d ~ :scope ~ .e)', [d57, d58])
271-
test_selector_all(':has(.d ~ .x:scope ~ .e)', [d57, d58])
272-
test_selector_all(':has(.d ~ :scope.x ~ .e)', [d57, d58])
188+
test_selector_all('.d ~ .x:has(~ .e)', [d57, d58])
273189

274-
test_selector_all(':has(:scope .d :scope)', [])
275-
test_selector_all(':has(:scope ~ .d ~ :scope)', [])
276190
</script>

testing/web-platform/tests/css/selectors/parsing/parse-has.html

-13
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,9 @@
1616
test_valid_selector(':has(:hover)');
1717
test_valid_selector('*:has(.a)', ['*:has(.a)', ':has(.a)']);
1818
test_valid_selector('.a:has(.b)');
19-
test_valid_selector('.a:has(:scope .b)');
20-
test_valid_selector(':has(.a:scope .b)');
21-
test_valid_selector(':has(.a .b:scope)');
2219
test_valid_selector('.a:has(> .b)');
23-
test_valid_selector('.a:has(:scope > .b)');
24-
test_valid_selector(':has(.a:scope > .b)');
25-
test_valid_selector(':has(> .a .b:scope)');
2620
test_valid_selector('.a:has(~ .b)');
27-
test_valid_selector('.a:has(:scope ~ .b)');
28-
test_valid_selector(':has(.a:scope ~ .b)');
29-
test_valid_selector(':has(~ .a .b:scope)');
3021
test_valid_selector('.a:has(+ .b)');
31-
test_valid_selector('.a:has(:scope + .b)');
32-
test_valid_selector(':has(.a:scope + .b)');
33-
test_valid_selector(':has(+ .a .b:scope)');
34-
test_valid_selector('.a:has(:scope .b :scope)');
3522
test_valid_selector('.a:has(.b) .c');
3623
test_valid_selector('.a .b:has(.c)');
3724
test_valid_selector('.a .b:has(.c .d)');

0 commit comments

Comments
 (0)