Skip to content

Commit 73c50cc

Browse files
committed
Update details and hidden=until-found ancestor revealing algorithm
https://bugs.webkit.org/show_bug.cgi?id=296797 rdar://157280008 Reviewed by Darin Adler. Follow whatwg/html#11457 which: - Prevents infinite looping due to mutations on the beforematch event, by collecting the ancestors beforehand (covered by beforematch-infinite-loop.html) - Combines both details and hidden=until-found ancestor revealing algorithms into a single one, to avoid two separate traversals (covered by hidden-until-found-and-details.html) - Adds checks for attribute/element removal mutations during the beforematch event (covered by removal WPTs) Also write WPTs for previously uncovered parts. * LayoutTests/imported/w3c/web-platform-tests/html/editing/the-hidden-attribute/beforematch-attribute-removal-001-expected.txt: Added. * LayoutTests/imported/w3c/web-platform-tests/html/editing/the-hidden-attribute/beforematch-attribute-removal-001.html: Added. * LayoutTests/imported/w3c/web-platform-tests/html/editing/the-hidden-attribute/beforematch-attribute-removal-002-expected.txt: Added. * LayoutTests/imported/w3c/web-platform-tests/html/editing/the-hidden-attribute/beforematch-attribute-removal-002.html: Added. * LayoutTests/imported/w3c/web-platform-tests/html/editing/the-hidden-attribute/beforematch-element-removal-001-expected.txt: Added. * LayoutTests/imported/w3c/web-platform-tests/html/editing/the-hidden-attribute/beforematch-element-removal-001.html: Added. * LayoutTests/imported/w3c/web-platform-tests/html/editing/the-hidden-attribute/beforematch-element-removal-002-expected.txt: Added. * LayoutTests/imported/w3c/web-platform-tests/html/editing/the-hidden-attribute/beforematch-element-removal-002.html: Added. * LayoutTests/imported/w3c/web-platform-tests/html/editing/the-hidden-attribute/beforematch-infinite-loop-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/html/editing/the-hidden-attribute/hidden-until-found-and-details-expected.txt: Added. * LayoutTests/imported/w3c/web-platform-tests/html/editing/the-hidden-attribute/hidden-until-found-and-details.html: Added. * Source/WebCore/dom/FindRevealAlgorithms.cpp: (WebCore::revealClosedDetailsAndHiddenUntilFoundAncestors): (WebCore::revealClosedDetailsAncestors): Deleted. (WebCore::revealHiddenUntilFoundAncestors): Deleted. * Source/WebCore/dom/FindRevealAlgorithms.h: Canonical link: https://commits.webkit.org/298164@main
1 parent b7e1d1a commit 73c50cc

13 files changed

