Bug 1644810: Implement accessible subclasses for AXOutline and AXOutlineItem r=eeejay

Differential Revision: https://phabricator.services.mozilla.com/D96074
This commit is contained in:
Morgan Reschenberg 2020-11-12 06:16:58 +00:00
parent 375f1764b4
commit e45400e5b3
9 changed files with 655 additions and 1 deletions

View file

@ -328,6 +328,13 @@ Class a11y::GetTypeFromRole(roles::Role aRole) {
case roles::SUMMARY:
return [MOXSummaryAccessible class];
case roles::OUTLINE:
case roles::TREE_TABLE:
return [mozOutlineAccessible class];
case roles::OUTLINEITEM:
return [mozOutlineRowAccessible class];
default:
return [mozAccessible class];
}

View file

@ -211,6 +211,23 @@
// AXIdentifier
- (NSString* _Nullable)moxIdentifier;
// Outline Attributes
// AXDisclosing
- (NSNumber* _Nullable)moxDisclosing;
// AXDisclosedByRow
- (id _Nullable)moxDisclosedByRow;
// AXDisclosureLevel
- (NSNumber* _Nullable)moxDisclosureLevel;
// AXDisclosedRows
- (NSArray* _Nullable)moxDisclosedRows;
// AXSelectedRows
- (NSArray* _Nullable)moxSelectedRows;
// Math Attributes
// AXMathRootRadicand

View file

@ -112,3 +112,14 @@ class RotorHeadingLevelRule : public RotorRoleRule {
private:
int32_t mLevel;
};
/**
* This rule matches all accessibles with roles::OUTLINEITEM. If
* outlines are nested, it ignores the nested subtree and returns
* only items which are descendants of the primary outline.
*/
class OutlineRule : public RotorRule {
public:
explicit OutlineRule();
uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
};

View file

@ -307,3 +307,27 @@ uint16_t RotorHeadingLevelRule::Match(const AccessibleOrProxy& aAccOrProxy) {
return result;
}
// Outline Rule
OutlineRule::OutlineRule() : RotorRule(){};
uint16_t OutlineRule::Match(const AccessibleOrProxy& aAccOrProxy) {
uint16_t result = RotorRule::Match(aAccOrProxy);
// if a match was found in the base-class's Match function,
// it is valid to consider that match again here.
if (result & nsIAccessibleTraversalRule::FILTER_MATCH) {
if (aAccOrProxy.Role() == roles::OUTLINE) {
// if the match is an outline, we ignore all children here
// and unmatch the outline itself
result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
} else if (aAccOrProxy.Role() != roles::OUTLINEITEM) {
// if the match is not an outline item, we unmatch here
result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
}
}
return result;
}

View file

