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 "RenderImage.h"
72 #include "RenderView.h"
73 #include "SVGElement.h"
76 #include "TextControlInnerElements.h"
77 #include "TextIterator.h"
78 #include "UserGestureIndicator.h"
79 #include "VisibleUnits.h"
81 #include "htmlediting.h"
82 #include <wtf/StdLibExtras.h>
83 #include <wtf/text/StringBuilder.h>
84 #include <wtf/unicode/CharacterNames.h>
88 using namespace HTMLNames;
90 static String accessibleNameForNode(Node*);
92 AccessibilityNodeObject::AccessibilityNodeObject(Node* node)
93 : AccessibilityObject()
94 , m_ariaRole(UnknownRole)
95 , m_childrenDirty(false)
96 , m_roleForMSAA(UnknownRole)
98 , m_initialized(false)
104 AccessibilityNodeObject::~AccessibilityNodeObject()
106 ASSERT(isDetached());
109 void AccessibilityNodeObject::init()
112 ASSERT(!m_initialized);
113 m_initialized = true;
115 m_role = determineAccessibilityRole();
118 PassRefPtr<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node)
120 return adoptRef(new AccessibilityNodeObject(node));
123 void AccessibilityNodeObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache)
125 // AccessibilityObject calls clearChildren.
126 AccessibilityObject::detach(detachmentType, cache);
130 void AccessibilityNodeObject::childrenChanged()
132 // This method is meant as a quick way of marking a portion of the accessibility tree dirty.
133 if (!node() && !renderer())
136 AXObjectCache* cache = axObjectCache();
139 cache->postNotification(this, document(), AXObjectCache::AXChildrenChanged);
141 // Go up the accessibility parent chain, but only if the element already exists. This method is
142 // called during render layouts, minimal work should be done.
143 // If AX elements are created now, they could interrogate the render tree while it's in a funky state.
144 // At the same time, process ARIA live region changes.
145 for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) {
146 parent->setNeedsToUpdateChildren();
148 // These notifications always need to be sent because screenreaders are reliant on them to perform.
149 // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update.
151 // If this element supports ARIA live regions, then notify the AT of changes.
152 if (parent->supportsARIALiveRegion())
153 cache->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged);
155 // If this element is an ARIA text control, notify the AT of changes.
156 if (parent->isARIATextControl() && !parent->isNativeTextControl() && !parent->node()->hasEditableStyle())
157 cache->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged);
161 void AccessibilityNodeObject::updateAccessibilityRole()
163 bool ignoredStatus = accessibilityIsIgnored();
164 m_role = determineAccessibilityRole();
166 // The AX hierarchy only needs to be updated if the ignored status of an element has changed.
167 if (ignoredStatus != accessibilityIsIgnored())
171 AccessibilityObject* AccessibilityNodeObject::firstChild() const
176 Node* firstChild = node()->firstChild();
181 return axObjectCache()->getOrCreate(firstChild);
184 AccessibilityObject* AccessibilityNodeObject::lastChild() const
189 Node* lastChild = node()->lastChild();
193 return axObjectCache()->getOrCreate(lastChild);
196 AccessibilityObject* AccessibilityNodeObject::previousSibling() const
201 Node* previousSibling = node()->previousSibling();
202 if (!previousSibling)
205 return axObjectCache()->getOrCreate(previousSibling);
208 AccessibilityObject* AccessibilityNodeObject::nextSibling() const
213 Node* nextSibling = node()->nextSibling();
217 return axObjectCache()->getOrCreate(nextSibling);
220 AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const
222 return parentObject();
225 AccessibilityObject* AccessibilityNodeObject::parentObject() const
230 Node* parentObj = node()->parentNode();
232 return axObjectCache()->getOrCreate(parentObj);
237 LayoutRect AccessibilityNodeObject::elementRect() const
239 return boundingBoxRect();
242 LayoutRect AccessibilityNodeObject::boundingBoxRect() const
244 // AccessibilityNodeObjects have no mechanism yet to return a size or position.
245 // For now, let's return the position of the ancestor that does have a position,
246 // 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.
248 LayoutRect boundingBox;
250 for (AccessibilityObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) {
251 if (positionProvider->isAccessibilityRenderObject()) {
252 LayoutRect parentRect = positionProvider->elementRect();
253 boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat()))));
254 boundingBox.setLocation(parentRect.location());
262 void AccessibilityNodeObject::setNode(Node* node)
267 Document* AccessibilityNodeObject::document() const
271 return &node()->document();
274 AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole()
279 m_ariaRole = determineAriaRoleAttribute();
281 AccessibilityRole ariaRole = ariaRoleAttribute();
282 if (ariaRole != UnknownRole)
285 if (node()->isLink())
286 return WebCoreLinkRole;
287 if (node()->isTextNode())
288 return StaticTextRole;
289 if (node()->hasTagName(buttonTag))
290 return buttonRoleType();
291 if (isHTMLInputElement(node())) {
292 HTMLInputElement* input = toHTMLInputElement(node());
293 if (input->isCheckbox())
295 if (input->isRadioButton())
296 return RadioButtonRole;
297 if (input->isTextButton())
298 return buttonRoleType();
299 if (input->isRangeControl())
302 #if ENABLE(INPUT_TYPE_COLOR)
303 const AtomicString& type = input->getAttribute(typeAttr);
304 if (equalIgnoringCase(type, "color"))
305 return ColorWellRole;
308 return TextFieldRole;
310 if (node()->hasTagName(selectTag)) {
311 HTMLSelectElement* selectElement = toHTMLSelectElement(node());
312 return selectElement->multiple() ? ListBoxRole : PopUpButtonRole;
314 if (isHTMLTextAreaElement(node()))
318 if (node()->hasTagName(divTag))
320 if (node()->hasTagName(pTag))
321 return ParagraphRole;
322 if (isHTMLLabelElement(node()))
324 if (node()->isElementNode() && toElement(node())->isFocusable())
330 void AccessibilityNodeObject::insertChild(AccessibilityObject* child, unsigned index)
335 // If the parent is asking for this child's children, then either it's the first time (and clearing is a no-op),
336 // or its visibility has changed. In the latter case, this child may have a stale child cached.
337 // This can prevent aria-hidden changes from working correctly. Hence, whenever a parent is getting children, ensure data is not stale.
338 child->clearChildren();
340 if (child->accessibilityIsIgnored()) {
341 const auto& children = child->children();
342 size_t length = children.size();
343 for (size_t i = 0; i < length; ++i)
344 m_children.insert(index + i, children[i]);
346 ASSERT(child->parentObject() == this);
347 m_children.insert(index, child);
351 void AccessibilityNodeObject::addChild(AccessibilityObject* child)
353 insertChild(child, m_children.size());
356 void AccessibilityNodeObject::addChildren()
358 // If the need to add more children in addition to existing children arises,
359 // childrenChanged should have been called, leaving the object with no children.
360 ASSERT(!m_haveChildren);
365 m_haveChildren = true;
367 // The only time we add children from the DOM tree to a node with a renderer is when it's a canvas.
368 if (renderer() && !m_node->hasTagName(canvasTag))
371 for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
372 addChild(axObjectCache()->getOrCreate(child));
375 bool AccessibilityNodeObject::canHaveChildren() const
377 // If this is an AccessibilityRenderObject, then it's okay if this object
378 // doesn't have a node - there are some renderers that don't have associated
379 // nodes, like scroll areas and css-generated text.
380 if (!node() && !isAccessibilityRenderObject())
383 // When <noscript> is not being used (its renderer() == 0), ignore its children.
384 if (node() && !renderer() && node()->hasTagName(noscriptTag))
387 // Elements that should not have children
388 switch (roleValue()) {
391 case PopUpButtonRole:
393 case RadioButtonRole:
395 case ToggleButtonRole:
397 case ListBoxOptionRole:
399 case ProgressIndicatorRole:
402 if (Element* element = this->element())
403 return !ancestorsOfType<HTMLFieldSetElement>(*element).first();
410 bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const
413 // Double-check that an AccessibilityObject is never accessed before
414 // it's been initialized.
415 ASSERT(m_initialized);
418 // Handle non-rendered text that is exposed through aria-hidden=false.
419 if (m_node && m_node->isTextNode() && !renderer()) {
420 // Fallback content in iframe nodes should be ignored.
421 if (m_node->parentNode() && m_node->parentNode()->hasTagName(iframeTag) && m_node->parentNode()->renderer())
424 // Whitespace only text elements should be ignored when they have no renderer.
425 String string = stringValue().stripWhiteSpace().simplifyWhiteSpace();
426 if (!string.length())
430 // If this element is within a parent that cannot have children, it should not be exposed.
431 if (isDescendantOfBarrenParent())
434 return m_role == UnknownRole;
437 bool AccessibilityNodeObject::canvasHasFallbackContent() const
439 Node* node = this->node();
440 if (!node || !node->hasTagName(canvasTag))
442 Element& canvasElement = toElement(*node);
443 // If it has any children that are elements, we'll assume it might be fallback
444 // content. If it has no children or its only children are not elements
445 // (e.g. just text nodes), it doesn't have fallback content.
446 return childrenOfType<Element>(canvasElement).first();
449 bool AccessibilityNodeObject::isImageButton() const
451 return isNativeImage() && isButton();
454 bool AccessibilityNodeObject::isAnchor() const
456 return !isNativeImage() && isLink();
459 bool AccessibilityNodeObject::isNativeTextControl() const
461 Node* node = this->node();
465 if (isHTMLTextAreaElement(node))
468 if (isHTMLInputElement(node)) {
469 HTMLInputElement* input = toHTMLInputElement(node);
470 return input->isText() || input->isNumberField();
476 bool AccessibilityNodeObject::isSearchField() const
478 Node* node = this->node();
482 HTMLInputElement* inputElement = node->toInputElement();
486 if (inputElement->isSearchField())
489 // Some websites don't label their search fields as such. However, they will
490 // use the word "search" in either the form or input type. This won't catch every case,
491 // but it will catch google.com for example.
493 // Check the node name of the input type, sometimes it's "search".
494 const AtomicString& nameAttribute = getAttribute(nameAttr);
495 if (nameAttribute.contains("search", false))
498 // Check the form action and the name, which will sometimes be "search".
499 HTMLFormElement* form = inputElement->form();
500 if (form && (form->name().contains("search", false) || form->action().contains("search", false)))
506 bool AccessibilityNodeObject::isNativeImage() const
508 Node* node = this->node();
512 if (isHTMLImageElement(node))
515 if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag))
518 if (isHTMLInputElement(node)) {
519 HTMLInputElement* input = toHTMLInputElement(node);
520 return input->isImageButton();
526 bool AccessibilityNodeObject::isImage() const
528 return roleValue() == ImageRole;
531 bool AccessibilityNodeObject::isPasswordField() const
533 Node* node = this->node();
534 if (!node || !node->isHTMLElement())
537 if (ariaRoleAttribute() != UnknownRole)
540 HTMLInputElement* inputElement = node->toInputElement();
544 return inputElement->isPasswordField();
547 bool AccessibilityNodeObject::isInputImage() const
549 Node* node = this->node();
553 if (roleValue() == ButtonRole && isHTMLInputElement(node)) {
554 HTMLInputElement* input = toHTMLInputElement(node);
555 return input->isImageButton();
561 bool AccessibilityNodeObject::isProgressIndicator() const
563 return roleValue() == ProgressIndicatorRole;
566 bool AccessibilityNodeObject::isSlider() const
568 return roleValue() == SliderRole;
571 bool AccessibilityNodeObject::isMenuRelated() const
573 switch (roleValue()) {
578 case MenuItemCheckboxRole:
579 case MenuItemRadioRole:
586 bool AccessibilityNodeObject::isMenu() const
588 return roleValue() == MenuRole;
591 bool AccessibilityNodeObject::isMenuBar() const
593 return roleValue() == MenuBarRole;
596 bool AccessibilityNodeObject::isMenuButton() const
598 return roleValue() == MenuButtonRole;
601 bool AccessibilityNodeObject::isMenuItem() const
603 switch (roleValue()) {
605 case MenuItemRadioRole:
606 case MenuItemCheckboxRole:
613 bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const
615 Node* node = this->node();
619 HTMLInputElement* input = node->toInputElement();
621 return input->isCheckbox() || input->isRadioButton();
626 bool AccessibilityNodeObject::isEnabled() const
628 // ARIA says that the disabled status applies to the current element and all descendant elements.
629 for (AccessibilityObject* object = const_cast<AccessibilityNodeObject*>(this); object; object = object->parentObject()) {
630 const AtomicString& disabledStatus = object->getAttribute(aria_disabledAttr);
631 if (equalIgnoringCase(disabledStatus, "true"))
633 if (equalIgnoringCase(disabledStatus, "false"))
637 Node* node = this->node();
638 if (!node || !node->isElementNode())
641 return !toElement(node)->isDisabledFormControl();
644 bool AccessibilityNodeObject::isIndeterminate() const
646 Node* node = this->node();
650 HTMLInputElement* inputElement = node->toInputElement();
654 return inputElement->shouldAppearIndeterminate();
657 bool AccessibilityNodeObject::isPressed() const
662 Node* node = this->node();
666 // If this is an ARIA button, check the aria-pressed attribute rather than node()->active()
667 if (ariaRoleAttribute() == ButtonRole) {
668 if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true"))
673 if (!node->isElementNode())
675 return toElement(node)->active();
678 bool AccessibilityNodeObject::isChecked() const
680 Node* node = this->node();
684 // First test for native checkedness semantics
685 HTMLInputElement* inputElement = node->toInputElement();
687 return inputElement->shouldAppearChecked();
689 // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute
690 bool validRole = false;
691 switch (ariaRoleAttribute()) {
692 case RadioButtonRole:
695 case MenuItemCheckboxRole:
696 case MenuItemRadioRole:
703 if (validRole && equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
709 bool AccessibilityNodeObject::isHovered() const
711 Node* node = this->node();
715 return node->isElementNode() && toElement(node)->hovered();
718 bool AccessibilityNodeObject::isMultiSelectable() const
720 const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
721 if (equalIgnoringCase(ariaMultiSelectable, "true"))
723 if (equalIgnoringCase(ariaMultiSelectable, "false"))
726 return node() && node()->hasTagName(selectTag) && toHTMLSelectElement(node())->multiple();
729 bool AccessibilityNodeObject::isReadOnly() const
731 Node* node = this->node();
735 if (isHTMLTextAreaElement(node))
736 return toHTMLFormControlElement(node)->isReadOnly();
738 if (isHTMLInputElement(node)) {
739 HTMLInputElement* input = toHTMLInputElement(node);
740 if (input->isTextField())
741 return input->isReadOnly();
744 return !node->hasEditableStyle();
747 bool AccessibilityNodeObject::isRequired() const
749 // Explicit aria-required values should trump native required attributes.
750 const AtomicString& requiredValue = getAttribute(aria_requiredAttr);
751 if (equalIgnoringCase(requiredValue, "true"))
753 if (equalIgnoringCase(requiredValue, "false"))
756 Node* n = this->node();
757 if (n && (n->isElementNode() && toElement(n)->isFormControlElement()))
758 return toHTMLFormControlElement(n)->isRequired();
763 bool AccessibilityNodeObject::supportsRequiredAttribute() const
765 switch (roleValue()) {
767 return isFileUploadButton();
772 case IncrementorRole:
774 case PopUpButtonRole:
775 case RadioButtonRole:
780 case TableHeaderContainerRole:
783 case ToggleButtonRole:
790 int AccessibilityNodeObject::headingLevel() const
792 // headings can be in block flow and non-block flow
793 Node* node = this->node();
798 int ariaLevel = getAttribute(aria_levelAttr).toInt();
803 if (node->hasTagName(h1Tag))
806 if (node->hasTagName(h2Tag))
809 if (node->hasTagName(h3Tag))
812 if (node->hasTagName(h4Tag))
815 if (node->hasTagName(h5Tag))
818 if (node->hasTagName(h6Tag))
824 String AccessibilityNodeObject::valueDescription() const
826 if (!isRangeControl())
829 return getAttribute(aria_valuetextAttr).string();
832 float AccessibilityNodeObject::valueForRange() const
834 if (node() && isHTMLInputElement(node())) {
835 HTMLInputElement* input = toHTMLInputElement(node());
836 if (input->isRangeControl())
837 return input->valueAsNumber();
840 if (!isRangeControl())
843 return getAttribute(aria_valuenowAttr).toFloat();
846 float AccessibilityNodeObject::maxValueForRange() const
848 if (node() && isHTMLInputElement(node())) {
849 HTMLInputElement* input = toHTMLInputElement(node());
850 if (input->isRangeControl())
851 return input->maximum();
854 if (!isRangeControl())
857 return getAttribute(aria_valuemaxAttr).toFloat();
860 float AccessibilityNodeObject::minValueForRange() const
862 if (node() && isHTMLInputElement(node())) {
863 HTMLInputElement* input = toHTMLInputElement(node());
864 if (input->isRangeControl())
865 return input->minimum();
868 if (!isRangeControl())
871 return getAttribute(aria_valueminAttr).toFloat();
874 float AccessibilityNodeObject::stepValueForRange() const
876 return getAttribute(stepAttr).toFloat();
879 bool AccessibilityNodeObject::isHeading() const
881 return roleValue() == HeadingRole;
884 bool AccessibilityNodeObject::isLink() const
886 return roleValue() == WebCoreLinkRole;
889 bool AccessibilityNodeObject::isControl() const
891 Node* node = this->node();
895 return ((node->isElementNode() && toElement(node)->isFormControlElement())
896 || AccessibilityObject::isARIAControl(ariaRoleAttribute()));
899 bool AccessibilityNodeObject::isFieldset() const
901 Node* node = this->node();
905 return node->hasTagName(fieldsetTag);
908 bool AccessibilityNodeObject::isGroup() const
910 return roleValue() == GroupRole;
913 AccessibilityObject* AccessibilityNodeObject::selectedRadioButton()
918 // Find the child radio button that is selected (ie. the intValue == 1).
919 for (const auto& child : children()) {
920 if (child->roleValue() == RadioButtonRole && child->checkboxOrRadioValue() == ButtonStateOn)
926 AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
931 // Find the child tab item that is selected (ie. the intValue == 1).
932 AccessibilityObject::AccessibilityChildrenVector tabs;
935 for (const auto& child : children()) {
936 if (child->isTabItem() && child->isChecked())
942 AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const
944 if (isNativeCheckboxOrRadio())
945 return isChecked() ? ButtonStateOn : ButtonStateOff;
947 return AccessibilityObject::checkboxOrRadioValue();
950 Element* AccessibilityNodeObject::anchorElement() const
952 Node* node = this->node();
956 AXObjectCache* cache = axObjectCache();
958 // search up the DOM tree for an anchor element
959 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
960 for ( ; node; node = node->parentNode()) {
961 if (isHTMLAnchorElement(node) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor()))
962 return toElement(node);
968 static bool isNodeActionElement(Node* node)
970 if (isHTMLInputElement(node)) {
971 HTMLInputElement* input = toHTMLInputElement(node);
972 if (!input->isDisabledFormControl() && (input->isRadioButton() || input->isCheckbox() || input->isTextButton() || input->isFileUpload() || input->isImageButton()))
974 } else if (node->hasTagName(buttonTag) || node->hasTagName(selectTag))
980 static Element* nativeActionElement(Node* start)
985 // Do a deep-dive to see if any nodes should be used as the action element.
986 // We have to look at Nodes, since this method should only be called on objects that do not have children (like buttons).
987 // It solves the problem when authors put role="button" on a group and leave the actual button inside the group.
989 for (Node* child = start->firstChild(); child; child = child->nextSibling()) {
990 if (isNodeActionElement(child))
991 return toElement(child);
993 if (Element* subChild = nativeActionElement(child))
999 Element* AccessibilityNodeObject::actionElement() const
1001 Node* node = this->node();
1005 if (isNodeActionElement(node))
1006 return toElement(node);
1008 if (AccessibilityObject::isARIAInput(ariaRoleAttribute()))
1009 return toElement(node);
1011 switch (roleValue()) {
1013 case PopUpButtonRole:
1014 case ToggleButtonRole:
1017 case MenuItemCheckboxRole:
1018 case MenuItemRadioRole:
1020 // Check if the author is hiding the real control element inside the ARIA element.
1021 if (Element* nativeElement = nativeActionElement(node))
1022 return nativeElement;
1023 return toElement(node);
1028 Element* elt = anchorElement();
1030 elt = mouseButtonListener();
1034 Element* AccessibilityNodeObject::mouseButtonListener() const
1036 Node* node = this->node();
1040 // check if our parent is a mouse button listener
1041 // FIXME: Do the continuation search like anchorElement does
1042 for (auto& element : elementLineage(node->isElementNode() ? toElement(node) : node->parentElement())) {
1043 // If we've reached the body and this is not a control element, do not expose press action for this element.
1044 // It can cause false positives, where every piece of text is labeled as accepting press actions.
1045 if (element.hasTagName(bodyTag) && isStaticText())
1048 if (element.hasEventListeners(eventNames().clickEvent) || element.hasEventListeners(eventNames().mousedownEvent) || element.hasEventListeners(eventNames().mouseupEvent))
1055 bool AccessibilityNodeObject::isDescendantOfBarrenParent() const
1057 for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) {
1058 if (!object->canHaveChildren())
1065 void AccessibilityNodeObject::alterSliderValue(bool increase)
1067 if (roleValue() != SliderRole)
1070 if (!getAttribute(stepAttr).isEmpty())
1071 changeValueByStep(increase);
1073 changeValueByPercent(increase ? 5 : -5);
1076 void AccessibilityNodeObject::increment()
1078 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
1079 alterSliderValue(true);
1082 void AccessibilityNodeObject::decrement()
1084 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
1085 alterSliderValue(false);
1088 void AccessibilityNodeObject::changeValueByStep(bool increase)
1090 float step = stepValueForRange();
1091 float value = valueForRange();
1093 value += increase ? step : -step;
1095 setValue(String::number(value));
1097 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged);
1100 void AccessibilityNodeObject::changeValueByPercent(float percentChange)
1102 float range = maxValueForRange() - minValueForRange();
1103 float step = range * (percentChange / 100);
1104 float value = valueForRange();
1106 // Make sure the specified percent will cause a change of one integer step or larger.
1108 step = fabs(percentChange) * (1 / percentChange);
1111 setValue(String::number(value));
1113 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged);
1116 bool AccessibilityNodeObject::isGenericFocusableElement() const
1118 if (!canSetFocusAttribute())
1121 // If it's a control, it's not generic.
1125 AccessibilityRole role = roleValue();
1126 if (role == VideoRole || role == AudioRole)
1129 // If it has an aria role, it's not generic.
1130 if (m_ariaRole != UnknownRole)
1133 // If the content editable attribute is set on this element, that's the reason
1134 // it's focusable, and existing logic should handle this case already - so it's not a
1135 // generic focusable element.
1137 if (hasContentEditableAttributeSet())
1140 // The web area and body element are both focusable, but existing logic handles these
1141 // cases already, so we don't need to include them here.
1142 if (role == WebAreaRole)
1144 if (node() && node()->hasTagName(bodyTag))
1147 // An SVG root is focusable by default, but it's probably not interactive, so don't
1148 // include it. It can still be made accessible by giving it an ARIA role.
1149 if (role == SVGRootRole)
1155 HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const
1157 if (!element->isHTMLElement() || !toHTMLElement(element)->isLabelable())
1160 const AtomicString& id = element->getIdAttribute();
1161 if (!id.isEmpty()) {
1162 if (HTMLLabelElement* label = element->treeScope().labelElementForId(id))
1166 return ancestorsOfType<HTMLLabelElement>(*element).first();
1169 String AccessibilityNodeObject::ariaAccessibilityDescription() const
1171 String ariaLabeledBy = ariaLabeledByAttribute();
1172 if (!ariaLabeledBy.isEmpty())
1173 return ariaLabeledBy;
1175 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1176 if (!ariaLabel.isEmpty())
1182 static Element* siblingWithAriaRole(String role, Node* node)
1184 ContainerNode* parent = node->parentNode();
1188 for (auto& sibling : childrenOfType<Element>(*parent)) {
1189 const AtomicString& siblingAriaRole = sibling.fastGetAttribute(roleAttr);
1190 if (equalIgnoringCase(siblingAriaRole, role))
1197 Element* AccessibilityNodeObject::menuElementForMenuButton() const
1199 if (ariaRoleAttribute() != MenuButtonRole)
1202 return siblingWithAriaRole("menu", node());
1205 AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const
1207 return axObjectCache()->getOrCreate(menuElementForMenuButton());
1210 Element* AccessibilityNodeObject::menuItemElementForMenu() const
1212 if (ariaRoleAttribute() != MenuRole)
1215 return siblingWithAriaRole("menuitem", node());
1218 AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
1220 Element* menuItem = menuItemElementForMenu();
1223 // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem
1224 AccessibilityObject* menuItemAX = axObjectCache()->getOrCreate(menuItem);
1225 if (menuItemAX && menuItemAX->isMenuButton())
1231 bool AccessibilityNodeObject::usesAltTagForTextComputation() const
1233 return isImage() || isInputImage() || isNativeImage() || isCanvas() || (node() && node()->hasTagName(imgTag));
1236 void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder) const
1238 Node* node = this->node();
1242 bool isInputTag = isHTMLInputElement(node);
1243 if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
1244 HTMLLabelElement* label = labelForElement(toElement(node));
1246 AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label);
1247 String innerText = label->innerText();
1248 // Only use the <label> text if there's no ARIA override.
1249 if (!innerText.isEmpty() && !ariaAccessibilityDescription())
1250 textOrder.append(AccessibilityText(innerText, LabelByElementText, labelObject));
1255 AccessibilityObject* titleUIElement = this->titleUIElement();
1257 textOrder.append(AccessibilityText(String(), LabelByElementText, titleUIElement));
1260 void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
1263 String webAreaText = alternativeTextForWebArea();
1264 if (!webAreaText.isEmpty())
1265 textOrder.append(AccessibilityText(webAreaText, AlternativeText));
1269 ariaLabeledByText(textOrder);
1271 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1272 if (!ariaLabel.isEmpty())
1273 textOrder.append(AccessibilityText(ariaLabel, AlternativeText));
1275 if (usesAltTagForTextComputation()) {
1276 if (renderer() && renderer()->isRenderImage()) {
1277 String renderAltText = toRenderImage(renderer())->altText();
1279 // RenderImage will return title as a fallback from altText, but we don't want title here because we consider that in helpText.
1280 if (!renderAltText.isEmpty() && renderAltText != getAttribute(titleAttr)) {
1281 textOrder.append(AccessibilityText(renderAltText, AlternativeText));
1285 // Images should use alt as long as the attribute is present, even if empty.
1286 // Otherwise, it should fallback to other methods, like the title attribute.
1287 const AtomicString& alt = getAttribute(altAttr);
1289 textOrder.append(AccessibilityText(alt, AlternativeText));
1292 Node* node = this->node();
1296 // The fieldset element derives its alternative text from the first associated legend element if one is available.
1297 if (isHTMLFieldSetElement(node)) {
1298 AccessibilityObject* object = axObjectCache()->getOrCreate(toHTMLFieldSetElement(node)->legend());
1299 if (object && !object->isHidden())
1300 textOrder.append(AccessibilityText(accessibleNameForNode(object->node()), AlternativeText));
1304 // SVG elements all can have a <svg:title> element inside which should act as the descriptive text.
1305 if (node->isSVGElement())
1306 textOrder.append(AccessibilityText(toSVGElement(node)->title(), AlternativeText));
1310 if (node->isMathMLElement())
1311 textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AlternativeText));
1315 void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) const
1317 Node* node = this->node();
1321 bool isInputTag = isHTMLInputElement(node);
1323 HTMLInputElement* input = toHTMLInputElement(node);
1324 if (input->isTextButton()) {
1325 textOrder.append(AccessibilityText(input->valueWithDefault(), VisibleText));
1330 // If this node isn't rendered, there's no inner text we can extract from a select element.
1331 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1334 bool useTextUnderElement = false;
1336 switch (roleValue()) {
1337 case PopUpButtonRole:
1338 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1339 if (node->hasTagName(selectTag))
1343 case ToggleButtonRole:
1345 case ListBoxOptionRole:
1346 // MacOS does not expect native <li> elements to expose label information, it only expects leaf node elements to do that.
1350 case MenuButtonRole:
1352 case MenuItemCheckboxRole:
1353 case MenuItemRadioRole:
1354 case RadioButtonRole:
1356 case ProgressIndicatorRole:
1357 useTextUnderElement = true;
1363 // If it's focusable but it's not content editable or a known control type, then it will appear to
1364 // the user as a single atomic object, so we should use its text as the default title.
1365 if (isHeading() || isLink())
1366 useTextUnderElement = true;
1368 if (useTextUnderElement) {
1369 AccessibilityTextUnderElementMode mode;
1371 // Headings often include links as direct children. Those links need to be included in text under element.
1373 mode.includeFocusableContent = true;
1375 String text = textUnderElement(mode);
1376 if (!text.isEmpty())
1377 textOrder.append(AccessibilityText(text, ChildrenText));
1381 void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) const
1383 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1384 if (!ariaHelp.isEmpty())
1385 textOrder.append(AccessibilityText(ariaHelp, HelpText));
1387 String describedBy = ariaDescribedByAttribute();
1388 if (!describedBy.isEmpty())
1389 textOrder.append(AccessibilityText(describedBy, SummaryText));
1391 // Add help type text that is derived from ancestors.
1392 for (Node* curr = node(); curr; curr = curr->parentNode()) {
1393 const AtomicString& summary = getAttribute(summaryAttr);
1394 if (!summary.isEmpty())
1395 textOrder.append(AccessibilityText(summary, SummaryText));
1397 // The title attribute should be used as help text unless it is already being used as descriptive text.
1398 const AtomicString& title = getAttribute(titleAttr);
1399 if (!title.isEmpty())
1400 textOrder.append(AccessibilityText(title, TitleTagText));
1402 // Only take help text from an ancestor element if its a group or an unknown role. If help was
1403 // added to those kinds of elements, it is likely it was meant for a child element.
1404 AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr);
1408 AccessibilityRole role = axObj->roleValue();
1409 if (role != GroupRole && role != UnknownRole)
1414 void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textOrder)
1416 titleElementText(textOrder);
1417 alternativeText(textOrder);
1418 visibleText(textOrder);
1419 helpText(textOrder);
1421 String placeholder = placeholderValue();
1422 if (!placeholder.isEmpty())
1423 textOrder.append(AccessibilityText(placeholder, PlaceholderText));
1426 void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
1428 String ariaLabeledBy = ariaLabeledByAttribute();
1429 if (!ariaLabeledBy.isEmpty()) {
1430 Vector<Element*> elements;
1431 ariaLabeledByElements(elements);
1433 Vector<RefPtr<AccessibilityObject>> axElements;
1434 for (const auto& element : elements) {
1435 RefPtr<AccessibilityObject> axElement = axObjectCache()->getOrCreate(element);
1436 axElements.append(axElement);
1439 textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, axElements));
1443 String AccessibilityNodeObject::alternativeTextForWebArea() const
1445 // The WebArea description should follow this order:
1446 // aria-label on the <html>
1447 // title on the <html>
1448 // <title> inside the <head> (of it was set through JS)
1449 // name on the <html>
1451 // aria-label on the <iframe>
1452 // title on the <iframe>
1453 // name on the <iframe>
1455 Document* document = this->document();
1459 // Check if the HTML element has an aria-label for the webpage.
1460 if (Element* documentElement = document->documentElement()) {
1461 const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr);
1462 if (!ariaLabel.isEmpty())
1466 Node* owner = document->ownerElement();
1468 if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
1469 const AtomicString& title = toElement(owner)->getAttribute(titleAttr);
1470 if (!title.isEmpty())
1472 return toElement(owner)->getNameAttribute();
1474 if (owner->isHTMLElement())
1475 return toHTMLElement(owner)->getNameAttribute();
1478 String documentTitle = document->title();
1479 if (!documentTitle.isEmpty())
1480 return documentTitle;
1482 owner = document->body();
1483 if (owner && owner->isHTMLElement())
1484 return toHTMLElement(owner)->getNameAttribute();
1489 String AccessibilityNodeObject::accessibilityDescription() const
1491 // Static text should not have a description, it should only have a stringValue.
1492 if (roleValue() == StaticTextRole)
1495 String ariaDescription = ariaAccessibilityDescription();
1496 if (!ariaDescription.isEmpty())
1497 return ariaDescription;
1499 if (usesAltTagForTextComputation()) {
1500 // Images should use alt as long as the attribute is present, even if empty.
1501 // Otherwise, it should fallback to other methods, like the title attribute.
1502 const AtomicString& alt = getAttribute(altAttr);
1508 // SVG elements all can have a <svg:title> element inside which should act as the descriptive text.
1509 if (m_node && m_node->isSVGElement())
1510 return toSVGElement(m_node)->title();
1514 if (m_node && m_node->isMathMLElement())
1515 return getAttribute(MathMLNames::alttextAttr);
1518 // An element's descriptive text is comprised of title() (what's visible on the screen) and accessibilityDescription() (other descriptive text).
1519 // Both are used to generate what a screen reader speaks.
1520 // If this point is reached (i.e. there's no accessibilityDescription) and there's no title(), we should fallback to using the title attribute.
1521 // 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).
1522 if (title().isEmpty())
1523 return getAttribute(titleAttr);
1528 String AccessibilityNodeObject::helpText() const
1530 Node* node = this->node();
1534 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1535 if (!ariaHelp.isEmpty())
1538 String describedBy = ariaDescribedByAttribute();
1539 if (!describedBy.isEmpty())
1542 String description = accessibilityDescription();
1543 for (Node* curr = node; curr; curr = curr->parentNode()) {
1544 if (curr->isHTMLElement()) {
1545 const AtomicString& summary = toElement(curr)->getAttribute(summaryAttr);
1546 if (!summary.isEmpty())
1549 // The title attribute should be used as help text unless it is already being used as descriptive text.
1550 const AtomicString& title = toElement(curr)->getAttribute(titleAttr);
1551 if (!title.isEmpty() && description != title)
1555 // Only take help text from an ancestor element if its a group or an unknown role. If help was
1556 // added to those kinds of elements, it is likely it was meant for a child element.
1557 AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr);
1559 AccessibilityRole role = axObj->roleValue();
1560 if (role != GroupRole && role != UnknownRole)
1568 unsigned AccessibilityNodeObject::hierarchicalLevel() const
1570 Node* node = this->node();
1571 if (!node || !node->isElementNode())
1573 Element* element = toElement(node);
1574 String ariaLevel = element->getAttribute(aria_levelAttr);
1575 if (!ariaLevel.isEmpty())
1576 return ariaLevel.toInt();
1578 // Only tree item will calculate its level through the DOM currently.
1579 if (roleValue() != TreeItemRole)
1582 // Hierarchy leveling starts at 1, to match the aria-level spec.
1583 // We measure tree hierarchy by the number of groups that the item is within.
1585 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
1586 AccessibilityRole parentRole = parent->roleValue();
1587 if (parentRole == GroupRole)
1589 else if (parentRole == TreeRole)
1596 // When building the textUnderElement for an object, determine whether or not
1597 // we should include the inner text of this given descendant object or skip it.
1598 static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode)
1600 // Do not use any heuristic if we are explicitly asking to include all the children.
1601 if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)
1604 // Consider this hypothetical example:
1607 // Table of contents
1609 // <a href="#start">Jump to start of book</a>
1611 // <li><a href="#1">Chapter 1</a></li>
1612 // <li><a href="#1">Chapter 2</a></li>
1616 // The goal is to return a reasonable title for the outer container div, because
1617 // it's focusable - but without making its title be the full inner text, which is
1618 // quite long. As a heuristic, skip links, controls, and elements that are usually
1619 // containers with lots of children.
1621 if (equalIgnoringCase(obj->getAttribute(aria_hiddenAttr), "true"))
1624 // If something doesn't expose any children, then we can always take the inner text content.
1625 // This is what we want when someone puts an <a> inside a <button> for example.
1626 if (obj->isDescendantOfBarrenParent())
1629 // Skip focusable children, so we don't include the text of links and controls.
1630 if (obj->canSetFocusAttribute() && !mode.includeFocusableContent)
1633 // Skip big container elements like lists, tables, etc.
1634 if (obj->isList() || obj->isAccessibilityTable() || obj->isTree() || obj->isCanvas())
1640 static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, String& childText)
1642 if (!builder.length() || !childText.length())
1645 // We don't need to add an additional space before or after a line break.
1646 return !(isHTMLLineBreak(childText[0]) || isHTMLLineBreak(builder[builder.length() - 1]));
1649 String AccessibilityNodeObject::textUnderElement(AccessibilityTextUnderElementMode mode) const
1651 Node* node = this->node();
1652 if (node && node->isTextNode())
1653 return toText(node)->wholeText();
1655 // The render tree should be stable before going ahead. Otherwise, further uses of the
1656 // TextIterator will force a layout update, potentially altering the accessibility tree
1657 // and leading to crashes in the loop that computes the result text from the children.
1658 ASSERT(!document()->renderView()->layoutState());
1659 ASSERT(!document()->childNeedsStyleRecalc());
1661 StringBuilder builder;
1662 for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
1663 if (!shouldUseAccessiblityObjectInnerText(child, mode))
1666 if (child->isAccessibilityNodeObject()) {
1667 Vector<AccessibilityText> textOrder;
1668 toAccessibilityNodeObject(child)->alternativeText(textOrder);
1669 if (textOrder.size() > 0 && textOrder[0].text.length()) {
1670 if (shouldAddSpaceBeforeAppendingNextElement(builder, textOrder[0].text))
1671 builder.append(' ');
1672 builder.append(textOrder[0].text);
1677 String childText = child->textUnderElement(mode);
1678 if (childText.length()) {
1679 if (shouldAddSpaceBeforeAppendingNextElement(builder, childText))
1680 builder.append(' ');
1681 builder.append(childText);
1685 return builder.toString().stripWhiteSpace().simplifyWhiteSpace(isHTMLSpaceButNotLineBreak);
1688 String AccessibilityNodeObject::title() const
1690 Node* node = this->node();
1694 bool isInputTag = isHTMLInputElement(node);
1696 HTMLInputElement* input = toHTMLInputElement(node);
1697 if (input->isTextButton())
1698 return input->valueWithDefault();
1701 if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
1702 HTMLLabelElement* label = labelForElement(toElement(node));
1703 // Use the label text as the title if 1) the title element is NOT an exposed element and 2) there's no ARIA override.
1704 if (label && !exposesTitleUIElement() && !ariaAccessibilityDescription().length())
1705 return label->innerText();
1708 // If this node isn't rendered, there's no inner text we can extract from a select element.
1709 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1712 switch (roleValue()) {
1713 case PopUpButtonRole:
1714 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1715 if (node->hasTagName(selectTag))
1719 case ToggleButtonRole:
1721 case ListBoxOptionRole:
1723 case MenuButtonRole:
1725 case MenuItemCheckboxRole:
1726 case MenuItemRadioRole:
1727 case RadioButtonRole:
1729 return textUnderElement();
1730 // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>.
1738 return textUnderElement();
1740 return textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeSkipIgnoredChildren, true));
1745 String AccessibilityNodeObject::text() const
1747 // If this is a user defined static text, use the accessible name computation.
1748 if (ariaRoleAttribute() == StaticTextRole) {
1749 Vector<AccessibilityText> textOrder;
1750 alternativeText(textOrder);
1751 if (textOrder.size() > 0 && textOrder[0].text.length())
1752 return textOrder[0].text;
1755 if (!isTextControl())
1758 Node* node = this->node();
1762 if (isNativeTextControl() && (isHTMLTextAreaElement(node) || isHTMLInputElement(node)))
1763 return toHTMLTextFormControlElement(node)->value();
1765 if (!node->isElementNode())
1768 return toElement(node)->innerText();
1771 String AccessibilityNodeObject::stringValue() const
1773 Node* node = this->node();
1777 if (ariaRoleAttribute() == StaticTextRole) {
1778 String staticText = text();
1779 if (!staticText.length())
1780 staticText = textUnderElement();
1784 if (node->isTextNode())
1785 return textUnderElement();
1787 if (node->hasTagName(selectTag)) {
1788 HTMLSelectElement* selectElement = toHTMLSelectElement(node);
1789 int selectedIndex = selectElement->selectedIndex();
1790 const Vector<HTMLElement*> listItems = selectElement->listItems();
1791 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
1792 const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr);
1793 if (!overriddenDescription.isNull())
1794 return overriddenDescription;
1796 if (!selectElement->multiple())
1797 return selectElement->value();
1801 if (isTextControl())
1804 // FIXME: We might need to implement a value here for more types
1805 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
1806 // this would require subclassing or making accessibilityAttributeNames do something other than return a
1807 // single static array.
1811 void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const
1820 if (!node() || !isHTMLInputElement(node()))
1823 HTMLInputElement* input = toHTMLInputElement(node());
1824 const AtomicString& type = input->getAttribute(typeAttr);
1825 if (!equalIgnoringCase(type, "color"))
1828 // HTMLInputElement::value always returns a string parseable by Color().
1829 Color color(input->value());
1835 // This function implements the ARIA accessible name as described by the Mozilla
1836 // ARIA Implementer's Guide.
1837 static String accessibleNameForNode(Node* node)
1839 if (!node->isHTMLElement())
1842 HTMLElement* element = toHTMLElement(node);
1844 const AtomicString& ariaLabel = element->fastGetAttribute(aria_labelAttr);
1845 if (!ariaLabel.isEmpty())
1848 const AtomicString& alt = element->fastGetAttribute(altAttr);
1852 if (isHTMLInputElement(node))
1853 return toHTMLInputElement(node)->value();
1855 // If the node can be turned into an AX object, we can use standard name computation rules.
1856 // If however, the node cannot (because there's no renderer e.g.) fallback to using the basic text underneath.
1857 AccessibilityObject* axObject = node->document().axObjectCache()->getOrCreate(node);
1860 text = axObject->textUnderElement();
1861 else if (node->isElementNode())
1862 text = toElement(node)->innerText();
1864 if (!text.isEmpty())
1867 const AtomicString& title = element->fastGetAttribute(titleAttr);
1868 if (!title.isEmpty())
1874 String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
1876 StringBuilder builder;
1877 unsigned size = elements.size();
1878 for (unsigned i = 0; i < size; ++i) {
1880 builder.append(' ');
1882 builder.append(accessibleNameForNode(elements[i]));
1884 return builder.toString();
1887 String AccessibilityNodeObject::ariaDescribedByAttribute() const
1889 Vector<Element*> elements;
1890 elementsFromAttribute(elements, aria_describedbyAttr);
1892 return accessibilityDescriptionForElements(elements);
1895 void AccessibilityNodeObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const
1897 Node* node = this->node();
1898 if (!node || !node->isElementNode())
1901 TreeScope& treeScope = node->treeScope();
1903 String idList = getAttribute(attribute).string();
1904 if (idList.isEmpty())
1907 idList.replace('\n', ' ');
1908 Vector<String> idVector;
1909 idList.split(' ', idVector);
1911 unsigned size = idVector.size();
1912 for (unsigned i = 0; i < size; ++i) {
1913 AtomicString idName(idVector[i]);
1914 Element* idElement = treeScope.getElementById(idName);
1916 elements.append(idElement);
1921 void AccessibilityNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const
1923 elementsFromAttribute(elements, aria_labelledbyAttr);
1924 if (!elements.size())
1925 elementsFromAttribute(elements, aria_labeledbyAttr);
1929 String AccessibilityNodeObject::ariaLabeledByAttribute() const
1931 Vector<Element*> elements;
1932 ariaLabeledByElements(elements);
1934 return accessibilityDescriptionForElements(elements);
1937 bool AccessibilityNodeObject::hasAttributesRequiredForInclusion() const
1939 if (AccessibilityObject::hasAttributesRequiredForInclusion())
1942 if (!ariaAccessibilityDescription().isEmpty())
1948 bool AccessibilityNodeObject::canSetFocusAttribute() const
1950 Node* node = this->node();
1957 // NOTE: It would be more accurate to ask the document whether setFocusedElement() would
1958 // do anything. For example, setFocusedElement() will do nothing if the current focused
1959 // node will not relinquish the focus.
1963 if (!node->isElementNode())
1966 Element* element = toElement(node);
1968 if (element->isDisabledFormControl())
1971 return element->supportsFocus();
1974 AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const
1976 const AtomicString& ariaRole = getAttribute(roleAttr);
1977 if (ariaRole.isNull() || ariaRole.isEmpty())
1980 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
1982 // ARIA states if an item can get focus, it should not be presentational.
1983 if (role == PresentationalRole && canSetFocusAttribute())
1986 if (role == ButtonRole)
1987 role = buttonRoleType();
1989 if (role == TextAreaRole && !ariaIsMultiline())
1990 role = TextFieldRole;
1992 role = remapAriaRoleDueToParent(role);
1994 // Presentational roles are invalidated by the presence of ARIA attributes.
1995 if (role == PresentationalRole && supportsARIAAttributes())
2004 AccessibilityRole AccessibilityNodeObject::ariaRoleAttribute() const
2009 AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
2011 // Some objects change their role based on their parent.
2012 // However, asking for the unignoredParent calls accessibilityIsIgnored(), which can trigger a loop.
2013 // While inside the call stack of creating an element, we need to avoid accessibilityIsIgnored().
2014 // https://bugs.webkit.org/show_bug.cgi?id=65174
2016 if (role != ListBoxOptionRole && role != MenuItemRole)
2019 for (AccessibilityObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
2020 AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
2022 // Selects and listboxes both have options as child roles, but they map to different roles within WebCore.
2023 if (role == ListBoxOptionRole && parentAriaRole == MenuRole)
2024 return MenuItemRole;
2025 // An aria "menuitem" may map to MenuButton or MenuItem depending on its parent.
2026 if (role == MenuItemRole && parentAriaRole == GroupRole)
2027 return MenuButtonRole;
2029 // If the parent had a different role, then we don't need to continue searching up the chain.
2037 // If you call node->hasEditableStyle() since that will return true if an ancestor is editable.
2038 // This only returns true if this is the element that actually has the contentEditable attribute set.
2039 bool AccessibilityNodeObject::hasContentEditableAttributeSet() const
2041 if (!hasAttribute(contenteditableAttr))
2043 const AtomicString& contentEditableValue = getAttribute(contenteditableAttr);
2044 // Both "true" (case-insensitive) and the empty string count as true.
2045 return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableValue, "true");
2048 bool AccessibilityNodeObject::canSetSelectedAttribute() const
2050 // Elements that can be selected
2051 switch (roleValue()) {
2053 case RadioButtonRole:
2067 } // namespace WebCore