Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
AX: Improve accessibility for tables and table components with displa…
…y flex, grid, block, inline-block, and contents

https://bugs.webkit.org/show_bug.cgi?id=258439
rdar://problem/111202843

Reviewed by Chris Fleizach.

https://bugs.webkit.org/show_bug.cgi?id=258223 did the hard work of computing table structure based on the DOM rather
than the render tree. But even after that patch, tables and table components with assorted display values were often
inaccessible to VoiceOver. This is due to parent-child mismatches caused by anonymous RenderTables, RenderTableRows,
and RenderTableCells generated when these display values are used on table components.

With this patch, we properly ignore all of these anonymous table renderers, completing the transition of table
accessibility to be DOM-based. This matches author expectations and is much more simple to reason about, and fixes
the parent-child mismatches that were breaking the accessibility hierarchy.

* LayoutTests/accessibility/display-contents/table-expected.txt:
* LayoutTests/accessibility/list-detection-expected.txt:
* LayoutTests/accessibility/list-detection.html:
* LayoutTests/accessibility/table-display-block-expected.txt:
* LayoutTests/accessibility/table-display-block.html: Added.
* LayoutTests/accessibility/table-display-flex-expected.txt:
* LayoutTests/accessibility/table-display-flex.html: Added.
* LayoutTests/accessibility/table-display-grid-expected.txt:
* LayoutTests/accessibility/table-display-grid.html: Added.
* LayoutTests/accessibility/table-display-inline-block-expected.txt:
* LayoutTests/accessibility/table-display-inline-block.html: Added.
* LayoutTests/platform/glib/accessibility/list-detection-expected.txt:
* LayoutTests/platform/ios/TestExpectations:
Enable all new tests.
* LayoutTests/platform/ios/accessibility/display-contents/table-expected.txt:
* LayoutTests/platform/ios/accessibility/table-display-block-expected.txt:
* LayoutTests/platform/ios/accessibility/table-display-flex-expected.txt:
* LayoutTests/platform/ios/accessibility/table-display-grid-expected.txt:
* LayoutTests/platform/ios/accessibility/table-display-inline-block-expected.txt:
* LayoutTests/platform/mac/accessibility/generated-content-with-display-table-crash-expected.txt:
* Source/WebCore/accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::createObjectFromRenderer):
* Source/WebCore/accessibility/AccessibilityRenderObject.cpp:
(WebCore::AccessibilityRenderObject::determineAccessibilityRole):

Canonical link: https://commits.webkit.org/265486@main
  • Loading branch information
twilco committed Jun 23, 2023
1 parent a05f740 commit 5da3ca1
Show file tree
Hide file tree
Showing 26 changed files with 1,059 additions and 14 deletions.
2 changes: 0 additions & 2 deletions LayoutTests/accessibility/display-contents/table-expected.txt
Expand Up @@ -75,8 +75,6 @@ Performing search traversal of body.