@ -167,7 +167,7 @@ static const uint64_t kCacheInitialized = ((uint64_t)0x1) << 63;
// They may be named explicitly, but they still provide a label not a title.
return mRole == roles::GROUPING || mRole == roles::RADIO_GROUP ||
mRole == roles::FIGURE || mRole == roles::GRAPHIC ||
mRole == roles::DOCUMENT;
mRole == roles::DOCUMENT || mRole == roles::OUTLINE;
}
- (mozilla::a11y::AccessibleOrProxy)geckoAccessible {

View file

@ -113,3 +113,41 @@
- (NSArray*)moxColumnHeaderUIElements;
@end
@interface mozOutlineAccessible : mozAccessible
// override
- (NSArray*)moxRows;
// override
- (NSArray*)moxColumns;
// override
- (NSArray*)moxSelectedRows;
@end
@interface mozOutlineRowAccessible : mozTableRowAccessible
// override
- (BOOL)isLayoutTablePart;
// override
- (NSNumber*)moxDisclosing;
// override
- (id)moxDisclosedByRow;
// override
- (NSNumber*)moxDisclosureLevel;
// override
- (NSArray*)moxDisclosedRows;
// override
- (NSNumber*)moxIndex;
// override
- (NSString*)moxLabel;
@end

View file

@ -8,11 +8,14 @@
#import "mozTableAccessible.h"
#import "nsCocoaUtils.h"
#import "MacUtils.h"
#import "RotorRules.h"
#include "AccIterator.h"
#include "Accessible.h"
#include "TableAccessible.h"
#include "TableCellAccessible.h"
#include "Pivot.h"
#include "Relation.h"
using namespace mozilla;
using namespace mozilla::a11y;
@ -127,6 +130,9 @@ using namespace mozilla::a11y;
- (BOOL)isLayoutTablePart {
if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
while (acc) {
if (acc->Role() == roles::TREE_TABLE) {
return false;
}
if (acc->IsTable()) {
return acc->AsTable()->IsProbablyLayoutTable();
}
@ -137,6 +143,9 @@ using namespace mozilla::a11y;
if (ProxyAccessible* proxy = mGeckoAccessible.AsProxy()) {
while (proxy) {
if (proxy->Role() == roles::TREE_TABLE) {
return false;
}
if (proxy->IsTable()) {
return proxy->TableIsProbablyForLayout();
}
@ -379,3 +388,174 @@ using namespace mozilla::a11y;
}
@end
@implementation mozOutlineAccessible
- (NSArray*)moxRows {
// Create a new array with the list of outline rows. We
// use pivot here to do a deep traversal of all rows nested
// in this outline, not just those which are direct
// children, since that's what VO expects.
NSMutableArray* allRows = [[NSMutableArray alloc] init];
Pivot p = Pivot(mGeckoAccessible);
OutlineRule rule = OutlineRule();
AccessibleOrProxy firstChild = mGeckoAccessible.FirstChild();
AccessibleOrProxy match = p.Next(firstChild, rule, true);
while (!match.IsNull()) {
[allRows addObject:GetNativeFromGeckoAccessible(match)];
match = p.Next(match, rule);
}
return allRows;
}
- (NSArray*)moxColumns {
// Webkit says we shouldn't do anything here
return @[];
}
- (NSArray*)moxSelectedRows {
NSMutableArray* selectedRows = [[NSMutableArray alloc] init];
NSArray* allRows = [self moxRows];
for (mozAccessible* row in allRows) {
if ([row stateWithMask:states::SELECTED] != 0) {
[selectedRows addObject:row];
}
}
return selectedRows;
}
@end
@implementation mozOutlineRowAccessible
- (BOOL)isLayoutTablePart {
return NO;
}
- (NSNumber*)moxDisclosing {
return @([self stateWithMask:states::EXPANDED] != 0);
}
- (id)moxDisclosedByRow {
// According to webkit: this attr corresponds to the row
// that contains this row. It should be the same as the
// first parent that is a treeitem. If the parent is the tree
// itself, this should be nil. This is tricky for xul trees because
// all rows are direct children of the outline; they use
// relations to expose their heirarchy structure.
mozAccessible* disclosingRow = nil;
// first we check the relations to see if we're in a xul tree
// with weird row semantics
if (mGeckoAccessible.IsAccessible()) {
Relation rel = mGeckoAccessible.AsAccessible()->RelationByType(
RelationType::NODE_CHILD_OF);
Accessible* maybeParent = rel.Next();
disclosingRow =
maybeParent ? GetNativeFromGeckoAccessible(maybeParent) : nil;
} else {
nsTArray<ProxyAccessible*> accs =
mGeckoAccessible.AsProxy()->RelationByType(RelationType::NODE_CHILD_OF);
disclosingRow =
accs.Length() > 0 ? GetNativeFromGeckoAccessible(accs[0]) : nil;
}
if (disclosingRow) {
// if we find a row from our relation check,
// verify it isn't the outline itself and return
// appropriately
if ([[disclosingRow moxRole] isEqualToString:@"AXOutline"]) {
return nil;
}
return disclosingRow;
}
mozAccessible* parent = (mozAccessible*)[self moxUnignoredParent];
// otherwise, its likely we're in an aria tree, so we can use
// these role and subrole checks
if ([[parent moxRole] isEqualToString:@"AXOutline"]) {
return nil;
}
if ([[parent moxSubrole] isEqualToString:@"AXOutlineRow"]) {
disclosingRow = parent;
}
return nil;
}
- (NSNumber*)moxDisclosureLevel {
GroupPos groupPos;
if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
groupPos = acc->GroupPosition();
} else if (ProxyAccessible* proxy = mGeckoAccessible.AsProxy()) {
groupPos = proxy->GroupPosition();
}
return @(groupPos.level);
}
- (NSArray*)moxDisclosedRows {
// According to webkit: this attr corresponds to the rows
// that are considered inside this row. Again, this is weird for
// xul trees so we have to use relations first and then fall-back
// to the children filter for non-xul outlines.
NSMutableArray* disclosedRows = [[NSMutableArray alloc] init];
// first we check the relations to see if we're in a xul tree
// with weird row semantics
if (mGeckoAccessible.IsAccessible()) {
Relation rel = mGeckoAccessible.AsAccessible()->RelationByType(
RelationType::NODE_PARENT_OF);
Accessible* acc = nullptr;
while ((acc = rel.Next())) {
[disclosedRows addObject:GetNativeFromGeckoAccessible(acc)];
}
} else {
nsTArray<ProxyAccessible*> accs =
mGeckoAccessible.AsProxy()->RelationByType(
RelationType::NODE_PARENT_OF);
disclosedRows = utils::ConvertToNSArray(accs);
}
if (disclosedRows) {
// if we find rows from our relation check, return them here
return disclosedRows;
}
// otherwise, filter our children for outline rows
return [[self moxChildren]
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
mozAccessible* child,
NSDictionary* bindings) {
return [child isKindOfClass:[mozOutlineRowAccessible class]];
}]];
}
- (NSNumber*)moxIndex {
mozAccessible* parent = (mozAccessible*)[self moxUnignoredParent];
while (parent) {
if ([[parent moxRole] isEqualToString:@"AXOutline"]) {
break;
}
parent = (mozAccessible*)[parent moxUnignoredParent];
}
NSUInteger index =
[[(mozOutlineAccessible*)parent moxRows] indexOfObjectIdenticalTo:self];
return index == NSNotFound ? nil : @(index);
}
- (NSString*)moxLabel {
nsAutoString title;
if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
acc->Name(title);
} else {
mGeckoAccessible.AsProxy()->Name(title);
}
// remove listmarker for clean label
return nsCocoaUtils::ToNSString(Substring(title, 1, title.Length()));
}
@end

