Chromium Code Reviews| Index: Source/modules/accessibility/InspectorAccessibilityAgent.cpp |
| diff --git a/Source/modules/accessibility/InspectorAccessibilityAgent.cpp b/Source/modules/accessibility/InspectorAccessibilityAgent.cpp |
| index 82584d2434012dea0d2cf75da9684ef5eba5d1b7..a8133f675e58b81cf80a943b5cdd51c7f3599859 100644 |
| --- a/Source/modules/accessibility/InspectorAccessibilityAgent.cpp |
| +++ b/Source/modules/accessibility/InspectorAccessibilityAgent.cpp |
| @@ -6,12 +6,19 @@ |
| #include "modules/accessibility/InspectorAccessibilityAgent.h" |
| +#include "core/HTMLNames.h" |
| #include "core/dom/AXObjectCache.h" |
| #include "core/dom/DOMNodeIds.h" |
| #include "core/dom/Element.h" |
| +#include "core/dom/ElementTraversal.h" |
| +#include "core/html/HTMLDialogElement.h" |
| +#include "core/html/HTMLFrameOwnerElement.h" |
| +#include "core/html/HTMLLabelElement.h" |
| #include "core/inspector/InspectorDOMAgent.h" |
| #include "core/inspector/InspectorState.h" |
| #include "core/inspector/InspectorStyleSheet.h" |
| +#include "core/layout/LayoutBlockFlow.h" |
| +#include "core/layout/LayoutBoxModelObject.h" |
| #include "core/page/Page.h" |
| #include "modules/accessibility/AXObject.h" |
| #include "modules/accessibility/AXObjectCacheImpl.h" |
| @@ -19,7 +26,13 @@ |
| namespace blink { |
| +using HTMLNames::altAttr; |
| +using HTMLNames::aria_describedbyAttr; |
| +using HTMLNames::aria_helpAttr; |
| +using HTMLNames::titleAttr; |
| using TypeBuilder::Accessibility::AXGlobalStates; |
| +using TypeBuilder::Accessibility::AXIgnoredNode; |
| +using TypeBuilder::Accessibility::AXIgnoredReasons; |
| using TypeBuilder::Accessibility::AXLiveRegionAttributes; |
| using TypeBuilder::Accessibility::AXNode; |
| using TypeBuilder::Accessibility::AXNodeId; |
| @@ -49,7 +62,6 @@ PassRefPtr<AXProperty> createProperty(AXLiveRegionAttributes::Enum name, PassRef |
| return createProperty(TypeBuilder::getEnumConstantValue(name), value); |
| } |
| - |
| PassRefPtr<AXProperty> createProperty(AXRelationshipAttributes::Enum name, PassRefPtr<AXValue> value) |
| { |
| return createProperty(TypeBuilder::getEnumConstantValue(name), value); |
| @@ -65,6 +77,10 @@ PassRefPtr<AXProperty> createProperty(AXWidgetStates::Enum name, PassRefPtr<AXVa |
| return createProperty(TypeBuilder::getEnumConstantValue(name), value); |
| } |
| +PassRefPtr<AXProperty> createProperty(AXIgnoredReasons::Enum name, PassRefPtr<AXValue> value) |
| +{ |
| + return createProperty(TypeBuilder::getEnumConstantValue(name), value); |
| +} |
| PassRefPtr<AXValue> createValue(String value, AXValueType::Enum type = AXValueType::String) |
| { |
| @@ -381,9 +397,8 @@ void fillRelationships(AXObject* axObject, PassRefPtr<TypeBuilder::Array<AXPrope |
| results.clear(); |
| } |
| -PassRefPtr<AXNode> buildObjectForNode(Node* node, AXObject* axObject, AXObjectCacheImpl* cacheImpl, PassRefPtr<TypeBuilder::Array<AXProperty>> properties) |
| +PassRefPtr<AXValue> createRoleNameValue(AccessibilityRole role) |
| { |
| - AccessibilityRole role = axObject->roleValue(); |
| AtomicString roleName = AXObject::roleName(role); |
| RefPtr<AXValue> roleNameValue; |
| if (!roleName.isNull()) { |
| @@ -391,7 +406,190 @@ PassRefPtr<AXNode> buildObjectForNode(Node* node, AXObject* axObject, AXObjectCa |
| } else { |
| roleNameValue = createValue(AXObject::internalRoleName(role), AXValueType::InternalRole); |
| } |
| - RefPtr<AXNode> nodeObject = AXNode::create().setNodeId(String::number(axObject->axObjectID())).setRole(roleNameValue).setProperties(properties); |
| + return roleNameValue; |
| +} |
| + |
| +PassRefPtr<TypeBuilder::Array<AXProperty>> buildIgnoredReasons(Node* node, AXObject* axObject, AXObjectCacheImpl* cacheImpl) |
|
dmazzoni
2015/04/09 05:46:10
Should this be in AXLayoutObject? It feels like we
aboxhall
2015/04/09 20:15:36
Yeah, I considered that. It would certainly be cle
|
| +{ |
| + RefPtr<TypeBuilder::Array<AXProperty>> ignoredReasons = TypeBuilder::Array<AXProperty>::create(); |
| + // hidden |
| + const AXObject* hiddenRoot = axObject->ariaHiddenRoot(); |
| + if (hiddenRoot) { |
| + if (hiddenRoot == axObject) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::AriaHidden, createBooleanValue(true))); |
| + else |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::AriaHiddenRoot, createRelatedNodeValue(hiddenRoot))); |
| + return ignoredReasons; |
| + } |
| + |
| + // inert |
| + if (node->isInert()) { |
| + HTMLDialogElement* activeModalDialog = node->document().activeModalDialog(); |
| + while (!activeModalDialog) { |
| + HTMLFrameOwnerElement* ownerElement = node->document().ownerElement(); |
| + if (!ownerElement) |
| + break; |
| + if (ownerElement && ownerElement->isInert()) |
| + activeModalDialog = ownerElement->document().activeModalDialog(); |
| + } |
| + |
| + if (activeModalDialog) { |
| + AXObject* dialogObject = cacheImpl->getOrCreate(activeModalDialog); |
| + if (dialogObject) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::ActiveModalDialog, createRelatedNodeValue(dialogObject))); |
| + return ignoredReasons; |
| + } |
| + } |
| + } |
| + |
| + // ancestor can't have children (incl menuitem/menubutton parent) |
| + if (axObject->isDescendantOfBarrenParent()) { |
| + AXObject* parent = axObject->parentObject(); |
| + while (parent && parent->canHaveChildren()) |
| + parent = parent->parentObject(); |
| + if (parent) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::AncestorDisallowsChild, createRelatedNodeValue(parent))); |
| + return ignoredReasons; |
| + } |
| + } |
| + |
| + // role == ignored || role == none || role == presentational |
| + AccessibilityRole role = axObject->roleValue(); |
| + if (role == IgnoredRole || role == NoneRole || role == PresentationalRole) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::IgnoredRole, createRoleNameValue(role))); |
| + return ignoredReasons; |
| + } |
| + |
| + // inherits presentational role |
| + if (axObject->hasInheritedPresentationalRole()) { |
| + AXObject* parent = axObject->parentObject(); |
| + while (parent && !parent->isPresentational()) |
| + parent = parent->parentObject(); |
| + if (parent) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::InheritsPresentation, createRelatedNodeValue(parent))); |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::Focusable, createBooleanValue(node->isElementNode() && toElement(node)->supportsFocus()))); |
| + return ignoredReasons; |
| + } |
| + } |
| + |
| + // disallowed child of ARIA tree |
| + AXObject* potentialTree = axObject->parentObject(); |
| + while (potentialTree && !potentialTree->isTree()) |
| + potentialTree = potentialTree->parentObject(); |
| + if (potentialTree && role != TreeItemRole && role != StaticTextRole) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::AncestorDisallowsChild, createRelatedNodeValue(potentialTree))); |
| + return ignoredReasons; |
| + } |
| + |
| + if (LayoutObject* layoutObject = axObject->layoutObject()) { |
| + // Popup menu |
| + for (LayoutObject* parent = layoutObject->parent(); parent; parent = parent->parent()) { |
| + if (parent->isBoxModelObject() && toLayoutBoxModelObject(parent)->isMenuList()) { |
| + AXObject* parentAXObject = cacheImpl->getOrCreate(parent); |
| + if (parentAXObject) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::AncestorDisallowsChild, createRelatedNodeValue(parentAXObject))); |
| + return ignoredReasons; |
| + } |
| + } |
| + } |
| + |
| + // static text inside MenuItem/MenuButton |
| + if (layoutObject->isText()) { |
| + AXObject* parent = axObject->parentObjectUnignored(); |
| + if (parent && (parent->ariaRoleAttribute() == MenuItemRadioRole || parent->ariaRoleAttribute() == MenuButtonRole)) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::AncestorDisallowsChild, createRelatedNodeValue(parent))); |
| + return ignoredReasons; |
| + } |
| + } |
| + |
| + // Empty/whitespace only layoutText |
| + LayoutText* layoutText = toLayoutText(layoutObject); |
| + if (!layoutText->firstTextBox() || layoutText->text().impl()->containsOnlyWhitespace()) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::EmptyText, createBooleanValue(true))); |
| + return ignoredReasons; |
| + } |
| + |
| + // Unfocusable layoutBlock |
| + if (layoutObject->isLayoutBlockFlow() && layoutObject->childrenInline() && !axObject->canSetFocusAttribute() && (!toLayoutBlockFlow(layoutObject)->firstLineBox())) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::Focusable, createBooleanValue(false))); |
|
dmazzoni
2015/04/09 05:46:10
"not focusable" seems to be a strange reason to me
aboxhall
2015/04/09 20:15:36
As you say, it's more of a heuristic than a reason
|
| + return ignoredReasons; |
| + } |
| + } |
| + |
| + // is [part of] label for checkbox or radio |
| + if (!axObject->isControl() && !node->isLink()) { |
| + HTMLLabelElement* label = Traversal<HTMLLabelElement>::firstAncestorOrSelf(*node); |
| + if (label) { |
| + if (!label->isSameNode(node)) { |
| + AXObject* labelAXObject = cacheImpl->getOrCreate(label); |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::LabelContainer, createRelatedNodeValue(labelAXObject))); |
| + return ignoredReasons; |
| + } |
| + |
| + HTMLElement* correspondingControl = label->control(); |
| + if (correspondingControl) { |
| + AXObject* controlAXObject = cacheImpl->getOrCreate(correspondingControl); |
| + if (controlAXObject) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::LabelFor, createRelatedNodeValue(controlAXObject))); |
| + return ignoredReasons; |
| + } |
| + } |
| + } |
| + } |
| + |
| + // is <span> |
| + if (isHTMLSpanElement(node)) { |
| + Element* element = toElement(node); |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::IgnoredTagName, createValue(element->tagName()))); |
| + return ignoredReasons; |
| + } |
| + |
| + if (axObject->isImage()) { |
| + if (!axObject->canSetFocusAttribute()) { |
| + // is image with zero alt text |
| + Element* element = toElement(node); |
| + const AtomicString& alt = element->getAttribute(altAttr); |
| + if (alt.string().containsOnlyWhitespace() && !alt.isNull()) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::Focusable, createBooleanValue(false))); |
| + |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::Alt, createValue(alt.string()))); |
| + return ignoredReasons; |
| + } |
| + |
| + // TODO(aboxhall): is spacer image |
| + } |
| + } |
| + |
| + // is canvas with no fallback |
| + if (axObject->isCanvas()) { |
| + if (!ElementTraversal::firstChild(*node)) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::NoFallbackContent, createBooleanValue(true))); |
| + return ignoredReasons; |
| + } |
| + } |
| + |
| + // is other and has no text alternative |
| + if (axObject->getAttribute(aria_helpAttr).isEmpty() && axObject->getAttribute(aria_describedbyAttr).isEmpty() && axObject->getAttribute(altAttr).isEmpty() && axObject->getAttribute(titleAttr).isEmpty() && axObject->accessibilityDescription().isEmpty()) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::NoTextAlternative, createBooleanValue(true))); |
| + return ignoredReasons; |
| + } |
| + return ignoredReasons; |
| +} |
| + |
| +PassRefPtr<AXNode> buildObjectForIgnoredNode(Node* node, AXObject* axObject, AXObjectCacheImpl* cacheImpl) |
| +{ |
| + RefPtr<TypeBuilder::Array<AXProperty>> ignoredReasons = buildIgnoredReasons(node, axObject, cacheImpl); |
| + RefPtr<AXNode> ignoredNodeObject = AXNode::create().setNodeId(String::number(axObject->axObjectID())).setIgnored(true); |
| + ignoredNodeObject->setIgnoredReasons(ignoredReasons); |
| + return ignoredNodeObject; |
| +} |
| + |
| +PassRefPtr<AXNode> buildObjectForNode(Node* node, AXObject* axObject, AXObjectCacheImpl* cacheImpl, PassRefPtr<TypeBuilder::Array<AXProperty>> properties) |
| +{ |
| + AccessibilityRole role = axObject->roleValue(); |
| + RefPtr<AXNode> nodeObject = AXNode::create().setNodeId(String::number(axObject->axObjectID())).setIgnored(false); |
| + nodeObject->setRole(createRoleNameValue(role)); |
| + nodeObject->setProperties(properties); |
| String computedName = cacheImpl->computedNameForNode(node); |
| if (!computedName.isEmpty()) |
| nodeObject->setName(createValue(computedName)); |
| @@ -431,6 +629,11 @@ void InspectorAccessibilityAgent::getAXNode(ErrorString* errorString, int nodeId |
| if (!axObject) |
| return; |
| + if (axObject->accessibilityIsIgnored()) { |
| + accessibilityNode = buildObjectForIgnoredNode(node, axObject, cacheImpl); |
| + return; |
| + } |
| + |
| RefPtr<TypeBuilder::Array<AXProperty>> properties = TypeBuilder::Array<AXProperty>::create(); |
| fillLiveRegionProperties(axObject, properties); |
| fillGlobalStates(axObject, properties); |