Bug 1494196: Create Accessible for elements with ARIA role, attributes and display: contents, r=Jamie

This revision modifies the logic in CreateAccessible such that we create
Accessibles for elements that have ARIA roles (other than presentation, none) or
other ARIA attributes, even if those elements have the display: contents style.
This revision also adds tests to verify the above.

Differential Revision: https://phabricator.services.mozilla.com/D170718
This commit is contained in:
Nathan LaPre 2023-03-01 05:53:30 +00:00
parent 8661b970a1
commit f7edb0b474
4 changed files with 283 additions and 43 deletions

View File

@ -103,15 +103,49 @@ using namespace mozilla::dom;
////////////////////////////////////////////////////////////////////////////////
/**
* Return true if the element must be accessible.
* Return true if the role map entry is an ARIA table part.
*/
static bool MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument) {
nsIFrame* frame = aContent->GetPrimaryFrame();
MOZ_ASSERT(frame);
if (frame->IsFocusable()) {
return true;
}
static bool IsARIATablePart(const nsRoleMapEntry* aRoleMapEntry) {
return aRoleMapEntry &&
(aRoleMapEntry->accTypes & (eTableCell | eTableRow | eTable));
}
/**
* Create and return an Accessible for the given content depending on which
* table part we think it is.
*/
static LocalAccessible* CreateARIATablePartAcc(
const nsRoleMapEntry* aRoleMapEntry, const LocalAccessible* aContext,
nsIContent* aContent, DocAccessible* aDocument) {
// In case of ARIA grid or table use table-specific classes if it's not
// native table based.
if ((aRoleMapEntry->accTypes & eTableCell)) {
if (aContext->IsTableRow()) {
return new ARIAGridCellAccessible(aContent, aDocument);
}
} else if (aRoleMapEntry->IsOfType(eTableRow)) {
if (aContext->IsTable() ||
// There can be an Accessible between a row and its table, but it
// can only be a row group or a generic container. This is
// consistent with Filters::GetRow and CachedTableAccessible's
// TablePartRule.
((aContext->Role() == roles::GROUPING ||
(aContext->IsGenericHyperText() && !aContext->ARIARoleMap())) &&
aContext->LocalParent() && aContext->LocalParent()->IsTable())) {
return new ARIARowAccessible(aContent, aDocument);
}
} else if (aRoleMapEntry->IsOfType(eTable)) {
return new ARIAGridAccessible(aContent, aDocument);
}
return nullptr;
}
/**
* Return true if the element has an attribute (ARIA, title, or relation) that
* requires the creation of an Accessible for the element.
*/
static bool AttributesMustBeAccessible(nsIContent* aContent,
DocAccessible* aDocument) {
if (aContent->IsElement()) {
uint32_t attrCount = aContent->AsElement()->GetAttrCount();
for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) {
@ -138,7 +172,7 @@ static bool MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument) {
}
// If the given ID is referred by relation attribute then create an
// accessible for it.
// Accessible for it.
nsAutoString id;
if (nsCoreUtils::GetID(aContent, id) && !id.IsEmpty()) {
return aDocument->IsDependentID(aContent->AsElement(), id);
@ -176,6 +210,19 @@ static bool MustBeGenericAccessible(nsIContent* aContent,
nsLayoutUtils::IsReallyFixedPos(frame)));
}
/**
* Return true if the element must be accessible.
*/
static bool MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument) {
nsIFrame* frame = aContent->GetPrimaryFrame();
MOZ_ASSERT(frame);
if (frame->IsFocusable()) {
return true;
}
return AttributesMustBeAccessible(aContent, aDocument);
}
bool nsAccessibilityService::ShouldCreateImgAccessible(
mozilla::dom::Element* aElement, DocAccessible* aDocument) {
// The element must have a layout frame for us to proceed. If there is no
@ -1022,16 +1069,41 @@ LocalAccessible* nsAccessibilityService::CreateAccessible(
// display:contents element doesn't have a frame, but retains the
// semantics. All its children are unaffected.
const MarkupMapInfo* markupMap = GetMarkupMapInfoFor(content);
RefPtr<LocalAccessible> newAcc;
if (markupMap && markupMap->new_func) {
RefPtr<LocalAccessible> newAcc =
markupMap->new_func(content->AsElement(), aContext);
if (newAcc) {
document->BindToDocument(newAcc,
aria::GetRoleMap(content->AsElement()));
}
return newAcc;
newAcc = markupMap->new_func(content->AsElement(), aContext);
}
return nullptr;
// Check whether this element has an ARIA role or attribute that requires
// us to create an Accessible.
const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(content->AsElement());
const bool hasNonPresentationalARIARole =
roleMapEntry && !roleMapEntry->Is(nsGkAtoms::presentation) &&
!roleMapEntry->Is(nsGkAtoms::none);
if (!newAcc && (hasNonPresentationalARIARole ||
AttributesMustBeAccessible(content, document))) {
// If this element is an ARIA table part, create the proper table part
// Accessible. Otherwise, create a generic HyperTextAccessible.
if (IsARIATablePart(roleMapEntry)) {
newAcc =
CreateARIATablePartAcc(roleMapEntry, aContext, content, document);
} else {
newAcc = new HyperTextAccessibleWrap(content, document);
}
}
// If there's still no Accessible but we do have an entry in the markup
// map for this non-presentational element, create a generic
// HyperTextAccessible.
if (!newAcc && markupMap &&
(!roleMapEntry || hasNonPresentationalARIARole)) {
newAcc = new HyperTextAccessibleWrap(content, document);
}
if (newAcc) {
document->BindToDocument(newAcc, roleMapEntry);
}
return newAcc;
} else {
if (aIsSubtreeHidden) {
*aIsSubtreeHidden = true;
@ -1152,8 +1224,7 @@ LocalAccessible* nsAccessibilityService::CreateAccessible(
}
if (!newAcc && content->IsHTMLElement()) { // HTML accessibles
bool isARIATablePart = roleMapEntry && (roleMapEntry->accTypes &
(eTableCell | eTableRow | eTable));
const bool isARIATablePart = IsARIATablePart(roleMapEntry);
if (!isARIATablePart || frame->AccessibleType() == eHTMLTableCellType ||
frame->AccessibleType() == eHTMLTableRowType ||
@ -1177,25 +1248,9 @@ LocalAccessible* nsAccessibilityService::CreateAccessible(
// In case of ARIA grid or table use table-specific classes if it's not
// native table based.
if (isARIATablePart && (!newAcc || newAcc->IsGenericHyperText())) {
if ((roleMapEntry->accTypes & eTableCell)) {
if (aContext->IsTableRow()) {
newAcc = new ARIAGridCellAccessible(content, document);
}
} else if (roleMapEntry->IsOfType(eTableRow)) {
if (aContext->IsTable() ||
// There can be an Accessible between a row and its table, but it
// can only be a row group or a generic container. This is
// consistent with Filters::GetRow and CachedTableAccessible's
// TablePartRule.
((aContext->Role() == roles::GROUPING ||
(aContext->IsGenericHyperText() && !aContext->ARIARoleMap())) &&
aContext->LocalParent() && aContext->LocalParent()->IsTable())) {
newAcc = new ARIARowAccessible(content, document);
}
} else if (roleMapEntry->IsOfType(eTable)) {
newAcc = new ARIAGridAccessible(content, document);
if (LocalAccessible* tablePartAcc = CreateARIATablePartAcc(
roleMapEntry, aContext, content, document)) {
newAcc = tablePartAcc;
}
}

View File

@ -11,6 +11,7 @@ support-files =
[test_applicationacc.xhtml]
skip-if = true # Bug 561508
[test_aria_display_contents.html]
[test_aria_globals.html]
[test_aria_grid.html]
[test_aria_imgmap.html]

View File

@ -0,0 +1,173 @@
<!DOCTYPE html>
<html>
<head>
<title>ARIA and style="display: contents;"</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../role.js"></script>
<script type="application/javascript">
function doTest() {
// Test ARIA grids that have display: contents; on different elements.
// They should all have equivalent trees.
var accTree =
{ TABLE: [
{ ROW: [
{ role: ROLE_COLUMNHEADER,
children: [ { TEXT_LEAF: [ ] }, ]
},
{ role: ROLE_COLUMNHEADER,
children: [ { TEXT_LEAF: [ ] }, ]
},
] },
{ ROW: [
{ ROWHEADER: [
{ TEXT_LEAF: [ ] },
] },
{ GRID_CELL: [
{ TEXT_LEAF: [ ] },
] },
] },
] };
testAccessibleTree("gridWithoutDisplayContents", accTree);
testAccessibleTree("gridWithDisplayContents", accTree);
testAccessibleTree("gridWithDisplayContentsRow", accTree);
testAccessibleTree("gridWithDisplayContentsColHeader", accTree);
testAccessibleTree("gridWithDisplayContentsRowHeader", accTree);
testAccessibleTree("gridWithDisplayContentsGridCell", accTree);
// Test divs with ARIA roles and attributes and display: contents to
// verify that Accessibles are created appropriately.
accTree =
{ SECTION: [
{ LIST: [
{ LISTITEM: [
{ TEXT_LEAF: [ ] }
] },
] },
{ SECTION: [
{ LISTITEM: [
{ TEXT_LEAF: [ ] }
] },
] },
{ LISTITEM: [
{ TEXT_LEAF: [ ] }
] },
] };
testAccessibleTree("container", accTree);
// Test paragraph with display: contents. It should create a generic
// Accessible that reports the role correctly.
accTree =
{ SECTION: [
{ PARAGRAPH: [ { TEXT_LEAF: [ ] } ] },
{ TEXT_LEAF: [ ] }, // space between paragraphs
{ TEXT_LEAF: [ ] },
] };
testAccessibleTree("paragraphContainer", accTree);
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
<a target="_blank"
title="Element with ARIA role and display: contents doesn't get an accessible"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1494196">
Mozilla Bug 1494196
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div id="gridWithoutDisplayContents" role="grid">
<div role="row">
<div role="columnheader">col1</div>
<div role="columnheader">col2</div>
</div>
<div role="row">
<div role="rowheader">row1</div>
<div role="gridcell">cell1</div>
</div>
</div>
<div id="gridWithDisplayContents" role="grid" style="display:contents;">
<div role="row">
<div role="columnheader">col1</div>
<div role="columnheader">col2</div>
</div>
<div role="row">
<div role="rowheader">row1</div>
<div role="gridcell">cell1</div>
</div>
</div>
<div id="gridWithDisplayContentsRow" role="grid">
<div role="row" style="display:contents;">
<div role="columnheader">col1</div>
<div role="columnheader">col2</div>
</div>
<div role="row">
<div role="rowheader">row1</div>
<div role="gridcell">cell1</div>
</div>
</div>
<div id="gridWithDisplayContentsColHeader" role="grid">
<div role="row">
<div role="columnheader" style="display:contents;">col1</div>
<div role="columnheader">col2</div>
</div>
<div role="row">
<div role="rowheader">row1</div>
<div role="gridcell">cell1</div>
</div>
</div>
<div id="gridWithDisplayContentsRowHeader" role="grid">
<div role="row">
<div role="columnheader">col1</div>
<div role="columnheader">col2</div>
</div>
<div role="row">
<div role="rowheader" style="display:contents;">row1</div>
<div role="gridcell">cell1</div>
</div>
</div>
<div id="gridWithDisplayContentsGridCell" role="grid">
<div role="row">
<div role="columnheader">col1</div>
<div role="columnheader">col2</div>
</div>
<div role="row">
<div role="rowheader">row1</div>
<div role="gridcell" style="display:contents;">cell1</div>
</div>
</div>
<div id="container">
<div role="list" style="display: contents;">
<div role="listitem">test</div>
</div>
<div aria-label="test" style="display: contents;">
<div role="listitem">test</div>
</div>
<div role="none" style="display: contents;">
<div role="listitem">test</div>
</div>
</div>
<div id="paragraphContainer">
<p style="display: contents;">test</p>
<p style="display: contents;" role="none">test</p>
</div>
</body>
</html>

View File

@ -36,10 +36,21 @@ function doTest() {
]},
] };
testAccessibleTree("tableTableContents", tree);
testAccessibleTree("tableTbodyContents", tree);
testAccessibleTree("tableTrContents", tree);
testAccessibleTree("tableTdContents", tree);
tree =
{ TABLE: [
{ GROUPING : [
{ ROW: [
{ CELL: [{ TEXT_LEAF: [] } ] },
{ CELL: [{ TEXT_LEAF: [] } ] },
]},
]},
] };
testAccessibleTree("tableTbodyContents", tree);
SimpleTest.finish();
}
@ -63,11 +74,6 @@ addA11yLoadEvent(doTest);
<table id="tableTableContents" summary="summary" style="display: contents;">
<tr><td>a</td><td>b</td></tr>
</table>
<table id="tableTbodyContents" summary="summary" style="display: block;">
<tbody style="display: contents;">
<tr><td>a</td><td>b</td></tr>
</tbody>
</table>
<table id="tableTrContents" summary="table" style="display: block;">
<tr style="display: contents;"><td>a</td><td>b</td></tr>
</table>
@ -77,5 +83,10 @@ addA11yLoadEvent(doTest);
<td style="display: contents;">b</td>
</tr>
</table>
<table id="tableTbodyContents" summary="summary" style="display: block;">
<tbody style="display: contents;">
<tr><td>a</td><td>b</td></tr>
</tbody>
</table>
</body>
</html>