View file

@ -37,4 +37,5 @@ skip-if = os == 'mac' && debug # Bug 1664577
[browser_rootgroup.js]
[browser_text_selection.js]
[browser_navigate.js]
[browser_outline.js]
[browser_hierarchy.js]

View file

@ -0,0 +1,376 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Test outline, outline rows with computed properties
*/
addAccessibleTask(
`
<h3 id="tree1">
Foods
</h3>
<ul role="tree" aria-labelledby="tree1" id="outline">
<li role="treeitem" aria-expanded="false">
<span>
Fruits
</span>
<ul>
<li role="none">Oranges</li>
<li role="treeitem" aria-expanded="true">
<span>
Apples
</span>
<ul role="group">
<li role="none">Honeycrisp</li>
<li role="none">Granny Smith</li>
</ul>
</li>
</ul>
</li>
<li role="treeitem" aria-expanded="false">
<span>
Vegetables
</span>
<ul role="group">
<li role="treeitem" aria-expanded="true">
<span>
Podded Vegetables
</span>
<ul role="group">
<li role="none">Lentil</li>
<li role="none">Pea</li>
</ul>
</li>
</ul>
</li>
</ul>
`,
async (browser, accDoc) => {
const outline = getNativeInterface(accDoc, "outline");
is(
outline.getAttributeValue("AXRole"),
"AXOutline",
"Correct role for outline"
);
const outChildren = outline.getAttributeValue("AXChildren");
is(outChildren.length, 2, "Outline has two direct children");
is(outChildren[0].getAttributeValue("AXSubrole"), "AXOutlineRow");
is(outChildren[1].getAttributeValue("AXSubrole"), "AXOutlineRow");
const outRows = outline.getAttributeValue("AXRows");
is(outRows.length, 4, "Outline has four rows");
is(
outRows[0].getAttributeValue("AXDisclosing"),
0,
"Row is not disclosing"
);
is(
outRows[0].getAttributeValue("AXDisclosedByRow"),
null,
"Row is direct child of outline"
);
is(
outRows[0].getAttributeValue("AXDisclosedRows").length,
0,
"Row has no row children, only group"
);
is(
outRows[0].getAttributeValue("AXDisclosureLevel"),
1,
"Row is level one"
);
is(outRows[1].getAttributeValue("AXDisclosing"), 1, "Row is disclosing");
is(
outRows[1].getAttributeValue("AXDisclosedByRow"),
null,
"Row is direct child of group"
);
is(
outRows[1].getAttributeValue("AXDisclosedRows").length,
0,
"Row has no row children"
);
is(
outRows[1].getAttributeValue("AXDisclosureLevel"),
1,
"Row is level one"
);
is(
outRows[2].getAttributeValue("AXDisclosing"),
0,
"Row is not disclosing"
);
is(
outRows[2].getAttributeValue("AXDisclosedByRow"),
null,
"Row is direct child of outline"
);
is(
outRows[2].getAttributeValue("AXDisclosedRows").length,
1,
"Row has one row child"
);
is(
outRows[2].getAttributeValue("AXDisclosureLevel"),
1,
"Row is level one"
);
is(outRows[3].getAttributeValue("AXDisclosing"), 1, "Row is disclosing");
is(
outRows[3]
.getAttributeValue("AXDisclosedByRow")
.getAttributeValue("AXDescription"),
outRows[2].getAttributeValue("AXDescription"),
"Row is direct child of row[2]"
);
is(
outRows[3].getAttributeValue("AXDisclosedRows").length,
0,
"Row has no row children"
);
is(
outRows[3].getAttributeValue("AXDisclosureLevel"),
2,
"Row is level two"
);
}
);
/**
* Test outline, outline rows with declared properties
*/
addAccessibleTask(
`
<h3 id="tree1">
Foods
</h3>
<ul role="tree" aria-labelledby="tree1" id="outline">
<li role="treeitem"
aria-level="1"
aria-setsize="2"
aria-posinset="1"
aria-expanded="false">
<span>
Fruits
</span>
<ul>
<li role="treeitem"
aria-level="3"
aria-setsize="2"
aria-posinset="1">
Oranges
</li>
<li role="treeitem"
aria-level="2"
aria-setsize="2"
aria-posinset="2"
aria-expanded="true">
<span>
Apples
</span>
<ul role="group">
<li role="treeitem"
aria-level="3"
aria-setsize="2"
aria-posinset="1">
Honeycrisp
</li>
<li role="treeitem"
aria-level="3"
aria-setsize="2"
aria-posinset="2">
Granny Smith
</li>
</ul>
</li>
</ul>
</li>
<li role="treeitem"
aria-level="1"
aria-setsize="2"
aria-posinset="2"
aria-expanded="false">
<span>
Vegetables
</span>
<ul role="group">
<li role="treeitem"
aria-level="2"
aria-setsize="1"
aria-posinset="1"
aria-expanded="true">
<span>
Podded Vegetables
</span>
<ul role="group">
<li role="treeitem"
aria-level="3"
aria-setsize="2"
aria-posinset="1">
Lentil
</li>
<li role="treeitem"
aria-level="3"
aria-setsize="2"
aria-posinset="2">
Pea
</li>
</ul>
</li>
</ul>
</li>
</ul>
`,
async (browser, accDoc) => {
const outline = getNativeInterface(accDoc, "outline");
is(
outline.getAttributeValue("AXRole"),
"AXOutline",
"Correct role for outline"
);
const outChildren = outline.getAttributeValue("AXChildren");
is(outChildren.length, 2, "Outline has two direct children");
is(outChildren[0].getAttributeValue("AXSubrole"), "AXOutlineRow");
is(outChildren[1].getAttributeValue("AXSubrole"), "AXOutlineRow");
const outRows = outline.getAttributeValue("AXRows");
for (let i = 0; i < outRows.length; i++) {
console.log(i);
console.log(outRows[i].getAttributeValue("AXDescription"));
console.log("\n");
}
is(outRows.length, 9, "Outline has nine rows");
is(
outRows[0].getAttributeValue("AXDisclosing"),
0,
"Row is not disclosing"
);
is(
outRows[0].getAttributeValue("AXDisclosedByRow"),
null,
"Row is direct child of outline"
);
is(
outRows[0].getAttributeValue("AXDisclosedRows").length,
0,
"Row has no direct row children, has list"
);
is(
outRows[0].getAttributeValue("AXDisclosureLevel"),
1,
"Row is level one"
);
is(outRows[2].getAttributeValue("AXDisclosing"), 1, "Row is disclosing");
is(
outRows[2].getAttributeValue("AXDisclosedByRow"),
null,
"Row is direct child of group"
);
is(
outRows[2].getAttributeValue("AXDisclosedRows").length,
2,
"Row has two row children"
);
is(
outRows[2].getAttributeValue("AXDisclosureLevel"),
2,
"Row is level two"
);
is(
outRows[3].getAttributeValue("AXDisclosing"),
0,
"Row is not disclosing"
);
is(
outRows[3]
.getAttributeValue("AXDisclosedByRow")
.getAttributeValue("AXDescription"),
outRows[2].getAttributeValue("AXDescription"),
"Row is direct child of row 2"
);
is(
outRows[3].getAttributeValue("AXDisclosedRows").length,
0,
"Row has no row children"
);
is(
outRows[3].getAttributeValue("AXDisclosureLevel"),
3,
"Row is level three"
);
is(
outRows[5].getAttributeValue("AXDisclosing"),
0,
"Row is not disclosing"
);
is(
outRows[5].getAttributeValue("AXDisclosedByRow"),
null,
"Row is direct child of outline"
);
is(
outRows[5].getAttributeValue("AXDisclosedRows").length,
1,
"Row has no one row child"
);
is(
outRows[5].getAttributeValue("AXDisclosureLevel"),
1,
"Row is level one"
);
is(outRows[6].getAttributeValue("AXDisclosing"), 1, "Row is disclosing");
is(
outRows[6]
.getAttributeValue("AXDisclosedByRow")
.getAttributeValue("AXDescription"),
outRows[5].getAttributeValue("AXDescription"),
"Row is direct child of row 5"
);
is(
outRows[6].getAttributeValue("AXDisclosedRows").length,
2,
"Row has two row children"
);
is(
outRows[6].getAttributeValue("AXDisclosureLevel"),
2,
"Row is level two"
);
is(
outRows[7].getAttributeValue("AXDisclosing"),
0,
"Row is not disclosing"
);
is(
outRows[7]
.getAttributeValue("AXDisclosedByRow")
.getAttributeValue("AXDescription"),
outRows[6].getAttributeValue("AXDescription"),
"Row is direct child of row 6"
);
is(
outRows[7].getAttributeValue("AXDisclosedRows").length,
0,
"Row has no row children"
);
is(
outRows[7].getAttributeValue("AXDisclosureLevel"),
3,
"Row is level three"
);
}
);