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 "AccessibilityNodeObject.h"
32 #include "AXObjectCache.h"
33 #include "AccessibilityImageMapLink.h"
34 #include "AccessibilityListBox.h"
35 #include "AccessibilitySpinButton.h"
36 #include "AccessibilityTable.h"
37 #include "EventNames.h"
38 #include "FloatRect.h"
40 #include "FrameLoader.h"
41 #include "FrameSelection.h"
42 #include "FrameView.h"
43 #include "HTMLAreaElement.h"
44 #include "HTMLFieldSetElement.h"
45 #include "HTMLFormElement.h"
46 #include "HTMLFrameElementBase.h"
47 #include "HTMLImageElement.h"
48 #include "HTMLInputElement.h"
49 #include "HTMLLabelElement.h"
50 #include "HTMLLegendElement.h"
51 #include "HTMLMapElement.h"
52 #include "HTMLNames.h"
53 #include "HTMLOptGroupElement.h"
54 #include "HTMLOptionElement.h"
55 #include "HTMLOptionsCollection.h"
56 #include "HTMLPlugInImageElement.h"
57 #include "HTMLSelectElement.h"
58 #include "HTMLTextAreaElement.h"
59 #include "HTMLTextFormControlElement.h"
60 #include "HitTestRequest.h"
61 #include "HitTestResult.h"
62 #include "LabelableElement.h"
63 #include "LocalizedStrings.h"
64 #include "MathMLNames.h"
66 #include "NodeTraversal.h"
68 #include "ProgressTracker.h"
70 #include "TextControlInnerElements.h"
71 #include "TextIterator.h"
72 #include "UserGestureIndicator.h"
73 #include "VisibleUnits.h"
75 #include "htmlediting.h"
76 #include <wtf/StdLibExtras.h>
77 #include <wtf/text/StringBuilder.h>
78 #include <wtf/unicode/CharacterNames.h>
84 using namespace HTMLNames;
86 AccessibilityNodeObject::AccessibilityNodeObject(Node* node)
87 : AccessibilityObject()
88 , m_ariaRole(UnknownRole)
89 , m_childrenDirty(false)
90 , m_roleForMSAA(UnknownRole)
92 , m_initialized(false)
98 AccessibilityNodeObject::~AccessibilityNodeObject()
100 ASSERT(isDetached());
103 void AccessibilityNodeObject::init()
106 ASSERT(!m_initialized);
107 m_initialized = true;
109 m_role = determineAccessibilityRole();
112 PassRefPtr<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node)
114 return adoptRef(new AccessibilityNodeObject(node));
117 void AccessibilityNodeObject::detach()
120 AccessibilityObject::detach();
124 void AccessibilityNodeObject::childrenChanged()
126 // This method is meant as a quick way of marking a portion of the accessibility tree dirty.
127 if (!node() && !renderer())
130 axObjectCache()->postNotification(this, document(), AXObjectCache::AXChildrenChanged, true);
132 // Go up the accessibility parent chain, but only if the element already exists. This method is
133 // called during render layouts, minimal work should be done.
134 // If AX elements are created now, they could interrogate the render tree while it's in a funky state.
135 // At the same time, process ARIA live region changes.
136 for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) {
137 parent->setNeedsToUpdateChildren();
139 // These notifications always need to be sent because screenreaders are reliant on them to perform.
140 // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update.
142 // If this element supports ARIA live regions, then notify the AT of changes.
143 if (parent->supportsARIALiveRegion())
144 axObjectCache()->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged, true);
146 // If this element is an ARIA text control, notify the AT of changes.
147 if (parent->isARIATextControl() && !parent->isNativeTextControl() && !parent->node()->rendererIsEditable())
148 axObjectCache()->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged, true);
152 void AccessibilityNodeObject::updateAccessibilityRole()
154 bool ignoredStatus = accessibilityIsIgnored();
155 m_role = determineAccessibilityRole();
157 // The AX hierarchy only needs to be updated if the ignored status of an element has changed.
158 if (ignoredStatus != accessibilityIsIgnored())
162 AccessibilityObject* AccessibilityNodeObject::firstChild() const
167 Node* firstChild = node()->firstChild();
172 return axObjectCache()->getOrCreate(firstChild);
175 AccessibilityObject* AccessibilityNodeObject::lastChild() const
180 Node* lastChild = node()->lastChild();
184 return axObjectCache()->getOrCreate(lastChild);
187 AccessibilityObject* AccessibilityNodeObject::previousSibling() const
192 Node* previousSibling = node()->previousSibling();
193 if (!previousSibling)
196 return axObjectCache()->getOrCreate(previousSibling);
199 AccessibilityObject* AccessibilityNodeObject::nextSibling() const
204 Node* nextSibling = node()->nextSibling();
208 return axObjectCache()->getOrCreate(nextSibling);
211 AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const
213 return parentObject();
216 AccessibilityObject* AccessibilityNodeObject::parentObject() const
221 Node* parentObj = node()->parentNode();
223 return axObjectCache()->getOrCreate(parentObj);
228 LayoutRect AccessibilityNodeObject::elementRect() const
230 return boundingBoxRect();
233 LayoutRect AccessibilityNodeObject::boundingBoxRect() const
235 // AccessibilityNodeObjects have no mechanism yet to return a size or position.
236 // For now, let's return the position of the ancestor that does have a position,
237 // 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.
239 LayoutRect boundingBox;
241 for (AccessibilityObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) {
242 if (positionProvider->isAccessibilityRenderObject()) {
243 LayoutRect parentRect = positionProvider->elementRect();
244 boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat()))));
245 boundingBox.setLocation(parentRect.location());
253 void AccessibilityNodeObject::setNode(Node* node)
258 Document* AccessibilityNodeObject::document() const
262 return node()->document();
265 AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole()
270 m_ariaRole = determineAriaRoleAttribute();
272 AccessibilityRole ariaRole = ariaRoleAttribute();
273 if (ariaRole != UnknownRole)
276 if (node()->isLink())
277 return WebCoreLinkRole;
278 if (node()->isTextNode())
279 return StaticTextRole;
280 if (node()->hasTagName(buttonTag))
281 return buttonRoleType();
282 if (node()->hasTagName(inputTag)) {
283 HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
284 if (input->isCheckbox())
286 if (input->isRadioButton())
287 return RadioButtonRole;
288 if (input->isTextButton())
289 return buttonRoleType();
290 if (input->isRangeControl())
293 #if ENABLE(INPUT_TYPE_COLOR)
294 const AtomicString& type = input->getAttribute(typeAttr);
295 if (equalIgnoringCase(type, "color"))
296 return ColorWellRole;
299 return TextFieldRole;
301 if (node()->hasTagName(selectTag)) {
302 HTMLSelectElement* selectElement = toHTMLSelectElement(node());
303 return selectElement->multiple() ? ListBoxRole : PopUpButtonRole;
305 if (node()->hasTagName(textareaTag))
309 if (node()->hasTagName(divTag))
311 if (node()->hasTagName(pTag))
312 return ParagraphRole;
313 if (node()->hasTagName(labelTag))
315 if (node()->isFocusable())
321 void AccessibilityNodeObject::insertChild(AccessibilityObject* child, unsigned index)
326 // If the parent is asking for this child's children, then either it's the first time (and clearing is a no-op),
327 // or its visibility has changed. In the latter case, this child may have a stale child cached.
328 // This can prevent aria-hidden changes from working correctly. Hence, whenever a parent is getting children, ensure data is not stale.
329 child->clearChildren();
331 if (child->accessibilityIsIgnored()) {
332 AccessibilityChildrenVector children = child->children();
333 size_t length = children.size();
334 for (size_t i = 0; i < length; ++i)
335 m_children.insert(index + i, children[i]);
337 ASSERT(child->parentObject() == this);
338 m_children.insert(index, child);
342 void AccessibilityNodeObject::addChild(AccessibilityObject* child)
344 insertChild(child, m_children.size());
347 void AccessibilityNodeObject::addChildren()
349 // If the need to add more children in addition to existing children arises,
350 // childrenChanged should have been called, leaving the object with no children.
351 ASSERT(!m_haveChildren);
356 m_haveChildren = true;
358 // The only time we add children from the DOM tree to a node with a renderer is when it's a canvas.
359 if (renderer() && !m_node->hasTagName(canvasTag))
362 for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
363 addChild(axObjectCache()->getOrCreate(child));
366 bool AccessibilityNodeObject::canHaveChildren() const
368 // If this is an AccessibilityRenderObject, then it's okay if this object
369 // doesn't have a node - there are some renderers that don't have associated
370 // nodes, like scroll areas and css-generated text.
371 if (!node() && !isAccessibilityRenderObject())
374 // Elements that should not have children
375 switch (roleValue()) {
378 case PopUpButtonRole:
380 case RadioButtonRole:
382 case ToggleButtonRole:
384 case ListBoxOptionRole:
392 bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const
395 // Double-check that an AccessibilityObject is never accessed before
396 // it's been initialized.
397 ASSERT(m_initialized);
400 // If this element is within a parent that cannot have children, it should not be exposed.
401 if (isDescendantOfBarrenParent())
404 return m_role == UnknownRole;
407 bool AccessibilityNodeObject::canvasHasFallbackContent() const
409 Node* node = this->node();
410 if (!node || !node->hasTagName(canvasTag))
413 // If it has any children that are elements, we'll assume it might be fallback
414 // content. If it has no children or its only children are not elements
415 // (e.g. just text nodes), it doesn't have fallback content.
416 for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
417 if (child->isElementNode())
424 bool AccessibilityNodeObject::isImageButton() const
426 return isNativeImage() && isButton();
429 bool AccessibilityNodeObject::isAnchor() const
431 return !isNativeImage() && isLink();
434 bool AccessibilityNodeObject::isNativeTextControl() const
436 Node* node = this->node();
440 if (node->hasTagName(textareaTag))
443 if (node->hasTagName(inputTag)) {
444 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
445 return input->isText() || input->isNumberField();
451 bool AccessibilityNodeObject::isSearchField() const
453 Node* node = this->node();
457 HTMLInputElement* inputElement = node->toInputElement();
461 if (inputElement->isSearchField())
464 // Some websites don't label their search fields as such. However, they will
465 // use the word "search" in either the form or input type. This won't catch every case,
466 // but it will catch google.com for example.
468 // Check the node name of the input type, sometimes it's "search".
469 const AtomicString& nameAttribute = getAttribute(nameAttr);
470 if (nameAttribute.contains("search", false))
473 // Check the form action and the name, which will sometimes be "search".
474 HTMLFormElement* form = inputElement->form();
475 if (form && (form->name().contains("search", false) || form->action().contains("search", false)))
481 bool AccessibilityNodeObject::isNativeImage() const
483 Node* node = this->node();
487 if (node->hasTagName(imgTag))
490 if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag))
493 if (node->hasTagName(inputTag)) {
494 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
495 return input->isImageButton();
501 bool AccessibilityNodeObject::isImage() const
503 return roleValue() == ImageRole;
506 bool AccessibilityNodeObject::isPasswordField() const
508 Node* node = this->node();
509 if (!node || !node->isHTMLElement())
512 if (ariaRoleAttribute() != UnknownRole)
515 HTMLInputElement* inputElement = node->toInputElement();
519 return inputElement->isPasswordField();
522 bool AccessibilityNodeObject::isInputImage() const
524 Node* node = this->node();
528 if (roleValue() == ButtonRole && node->hasTagName(inputTag)) {
529 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
530 return input->isImageButton();
536 bool AccessibilityNodeObject::isProgressIndicator() const
538 return roleValue() == ProgressIndicatorRole;
541 bool AccessibilityNodeObject::isSlider() const
543 return roleValue() == SliderRole;
546 bool AccessibilityNodeObject::isMenuRelated() const
548 switch (roleValue()) {
559 bool AccessibilityNodeObject::isMenu() const
561 return roleValue() == MenuRole;
564 bool AccessibilityNodeObject::isMenuBar() const
566 return roleValue() == MenuBarRole;
569 bool AccessibilityNodeObject::isMenuButton() const
571 return roleValue() == MenuButtonRole;
574 bool AccessibilityNodeObject::isMenuItem() const
576 return roleValue() == MenuItemRole;
579 bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const
581 Node* node = this->node();
585 HTMLInputElement* input = node->toInputElement();
587 return input->isCheckbox() || input->isRadioButton();
592 bool AccessibilityNodeObject::isEnabled() const
594 if (equalIgnoringCase(getAttribute(aria_disabledAttr), "true"))
597 Node* node = this->node();
598 if (!node || !node->isElementNode())
601 return !toElement(node)->isDisabledFormControl();
604 bool AccessibilityNodeObject::isIndeterminate() const
606 Node* node = this->node();
610 HTMLInputElement* inputElement = node->toInputElement();
614 return inputElement->shouldAppearIndeterminate();
617 bool AccessibilityNodeObject::isPressed() const
622 Node* node = this->node();
626 // If this is an ARIA button, check the aria-pressed attribute rather than node()->active()
627 if (ariaRoleAttribute() == ButtonRole) {
628 if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true"))
633 return node->active();
636 bool AccessibilityNodeObject::isChecked() const
638 Node* node = this->node();
642 // First test for native checkedness semantics
643 HTMLInputElement* inputElement = node->toInputElement();
645 return inputElement->shouldAppearChecked();
647 // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute
648 AccessibilityRole ariaRole = ariaRoleAttribute();
649 if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole) {
650 if (equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
655 // Otherwise it's not checked
659 bool AccessibilityNodeObject::isHovered() const
661 Node* node = this->node();
665 return node->hovered();
668 bool AccessibilityNodeObject::isMultiSelectable() const
670 const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
671 if (equalIgnoringCase(ariaMultiSelectable, "true"))
673 if (equalIgnoringCase(ariaMultiSelectable, "false"))
676 return node() && node()->hasTagName(selectTag) && toHTMLSelectElement(node())->multiple();
679 bool AccessibilityNodeObject::isReadOnly() const
681 Node* node = this->node();
685 if (node->hasTagName(textareaTag))
686 return static_cast<HTMLTextAreaElement*>(node)->isReadOnly();
688 if (node->hasTagName(inputTag)) {
689 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
690 if (input->isTextField())
691 return input->isReadOnly();
694 return !node->rendererIsEditable();
697 bool AccessibilityNodeObject::isRequired() const
699 if (equalIgnoringCase(getAttribute(aria_requiredAttr), "true"))
702 Node* n = this->node();
703 if (n && (n->isElementNode() && toElement(n)->isFormControlElement()))
704 return static_cast<HTMLFormControlElement*>(n)->isRequired();
709 int AccessibilityNodeObject::headingLevel() const
711 // headings can be in block flow and non-block flow
712 Node* node = this->node();
716 if (ariaRoleAttribute() == HeadingRole)
717 return getAttribute(aria_levelAttr).toInt();
719 if (node->hasTagName(h1Tag))
722 if (node->hasTagName(h2Tag))
725 if (node->hasTagName(h3Tag))
728 if (node->hasTagName(h4Tag))
731 if (node->hasTagName(h5Tag))
734 if (node->hasTagName(h6Tag))
740 String AccessibilityNodeObject::valueDescription() const
745 return getAttribute(aria_valuetextAttr).string();
748 bool AccessibilityNodeObject::isARIARange() const
750 switch (m_ariaRole) {
751 case ProgressIndicatorRole:
761 float AccessibilityNodeObject::valueForRange() const
763 if (node() && node()->hasTagName(inputTag)) {
764 HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
765 if (input->isRangeControl())
766 return input->valueAsNumber();
772 return getAttribute(aria_valuenowAttr).toFloat();
775 float AccessibilityNodeObject::maxValueForRange() const
777 if (node() && node()->hasTagName(inputTag)) {
778 HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
779 if (input->isRangeControl())
780 return input->maximum();
786 return getAttribute(aria_valuemaxAttr).toFloat();
789 float AccessibilityNodeObject::minValueForRange() const
791 if (node() && node()->hasTagName(inputTag)) {
792 HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
793 if (input->isRangeControl())
794 return input->minimum();
800 return getAttribute(aria_valueminAttr).toFloat();
803 float AccessibilityNodeObject::stepValueForRange() const
805 return getAttribute(stepAttr).toFloat();
808 bool AccessibilityNodeObject::isHeading() const
810 return roleValue() == HeadingRole;
813 bool AccessibilityNodeObject::isLink() const
815 return roleValue() == WebCoreLinkRole;
818 bool AccessibilityNodeObject::isControl() const
820 Node* node = this->node();
824 return ((node->isElementNode() && toElement(node)->isFormControlElement())
825 || AccessibilityObject::isARIAControl(ariaRoleAttribute()));
828 bool AccessibilityNodeObject::isFieldset() const
830 Node* node = this->node();
834 return node->hasTagName(fieldsetTag);
837 bool AccessibilityNodeObject::isGroup() const
839 return roleValue() == GroupRole;
842 AccessibilityObject* AccessibilityNodeObject::selectedRadioButton()
847 AccessibilityObject::AccessibilityChildrenVector children = this->children();
849 // Find the child radio button that is selected (ie. the intValue == 1).
850 size_t size = children.size();
851 for (size_t i = 0; i < size; ++i) {
852 AccessibilityObject* object = children[i].get();
853 if (object->roleValue() == RadioButtonRole && object->checkboxOrRadioValue() == ButtonStateOn)
859 AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
864 // Find the child tab item that is selected (ie. the intValue == 1).
865 AccessibilityObject::AccessibilityChildrenVector tabs;
868 AccessibilityObject::AccessibilityChildrenVector children = this->children();
869 size_t size = tabs.size();
870 for (size_t i = 0; i < size; ++i) {
871 AccessibilityObject* object = children[i].get();
872 if (object->isTabItem() && object->isChecked())
878 AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const
880 if (isNativeCheckboxOrRadio())
881 return isChecked() ? ButtonStateOn : ButtonStateOff;
883 return AccessibilityObject::checkboxOrRadioValue();
886 Element* AccessibilityNodeObject::anchorElement() const
888 Node* node = this->node();
892 AXObjectCache* cache = axObjectCache();
894 // search up the DOM tree for an anchor element
895 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
896 for ( ; node; node = node->parentNode()) {
897 if (node->hasTagName(aTag) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor()))
898 return toElement(node);
904 Element* AccessibilityNodeObject::actionElement() const
906 Node* node = this->node();
910 if (node->hasTagName(inputTag)) {
911 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
912 if (!input->isDisabledFormControl() && (isCheckboxOrRadio() || input->isTextButton()))
914 } else if (node->hasTagName(buttonTag))
915 return toElement(node);
917 if (isFileUploadButton())
918 return toElement(node);
920 if (AccessibilityObject::isARIAInput(ariaRoleAttribute()))
921 return toElement(node);
924 return toElement(node);
926 if (node->hasTagName(selectTag))
927 return toElement(node);
929 switch (roleValue()) {
931 case PopUpButtonRole:
932 case ToggleButtonRole:
936 return toElement(node);
941 Element* elt = anchorElement();
943 elt = mouseButtonListener();
947 Element* AccessibilityNodeObject::mouseButtonListener() const
949 Node* node = this->node();
953 // check if our parent is a mouse button listener
954 while (node && !node->isElementNode())
955 node = node->parentNode();
960 // FIXME: Do the continuation search like anchorElement does
961 for (Element* element = toElement(node); element; element = element->parentElement()) {
962 if (element->getAttributeEventListener(eventNames().clickEvent) || element->getAttributeEventListener(eventNames().mousedownEvent) || element->getAttributeEventListener(eventNames().mouseupEvent))
969 bool AccessibilityNodeObject::isDescendantOfBarrenParent() const
971 for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) {
972 if (!object->canHaveChildren())
979 void AccessibilityNodeObject::alterSliderValue(bool increase)
981 if (roleValue() != SliderRole)
984 if (!getAttribute(stepAttr).isEmpty())
985 changeValueByStep(increase);
987 changeValueByPercent(increase ? 5 : -5);
990 void AccessibilityNodeObject::increment()
992 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
993 alterSliderValue(true);
996 void AccessibilityNodeObject::decrement()
998 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
999 alterSliderValue(false);
1002 void AccessibilityNodeObject::changeValueByStep(bool increase)
1004 float step = stepValueForRange();
1005 float value = valueForRange();
1007 value += increase ? step : -step;
1009 setValue(String::number(value));
1011 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true);
1014 void AccessibilityNodeObject::changeValueByPercent(float percentChange)
1016 float range = maxValueForRange() - minValueForRange();
1017 float value = valueForRange();
1019 value += range * (percentChange / 100);
1020 setValue(String::number(value));
1022 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true);
1025 bool AccessibilityNodeObject::isGenericFocusableElement() const
1027 if (!canSetFocusAttribute())
1030 // If it's a control, it's not generic.
1034 // If it has an aria role, it's not generic.
1035 if (m_ariaRole != UnknownRole)
1038 // If the content editable attribute is set on this element, that's the reason
1039 // it's focusable, and existing logic should handle this case already - so it's not a
1040 // generic focusable element.
1042 if (hasContentEditableAttributeSet())
1045 // The web area and body element are both focusable, but existing logic handles these
1046 // cases already, so we don't need to include them here.
1047 if (roleValue() == WebAreaRole)
1049 if (node() && node()->hasTagName(bodyTag))
1052 // An SVG root is focusable by default, but it's probably not interactive, so don't
1053 // include it. It can still be made accessible by giving it an ARIA role.
1054 if (roleValue() == SVGRootRole)
1060 HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const
1062 if (!element->isHTMLElement() || !toHTMLElement(element)->isLabelable())
1065 const AtomicString& id = element->getIdAttribute();
1066 if (!id.isEmpty()) {
1067 if (HTMLLabelElement* label = element->treeScope()->labelElementForId(id))
1071 for (Element* parent = element->parentElement(); parent; parent = parent->parentElement()) {
1072 if (parent->hasTagName(labelTag))
1073 return static_cast<HTMLLabelElement*>(parent);
1079 String AccessibilityNodeObject::ariaAccessibilityDescription() const
1081 String ariaLabeledBy = ariaLabeledByAttribute();
1082 if (!ariaLabeledBy.isEmpty())
1083 return ariaLabeledBy;
1085 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1086 if (!ariaLabel.isEmpty())
1092 static Element* siblingWithAriaRole(String role, Node* node)
1094 Node* parent = node->parentNode();
1098 for (Node* sibling = parent->firstChild(); sibling; sibling = sibling->nextSibling()) {
1099 if (sibling->isElementNode()) {
1100 const AtomicString& siblingAriaRole = toElement(sibling)->getAttribute(roleAttr);
1101 if (equalIgnoringCase(siblingAriaRole, role))
1102 return toElement(sibling);
1109 Element* AccessibilityNodeObject::menuElementForMenuButton() const
1111 if (ariaRoleAttribute() != MenuButtonRole)
1114 return siblingWithAriaRole("menu", node());
1117 AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const
1119 return axObjectCache()->getOrCreate(menuElementForMenuButton());
1122 Element* AccessibilityNodeObject::menuItemElementForMenu() const
1124 if (ariaRoleAttribute() != MenuRole)
1127 return siblingWithAriaRole("menuitem", node());
1130 AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
1132 Element* menuItem = menuItemElementForMenu();
1135 // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem
1136 AccessibilityObject* menuItemAX = axObjectCache()->getOrCreate(menuItem);
1137 if (menuItemAX && menuItemAX->isMenuButton())
1143 void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder)
1145 Node* node = this->node();
1149 bool isInputTag = node->hasTagName(inputTag);
1150 if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
1151 HTMLLabelElement* label = labelForElement(toElement(node));
1153 AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label);
1154 textOrder.append(AccessibilityText(label->innerText(), LabelByElementText, labelObject));
1159 AccessibilityObject* titleUIElement = this->titleUIElement();
1161 textOrder.append(AccessibilityText(String(), LabelByElementText, titleUIElement));
1164 void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
1167 String webAreaText = alternativeTextForWebArea();
1168 if (!webAreaText.isEmpty())
1169 textOrder.append(AccessibilityText(webAreaText, AlternativeText));
1173 ariaLabeledByText(textOrder);
1175 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1176 if (!ariaLabel.isEmpty())
1177 textOrder.append(AccessibilityText(ariaLabel, AlternativeText));
1179 if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
1180 // Images should use alt as long as the attribute is present, even if empty.
1181 // Otherwise, it should fallback to other methods, like the title attribute.
1182 const AtomicString& alt = getAttribute(altAttr);
1184 textOrder.append(AccessibilityText(alt, AlternativeText));
1188 Node* node = this->node();
1189 if (node && node->isElementNode() && toElement(node)->isMathMLElement())
1190 textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AlternativeText));
1194 void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) const
1196 Node* node = this->node();
1200 bool isInputTag = node->hasTagName(inputTag);
1202 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
1203 if (input->isTextButton()) {
1204 textOrder.append(AccessibilityText(input->valueWithDefault(), VisibleText));
1209 // If this node isn't rendered, there's no inner text we can extract from a select element.
1210 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1213 bool useTextUnderElement = false;
1215 switch (roleValue()) {
1216 case PopUpButtonRole:
1217 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1218 if (node->hasTagName(selectTag))
1221 case ToggleButtonRole:
1223 case ListBoxOptionRole:
1224 case MenuButtonRole:
1226 case RadioButtonRole:
1228 useTextUnderElement = true;
1234 // If it's focusable but it's not content editable or a known control type, then it will appear to
1235 // the user as a single atomic object, so we should use its text as the default title.
1236 if (isHeading() || isLink() || isGenericFocusableElement())
1237 useTextUnderElement = true;
1239 if (useTextUnderElement) {
1240 String text = textUnderElement();
1241 if (!text.isEmpty())
1242 textOrder.append(AccessibilityText(text, ChildrenText));
1246 void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) const
1248 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1249 if (!ariaHelp.isEmpty())
1250 textOrder.append(AccessibilityText(ariaHelp, HelpText));
1252 String describedBy = ariaDescribedByAttribute();
1253 if (!describedBy.isEmpty())
1254 textOrder.append(AccessibilityText(describedBy, SummaryText));
1256 // Add help type text that is derived from ancestors.
1257 for (Node* curr = node(); curr; curr = curr->parentNode()) {
1258 const AtomicString& summary = getAttribute(summaryAttr);
1259 if (!summary.isEmpty())
1260 textOrder.append(AccessibilityText(summary, SummaryText));
1262 // The title attribute should be used as help text unless it is already being used as descriptive text.
1263 const AtomicString& title = getAttribute(titleAttr);
1264 if (!title.isEmpty())
1265 textOrder.append(AccessibilityText(title, TitleTagText));
1267 // Only take help text from an ancestor element if its a group or an unknown role. If help was
1268 // added to those kinds of elements, it is likely it was meant for a child element.
1269 AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr);
1273 AccessibilityRole role = axObj->roleValue();
1274 if (role != GroupRole && role != UnknownRole)
1279 void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textOrder)
1281 titleElementText(textOrder);
1282 alternativeText(textOrder);
1283 visibleText(textOrder);
1284 helpText(textOrder);
1286 String placeholder = placeholderValue();
1287 if (!placeholder.isEmpty())
1288 textOrder.append(AccessibilityText(placeholder, PlaceholderText));
1291 void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
1293 String ariaLabeledBy = ariaLabeledByAttribute();
1294 if (!ariaLabeledBy.isEmpty()) {
1295 Vector<Element*> elements;
1296 ariaLabeledByElements(elements);
1298 Vector<RefPtr<AccessibilityObject> > axElements;
1299 unsigned length = elements.size();
1300 for (unsigned k = 0; k < length; k++) {
1301 RefPtr<AccessibilityObject> axElement = axObjectCache()->getOrCreate(elements[k]);
1302 axElements.append(axElement);
1305 textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, axElements));
1309 String AccessibilityNodeObject::alternativeTextForWebArea() const
1311 // The WebArea description should follow this order:
1312 // aria-label on the <html>
1313 // title on the <html>
1314 // <title> inside the <head> (of it was set through JS)
1315 // name on the <html>
1317 // aria-label on the <iframe>
1318 // title on the <iframe>
1319 // name on the <iframe>
1321 Document* document = this->document();
1325 // Check if the HTML element has an aria-label for the webpage.
1326 if (Element* documentElement = document->documentElement()) {
1327 const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr);
1328 if (!ariaLabel.isEmpty())
1332 Node* owner = document->ownerElement();
1334 if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
1335 const AtomicString& title = static_cast<HTMLFrameElementBase*>(owner)->getAttribute(titleAttr);
1336 if (!title.isEmpty())
1338 return static_cast<HTMLFrameElementBase*>(owner)->getNameAttribute();
1340 if (owner->isHTMLElement())
1341 return toHTMLElement(owner)->getNameAttribute();
1344 String documentTitle = document->title();
1345 if (!documentTitle.isEmpty())
1346 return documentTitle;
1348 owner = document->body();
1349 if (owner && owner->isHTMLElement())
1350 return toHTMLElement(owner)->getNameAttribute();
1355 String AccessibilityNodeObject::accessibilityDescription() const
1357 // Static text should not have a description, it should only have a stringValue.
1358 if (roleValue() == StaticTextRole)
1361 String ariaDescription = ariaAccessibilityDescription();
1362 if (!ariaDescription.isEmpty())
1363 return ariaDescription;
1365 if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
1366 // Images should use alt as long as the attribute is present, even if empty.
1367 // Otherwise, it should fallback to other methods, like the title attribute.
1368 const AtomicString& alt = getAttribute(altAttr);
1374 Node* node = this->node();
1375 if (node && node->isElementNode() && toElement(node)->isMathMLElement())
1376 return getAttribute(MathMLNames::alttextAttr);
1379 // An element's descriptive text is comprised of title() (what's visible on the screen) and accessibilityDescription() (other descriptive text).
1380 // Both are used to generate what a screen reader speaks.
1381 // If this point is reached (i.e. there's no accessibilityDescription) and there's no title(), we should fallback to using the title attribute.
1382 // 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).
1383 if (title().isEmpty())
1384 return getAttribute(titleAttr);
1389 String AccessibilityNodeObject::helpText() const
1391 Node* node = this->node();
1395 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1396 if (!ariaHelp.isEmpty())
1399 String describedBy = ariaDescribedByAttribute();
1400 if (!describedBy.isEmpty())
1403 String description = accessibilityDescription();
1404 for (Node* curr = node; curr; curr = curr->parentNode()) {
1405 if (curr->isHTMLElement()) {
1406 const AtomicString& summary = toElement(curr)->getAttribute(summaryAttr);
1407 if (!summary.isEmpty())
1410 // The title attribute should be used as help text unless it is already being used as descriptive text.
1411 const AtomicString& title = toElement(curr)->getAttribute(titleAttr);
1412 if (!title.isEmpty() && description != title)
1416 // Only take help text from an ancestor element if its a group or an unknown role. If help was
1417 // added to those kinds of elements, it is likely it was meant for a child element.
1418 AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr);
1420 AccessibilityRole role = axObj->roleValue();
1421 if (role != GroupRole && role != UnknownRole)
1429 unsigned AccessibilityNodeObject::hierarchicalLevel() const
1431 Node* node = this->node();
1432 if (!node || !node->isElementNode())
1434 Element* element = toElement(node);
1435 String ariaLevel = element->getAttribute(aria_levelAttr);
1436 if (!ariaLevel.isEmpty())
1437 return ariaLevel.toInt();
1439 // Only tree item will calculate its level through the DOM currently.
1440 if (roleValue() != TreeItemRole)
1443 // Hierarchy leveling starts at 1, to match the aria-level spec.
1444 // We measure tree hierarchy by the number of groups that the item is within.
1446 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
1447 AccessibilityRole parentRole = parent->roleValue();
1448 if (parentRole == GroupRole)
1450 else if (parentRole == TreeRole)
1457 // When building the textUnderElement for an object, determine whether or not
1458 // we should include the inner text of this given descendant object or skip it.
1459 static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj)
1461 // Consider this hypothetical example:
1464 // Table of contents
1466 // <a href="#start">Jump to start of book</a>
1468 // <li><a href="#1">Chapter 1</a></li>
1469 // <li><a href="#1">Chapter 2</a></li>
1473 // The goal is to return a reasonable title for the outer container div, because
1474 // it's focusable - but without making its title be the full inner text, which is
1475 // quite long. As a heuristic, skip links, controls, and elements that are usually
1476 // containers with lots of children.
1478 // Skip focusable children, so we don't include the text of links and controls.
1479 if (obj->canSetFocusAttribute())
1482 // Skip big container elements like lists, tables, etc.
1483 if (obj->isList() || obj->isAccessibilityTable() || obj->isTree() || obj->isCanvas())
1489 String AccessibilityNodeObject::textUnderElement() const
1491 Node* node = this->node();
1492 if (node && node->isTextNode())
1493 return toText(node)->wholeText();
1496 for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
1497 if (!shouldUseAccessiblityObjectInnerText(child))
1500 if (child->isAccessibilityNodeObject()) {
1501 Vector<AccessibilityText> textOrder;
1502 toAccessibilityNodeObject(child)->alternativeText(textOrder);
1503 if (textOrder.size() > 0) {
1504 result.append(textOrder[0].text);
1509 result.append(child->textUnderElement());
1515 String AccessibilityNodeObject::title() const
1517 Node* node = this->node();
1521 bool isInputTag = node->hasTagName(inputTag);
1523 HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
1524 if (input->isTextButton())
1525 return input->valueWithDefault();
1528 if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
1529 HTMLLabelElement* label = labelForElement(toElement(node));
1530 if (label && !exposesTitleUIElement())
1531 return label->innerText();
1534 // If this node isn't rendered, there's no inner text we can extract from a select element.
1535 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1538 switch (roleValue()) {
1539 case PopUpButtonRole:
1540 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1541 if (node->hasTagName(selectTag))
1544 case ToggleButtonRole:
1546 case ListBoxOptionRole:
1547 case MenuButtonRole:
1549 case RadioButtonRole:
1551 return textUnderElement();
1552 // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>.
1559 if (isHeading() || isLink())
1560 return textUnderElement();
1562 // If it's focusable but it's not content editable or a known control type, then it will appear to
1563 // the user as a single atomic object, so we should use its text as the default title.
1564 if (isGenericFocusableElement())
1565 return textUnderElement();
1570 String AccessibilityNodeObject::text() const
1572 // If this is a user defined static text, use the accessible name computation.
1573 if (ariaRoleAttribute() == StaticTextRole)
1574 return ariaAccessibilityDescription();
1576 if (!isTextControl())
1579 Node* node = this->node();
1583 if (isNativeTextControl()) {
1584 if (node->hasTagName(textareaTag))
1585 return static_cast<HTMLTextAreaElement*>(node)->value();
1586 if (node->hasTagName(inputTag))
1587 return node->toInputElement()->value();
1590 if (!node->isElementNode())
1593 return toElement(node)->innerText();
1596 String AccessibilityNodeObject::stringValue() const
1598 Node* node = this->node();
1602 if (ariaRoleAttribute() == StaticTextRole) {
1603 String staticText = text();
1604 if (!staticText.length())
1605 staticText = textUnderElement();
1609 if (node->isTextNode())
1610 return textUnderElement();
1612 if (node->hasTagName(selectTag)) {
1613 HTMLSelectElement* selectElement = toHTMLSelectElement(node);
1614 int selectedIndex = selectElement->selectedIndex();
1615 const Vector<HTMLElement*> listItems = selectElement->listItems();
1616 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
1617 const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr);
1618 if (!overriddenDescription.isNull())
1619 return overriddenDescription;
1621 if (!selectElement->multiple())
1622 return selectElement->value();
1626 if (isTextControl())
1629 // FIXME: We might need to implement a value here for more types
1630 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
1631 // this would require subclassing or making accessibilityAttributeNames do something other than return a
1632 // single static array.
1636 void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const
1645 if (!node() || !node()->hasTagName(inputTag))
1648 HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
1649 const AtomicString& type = input->getAttribute(typeAttr);
1650 if (!equalIgnoringCase(type, "color"))
1653 // HTMLInputElement::value always returns a string parseable by Color().
1654 Color color(input->value());
1660 // This function implements the ARIA accessible name as described by the Mozilla
1661 // ARIA Implementer's Guide.
1662 static String accessibleNameForNode(Node* node)
1664 if (node->isTextNode())
1665 return toText(node)->data();
1667 if (node->hasTagName(inputTag))
1668 return static_cast<HTMLInputElement*>(node)->value();
1670 if (node->isHTMLElement()) {
1671 const AtomicString& alt = toHTMLElement(node)->getAttribute(altAttr);
1679 String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
1681 StringBuilder builder;
1682 unsigned size = elements.size();
1683 for (unsigned i = 0; i < size; ++i) {
1684 Element* idElement = elements[i];
1686 builder.append(accessibleNameForNode(idElement));
1687 for (Node* n = idElement->firstChild(); n; n = NodeTraversal::next(n, idElement))
1688 builder.append(accessibleNameForNode(n));
1691 builder.append(' ');
1693 return builder.toString();
1696 String AccessibilityNodeObject::ariaDescribedByAttribute() const
1698 Vector<Element*> elements;
1699 elementsFromAttribute(elements, aria_describedbyAttr);
1701 return accessibilityDescriptionForElements(elements);
1704 void AccessibilityNodeObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const
1706 Node* node = this->node();
1707 if (!node || !node->isElementNode())
1710 TreeScope* scope = node->treeScope();
1714 String idList = getAttribute(attribute).string();
1715 if (idList.isEmpty())
1718 idList.replace('\n', ' ');
1719 Vector<String> idVector;
1720 idList.split(' ', idVector);
1722 unsigned size = idVector.size();
1723 for (unsigned i = 0; i < size; ++i) {
1724 AtomicString idName(idVector[i]);
1725 Element* idElement = scope->getElementById(idName);
1727 elements.append(idElement);
1732 void AccessibilityNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const
1734 elementsFromAttribute(elements, aria_labeledbyAttr);
1735 if (!elements.size())
1736 elementsFromAttribute(elements, aria_labelledbyAttr);
1740 String AccessibilityNodeObject::ariaLabeledByAttribute() const
1742 Vector<Element*> elements;
1743 ariaLabeledByElements(elements);
1745 return accessibilityDescriptionForElements(elements);
1748 bool AccessibilityNodeObject::canSetFocusAttribute() const
1750 Node* node = this->node();
1757 // NOTE: It would be more accurate to ask the document whether setFocusedNode() would
1758 // do anything. For example, setFocusedNode() will do nothing if the current focused
1759 // node will not relinquish the focus.
1763 if (isDisabledFormControl(node))
1766 return node->supportsFocus();
1769 AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const
1771 const AtomicString& ariaRole = getAttribute(roleAttr);
1772 if (ariaRole.isNull() || ariaRole.isEmpty())
1775 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
1777 // ARIA states if an item can get focus, it should not be presentational.
1778 if (role == PresentationalRole && canSetFocusAttribute())
1781 if (role == ButtonRole)
1782 role = buttonRoleType();
1784 if (role == TextAreaRole && !ariaIsMultiline())
1785 role = TextFieldRole;
1787 role = remapAriaRoleDueToParent(role);
1795 AccessibilityRole AccessibilityNodeObject::ariaRoleAttribute() const
1800 AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
1802 // Some objects change their role based on their parent.
1803 // However, asking for the unignoredParent calls accessibilityIsIgnored(), which can trigger a loop.
1804 // While inside the call stack of creating an element, we need to avoid accessibilityIsIgnored().
1805 // https://bugs.webkit.org/show_bug.cgi?id=65174
1807 if (role != ListBoxOptionRole && role != MenuItemRole)
1810 for (AccessibilityObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
1811 AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
1813 // Selects and listboxes both have options as child roles, but they map to different roles within WebCore.
1814 if (role == ListBoxOptionRole && parentAriaRole == MenuRole)
1815 return MenuItemRole;
1816 // An aria "menuitem" may map to MenuButton or MenuItem depending on its parent.
1817 if (role == MenuItemRole && parentAriaRole == GroupRole)
1818 return MenuButtonRole;
1820 // If the parent had a different role, then we don't need to continue searching up the chain.
1828 // If you call node->rendererIsEditable() since that will return true if an ancestor is editable.
1829 // This only returns true if this is the element that actually has the contentEditable attribute set.
1830 bool AccessibilityNodeObject::hasContentEditableAttributeSet() const
1832 if (!hasAttribute(contenteditableAttr))
1834 const AtomicString& contentEditableValue = getAttribute(contenteditableAttr);
1835 // Both "true" (case-insensitive) and the empty string count as true.
1836 return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableValue, "true");
1839 } // namespace WebCore