Skip to content

Commit

Permalink
Bug 1787283: Provide NODE_CHILD_OF, NODE_PARENT_OF relations in remot…
Browse files Browse the repository at this point in the history
…e acc, r=morgan,Jamie

We'd like to provide NODE_CHILD_OF and NODE_PARENT_OF relations in the parent
process, relying on cached information, to avoid sync IPDL messages. This
revision implements most of the use cases for this relation for remote
accessibles, notably setting aside handling of MathML's <mroot> parent/child
relation. ARIA trees, treegrids, lists, and so on are handled in the remote
accessible largely identically to how they were handled in LocalAccessible.
Rather than define a new rule for walking the tree to find children, this
revision unifies the ItemIterator so it works on generic Accessibles, and uses
it in RemoteAccessibleBase the same way it's used in LocalAccessible. The
special case carve-out for MSAA clients now exists as a simpler IsTopLevel
check in DocAccessibleParent.

Differential Revision: https://phabricator.services.mozilla.com/D159452
  • Loading branch information
Nathan LaPre committed Oct 18, 2022
1 parent e6ba9b8 commit 3fc16a5
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 11 deletions.
8 changes: 3 additions & 5 deletions accessible/base/AccIterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,17 +296,15 @@ Accessible* SingleAccIterator::Next() {
// ItemIterator
////////////////////////////////////////////////////////////////////////////////

LocalAccessible* ItemIterator::Next() {
Accessible* ItemIterator::Next() {
if (mContainer) {
Accessible* first = AccGroupInfo::FirstItemOf(mContainer);
mAnchor = first ? first->AsLocal() : nullptr;
mAnchor = AccGroupInfo::FirstItemOf(mContainer);
mContainer = nullptr;
return mAnchor;
}

if (mAnchor) {
Accessible* next = AccGroupInfo::NextItemTo(mAnchor);
mAnchor = next ? next->AsLocal() : nullptr;
mAnchor = AccGroupInfo::NextItemTo(mAnchor);
}

return mAnchor;
Expand Down
9 changes: 4 additions & 5 deletions accessible/base/AccIterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,19 +262,18 @@ class SingleAccIterator : public AccIterable {
*/
class ItemIterator : public AccIterable {
public:
explicit ItemIterator(const LocalAccessible* aItemContainer)
explicit ItemIterator(const Accessible* aItemContainer)
: mContainer(aItemContainer), mAnchor(nullptr) {}
virtual ~ItemIterator() {}

virtual LocalAccessible* Next() override;
virtual Accessible* Next() override;

private:
ItemIterator() = delete;
ItemIterator(const ItemIterator&) = delete;
ItemIterator& operator=(const ItemIterator&) = delete;

const LocalAccessible* mContainer;
LocalAccessible* mAnchor;
const Accessible* mContainer;
Accessible* mAnchor;
};

/**
Expand Down
2 changes: 1 addition & 1 deletion accessible/base/CacheConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ struct RelationData {
* CONTROLLER_FOR relation, while the `for` attribute of a <label> describes a
* LABEL_FOR relation. To ensure we process these attributes appropriately,
* RelationData.mValidTag contains the atom for the tag this attribute/relation
* type paring is valid on. If the pairing is valid for all tag types, this
* type pairing is valid on. If the pairing is valid for all tag types, this
* field is null.
*/
static constexpr RelationData kRelationTypeAtoms[] = {
Expand Down
14 changes: 14 additions & 0 deletions accessible/ipc/DocAccessibleParent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "nsAccUtils.h"
#include "nsIIOService.h"
#include "TextRange.h"
#include "Relation.h"
#include "RootAccessible.h"

#if defined(XP_WIN)
Expand Down Expand Up @@ -1326,6 +1327,19 @@ void DocAccessibleParent::URL(nsAString& aURL) const {
CopyUTF8toUTF16(url, aURL);
}

Relation DocAccessibleParent::RelationByType(RelationType aType) const {
// If the accessible is top-level, provide the NODE_CHILD_OF relation so that
// MSAA clients can easily get to true parent instead of getting to oleacc's
// ROLE_WINDOW accessible when window emulation is enabled which will prevent
// us from going up further (because it is system generated and has no idea
// about the hierarchy above it).
if (aType == RelationType::NODE_CHILD_OF && IsTopLevel()) {
return Relation(Parent());
}

return RemoteAccessibleBase<RemoteAccessible>::RelationByType(aType);
}

DocAccessibleParent* DocAccessibleParent::GetFrom(
dom::BrowsingContext* aBrowsingContext) {
if (!aBrowsingContext) {
Expand Down
2 changes: 2 additions & 0 deletions accessible/ipc/DocAccessibleParent.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ class DocAccessibleParent : public RemoteAccessible,

void URL(nsAString& aURL) const;

virtual Relation RelationByType(RelationType aType) const override;

// Tracks cached reverse relations (ie. those not set explicitly by an
// attribute like aria-labelledby) for accessibles in this doc. This map is of
// the form: {accID, {relationType, [targetAccID, targetAccID, ...]}}
Expand Down
31 changes: 31 additions & 0 deletions accessible/ipc/RemoteAccessibleBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,37 @@ Relation RemoteAccessibleBase<Derived>::RelationByType(
return Relation();
}

// Handle ARIA tree, treegrid parent/child relations. Each of these cases
// relies on cached group info. To find the parent of an accessible, use the
// unified conceptual parent.
if (aType == RelationType::NODE_CHILD_OF) {
const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
roleMapEntry->role == roles::LISTITEM ||
roleMapEntry->role == roles::ROW)) {
if (const AccGroupInfo* groupInfo =
const_cast<RemoteAccessibleBase<Derived>*>(this)
->GetOrCreateGroupInfo()) {
return Relation(groupInfo->ConceptualParent());
}
}
return Relation();
}

// To find the children of a parent, provide an iterator through its items.
if (aType == RelationType::NODE_PARENT_OF) {
const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
roleMapEntry->role == roles::LISTITEM ||
roleMapEntry->role == roles::ROW ||
roleMapEntry->role == roles::OUTLINE ||
roleMapEntry->role == roles::LIST ||
roleMapEntry->role == roles::TREE_TABLE)) {
return Relation(new ItemIterator(this));
}
return Relation();
}

Relation rel;
if (!mCachedFields) {
return rel;
Expand Down
63 changes: 63 additions & 0 deletions accessible/tests/browser/e10s/browser_caching_relations.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,3 +382,66 @@ addAccessibleTask(
},
{ chrome: true, iframe: true, remoteIframe: true }
);

/*
* Test relation caching for NODE_CHILD_OF and NODE_PARENT_OF with aria trees.
*/
addAccessibleTask(
`
<div role="tree" id="tree">
<div role="treeitem" id="treeitem">test</div>
<div role="treeitem" id="treeitem2">test</div>
</div>`,
async function(browser, accDoc) {
const tree = findAccessibleChildByID(accDoc, "tree");
const treeItem = findAccessibleChildByID(accDoc, "treeitem");
const treeItem2 = findAccessibleChildByID(accDoc, "treeitem2");

await testCachedRelation(tree, RELATION_NODE_PARENT_OF, [
treeItem,
treeItem2,
]);
await testCachedRelation(treeItem, RELATION_NODE_CHILD_OF, tree);
},
{ chrome: true, iframe: true, remoteIframe: true }
);

/*
* Test relation caching for NODE_CHILD_OF and NODE_PARENT_OF with aria lists.
*/
addAccessibleTask(
`
<div id="l1" role="list">
<div id="l1i1" role="listitem" aria-level="1">a</div>
<div id="l1i2" role="listitem" aria-level="2">b</div>
<div id="l1i3" role="listitem" aria-level="1">c</div>
</div>`,
async function(browser, accDoc) {
const list = findAccessibleChildByID(accDoc, "l1");
const listItem1 = findAccessibleChildByID(accDoc, "l1i1");
const listItem2 = findAccessibleChildByID(accDoc, "l1i2");
const listItem3 = findAccessibleChildByID(accDoc, "l1i3");

await testCachedRelation(list, RELATION_NODE_PARENT_OF, [
listItem1,
listItem3,
]);
await testCachedRelation(listItem1, RELATION_NODE_CHILD_OF, list);
await testCachedRelation(listItem3, RELATION_NODE_CHILD_OF, list);

await testCachedRelation(listItem1, RELATION_NODE_PARENT_OF, listItem2);
await testCachedRelation(listItem2, RELATION_NODE_CHILD_OF, listItem1);
},
{ chrome: true, iframe: true, remoteIframe: true }
);

/*
* Test NODE_CHILD_OF relation caching for JAWS window emulation special case.
*/
addAccessibleTask(
``,
async function(browser, accDoc) {
await testCachedRelation(accDoc, RELATION_NODE_CHILD_OF, accDoc.parent);
},
{ topLevel: isCacheEnabled, chrome: true }
);

0 comments on commit 3fc16a5

Please sign in to comment.