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 "ElementIterator.h"
38 #include "EventNames.h"
39 #include "FloatRect.h"
41 #include "FrameLoader.h"
42 #include "FrameSelection.h"
43 #include "FrameView.h"
44 #include "HTMLAreaElement.h"
45 #include "HTMLFieldSetElement.h"
46 #include "HTMLFormElement.h"
47 #include "HTMLFrameElementBase.h"
48 #include "HTMLImageElement.h"
49 #include "HTMLInputElement.h"
50 #include "HTMLLabelElement.h"
51 #include "HTMLLegendElement.h"
52 #include "HTMLMapElement.h"
53 #include "HTMLNames.h"
54 #include "HTMLOptGroupElement.h"
55 #include "HTMLOptionElement.h"
56 #include "HTMLOptionsCollection.h"
57 #include "HTMLParserIdioms.h"
58 #include "HTMLPlugInImageElement.h"
59 #include "HTMLSelectElement.h"
60 #include "HTMLTextAreaElement.h"
61 #include "HTMLTextFormControlElement.h"
62 #include "HitTestRequest.h"
63 #include "HitTestResult.h"
64 #include "LabelableElement.h"
65 #include "LocalizedStrings.h"
66 #include "MathMLNames.h"
68 #include "NodeTraversal.h"
70 #include "ProgressTracker.h"
71 #include "SVGElement.h"
74 #include "TextControlInnerElements.h"
75 #include "TextIterator.h"
76 #include "UserGestureIndicator.h"
77 #include "VisibleUnits.h"
79 #include "htmlediting.h"
80 #include <wtf/StdLibExtras.h>
81 #include <wtf/text/StringBuilder.h>
82 #include <wtf/unicode/CharacterNames.h>
88 using namespace HTMLNames;
90 AccessibilityNodeObject::AccessibilityNodeObject(Node* node)
91 : AccessibilityObject()
92 , m_ariaRole(UnknownRole)
93 , m_childrenDirty(false)
94 , m_roleForMSAA(UnknownRole)
96 , m_initialized(false)
102 AccessibilityNodeObject::~AccessibilityNodeObject()
104 ASSERT(isDetached());
107 void AccessibilityNodeObject::init()
110 ASSERT(!m_initialized);
111 m_initialized = true;
113 m_role = determineAccessibilityRole();
116 PassRefPtr<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node)
118 return adoptRef(new AccessibilityNodeObject(node));
121 void AccessibilityNodeObject::detach()
124 AccessibilityObject::detach();
128 void AccessibilityNodeObject::childrenChanged()
130 // This method is meant as a quick way of marking a portion of the accessibility tree dirty.
131 if (!node() && !renderer())
134 axObjectCache()->postNotification(this, document(), AXObjectCache::AXChildrenChanged, true);
136 // Go up the accessibility parent chain, but only if the element already exists. This method is
137 // called during render layouts, minimal work should be done.
138 // If AX elements are created now, they could interrogate the render tree while it's in a funky state.
139 // At the same time, process ARIA live region changes.
140 for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) {
141 parent->setNeedsToUpdateChildren();
143 // These notifications always need to be sent because screenreaders are reliant on them to perform.
144 // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update.
146 // If this element supports ARIA live regions, then notify the AT of changes.
147 if (parent->supportsARIALiveRegion())
148 axObjectCache()->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged, true);
150 // If this element is an ARIA text control, notify the AT of changes.
151 if (parent->isARIATextControl() && !parent->isNativeTextControl() && !parent->node()->rendererIsEditable())
152 axObjectCache()->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged, true);
156 void AccessibilityNodeObject::updateAccessibilityRole()
158 bool ignoredStatus = accessibilityIsIgnored();
159 m_role = determineAccessibilityRole();
161 // The AX hierarchy only needs to be updated if the ignored status of an element has changed.
162 if (ignoredStatus != accessibilityIsIgnored())
166 AccessibilityObject* AccessibilityNodeObject::firstChild() const
171 Node* firstChild = node()->firstChild();
176 return axObjectCache()->getOrCreate(firstChild);
179 AccessibilityObject* AccessibilityNodeObject::lastChild() const
184 Node* lastChild = node()->lastChild();
188 return axObjectCache()->getOrCreate(lastChild);
191 AccessibilityObject* AccessibilityNodeObject::previousSibling() const
196 Node* previousSibling = node()->previousSibling();
197 if (!previousSibling)
200 return axObjectCache()->getOrCreate(previousSibling);
203 AccessibilityObject* AccessibilityNodeObject::nextSibling() const
208 Node* nextSibling = node()->nextSibling();
212 return axObjectCache()->getOrCreate(nextSibling);
215 AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const
217 return parentObject();
220 AccessibilityObject* AccessibilityNodeObject::parentObject() const
225 Node* parentObj = node()->parentNode();
227 return axObjectCache()->getOrCreate(parentObj);
232 LayoutRect AccessibilityNodeObject::elementRect() const
234 return boundingBoxRect();
237 LayoutRect AccessibilityNodeObject::boundingBoxRect() const
239 // AccessibilityNodeObjects have no mechanism yet to return a size or position.
240 // For now, let's return the position of the ancestor that does have a position,
241 // 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.
243 LayoutRect boundingBox;
245 for (AccessibilityObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) {
246 if (positionProvider->isAccessibilityRenderObject()) {
247 LayoutRect parentRect = positionProvider->elementRect();
248 boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat()))));
249 boundingBox.setLocation(parentRect.location());
257 void AccessibilityNodeObject::setNode(Node* node)
262 Document* AccessibilityNodeObject::document() const
266 return &node()->document();
269 AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole()
274 m_ariaRole = determineAriaRoleAttribute();
276 AccessibilityRole ariaRole = ariaRoleAttribute();
277 if (ariaRole != UnknownRole)
280 if (node()->isLink())
281 return WebCoreLinkRole;
282 if (node()->isTextNode())
283 return StaticTextRole;
284 if (node()->hasTagName(buttonTag))
285 return buttonRoleType();
286 if (isHTMLInputElement(node())) {
287 HTMLInputElement* input = toHTMLInputElement(node());
288 if (input->isCheckbox())
290 if (input->isRadioButton())
291 return RadioButtonRole;
292 if (input->isTextButton())
293 return buttonRoleType();
294 if (input->isRangeControl())
297 #if ENABLE(INPUT_TYPE_COLOR)
298 const AtomicString& type = input->getAttribute(typeAttr);
299 if (equalIgnoringCase(type, "color"))
300 return ColorWellRole;
303 return TextFieldRole;
305 if (node()->hasTagName(selectTag)) {
306 HTMLSelectElement* selectElement = toHTMLSelectElement(node());
307 return selectElement->multiple() ? ListBoxRole : PopUpButtonRole;
309 if (isHTMLTextAreaElement(node()))
313 if (node()->hasTagName(divTag))
315 if (node()->hasTagName(pTag))
316 return ParagraphRole;
317 if (isHTMLLabelElement(node()))
319 if (node()->isElementNode() && toElement(node())->isFocusable())
325 void AccessibilityNodeObject::insertChild(AccessibilityObject* child, unsigned index)
330 // If the parent is asking for this child's children, then either it's the first time (and clearing is a no-op),
331 // or its visibility has changed. In the latter case, this child may have a stale child cached.
332 // This can prevent aria-hidden changes from working correctly. Hence, whenever a parent is getting children, ensure data is not stale.
333 child->clearChildren();
335 if (child->accessibilityIsIgnored()) {
336 AccessibilityChildrenVector children = child->children();
337 size_t length = children.size();
338 for (size_t i = 0; i < length; ++i)
339 m_children.insert(index + i, children[i]);
341 ASSERT(child->parentObject() == this);
342 m_children.insert(index, child);
346 void AccessibilityNodeObject::addChild(AccessibilityObject* child)
348 insertChild(child, m_children.size());
351 void AccessibilityNodeObject::addChildren()
353 // If the need to add more children in addition to existing children arises,
354 // childrenChanged should have been called, leaving the object with no children.
355 ASSERT(!m_haveChildren);
360 m_haveChildren = true;
362 // The only time we add children from the DOM tree to a node with a renderer is when it's a canvas.
363 if (renderer() && !m_node->hasTagName(canvasTag))
366 for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
367 addChild(axObjectCache()->getOrCreate(child));
370 bool AccessibilityNodeObject::canHaveChildren() const
372 // If this is an AccessibilityRenderObject, then it's okay if this object
373 // doesn't have a node - there are some renderers that don't have associated
374 // nodes, like scroll areas and css-generated text.
375 if (!node() && !isAccessibilityRenderObject())
378 // When <noscript> is not being used (its renderer() == 0), ignore its children.
379 if (node() && !renderer() && node()->hasTagName(noscriptTag))
382 // Elements that should not have children
383 switch (roleValue()) {
386 case PopUpButtonRole:
388 case RadioButtonRole:
390 case ToggleButtonRole:
392 case ListBoxOptionRole:
394 case ProgressIndicatorRole:
401 bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const
404 // Double-check that an AccessibilityObject is never accessed before
405 // it's been initialized.
406 ASSERT(m_initialized);
409 // If this element is within a parent that cannot have children, it should not be exposed.
410 if (isDescendantOfBarrenParent())
413 return m_role == UnknownRole;
416 bool AccessibilityNodeObject::canvasHasFallbackContent() const
418 Node* node = this->node();
419 if (!node || !node->hasTagName(canvasTag))
421 Element* canvasElement = toElement(node);
422 // If it has any children that are elements, we'll assume it might be fallback
423 // content. If it has no children or its only children are not elements
424 // (e.g. just text nodes), it doesn't have fallback content.
425 return elementChildren(canvasElement).begin() != elementChildren(canvasElement).end();
428 bool AccessibilityNodeObject::isImageButton() const
430 return isNativeImage() && isButton();
433 bool AccessibilityNodeObject::isAnchor() const
435 return !isNativeImage() && isLink();
438 bool AccessibilityNodeObject::isNativeTextControl() const
440 Node* node = this->node();
444 if (isHTMLTextAreaElement(node))
447 if (isHTMLInputElement(node)) {
448 HTMLInputElement* input = toHTMLInputElement(node);
449 return input->isText() || input->isNumberField();
455 bool AccessibilityNodeObject::isSearchField() const
457 Node* node = this->node();
461 HTMLInputElement* inputElement = node->toInputElement();
465 if (inputElement->isSearchField())
468 // Some websites don't label their search fields as such. However, they will
469 // use the word "search" in either the form or input type. This won't catch every case,
470 // but it will catch google.com for example.
472 // Check the node name of the input type, sometimes it's "search".
473 const AtomicString& nameAttribute = getAttribute(nameAttr);
474 if (nameAttribute.contains("search", false))
477 // Check the form action and the name, which will sometimes be "search".
478 HTMLFormElement* form = inputElement->form();
479 if (form && (form->name().contains("search", false) || form->action().contains("search", false)))
485 bool AccessibilityNodeObject::isNativeImage() const
487 Node* node = this->node();
491 if (isHTMLImageElement(node))
494 if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag))
497 if (isHTMLInputElement(node)) {
498 HTMLInputElement* input = toHTMLInputElement(node);
499 return input->isImageButton();
505 bool AccessibilityNodeObject::isImage() const
507 return roleValue() == ImageRole;
510 bool AccessibilityNodeObject::isPasswordField() const
512 Node* node = this->node();
513 if (!node || !node->isHTMLElement())
516 if (ariaRoleAttribute() != UnknownRole)
519 HTMLInputElement* inputElement = node->toInputElement();
523 return inputElement->isPasswordField();
526 bool AccessibilityNodeObject::isInputImage() const
528 Node* node = this->node();
532 if (roleValue() == ButtonRole && isHTMLInputElement(node)) {
533 HTMLInputElement* input = toHTMLInputElement(node);
534 return input->isImageButton();
540 bool AccessibilityNodeObject::isProgressIndicator() const
542 return roleValue() == ProgressIndicatorRole;
545 bool AccessibilityNodeObject::isSlider() const
547 return roleValue() == SliderRole;
550 bool AccessibilityNodeObject::isMenuRelated() const
552 switch (roleValue()) {
563 bool AccessibilityNodeObject::isMenu() const
565 return roleValue() == MenuRole;
568 bool AccessibilityNodeObject::isMenuBar() const
570 return roleValue() == MenuBarRole;
573 bool AccessibilityNodeObject::isMenuButton() const
575 return roleValue() == MenuButtonRole;
578 bool AccessibilityNodeObject::isMenuItem() const
580 return roleValue() == MenuItemRole;
583 bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const
585 Node* node = this->node();
589 HTMLInputElement* input = node->toInputElement();
591 return input->isCheckbox() || input->isRadioButton();
596 bool AccessibilityNodeObject::isEnabled() const
598 if (equalIgnoringCase(getAttribute(aria_disabledAttr), "true"))
601 Node* node = this->node();
602 if (!node || !node->isElementNode())
605 return !toElement(node)->isDisabledFormControl();
608 bool AccessibilityNodeObject::isIndeterminate() const
610 Node* node = this->node();
614 HTMLInputElement* inputElement = node->toInputElement();
618 return inputElement->shouldAppearIndeterminate();
621 bool AccessibilityNodeObject::isPressed() const
626 Node* node = this->node();
630 // If this is an ARIA button, check the aria-pressed attribute rather than node()->active()
631 if (ariaRoleAttribute() == ButtonRole) {
632 if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true"))
637 if (!node->isElementNode())
639 return toElement(node)->active();
642 bool AccessibilityNodeObject::isChecked() const
644 Node* node = this->node();
648 // First test for native checkedness semantics
649 HTMLInputElement* inputElement = node->toInputElement();
651 return inputElement->shouldAppearChecked();
653 // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute
654 bool validRole = false;
655 switch (ariaRoleAttribute()) {
656 case RadioButtonRole:
665 if (validRole && equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
671 bool AccessibilityNodeObject::isHovered() const
673 Node* node = this->node();
677 return node->isElementNode() && toElement(node)->hovered();
680 bool AccessibilityNodeObject::isMultiSelectable() const
682 const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
683 if (equalIgnoringCase(ariaMultiSelectable, "true"))
685 if (equalIgnoringCase(ariaMultiSelectable, "false"))
688 return node() && node()->hasTagName(selectTag) && toHTMLSelectElement(node())->multiple();
691 bool AccessibilityNodeObject::isReadOnly() const
693 Node* node = this->node();
697 if (isHTMLTextAreaElement(node))
698 return toHTMLFormControlElement(node)->isReadOnly();
700 if (isHTMLInputElement(node)) {
701 HTMLInputElement* input = toHTMLInputElement(node);
702 if (input->isTextField())
703 return input->isReadOnly();
706 return !node->rendererIsEditable();
709 bool AccessibilityNodeObject::isRequired() const
711 if (equalIgnoringCase(getAttribute(aria_requiredAttr), "true"))
714 Node* n = this->node();
715 if (n && (n->isElementNode() && toElement(n)->isFormControlElement()))
716 return static_cast<HTMLFormControlElement*>(n)->isRequired();
721 bool AccessibilityNodeObject::supportsRequiredAttribute() const
723 switch (roleValue()) {
728 case IncrementorRole:
730 case PopUpButtonRole:
731 case RadioButtonRole:
736 case TableHeaderContainerRole:
739 case ToggleButtonRole:
746 int AccessibilityNodeObject::headingLevel() const
748 // headings can be in block flow and non-block flow
749 Node* node = this->node();
754 int ariaLevel = getAttribute(aria_levelAttr).toInt();
759 if (node->hasTagName(h1Tag))
762 if (node->hasTagName(h2Tag))
765 if (node->hasTagName(h3Tag))
768 if (node->hasTagName(h4Tag))
771 if (node->hasTagName(h5Tag))
774 if (node->hasTagName(h6Tag))
780 String AccessibilityNodeObject::valueDescription() const
782 if (!isRangeControl())
785 return getAttribute(aria_valuetextAttr).string();
788 float AccessibilityNodeObject::valueForRange() const
790 if (node() && isHTMLInputElement(node())) {
791 HTMLInputElement* input = toHTMLInputElement(node());
792 if (input->isRangeControl())
793 return input->valueAsNumber();
796 if (!isRangeControl())
799 return getAttribute(aria_valuenowAttr).toFloat();
802 float AccessibilityNodeObject::maxValueForRange() const
804 if (node() && isHTMLInputElement(node())) {
805 HTMLInputElement* input = toHTMLInputElement(node());
806 if (input->isRangeControl())
807 return input->maximum();
810 if (!isRangeControl())
813 return getAttribute(aria_valuemaxAttr).toFloat();
816 float AccessibilityNodeObject::minValueForRange() const
818 if (node() && isHTMLInputElement(node())) {
819 HTMLInputElement* input = toHTMLInputElement(node());
820 if (input->isRangeControl())
821 return input->minimum();
824 if (!isRangeControl())
827 return getAttribute(aria_valueminAttr).toFloat();
830 float AccessibilityNodeObject::stepValueForRange() const
832 return getAttribute(stepAttr).toFloat();
835 bool AccessibilityNodeObject::isHeading() const
837 return roleValue() == HeadingRole;
840 bool AccessibilityNodeObject::isLink() const
842 return roleValue() == WebCoreLinkRole;
845 bool AccessibilityNodeObject::isControl() const
847 Node* node = this->node();
851 return ((node->isElementNode() && toElement(node)->isFormControlElement())
852 || AccessibilityObject::isARIAControl(ariaRoleAttribute()));
855 bool AccessibilityNodeObject::isFieldset() const
857 Node* node = this->node();
861 return node->hasTagName(fieldsetTag);
864 bool AccessibilityNodeObject::isGroup() const
866 return roleValue() == GroupRole;
869 AccessibilityObject* AccessibilityNodeObject::selectedRadioButton()
874 AccessibilityObject::AccessibilityChildrenVector children = this->children();
876 // Find the child radio button that is selected (ie. the intValue == 1).
877 size_t size = children.size();
878 for (size_t i = 0; i < size; ++i) {
879 AccessibilityObject* object = children[i].get();
880 if (object->roleValue() == RadioButtonRole && object->checkboxOrRadioValue() == ButtonStateOn)
886 AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
891 // Find the child tab item that is selected (ie. the intValue == 1).
892 AccessibilityObject::AccessibilityChildrenVector tabs;
895 AccessibilityObject::AccessibilityChildrenVector children = this->children();
896 size_t size = tabs.size();
897 for (size_t i = 0; i < size; ++i) {
898 AccessibilityObject* object = children[i].get();
899 if (object->isTabItem() && object->isChecked())
905 AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const
907 if (isNativeCheckboxOrRadio())
908 return isChecked() ? ButtonStateOn : ButtonStateOff;
910 return AccessibilityObject::checkboxOrRadioValue();
913 Element* AccessibilityNodeObject::anchorElement() const
915 Node* node = this->node();
919 AXObjectCache* cache = axObjectCache();
921 // search up the DOM tree for an anchor element
922 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
923 for ( ; node; node = node->parentNode()) {
924 if (isHTMLAnchorElement(node) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor()))
925 return toElement(node);
931 Element* AccessibilityNodeObject::actionElement() const
933 Node* node = this->node();
937 if (isHTMLInputElement(node)) {
938 HTMLInputElement* input = toHTMLInputElement(node);
939 if (!input->isDisabledFormControl() && (isCheckboxOrRadio() || input->isTextButton()))
941 } else if (node->hasTagName(buttonTag))
942 return toElement(node);
944 if (isFileUploadButton())
945 return toElement(node);
947 if (AccessibilityObject::isARIAInput(ariaRoleAttribute()))
948 return toElement(node);
951 return toElement(node);
953 if (node->hasTagName(selectTag))
954 return toElement(node);
956 switch (roleValue()) {
958 case PopUpButtonRole:
959 case ToggleButtonRole:
963 return toElement(node);
968 Element* elt = anchorElement();
970 elt = mouseButtonListener();
974 Element* AccessibilityNodeObject::mouseButtonListener() const
976 Node* node = this->node();
980 // check if our parent is a mouse button listener
981 // FIXME: Do the continuation search like anchorElement does
982 auto lineage = elementLineage(node->isElementNode() ? toElement(node) : node->parentElement());
983 for (auto element = lineage.begin(), end = lineage.end(); element != end; ++element) {
984 // If we've reached the body and this is not a control element, do not expose press action for this element.
985 // It can cause false positives, where every piece of text is labeled as accepting press actions.
986 if (element->hasTagName(bodyTag) && isStaticText())
989 if (element->hasEventListeners(eventNames().clickEvent) || element->hasEventListeners(eventNames().mousedownEvent) || element->hasEventListeners(eventNames().mouseupEvent))
996 bool AccessibilityNodeObject::isDescendantOfBarrenParent() const
998 for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) {
999 if (!object->canHaveChildren())
1006 void AccessibilityNodeObject::alterSliderValue(bool increase)
1008 if (roleValue() != SliderRole)
1011 if (!getAttribute(stepAttr).isEmpty())
1012 changeValueByStep(increase);
1014 changeValueByPercent(increase ? 5 : -5);
1017 void AccessibilityNodeObject::increment()
1019 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
1020 alterSliderValue(true);
1023 void AccessibilityNodeObject::decrement()
1025 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
1026 alterSliderValue(false);
1029 void AccessibilityNodeObject::changeValueByStep(bool increase)
1031 float step = stepValueForRange();
1032 float value = valueForRange();
1034 value += increase ? step : -step;
1036 setValue(String::number(value));
1038 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true);
1041 void AccessibilityNodeObject::changeValueByPercent(float percentChange)
1043 float range = maxValueForRange() - minValueForRange();
1044 float value = valueForRange();
1046 value += range * (percentChange / 100);
1047 setValue(String::number(value));
1049 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true);
1052 bool AccessibilityNodeObject::isGenericFocusableElement() const
1054 if (!canSetFocusAttribute())
1057 // If it's a control, it's not generic.
1061 // If it has an aria role, it's not generic.
1062 if (m_ariaRole != UnknownRole)
1065 // If the content editable attribute is set on this element, that's the reason
1066 // it's focusable, and existing logic should handle this case already - so it's not a
1067 // generic focusable element.
1069 if (hasContentEditableAttributeSet())
1072 // The web area and body element are both focusable, but existing logic handles these
1073 // cases already, so we don't need to include them here.
1074 if (roleValue() == WebAreaRole)
1076 if (node() && node()->hasTagName(bodyTag))
1079 // An SVG root is focusable by default, but it's probably not interactive, so don't
1080 // include it. It can still be made accessible by giving it an ARIA role.
1081 if (roleValue() == SVGRootRole)
1087 HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const
1089 if (!element->isHTMLElement() || !toHTMLElement(element)->isLabelable())
1092 const AtomicString& id = element->getIdAttribute();
1093 if (!id.isEmpty()) {
1094 if (HTMLLabelElement* label = element->treeScope()->labelElementForId(id))
1098 auto labelAncestors = ancestorsOfType<HTMLLabelElement>(element);
1099 auto enclosingLabel = labelAncestors.begin();
1100 return enclosingLabel != labelAncestors.end() ? &*enclosingLabel : nullptr;
1103 String AccessibilityNodeObject::ariaAccessibilityDescription() const
1105 String ariaLabeledBy = ariaLabeledByAttribute();
1106 if (!ariaLabeledBy.isEmpty())
1107 return ariaLabeledBy;
1109 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1110 if (!ariaLabel.isEmpty())
1116 static Element* siblingWithAriaRole(String role, Node* node)
1118 ContainerNode* parent = node->parentNode();
1121 for (auto sibling = elementChildren(parent).begin(), end = elementChildren(parent).end(); sibling != end; ++sibling) {
1122 const AtomicString& siblingAriaRole = sibling->fastGetAttribute(roleAttr);
1123 if (equalIgnoringCase(siblingAriaRole, role))
1130 Element* AccessibilityNodeObject::menuElementForMenuButton() const
1132 if (ariaRoleAttribute() != MenuButtonRole)
1135 return siblingWithAriaRole("menu", node());
1138 AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const
1140 return axObjectCache()->getOrCreate(menuElementForMenuButton());
1143 Element* AccessibilityNodeObject::menuItemElementForMenu() const
1145 if (ariaRoleAttribute() != MenuRole)
1148 return siblingWithAriaRole("menuitem", node());
1151 AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
1153 Element* menuItem = menuItemElementForMenu();
1156 // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem
1157 AccessibilityObject* menuItemAX = axObjectCache()->getOrCreate(menuItem);
1158 if (menuItemAX && menuItemAX->isMenuButton())
1164 void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder)
1166 Node* node = this->node();
1170 bool isInputTag = isHTMLInputElement(node);
1171 if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
1172 HTMLLabelElement* label = labelForElement(toElement(node));
1174 AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label);
1175 String innerText = label->innerText();
1176 if (!innerText.isEmpty())
1177 textOrder.append(AccessibilityText(innerText, LabelByElementText, labelObject));
1182 AccessibilityObject* titleUIElement = this->titleUIElement();
1184 textOrder.append(AccessibilityText(String(), LabelByElementText, titleUIElement));
1187 void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
1190 String webAreaText = alternativeTextForWebArea();
1191 if (!webAreaText.isEmpty())
1192 textOrder.append(AccessibilityText(webAreaText, AlternativeText));
1196 ariaLabeledByText(textOrder);
1198 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1199 if (!ariaLabel.isEmpty())
1200 textOrder.append(AccessibilityText(ariaLabel, AlternativeText));
1202 if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
1203 // Images should use alt as long as the attribute is present, even if empty.
1204 // Otherwise, it should fallback to other methods, like the title attribute.
1205 const AtomicString& alt = getAttribute(altAttr);
1207 textOrder.append(AccessibilityText(alt, AlternativeText));
1210 Node* node = this->node();
1215 // SVG elements all can have a <svg:title> element inside which should act as the descriptive text.
1216 if (node->isSVGElement())
1217 textOrder.append(AccessibilityText(toSVGElement(node)->title(), AlternativeText));
1221 if (node->isElementNode() && toElement(node)->isMathMLElement())
1222 textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AlternativeText));
1226 void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) const
1228 Node* node = this->node();
1232 bool isInputTag = isHTMLInputElement(node);
1234 HTMLInputElement* input = toHTMLInputElement(node);
1235 if (input->isTextButton()) {
1236 textOrder.append(AccessibilityText(input->valueWithDefault(), VisibleText));
1241 // If this node isn't rendered, there's no inner text we can extract from a select element.
1242 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1245 bool useTextUnderElement = false;
1247 switch (roleValue()) {
1248 case PopUpButtonRole:
1249 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1250 if (node->hasTagName(selectTag))
1253 case ToggleButtonRole:
1255 case ListBoxOptionRole:
1257 case MenuButtonRole:
1259 case RadioButtonRole:
1261 case ProgressIndicatorRole:
1262 useTextUnderElement = true;
1268 // If it's focusable but it's not content editable or a known control type, then it will appear to
1269 // the user as a single atomic object, so we should use its text as the default title.
1270 if (isHeading() || isLink() || isGenericFocusableElement())
1271 useTextUnderElement = true;
1273 if (useTextUnderElement) {
1274 AccessibilityTextUnderElementMode mode;
1276 // Headings often include links as direct children. Those links need to be included in text under element.
1278 mode.includeFocusableContent = true;
1280 String text = textUnderElement(mode);
1281 if (!text.isEmpty())
1282 textOrder.append(AccessibilityText(text, ChildrenText));
1286 void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) const
1288 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1289 if (!ariaHelp.isEmpty())
1290 textOrder.append(AccessibilityText(ariaHelp, HelpText));
1292 String describedBy = ariaDescribedByAttribute();
1293 if (!describedBy.isEmpty())
1294 textOrder.append(AccessibilityText(describedBy, SummaryText));
1296 // Add help type text that is derived from ancestors.
1297 for (Node* curr = node(); curr; curr = curr->parentNode()) {
1298 const AtomicString& summary = getAttribute(summaryAttr);
1299 if (!summary.isEmpty())
1300 textOrder.append(AccessibilityText(summary, SummaryText));
1302 // The title attribute should be used as help text unless it is already being used as descriptive text.
1303 const AtomicString& title = getAttribute(titleAttr);
1304 if (!title.isEmpty())
1305 textOrder.append(AccessibilityText(title, TitleTagText));
1307 // Only take help text from an ancestor element if its a group or an unknown role. If help was
1308 // added to those kinds of elements, it is likely it was meant for a child element.
1309 AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr);
1313 AccessibilityRole role = axObj->roleValue();
1314 if (role != GroupRole && role != UnknownRole)
1319 void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textOrder)
1321 titleElementText(textOrder);
1322 alternativeText(textOrder);
1323 visibleText(textOrder);
1324 helpText(textOrder);
1326 String placeholder = placeholderValue();
1327 if (!placeholder.isEmpty())
1328 textOrder.append(AccessibilityText(placeholder, PlaceholderText));
1331 void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
1333 String ariaLabeledBy = ariaLabeledByAttribute();
1334 if (!ariaLabeledBy.isEmpty()) {
1335 Vector<Element*> elements;
1336 ariaLabeledByElements(elements);
1338 Vector<RefPtr<AccessibilityObject> > axElements;
1339 unsigned length = elements.size();
1340 for (unsigned k = 0; k < length; k++) {
1341 RefPtr<AccessibilityObject> axElement = axObjectCache()->getOrCreate(elements[k]);
1342 axElements.append(axElement);
1345 textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, axElements));
1349 String AccessibilityNodeObject::alternativeTextForWebArea() const
1351 // The WebArea description should follow this order:
1352 // aria-label on the <html>
1353 // title on the <html>
1354 // <title> inside the <head> (of it was set through JS)
1355 // name on the <html>
1357 // aria-label on the <iframe>
1358 // title on the <iframe>
1359 // name on the <iframe>
1361 Document* document = this->document();
1365 // Check if the HTML element has an aria-label for the webpage.
1366 if (Element* documentElement = document->documentElement()) {
1367 const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr);
1368 if (!ariaLabel.isEmpty())
1372 Node* owner = document->ownerElement();
1374 if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
1375 const AtomicString& title = toElement(owner)->getAttribute(titleAttr);
1376 if (!title.isEmpty())
1378 return toElement(owner)->getNameAttribute();
1380 if (owner->isHTMLElement())
1381 return toHTMLElement(owner)->getNameAttribute();
1384 String documentTitle = document->title();
1385 if (!documentTitle.isEmpty())
1386 return documentTitle;
1388 owner = document->body();
1389 if (owner && owner->isHTMLElement())
1390 return toHTMLElement(owner)->getNameAttribute();
1395 String AccessibilityNodeObject::accessibilityDescription() const
1397 // Static text should not have a description, it should only have a stringValue.
1398 if (roleValue() == StaticTextRole)
1401 String ariaDescription = ariaAccessibilityDescription();
1402 if (!ariaDescription.isEmpty())
1403 return ariaDescription;
1405 if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
1406 // Images should use alt as long as the attribute is present, even if empty.
1407 // Otherwise, it should fallback to other methods, like the title attribute.
1408 const AtomicString& alt = getAttribute(altAttr);
1414 // SVG elements all can have a <svg:title> element inside which should act as the descriptive text.
1415 if (m_node && m_node->isSVGElement())
1416 return toSVGElement(m_node)->title();
1420 if (m_node && m_node->isElementNode() && toElement(m_node)->isMathMLElement())
1421 return getAttribute(MathMLNames::alttextAttr);
1424 // An element's descriptive text is comprised of title() (what's visible on the screen) and accessibilityDescription() (other descriptive text).
1425 // Both are used to generate what a screen reader speaks.
1426 // If this point is reached (i.e. there's no accessibilityDescription) and there's no title(), we should fallback to using the title attribute.
1427 // 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).
1428 if (title().isEmpty())
1429 return getAttribute(titleAttr);
1434 String AccessibilityNodeObject::helpText() const
1436 Node* node = this->node();
1440 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1441 if (!ariaHelp.isEmpty())
1444 String describedBy = ariaDescribedByAttribute();
1445 if (!describedBy.isEmpty())
1448 String description = accessibilityDescription();
1449 for (Node* curr = node; curr; curr = curr->parentNode()) {
1450 if (curr->isHTMLElement()) {
1451 const AtomicString& summary = toElement(curr)->getAttribute(summaryAttr);
1452 if (!summary.isEmpty())
1455 // The title attribute should be used as help text unless it is already being used as descriptive text.
1456 const AtomicString& title = toElement(curr)->getAttribute(titleAttr);
1457 if (!title.isEmpty() && description != title)
1461 // Only take help text from an ancestor element if its a group or an unknown role. If help was
1462 // added to those kinds of elements, it is likely it was meant for a child element.
1463 AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr);
1465 AccessibilityRole role = axObj->roleValue();
1466 if (role != GroupRole && role != UnknownRole)
1474 unsigned AccessibilityNodeObject::hierarchicalLevel() const
1476 Node* node = this->node();
1477 if (!node || !node->isElementNode())
1479 Element* element = toElement(node);
1480 String ariaLevel = element->getAttribute(aria_levelAttr);
1481 if (!ariaLevel.isEmpty())
1482 return ariaLevel.toInt();
1484 // Only tree item will calculate its level through the DOM currently.
1485 if (roleValue() != TreeItemRole)
1488 // Hierarchy leveling starts at 1, to match the aria-level spec.
1489 // We measure tree hierarchy by the number of groups that the item is within.
1491 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
1492 AccessibilityRole parentRole = parent->roleValue();
1493 if (parentRole == GroupRole)
1495 else if (parentRole == TreeRole)
1502 // When building the textUnderElement for an object, determine whether or not
1503 // we should include the inner text of this given descendant object or skip it.
1504 static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode)
1506 // Do not use any heuristic if we are explicitly asking to include all the children.
1507 if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)
1510 // Consider this hypothetical example:
1513 // Table of contents
1515 // <a href="#start">Jump to start of book</a>
1517 // <li><a href="#1">Chapter 1</a></li>
1518 // <li><a href="#1">Chapter 2</a></li>
1522 // The goal is to return a reasonable title for the outer container div, because
1523 // it's focusable - but without making its title be the full inner text, which is
1524 // quite long. As a heuristic, skip links, controls, and elements that are usually
1525 // containers with lots of children.
1527 if (equalIgnoringCase(obj->getAttribute(aria_hiddenAttr), "true"))
1530 // If something doesn't expose any children, then we can always take the inner text content.
1531 // This is what we want when someone puts an <a> inside a <button> for example.
1532 if (obj->isDescendantOfBarrenParent())
1535 // Skip focusable children, so we don't include the text of links and controls.
1536 if (obj->canSetFocusAttribute() && !mode.includeFocusableContent)
1539 // Skip big container elements like lists, tables, etc.
1540 if (obj->isList() || obj->isAccessibilityTable() || obj->isTree() || obj->isCanvas())
1546 static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, String& childText)
1548 if (!builder.length() || !childText.length())
1551 // We don't need to add an additional space before or after a line break.
1552 return !(isHTMLLineBreak(childText[0]) || isHTMLLineBreak(builder[builder.length() - 1]));
1555 String AccessibilityNodeObject::textUnderElement(AccessibilityTextUnderElementMode mode) const
1557 Node* node = this->node();
1558 if (node && node->isTextNode())
1559 return toText(node)->wholeText();
1561 StringBuilder builder;
1562 for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
1563 if (!shouldUseAccessiblityObjectInnerText(child, mode))
1566 if (child->isAccessibilityNodeObject()) {
1567 Vector<AccessibilityText> textOrder;
1568 toAccessibilityNodeObject(child)->alternativeText(textOrder);
1569 if (textOrder.size() > 0 && textOrder[0].text.length()) {
1570 if (shouldAddSpaceBeforeAppendingNextElement(builder, textOrder[0].text))
1571 builder.append(' ');
1572 builder.append(textOrder[0].text);
1577 String childText = child->textUnderElement(mode);
1578 if (childText.length()) {
1579 if (shouldAddSpaceBeforeAppendingNextElement(builder, childText))
1580 builder.append(' ');
1581 builder.append(childText);
1585 return builder.toString().stripWhiteSpace().simplifyWhiteSpace(isHTMLSpaceButNotLineBreak);
1588 String AccessibilityNodeObject::title() const
1590 Node* node = this->node();
1594 bool isInputTag = isHTMLInputElement(node);
1596 HTMLInputElement* input = toHTMLInputElement(node);
1597 if (input->isTextButton())
1598 return input->valueWithDefault();
1601 if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
1602 HTMLLabelElement* label = labelForElement(toElement(node));
1603 if (label && !exposesTitleUIElement())
1604 return label->innerText();
1607 // If this node isn't rendered, there's no inner text we can extract from a select element.
1608 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1611 switch (roleValue()) {
1612 case PopUpButtonRole:
1613 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1614 if (node->hasTagName(selectTag))
1617 case ToggleButtonRole:
1619 case ListBoxOptionRole:
1621 case MenuButtonRole:
1623 case RadioButtonRole:
1625 return textUnderElement();
1626 // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>.
1634 return textUnderElement();
1636 return textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeSkipIgnoredChildren, true));
1638 // If it's focusable but it's not content editable or a known control type, then it will appear to
1639 // the user as a single atomic object, so we should use its text as the default title.
1640 if (isGenericFocusableElement())
1641 return textUnderElement();
1646 String AccessibilityNodeObject::text() const
1648 // If this is a user defined static text, use the accessible name computation.
1649 if (ariaRoleAttribute() == StaticTextRole)
1650 return ariaAccessibilityDescription();
1652 if (!isTextControl())
1655 Node* node = this->node();
1659 if (isNativeTextControl() && (isHTMLTextAreaElement(node) || isHTMLInputElement(node)))
1660 return toHTMLTextFormControlElement(node)->value();
1662 if (!node->isElementNode())
1665 return toElement(node)->innerText();
1668 String AccessibilityNodeObject::stringValue() const
1670 Node* node = this->node();
1674 if (ariaRoleAttribute() == StaticTextRole) {
1675 String staticText = text();
1676 if (!staticText.length())
1677 staticText = textUnderElement();
1681 if (node->isTextNode())
1682 return textUnderElement();
1684 if (node->hasTagName(selectTag)) {
1685 HTMLSelectElement* selectElement = toHTMLSelectElement(node);
1686 int selectedIndex = selectElement->selectedIndex();
1687 const Vector<HTMLElement*> listItems = selectElement->listItems();
1688 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
1689 const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr);
1690 if (!overriddenDescription.isNull())
1691 return overriddenDescription;
1693 if (!selectElement->multiple())
1694 return selectElement->value();
1698 if (isTextControl())
1701 // FIXME: We might need to implement a value here for more types
1702 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
1703 // this would require subclassing or making accessibilityAttributeNames do something other than return a
1704 // single static array.
1708 void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const
1717 if (!node() || !isHTMLInputElement(node()))
1720 HTMLInputElement* input = toHTMLInputElement(node());
1721 const AtomicString& type = input->getAttribute(typeAttr);
1722 if (!equalIgnoringCase(type, "color"))
1725 // HTMLInputElement::value always returns a string parseable by Color().
1726 Color color(input->value());
1732 // This function implements the ARIA accessible name as described by the Mozilla
1733 // ARIA Implementer's Guide.
1734 static String accessibleNameForNode(Node* node)
1736 if (node->isTextNode())
1737 return toText(node)->data();
1739 if (isHTMLInputElement(node))
1740 return toHTMLInputElement(node)->value();
1742 if (node->isHTMLElement()) {
1743 const AtomicString& alt = toHTMLElement(node)->getAttribute(altAttr);
1751 String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
1753 StringBuilder builder;
1754 unsigned size = elements.size();
1755 for (unsigned i = 0; i < size; ++i) {
1756 Element* idElement = elements[i];
1758 builder.append(accessibleNameForNode(idElement));
1759 for (Node* n = idElement->firstChild(); n; n = NodeTraversal::next(n, idElement))
1760 builder.append(accessibleNameForNode(n));
1763 builder.append(' ');
1765 return builder.toString();
1768 String AccessibilityNodeObject::ariaDescribedByAttribute() const
1770 Vector<Element*> elements;
1771 elementsFromAttribute(elements, aria_describedbyAttr);
1773 return accessibilityDescriptionForElements(elements);
1776 void AccessibilityNodeObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const
1778 Node* node = this->node();
1779 if (!node || !node->isElementNode())
1782 TreeScope* scope = node->treeScope();
1786 String idList = getAttribute(attribute).string();
1787 if (idList.isEmpty())
1790 idList.replace('\n', ' ');
1791 Vector<String> idVector;
1792 idList.split(' ', idVector);
1794 unsigned size = idVector.size();
1795 for (unsigned i = 0; i < size; ++i) {
1796 AtomicString idName(idVector[i]);
1797 Element* idElement = scope->getElementById(idName);
1799 elements.append(idElement);
1804 void AccessibilityNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const
1806 elementsFromAttribute(elements, aria_labeledbyAttr);
1807 if (!elements.size())
1808 elementsFromAttribute(elements, aria_labelledbyAttr);
1812 String AccessibilityNodeObject::ariaLabeledByAttribute() const
1814 Vector<Element*> elements;
1815 ariaLabeledByElements(elements);
1817 return accessibilityDescriptionForElements(elements);
1820 bool AccessibilityNodeObject::canSetFocusAttribute() const
1822 Node* node = this->node();
1829 // NOTE: It would be more accurate to ask the document whether setFocusedElement() would
1830 // do anything. For example, setFocusedElement() will do nothing if the current focused
1831 // node will not relinquish the focus.
1835 if (!node->isElementNode())
1838 Element* element = toElement(node);
1840 if (element->isDisabledFormControl())
1843 return element->supportsFocus();
1846 AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const
1848 const AtomicString& ariaRole = getAttribute(roleAttr);
1849 if (ariaRole.isNull() || ariaRole.isEmpty())
1852 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
1854 // ARIA states if an item can get focus, it should not be presentational.
1855 if (role == PresentationalRole && canSetFocusAttribute())
1858 if (role == ButtonRole)
1859 role = buttonRoleType();
1861 if (role == TextAreaRole && !ariaIsMultiline())
1862 role = TextFieldRole;
1864 role = remapAriaRoleDueToParent(role);
1866 // Presentational roles are invalidated by the presence of ARIA attributes.
1867 if (role == PresentationalRole && supportsARIAAttributes())
1876 AccessibilityRole AccessibilityNodeObject::ariaRoleAttribute() const
1881 AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
1883 // Some objects change their role based on their parent.
1884 // However, asking for the unignoredParent calls accessibilityIsIgnored(), which can trigger a loop.
1885 // While inside the call stack of creating an element, we need to avoid accessibilityIsIgnored().
1886 // https://bugs.webkit.org/show_bug.cgi?id=65174
1888 if (role != ListBoxOptionRole && role != MenuItemRole)
1891 for (AccessibilityObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
1892 AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
1894 // Selects and listboxes both have options as child roles, but they map to different roles within WebCore.
1895 if (role == ListBoxOptionRole && parentAriaRole == MenuRole)
1896 return MenuItemRole;
1897 // An aria "menuitem" may map to MenuButton or MenuItem depending on its parent.
1898 if (role == MenuItemRole && parentAriaRole == GroupRole)
1899 return MenuButtonRole;
1901 // If the parent had a different role, then we don't need to continue searching up the chain.
1909 // If you call node->rendererIsEditable() since that will return true if an ancestor is editable.
1910 // This only returns true if this is the element that actually has the contentEditable attribute set.
1911 bool AccessibilityNodeObject::hasContentEditableAttributeSet() const
1913 if (!hasAttribute(contenteditableAttr))
1915 const AtomicString& contentEditableValue = getAttribute(contenteditableAttr);
1916 // Both "true" (case-insensitive) and the empty string count as true.
1917 return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableValue, "true");
1920 bool AccessibilityNodeObject::canSetSelectedAttribute() const
1922 // Elements that can be selected
1923 switch (roleValue()) {
1925 case RadioButtonRole:
1939 } // namespace WebCore