{AXRole: AXStaticText AXValue: 2020} (parent: {#r3c2 AXRole: AXCell})

{AXRole: AXTable} (parent: {#body AXRole: AXGroup})

PASS successfullyParsed is true

TEST COMPLETE
Expand Down
2 changes: 1 addition & 1 deletion LayoutTests/accessibility/list-detection-expected.txt
Expand Up @@ -56,7 +56,7 @@ PASS axElement.role == 'AXRole: AXList' is true


Inline list elements with an aria role should be a list
PASS axElement.role == 'AXRole: AXList' is false
PASS axElement.role == 'AXRole: AXList' is true


PASS successfullyParsed is true
Expand Down
2 changes: 1 addition & 1 deletion LayoutTests/accessibility/list-detection.html
Expand Up @@ -63,7 +63,7 @@
<div>item</div>
</ul>

<ul id="list14" role="list" test-description="Inline list elements with an aria role should be a list" is-list="false" style="display: table;">
<ul id="list14" role="list" test-description="Inline list elements with an aria role should be a list" is-list="true" style="display: table;">
<li role="listitem" style="display: inline-block;">Item 1</li>
<li role="listitem" style="display: inline-block;">Item 2</li>
<li role="listitem" style="display: inline-block;">Item 3</li>
Expand Down
93 changes: 93 additions & 0 deletions LayoutTests/accessibility/table-display-block-expected.txt
@@ -0,0 +1,93 @@
This test ensures that a table with display:block components is accessible.

PASS: table.rowCount === 4
PASS: table.columnCount === 3
PASS: table.cellForColumnAndRow(0, 0).domIdentifier === "r0c0"
PASS: table.cellForColumnAndRow(1, 0).domIdentifier === "r0c1"
PASS: table.cellForColumnAndRow(2, 0).domIdentifier === "r0c2"
PASS: table.cellForColumnAndRow(0, 1).domIdentifier === "r1c0"
PASS: table.cellForColumnAndRow(1, 1).domIdentifier === "r1c1"
PASS: table.cellForColumnAndRow(2, 1).domIdentifier === "r1c2"
PASS: table.cellForColumnAndRow(0, 2).domIdentifier === "r2c0"
PASS: table.cellForColumnAndRow(1, 2).domIdentifier === "r2c1"
PASS: table.cellForColumnAndRow(2, 2).domIdentifier === "r2c2"
PASS: table.cellForColumnAndRow(0, 3).domIdentifier === "r3c0"
PASS: table.cellForColumnAndRow(1, 3).domIdentifier === "r3c1"
PASS: table.cellForColumnAndRow(2, 3).domIdentifier === "r3c2"

Performing search traversal of body.

{#table AXRole: AXTable} (parent: {#body AXRole: AXGroup})

{#r0 AXRole: AXRow} (parent: {#table AXRole: AXTable})

{#r0c0 AXRole: AXCell} (parent: {#r0 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Author} (parent: {#r0c0 AXRole: AXCell})

{#r0c1 AXRole: AXCell} (parent: {#r0 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Title} (parent: {#r0c1 AXRole: AXCell})

{#r0c2 AXRole: AXCell} (parent: {#r0 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Year} (parent: {#r0c2 AXRole: AXCell})

{#r1 AXRole: AXRow} (parent: {#table AXRole: AXTable})

{#r1c0 AXRole: AXCell} (parent: {#r1 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Stephen Hawking} (parent: {#r1c0 AXRole: AXCell})

{#r1c1 AXRole: AXCell} (parent: {#r1 AXRole: AXRow})

{AXRole: AXStaticText AXValue: A Brief History of Time} (parent: {#r1c1 AXRole: AXCell})

{#r1c2 AXRole: AXCell} (parent: {#r1 AXRole: AXRow})

{AXRole: AXStaticText AXValue: 1988} (parent: {#r1c2 AXRole: AXCell})

{#r2 AXRole: AXRow} (parent: {#table AXRole: AXTable})

{#r2c0 AXRole: AXCell} (parent: {#r2 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Carl Sagan} (parent: {#r2c0 AXRole: AXCell})

{#r2c1 AXRole: AXCell} (parent: {#r2 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Cosmos} (parent: {#r2c1 AXRole: AXCell})

{#r2c2 AXRole: AXCell} (parent: {#r2 AXRole: AXRow})

{AXRole: AXStaticText AXValue: 1980} (parent: {#r2c2 AXRole: AXCell})

{#r3 AXRole: AXRow} (parent: {#table AXRole: AXTable})

{#r3c0 AXRole: AXCell} (parent: {#r3 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Will Gater} (parent: {#r3c0 AXRole: AXCell})

{#r3c1 AXRole: AXCell} (parent: {#r3 AXRole: AXRow})

{AXRole: AXStaticText AXValue: The Mysteries of the Universe} (parent: {#r3c1 AXRole: AXCell})

{#r3c2 AXRole: AXCell} (parent: {#r3 AXRole: AXRow})

{AXRole: AXStaticText AXValue: 2020} (parent: {#r3c2 AXRole: AXCell})

PASS successfullyParsed is true

TEST COMPLETE
This is a table caption
Author
Title
Year
Stephen Hawking
A Brief History of Time
1988
Carl Sagan
Cosmos
1980
Will Gater
The Mysteries of the Universe
2020
82 changes: 82 additions & 0 deletions LayoutTests/accessibility/table-display-block.html
@@ -0,0 +1,82 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../resources/accessibility-helper.js"></script>
<script src="../resources/js-test.js"></script>
<style>
table, tr, td, th {
display: block;
}
</style>
</head>
<body role="group" id="body">

<table id="table">
<caption>This is a table caption</caption>
<thead>
<tr id="r0">
<th id="r0c0">Author</th>
<th id="r0c1">Title</th>
<th id="r0c2">Year</th>
</tr>
</thead>
<tbody>
<tr id="r1">
<td id="r1c0">Stephen Hawking</td>
<td id="r1c1">A Brief History of Time</td>
<td id="r1c2">1988</td>
</tr>
<tr id="r2">
<td id="r2c0">Carl Sagan</td>
<td id="r2c1">Cosmos</td>
<td id="r2c2">1980</td>
</tr>
<tr id="r3">
<td id="r3c0">Will Gater</td>
<td id="r3c1">The Mysteries of the Universe</td>
<td id="r3c2">2020</td>
</tr>
</tbody>
</table>

<script>
var output = "This test ensures that a table with display:block components is accessible.\n\n";

if (window.accessibilityController) {
var table = accessibilityController.accessibleElementById("table");
output += expect("table.rowCount", "4");
output += expect("table.columnCount", "3");

for (let row = 0; row < 4; row++) {
for (let column = 0; column < 3; column++)
output += expect(`table.cellForColumnAndRow(${column}, ${row}).domIdentifier`, `"r${row}c${column}"`);
}

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`;
}
debug(output);
}
</script>
</body>
</html>

93 changes: 93 additions & 0 deletions LayoutTests/accessibility/table-display-flex-expected.txt
@@ -0,0 +1,93 @@
This test ensures that a table with display:flex components is accessible.

PASS: table.rowCount === 4
PASS: table.columnCount === 3
PASS: table.cellForColumnAndRow(0, 0).domIdentifier === "r0c0"
PASS: table.cellForColumnAndRow(1, 0).domIdentifier === "r0c1"
PASS: table.cellForColumnAndRow(2, 0).domIdentifier === "r0c2"
PASS: table.cellForColumnAndRow(0, 1).domIdentifier === "r1c0"
PASS: table.cellForColumnAndRow(1, 1).domIdentifier === "r1c1"
PASS: table.cellForColumnAndRow(2, 1).domIdentifier === "r1c2"
PASS: table.cellForColumnAndRow(0, 2).domIdentifier === "r2c0"
PASS: table.cellForColumnAndRow(1, 2).domIdentifier === "r2c1"
PASS: table.cellForColumnAndRow(2, 2).domIdentifier === "r2c2"
PASS: table.cellForColumnAndRow(0, 3).domIdentifier === "r3c0"
PASS: table.cellForColumnAndRow(1, 3).domIdentifier === "r3c1"
PASS: table.cellForColumnAndRow(2, 3).domIdentifier === "r3c2"

Performing search traversal of body.

{#table AXRole: AXTable} (parent: {#body AXRole: AXGroup})

{#r0 AXRole: AXRow} (parent: {#table AXRole: AXTable})

{#r0c0 AXRole: AXCell} (parent: {#r0 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Author} (parent: {#r0c0 AXRole: AXCell})

{#r0c1 AXRole: AXCell} (parent: {#r0 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Title} (parent: {#r0c1 AXRole: AXCell})

{#r0c2 AXRole: AXCell} (parent: {#r0 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Year} (parent: {#r0c2 AXRole: AXCell})

{#r1 AXRole: AXRow} (parent: {#table AXRole: AXTable})

{#r1c0 AXRole: AXCell} (parent: {#r1 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Stephen Hawking} (parent: {#r1c0 AXRole: AXCell})

{#r1c1 AXRole: AXCell} (parent: {#r1 AXRole: AXRow})

{AXRole: AXStaticText AXValue: A Brief History of Time} (parent: {#r1c1 AXRole: AXCell})

{#r1c2 AXRole: AXCell} (parent: {#r1 AXRole: AXRow})

{AXRole: AXStaticText AXValue: 1988} (parent: {#r1c2 AXRole: AXCell})

{#r2 AXRole: AXRow} (parent: {#table AXRole: AXTable})

{#r2c0 AXRole: AXCell} (parent: {#r2 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Carl Sagan} (parent: {#r2c0 AXRole: AXCell})

{#r2c1 AXRole: AXCell} (parent: {#r2 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Cosmos} (parent: {#r2c1 AXRole: AXCell})

{#r2c2 AXRole: AXCell} (parent: {#r2 AXRole: AXRow})

{AXRole: AXStaticText AXValue: 1980} (parent: {#r2c2 AXRole: AXCell})

{#r3 AXRole: AXRow} (parent: {#table AXRole: AXTable})

{#r3c0 AXRole: AXCell} (parent: {#r3 AXRole: AXRow})

{AXRole: AXStaticText AXValue: Will Gater} (parent: {#r3c0 AXRole: AXCell})

{#r3c1 AXRole: AXCell} (parent: {#r3 AXRole: AXRow})

{AXRole: AXStaticText AXValue: The Mysteries of the Universe} (parent: {#r3c1 AXRole: AXCell})

{#r3c2 AXRole: AXCell} (parent: {#r3 AXRole: AXRow})

{AXRole: AXStaticText AXValue: 2020} (parent: {#r3c2 AXRole: AXCell})

PASS successfullyParsed is true

TEST COMPLETE
This is a table caption
Author
Title
Year
Stephen Hawking
A Brief History of Time
1988
Carl Sagan
Cosmos
1980
Will Gater
The Mysteries of the Universe
2020
82 changes: 82 additions & 0 deletions LayoutTests/accessibility/table-display-flex.html
@@ -0,0 +1,82 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../resources/accessibility-helper.js"></script>
<script src="../resources/js-test.js"></script>
<style>
table, tr, td, th {
display: flex;
}
</style>
</head>
<body role="group" id="body">

<table id="table">
<caption>This is a table caption</caption>
<thead>
<tr id="r0">
<th id="r0c0">Author</th>
<th id="r0c1">Title</th>
<th id="r0c2">Year</th>
</tr>
</thead>
<tbody>
<tr id="r1">
<td id="r1c0">Stephen Hawking</td>
<td id="r1c1">A Brief History of Time</td>
<td id="r1c2">1988</td>
</tr>
<tr id="r2">
<td id="r2c0">Carl Sagan</td>
<td id="r2c1">Cosmos</td>
<td id="r2c2">1980</td>
</tr>
<tr id="r3">
<td id="r3c0">Will Gater</td>
<td id="r3c1">The Mysteries of the Universe</td>
<td id="r3c2">2020</td>
</tr>
</tbody>
</table>

<script>
var output = "This test ensures that a table with display:flex components is accessible.\n\n";

if (window.accessibilityController) {
var table = accessibilityController.accessibleElementById("table");
output += expect("table.rowCount", "4");
output += expect("table.columnCount", "3");

for (let row = 0; row < 4; row++) {
for (let column = 0; column < 3; column++)
output += expect(`table.cellForColumnAndRow(${column}, ${row}).domIdentifier`, `"r${row}c${column}"`);
}

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`;
}
debug(output);
}
</script>
</body>
</html>

0 comments on commit 5da3ca1

Please sign in to comment.