Chromium Code Reviews| Index: Source/modules/accessibility/AXLayoutObject.cpp |
| diff --git a/Source/modules/accessibility/AXLayoutObject.cpp b/Source/modules/accessibility/AXLayoutObject.cpp |
| index 46ead68c75698a1e6654024b5115b0f297c38d9e..67f7e5fefcbfcfa26b54a0a86d0382907fca0192 100644 |
| --- a/Source/modules/accessibility/AXLayoutObject.cpp |
| +++ b/Source/modules/accessibility/AXLayoutObject.cpp |
| @@ -71,6 +71,7 @@ |
| #include "modules/accessibility/AXSVGRoot.h" |
| #include "modules/accessibility/AXSpinButton.h" |
| #include "modules/accessibility/AXTable.h" |
| +#include "modules/accessibility/InspectorTypeBuilderHelper.h" |
| #include "platform/text/PlatformLocale.h" |
| #include "wtf/StdLibExtras.h" |
| @@ -80,6 +81,8 @@ namespace blink { |
| using namespace HTMLNames; |
| +using TypeBuilder::Accessibility::AXIgnoredReasons; |
| + |
| static inline LayoutObject* firstChildInContinuation(const LayoutInline& layoutObject) |
| { |
| LayoutBoxModelObject* r = layoutObject.continuation(); |
| @@ -464,52 +467,74 @@ bool AXLayoutObject::isSelected() const |
| // Whether objects are ignored, i.e. not included in the tree. |
| // |
| -AXObjectInclusion AXLayoutObject::defaultObjectInclusion() const |
| +AXObjectInclusion AXLayoutObject::defaultObjectInclusion(PassRefPtr<TypeBuilder::Array<TypeBuilder::Accessibility::AXProperty>> passIgnoredReasons) const |
| { |
| // The following cases can apply to any element that's a subclass of AXLayoutObject. |
| - if (!m_layoutObject) |
| + RefPtr<TypeBuilder::Array<TypeBuilder::Accessibility::AXProperty>> ignoredReasons = passIgnoredReasons; |
| + |
| + if (!m_layoutObject) { |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::NotRendered, createBooleanValue(true))); |
| return IgnoreObject; |
| + } |
| if (m_layoutObject->style()->visibility() != VISIBLE) { |
| // aria-hidden is meant to override visibility as the determinant in AX hierarchy inclusion. |
| if (equalIgnoringCase(getAttribute(aria_hiddenAttr), "false")) |
| return DefaultBehavior; |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::NotVisible, createBooleanValue(true))); |
| return IgnoreObject; |
| } |
| - return AXObject::defaultObjectInclusion(); |
| + return AXObject::defaultObjectInclusion(ignoredReasons); |
| } |
| -bool AXLayoutObject::computeAccessibilityIsIgnored() const |
| +bool AXLayoutObject::computeAccessibilityIsIgnored(PassRefPtr<TypeBuilder::Array<TypeBuilder::Accessibility::AXProperty>> passIgnoredReasons) const |
| { |
| #if ENABLE(ASSERT) |
| ASSERT(m_initialized); |
| #endif |
| + RefPtr<TypeBuilder::Array<TypeBuilder::Accessibility::AXProperty>> ignoredReasons = passIgnoredReasons; |
| + |
| // Check first if any of the common reasons cause this element to be ignored. |
| // Then process other use cases that need to be applied to all the various roles |
| // that AXLayoutObjects take on. |
| - AXObjectInclusion decision = defaultObjectInclusion(); |
| + AXObjectInclusion decision = defaultObjectInclusion(ignoredReasons); |
| if (decision == IncludeObject) |
| return false; |
| if (decision == IgnoreObject) |
| return true; |
| - // If this element is within a parent that cannot have children, it should not be exposed. |
| - if (isDescendantOfBarrenParent()) |
| + // If this element is within a parent that cannot have children, it should not be exposed |
| + if (isDescendantOfLeafNode()) { |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::AncestorIsLeafNode, createRelatedNodeValue(leafNodeAncestor()))); |
|
dmazzoni
2015/04/15 17:00:02
Perhaps you could add a helper function for this.
aboxhall
2015/04/15 17:55:46
I feel silly for not thinking of this. Done.
aboxhall
2015/04/15 17:55:46
Great idea, done.
|
| return true; |
| + } |
| - if (roleValue() == IgnoredRole) |
| + if (roleValue() == IgnoredRole) { |
| + if (ignoredReasons) { |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::Uninteresting, createBooleanValue(true))); |
| + } |
| return true; |
| + } |
| - if (hasInheritedPresentationalRole()) |
| + if (hasInheritedPresentationalRole()) { |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::InheritsPresentation, createRelatedNodeValue(inheritsPresentationalRoleFrom()))); |
| return true; |
| + } |
| // An ARIA tree can only have tree items and static text as children. |
| - if (!isAllowedChildOfTree()) |
| + if (AXObject* treeAncestor = treeAncestorDisallowingChild()) { |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::AncestorDisallowsChild, createRelatedNodeValue(treeAncestor))); |
| return true; |
| + } |
| // TODO: we should refactor this - but right now this is necessary to make |
| // sure scroll areas stay in the tree. |
| @@ -518,28 +543,49 @@ bool AXLayoutObject::computeAccessibilityIsIgnored() const |
| // ignore popup menu items because AppKit does |
| for (LayoutObject* parent = m_layoutObject->parent(); parent; parent = parent->parent()) { |
| - if (parent->isBoxModelObject() && toLayoutBoxModelObject(parent)->isMenuList()) |
| + if (parent->isBoxModelObject() && toLayoutBoxModelObject(parent)->isMenuList()) { |
| + if (ignoredReasons) { |
| + AXObject* parentObject = axObjectCache()->getOrCreate(parent); |
| + if (parentObject) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::AncestorDisallowsChild, createRelatedNodeValue(parentObject))); |
| + } |
| return true; |
| + } |
| } |
| // find out if this element is inside of a label element. |
| // if so, it may be ignored because it's the label for a checkbox or radio button |
| AXObject* controlObject = correspondingControlForLabelElement(); |
| - if (controlObject && !controlObject->deprecatedExposesTitleUIElement() && controlObject->isCheckboxOrRadio()) |
| + if (controlObject && !controlObject->deprecatedExposesTitleUIElement() && controlObject->isCheckboxOrRadio()) { |
| + if (ignoredReasons) { |
| + HTMLLabelElement* label = labelElementContainer(); |
| + if (label && !label->isSameNode(node())) { |
| + AXObject* labelAXObject = axObjectCache()->getOrCreate(label); |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::LabelContainer, createRelatedNodeValue(labelAXObject))); |
| + } |
| + |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::LabelFor, createRelatedNodeValue(controlObject))); |
| + } |
| return true; |
| + } |
| if (m_layoutObject->isBR()) |
| return false; |
| - // NOTE: BRs always have text boxes now, so the text box check here can be removed |
| if (m_layoutObject->isText()) { |
| // static text beneath MenuItems and MenuButtons are just reported along with the menu item, so it's ignored on an individual level |
| AXObject* parent = parentObjectUnignored(); |
| - if (parent && (parent->ariaRoleAttribute() == MenuItemRole || parent->ariaRoleAttribute() == MenuButtonRole)) |
| + if (parent && (parent->ariaRoleAttribute() == MenuItemRole || parent->ariaRoleAttribute() == MenuButtonRole)) { |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::StaticTextUsedAsNameFor, createRelatedNodeValue(parent))); |
| return true; |
| + } |
| LayoutText* layoutText = toLayoutText(m_layoutObject); |
| - if (m_layoutObject->isBR() || !layoutText->firstTextBox()) |
| + if (!layoutText->firstTextBox()) { |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::EmptyText, createBooleanValue(true))); |
| return true; |
| + } |
| // Don't ignore static text in editable text controls. |
| for (AXObject* parent = parentObject(); parent; parent = parent->parentObject()) { |
| @@ -549,7 +595,12 @@ bool AXLayoutObject::computeAccessibilityIsIgnored() const |
| // text elements that are just empty whitespace should not be returned |
| // FIXME(dmazzoni): we probably shouldn't ignore this if the style is 'pre', or similar... |
| - return layoutText->text().impl()->containsOnlyWhitespace(); |
| + if (layoutText->text().impl()->containsOnlyWhitespace()) { |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::EmptyText, createBooleanValue(true))); |
| + return true; |
| + } |
| + return false; |
| } |
| if (isHeading()) |
| @@ -617,11 +668,20 @@ bool AXLayoutObject::computeAccessibilityIsIgnored() const |
| // objects are often containers with meaningful information, the inclusion of a span can have |
| // the side effect of causing the immediate parent accessible to be ignored. This is especially |
| // problematic for platforms which have distinct roles for textual block elements. |
| - if (isHTMLSpanElement(node)) |
| + if (isHTMLSpanElement(node)) { |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::Uninteresting, createBooleanValue(true))); |
| return true; |
| + } |
| - if (m_layoutObject->isLayoutBlockFlow() && m_layoutObject->childrenInline() && !canSetFocusAttribute()) |
| - return !toLayoutBlockFlow(m_layoutObject)->firstLineBox() && !mouseButtonListener(); |
| + if (m_layoutObject->isLayoutBlockFlow() && m_layoutObject->childrenInline() && !canSetFocusAttribute()) { |
| + if (toLayoutBlockFlow(m_layoutObject)->firstLineBox() || mouseButtonListener()) |
| + return false; |
| + |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::Uninteresting, createBooleanValue(true))); |
| + return true; |
| + } |
| // ignore images seemingly used as spacers |
| if (isImage()) { |
| @@ -637,20 +697,31 @@ bool AXLayoutObject::computeAccessibilityIsIgnored() const |
| if (!alt.string().containsOnlyWhitespace()) |
| return false; |
| // informal standard is to ignore images with zero-length alt strings |
| - if (!alt.isNull()) |
| + if (!alt.isNull()) { |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::EmptyAlt, createBooleanValue(true))); |
| return true; |
| + } |
| } |
| if (isNativeImage() && m_layoutObject->isImage()) { |
| // check for one-dimensional image |
| LayoutImage* image = toLayoutImage(m_layoutObject); |
| - if (image->size().height() <= 1 || image->size().width() <= 1) |
| + if (image->size().height() <= 1 || image->size().width() <= 1) { |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::ProbablyPresentational, createBooleanValue(true))); |
| return true; |
| + } |
| // check whether laid out image was stretched from one-dimensional file image |
| if (image->cachedImage()) { |
| LayoutSize imageSize = image->cachedImage()->imageSizeForLayoutObject(m_layoutObject, image->view()->zoomFactor()); |
| - return imageSize.height() <= 1 || imageSize.width() <= 1; |
| + if (imageSize.height() <= 1 || imageSize.width() <= 1) { |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::ProbablyPresentational, createBooleanValue(true))); |
| + return true; |
| + } |
| + return false; |
| } |
| } |
| return false; |
| @@ -660,8 +731,11 @@ bool AXLayoutObject::computeAccessibilityIsIgnored() const |
| if (canvasHasFallbackContent()) |
| return false; |
| LayoutHTMLCanvas* canvas = toLayoutHTMLCanvas(m_layoutObject); |
| - if (canvas->size().height() <= 1 || canvas->size().width() <= 1) |
| + if (canvas->size().height() <= 1 || canvas->size().width() <= 1) { |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::ProbablyPresentational, createBooleanValue(true))); |
| return true; |
| + } |
| // Otherwise fall through; use presence of help text, title, or description to decide. |
| } |
| @@ -689,6 +763,8 @@ bool AXLayoutObject::computeAccessibilityIsIgnored() const |
| // By default, objects should be ignored so that the AX hierarchy is not |
| // filled with unnecessary items. |
| + if (ignoredReasons) |
| + ignoredReasons->addItem(createProperty(AXIgnoredReasons::Uninteresting, createBooleanValue(true))); |
| return true; |
| } |
| @@ -1012,7 +1088,7 @@ bool AXLayoutObject::ariaRoleHasPresentationalChildren() const |
| } |
| } |
| -bool AXLayoutObject::isPresentationalChildOfAriaRole() const |
| +AXObject* AXLayoutObject::ancestorForWhichThisIsAPresentationalChild() const |
| { |
| // Walk the parent chain looking for a parent that has presentational children |
| AXObject* parent; |
| @@ -1841,26 +1917,26 @@ void AXLayoutObject::lineBreaks(Vector<int>& lineBreaks) const |
| // Private. |
| // |
| -bool AXLayoutObject::isAllowedChildOfTree() const |
| +AXObject* AXLayoutObject::treeAncestorDisallowingChild() const |
| { |
| // Determine if this is in a tree. If so, we apply special behavior to make it work like an AXOutline. |
| AXObject* axObj = parentObject(); |
| - bool isInTree = false; |
| + AXObject* treeAncestor = 0; |
| while (axObj) { |
| if (axObj->isTree()) { |
| - isInTree = true; |
| + treeAncestor = axObj; |
| break; |
| } |
| axObj = axObj->parentObject(); |
| } |
| // If the object is in a tree, only tree items should be exposed (and the children of tree items). |
| - if (isInTree) { |
| + if (treeAncestor) { |
| AccessibilityRole role = roleValue(); |
| if (role != TreeItemRole && role != StaticTextRole) |
| - return false; |
| + return treeAncestor; |
| } |
| - return true; |
| + return 0; |
| } |
| void AXLayoutObject::ariaListboxSelectedChildren(AccessibilityChildrenVector& result) |