+350
-37
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
PASS hidden=until-found and details revealing algorithm should abort if attribute states are mutated on beforematch events.
3+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!DOCTYPE html>
2+
<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
3+
<link rel="help" href="https://html.spec.whatwg.org/#ancestor-revealing-algorithm">
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
7+
<details id="a3">
8+
<div id="a2" hidden="until-found">
9+
<details id="a1" hidden="until-found">
10+
<div id="a1child">Hidden</div>
11+
</details>
12+
</div>
13+
</details>
14+
15+
<script>
16+
function test_state({ a1open, a1hidden, a2hidden, a3open }) {
17+
assert_equals(a1.open, a1open, `a1 should ${a1open ? "" : "not "}be open`);
18+
assert_equals(a1.hidden, a1hidden ? "until-found" : false, `a1 should ${a1hidden ? "" : "not "}be hidden`);
19+
assert_equals(a2.hidden, a2hidden ? "until-found" : false, `a2 should ${a2hidden ? "" : "not "}be hidden`);
20+
assert_equals(a3.open, a3open, `a3 should ${a3open ? "" : "not "}be open`);
21+
}
22+
t = async_test("hidden=until-found and details revealing algorithm should abort if attribute states are mutated on beforematch events.");
23+
test_state({
24+
a1open: false,
25+
a1hidden: true,
26+
a2hidden: true,
27+
a3open: false
28+
});
29+
a1.addEventListener("beforematch", t.step_func(() => {
30+
test_state({
31+
a1open: true, // We find the <details> element before finding hidden=until-found as a consequence of tree-traversal order.
32+
a1hidden: true, // hidden=until-found removal happens after beforematch event.
33+
a2hidden: true,
34+
a3open: false
35+
});
36+
a2.addEventListener("beforematch", t.step_func((e) => {
37+
assert_equals(e.target, a1, "a1 beforematch event bubbles up");
38+
// No change in state, since it's part of the same event dispatch as above.
39+
test_state({
40+
a1open: true,
41+
a1hidden: true,
42+
a2hidden: true,
43+
a3open: false
44+
});
45+
a1.hidden = true;
46+
a2.addEventListener("beforematch", t.unreached_func("Algorithm should have aborted due to hidden attribute no longer being in the until-found state."));
47+
a3.addEventListener("toggle", t.unreached_func("Algorithm should have aborted due to hidden attribute no longer being in the until-found state."));
48+
t.done();
49+
}), { once: true });
50+
}), { once: true });
51+
52+
location.hash = "#a1child";
53+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Hidden
2+
3+
PASS hidden=until-found and details revealing algorithm should abort if attribute states are mutated on beforematch events.
4+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!DOCTYPE html>
2+
<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
3+
<link rel="help" href="https://html.spec.whatwg.org/#ancestor-revealing-algorithm">
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
7+
<details id="a3">
8+
<div id="a2" hidden="until-found">
9+
<details id="a1" hidden="until-found">
10+
<div id="a1child">Hidden</div>
11+
</details>
12+
</div>
13+
</details>
14+
15+
<script>
16+
function test_state({ a1open, a1hidden, a2hidden, a3open }) {
17+
assert_equals(a1.open, a1open, `a1 should ${a1open ? "" : "not "}be open`);
18+
assert_equals(a1.hidden, a1hidden ? "until-found" : false, `a1 should ${a1hidden ? "" : "not "}be hidden`);
19+
assert_equals(a2.hidden, a2hidden ? "until-found" : false, `a2 should ${a2hidden ? "" : "not "}be hidden`);
20+
assert_equals(a3.open, a3open, `a3 should ${a3open ? "" : "not "}be open`);
21+
}
22+
t = async_test("hidden=until-found and details revealing algorithm should abort if attribute states are mutated on beforematch events.");
23+
test_state({
24+
a1open: false,
25+
a1hidden: true,
26+
a2hidden: true,
27+
a3open: false
28+
});
29+
a1.addEventListener("beforematch", t.step_func(() => {
30+
test_state({
31+
a1open: true, // We find the <details> element before finding hidden=until-found as a consequence of tree-traversal order.
32+
a1hidden: true, // hidden=until-found removal happens after beforematch event.
33+
a2hidden: true,
34+
a3open: false
35+
});
36+
a2.addEventListener("beforematch", t.step_func((e) => {
37+
assert_equals(e.target, a1, "a1 beforematch event bubbles up");
38+
// No change in state, since it's part of the same event dispatch as above.
39+
test_state({
40+
a1open: true,
41+
a1hidden: true,
42+
a2hidden: true,
43+
a3open: false
44+
});
45+
a1.hidden = false;
46+
a2.addEventListener("beforematch", t.unreached_func("Algorithm should have aborted due to hidden attribute no longer being in the until-found state."));
47+
a3.addEventListener("toggle", t.unreached_func("Algorithm should have aborted due to hidden attribute no longer being in the until-found state."));
48+
t.done();
49+
}), { once: true });
50+
}), { once: true });
51+
52+
location.hash = "#a1child";
53+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
PASS hidden=until-found and details revealing algorithm should abort if revealed node is removed.
3+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!DOCTYPE html>
2+
<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
3+
<link rel="help" href="https://html.spec.whatwg.org/#ancestor-revealing-algorithm">
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
7+
<details id="a3">
8+
<div id="a2" hidden="until-found">
9+
<details id="a1" hidden="until-found">
10+
<div id="a1child">Hidden</div>
11+
</details>
12+
</div>
13+
</details>
14+
15+
<script>
16+
function test_state({ a1open, a1hidden, a2hidden, a3open }) {
17+
assert_equals(a1.open, a1open, `a1 should ${a1open ? "" : "not "}be open`);
18+
assert_equals(a1.hidden, a1hidden ? "until-found" : false, `a1 should ${a1hidden ? "" : "not "}be hidden`);
19+
assert_equals(a2.hidden, a2hidden ? "until-found" : false, `a2 should ${a2hidden ? "" : "not "}be hidden`);
20+
assert_equals(a3.open, a3open, `a3 should ${a3open ? "" : "not "}be open`);
21+
}
22+
t = async_test("hidden=until-found and details revealing algorithm should abort if revealed node is removed.");
23+
test_state({
24+
a1open: false,
25+
a1hidden: true,
26+
a2hidden: true,
27+
a3open: false
28+
});
29+
a1.addEventListener("beforematch", t.step_func(() => {
30+
test_state({
31+
a1open: true, // We find the <details> element before finding hidden=until-found as a consequence of tree-traversal order.
32+
a1hidden: true, // hidden=until-found removal happens after beforematch event.
33+
a2hidden: true,
34+
a3open: false
35+
});
36+
a2.addEventListener("beforematch", t.step_func((e) => {
37+
assert_equals(e.target, a1, "a1 beforematch event bubbles up");
38+
// No change in state, since it's part of the same event dispatch as above.
39+
test_state({
40+
a1open: true,
41+
a1hidden: true,
42+
a2hidden: true,
43+
a3open: false
44+
});
45+
a1.remove();
46+
a2.addEventListener("beforematch", t.unreached_func("Algorithm should have aborted due to node removal."));
47+
a3.addEventListener("toggle", t.unreached_func("Algorithm should have aborted due to node removal."));
48+
t.done();
49+
}), { once: true });
50+
}), { once: true });
51+
52+
location.hash = "#a1child";
53+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
PASS hidden=until-found and details revealing algorithm should abort if attribute states are mutated on beforematch events.
3+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<!DOCTYPE html>
2+
<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
3+
<link rel="help" href="https://html.spec.whatwg.org/#ancestor-revealing-algorithm">
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
7+
<details id="a4">
8+
<details id="a3">
9+
<div id="a2" hidden="until-found">
10+
<details id="a1" hidden="until-found">
11+
<div id="a1child">Hidden</div>
12+
</details>
13+
</div>
14+
</details>
15+
</details>
16+
17+
<script>
18+
function test_state({ a1open, a1hidden, a2hidden, a3open }) {
19+
assert_equals(a1.open, a1open, `a1 should ${a1open ? "" : "not "}be open`);
20+
assert_equals(a1.hidden, a1hidden ? "until-found" : false, `a1 should ${a1hidden ? "" : "not "}be hidden`);
21+
assert_equals(a2.hidden, a2hidden ? "until-found" : false, `a2 should ${a2hidden ? "" : "not "}be hidden`);
22+
assert_equals(a3.open, a3open, `a3 should ${a3open ? "" : "not "}be open`);
23+
}
24+
t = async_test("hidden=until-found and details revealing algorithm should abort if attribute states are mutated on beforematch events.");
25+
test_state({
26+
a1open: false,
27+
a1hidden: true,
28+
a2hidden: true,
29+
a3open: false
30+
});
31+
a1.addEventListener("beforematch", t.step_func(() => {
32+
test_state({
33+
a1open: true, // We find the <details> element before finding hidden=until-found as a consequence of tree-traversal order.
34+
a1hidden: true, // hidden=until-found removal happens after beforematch event.
35+
a2hidden: true,
36+
a3open: false
37+
});
38+
a2.addEventListener("beforematch", t.step_func((e) => {
39+
assert_equals(e.target, a1, "a1 beforematch event bubbles up");
40+
// No change in state, since it's part of the same event dispatch as above.
41+
test_state({
42+
a1open: true,
43+
a1hidden: true,
44+
a2hidden: true,
45+
a3open: false
46+
});
47+
a1.hidden = false;
48+
a2.addEventListener("beforematch", t.step_func((e) => {
49+
assert_equals(e.target, a2, "beforematch event for a2");
50+
test_state({
51+
a1open: true,
52+
a1hidden: false, // a1 was revealed after its beforematch event.
53+
a2hidden: true,
54+
a3open: false
55+
});
56+
a3.addEventListener("toggle", t.unreached_func("Algorithm should have aborted due to element removal."));
57+
a3.remove();
58+
a4.addEventListener("toggle", t.unreached_func("Algorithm should have aborted due to element removal."));
59+
t.done();
60+
}), { once: true });
61+
}), { once: true });
62+
}), { once: true });
63+
64+
location.hash = "#a1child";
65+
</script>
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
hidden 2
22
hidden 1
33

4-
FAIL hidden=until-found revealing algorithm should collect elements to reveal before revealing them. assert_false: beforematch should not have been fired on h2. expected false got true
4+
PASS hidden=until-found revealing algorithm should collect elements to reveal before revealing them.
55

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Hidden
2+
3+
PASS hidden=until-found and details revealing algorithm should reveal elements in a certain order.
4+

0 commit comments

Comments
 (0)