-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AX: display:contents elements are sometimes missing their children
https://bugs.webkit.org/show_bug.cgi?id=259608 rdar://problem/113044333 Reviewed by Chris Fleizach. This happened because AccessibilityObject::insertChild detects and bails when an object is trying to insert a child that belongs to a display:contents object that is not `this`. But if the correct parent of that child does not have it’s dirty-children bit set, we never actually insert the child, resulting in it being missing from the accessibility tree. With this patch, we set that bit, ensuring the accessibility tree is updated correctly. * LayoutTests/accessibility/display-contents/table-dynamic-expected.txt: Added. * LayoutTests/accessibility/display-contents/table-dynamic.html: Added. * LayoutTests/platform/ios/accessibility/display-contents/table-dynamic-expected.txt: Added. * Source/WebCore/accessibility/AccessibilityObject.cpp: (WebCore::AccessibilityObject::insertChild): Canonical link: https://commits.webkit.org/266407@main
- Loading branch information
Showing
5 changed files
with
474 additions
and
1 deletion.
There are no files selected for viewing
184 changes: 184 additions & 0 deletions
184
LayoutTests/accessibility/display-contents/table-dynamic-expected.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
This test ensures that display:contents tables with dynamic content are accessible. | ||
|
||
Dumping table #t0 (expecting row count 3, column count 2) | ||
PASS: table.rowCount === 3 | ||
PASS: table.columnCount === 2 | ||
PASS: table.cellForColumnAndRow(0, 0).domIdentifier === "r0c0-t0" | ||
PASS: table.cellForColumnAndRow(1, 0).domIdentifier === "r0c1-t0" | ||
PASS: table.cellForColumnAndRow(0, 1).domIdentifier === "r1c0-t0" | ||
PASS: table.cellForColumnAndRow(1, 1).domIdentifier === "r1c1-t0" | ||
PASS: table.cellForColumnAndRow(0, 2).domIdentifier === "r2c0-t0" | ||
PASS: table.cellForColumnAndRow(1, 2).domIdentifier === "r2c1-t0" | ||
Dumping table #t1 (expecting row count 3, column count 2) | ||
PASS: table.rowCount === 3 | ||
PASS: table.columnCount === 2 | ||
PASS: table.cellForColumnAndRow(0, 0).domIdentifier === "r0c0-t1" | ||
PASS: table.cellForColumnAndRow(1, 0).domIdentifier === "r0c1-t1" | ||
PASS: table.cellForColumnAndRow(0, 1).domIdentifier === "r1c0-t1" | ||
PASS: table.cellForColumnAndRow(1, 1).domIdentifier === "r1c1-t1" | ||
PASS: table.cellForColumnAndRow(0, 2).domIdentifier === "r2c0-t1" | ||
PASS: table.cellForColumnAndRow(1, 2).domIdentifier === "r2c1-t1" | ||
|
||
Performing search traversal of body. | ||
|
||
{#t0 AXRole: AXTable} (parent: {#body AXRole: AXGroup}) | ||
|
||
{#r0-t0 AXRole: AXRow} (parent: {#t0 AXRole: AXTable}) | ||
|
||
{#r0c0-t0 AXRole: AXCell} (parent: {#r0-t0 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T0) Author} (parent: {#r0c0-t0 AXRole: AXCell}) | ||
|
||
{#r0c1-t0 AXRole: AXCell} (parent: {#r0-t0 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T0) Title} (parent: {#r0c1-t0 AXRole: AXCell}) | ||
|
||
{#r1-t0 AXRole: AXRow} (parent: {#t0 AXRole: AXTable}) | ||
|
||
{#r1c0-t0 AXRole: AXCell} (parent: {#r1-t0 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T0) Stephen Hawking} (parent: {#r1c0-t0 AXRole: AXCell}) | ||
|
||
{#r1c1-t0 AXRole: AXCell} (parent: {#r1-t0 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T0) A Brief History of Time} (parent: {#r1c1-t0 AXRole: AXCell}) | ||
|
||
{#r2-t0 AXRole: AXRow} (parent: {#t0 AXRole: AXTable}) | ||
|
||
{#r2c0-t0 AXRole: AXCell} (parent: {#r2-t0 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T0) Carl Sagan} (parent: {#r2c0-t0 AXRole: AXCell}) | ||
|
||
{#r2c1-t0 AXRole: AXCell} (parent: {#r2-t0 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T0) Cosmos} (parent: {#r2c1-t0 AXRole: AXCell}) | ||
|
||
{#t1 AXRole: AXTable} (parent: {#body AXRole: AXGroup}) | ||
|
||
{#r0-t1 AXRole: AXRow} (parent: {#t1 AXRole: AXTable}) | ||
|
||
{#r0c0-t1 AXRole: AXCell} (parent: {#r0-t1 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T1) Author} (parent: {#r0c0-t1 AXRole: AXCell}) | ||
|
||
{#r0c1-t1 AXRole: AXCell} (parent: {#r0-t1 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T1) Title} (parent: {#r0c1-t1 AXRole: AXCell}) | ||
|
||
{#r1-t1 AXRole: AXRow} (parent: {#t1 AXRole: AXTable}) | ||
|
||
{#r1c0-t1 AXRole: AXCell} (parent: {#r1-t1 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T1) Stephen Hawking} (parent: {#r1c0-t1 AXRole: AXCell}) | ||
|
||
{#r1c1-t1 AXRole: AXCell} (parent: {#r1-t1 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T1) A Brief History of Time} (parent: {#r1c1-t1 AXRole: AXCell}) | ||
|
||
{#r2-t1 AXRole: AXRow} (parent: {#t1 AXRole: AXTable}) | ||
|
||
{#r2c0-t1 AXRole: AXCell} (parent: {#r2-t1 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T1) Carl Sagan} (parent: {#r2c0-t1 AXRole: AXCell}) | ||
|
||
{#r2c1-t1 AXRole: AXCell} (parent: {#r2-t1 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T1) Cosmos} (parent: {#r2c1-t1 AXRole: AXCell}) | ||
|
||
Moving #r2-t1 from table one to table zero. | ||
Dumping table #t0 (expecting row count 4, column count 2) | ||
PASS: table.rowCount === 4 | ||
PASS: table.columnCount === 2 | ||
table.cellForColumnAndRow(0, 0).domIdentifier is r0c0-t0 | ||
table.cellForColumnAndRow(1, 0).domIdentifier is r0c1-t0 | ||
table.cellForColumnAndRow(0, 1).domIdentifier is r1c0-t0 | ||
table.cellForColumnAndRow(1, 1).domIdentifier is r1c1-t0 | ||
table.cellForColumnAndRow(0, 2).domIdentifier is r2c0-t0 | ||
table.cellForColumnAndRow(1, 2).domIdentifier is r2c1-t0 | ||
table.cellForColumnAndRow(0, 3).domIdentifier is r2c0-t1 | ||
table.cellForColumnAndRow(1, 3).domIdentifier is r2c1-t1 | ||
Dumping table #t1 (expecting row count 2, column count 2) | ||
PASS: table.rowCount === 2 | ||
PASS: table.columnCount === 2 | ||
PASS: table.cellForColumnAndRow(0, 0).domIdentifier === "r0c0-t1" | ||
PASS: table.cellForColumnAndRow(1, 0).domIdentifier === "r0c1-t1" | ||
PASS: table.cellForColumnAndRow(0, 1).domIdentifier === "r1c0-t1" | ||
PASS: table.cellForColumnAndRow(1, 1).domIdentifier === "r1c1-t1" | ||
|
||
Performing search traversal of body. | ||
|
||
{#t0 AXRole: AXTable} (parent: {#body AXRole: AXGroup}) | ||
|
||
{#r0-t0 AXRole: AXRow} (parent: {#t0 AXRole: AXTable}) | ||
|
||
{#r0c0-t0 AXRole: AXCell} (parent: {#r0-t0 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T0) Author} (parent: {#r0c0-t0 AXRole: AXCell}) | ||
|
||
{#r0c1-t0 AXRole: AXCell} (parent: {#r0-t0 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T0) Title} (parent: {#r0c1-t0 AXRole: AXCell}) | ||
|
||
{#r1-t0 AXRole: AXRow} (parent: {#t0 AXRole: AXTable}) | ||
|
||
{#r1c0-t0 AXRole: AXCell} (parent: {#r1-t0 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T0) Stephen Hawking} (parent: {#r1c0-t0 AXRole: AXCell}) | ||
|
||
{#r1c1-t0 AXRole: AXCell} (parent: {#r1-t0 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T0) A Brief History of Time} (parent: {#r1c1-t0 AXRole: AXCell}) | ||
|
||
{#r2-t0 AXRole: AXRow} (parent: {#t0 AXRole: AXTable}) | ||
|
||
{#r2c0-t0 AXRole: AXCell} (parent: {#r2-t0 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T0) Carl Sagan} (parent: {#r2c0-t0 AXRole: AXCell}) | ||
|
||
{#r2c1-t0 AXRole: AXCell} (parent: {#r2-t0 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T0) Cosmos} (parent: {#r2c1-t0 AXRole: AXCell}) | ||
|
||
{#r2-t1 AXRole: AXRow} (parent: {#t0 AXRole: AXTable}) | ||
|
||
{#r2c0-t1 AXRole: AXCell} (parent: {#r2-t1 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T1) Carl Sagan} (parent: {#r2c0-t1 AXRole: AXCell}) | ||
|
||
{#r2c1-t1 AXRole: AXCell} (parent: {#r2-t1 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T1) Cosmos} (parent: {#r2c1-t1 AXRole: AXCell}) | ||
|
||
{#t1 AXRole: AXTable} (parent: {#body AXRole: AXGroup}) | ||
|
||
{#r0-t1 AXRole: AXRow} (parent: {#t1 AXRole: AXTable}) | ||
|
||
{#r0c0-t1 AXRole: AXCell} (parent: {#r0-t1 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T1) Author} (parent: {#r0c0-t1 AXRole: AXCell}) | ||
|
||
{#r0c1-t1 AXRole: AXCell} (parent: {#r0-t1 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T1) Title} (parent: {#r0c1-t1 AXRole: AXCell}) | ||
|
||
{#r1-t1 AXRole: AXRow} (parent: {#t1 AXRole: AXTable}) | ||
|
||
{#r1c0-t1 AXRole: AXCell} (parent: {#r1-t1 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T1) Stephen Hawking} (parent: {#r1c0-t1 AXRole: AXCell}) | ||
|
||
{#r1c1-t1 AXRole: AXCell} (parent: {#r1-t1 AXRole: AXRow}) | ||
|
||
{AXRole: AXStaticText AXValue: (T1) A Brief History of Time} (parent: {#r1c1-t1 AXRole: AXCell}) | ||
|
||
PASS successfullyParsed is true | ||
|
||
TEST COMPLETE | ||
Table zero caption | ||
(T0) Author (T0) Title | ||
(T0) Stephen Hawking (T0) A Brief History of Time | ||
(T0) Carl Sagan (T0) Cosmos | ||
(T1) Carl Sagan (T1) Cosmos | ||
Table one caption | ||
(T1) Author (T1) Title | ||
(T1) Stephen Hawking (T1) A Brief History of Time |
126 changes: 126 additions & 0 deletions
126
LayoutTests/accessibility/display-contents/table-dynamic.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> | ||
<html> | ||
<head> | ||
<script src="../../resources/accessibility-helper.js"></script> | ||
<script src="../../resources/js-test.js"></script> | ||
<style> | ||
td, th { display: contents; } | ||
</style> | ||
</head> | ||
<body role="group" id="body"> | ||
|
||
<table id="t0"> | ||
<caption>Table zero caption</caption> | ||
<thead> | ||
<tr id="r0-t0"> | ||
<th id="r0c0-t0">(T0) Author</th> | ||
<th id="r0c1-t0">(T0) Title</th> | ||
</tr> | ||
</thead> | ||
<tbody id="tbody-t0"> | ||
<tr id="r1-t0"> | ||
<td id="r1c0-t0">(T0) Stephen Hawking</td> | ||
<td id="r1c1-t0">(T0) A Brief History of Time</td> | ||
</tr> | ||
<tr id="r2-t0"> | ||
<td id="r2c0-t0">(T0) Carl Sagan</td> | ||
<td id="r2c1-t0">(T0) Cosmos</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
|
||
<table id="t1"> | ||
<caption>Table one caption</caption> | ||
<thead> | ||
<tr id="r0-t1"> | ||
<th id="r0c0-t1">(T1) Author</th> | ||
<th id="r0c1-t1">(T1) Title</th> | ||
</tr> | ||
</thead> | ||
<tbody id="tbody-t1"> | ||
<tr id="r1-t1"> | ||
<td id="r1c0-t1">(T1) Stephen Hawking</td> | ||
<td id="r1c1-t1">(T1) A Brief History of Time</td> | ||
</tr> | ||
<tr id="r2-t1"> | ||
<td id="r2c0-t1">(T1) Carl Sagan</td> | ||
<td id="r2c1-t1">(T1) Cosmos</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
|
||
<script> | ||
var output = "This test ensures that display:contents tables with dynamic content are accessible.\n\n"; | ||
|
||
var table; | ||
function dumpTable(tableID, expectedRowCount, expectedColumnCount, useExpect = true) { | ||
output += `Dumping table #${tableID} (expecting row count ${expectedRowCount}, column count ${expectedColumnCount})\n`; | ||
table = accessibilityController.accessibleElementById(tableID); | ||
output += expect("table.rowCount", expectedRowCount); | ||
output += expect("table.columnCount", expectedColumnCount); | ||
|
||
for (let row = 0; row < expectedRowCount; row++) { | ||
for (let column = 0; column < expectedColumnCount; column++) { | ||
if (useExpect) | ||
output += expect(`table.cellForColumnAndRow(${column}, ${row}).domIdentifier`, `"r${row}c${column}-${tableID}"`); | ||
else | ||
output += `table.cellForColumnAndRow(${column}, ${row}).domIdentifier is ${table.cellForColumnAndRow(column, row).domIdentifier}\n`; | ||
} | ||
} | ||
} | ||
|
||
function traverseBody() { | ||
output += `\nPerforming search traversal of body.\n`; | ||
function elementDescription(axElement) { | ||
if (!axElement) | ||
return "null"; | ||
|
||
const role = axElement.role; | ||
const id = axElement.domIdentifier; | ||
let result = `${id ? `#${id} ` : ""}${role}`; | ||
if (role.includes("StaticText")) | ||
result += ` ${accessibilityController.platformName === "ios" ? axElement.description : axElement.stringValue}`; | ||
return result; | ||
} | ||
|
||
const container = accessibilityController.accessibleElementById("body"); | ||
let searchResult = null; | ||
while (true) { | ||
searchResult = container.uiElementForSearchPredicate(searchResult, true, "AXAnyTypeSearchKey", "", false); | ||
if (!searchResult) | ||
break; | ||
const parentOutput = accessibilityController.platformName === "ios" ? "" : ` (parent: {${elementDescription(searchResult.parentElement())}})`; | ||
output += `\n{${elementDescription(searchResult)}}${parentOutput}\n`; | ||
} | ||
} | ||
|
||
if (window.accessibilityController) { | ||
window.jsTestIsAsync = true; | ||
|
||
dumpTable("t0", 3, 2); | ||
dumpTable("t1", 3, 2); | ||
traverseBody(); | ||
|
||
output += "\nMoving #r2-t1 from table one to table zero.\n"; | ||
document.getElementById("tbody-t0").appendChild(document.getElementById("r2-t1")); | ||
setTimeout(async () => { | ||
await waitFor(() => { | ||
var tableZero = accessibilityController.accessibleElementById("t0"); | ||
var tableOne = accessibilityController.accessibleElementById("t1"); | ||
if (!tableZero || !tableOne) | ||
return false; | ||
return tableZero.rowCount >= 4 && tableOne.rowCount === 2; | ||
}); | ||
|
||
dumpTable("t0", 4, 2, false /* useExpect */); | ||
dumpTable("t1", 2, 2); | ||
traverseBody(); | ||
|
||
debug(output); | ||
finishJSTest(); | ||
}); | ||
} | ||
</script> | ||
</body> | ||
</html> | ||
|
56 changes: 56 additions & 0 deletions
56
LayoutTests/platform/glib/accessibility/display-contents/table-dynamic-expected.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
This test ensures that display:contents tables with dynamic content are accessible. | ||
|
||
Dumping table #t0 (expecting row count 3, column count 2) | ||
PASS: table.rowCount === 3 | ||
PASS: table.columnCount === 2 | ||
PASS: table.cellForColumnAndRow(0, 0).domIdentifier === "r0c0-t0" | ||
PASS: table.cellForColumnAndRow(1, 0).domIdentifier === "r0c1-t0" | ||
PASS: table.cellForColumnAndRow(0, 1).domIdentifier === "r1c0-t0" | ||
PASS: table.cellForColumnAndRow(1, 1).domIdentifier === "r1c1-t0" | ||
PASS: table.cellForColumnAndRow(0, 2).domIdentifier === "r2c0-t0" | ||
PASS: table.cellForColumnAndRow(1, 2).domIdentifier === "r2c1-t0" | ||
Dumping table #t1 (expecting row count 3, column count 2) | ||
PASS: table.rowCount === 3 | ||
PASS: table.columnCount === 2 | ||
PASS: table.cellForColumnAndRow(0, 0).domIdentifier === "r0c0-t1" | ||
PASS: table.cellForColumnAndRow(1, 0).domIdentifier === "r0c1-t1" | ||
PASS: table.cellForColumnAndRow(0, 1).domIdentifier === "r1c0-t1" | ||
PASS: table.cellForColumnAndRow(1, 1).domIdentifier === "r1c1-t1" | ||
PASS: table.cellForColumnAndRow(0, 2).domIdentifier === "r2c0-t1" | ||
PASS: table.cellForColumnAndRow(1, 2).domIdentifier === "r2c1-t1" | ||
|
||
Performing search traversal of body. | ||
|
||
Moving #r2-t1 from table one to table zero. | ||
Dumping table #t0 (expecting row count 4, column count 2) | ||
PASS: table.rowCount === 4 | ||
PASS: table.columnCount === 2 | ||
table.cellForColumnAndRow(0, 0).domIdentifier is r0c0-t0 | ||
table.cellForColumnAndRow(1, 0).domIdentifier is r0c1-t0 | ||
table.cellForColumnAndRow(0, 1).domIdentifier is r1c0-t0 | ||
table.cellForColumnAndRow(1, 1).domIdentifier is r1c1-t0 | ||
table.cellForColumnAndRow(0, 2).domIdentifier is r2c0-t0 | ||
table.cellForColumnAndRow(1, 2).domIdentifier is r2c1-t0 | ||
table.cellForColumnAndRow(0, 3).domIdentifier is r2c0-t1 | ||
table.cellForColumnAndRow(1, 3).domIdentifier is r2c1-t1 | ||
Dumping table #t1 (expecting row count 2, column count 2) | ||
PASS: table.rowCount === 2 | ||
PASS: table.columnCount === 2 | ||
PASS: table.cellForColumnAndRow(0, 0).domIdentifier === "r0c0-t1" | ||
PASS: table.cellForColumnAndRow(1, 0).domIdentifier === "r0c1-t1" | ||
PASS: table.cellForColumnAndRow(0, 1).domIdentifier === "r1c0-t1" | ||
PASS: table.cellForColumnAndRow(1, 1).domIdentifier === "r1c1-t1" | ||
|
||
Performing search traversal of body. | ||
|
||
PASS successfullyParsed is true | ||
|
||
TEST COMPLETE | ||
Table zero caption | ||
(T0) Author (T0) Title | ||
(T0) Stephen Hawking (T0) A Brief History of Time | ||
(T0) Carl Sagan (T0) Cosmos | ||
(T1) Carl Sagan (T1) Cosmos | ||
Table one caption | ||
(T1) Author (T1) Title | ||
(T1) Stephen Hawking (T1) A Brief History of Time |
Oops, something went wrong.