2 * Copyright (C) 2012, Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include "core/accessibility/AXNodeObject.h"
32 #include "core/accessibility/AXObjectCache.h"
33 #include "core/dom/NodeTraversal.h"
34 #include "core/dom/Text.h"
35 #include "core/html/HTMLFieldSetElement.h"
36 #include "core/html/HTMLFrameElementBase.h"
37 #include "core/html/HTMLInputElement.h"
38 #include "core/html/HTMLLabelElement.h"
39 #include "core/html/HTMLLegendElement.h"
40 #include "core/html/HTMLSelectElement.h"
41 #include "core/html/HTMLTextAreaElement.h"
42 #include "core/rendering/RenderObject.h"
43 #include "platform/UserGestureIndicator.h"
44 #include "wtf/text/StringBuilder.h"
49 using namespace HTMLNames;
51 AXNodeObject::AXNodeObject(Node* node)
53 , m_ariaRole(UnknownRole)
54 , m_childrenDirty(false)
56 , m_initialized(false)
62 PassRefPtr<AXNodeObject> AXNodeObject::create(Node* node)
64 return adoptRef(new AXNodeObject(node));
67 AXNodeObject::~AXNodeObject()
72 // This function implements the ARIA accessible name as described by the Mozilla
73 // ARIA Implementer's Guide.
74 static String accessibleNameForNode(Node* node)
76 if (node->isTextNode())
77 return toText(node)->data();
79 if (isHTMLInputElement(*node))
80 return toHTMLInputElement(*node).value();
82 if (node->isHTMLElement()) {
83 const AtomicString& alt = toHTMLElement(node)->getAttribute(altAttr);
91 String AXNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
93 StringBuilder builder;
94 unsigned size = elements.size();
95 for (unsigned i = 0; i < size; ++i) {
96 Element* idElement = elements[i];
98 builder.append(accessibleNameForNode(idElement));
99 for (Node* n = idElement->firstChild(); n; n = NodeTraversal::next(*n, idElement))
100 builder.append(accessibleNameForNode(n));
105 return builder.toString();
108 void AXNodeObject::alterSliderValue(bool increase)
110 if (roleValue() != SliderRole)
113 if (!getAttribute(stepAttr).isEmpty())
114 changeValueByStep(increase);
116 changeValueByPercent(increase ? 5 : -5);
119 String AXNodeObject::ariaAccessibilityDescription() const
121 String ariaLabeledBy = ariaLabeledByAttribute();
122 if (!ariaLabeledBy.isEmpty())
123 return ariaLabeledBy;
125 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
126 if (!ariaLabel.isEmpty())
133 void AXNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const
135 elementsFromAttribute(elements, aria_labeledbyAttr);
136 if (!elements.size())
137 elementsFromAttribute(elements, aria_labelledbyAttr);
140 void AXNodeObject::changeValueByStep(bool increase)
142 float step = stepValueForRange();
143 float value = valueForRange();
145 value += increase ? step : -step;
147 setValue(String::number(value));
149 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true);
152 bool AXNodeObject::computeAccessibilityIsIgnored() const
155 // Double-check that an AXObject is never accessed before
156 // it's been initialized.
157 ASSERT(m_initialized);
160 // If this element is within a parent that cannot have children, it should not be exposed.
161 if (isDescendantOfBarrenParent())
164 // Ignore labels that are already referenced by a control's title UI element.
165 AXObject* controlObject = correspondingControlForLabelElement();
166 if (controlObject && !controlObject->exposesTitleUIElement() && controlObject->isCheckboxOrRadio())
169 return m_role == UnknownRole;
172 AccessibilityRole AXNodeObject::determineAccessibilityRole()
177 m_ariaRole = determineAriaRoleAttribute();
179 AccessibilityRole ariaRole = ariaRoleAttribute();
180 if (ariaRole != UnknownRole)
183 if (node()->isLink())
185 if (node()->isTextNode())
186 return StaticTextRole;
187 if (isHTMLButtonElement(*node()))
188 return buttonRoleType();
189 if (isHTMLInputElement(*node())) {
190 HTMLInputElement& input = toHTMLInputElement(*node());
191 if (input.isCheckbox())
193 if (input.isRadioButton())
194 return RadioButtonRole;
195 if (input.isTextButton())
196 return buttonRoleType();
197 if (input.isRangeControl())
200 const AtomicString& type = input.getAttribute(typeAttr);
201 if (equalIgnoringCase(type, "color"))
202 return ColorWellRole;
204 return TextFieldRole;
206 if (isHTMLSelectElement(*node())) {
207 HTMLSelectElement& selectElement = toHTMLSelectElement(*node());
208 return selectElement.multiple() ? ListBoxRole : PopUpButtonRole;
210 if (isHTMLTextAreaElement(*node()))
214 if (isHTMLDivElement(*node()))
216 if (isHTMLParagraphElement(*node()))
217 return ParagraphRole;
218 if (isHTMLLabelElement(*node()))
220 if (node()->isElementNode() && toElement(node())->isFocusable())
222 if (isHTMLAnchorElement(*node()) && isClickable())
228 AccessibilityRole AXNodeObject::determineAriaRoleAttribute() const
230 const AtomicString& ariaRole = getAttribute(roleAttr);
231 if (ariaRole.isNull() || ariaRole.isEmpty())
234 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
236 // ARIA states if an item can get focus, it should not be presentational.
237 if (role == PresentationalRole && canSetFocusAttribute())
240 if (role == ButtonRole)
241 role = buttonRoleType();
243 if (role == TextAreaRole && !ariaIsMultiline())
244 role = TextFieldRole;
246 role = remapAriaRoleDueToParent(role);
254 void AXNodeObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const
256 Node* node = this->node();
257 if (!node || !node->isElementNode())
260 TreeScope& scope = node->treeScope();
262 String idList = getAttribute(attribute).string();
263 if (idList.isEmpty())
266 idList.replace('\n', ' ');
267 Vector<String> idVector;
268 idList.split(' ', idVector);
270 unsigned size = idVector.size();
271 for (unsigned i = 0; i < size; ++i) {
272 AtomicString idName(idVector[i]);
273 Element* idElement = scope.getElementById(idName);
275 elements.append(idElement);
279 // If you call node->rendererIsEditable() since that will return true if an ancestor is editable.
280 // This only returns true if this is the element that actually has the contentEditable attribute set.
281 bool AXNodeObject::hasContentEditableAttributeSet() const
283 if (!hasAttribute(contenteditableAttr))
285 const AtomicString& contentEditableValue = getAttribute(contenteditableAttr);
286 // Both "true" (case-insensitive) and the empty string count as true.
287 return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableValue, "true");
290 bool AXNodeObject::isDescendantOfBarrenParent() const
292 for (AXObject* object = parentObject(); object; object = object->parentObject()) {
293 if (!object->canHaveChildren())
300 bool AXNodeObject::isGenericFocusableElement() const
302 if (!canSetFocusAttribute())
305 // If it's a control, it's not generic.
309 // If it has an aria role, it's not generic.
310 if (m_ariaRole != UnknownRole)
313 // If the content editable attribute is set on this element, that's the reason
314 // it's focusable, and existing logic should handle this case already - so it's not a
315 // generic focusable element.
317 if (hasContentEditableAttributeSet())
320 // The web area and body element are both focusable, but existing logic handles these
321 // cases already, so we don't need to include them here.
322 if (roleValue() == WebAreaRole)
324 if (isHTMLBodyElement(node()))
327 // An SVG root is focusable by default, but it's probably not interactive, so don't
328 // include it. It can still be made accessible by giving it an ARIA role.
329 if (roleValue() == SVGRootRole)
335 HTMLLabelElement* AXNodeObject::labelForElement(Element* element) const
337 if (!element->isHTMLElement() || !toHTMLElement(element)->isLabelable())
340 const AtomicString& id = element->getIdAttribute();
342 if (HTMLLabelElement* label = element->treeScope().labelElementForId(id))
346 for (Element* parent = element->parentElement(); parent; parent = parent->parentElement()) {
347 if (isHTMLLabelElement(*parent))
348 return toHTMLLabelElement(parent);
354 AXObject* AXNodeObject::menuButtonForMenu() const
356 Element* menuItem = menuItemElementForMenu();
359 // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem
360 AXObject* menuItemAX = axObjectCache()->getOrCreate(menuItem);
361 if (menuItemAX && menuItemAX->isMenuButton())
367 static Element* siblingWithAriaRole(String role, Node* node)
369 Node* parent = node->parentNode();
373 for (Element* sibling = ElementTraversal::firstChild(*parent); sibling; sibling = ElementTraversal::nextSibling(*sibling)) {
374 const AtomicString& siblingAriaRole = sibling->getAttribute(roleAttr);
375 if (equalIgnoringCase(siblingAriaRole, role))
382 Element* AXNodeObject::menuItemElementForMenu() const
384 if (ariaRoleAttribute() != MenuRole)
387 return siblingWithAriaRole("menuitem", node());
390 Element* AXNodeObject::mouseButtonListener() const
392 Node* node = this->node();
396 // check if our parent is a mouse button listener
397 while (node && !node->isElementNode())
398 node = node->parentNode();
403 // FIXME: Do the continuation search like anchorElement does
404 for (Element* element = toElement(node); element; element = element->parentElement()) {
405 if (element->getAttributeEventListener(EventTypeNames::click) || element->getAttributeEventListener(EventTypeNames::mousedown) || element->getAttributeEventListener(EventTypeNames::mouseup))
412 AccessibilityRole AXNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
414 // Some objects change their role based on their parent.
415 // However, asking for the unignoredParent calls accessibilityIsIgnored(), which can trigger a loop.
416 // While inside the call stack of creating an element, we need to avoid accessibilityIsIgnored().
417 // https://bugs.webkit.org/show_bug.cgi?id=65174
419 if (role != ListBoxOptionRole && role != MenuItemRole)
422 for (AXObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
423 AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
425 // Selects and listboxes both have options as child roles, but they map to different roles within WebCore.
426 if (role == ListBoxOptionRole && parentAriaRole == MenuRole)
428 // An aria "menuitem" may map to MenuButton or MenuItem depending on its parent.
429 if (role == MenuItemRole && parentAriaRole == GroupRole)
430 return MenuButtonRole;
432 // If the parent had a different role, then we don't need to continue searching up the chain.
440 void AXNodeObject::init()
443 ASSERT(!m_initialized);
444 m_initialized = true;
446 m_role = determineAccessibilityRole();
449 void AXNodeObject::detach()
456 bool AXNodeObject::isAnchor() const
458 return !isNativeImage() && isLink();
461 bool AXNodeObject::isControl() const
463 Node* node = this->node();
467 return ((node->isElementNode() && toElement(node)->isFormControlElement())
468 || AXObject::isARIAControl(ariaRoleAttribute()));
471 bool AXNodeObject::isFieldset() const
473 return isHTMLFieldSetElement(node());
476 bool AXNodeObject::isHeading() const
478 return roleValue() == HeadingRole;
481 bool AXNodeObject::isHovered() const
483 Node* node = this->node();
487 return node->hovered();
490 bool AXNodeObject::isImage() const
492 return roleValue() == ImageRole;
495 bool AXNodeObject::isImageButton() const
497 return isNativeImage() && isButton();
500 bool AXNodeObject::isInputImage() const
502 Node* node = this->node();
503 if (roleValue() == ButtonRole && isHTMLInputElement(node))
504 return toHTMLInputElement(*node).isImageButton();
509 bool AXNodeObject::isLink() const
511 return roleValue() == LinkRole;
514 bool AXNodeObject::isMenu() const
516 return roleValue() == MenuRole;
519 bool AXNodeObject::isMenuButton() const
521 return roleValue() == MenuButtonRole;
524 bool AXNodeObject::isMultiSelectable() const
526 const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
527 if (equalIgnoringCase(ariaMultiSelectable, "true"))
529 if (equalIgnoringCase(ariaMultiSelectable, "false"))
532 return isHTMLSelectElement(node()) && toHTMLSelectElement(*node()).multiple();
535 bool AXNodeObject::isNativeCheckboxOrRadio() const
537 Node* node = this->node();
538 if (!isHTMLInputElement(node))
541 HTMLInputElement* input = toHTMLInputElement(node);
542 return input->isCheckbox() || input->isRadioButton();
545 bool AXNodeObject::isNativeImage() const
547 Node* node = this->node();
551 if (isHTMLImageElement(*node))
554 if (isHTMLAppletElement(*node) || isHTMLEmbedElement(*node) || isHTMLObjectElement(*node))
557 if (isHTMLInputElement(*node))
558 return toHTMLInputElement(*node).isImageButton();
563 bool AXNodeObject::isNativeTextControl() const
565 Node* node = this->node();
569 if (isHTMLTextAreaElement(*node))
572 if (isHTMLInputElement(*node)) {
573 HTMLInputElement* input = toHTMLInputElement(node);
574 return input->isText() || input->isNumberField();
580 bool AXNodeObject::isNonNativeTextControl() const
582 if (isNativeTextControl())
585 if (hasContentEditableAttributeSet())
588 if (isARIATextControl())
594 bool AXNodeObject::isPasswordField() const
596 Node* node = this->node();
597 if (!isHTMLInputElement(node))
600 if (ariaRoleAttribute() != UnknownRole)
603 return toHTMLInputElement(node)->isPasswordField();
606 bool AXNodeObject::isProgressIndicator() const
608 return roleValue() == ProgressIndicatorRole;
611 bool AXNodeObject::isSlider() const
613 return roleValue() == SliderRole;
616 bool AXNodeObject::isChecked() const
618 Node* node = this->node();
622 // First test for native checkedness semantics
623 if (isHTMLInputElement(*node))
624 return toHTMLInputElement(*node).shouldAppearChecked();
626 // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute
627 AccessibilityRole ariaRole = ariaRoleAttribute();
628 if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole) {
629 if (equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
634 // Otherwise it's not checked
638 bool AXNodeObject::isClickable() const
641 if (node()->isElementNode() && toElement(node())->isDisabledFormControl())
644 // Note: we can't call node()->willRespondToMouseClickEvents() because that triggers a style recalc and can delete this.
645 if (node()->hasEventListeners(EventTypeNames::mouseup) || node()->hasEventListeners(EventTypeNames::mousedown) || node()->hasEventListeners(EventTypeNames::click) || node()->hasEventListeners(EventTypeNames::DOMActivate))
649 return AXObject::isClickable();
652 bool AXNodeObject::isEnabled() const
654 if (equalIgnoringCase(getAttribute(aria_disabledAttr), "true"))
657 Node* node = this->node();
658 if (!node || !node->isElementNode())
661 return !toElement(node)->isDisabledFormControl();
664 bool AXNodeObject::isIndeterminate() const
666 Node* node = this->node();
667 if (!isHTMLInputElement(node))
670 return toHTMLInputElement(node)->shouldAppearIndeterminate();
673 bool AXNodeObject::isPressed() const
678 Node* node = this->node();
682 // If this is an ARIA button, check the aria-pressed attribute rather than node()->active()
683 if (ariaRoleAttribute() == ButtonRole) {
684 if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true"))
689 return node->active();
692 bool AXNodeObject::isReadOnly() const
694 Node* node = this->node();
698 if (isHTMLTextAreaElement(*node))
699 return toHTMLTextAreaElement(*node).isReadOnly();
701 if (isHTMLInputElement(*node)) {
702 HTMLInputElement& input = toHTMLInputElement(*node);
703 if (input.isTextField())
704 return input.isReadOnly();
707 return !node->rendererIsEditable();
710 bool AXNodeObject::isRequired() const
712 if (equalIgnoringCase(getAttribute(aria_requiredAttr), "true"))
715 Node* n = this->node();
716 if (n && (n->isElementNode() && toElement(n)->isFormControlElement()))
717 return toHTMLFormControlElement(n)->isRequired();
722 bool AXNodeObject::canSetFocusAttribute() const
724 Node* node = this->node();
731 // NOTE: It would be more accurate to ask the document whether setFocusedNode() would
732 // do anything. For example, setFocusedNode() will do nothing if the current focused
733 // node will not relinquish the focus.
737 if (isDisabledFormControl(node))
740 return node->isElementNode() && toElement(node)->supportsFocus();
743 bool AXNodeObject::canSetValueAttribute() const
745 if (equalIgnoringCase(getAttribute(aria_readonlyAttr), "true"))
748 if (isProgressIndicator() || isSlider())
751 if (isTextControl() && !isNativeTextControl())
754 // Any node could be contenteditable, so isReadOnly should be relied upon
755 // for this information for all elements.
756 return !isReadOnly();
759 bool AXNodeObject::canvasHasFallbackContent() const
761 Node* node = this->node();
762 if (!isHTMLCanvasElement(node))
765 // If it has any children that are elements, we'll assume it might be fallback
766 // content. If it has no children or its only children are not elements
767 // (e.g. just text nodes), it doesn't have fallback content.
768 return ElementTraversal::firstChild(*node);
771 bool AXNodeObject::exposesTitleUIElement() const
776 // If this control is ignored (because it's invisible),
777 // then the label needs to be exposed so it can be visible to accessibility.
778 if (accessibilityIsIgnored())
781 // ARIA: section 2A, bullet #3 says if aria-labeledby or aria-label appears, it should
782 // override the "label" element association.
783 bool hasTextAlternative = (!ariaLabeledByAttribute().isEmpty() || !getAttribute(aria_labelAttr).isEmpty());
785 // Checkboxes and radio buttons use the text of their title ui element as their own AXTitle.
786 // This code controls whether the title ui element should appear in the AX tree (usually, no).
787 // It should appear if the control already has a label (which will be used as the AXTitle instead).
788 if (isCheckboxOrRadio())
789 return hasTextAlternative;
791 // When controls have their own descriptions, the title element should be ignored.
792 if (hasTextAlternative)
798 int AXNodeObject::headingLevel() const
800 // headings can be in block flow and non-block flow
801 Node* node = this->node();
805 if (ariaRoleAttribute() == HeadingRole)
806 return getAttribute(aria_levelAttr).toInt();
808 if (node->hasTagName(h1Tag))
811 if (node->hasTagName(h2Tag))
814 if (node->hasTagName(h3Tag))
817 if (node->hasTagName(h4Tag))
820 if (node->hasTagName(h5Tag))
823 if (node->hasTagName(h6Tag))
829 unsigned AXNodeObject::hierarchicalLevel() const
831 Node* node = this->node();
832 if (!node || !node->isElementNode())
834 Element* element = toElement(node);
835 String ariaLevel = element->getAttribute(aria_levelAttr);
836 if (!ariaLevel.isEmpty())
837 return ariaLevel.toInt();
839 // Only tree item will calculate its level through the DOM currently.
840 if (roleValue() != TreeItemRole)
843 // Hierarchy leveling starts at 1, to match the aria-level spec.
844 // We measure tree hierarchy by the number of groups that the item is within.
846 for (AXObject* parent = parentObject(); parent; parent = parent->parentObject()) {
847 AccessibilityRole parentRole = parent->roleValue();
848 if (parentRole == GroupRole)
850 else if (parentRole == TreeRole)
857 String AXNodeObject::text() const
859 // If this is a user defined static text, use the accessible name computation.
860 if (ariaRoleAttribute() == StaticTextRole)
861 return ariaAccessibilityDescription();
863 if (!isTextControl())
866 Node* node = this->node();
870 if (isNativeTextControl() && (isHTMLTextAreaElement(*node) || isHTMLInputElement(*node)))
871 return toHTMLTextFormControlElement(*node).value();
873 if (!node->isElementNode())
876 return toElement(node)->innerText();
879 AXObject* AXNodeObject::titleUIElement() const
881 if (!node() || !node()->isElementNode())
885 return axObjectCache()->getOrCreate(toHTMLFieldSetElement(node())->legend());
887 HTMLLabelElement* label = labelForElement(toElement(node()));
889 return axObjectCache()->getOrCreate(label);
894 AccessibilityButtonState AXNodeObject::checkboxOrRadioValue() const
896 if (isNativeCheckboxOrRadio())
897 return isChecked() ? ButtonStateOn : ButtonStateOff;
899 return AXObject::checkboxOrRadioValue();
902 void AXNodeObject::colorValue(int& r, int& g, int& b) const
911 if (!isHTMLInputElement(node()))
914 HTMLInputElement* input = toHTMLInputElement(node());
915 const AtomicString& type = input->getAttribute(typeAttr);
916 if (!equalIgnoringCase(type, "color"))
919 // HTMLInputElement::value always returns a string parseable by Color.
921 bool success = color.setFromString(input->value());
922 ASSERT_UNUSED(success, success);
928 String AXNodeObject::valueDescription() const
930 if (!supportsRangeValue())
933 return getAttribute(aria_valuetextAttr).string();
936 float AXNodeObject::valueForRange() const
938 if (hasAttribute(aria_valuenowAttr))
939 return getAttribute(aria_valuenowAttr).toFloat();
941 if (isHTMLInputElement(node())) {
942 HTMLInputElement& input = toHTMLInputElement(*node());
943 if (input.isRangeControl())
944 return input.valueAsNumber();
950 float AXNodeObject::maxValueForRange() const
952 if (hasAttribute(aria_valuemaxAttr))
953 return getAttribute(aria_valuemaxAttr).toFloat();
955 if (isHTMLInputElement(node())) {
956 HTMLInputElement& input = toHTMLInputElement(*node());
957 if (input.isRangeControl())
958 return input.maximum();
964 float AXNodeObject::minValueForRange() const
966 if (hasAttribute(aria_valueminAttr))
967 return getAttribute(aria_valueminAttr).toFloat();
969 if (isHTMLInputElement(node())) {
970 HTMLInputElement& input = toHTMLInputElement(*node());
971 if (input.isRangeControl())
972 return input.minimum();
978 float AXNodeObject::stepValueForRange() const
980 return getAttribute(stepAttr).toFloat();
983 String AXNodeObject::stringValue() const
985 Node* node = this->node();
989 if (ariaRoleAttribute() == StaticTextRole) {
990 String staticText = text();
991 if (!staticText.length())
992 staticText = textUnderElement();
996 if (node->isTextNode())
997 return textUnderElement();
999 if (isHTMLSelectElement(*node)) {
1000 HTMLSelectElement& selectElement = toHTMLSelectElement(*node);
1001 int selectedIndex = selectElement.selectedIndex();
1002 const Vector<HTMLElement*> listItems = selectElement.listItems();
1003 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
1004 const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr);
1005 if (!overriddenDescription.isNull())
1006 return overriddenDescription;
1008 if (!selectElement.multiple())
1009 return selectElement.value();
1013 if (isTextControl())
1016 // FIXME: We might need to implement a value here for more types
1017 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
1018 // this would require subclassing or making accessibilityAttributeNames do something other than return a
1019 // single static array.
1023 String AXNodeObject::ariaDescribedByAttribute() const
1025 Vector<Element*> elements;
1026 elementsFromAttribute(elements, aria_describedbyAttr);
1028 return accessibilityDescriptionForElements(elements);
1032 String AXNodeObject::ariaLabeledByAttribute() const
1034 Vector<Element*> elements;
1035 ariaLabeledByElements(elements);
1037 return accessibilityDescriptionForElements(elements);
1040 AccessibilityRole AXNodeObject::ariaRoleAttribute() const
1045 // When building the textUnderElement for an object, determine whether or not
1046 // we should include the inner text of this given descendant object or skip it.
1047 static bool shouldUseAccessiblityObjectInnerText(AXObject* obj)
1049 // Consider this hypothetical example:
1052 // Table of contents
1054 // <a href="#start">Jump to start of book</a>
1056 // <li><a href="#1">Chapter 1</a></li>
1057 // <li><a href="#1">Chapter 2</a></li>
1061 // The goal is to return a reasonable title for the outer container div, because
1062 // it's focusable - but without making its title be the full inner text, which is
1063 // quite long. As a heuristic, skip links, controls, and elements that are usually
1064 // containers with lots of children.
1066 // Skip hidden children
1067 if (obj->isInertOrAriaHidden())
1070 // Skip focusable children, so we don't include the text of links and controls.
1071 if (obj->canSetFocusAttribute())
1074 // Skip big container elements like lists, tables, etc.
1075 if (obj->isList() || obj->isAXTable() || obj->isTree() || obj->isCanvas())
1081 String AXNodeObject::textUnderElement() const
1083 Node* node = this->node();
1084 if (node && node->isTextNode())
1085 return toText(node)->wholeText();
1087 StringBuilder builder;
1088 for (AXObject* child = firstChild(); child; child = child->nextSibling()) {
1089 if (!shouldUseAccessiblityObjectInnerText(child))
1092 if (child->isAXNodeObject()) {
1093 Vector<AccessibilityText> textOrder;
1094 toAXNodeObject(child)->alternativeText(textOrder);
1095 if (textOrder.size() > 0) {
1096 builder.append(textOrder[0].text);
1101 builder.append(child->textUnderElement());
1104 return builder.toString();
1107 String AXNodeObject::accessibilityDescription() const
1109 // Static text should not have a description, it should only have a stringValue.
1110 if (roleValue() == StaticTextRole)
1113 String ariaDescription = ariaAccessibilityDescription();
1114 if (!ariaDescription.isEmpty())
1115 return ariaDescription;
1117 if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
1118 // Images should use alt as long as the attribute is present, even if empty.
1119 // Otherwise, it should fallback to other methods, like the title attribute.
1120 const AtomicString& alt = getAttribute(altAttr);
1125 // An element's descriptive text is comprised of title() (what's visible on the screen) and accessibilityDescription() (other descriptive text).
1126 // Both are used to generate what a screen reader speaks.
1127 // If this point is reached (i.e. there's no accessibilityDescription) and there's no title(), we should fallback to using the title attribute.
1128 // 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).
1129 if (title().isEmpty())
1130 return getAttribute(titleAttr);
1135 String AXNodeObject::title() const
1137 Node* node = this->node();
1141 bool isInputElement = isHTMLInputElement(*node);
1142 if (isInputElement) {
1143 HTMLInputElement& input = toHTMLInputElement(*node);
1144 if (input.isTextButton())
1145 return input.valueWithDefault();
1148 if (isInputElement || AXObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
1149 HTMLLabelElement* label = labelForElement(toElement(node));
1150 if (label && !exposesTitleUIElement())
1151 return label->innerText();
1154 // If this node isn't rendered, there's no inner text we can extract from a select element.
1155 if (!isAXRenderObject() && isHTMLSelectElement(*node))
1158 switch (roleValue()) {
1159 case PopUpButtonRole:
1160 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1161 if (isHTMLSelectElement(*node))
1164 case ToggleButtonRole:
1166 case ListBoxOptionRole:
1167 case MenuButtonRole:
1169 case RadioButtonRole:
1171 return textUnderElement();
1172 // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>.
1179 if (isHeading() || isLink())
1180 return textUnderElement();
1182 // If it's focusable but it's not content editable or a known control type, then it will appear to
1183 // the user as a single atomic object, so we should use its text as the default title.
1184 if (isGenericFocusableElement())
1185 return textUnderElement();
1190 String AXNodeObject::helpText() const
1192 Node* node = this->node();
1196 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1197 if (!ariaHelp.isEmpty())
1200 String describedBy = ariaDescribedByAttribute();
1201 if (!describedBy.isEmpty())
1204 String description = accessibilityDescription();
1205 for (Node* curr = node; curr; curr = curr->parentNode()) {
1206 if (curr->isHTMLElement()) {
1207 const AtomicString& summary = toElement(curr)->getAttribute(summaryAttr);
1208 if (!summary.isEmpty())
1211 // The title attribute should be used as help text unless it is already being used as descriptive text.
1212 const AtomicString& title = toElement(curr)->getAttribute(titleAttr);
1213 if (!title.isEmpty() && description != title)
1217 // Only take help text from an ancestor element if its a group or an unknown role. If help was
1218 // added to those kinds of elements, it is likely it was meant for a child element.
1219 AXObject* axObj = axObjectCache()->getOrCreate(curr);
1221 AccessibilityRole role = axObj->roleValue();
1222 if (role != GroupRole && role != UnknownRole)
1230 LayoutRect AXNodeObject::elementRect() const
1232 // First check if it has a custom rect, for example if this element is tied to a canvas path.
1233 if (!m_explicitElementRect.isEmpty())
1234 return m_explicitElementRect;
1236 // AXNodeObjects have no mechanism yet to return a size or position.
1237 // For now, let's return the position of the ancestor that does have a position,
1238 // and make it the width of that parent, and about the height of a line of text, so that it's clear the object is a child of the parent.
1240 LayoutRect boundingBox;
1242 for (AXObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) {
1243 if (positionProvider->isAXRenderObject()) {
1244 LayoutRect parentRect = positionProvider->elementRect();
1245 boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat()))));
1246 boundingBox.setLocation(parentRect.location());
1254 AXObject* AXNodeObject::parentObject() const
1259 Node* parentObj = node()->parentNode();
1261 return axObjectCache()->getOrCreate(parentObj);
1266 AXObject* AXNodeObject::parentObjectIfExists() const
1268 return parentObject();
1271 AXObject* AXNodeObject::firstChild() const
1276 Node* firstChild = node()->firstChild();
1281 return axObjectCache()->getOrCreate(firstChild);
1284 AXObject* AXNodeObject::nextSibling() const
1289 Node* nextSibling = node()->nextSibling();
1293 return axObjectCache()->getOrCreate(nextSibling);
1296 void AXNodeObject::addChildren()
1298 // If the need to add more children in addition to existing children arises,
1299 // childrenChanged should have been called, leaving the object with no children.
1300 ASSERT(!m_haveChildren);
1305 m_haveChildren = true;
1307 // The only time we add children from the DOM tree to a node with a renderer is when it's a canvas.
1308 if (renderer() && !isHTMLCanvasElement(*m_node))
1311 for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
1312 addChild(axObjectCache()->getOrCreate(child));
1315 void AXNodeObject::addChild(AXObject* child)
1317 insertChild(child, m_children.size());
1320 void AXNodeObject::insertChild(AXObject* child, unsigned index)
1325 // If the parent is asking for this child's children, then either it's the first time (and clearing is a no-op),
1326 // or its visibility has changed. In the latter case, this child may have a stale child cached.
1327 // This can prevent aria-hidden changes from working correctly. Hence, whenever a parent is getting children, ensure data is not stale.
1328 child->clearChildren();
1330 if (child->accessibilityIsIgnored()) {
1331 AccessibilityChildrenVector children = child->children();
1332 size_t length = children.size();
1333 for (size_t i = 0; i < length; ++i)
1334 m_children.insert(index + i, children[i]);
1336 ASSERT(child->parentObject() == this);
1337 m_children.insert(index, child);
1341 bool AXNodeObject::canHaveChildren() const
1343 // If this is an AXRenderObject, then it's okay if this object
1344 // doesn't have a node - there are some renderers that don't have associated
1345 // nodes, like scroll areas and css-generated text.
1346 if (!node() && !isAXRenderObject())
1349 // Elements that should not have children
1350 switch (roleValue()) {
1353 case PopUpButtonRole:
1355 case RadioButtonRole:
1357 case ToggleButtonRole:
1358 case ListBoxOptionRole:
1361 case StaticTextRole:
1362 if (!axObjectCache()->inlineTextBoxAccessibility())
1369 Element* AXNodeObject::actionElement() const
1371 Node* node = this->node();
1375 if (isHTMLInputElement(*node)) {
1376 HTMLInputElement& input = toHTMLInputElement(*node);
1377 if (!input.isDisabledFormControl() && (isCheckboxOrRadio() || input.isTextButton()))
1379 } else if (isHTMLButtonElement(*node)) {
1380 return toElement(node);
1383 if (isFileUploadButton())
1384 return toElement(node);
1386 if (AXObject::isARIAInput(ariaRoleAttribute()))
1387 return toElement(node);
1389 if (isImageButton())
1390 return toElement(node);
1392 if (isHTMLSelectElement(*node))
1393 return toElement(node);
1395 switch (roleValue()) {
1397 case PopUpButtonRole:
1398 case ToggleButtonRole:
1402 return toElement(node);
1407 Element* elt = anchorElement();
1409 elt = mouseButtonListener();
1413 Element* AXNodeObject::anchorElement() const
1415 Node* node = this->node();
1419 AXObjectCache* cache = axObjectCache();
1421 // search up the DOM tree for an anchor element
1422 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
1423 for ( ; node; node = node->parentNode()) {
1424 if (isHTMLAnchorElement(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor()))
1425 return toElement(node);
1431 Document* AXNodeObject::document() const
1435 return &node()->document();
1438 void AXNodeObject::setNode(Node* node)
1443 AXObject* AXNodeObject::correspondingControlForLabelElement() const
1445 HTMLLabelElement* labelElement = labelElementContainer();
1449 HTMLElement* correspondingControl = labelElement->control();
1450 if (!correspondingControl)
1453 // Make sure the corresponding control isn't a descendant of this label
1454 // that's in the middle of being destroyed.
1455 if (correspondingControl->renderer() && !correspondingControl->renderer()->parent())
1458 return axObjectCache()->getOrCreate(correspondingControl);
1461 HTMLLabelElement* AXNodeObject::labelElementContainer() const
1466 // the control element should not be considered part of the label
1470 // find if this has a ancestor that is a label
1471 return Traversal<HTMLLabelElement>::firstAncestorOrSelf(*node());
1474 void AXNodeObject::setFocused(bool on)
1476 if (!canSetFocusAttribute())
1479 Document* document = this->document();
1481 document->setFocusedElement(nullptr);
1483 Node* node = this->node();
1484 if (node && node->isElementNode()) {
1485 // If this node is already the currently focused node, then calling focus() won't do anything.
1486 // That is a problem when focus is removed from the webpage to chrome, and then returns.
1487 // In these cases, we need to do what keyboard and mouse focus do, which is reset focus first.
1488 if (document->focusedElement() == node)
1489 document->setFocusedElement(nullptr);
1491 toElement(node)->focus();
1493 document->setFocusedElement(nullptr);
1498 void AXNodeObject::increment()
1500 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
1501 alterSliderValue(true);
1504 void AXNodeObject::decrement()
1506 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
1507 alterSliderValue(false);
1510 void AXNodeObject::childrenChanged()
1512 // This method is meant as a quick way of marking a portion of the accessibility tree dirty.
1513 if (!node() && !renderer())
1516 axObjectCache()->postNotification(this, document(), AXObjectCache::AXChildrenChanged, true);
1518 // Go up the accessibility parent chain, but only if the element already exists. This method is
1519 // called during render layouts, minimal work should be done.
1520 // If AX elements are created now, they could interrogate the render tree while it's in a funky state.
1521 // At the same time, process ARIA live region changes.
1522 for (AXObject* parent = this; parent; parent = parent->parentObjectIfExists()) {
1523 parent->setNeedsToUpdateChildren();
1525 // These notifications always need to be sent because screenreaders are reliant on them to perform.
1526 // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update.
1528 // If this element supports ARIA live regions, then notify the AT of changes.
1529 if (parent->supportsARIALiveRegion())
1530 axObjectCache()->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged, true);
1532 // If this element is an ARIA text box or content editable, post a "value changed" notification on it
1533 // so that it behaves just like a native input element or textarea.
1534 if (isNonNativeTextControl())
1535 axObjectCache()->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged, true);
1539 void AXNodeObject::selectionChanged()
1541 // Post the selected text changed event on the first ancestor that's
1542 // focused (to handle form controls, ARIA text boxes and contentEditable),
1543 // or the web area if the selection is just in the document somewhere.
1544 if (isFocused() || isWebArea())
1545 axObjectCache()->postNotification(this, document(), AXObjectCache::AXSelectedTextChanged, true);
1547 AXObject::selectionChanged(); // Calls selectionChanged on parent.
1550 void AXNodeObject::textChanged()
1552 // If this element supports ARIA live regions, or is part of a region with an ARIA editable role,
1553 // then notify the AT of changes.
1554 AXObjectCache* cache = axObjectCache();
1555 for (Node* parentNode = node(); parentNode; parentNode = parentNode->parentNode()) {
1556 AXObject* parent = cache->get(parentNode);
1560 if (parent->supportsARIALiveRegion())
1561 cache->postNotification(parentNode, AXObjectCache::AXLiveRegionChanged, true);
1563 // If this element is an ARIA text box or content editable, post a "value changed" notification on it
1564 // so that it behaves just like a native input element or textarea.
1565 if (parent->isNonNativeTextControl())
1566 cache->postNotification(parentNode, AXObjectCache::AXValueChanged, true);
1570 void AXNodeObject::updateAccessibilityRole()
1572 bool ignoredStatus = accessibilityIsIgnored();
1573 m_role = determineAccessibilityRole();
1575 // The AX hierarchy only needs to be updated if the ignored status of an element has changed.
1576 if (ignoredStatus != accessibilityIsIgnored())
1580 String AXNodeObject::alternativeTextForWebArea() const
1582 // The WebArea description should follow this order:
1583 // aria-label on the <html>
1584 // title on the <html>
1585 // <title> inside the <head> (of it was set through JS)
1586 // name on the <html>
1588 // aria-label on the <iframe>
1589 // title on the <iframe>
1590 // name on the <iframe>
1592 Document* document = this->document();
1596 // Check if the HTML element has an aria-label for the webpage.
1597 if (Element* documentElement = document->documentElement()) {
1598 const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr);
1599 if (!ariaLabel.isEmpty())
1603 Node* owner = document->ownerElement();
1605 if (isHTMLFrameElementBase(*owner)) {
1606 const AtomicString& title = toElement(owner)->getAttribute(titleAttr);
1607 if (!title.isEmpty())
1609 return toElement(owner)->getNameAttribute();
1611 if (owner->isHTMLElement())
1612 return toHTMLElement(owner)->getNameAttribute();
1615 String documentTitle = document->title();
1616 if (!documentTitle.isEmpty())
1617 return documentTitle;
1619 owner = document->body();
1620 if (owner && owner->isHTMLElement())
1621 return toHTMLElement(owner)->getNameAttribute();
1626 void AXNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
1629 String webAreaText = alternativeTextForWebArea();
1630 if (!webAreaText.isEmpty())
1631 textOrder.append(AccessibilityText(webAreaText, AlternativeText));
1635 ariaLabeledByText(textOrder);
1637 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1638 if (!ariaLabel.isEmpty())
1639 textOrder.append(AccessibilityText(ariaLabel, AlternativeText));
1641 if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
1642 // Images should use alt as long as the attribute is present, even if empty.
1643 // Otherwise, it should fallback to other methods, like the title attribute.
1644 const AtomicString& alt = getAttribute(altAttr);
1646 textOrder.append(AccessibilityText(alt, AlternativeText));
1650 void AXNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
1652 String ariaLabeledBy = ariaLabeledByAttribute();
1653 if (!ariaLabeledBy.isEmpty()) {
1654 Vector<Element*> elements;
1655 ariaLabeledByElements(elements);
1657 unsigned length = elements.size();
1658 for (unsigned k = 0; k < length; k++) {
1659 RefPtr<AXObject> axElement = axObjectCache()->getOrCreate(elements[k]);
1660 textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, axElement));
1665 void AXNodeObject::changeValueByPercent(float percentChange)
1667 float range = maxValueForRange() - minValueForRange();
1668 float value = valueForRange();
1670 value += range * (percentChange / 100);
1671 setValue(String::number(value));
1673 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true);
1676 } // namespace WebCore