Chromium Code Reviews| Index: third_party/WebKit/Source/modules/accessibility/InspectorAccessibilityAgent.cpp |
| diff --git a/third_party/WebKit/Source/modules/accessibility/InspectorAccessibilityAgent.cpp b/third_party/WebKit/Source/modules/accessibility/InspectorAccessibilityAgent.cpp |
| index 6b7ddcc722fb34ac001493c2a70b9aea0fc7bda3..ddf908d66559c5a631364d6431896db247551f77 100644 |
| --- a/third_party/WebKit/Source/modules/accessibility/InspectorAccessibilityAgent.cpp |
| +++ b/third_party/WebKit/Source/modules/accessibility/InspectorAccessibilityAgent.cpp |
| @@ -38,6 +38,8 @@ using namespace HTMLNames; |
| namespace { |
| +static const AXID kIDForInspectedNodeWithNoAXNode = 0; |
| + |
| void fillLiveRegionProperties(AXObject& axObject, |
| protocol::Array<AXProperty>& properties) { |
| if (!axObject.liveRegionRoot()) |
| @@ -373,6 +375,19 @@ std::unique_ptr<AXValue> createRoleNameValue(AccessibilityRole role) { |
| return roleNameValue; |
| } |
| +bool isAncestorOf(AXObject* possibleAncestor, AXObject* descendant) { |
| + if (!possibleAncestor || !descendant) |
| + return false; |
| + |
| + AXObject* ancestor = descendant; |
| + while (ancestor) { |
| + if (ancestor == possibleAncestor) |
| + return true; |
| + ancestor = ancestor->parentObject(); |
| + } |
| + return false; |
| +} |
| + |
| } // namespace |
| InspectorAccessibilityAgent::InspectorAccessibilityAgent( |
| @@ -380,11 +395,11 @@ InspectorAccessibilityAgent::InspectorAccessibilityAgent( |
| InspectorDOMAgent* domAgent) |
| : m_page(page), m_domAgent(domAgent) {} |
| -void InspectorAccessibilityAgent::getAXNodeChain( |
| +void InspectorAccessibilityAgent::getPartialAXTree( |
| ErrorString* errorString, |
| int domNodeId, |
| - bool fetchAncestors, |
| - std::unique_ptr<protocol::Array<protocol::Accessibility::AXNode>>* nodes) { |
| + const Maybe<bool>& fetchRelatives, |
| + std::unique_ptr<protocol::Array<AXNode>>* nodes) { |
| if (!m_domAgent->enabled()) { |
| *errorString = "DOM agent must be enabled"; |
| return; |
| @@ -408,40 +423,76 @@ void InspectorAccessibilityAgent::getAXNodeChain( |
| AXObject* inspectedAXObject = cache->getOrCreate(domNode); |
| *nodes = protocol::Array<protocol::Accessibility::AXNode>::create(); |
| + bool justFetchRelatives = true; |
| + if (fetchRelatives.isJust()) |
| + justFetchRelatives = fetchRelatives.fromJust(); |
| if (!inspectedAXObject || inspectedAXObject->accessibilityIsIgnored()) { |
| - (*nodes)->addItem(buildObjectForIgnoredNode(domNode, inspectedAXObject)); |
| + (*nodes)->addItem(buildObjectForIgnoredNode( |
| + domNode, inspectedAXObject, justFetchRelatives, *nodes, *cache)); |
|
dgozman
2016/10/31 21:36:31
fetchRelatives.fromMaybe(true)
would be easier.
aboxhall
2016/10/31 22:45:15
Thanks, done!
|
| } else { |
| - (*nodes)->addItem(buildProtocolAXObject(*inspectedAXObject)); |
| + (*nodes)->addItem( |
| + buildProtocolAXObject(*inspectedAXObject, inspectedAXObject, |
| + justFetchRelatives, *nodes, *cache)); |
| } |
| - if (fetchAncestors && inspectedAXObject) { |
| - AXObject* parent = inspectedAXObject->parentObjectUnignored(); |
| - while (parent) { |
| - (*nodes)->addItem(buildProtocolAXObject(*parent)); |
| - parent = parent->parentObjectUnignored(); |
| - } |
| + if (!inspectedAXObject || !inspectedAXObject->isAXLayoutObject()) |
| + return; |
| + |
| + AXObject* parent = inspectedAXObject->parentObjectUnignored(); |
| + if (!parent) |
| + return; |
| + |
| + if (justFetchRelatives) |
| + addAncestors(*parent, inspectedAXObject, *nodes, *cache); |
| +} |
| + |
| +void InspectorAccessibilityAgent::addAncestors( |
| + AXObject& firstAncestor, |
| + AXObject* inspectedAXObject, |
| + std::unique_ptr<protocol::Array<AXNode>>& nodes, |
| + AXObjectCacheImpl& cache) const { |
| + AXObject* ancestor = &firstAncestor; |
| + while (ancestor) { |
| + nodes->addItem(buildProtocolAXObject(*ancestor, inspectedAXObject, true, |
| + nodes, cache)); |
| + ancestor = ancestor->parentObjectUnignored(); |
| } |
| } |
| std::unique_ptr<AXNode> InspectorAccessibilityAgent::buildObjectForIgnoredNode( |
| Node* domNode, |
| - AXObject* axObject) const { |
| + AXObject* axObject, |
| + bool fetchRelatives, |
| + std::unique_ptr<protocol::Array<AXNode>>& nodes, |
| + AXObjectCacheImpl& cache) const { |
| AXObject::IgnoredReasons ignoredReasons; |
| - AXID axID = 0; |
| - if (axObject) |
| + AXID axID = kIDForInspectedNodeWithNoAXNode; |
| + if (axObject && axObject->isAXLayoutObject()) |
| axID = axObject->axObjectID(); |
| std::unique_ptr<AXNode> ignoredNodeObject = |
| AXNode::create().setNodeId(String::number(axID)).setIgnored(true).build(); |
| - if (axObject) { |
| + AccessibilityRole role = AccessibilityRole::IgnoredRole; |
| + ignoredNodeObject->setRole(createRoleNameValue(role)); |
| + |
| + if (axObject && axObject->isAXLayoutObject()) { |
| axObject->computeAccessibilityIsIgnored(&ignoredReasons); |
| - axID = axObject->axObjectID(); |
| - AccessibilityRole role = axObject->roleValue(); |
| - ignoredNodeObject->setRole(createRoleNameValue(role)); |
| + |
| + if (fetchRelatives) { |
| + populateRelatives(*axObject, axObject, *(ignoredNodeObject.get()), nodes, |
| + cache); |
| + } |
| } else if (domNode && !domNode->layoutObject()) { |
| + if (fetchRelatives) { |
| + populateDOMNodeRelatives(*domNode, *(ignoredNodeObject.get()), nodes, |
| + cache); |
| + } |
| ignoredReasons.append(IgnoredReason(AXNotRendered)); |
| } |
| + if (domNode) |
| + ignoredNodeObject->setBackendDomNodeId(DOMNodeIds::idForNode(domNode)); |
| + |
| std::unique_ptr<protocol::Array<AXProperty>> ignoredReasonProperties = |
| protocol::Array<AXProperty>::create(); |
| for (size_t i = 0; i < ignoredReasons.size(); i++) |
| @@ -451,8 +502,105 @@ std::unique_ptr<AXNode> InspectorAccessibilityAgent::buildObjectForIgnoredNode( |
| return ignoredNodeObject; |
| } |
| +void InspectorAccessibilityAgent::populateDOMNodeRelatives( |
| + Node& inspectedDOMNode, |
| + AXNode& nodeObject, |
| + std::unique_ptr<protocol::Array<AXNode>>& nodes, |
| + AXObjectCacheImpl& cache) const { |
| + // Walk up parents until an AXObject can be found. |
| + Node* parentNode = FlatTreeTraversal::parent(inspectedDOMNode); |
| + AXObject* parentAXObject = cache.getOrCreate(parentNode); |
| + while (!parentAXObject) { |
|
dgozman
2016/10/31 21:36:31
while (parentNode && !parentAXObject)
aboxhall
2016/10/31 22:45:15
Done.
|
| + parentNode = FlatTreeTraversal::parent(inspectedDOMNode); |
| + parentAXObject = cache.getOrCreate(parentNode); |
| + }; |
| + if (parentAXObject) { |
| + if (parentAXObject->accessibilityIsIgnored()) |
| + parentAXObject = parentAXObject->parentObjectUnignored(); |
| + // Populate parent, ancestors and siblings. |
| + nodeObject.setParentId(String::number(parentAXObject->axObjectID())); |
| + std::unique_ptr<AXNode> parentNodeObject = |
| + buildProtocolAXObject(*parentAXObject, nullptr, true, nodes, cache); |
| + std::unique_ptr<protocol::Array<AXNodeId>> siblingIds = |
| + protocol::Array<AXNodeId>::create(); |
| + findDOMNodeChildren(siblingIds, *parentNode, *parentNode, inspectedDOMNode, |
| + nodes, cache); |
| + parentNodeObject->setChildIds(std::move(siblingIds)); |
| + nodes->addItem(std::move(parentNodeObject)); |
| + |
| + AXObject* grandparentAXObject = parentAXObject->parentObjectUnignored(); |
| + if (grandparentAXObject) |
| + addAncestors(*grandparentAXObject, nullptr, nodes, cache); |
| + } |
| + |
| + // Then populate children. |
| + std::unique_ptr<protocol::Array<AXNodeId>> childIds = |
| + protocol::Array<AXNodeId>::create(); |
| + findDOMNodeChildren(childIds, inspectedDOMNode, inspectedDOMNode, |
| + inspectedDOMNode, nodes, cache); |
| + nodeObject.setChildIds(std::move(childIds)); |
| +} |
| + |
| +void InspectorAccessibilityAgent::findDOMNodeChildren( |
| + std::unique_ptr<protocol::Array<AXNodeId>>& childIds, |
| + Node& parentNode, |
| + Node& treeParentNode, |
| + Node& inspectedDOMNode, |
| + std::unique_ptr<protocol::Array<AXNode>>& nodes, |
| + AXObjectCacheImpl& cache) const { |
| + NodeList* childNodes = parentNode.childNodes(); |
| + for (size_t i = 0; i < childNodes->length(); ++i) { |
| + Node* childNode = childNodes->item(i); |
| + if (childNode == &inspectedDOMNode) { |
| + childIds->addItem(String::number(kIDForInspectedNodeWithNoAXNode)); |
| + continue; |
| + } |
| + |
| + AXObject* childAXObject = cache.getOrCreate(childNode); |
| + if (childAXObject) { |
| + if (childAXObject->accessibilityIsIgnored()) { |
| + // search for un-ignored descendants |
| + findDOMNodeChildren(childIds, *childNode, parentNode, inspectedDOMNode, |
| + nodes, cache); |
| + } else { |
| + if (&treeParentNode == &inspectedDOMNode) { |
| + childIds->addItem(String::number(childAXObject->axObjectID())); |
| + std::unique_ptr<AXNode> childNode = buildProtocolAXObject( |
| + *childAXObject, nullptr, true, nodes, cache); |
| + childNode->setParentId( |
| + String::number(kIDForInspectedNodeWithNoAXNode)); |
| + nodes->addItem(std::move(childNode)); |
| + } else { |
| + addChild(childIds, *childAXObject, nullptr, nodes, cache); |
| + } |
| + } |
| + |
| + continue; |
| + } |
| + |
| + if (!childAXObject || |
| + childNode->isShadowIncludingInclusiveAncestorOf(&inspectedDOMNode)) { |
| + // If the inspected node may be a descendant of this node, keep walking |
| + // recursively until we find its actual siblings. |
| + findDOMNodeChildren(childIds, *childNode, parentNode, inspectedDOMNode, |
| + nodes, cache); |
| + continue; |
| + } |
| + |
| + // Otherwise, just add the un-ignored children. |
| + const AXObject::AXObjectVector& indirectChildren = |
| + childAXObject->children(); |
| + for (unsigned i = 0; i < indirectChildren.size(); ++i) |
| + addChild(childIds, *(indirectChildren[i]), nullptr, nodes, cache); |
| + } |
| +} |
| + |
| std::unique_ptr<AXNode> InspectorAccessibilityAgent::buildProtocolAXObject( |
| - AXObject& axObject) const { |
| + AXObject& axObject, |
| + AXObject* inspectedAXObject, |
| + bool fetchRelatives, |
| + std::unique_ptr<protocol::Array<AXNode>>& nodes, |
| + AXObjectCacheImpl& cache) const { |
| AccessibilityRole role = axObject.roleValue(); |
| std::unique_ptr<AXNode> nodeObject = |
| AXNode::create() |
| @@ -496,12 +644,18 @@ std::unique_ptr<AXNode> InspectorAccessibilityAgent::buildProtocolAXObject( |
| nodeObject->setProperties(std::move(properties)); |
| } |
| - fillCoreProperties(axObject, *(nodeObject.get())); |
| + fillCoreProperties(axObject, inspectedAXObject, fetchRelatives, |
| + *(nodeObject.get()), nodes, cache); |
| return nodeObject; |
| } |
| -void InspectorAccessibilityAgent::fillCoreProperties(AXObject& axObject, |
| - AXNode& nodeObject) const { |
| +void InspectorAccessibilityAgent::fillCoreProperties( |
| + AXObject& axObject, |
| + AXObject* inspectedAXObject, |
| + bool fetchRelatives, |
| + AXNode& nodeObject, |
| + std::unique_ptr<protocol::Array<AXNode>>& nodes, |
| + AXObjectCacheImpl& cache) const { |
| AXNameFrom nameFrom; |
| AXObject::AXObjectVector nameObjects; |
| axObject.name(nameFrom, &nameObjects); |
| @@ -522,6 +676,112 @@ void InspectorAccessibilityAgent::fillCoreProperties(AXObject& axObject, |
| if (!stringValue.isEmpty()) |
| nodeObject.setValue(createValue(stringValue)); |
| } |
| + |
| + if (fetchRelatives) |
| + populateRelatives(axObject, inspectedAXObject, nodeObject, nodes, cache); |
| + |
| + Node* node = axObject.getNode(); |
| + if (node) |
| + nodeObject.setBackendDomNodeId(DOMNodeIds::idForNode(node)); |
| +} |
| + |
| +void InspectorAccessibilityAgent::populateRelatives( |
| + AXObject& axObject, |
| + AXObject* inspectedAXObject, |
| + AXNode& nodeObject, |
| + std::unique_ptr<protocol::Array<AXNode>>& nodes, |
| + AXObjectCacheImpl& cache) const { |
| + AXObject* parentObject = axObject.parentObject(); |
| + if (parentObject && parentObject != inspectedAXObject) { |
| + // Use unignored parent unless parent is inspected ignored object. |
| + parentObject = axObject.parentObjectUnignored(); |
| + } |
| + if (parentObject) |
| + nodeObject.setParentId(String::number(parentObject->axObjectID())); |
| + |
| + std::unique_ptr<protocol::Array<AXNodeId>> childIds = |
| + protocol::Array<AXNodeId>::create(); |
| + if (!inspectedAXObject) { |
| + // If there is no AXObject for the inspected node, the caller will populate |
| + // its relatives. |
| + return; |
| + } |
| + |
| + if (&axObject == inspectedAXObject->parentObjectUnignored() && |
| + inspectedAXObject->accessibilityIsIgnored()) { |
| + // This is the parent of an ignored object, so search for its siblings. |
| + addSiblingsOfIgnored(childIds, axObject, inspectedAXObject, nodes, cache); |
| + } else { |
| + addChildren(axObject, *inspectedAXObject, childIds, nodes, cache); |
| + } |
| + nodeObject.setChildIds(std::move(childIds)); |
| +} |
| + |
| +void InspectorAccessibilityAgent::addSiblingsOfIgnored( |
| + std::unique_ptr<protocol::Array<AXNodeId>>& childIds, |
| + AXObject& parentAXObject, |
| + AXObject* inspectedAXObject, |
| + std::unique_ptr<protocol::Array<AXNode>>& nodes, |
| + AXObjectCacheImpl& cache) const { |
| + for (AXObject* childAXObject = parentAXObject.rawFirstChild(); childAXObject; |
| + childAXObject = childAXObject->rawNextSibling()) { |
| + if (!childAXObject->accessibilityIsIgnored() || |
| + childAXObject == inspectedAXObject) { |
| + addChild(childIds, *childAXObject, inspectedAXObject, nodes, cache); |
| + continue; |
| + } |
| + |
| + if (isAncestorOf(childAXObject, inspectedAXObject)) { |
| + addSiblingsOfIgnored(childIds, *childAXObject, inspectedAXObject, nodes, |
| + cache); |
| + continue; |
| + } |
| + |
| + const AXObject::AXObjectVector& indirectChildren = |
| + childAXObject->children(); |
| + for (unsigned i = 0; i < indirectChildren.size(); ++i) { |
| + addChild(childIds, *(indirectChildren[i]), inspectedAXObject, nodes, |
| + cache); |
| + } |
| + } |
| +} |
| + |
| +void InspectorAccessibilityAgent::addChild( |
| + std::unique_ptr<protocol::Array<AXNodeId>>& childIds, |
| + AXObject& childAXObject, |
| + AXObject* inspectedAXObject, |
| + std::unique_ptr<protocol::Array<AXNode>>& nodes, |
| + AXObjectCacheImpl& cache) const { |
| + childIds->addItem(String::number(childAXObject.axObjectID())); |
| + if (&childAXObject == inspectedAXObject) |
| + return; |
| + nodes->addItem(buildProtocolAXObject(childAXObject, inspectedAXObject, true, |
| + nodes, cache)); |
| +} |
| + |
| +void InspectorAccessibilityAgent::addChildren( |
| + AXObject& axObject, |
| + AXObject& inspectedAXObject, |
| + std::unique_ptr<protocol::Array<AXNodeId>>& childIds, |
| + std::unique_ptr<protocol::Array<AXNode>>& nodes, |
| + AXObjectCacheImpl& cache) const { |
| + const AXObject::AXObjectVector& children = axObject.children(); |
| + for (unsigned i = 0; i < children.size(); i++) { |
| + AXObject& childAXObject = *children[i].get(); |
| + childIds->addItem(String::number(childAXObject.axObjectID())); |
| + if (&childAXObject != &inspectedAXObject && |
| + (&axObject == &inspectedAXObject || |
| + &axObject == inspectedAXObject.parentObjectUnignored())) { |
| + // Only add children/siblings of inspected node to returned nodes. |
| + std::unique_ptr<AXNode> childNode = buildProtocolAXObject( |
| + childAXObject, &inspectedAXObject, true, nodes, cache); |
| + // If the inspected node is ignored, the default parent ID will be the |
| + // unignored parent. |
| + if (axObject.accessibilityIsIgnored()) |
| + childNode->setParentId(String::number(axObject.axObjectID())); |
| + nodes->addItem(std::move(childNode)); |
| + } |
| + } |
| } |
| DEFINE_TRACE(InspectorAccessibilityAgent) { |