diff options
Diffstat (limited to 'Source/WebCore/accessibility/AccessibilityNodeObject.cpp')
| -rw-r--r-- | Source/WebCore/accessibility/AccessibilityNodeObject.cpp | 1063 |
1 files changed, 1061 insertions, 2 deletions
diff --git a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp index e58456c14..5ba64c0b6 100644 --- a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp @@ -259,12 +259,24 @@ AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole() return RadioButtonRole; if (input->isTextButton()) return buttonRoleType(); + if (input->isRangeControl()) + return SliderRole; return TextFieldRole; } if (node()->hasTagName(selectTag)) { HTMLSelectElement* selectElement = toHTMLSelectElement(node()); - return selectElement->multiple() ? ListRole : PopUpButtonRole; + return selectElement->multiple() ? ListBoxRole : PopUpButtonRole; } + if (node()->hasTagName(textareaTag)) + return TextAreaRole; + if (headingLevel()) + return HeadingRole; + if (node()->hasTagName(divTag)) + return DivRole; + if (node()->hasTagName(pTag)) + return ParagraphRole; + if (node()->hasTagName(labelTag)) + return LabelRole; if (node()->isFocusable()) return GroupRole; @@ -301,8 +313,38 @@ void AccessibilityNodeObject::addChildren() } } +bool AccessibilityNodeObject::canHaveChildren() const +{ + // If this is an AccessibilityRenderObject, then it's okay if this object + // doesn't have a node - there are some renderers that don't have associated + // nodes, like scroll areas and css-generated text. + if (!node() && !isAccessibilityRenderObject()) + return false; + + // Elements that should not have children + switch (roleValue()) { + case ImageRole: + case ButtonRole: + case PopUpButtonRole: + case CheckBoxRole: + case RadioButtonRole: + case TabRole: + case ToggleButtonRole: + case StaticTextRole: + case ListBoxOptionRole: + case ScrollBarRole: + return false; + default: + return true; + } +} + bool AccessibilityNodeObject::accessibilityIsIgnored() const { + // If this element is within a parent that cannot have children, it should not be exposed. + if (isDescendantOfBarrenParent()) + return true; + return m_role == UnknownRole; } @@ -323,9 +365,1026 @@ bool AccessibilityNodeObject::canvasHasFallbackContent() const return false; } +bool AccessibilityNodeObject::isWebArea() const +{ + return roleValue() == WebAreaRole; +} + +bool AccessibilityNodeObject::isImageButton() const +{ + return isNativeImage() && roleValue() == ButtonRole; +} + +bool AccessibilityNodeObject::isAnchor() const +{ + return !isNativeImage() && isLink(); +} + +bool AccessibilityNodeObject::isNativeTextControl() const +{ + Node* node = this->node(); + if (!node) + return false; + + if (node->hasTagName(textareaTag)) + return true; + + if (node->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + return input->isText() || input->isNumberField(); + } + + return false; +} + +bool AccessibilityNodeObject::isSearchField() const +{ + Node* node = this->node(); + if (!node) + return false; + + HTMLInputElement* inputElement = node->toInputElement(); + if (!inputElement) + return false; + + if (inputElement->isSearchField()) + return true; + + // Some websites don't label their search fields as such. However, they will + // use the word "search" in either the form or input type. This won't catch every case, + // but it will catch google.com for example. + + // Check the node name of the input type, sometimes it's "search". + const AtomicString& nameAttribute = getAttribute(nameAttr); + if (nameAttribute.contains("search", false)) + return true; + + // Check the form action and the name, which will sometimes be "search". + HTMLFormElement* form = inputElement->form(); + if (form && (form->name().contains("search", false) || form->action().contains("search", false))) + return true; + + return false; +} + +bool AccessibilityNodeObject::isNativeImage() const +{ + Node* node = this->node(); + if (!node) + return false; + + if (node->hasTagName(imgTag)) + return true; + + if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag)) + return true; + + if (node->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + return input->isImageButton(); + } + + return false; +} + +bool AccessibilityNodeObject::isImage() const +{ + return roleValue() == ImageRole; +} + +bool AccessibilityNodeObject::isPasswordField() const +{ + Node* node = this->node(); + if (!node || !node->isHTMLElement()) + return false; + + if (ariaRoleAttribute() != UnknownRole) + return false; + + HTMLInputElement* inputElement = node->toInputElement(); + if (!inputElement) + return false; + + return inputElement->isPasswordField(); +} + +bool AccessibilityNodeObject::isInputImage() const +{ + Node* node = this->node(); + if (!node) + return false; + + if (roleValue() == ButtonRole && node->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + return input->isImageButton(); + } + + return false; +} + +bool AccessibilityNodeObject::isProgressIndicator() const +{ + return roleValue() == ProgressIndicatorRole; +} + +bool AccessibilityNodeObject::isSlider() const +{ + return roleValue() == SliderRole; +} + +bool AccessibilityNodeObject::isMenuRelated() const +{ + switch (roleValue()) { + case MenuRole: + case MenuBarRole: + case MenuButtonRole: + case MenuItemRole: + return true; + default: + return false; + } +} + +bool AccessibilityNodeObject::isMenu() const +{ + return roleValue() == MenuRole; +} + +bool AccessibilityNodeObject::isMenuBar() const +{ + return roleValue() == MenuBarRole; +} + +bool AccessibilityNodeObject::isMenuButton() const +{ + return roleValue() == MenuButtonRole; +} + +bool AccessibilityNodeObject::isMenuItem() const +{ + return roleValue() == MenuItemRole; +} + +bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const +{ + Node* node = this->node(); + if (!node) + return false; + + HTMLInputElement* input = node->toInputElement(); + if (input) + return input->isCheckbox() || input->isRadioButton(); + + return false; +} + +bool AccessibilityNodeObject::isEnabled() const +{ + if (equalIgnoringCase(getAttribute(aria_disabledAttr), "true")) + return false; + + Node* node = this->node(); + if (!node || !node->isElementNode()) + return true; + + return toElement(node)->isEnabledFormControl(); +} + +bool AccessibilityNodeObject::isIndeterminate() const +{ + Node* node = this->node(); + if (!node) + return false; + + HTMLInputElement* inputElement = node->toInputElement(); + if (!inputElement) + return false; + + return inputElement->isIndeterminate(); +} + +bool AccessibilityNodeObject::isPressed() const +{ + if (roleValue() != ButtonRole) + return false; + + Node* node = this->node(); + if (!node) + return false; + + // If this is an ARIA button, check the aria-pressed attribute rather than node()->active() + if (ariaRoleAttribute() == ButtonRole) { + if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true")) + return true; + return false; + } + + return node->active(); +} + +bool AccessibilityNodeObject::isChecked() const +{ + Node* node = this->node(); + if (!node) + return false; + + // First test for native checkedness semantics + HTMLInputElement* inputElement = node->toInputElement(); + if (inputElement) + return inputElement->shouldAppearChecked(); + + // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute + AccessibilityRole ariaRole = ariaRoleAttribute(); + if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole) { + if (equalIgnoringCase(getAttribute(aria_checkedAttr), "true")) + return true; + return false; + } + + // Otherwise it's not checked + return false; +} + +bool AccessibilityNodeObject::isHovered() const +{ + Node* node = this->node(); + if (!node) + return false; + + return node->hovered(); +} + +bool AccessibilityNodeObject::isMultiSelectable() const +{ + const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr); + if (equalIgnoringCase(ariaMultiSelectable, "true")) + return true; + if (equalIgnoringCase(ariaMultiSelectable, "false")) + return false; + + return node() && node()->hasTagName(selectTag) && toHTMLSelectElement(node())->multiple(); +} + +bool AccessibilityNodeObject::isReadOnly() const +{ + Node* node = this->node(); + if (!node) + return true; + + if (node->hasTagName(textareaTag)) + return static_cast<HTMLTextAreaElement*>(node)->readOnly(); + + if (node->hasTagName(inputTag)) + return static_cast<HTMLInputElement*>(node)->readOnly(); + + return !node->rendererIsEditable(); +} + +bool AccessibilityNodeObject::isRequired() const +{ + if (equalIgnoringCase(getAttribute(aria_requiredAttr), "true")) + return true; + + Node* n = this->node(); + if (n && (n->isElementNode() && toElement(n)->isFormControlElement())) + return static_cast<HTMLFormControlElement*>(n)->required(); + + return false; +} + +int AccessibilityNodeObject::headingLevel() const +{ + // headings can be in block flow and non-block flow + Node* node = this->node(); + if (!node) + return false; + + if (ariaRoleAttribute() == HeadingRole) + return getAttribute(aria_levelAttr).toInt(); + + if (node->hasTagName(h1Tag)) + return 1; + + if (node->hasTagName(h2Tag)) + return 2; + + if (node->hasTagName(h3Tag)) + return 3; + + if (node->hasTagName(h4Tag)) + return 4; + + if (node->hasTagName(h5Tag)) + return 5; + + if (node->hasTagName(h6Tag)) + return 6; + + return 0; +} + +String AccessibilityNodeObject::valueDescription() const +{ + if (!isARIARange()) + return String(); + + return getAttribute(aria_valuetextAttr).string(); +} + +bool AccessibilityNodeObject::isARIARange() const +{ + switch (m_ariaRole) { + case ProgressIndicatorRole: + case SliderRole: + case ScrollBarRole: + case SpinButtonRole: + return true; + default: + return false; + } +} + +float AccessibilityNodeObject::valueForRange() const +{ + if (node() && node()->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); + if (input->isRangeControl()) + return input->valueAsNumber(); + } + + if (!isARIARange()) + return 0.0f; + + return getAttribute(aria_valuenowAttr).toFloat(); +} + +float AccessibilityNodeObject::maxValueForRange() const +{ + if (node() && node()->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); + if (input->isRangeControl()) + return input->maximum(); + } + + if (!isARIARange()) + return 0.0f; + + return getAttribute(aria_valuemaxAttr).toFloat(); +} + +float AccessibilityNodeObject::minValueForRange() const +{ + if (node() && node()->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node()); + if (input->isRangeControl()) + return input->minimum(); + } + + if (!isARIARange()) + return 0.0f; + + return getAttribute(aria_valueminAttr).toFloat(); +} + +float AccessibilityNodeObject::stepValueForRange() const +{ + return getAttribute(stepAttr).toFloat(); +} + +bool AccessibilityNodeObject::isHeading() const +{ + return roleValue() == HeadingRole; +} + +bool AccessibilityNodeObject::isLink() const +{ + return roleValue() == WebCoreLinkRole; +} + +bool AccessibilityNodeObject::isControl() const +{ + Node* node = this->node(); + if (!node) + return false; + + return ((node->isElementNode() && toElement(node)->isFormControlElement()) + || AccessibilityObject::isARIAControl(ariaRoleAttribute())); +} + +bool AccessibilityNodeObject::isFieldset() const +{ + Node* node = this->node(); + if (!node) + return false; + + return node->hasTagName(fieldsetTag); +} + +bool AccessibilityNodeObject::isGroup() const +{ + return roleValue() == GroupRole; +} + +AccessibilityObject* AccessibilityNodeObject::selectedRadioButton() +{ + if (!isRadioGroup()) + return 0; + + AccessibilityObject::AccessibilityChildrenVector children = this->children(); + + // Find the child radio button that is selected (ie. the intValue == 1). + size_t size = children.size(); + for (size_t i = 0; i < size; ++i) { + AccessibilityObject* object = children[i].get(); + if (object->roleValue() == RadioButtonRole && object->checkboxOrRadioValue() == ButtonStateOn) + return object; + } + return 0; +} + +AccessibilityObject* AccessibilityNodeObject::selectedTabItem() +{ + if (!isTabList()) + return 0; + + // Find the child tab item that is selected (ie. the intValue == 1). + AccessibilityObject::AccessibilityChildrenVector tabs; + tabChildren(tabs); + + AccessibilityObject::AccessibilityChildrenVector children = this->children(); + size_t size = tabs.size(); + for (size_t i = 0; i < size; ++i) { + AccessibilityObject* object = children[i].get(); + if (object->isTabItem() && object->isChecked()) + return object; + } + return 0; +} + +AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const +{ + if (isNativeCheckboxOrRadio()) + return isChecked() ? ButtonStateOn : ButtonStateOff; + + return AccessibilityObject::checkboxOrRadioValue(); +} + +Element* AccessibilityNodeObject::anchorElement() const +{ + Node* node = this->node(); + if (!node) + return 0; + + AXObjectCache* cache = axObjectCache(); + + // search up the DOM tree for an anchor element + // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement + for ( ; node; node = node->parentNode()) { + if (node->hasTagName(aTag) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor())) + return toElement(node); + } + + return 0; +} + +Element* AccessibilityNodeObject::actionElement() const +{ + Node* node = this->node(); + if (!node) + return 0; + + if (node->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + if (!input->disabled() && (isCheckboxOrRadio() || input->isTextButton())) + return input; + } else if (node->hasTagName(buttonTag)) + return toElement(node); + + if (isFileUploadButton()) + return toElement(node); + + if (AccessibilityObject::isARIAInput(ariaRoleAttribute())) + return toElement(node); + + if (isImageButton()) + return toElement(node); + + if (node->hasTagName(selectTag)) + return toElement(node); + + switch (roleValue()) { + case ButtonRole: + case PopUpButtonRole: + case TabRole: + case MenuItemRole: + case ListItemRole: + return toElement(node); + default: + break; + } + + Element* elt = anchorElement(); + if (!elt) + elt = mouseButtonListener(); + return elt; +} + +Element* AccessibilityNodeObject::mouseButtonListener() const +{ + Node* node = this->node(); + if (!node) + return 0; + + // check if our parent is a mouse button listener + while (node && !node->isElementNode()) + node = node->parentNode(); + + if (!node) + return 0; + + // FIXME: Do the continuation search like anchorElement does + for (Element* element = toElement(node); element; element = element->parentElement()) { + if (element->getAttributeEventListener(eventNames().clickEvent) || element->getAttributeEventListener(eventNames().mousedownEvent) || element->getAttributeEventListener(eventNames().mouseupEvent)) + return element; + } + + return 0; +} + +bool AccessibilityNodeObject::isDescendantOfBarrenParent() const +{ + for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) { + if (!object->canHaveChildren()) + return true; + } + + return false; +} + +void AccessibilityNodeObject::alterSliderValue(bool increase) +{ + if (roleValue() != SliderRole) + return; + + if (!getAttribute(stepAttr).isEmpty()) + changeValueByStep(increase); + else + changeValueByPercent(increase ? 5 : -5); +} + +void AccessibilityNodeObject::increment() +{ + alterSliderValue(true); +} + +void AccessibilityNodeObject::decrement() +{ + alterSliderValue(false); +} + +void AccessibilityNodeObject::changeValueByStep(bool increase) +{ + float step = stepValueForRange(); + float value = valueForRange(); + + value += increase ? step : -step; + + setValue(String::number(value)); + + axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true); +} + +void AccessibilityNodeObject::changeValueByPercent(float percentChange) +{ + float range = maxValueForRange() - minValueForRange(); + float value = valueForRange(); + + value += range * (percentChange / 100); + setValue(String::number(value)); + + axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true); +} + +bool AccessibilityNodeObject::isGenericFocusableElement() const +{ + if (!canSetFocusAttribute()) + return false; + + // If it's a control, it's not generic. + if (isControl()) + return false; + + // If it has an aria role, it's not generic. + if (m_ariaRole != UnknownRole) + return false; + + // If the content editable attribute is set on this element, that's the reason + // it's focusable, and existing logic should handle this case already - so it's not a + // generic focusable element. + + if (hasContentEditableAttributeSet()) + return false; + + // The web area and body element are both focusable, but existing logic handles these + // cases already, so we don't need to include them here. + if (roleValue() == WebAreaRole) + return false; + if (node() && node()->hasTagName(bodyTag)) + return false; + + return true; +} + +HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const +{ + RefPtr<NodeList> list = element->document()->getElementsByTagName("label"); + unsigned len = list->length(); + for (unsigned i = 0; i < len; i++) { + if (list->item(i)->hasTagName(labelTag)) { + HTMLLabelElement* label = static_cast<HTMLLabelElement*>(list->item(i)); + if (label->control() == element) + return label; + } + } + + return 0; +} + +String AccessibilityNodeObject::ariaAccessibilityDescription() const +{ + String ariaLabeledBy = ariaLabeledByAttribute(); + if (!ariaLabeledBy.isEmpty()) + return ariaLabeledBy; + + const AtomicString& ariaLabel = getAttribute(aria_labelAttr); + if (!ariaLabel.isEmpty()) + return ariaLabel; + + return String(); +} + +static Element* siblingWithAriaRole(String role, Node* node) +{ + for (Node* sibling = node->parentNode()->firstChild(); sibling; sibling = sibling->nextSibling()) { + if (sibling->isElementNode()) { + const AtomicString& siblingAriaRole = toElement(sibling)->getAttribute(roleAttr); + if (equalIgnoringCase(siblingAriaRole, role)) + return toElement(sibling); + } + } + + return 0; +} + +Element* AccessibilityNodeObject::menuElementForMenuButton() const +{ + if (ariaRoleAttribute() != MenuButtonRole) + return 0; + + return siblingWithAriaRole("menu", node()); +} + +AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const +{ + return axObjectCache()->getOrCreate(menuElementForMenuButton()); +} + +Element* AccessibilityNodeObject::menuItemElementForMenu() const +{ + if (ariaRoleAttribute() != MenuRole) + return 0; + + return siblingWithAriaRole("menuitem", node()); +} + +AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const +{ + Element* menuItem = menuItemElementForMenu(); + + if (menuItem) { + // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem + AccessibilityObject* menuItemAX = axObjectCache()->getOrCreate(menuItem); + if (menuItemAX->isMenuButton()) + return menuItemAX; + } + return 0; +} + +String AccessibilityNodeObject::accessibilityDescription() const +{ + // Static text should not have a description, it should only have a stringValue. + if (roleValue() == StaticTextRole) + return String(); + + String ariaDescription = ariaAccessibilityDescription(); + if (!ariaDescription.isEmpty()) + return ariaDescription; + + if (isImage() || isInputImage() || isNativeImage() || isCanvas()) { + // Images should use alt as long as the attribute is present, even if empty. + // Otherwise, it should fallback to other methods, like the title attribute. + const AtomicString& alt = getAttribute(altAttr); + if (!alt.isNull()) + return alt; + } + +#if ENABLE(MATHML) + Node* node = this->node(); + if (node && node->isElementNode() && toElement(node)->isMathMLElement()) + return getAttribute(MathMLNames::alttextAttr); +#endif + + // An element's descriptive text is comprised of title() (what's visible on the screen) and accessibilityDescription() (other descriptive text). + // Both are used to generate what a screen reader speaks. + // If this point is reached (i.e. there's no accessibilityDescription) and there's no title(), we should fallback to using the title attribute. + // The title attribute is normally used as help text (because it is a tooltip), but if there is nothing else available, this should be used (according to ARIA). + if (title().isEmpty()) + return getAttribute(titleAttr); + + return String(); +} + +String AccessibilityNodeObject::helpText() const +{ + Node* node = this->node(); + if (!node) + return String(); + + const AtomicString& ariaHelp = getAttribute(aria_helpAttr); + if (!ariaHelp.isEmpty()) + return ariaHelp; + + String describedBy = ariaDescribedByAttribute(); + if (!describedBy.isEmpty()) + return describedBy; + + String description = accessibilityDescription(); + for (Node* curr = node; curr; curr = curr->parentNode()) { + if (curr->isHTMLElement()) { + const AtomicString& summary = toElement(curr)->getAttribute(summaryAttr); + if (!summary.isEmpty()) + return summary; + + // The title attribute should be used as help text unless it is already being used as descriptive text. + const AtomicString& title = toElement(curr)->getAttribute(titleAttr); + if (!title.isEmpty() && description != title) + return title; + } + + // Only take help text from an ancestor element if its a group or an unknown role. If help was + // added to those kinds of elements, it is likely it was meant for a child element. + AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr); + if (axObj) { + AccessibilityRole role = axObj->roleValue(); + if (role != GroupRole && role != UnknownRole) + break; + } + } + + return String(); +} + +unsigned AccessibilityNodeObject::hierarchicalLevel() const +{ + Node* node = this->node(); + if (!node || !node->isElementNode()) + return 0; + Element* element = toElement(node); + String ariaLevel = element->getAttribute(aria_levelAttr); + if (!ariaLevel.isEmpty()) + return ariaLevel.toInt(); + + // Only tree item will calculate its level through the DOM currently. + if (roleValue() != TreeItemRole) + return 0; + + // Hierarchy leveling starts at 0. + // We measure tree hierarchy by the number of groups that the item is within. + unsigned level = 0; + for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { + AccessibilityRole parentRole = parent->roleValue(); + if (parentRole == GroupRole) + level++; + else if (parentRole == TreeRole) + break; + } + + return level; +} + +String AccessibilityNodeObject::textUnderElement() const +{ + Node* node = this->node(); + if (!node) + return String(); + + // Note: TextIterator doesn't return any text for nodes that don't have renderers. + // If this could be fixed, it'd be more accurate use TextIterator here. + if (node->isElementNode()) + return toElement(node)->innerText(); + + return String(); +} + +String AccessibilityNodeObject::title() const +{ + Node* node = this->node(); + if (!node) + return String(); + + bool isInputTag = node->hasTagName(inputTag); + if (isInputTag) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + if (input->isTextButton()) + return input->valueWithDefault(); + } + + if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) { + HTMLLabelElement* label = labelForElement(toElement(node)); + if (label && !exposesTitleUIElement()) + return label->innerText(); + } + + // If this node isn't rendered, there's no inner text we can extract from a select element. + if (!isAccessibilityRenderObject() && node->hasTagName(selectTag)) + return String(); + + switch (roleValue()) { + case PopUpButtonRole: + case ButtonRole: + case CheckBoxRole: + case ListBoxOptionRole: + case MenuButtonRole: + case MenuItemRole: + case RadioButtonRole: + case TabRole: + return textUnderElement(); + default: + break; + } + + if (isHeading() || isLink()) + return textUnderElement(); + + // If it's focusable but it's not content editable or a known control type, then it will appear to + // the user as a single atomic object, so we should use its text as the default title. + if (isGenericFocusableElement()) + return textUnderElement(); + + return String(); +} + +String AccessibilityNodeObject::text() const +{ + // If this is a user defined static text, use the accessible name computation. + if (ariaRoleAttribute() == StaticTextRole) + return ariaAccessibilityDescription(); + + if (!isTextControl()) + return String(); + + Node* node = this->node(); + if (!node) + return String(); + + if (isNativeTextControl()) { + if (node->hasTagName(textareaTag)) + return static_cast<HTMLTextAreaElement*>(node)->value(); + if (node->hasTagName(inputTag)) + return node->toInputElement()->value(); + } + + if (!node->isElementNode()) + return String(); + + return toElement(node)->innerText(); +} + +String AccessibilityNodeObject::stringValue() const +{ + Node* node = this->node(); + if (!node) + return String(); + + if (ariaRoleAttribute() == StaticTextRole) { + String staticText = text(); + if (!staticText.length()) + staticText = textUnderElement(); + return staticText; + } + + if (node->isTextNode()) + return textUnderElement(); + + if (node->hasTagName(selectTag)) { + HTMLSelectElement* selectElement = toHTMLSelectElement(node); + int selectedIndex = selectElement->selectedIndex(); + const Vector<HTMLElement*> listItems = selectElement->listItems(); + if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) { + const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr); + if (!overriddenDescription.isNull()) + return overriddenDescription; + } + if (!selectElement->multiple()) + return selectElement->value(); + return String(); + } + + if (isTextControl()) + return text(); + + // FIXME: We might need to implement a value here for more types + // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one; + // this would require subclassing or making accessibilityAttributeNames do something other than return a + // single static array. + return String(); +} + +// This function implements the ARIA accessible name as described by the Mozilla +// ARIA Implementer's Guide. +static String accessibleNameForNode(Node* node) +{ + if (node->isTextNode()) + return toText(node)->data(); + + if (node->hasTagName(inputTag)) + return static_cast<HTMLInputElement*>(node)->value(); + + if (node->isHTMLElement()) { + const AtomicString& alt = toHTMLElement(node)->getAttribute(altAttr); + if (!alt.isEmpty()) + return alt; + } + + return String(); +} + +String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const +{ + StringBuilder builder; + unsigned size = elements.size(); + for (unsigned i = 0; i < size; ++i) { + Element* idElement = elements[i]; + + builder.append(accessibleNameForNode(idElement)); + for (Node* n = idElement->firstChild(); n; n = n->traverseNextNode(idElement)) + builder.append(accessibleNameForNode(n)); + + if (i != size - 1) + builder.append(' '); + } + return builder.toString(); +} + +void AccessibilityNodeObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const +{ + Node* node = this->node(); + if (!node || !node->isElementNode()) + return; + + TreeScope* scope = node->treeScope(); + if (!scope) + return; + + String idList = getAttribute(attribute).string(); + if (idList.isEmpty()) + return; + + idList.replace('\n', ' '); + Vector<String> idVector; + idList.split(' ', idVector); + + unsigned size = idVector.size(); + for (unsigned i = 0; i < size; ++i) { + AtomicString idName(idVector[i]); + Element* idElement = scope->getElementById(idName); + if (idElement) + elements.append(idElement); + } +} + + +void AccessibilityNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const +{ + elementsFromAttribute(elements, aria_labeledbyAttr); + if (!elements.size()) + elementsFromAttribute(elements, aria_labelledbyAttr); +} + + +String AccessibilityNodeObject::ariaLabeledByAttribute() const +{ + Vector<Element*> elements; + ariaLabeledByElements(elements); + + return accessibilityDescriptionForElements(elements); +} + bool AccessibilityNodeObject::canSetFocusAttribute() const { Node* node = this->node(); + if (!node) + return false; if (isWebArea()) return true; @@ -336,7 +1395,7 @@ bool AccessibilityNodeObject::canSetFocusAttribute() const if (!node) return false; - if (node->isElementNode() && !static_cast<Element*>(node)->isEnabledFormControl()) + if (node->isElementNode() && !toElement(node)->isEnabledFormControl()) return false; return node->supportsFocus(); |
