Chromium Code Reviews| Index: Source/modules/accessibility/AXNodeObject.cpp |
| diff --git a/Source/modules/accessibility/AXNodeObject.cpp b/Source/modules/accessibility/AXNodeObject.cpp |
| index cebfae8fc3569c106e8850435ea596f25a634808..791b9678a17f2dee80d49092c8a99624ebc18db2 100644 |
| --- a/Source/modules/accessibility/AXNodeObject.cpp |
| +++ b/Source/modules/accessibility/AXNodeObject.cpp |
| @@ -1,30 +1,30 @@ |
| /* |
| -* Copyright (C) 2012, Google Inc. All rights reserved. |
| -* |
| -* Redistribution and use in source and binary forms, with or without |
| -* modification, are permitted provided that the following conditions |
| -* are met: |
| -* |
| -* 1. Redistributions of source code must retain the above copyright |
| -* notice, this list of conditions and the following disclaimer. |
| -* 2. Redistributions in binary form must reproduce the above copyright |
| -* notice, this list of conditions and the following disclaimer in the |
| -* documentation and/or other materials provided with the distribution. |
| -* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| -* its contributors may be used to endorse or promote products derived |
| -* from this software without specific prior written permission. |
| -* |
| -* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| -* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| -* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| -* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| -* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| -* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| -* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| -*/ |
| + * Copyright (C) 2012, Google Inc. All rights reserved. |
| + * |
| + * Redistribution and use in source and binary forms, with or without |
| + * modification, are permitted provided that the following conditions |
| + * are met: |
| + * |
| + * 1. Redistributions of source code must retain the above copyright |
| + * notice, this list of conditions and the following disclaimer. |
| + * 2. Redistributions in binary form must reproduce the above copyright |
| + * notice, this list of conditions and the following disclaimer in the |
| + * documentation and/or other materials provided with the distribution. |
| + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| + * its contributors may be used to endorse or promote products derived |
| + * from this software without specific prior written permission. |
| + * |
| + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| + */ |
| #include "config.h" |
| #include "modules/accessibility/AXNodeObject.h" |
| @@ -32,6 +32,7 @@ |
| #include "core/InputTypeNames.h" |
| #include "core/dom/NodeTraversal.h" |
| #include "core/dom/Text.h" |
| +#include "core/dom/shadow/ComposedTreeTraversal.h" |
| #include "core/html/HTMLDListElement.h" |
| #include "core/html/HTMLFieldSetElement.h" |
| #include "core/html/HTMLFrameElementBase.h" |
| @@ -54,6 +55,7 @@ |
| #include "core/layout/LayoutObject.h" |
| #include "modules/accessibility/AXObjectCacheImpl.h" |
| #include "platform/UserGestureIndicator.h" |
| +#include "platform/text/PlatformLocale.h" |
| #include "wtf/text/StringBuilder.h" |
| @@ -250,7 +252,7 @@ static bool isRequiredOwnedElement(AXObject* parent, AccessibilityRole currentRo |
| return isListElement(parentNode); |
| if (currentRole == ListMarkerRole) |
| return isHTMLLIElement(*parentNode); |
| - if (currentRole == MenuItemCheckBoxRole || currentRole == MenuItemRole || currentRole == MenuItemRadioRole) |
| + if (currentRole == MenuItemCheckBoxRole || currentRole == MenuItemRole || currentRole == MenuItemRadioRole) |
| return isHTMLMenuElement(*parentNode); |
| if (!currentElement) |
| @@ -267,7 +269,6 @@ static bool isRequiredOwnedElement(AXObject* parent, AccessibilityRole currentRo |
| return false; |
| } |
| - |
| const AXObject* AXNodeObject::inheritsPresentationalRoleFrom() const |
| { |
| // ARIA states if an item can get focus, it should not be presentational. |
| @@ -336,7 +337,7 @@ AccessibilityRole AXNodeObject::determineAccessibilityRoleUtil() |
| return DetailsRole; |
| if (isHTMLSummaryElement(*node())) { |
| - if (node()->parentNode() && isHTMLDetailsElement(node()->parentNode())) |
| + if (ComposedTreeTraversal::parent(*node()) && isHTMLDetailsElement(ComposedTreeTraversal::parent(*node()))) |
| return DisclosureTriangleRole; |
| return UnknownRole; |
| } |
| @@ -1778,8 +1779,12 @@ String AXNodeObject::computedName() const |
| // New AX name calculation. |
| // |
| -String AXNodeObject::textAlternative(bool recursive, bool inAriaLabelledByTraversal, AXObjectSet& visited, AXNameFrom& nameFrom, AXObjectVector& nameObjects, NameSources* nameSources) const |
| +String AXNodeObject::textAlternative(bool recursive, bool inAriaLabelledByTraversal, AXObjectSet& visited, AXNameFrom& nameFrom, AXObjectVector* nameObjects, NameSources* nameSources) const |
| { |
| + // If nameSources is non-null, nameObjects is used in filling it in, so it must be non-null as well. |
| + if (nameSources) |
| + assert(nameObjects); |
| + |
| bool alreadyVisited = visited.contains(this); |
| bool foundTextAlternative = false; |
| visited.add(this); |
| @@ -1814,7 +1819,7 @@ String AXNodeObject::textAlternative(bool recursive, bool inAriaLabelledByTraver |
| if (nameSources) { |
| NameSource& source = nameSources->last(); |
| source.type = nameFrom; |
| - source.nameObjects = nameObjects; |
| + source.nameObjects = *nameObjects; |
| source.text = textAlternative; |
| foundTextAlternative = true; |
| } else { |
| @@ -1854,7 +1859,16 @@ String AXNodeObject::textAlternative(bool recursive, bool inAriaLabelledByTraver |
| return textAlternative; |
| // Step 2E from: http://www.w3.org/TR/accname-aam-1.1 |
| - |
| + if (recursive && !inAriaLabelledByTraversal && isControl()) { |
| + // No need to set any name source info in a recursive call. |
| + if (roleValue() == TextFieldRole || roleValue() == ComboBoxRole) |
| + return text(); |
| + if (isRange()) { |
| + if (hasAttribute(aria_valuetextAttr)) |
| + return getAttribute(aria_valuetextAttr).string(); |
| + return String::number(valueForRange()); |
| + } |
| + } |
| // Step 2F / 2G from: http://www.w3.org/TR/accname-aam-1.1 |
| if (recursive || nameFromContents()) { |
| @@ -1870,22 +1884,45 @@ String AXNodeObject::textAlternative(bool recursive, bool inAriaLabelledByTraver |
| else |
| textAlternative = textFromDescendants(visited); |
| - if (nameSources) { |
| - foundTextAlternative = true; |
| - nameSources->last().text = textAlternative; |
| - } else { |
| - return textAlternative; |
| + if (!textAlternative.isEmpty()) { |
| + if (nameSources) { |
| + foundTextAlternative = true; |
| + nameSources->last().text = textAlternative; |
| + } else { |
| + return textAlternative; |
| + } |
| + } |
| + } |
| + |
| + // Step 2H from: http://www.w3.org/TR/accname-aam-1.1 |
| + nameFrom = AXNameFromAttribute; |
| + if (nameSources) { |
| + nameSources->append(NameSource(foundTextAlternative, titleAttr)); |
| + nameSources->last().type = nameFrom; |
| + } |
| + if (hasAttribute(titleAttr)) { |
| + const AtomicString& title = getAttribute(titleAttr); |
| + if (!title.isEmpty()) { |
| + textAlternative = title; |
| + if (nameSources) { |
| + foundTextAlternative = true; |
| + nameSources->last().text = textAlternative; |
| + } else { |
| + return textAlternative; |
| + } |
| } |
| } |
| nameFrom = AXNameFromUninitialized; |
| - if (nameSources && !nameSources->isEmpty()) { |
| + if (foundTextAlternative) { |
| for (size_t i = 0; i < nameSources->size(); ++i) { |
| if (!(*nameSources)[i].text.isNull() && !(*nameSources)[i].superseded) { |
| - nameFrom = (*nameSources)[i].type; |
| - nameObjects = (*nameSources)[i].nameObjects; |
| - return (*nameSources)[i].text; |
| + NameSource& nameSource = (*nameSources)[i]; |
| + nameFrom = nameSource.type; |
| + if (!nameSource.nameObjects.isEmpty()) |
| + *nameObjects = nameSource.nameObjects; |
| + return nameSource.text; |
| } |
| } |
| } |
| @@ -1908,9 +1945,7 @@ String AXNodeObject::textFromDescendants(AXObjectSet& visited) const |
| accumulatedText.append(' '); |
| } |
| - AXNameFrom nameFrom; |
| - AXObjectVector nameObjects; |
| - String result = child->textAlternative(true, false, visited, nameFrom, nameObjects, nullptr); |
| + String result = recursiveTextAlternative(*child, false, visited); |
| accumulatedText.append(result); |
| previous = child; |
| } |
| @@ -1918,7 +1953,7 @@ String AXNodeObject::textFromDescendants(AXObjectSet& visited) const |
| return accumulatedText.toString(); |
| } |
| -String AXNodeObject::textFromElements(bool inAriaLabelledbyTraversal, AXObjectSet& visited, WillBeHeapVector<RawPtrWillBeMember<Element>>& elements, AXObjectVector& nameObjects) const |
| +String AXNodeObject::textFromElements(bool inAriaLabelledbyTraversal, AXObjectSet& visited, WillBeHeapVector<RawPtrWillBeMember<Element>>& elements, AXObjectVector* nameObjects) const |
| { |
| StringBuilder accumulatedText; |
| bool foundValidElement = false; |
| @@ -1940,11 +1975,12 @@ String AXNodeObject::textFromElements(bool inAriaLabelledbyTraversal, AXObjectSe |
| } |
| if (!foundValidElement) |
| return String(); |
| - nameObjects = localNameObjects; |
| + if (nameObjects) |
| + *nameObjects = localNameObjects; |
| return accumulatedText.toString(); |
| } |
| -String AXNodeObject::textFromAriaLabelledby(AXObjectSet& visited, AXObjectVector& nameObjects) const |
| +String AXNodeObject::textFromAriaLabelledby(AXObjectSet& visited, AXObjectVector* nameObjects) const |
| { |
| WillBeHeapVector<RawPtrWillBeMember<Element>> elements; |
| ariaLabelledbyElements(elements); |
| @@ -2147,8 +2183,7 @@ Element* AXNodeObject::actionElement() const |
| if (isHTMLInputElement(*node)) { |
| HTMLInputElement& input = toHTMLInputElement(*node); |
| - if (!input.isDisabledFormControl() && (isCheckboxOrRadio() || input.isTextButton() |
| - || input.type() == InputTypeNames::file)) |
| + if (!input.isDisabledFormControl() && (isCheckboxOrRadio() || input.isTextButton() || input.type() == InputTypeNames::file)) |
| return &input; |
| } else if (isHTMLButtonElement(*node)) { |
| return toElement(node); |
| @@ -2454,11 +2489,15 @@ void AXNodeObject::deprecatedAriaLabelledbyText(WillBeHeapVector<OwnPtrWillBeMem |
| } |
| // Based on http://rawgit.com/w3c/aria/master/html-aam/html-aam.html#accessible-name-and-description-calculation |
| -String AXNodeObject::nativeTextAlternative(AXObjectSet& visited, AXNameFrom& nameFrom, AXObjectVector& nameObjects, NameSources* nameSources, bool* foundTextAlternative) const |
| +String AXNodeObject::nativeTextAlternative(AXObjectSet& visited, AXNameFrom& nameFrom, AXObjectVector* nameObjects, NameSources* nameSources, bool* foundTextAlternative) const |
| { |
| if (!node()) |
| return String(); |
| + // If nameSources is non-null, nameObjects is used in filling it in, so it must be non-null as well. |
| + if (nameSources) |
| + assert(nameObjects); |
| + |
| String textAlternative; |
| AXObjectVector localNameObjects; |
| @@ -2466,6 +2505,45 @@ String AXNodeObject::nativeTextAlternative(AXObjectSet& visited, AXNameFrom& nam |
| if (isHTMLInputElement(node())) |
| inputElement = toHTMLInputElement(node()); |
| + // 5.1/5.5 Text inputs, Other labelable Elements |
| + HTMLElement* htmlElement = nullptr; |
| + if (node()->isHTMLElement()) |
| + htmlElement = toHTMLElement(node()); |
| + if (htmlElement && htmlElement->isLabelable()) { |
| + // label |
| + nameFrom = AXNameFromRelatedElement; |
| + if (nameSources) { |
| + nameSources->append(NameSource(*foundTextAlternative)); |
| + nameSources->last().type = nameFrom; |
| + nameSources->last().nativeSource = AXTextFromNativeHTMLLabel; |
| + } |
| + HTMLLabelElement* label = labelForElement(htmlElement); |
| + if (label) { |
| + RefPtrWillBeRawPtr<AXObject> labelAXObject = axObjectCache().getOrCreate(label); |
| + if (labelAXObject) { |
| + if (nameObjects) { |
| + localNameObjects.append(labelAXObject.get()); |
| + *nameObjects = localNameObjects; |
| + localNameObjects.clear(); |
| + } |
| + textAlternative = recursiveTextAlternative(*labelAXObject, false, visited); |
| + |
| + if (nameSources) { |
| + NameSource& source = nameSources->last(); |
| + source.nameObjects = *nameObjects; |
| + source.text = textAlternative; |
| + if (label->getAttribute(forAttr).isNull()) |
| + source.nativeSource = AXTextFromNativeHTMLLabelWrapped; |
| + else |
| + source.nativeSource = AXTextFromNativeHTMLLabelFor; |
| + *foundTextAlternative = true; |
| + } else { |
| + return textAlternative; |
| + } |
| + } |
| + } |
| + } |
| + |
| // 5.2 input type="button", input type="submit" and input type="reset" |
| if (inputElement && inputElement->isTextButton()) { |
| // value attribue |
| @@ -2485,23 +2563,6 @@ String AXNodeObject::nativeTextAlternative(AXObjectSet& visited, AXNameFrom& nam |
| return textAlternative; |
| } |
| } |
| - |
| - // localised default value ("Submit" or "Reset") |
| - String defaultValue = inputElement->defaultValue(); |
| - if (!defaultValue.isNull()) { |
| - // defaultValue is always set for submit and reset buttons, and never set for anything else. |
| - nameFrom = AXNameFromAttribute; |
| - textAlternative = defaultValue; |
| - if (nameSources) { |
| - nameSources->append(NameSource(*foundTextAlternative, typeAttr)); |
| - NameSource& source = nameSources->last(); |
| - source.attributeValue = inputElement->getAttribute(typeAttr); |
| - source.text = textAlternative; |
| - *foundTextAlternative = true; |
| - } else { |
| - return textAlternative; |
| - } |
| - } |
| return textAlternative; |
| } |
| @@ -2544,37 +2605,39 @@ String AXNodeObject::nativeTextAlternative(AXObjectSet& visited, AXNameFrom& nam |
| } |
| } |
| + // localised default value ("Submit") |
|
dmazzoni
2015/08/24 18:02:03
Oh, got it - <input type=image> implies a Submit b
|
| + nameFrom = AXNameFromAttribute; |
| + textAlternative = inputElement->locale().queryString(WebLocalizedString::SubmitButtonDefaultLabel); |
| + if (nameSources) { |
| + nameSources->append(NameSource(*foundTextAlternative, typeAttr)); |
| + NameSource& source = nameSources->last(); |
| + source.attributeValue = inputElement->getAttribute(typeAttr); |
| + source.type = nameFrom; |
| + source.text = textAlternative; |
| + *foundTextAlternative = true; |
| + } else { |
| + return textAlternative; |
| + } |
| return textAlternative; |
| } |
| - // 5.5 Other labelable Elements |
| - if (node()->isHTMLElement() && toHTMLElement(node())->isLabelable()) { |
| - // label |
| - nameFrom = AXNameFromRelatedElement; |
| + // 5.1 Text inputs - step 3 (placeholder attribute) |
| + if (htmlElement && htmlElement->isTextFormControl()) { |
| + nameFrom = AXNameFromPlaceholder; |
| if (nameSources) { |
| - nameSources->append(NameSource(*foundTextAlternative)); |
| - nameSources->last().type = nameFrom; |
| - nameSources->last().nativeSource = AXTextFromNativeHTMLLabel; |
| + nameSources->append(NameSource(*foundTextAlternative, placeholderAttr)); |
| + NameSource& source = nameSources->last(); |
| + source.type = nameFrom; |
| } |
| - HTMLLabelElement* label = labelForElement(toHTMLElement(node())); |
| - if (label) { |
| - RefPtrWillBeRawPtr<AXObject> labelAXObject = axObjectCache().getOrCreate(label); |
| - if (labelAXObject) { |
| - localNameObjects.append(labelAXObject.get()); |
| - nameObjects = localNameObjects; |
| - localNameObjects.clear(); |
| - |
| - textAlternative = recursiveTextAlternative(*labelAXObject, false, visited); |
| - |
| + HTMLElement* element = toHTMLElement(node()); |
| + if (element->fastHasAttribute(placeholderAttr)) { |
| + const AtomicString& placeholder = element->fastGetAttribute(placeholderAttr); |
| + if (!placeholder.isEmpty()) { |
| + textAlternative = placeholder; |
| if (nameSources) { |
| NameSource& source = nameSources->last(); |
| - source.nameObjects = nameObjects; |
| source.text = textAlternative; |
| - if (label->getAttribute(forAttr).isNull()) |
| - source.nativeSource = AXTextFromNativeHTMLLabelWrapped; |
| - else |
| - source.nativeSource = AXTextFromNativeHTMLLabelFor; |
| - *foundTextAlternative = true; |
| + source.attributeValue = placeholder; |
| } else { |
| return textAlternative; |
| } |
| @@ -2602,15 +2665,17 @@ String AXNodeObject::nativeTextAlternative(AXObjectSet& visited, AXNameFrom& nam |
| if (figcaption) { |
| RefPtrWillBeRawPtr<AXObject> figcaptionAXObject = axObjectCache().getOrCreate(figcaption); |
| if (figcaptionAXObject) { |
| - localNameObjects.append(figcaptionAXObject.get()); |
| - nameObjects = localNameObjects; |
| - localNameObjects.clear(); |
| + if (nameObjects) { |
| + localNameObjects.append(figcaptionAXObject.get()); |
| + *nameObjects = localNameObjects; |
| + localNameObjects.clear(); |
| + } |
| textAlternative = recursiveTextAlternative(*figcaptionAXObject, false, visited); |
| if (nameSources) { |
| NameSource& source = nameSources->last(); |
| - source.nameObjects = nameObjects; |
| + source.nameObjects = *nameObjects; |
| source.text = textAlternative; |
| } else { |
| return textAlternative; |
| @@ -2622,16 +2687,14 @@ String AXNodeObject::nativeTextAlternative(AXObjectSet& visited, AXNameFrom& nam |
| // 5.8 img Element |
| if (isHTMLImageElement(node())) { |
| - HTMLImageElement* imgElement = toHTMLImageElement(node()); |
| - |
| // alt |
| nameFrom = AXNameFromAttribute; |
| if (nameSources) { |
| nameSources->append(NameSource(*foundTextAlternative, altAttr)); |
| nameSources->last().type = nameFrom; |
| } |
| - if (imgElement->hasAttribute(altAttr)) { |
| - AtomicString alt = imgElement->getAttribute(altAttr); |
| + if (hasAttribute(altAttr)) { |
| + AtomicString alt = getAttribute(altAttr); |
| textAlternative = alt; |
| if (nameSources) { |
| NameSource& source = nameSources->last(); |
| @@ -2660,20 +2723,42 @@ String AXNodeObject::nativeTextAlternative(AXObjectSet& visited, AXNameFrom& nam |
| if (caption) { |
| RefPtrWillBeRawPtr<AXObject> captionAXObject = axObjectCache().getOrCreate(caption); |
| if (captionAXObject) { |
| - localNameObjects.append(captionAXObject.get()); |
| - nameObjects = localNameObjects; |
| - localNameObjects.clear(); |
| + if (nameObjects) { |
| + localNameObjects.append(captionAXObject.get()); |
| + *nameObjects = localNameObjects; |
| + localNameObjects.clear(); |
| + } |
| textAlternative = recursiveTextAlternative(*captionAXObject, false, visited); |
| if (nameSources) { |
| NameSource& source = nameSources->last(); |
| - source.nameObjects = nameObjects; |
| + source.nameObjects = *nameObjects; |
| source.text = textAlternative; |
| } else { |
| return textAlternative; |
| } |
| } |
| } |
| + |
| + // summary |
| + nameFrom = AXNameFromAttribute; |
| + if (nameSources) { |
| + nameSources->append(NameSource(*foundTextAlternative)); |
| + nameSources->last().type = nameFrom; |
| + } |
| + if (hasAttribute(summaryAttr)) { |
| + AtomicString summary = getAttribute(summaryAttr); |
| + textAlternative = summary; |
| + if (nameSources) { |
| + NameSource& source = nameSources->last(); |
| + source.attributeValue = summary; |
| + source.text = textAlternative; |
| + *foundTextAlternative = true; |
| + } else { |
| + return textAlternative; |
| + } |
| + } |
| + |
| return textAlternative; |
| } |