From f7edb0b474a1a922f3285107620e802c6e19914d Mon Sep 17 00:00:00 2001 From: Nathan LaPre Date: Wed, 1 Mar 2023 05:53:30 +0000 Subject: [PATCH] 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 --- accessible/base/nsAccessibilityService.cpp | 129 +++++++++---- accessible/tests/mochitest/tree/a11y.ini | 1 + .../tree/test_aria_display_contents.html | 173 ++++++++++++++++++ .../mochitest/tree/test_display_contents.html | 23 ++- 4 files changed, 283 insertions(+), 43 deletions(-) create mode 100644 accessible/tests/mochitest/tree/test_aria_display_contents.html diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp index 9414d6d13916..bc51ad74880b 100644 --- a/accessible/base/nsAccessibilityService.cpp +++ b/accessible/base/nsAccessibilityService.cpp @@ -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 newAcc; if (markupMap && markupMap->new_func) { - RefPtr 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; } } diff --git a/accessible/tests/mochitest/tree/a11y.ini b/accessible/tests/mochitest/tree/a11y.ini index a1a71ed74ce7..c2a78c76a7e8 100644 --- a/accessible/tests/mochitest/tree/a11y.ini +++ b/accessible/tests/mochitest/tree/a11y.ini @@ -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] diff --git a/accessible/tests/mochitest/tree/test_aria_display_contents.html b/accessible/tests/mochitest/tree/test_aria_display_contents.html new file mode 100644 index 000000000000..5c6f7f20fbb5 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_display_contents.html @@ -0,0 +1,173 @@ + + + + ARIA and style="display: contents;" + + + + + + + + + + + + + Mozilla Bug 1494196 + +

+ +
+  
+ +
+
+
col1
+
col2
+
+
+
row1
+
cell1
+
+
+
+
+
col1
+
col2
+
+
+
row1
+
cell1
+
+
+
+
+
col1
+
col2
+
+
+
row1
+
cell1
+
+
+
+
+
col1
+
col2
+
+
+
row1
+
cell1
+
+
+
+
+
col1
+
col2
+
+
+
row1
+
cell1
+
+
+
+
+
col1
+
col2
+
+
+
row1
+
cell1
+
+
+ +
+
+
test
+
+
+
test
+
+
+
test
+
+
+ +
+

test

+

test

+
+ + diff --git a/accessible/tests/mochitest/tree/test_display_contents.html b/accessible/tests/mochitest/tree/test_display_contents.html index 2cf60ce72aa0..8393a35b4179 100644 --- a/accessible/tests/mochitest/tree/test_display_contents.html +++ b/accessible/tests/mochitest/tree/test_display_contents.html @@ -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);
ab
- - - - -
ab
ab
@@ -77,5 +83,10 @@ addA11yLoadEvent(doTest); b + + + + +
ab