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 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 "AccessibilityList.h"
35 #include "AccessibilityListBox.h"
36 #include "AccessibilitySpinButton.h"
37 #include "AccessibilityTable.h"
38 #include "ElementIterator.h"
39 #include "EventNames.h"
40 #include "FloatRect.h"
42 #include "FrameLoader.h"
43 #include "FrameSelection.h"
44 #include "FrameView.h"
45 #include "HTMLAreaElement.h"
46 #include "HTMLCanvasElement.h"
47 #include "HTMLFieldSetElement.h"
48 #include "HTMLFormElement.h"
49 #include "HTMLFrameElementBase.h"
50 #include "HTMLImageElement.h"
51 #include "HTMLInputElement.h"
52 #include "HTMLLabelElement.h"
53 #include "HTMLLegendElement.h"
54 #include "HTMLMapElement.h"
55 #include "HTMLNames.h"
56 #include "HTMLOptGroupElement.h"
57 #include "HTMLOptionElement.h"
58 #include "HTMLOptionsCollection.h"
59 #include "HTMLParserIdioms.h"
60 #include "HTMLPlugInImageElement.h"
61 #include "HTMLSelectElement.h"
62 #include "HTMLTextAreaElement.h"
63 #include "HTMLTextFormControlElement.h"
64 #include "HitTestRequest.h"
65 #include "HitTestResult.h"
66 #include "LabelableElement.h"
67 #include "LocalizedStrings.h"
68 #include "MathMLElement.h"
69 #include "MathMLNames.h"
71 #include "NodeTraversal.h"
73 #include "ProgressTracker.h"
74 #include "RenderImage.h"
75 #include "RenderView.h"
76 #include "SVGElement.h"
79 #include "TextControlInnerElements.h"
80 #include "UserGestureIndicator.h"
81 #include "VisibleUnits.h"
83 #include "htmlediting.h"
84 #include <wtf/StdLibExtras.h>
85 #include <wtf/text/StringBuilder.h>
86 #include <wtf/unicode/CharacterNames.h>
90 using namespace HTMLNames;
92 static String accessibleNameForNode(Node* node, Node* labelledbyNode = nullptr);
94 AccessibilityNodeObject::AccessibilityNodeObject(Node* node)
95 : AccessibilityObject()
96 , m_ariaRole(UnknownRole)
97 , m_childrenDirty(false)
98 , m_roleForMSAA(UnknownRole)
100 , m_initialized(false)
106 AccessibilityNodeObject::~AccessibilityNodeObject()
108 ASSERT(isDetached());
111 void AccessibilityNodeObject::init()
114 ASSERT(!m_initialized);
115 m_initialized = true;
117 m_role = determineAccessibilityRole();
120 Ref<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node)
122 return adoptRef(*new AccessibilityNodeObject(node));
125 void AccessibilityNodeObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache)
127 // AccessibilityObject calls clearChildren.
128 AccessibilityObject::detach(detachmentType, cache);
132 void AccessibilityNodeObject::childrenChanged()
134 // This method is meant as a quick way of marking a portion of the accessibility tree dirty.
135 if (!node() && !renderer())
138 AXObjectCache* cache = axObjectCache();
141 cache->postNotification(this, document(), AXObjectCache::AXChildrenChanged);
143 // Go up the accessibility parent chain, but only if the element already exists. This method is
144 // called during render layouts, minimal work should be done.
145 // If AX elements are created now, they could interrogate the render tree while it's in a funky state.
146 // At the same time, process ARIA live region changes.
147 for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) {
148 parent->setNeedsToUpdateChildren();
150 // These notifications always need to be sent because screenreaders are reliant on them to perform.
151 // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update.
153 // If this element supports ARIA live regions, then notify the AT of changes.
154 if (parent->supportsARIALiveRegion())
155 cache->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged);
157 // If this element is an ARIA text control, notify the AT of changes.
158 if ((parent->isARIATextControl() || parent->hasContentEditableAttributeSet()) && !parent->isNativeTextControl())
159 cache->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged);
163 void AccessibilityNodeObject::updateAccessibilityRole()
165 bool ignoredStatus = accessibilityIsIgnored();
166 m_role = determineAccessibilityRole();
168 // The AX hierarchy only needs to be updated if the ignored status of an element has changed.
169 if (ignoredStatus != accessibilityIsIgnored())
173 AccessibilityObject* AccessibilityNodeObject::firstChild() const
178 Node* firstChild = node()->firstChild();
183 return axObjectCache()->getOrCreate(firstChild);
186 AccessibilityObject* AccessibilityNodeObject::lastChild() const
191 Node* lastChild = node()->lastChild();
195 return axObjectCache()->getOrCreate(lastChild);
198 AccessibilityObject* AccessibilityNodeObject::previousSibling() const
203 Node* previousSibling = node()->previousSibling();
204 if (!previousSibling)
207 return axObjectCache()->getOrCreate(previousSibling);
210 AccessibilityObject* AccessibilityNodeObject::nextSibling() const
215 Node* nextSibling = node()->nextSibling();
219 return axObjectCache()->getOrCreate(nextSibling);
222 AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const
224 return parentObject();
227 AccessibilityObject* AccessibilityNodeObject::parentObject() const
232 Node* parentObj = node()->parentNode();
236 if (AXObjectCache* cache = axObjectCache())
237 return cache->getOrCreate(parentObj);
242 LayoutRect AccessibilityNodeObject::elementRect() const
244 return boundingBoxRect();
247 LayoutRect AccessibilityNodeObject::boundingBoxRect() const
249 // AccessibilityNodeObjects have no mechanism yet to return a size or position.
250 // For now, let's return the position of the ancestor that does have a position,
251 // 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.
253 LayoutRect boundingBox;
255 for (AccessibilityObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) {
256 if (positionProvider->isAccessibilityRenderObject()) {
257 LayoutRect parentRect = positionProvider->elementRect();
258 boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat()))));
259 boundingBox.setLocation(parentRect.location());
267 void AccessibilityNodeObject::setNode(Node* node)
272 Document* AccessibilityNodeObject::document() const
276 return &node()->document();
279 AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole()
284 if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole)
287 if (node()->isLink())
288 return WebCoreLinkRole;
289 if (node()->isTextNode())
290 return StaticTextRole;
291 if (node()->hasTagName(buttonTag))
292 return buttonRoleType();
293 if (is<HTMLInputElement>(*node())) {
294 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
295 if (input.isCheckbox())
297 if (input.isRadioButton())
298 return RadioButtonRole;
299 if (input.isTextButton())
300 return buttonRoleType();
301 if (input.isRangeControl())
303 if (input.isInputTypeHidden())
306 #if ENABLE(INPUT_TYPE_COLOR)
307 const AtomicString& type = input.getAttribute(typeAttr);
308 if (equalIgnoringCase(type, "color"))
309 return ColorWellRole;
312 return TextFieldRole;
314 if (node()->hasTagName(selectTag)) {
315 HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node());
316 return selectElement.multiple() ? ListBoxRole : PopUpButtonRole;
318 if (is<HTMLTextAreaElement>(*node()))
322 if (node()->hasTagName(blockquoteTag))
323 return BlockquoteRole;
324 if (node()->hasTagName(divTag))
326 if (node()->hasTagName(pTag))
327 return ParagraphRole;
328 if (is<HTMLLabelElement>(*node()))
330 if (is<Element>(*node()) && downcast<Element>(*node()).isFocusable())
336 void AccessibilityNodeObject::insertChild(AccessibilityObject* child, unsigned index)
341 // If the parent is asking for this child's children, then either it's the first time (and clearing is a no-op),
342 // or its visibility has changed. In the latter case, this child may have a stale child cached.
343 // This can prevent aria-hidden changes from working correctly. Hence, whenever a parent is getting children, ensure data is not stale.
344 child->clearChildren();
346 if (child->accessibilityIsIgnored()) {
347 const auto& children = child->children();
348 size_t length = children.size();
349 for (size_t i = 0; i < length; ++i)
350 m_children.insert(index + i, children[i]);
352 ASSERT(child->parentObject() == this);
353 m_children.insert(index, child);
357 void AccessibilityNodeObject::addChild(AccessibilityObject* child)
359 insertChild(child, m_children.size());
362 void AccessibilityNodeObject::addChildren()
364 // If the need to add more children in addition to existing children arises,
365 // childrenChanged should have been called, leaving the object with no children.
366 ASSERT(!m_haveChildren);
371 m_haveChildren = true;
373 // The only time we add children from the DOM tree to a node with a renderer is when it's a canvas.
374 if (renderer() && !m_node->hasTagName(canvasTag))
377 for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
378 addChild(axObjectCache()->getOrCreate(child));
381 bool AccessibilityNodeObject::canHaveChildren() const
383 // If this is an AccessibilityRenderObject, then it's okay if this object
384 // doesn't have a node - there are some renderers that don't have associated
385 // nodes, like scroll areas and css-generated text.
386 if (!node() && !isAccessibilityRenderObject())
389 // When <noscript> is not being used (its renderer() == 0), ignore its children.
390 if (node() && !renderer() && node()->hasTagName(noscriptTag))
393 // Elements that should not have children
394 switch (roleValue()) {
397 case PopUpButtonRole:
399 case RadioButtonRole:
401 case ToggleButtonRole:
403 case ListBoxOptionRole:
405 case ProgressIndicatorRole:
412 bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const
415 // Double-check that an AccessibilityObject is never accessed before
416 // it's been initialized.
417 ASSERT(m_initialized);
420 // Handle non-rendered text that is exposed through aria-hidden=false.
421 if (m_node && m_node->isTextNode() && !renderer()) {
422 // Fallback content in iframe nodes should be ignored.
423 if (m_node->parentNode() && m_node->parentNode()->hasTagName(iframeTag) && m_node->parentNode()->renderer())
426 // Whitespace only text elements should be ignored when they have no renderer.
427 String string = stringValue().stripWhiteSpace().simplifyWhiteSpace();
428 if (!string.length())
432 AccessibilityObjectInclusion decision = defaultObjectInclusion();
433 if (decision == IncludeObject)
435 if (decision == IgnoreObject)
437 // If this element is within a parent that cannot have children, it should not be exposed.
438 if (isDescendantOfBarrenParent())
441 if (roleValue() == IgnoredRole)
444 return m_role == UnknownRole;
447 bool AccessibilityNodeObject::canvasHasFallbackContent() const
449 Node* node = this->node();
450 if (!is<HTMLCanvasElement>(node))
452 HTMLCanvasElement& canvasElement = downcast<HTMLCanvasElement>(*node);
453 // If it has any children that are elements, we'll assume it might be fallback
454 // content. If it has no children or its only children are not elements
455 // (e.g. just text nodes), it doesn't have fallback content.
456 return childrenOfType<Element>(canvasElement).first();
459 bool AccessibilityNodeObject::isImageButton() const
461 return isNativeImage() && isButton();
464 bool AccessibilityNodeObject::isNativeTextControl() const
466 Node* node = this->node();
470 if (is<HTMLTextAreaElement>(*node))
473 if (is<HTMLInputElement>(*node)) {
474 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
475 return input.isText() || input.isNumberField();
481 bool AccessibilityNodeObject::isSearchField() const
483 Node* node = this->node();
487 HTMLInputElement* inputElement = node->toInputElement();
491 if (inputElement->isSearchField())
494 // Some websites don't label their search fields as such. However, they will
495 // use the word "search" in either the form or input type. This won't catch every case,
496 // but it will catch google.com for example.
498 // Check the node name of the input type, sometimes it's "search".
499 const AtomicString& nameAttribute = getAttribute(nameAttr);
500 if (nameAttribute.contains("search", false))
503 // Check the form action and the name, which will sometimes be "search".
504 HTMLFormElement* form = inputElement->form();
505 if (form && (form->name().contains("search", false) || form->action().contains("search", false)))
511 bool AccessibilityNodeObject::isNativeImage() const
513 Node* node = this->node();
517 if (is<HTMLImageElement>(*node))
520 if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag))
523 if (is<HTMLInputElement>(*node)) {
524 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
525 return input.isImageButton();
531 bool AccessibilityNodeObject::isImage() const
533 return roleValue() == ImageRole;
536 bool AccessibilityNodeObject::isPasswordField() const
538 Node* node = this->node();
539 if (!node || !node->isHTMLElement())
542 if (ariaRoleAttribute() != UnknownRole)
545 HTMLInputElement* inputElement = node->toInputElement();
549 return inputElement->isPasswordField();
552 bool AccessibilityNodeObject::isInputImage() const
554 Node* node = this->node();
555 if (is<HTMLInputElement>(node) && roleValue() == ButtonRole) {
556 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
557 return input.isImageButton();
563 bool AccessibilityNodeObject::isProgressIndicator() const
565 return roleValue() == ProgressIndicatorRole;
568 bool AccessibilityNodeObject::isSlider() const
570 return roleValue() == SliderRole;
573 bool AccessibilityNodeObject::isMenuRelated() const
575 switch (roleValue()) {
580 case MenuItemCheckboxRole:
581 case MenuItemRadioRole:
588 bool AccessibilityNodeObject::isMenu() const
590 return roleValue() == MenuRole;
593 bool AccessibilityNodeObject::isMenuBar() const
595 return roleValue() == MenuBarRole;
598 bool AccessibilityNodeObject::isMenuButton() const
600 return roleValue() == MenuButtonRole;
603 bool AccessibilityNodeObject::isMenuItem() const
605 switch (roleValue()) {
607 case MenuItemRadioRole:
608 case MenuItemCheckboxRole:
615 bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const
617 Node* node = this->node();
621 HTMLInputElement* input = node->toInputElement();
623 return input->isCheckbox() || input->isRadioButton();
628 bool AccessibilityNodeObject::isEnabled() const
630 // ARIA says that the disabled status applies to the current element and all descendant elements.
631 for (AccessibilityObject* object = const_cast<AccessibilityNodeObject*>(this); object; object = object->parentObject()) {
632 const AtomicString& disabledStatus = object->getAttribute(aria_disabledAttr);
633 if (equalIgnoringCase(disabledStatus, "true"))
635 if (equalIgnoringCase(disabledStatus, "false"))
639 if (roleValue() == HorizontalRuleRole)
642 Node* node = this->node();
643 if (!is<Element>(node))
646 return !downcast<Element>(*node).isDisabledFormControl();
649 bool AccessibilityNodeObject::isIndeterminate() const
651 Node* node = this->node();
655 HTMLInputElement* inputElement = node->toInputElement();
659 return inputElement->shouldAppearIndeterminate();
662 bool AccessibilityNodeObject::isPressed() const
667 Node* node = this->node();
671 // If this is an ARIA button, check the aria-pressed attribute rather than node()->active()
672 if (ariaRoleAttribute() == ButtonRole) {
673 if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true"))
678 if (!is<Element>(*node))
680 return downcast<Element>(*node).active();
683 bool AccessibilityNodeObject::isChecked() const
685 Node* node = this->node();
689 // First test for native checkedness semantics
690 HTMLInputElement* inputElement = node->toInputElement();
692 return inputElement->shouldAppearChecked();
694 // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute
695 bool validRole = false;
696 switch (ariaRoleAttribute()) {
697 case RadioButtonRole:
700 case MenuItemCheckboxRole:
701 case MenuItemRadioRole:
708 if (validRole && equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
714 bool AccessibilityNodeObject::isHovered() const
716 Node* node = this->node();
717 return is<Element>(node) && downcast<Element>(*node).hovered();
720 bool AccessibilityNodeObject::isMultiSelectable() const
722 const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
723 if (equalIgnoringCase(ariaMultiSelectable, "true"))
725 if (equalIgnoringCase(ariaMultiSelectable, "false"))
728 return node() && node()->hasTagName(selectTag) && downcast<HTMLSelectElement>(*node()).multiple();
731 bool AccessibilityNodeObject::isReadOnly() const
733 Node* node = this->node();
737 if (is<HTMLTextAreaElement>(*node))
738 return downcast<HTMLTextAreaElement>(*node).isReadOnly();
740 if (is<HTMLInputElement>(*node)) {
741 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
742 if (input.isTextField())
743 return input.isReadOnly();
746 return !node->hasEditableStyle();
749 bool AccessibilityNodeObject::isRequired() const
751 // Explicit aria-required values should trump native required attributes.
752 const AtomicString& requiredValue = getAttribute(aria_requiredAttr);
753 if (equalIgnoringCase(requiredValue, "true"))
755 if (equalIgnoringCase(requiredValue, "false"))
758 Node* n = this->node();
759 if (is<HTMLFormControlElement>(n))
760 return downcast<HTMLFormControlElement>(*n).isRequired();
765 bool AccessibilityNodeObject::supportsRequiredAttribute() const
767 switch (roleValue()) {
769 return isFileUploadButton();
774 case IncrementorRole:
776 case PopUpButtonRole:
777 case RadioButtonRole:
782 case TableHeaderContainerRole:
785 case ToggleButtonRole:
792 int AccessibilityNodeObject::headingLevel() const
794 // headings can be in block flow and non-block flow
795 Node* node = this->node();
800 int ariaLevel = getAttribute(aria_levelAttr).toInt();
805 if (node->hasTagName(h1Tag))
808 if (node->hasTagName(h2Tag))
811 if (node->hasTagName(h3Tag))
814 if (node->hasTagName(h4Tag))
817 if (node->hasTagName(h5Tag))
820 if (node->hasTagName(h6Tag))
826 String AccessibilityNodeObject::valueDescription() const
828 if (!isRangeControl())
831 return getAttribute(aria_valuetextAttr).string();
834 float AccessibilityNodeObject::valueForRange() const
836 if (is<HTMLInputElement>(node())) {
837 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
838 if (input.isRangeControl())
839 return input.valueAsNumber();
842 if (!isRangeControl())
845 return getAttribute(aria_valuenowAttr).toFloat();
848 float AccessibilityNodeObject::maxValueForRange() const
850 if (is<HTMLInputElement>(node())) {
851 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
852 if (input.isRangeControl())
853 return input.maximum();
856 if (!isRangeControl())
859 return getAttribute(aria_valuemaxAttr).toFloat();
862 float AccessibilityNodeObject::minValueForRange() const
864 if (is<HTMLInputElement>(node())) {
865 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
866 if (input.isRangeControl())
867 return input.minimum();
870 if (!isRangeControl())
873 return getAttribute(aria_valueminAttr).toFloat();
876 float AccessibilityNodeObject::stepValueForRange() const
878 return getAttribute(stepAttr).toFloat();
881 bool AccessibilityNodeObject::isHeading() const
883 return roleValue() == HeadingRole;
886 bool AccessibilityNodeObject::isLink() const
888 return roleValue() == WebCoreLinkRole;
891 bool AccessibilityNodeObject::isControl() const
893 Node* node = this->node();
897 return is<HTMLFormControlElement>(*node) || AccessibilityObject::isARIAControl(ariaRoleAttribute());
900 bool AccessibilityNodeObject::isFieldset() const
902 Node* node = this->node();
906 return node->hasTagName(fieldsetTag);
909 bool AccessibilityNodeObject::isGroup() const
911 return roleValue() == GroupRole;
914 AccessibilityObject* AccessibilityNodeObject::selectedRadioButton()
919 // Find the child radio button that is selected (ie. the intValue == 1).
920 for (const auto& child : children()) {
921 if (child->roleValue() == RadioButtonRole && child->checkboxOrRadioValue() == ButtonStateOn)
927 AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
932 // Find the child tab item that is selected (ie. the intValue == 1).
933 AccessibilityObject::AccessibilityChildrenVector tabs;
936 for (const auto& child : children()) {
937 if (child->isTabItem() && child->isChecked())
943 AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const
945 if (isNativeCheckboxOrRadio())
946 return isChecked() ? ButtonStateOn : ButtonStateOff;
948 return AccessibilityObject::checkboxOrRadioValue();
951 Element* AccessibilityNodeObject::anchorElement() const
953 Node* node = this->node();
957 AXObjectCache* cache = axObjectCache();
959 // search up the DOM tree for an anchor element
960 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
961 for ( ; node; node = node->parentNode()) {
962 if (is<HTMLAnchorElement>(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isLink()))
963 return downcast<Element>(node);
969 static bool isNodeActionElement(Node* node)
971 if (is<HTMLInputElement>(*node)) {
972 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
973 if (!input.isDisabledFormControl() && (input.isRadioButton() || input.isCheckbox() || input.isTextButton() || input.isFileUpload() || input.isImageButton()))
975 } else if (node->hasTagName(buttonTag) || node->hasTagName(selectTag))
981 static Element* nativeActionElement(Node* start)
986 // Do a deep-dive to see if any nodes should be used as the action element.
987 // We have to look at Nodes, since this method should only be called on objects that do not have children (like buttons).
988 // It solves the problem when authors put role="button" on a group and leave the actual button inside the group.
990 for (Node* child = start->firstChild(); child; child = child->nextSibling()) {
991 if (isNodeActionElement(child))
992 return downcast<Element>(child);
994 if (Element* subChild = nativeActionElement(child))
1000 Element* AccessibilityNodeObject::actionElement() const
1002 Node* node = this->node();
1006 if (isNodeActionElement(node))
1007 return downcast<Element>(node);
1009 if (AccessibilityObject::isARIAInput(ariaRoleAttribute()))
1010 return downcast<Element>(node);
1012 switch (roleValue()) {
1014 case PopUpButtonRole:
1015 case ToggleButtonRole:
1018 case MenuItemCheckboxRole:
1019 case MenuItemRadioRole:
1021 // Check if the author is hiding the real control element inside the ARIA element.
1022 if (Element* nativeElement = nativeActionElement(node))
1023 return nativeElement;
1024 return downcast<Element>(node);
1029 Element* elt = anchorElement();
1031 elt = mouseButtonListener();
1035 Element* AccessibilityNodeObject::mouseButtonListener(MouseButtonListenerResultFilter filter) const
1037 Node* node = this->node();
1041 // check if our parent is a mouse button listener
1042 // FIXME: Do the continuation search like anchorElement does
1043 for (auto& element : elementLineage(is<Element>(*node) ? downcast<Element>(node) : node->parentElement())) {
1044 // If we've reached the body and this is not a control element, do not expose press action for this element unless filter is IncludeBodyElement.
1045 // It can cause false positives, where every piece of text is labeled as accepting press actions.
1046 if (element.hasTagName(bodyTag) && isStaticText() && filter == ExcludeBodyElement)
1049 if (element.hasEventListeners(eventNames().clickEvent) || element.hasEventListeners(eventNames().mousedownEvent) || element.hasEventListeners(eventNames().mouseupEvent))
1056 bool AccessibilityNodeObject::isDescendantOfBarrenParent() const
1058 for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) {
1059 if (!object->canHaveChildren())
1066 void AccessibilityNodeObject::alterSliderValue(bool increase)
1068 if (roleValue() != SliderRole)
1071 if (!getAttribute(stepAttr).isEmpty())
1072 changeValueByStep(increase);
1074 changeValueByPercent(increase ? 5 : -5);
1077 void AccessibilityNodeObject::increment()
1079 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture, document());
1080 alterSliderValue(true);
1083 void AccessibilityNodeObject::decrement()
1085 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture, document());
1086 alterSliderValue(false);
1089 void AccessibilityNodeObject::changeValueByStep(bool increase)
1091 float step = stepValueForRange();
1092 float value = valueForRange();
1094 value += increase ? step : -step;
1096 setValue(String::number(value));
1098 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged);
1101 void AccessibilityNodeObject::changeValueByPercent(float percentChange)
1103 float range = maxValueForRange() - minValueForRange();
1104 float step = range * (percentChange / 100);
1105 float value = valueForRange();
1107 // Make sure the specified percent will cause a change of one integer step or larger.
1109 step = fabs(percentChange) * (1 / percentChange);
1112 setValue(String::number(value));
1114 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged);
1117 bool AccessibilityNodeObject::isGenericFocusableElement() const
1119 if (!canSetFocusAttribute())
1122 // If it's a control, it's not generic.
1126 AccessibilityRole role = roleValue();
1127 if (role == VideoRole || role == AudioRole)
1130 // If it has an aria role, it's not generic.
1131 if (m_ariaRole != UnknownRole)
1134 // If the content editable attribute is set on this element, that's the reason
1135 // it's focusable, and existing logic should handle this case already - so it's not a
1136 // generic focusable element.
1138 if (hasContentEditableAttributeSet())
1141 // The web area and body element are both focusable, but existing logic handles these
1142 // cases already, so we don't need to include them here.
1143 if (role == WebAreaRole)
1145 if (node() && node()->hasTagName(bodyTag))
1148 // An SVG root is focusable by default, but it's probably not interactive, so don't
1149 // include it. It can still be made accessible by giving it an ARIA role.
1150 if (role == SVGRootRole)
1156 HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const
1158 if (!is<HTMLElement>(*element) || !downcast<HTMLElement>(*element).isLabelable())
1161 const AtomicString& id = element->getIdAttribute();
1162 if (!id.isEmpty()) {
1163 if (HTMLLabelElement* label = element->treeScope().labelElementForId(id))
1167 return ancestorsOfType<HTMLLabelElement>(*element).first();
1170 String AccessibilityNodeObject::ariaAccessibilityDescription() const
1172 String ariaLabeledBy = ariaLabeledByAttribute();
1173 if (!ariaLabeledBy.isEmpty())
1174 return ariaLabeledBy;
1176 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1177 if (!ariaLabel.isEmpty())
1183 static Element* siblingWithAriaRole(String role, Node* node)
1185 ContainerNode* parent = node->parentNode();
1189 for (auto& sibling : childrenOfType<Element>(*parent)) {
1190 const AtomicString& siblingAriaRole = sibling.fastGetAttribute(roleAttr);
1191 if (equalIgnoringCase(siblingAriaRole, role))
1198 Element* AccessibilityNodeObject::menuElementForMenuButton() const
1200 if (ariaRoleAttribute() != MenuButtonRole)
1203 return siblingWithAriaRole("menu", node());
1206 AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const
1208 if (AXObjectCache* cache = axObjectCache())
1209 return cache->getOrCreate(menuElementForMenuButton());
1213 Element* AccessibilityNodeObject::menuItemElementForMenu() const
1215 if (ariaRoleAttribute() != MenuRole)
1218 return siblingWithAriaRole("menuitem", node());
1221 AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
1223 AXObjectCache* cache = axObjectCache();
1227 Element* menuItem = menuItemElementForMenu();
1230 // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem
1231 AccessibilityObject* menuItemAX = cache->getOrCreate(menuItem);
1232 if (menuItemAX && menuItemAX->isMenuButton())
1238 bool AccessibilityNodeObject::usesAltTagForTextComputation() const
1240 return isImage() || isInputImage() || isNativeImage() || isCanvas() || (node() && node()->hasTagName(imgTag));
1243 void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder) const
1245 Node* node = this->node();
1249 bool isInputTag = is<HTMLInputElement>(*node);
1250 if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
1251 if (HTMLLabelElement* label = labelForElement(downcast<Element>(node))) {
1252 AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label);
1253 String innerText = label->innerText();
1254 // Only use the <label> text if there's no ARIA override.
1255 if (!innerText.isEmpty() && !ariaAccessibilityDescription())
1256 textOrder.append(AccessibilityText(innerText, LabelByElementText, labelObject));
1261 AccessibilityObject* titleUIElement = this->titleUIElement();
1263 textOrder.append(AccessibilityText(String(), LabelByElementText, titleUIElement));
1266 void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
1269 String webAreaText = alternativeTextForWebArea();
1270 if (!webAreaText.isEmpty())
1271 textOrder.append(AccessibilityText(webAreaText, AlternativeText));
1275 ariaLabeledByText(textOrder);
1277 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1278 if (!ariaLabel.isEmpty())
1279 textOrder.append(AccessibilityText(ariaLabel, AlternativeText));
1281 if (usesAltTagForTextComputation()) {
1282 if (is<RenderImage>(renderer())) {
1283 String renderAltText = downcast<RenderImage>(*renderer()).altText();
1285 // RenderImage will return title as a fallback from altText, but we don't want title here because we consider that in helpText.
1286 if (!renderAltText.isEmpty() && renderAltText != getAttribute(titleAttr)) {
1287 textOrder.append(AccessibilityText(renderAltText, AlternativeText));
1291 // Images should use alt as long as the attribute is present, even if empty.
1292 // Otherwise, it should fallback to other methods, like the title attribute.
1293 const AtomicString& alt = getAttribute(altAttr);
1295 textOrder.append(AccessibilityText(alt, AlternativeText));
1298 Node* node = this->node();
1302 // The fieldset element derives its alternative text from the first associated legend element if one is available.
1303 if (is<HTMLFieldSetElement>(*node)) {
1304 AccessibilityObject* object = axObjectCache()->getOrCreate(downcast<HTMLFieldSetElement>(*node).legend());
1305 if (object && !object->isHidden())
1306 textOrder.append(AccessibilityText(accessibleNameForNode(object->node()), AlternativeText));
1309 // SVG elements all can have a <svg:title> element inside which should act as the descriptive text.
1310 if (node->isSVGElement())
1311 textOrder.append(AccessibilityText(downcast<SVGElement>(*node).title(), AlternativeText));
1314 if (node->isMathMLElement())
1315 textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AlternativeText));
1319 void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) const
1321 Node* node = this->node();
1325 bool isInputTag = is<HTMLInputElement>(*node);
1327 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
1328 if (input.isTextButton()) {
1329 textOrder.append(AccessibilityText(input.valueWithDefault(), VisibleText));
1334 // If this node isn't rendered, there's no inner text we can extract from a select element.
1335 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1338 bool useTextUnderElement = false;
1340 switch (roleValue()) {
1341 case PopUpButtonRole:
1342 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1343 if (node->hasTagName(selectTag))
1347 case ToggleButtonRole:
1349 case ListBoxOptionRole:
1350 // MacOS does not expect native <li> elements to expose label information, it only expects leaf node elements to do that.
1351 #if !PLATFORM(COCOA)
1354 case MenuButtonRole:
1356 case MenuItemCheckboxRole:
1357 case MenuItemRadioRole:
1358 case RadioButtonRole:
1360 case ProgressIndicatorRole:
1361 useTextUnderElement = true;
1367 // If it's focusable but it's not content editable or a known control type, then it will appear to
1368 // the user as a single atomic object, so we should use its text as the default title.
1369 if (isHeading() || isLink())
1370 useTextUnderElement = true;
1372 if (useTextUnderElement) {
1373 AccessibilityTextUnderElementMode mode;
1375 // Headings often include links as direct children. Those links need to be included in text under element.
1377 mode.includeFocusableContent = true;
1379 String text = textUnderElement(mode);
1380 if (!text.isEmpty())
1381 textOrder.append(AccessibilityText(text, ChildrenText));
1385 void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) const
1387 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1388 if (!ariaHelp.isEmpty())
1389 textOrder.append(AccessibilityText(ariaHelp, HelpText));
1391 String describedBy = ariaDescribedByAttribute();
1392 if (!describedBy.isEmpty())
1393 textOrder.append(AccessibilityText(describedBy, SummaryText));
1395 // Summary attribute used as help text on tables.
1396 const AtomicString& summary = getAttribute(summaryAttr);
1397 if (!summary.isEmpty())
1398 textOrder.append(AccessibilityText(summary, SummaryText));
1400 // The title attribute should be used as help text unless it is already being used as descriptive text.
1401 const AtomicString& title = getAttribute(titleAttr);
1402 if (!title.isEmpty())
1403 textOrder.append(AccessibilityText(title, TitleTagText));
1406 void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textOrder)
1408 titleElementText(textOrder);
1409 alternativeText(textOrder);
1410 visibleText(textOrder);
1411 helpText(textOrder);
1413 String placeholder = placeholderValue();
1414 if (!placeholder.isEmpty())
1415 textOrder.append(AccessibilityText(placeholder, PlaceholderText));
1418 void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
1420 String ariaLabeledBy = ariaLabeledByAttribute();
1421 if (!ariaLabeledBy.isEmpty()) {
1422 Vector<Element*> elements;
1423 ariaLabeledByElements(elements);
1425 Vector<RefPtr<AccessibilityObject>> axElements;
1426 for (const auto& element : elements) {
1427 RefPtr<AccessibilityObject> axElement = axObjectCache()->getOrCreate(element);
1428 axElements.append(axElement);
1431 textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, WTF::move(axElements)));
1435 String AccessibilityNodeObject::alternativeTextForWebArea() const
1437 // The WebArea description should follow this order:
1438 // aria-label on the <html>
1439 // title on the <html>
1440 // <title> inside the <head> (of it was set through JS)
1441 // name on the <html>
1443 // aria-label on the <iframe>
1444 // title on the <iframe>
1445 // name on the <iframe>
1447 Document* document = this->document();
1451 // Check if the HTML element has an aria-label for the webpage.
1452 if (Element* documentElement = document->documentElement()) {
1453 const AtomicString& ariaLabel = documentElement->fastGetAttribute(aria_labelAttr);
1454 if (!ariaLabel.isEmpty())
1458 if (auto* owner = document->ownerElement()) {
1459 if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
1460 const AtomicString& title = owner->fastGetAttribute(titleAttr);
1461 if (!title.isEmpty())
1464 return owner->getNameAttribute();
1467 String documentTitle = document->title();
1468 if (!documentTitle.isEmpty())
1469 return documentTitle;
1471 if (auto* body = document->bodyOrFrameset())
1472 return body->getNameAttribute();
1477 String AccessibilityNodeObject::accessibilityDescription() const
1479 // Static text should not have a description, it should only have a stringValue.
1480 if (roleValue() == StaticTextRole)
1483 String ariaDescription = ariaAccessibilityDescription();
1484 if (!ariaDescription.isEmpty())
1485 return ariaDescription;
1487 if (usesAltTagForTextComputation()) {
1488 // Images should use alt as long as the attribute is present, even if empty.
1489 // Otherwise, it should fallback to other methods, like the title attribute.
1490 const AtomicString& alt = getAttribute(altAttr);
1495 // SVG elements all can have a <svg:title> element inside which should act as the descriptive text.
1496 if (m_node && m_node->isSVGElement())
1497 return downcast<SVGElement>(*m_node).title();
1500 if (is<MathMLElement>(m_node))
1501 return getAttribute(MathMLNames::alttextAttr);
1504 // An element's descriptive text is comprised of title() (what's visible on the screen) and accessibilityDescription() (other descriptive text).
1505 // Both are used to generate what a screen reader speaks.
1506 // If this point is reached (i.e. there's no accessibilityDescription) and there's no title(), we should fallback to using the title attribute.
1507 // 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).
1508 if (title().isEmpty())
1509 return getAttribute(titleAttr);
1514 String AccessibilityNodeObject::helpText() const
1516 Node* node = this->node();
1520 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1521 if (!ariaHelp.isEmpty())
1524 String describedBy = ariaDescribedByAttribute();
1525 if (!describedBy.isEmpty())
1528 String description = accessibilityDescription();
1529 for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
1530 if (is<HTMLElement>(*ancestor)) {
1531 HTMLElement& element = downcast<HTMLElement>(*ancestor);
1532 const AtomicString& summary = element.getAttribute(summaryAttr);
1533 if (!summary.isEmpty())
1536 // The title attribute should be used as help text unless it is already being used as descriptive text.
1537 const AtomicString& title = element.getAttribute(titleAttr);
1538 if (!title.isEmpty() && description != title)
1542 // Only take help text from an ancestor element if its a group or an unknown role. If help was
1543 // added to those kinds of elements, it is likely it was meant for a child element.
1544 AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor);
1546 AccessibilityRole role = axObj->roleValue();
1547 if (role != GroupRole && role != UnknownRole)
1555 unsigned AccessibilityNodeObject::hierarchicalLevel() const
1557 Node* node = this->node();
1558 if (!is<Element>(node))
1560 Element& element = downcast<Element>(*node);
1561 const AtomicString& ariaLevel = element.fastGetAttribute(aria_levelAttr);
1562 if (!ariaLevel.isEmpty())
1563 return ariaLevel.toInt();
1565 // Only tree item will calculate its level through the DOM currently.
1566 if (roleValue() != TreeItemRole)
1569 // Hierarchy leveling starts at 1, to match the aria-level spec.
1570 // We measure tree hierarchy by the number of groups that the item is within.
1572 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
1573 AccessibilityRole parentRole = parent->roleValue();
1574 if (parentRole == GroupRole)
1576 else if (parentRole == TreeRole)
1583 // When building the textUnderElement for an object, determine whether or not
1584 // we should include the inner text of this given descendant object or skip it.
1585 static bool shouldUseAccessibilityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode)
1587 // Do not use any heuristic if we are explicitly asking to include all the children.
1588 if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)
1591 // Consider this hypothetical example:
1594 // Table of contents
1596 // <a href="#start">Jump to start of book</a>
1598 // <li><a href="#1">Chapter 1</a></li>
1599 // <li><a href="#1">Chapter 2</a></li>
1603 // The goal is to return a reasonable title for the outer container div, because
1604 // it's focusable - but without making its title be the full inner text, which is
1605 // quite long. As a heuristic, skip links, controls, and elements that are usually
1606 // containers with lots of children.
1608 // ARIA states that certain elements are not allowed to expose their children content for name calculation.
1609 if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren
1610 && !obj->accessibleNameDerivesFromContent())
1613 if (equalIgnoringCase(obj->getAttribute(aria_hiddenAttr), "true"))
1616 // If something doesn't expose any children, then we can always take the inner text content.
1617 // This is what we want when someone puts an <a> inside a <button> for example.
1618 if (obj->isDescendantOfBarrenParent())
1621 // Skip focusable children, so we don't include the text of links and controls.
1622 if (obj->canSetFocusAttribute() && !mode.includeFocusableContent)
1625 // Skip big container elements like lists, tables, etc.
1626 if (is<AccessibilityList>(*obj))
1629 if (is<AccessibilityTable>(*obj) && downcast<AccessibilityTable>(*obj).isExposableThroughAccessibility())
1632 if (obj->isTree() || obj->isCanvas())
1638 static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, const String& childText)
1640 if (!builder.length() || !childText.length())
1643 // We don't need to add an additional space before or after a line break.
1644 return !(isHTMLLineBreak(childText[0]) || isHTMLLineBreak(builder[builder.length() - 1]));
1647 static void appendNameToStringBuilder(StringBuilder& builder, const String& text)
1649 if (shouldAddSpaceBeforeAppendingNextElement(builder, text))
1650 builder.append(' ');
1651 builder.append(text);
1654 String AccessibilityNodeObject::textUnderElement(AccessibilityTextUnderElementMode mode) const
1656 Node* node = this->node();
1658 return downcast<Text>(*node).wholeText();
1660 // The render tree should be stable before going ahead. Otherwise, further uses of the
1661 // TextIterator will force a layout update, potentially altering the accessibility tree
1662 // and leading to crashes in the loop that computes the result text from the children.
1663 ASSERT(!document()->renderView()->layoutState());
1664 ASSERT(!document()->childNeedsStyleRecalc());
1666 StringBuilder builder;
1667 for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
1668 if (mode.ignoredChildNode && child->node() == mode.ignoredChildNode)
1671 bool shouldDeriveNameFromAuthor = (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren && !child->accessibleNameDerivesFromContent());
1672 if (shouldDeriveNameFromAuthor) {
1673 appendNameToStringBuilder(builder, accessibleNameForNode(child->node()));
1677 if (!shouldUseAccessibilityObjectInnerText(child, mode))
1680 if (is<AccessibilityNodeObject>(*child)) {
1681 Vector<AccessibilityText> textOrder;
1682 downcast<AccessibilityNodeObject>(*child).alternativeText(textOrder);
1683 if (textOrder.size() > 0 && textOrder[0].text.length()) {
1684 appendNameToStringBuilder(builder, textOrder[0].text);
1689 String childText = child->textUnderElement(mode);
1690 if (childText.length())
1691 appendNameToStringBuilder(builder, childText);
1694 return builder.toString().stripWhiteSpace().simplifyWhiteSpace(isHTMLSpaceButNotLineBreak);
1697 String AccessibilityNodeObject::title() const
1699 Node* node = this->node();
1703 bool isInputTag = is<HTMLInputElement>(*node);
1705 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
1706 if (input.isTextButton())
1707 return input.valueWithDefault();
1710 if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
1711 HTMLLabelElement* label = labelForElement(downcast<Element>(node));
1712 // Use the label text as the title if 1) the title element is NOT an exposed element and 2) there's no ARIA override.
1713 if (label && !exposesTitleUIElement() && !ariaAccessibilityDescription().length())
1714 return label->innerText();
1717 // If this node isn't rendered, there's no inner text we can extract from a select element.
1718 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1721 switch (roleValue()) {
1722 case PopUpButtonRole:
1723 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1724 if (node->hasTagName(selectTag))
1728 case ToggleButtonRole:
1730 case ListBoxOptionRole:
1732 case MenuButtonRole:
1734 case MenuItemCheckboxRole:
1735 case MenuItemRadioRole:
1736 case RadioButtonRole:
1738 return textUnderElement();
1739 // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>.
1747 return textUnderElement();
1749 return textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeSkipIgnoredChildren, true));
1754 String AccessibilityNodeObject::text() const
1756 // If this is a user defined static text, use the accessible name computation.
1757 if (ariaRoleAttribute() == StaticTextRole) {
1758 Vector<AccessibilityText> textOrder;
1759 alternativeText(textOrder);
1760 if (textOrder.size() > 0 && textOrder[0].text.length())
1761 return textOrder[0].text;
1764 if (!isTextControl())
1767 Node* node = this->node();
1771 if (isNativeTextControl() && is<HTMLTextFormControlElement>(*node))
1772 return downcast<HTMLTextFormControlElement>(*node).value();
1774 if (!node->isElementNode())
1777 return downcast<Element>(node)->innerText();
1780 String AccessibilityNodeObject::stringValue() const
1782 Node* node = this->node();
1786 if (ariaRoleAttribute() == StaticTextRole) {
1787 String staticText = text();
1788 if (!staticText.length())
1789 staticText = textUnderElement();
1793 if (node->isTextNode())
1794 return textUnderElement();
1796 if (node->hasTagName(selectTag)) {
1797 HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node);
1798 int selectedIndex = selectElement.selectedIndex();
1799 const Vector<HTMLElement*>& listItems = selectElement.listItems();
1800 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
1801 const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr);
1802 if (!overriddenDescription.isNull())
1803 return overriddenDescription;
1805 if (!selectElement.multiple())
1806 return selectElement.value();
1810 if (isTextControl())
1813 // FIXME: We might need to implement a value here for more types
1814 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
1815 // this would require subclassing or making accessibilityAttributeNames do something other than return a
1816 // single static array.
1820 void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const
1829 if (!is<HTMLInputElement>(node()))
1832 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
1833 const AtomicString& type = input.getAttribute(typeAttr);
1834 if (!equalIgnoringCase(type, "color"))
1837 // HTMLInputElement::value always returns a string parseable by Color().
1838 Color color(input.value());
1844 // This function implements the ARIA accessible name as described by the Mozilla
1845 // ARIA Implementer's Guide.
1846 static String accessibleNameForNode(Node* node, Node* labelledbyNode)
1849 if (!is<Element>(node))
1852 Element& element = downcast<Element>(*node);
1853 const AtomicString& ariaLabel = element.fastGetAttribute(aria_labelAttr);
1854 if (!ariaLabel.isEmpty())
1857 const AtomicString& alt = element.fastGetAttribute(altAttr);
1861 // If the node can be turned into an AX object, we can use standard name computation rules.
1862 // If however, the node cannot (because there's no renderer e.g.) fallback to using the basic text underneath.
1863 AccessibilityObject* axObject = node->document().axObjectCache()->getOrCreate(node);
1865 String valueDescription = axObject->valueDescription();
1866 if (!valueDescription.isEmpty())
1867 return valueDescription;
1870 if (is<HTMLInputElement>(*node))
1871 return downcast<HTMLInputElement>(*node).value();
1875 if (axObject->accessibleNameDerivesFromContent())
1876 text = axObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren, true, labelledbyNode));
1878 text = element.innerText();
1880 if (!text.isEmpty())
1883 const AtomicString& title = element.fastGetAttribute(titleAttr);
1884 if (!title.isEmpty())
1890 String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
1892 StringBuilder builder;
1893 unsigned size = elements.size();
1894 for (unsigned i = 0; i < size; ++i)
1895 appendNameToStringBuilder(builder, accessibleNameForNode(elements[i], node()));
1896 return builder.toString();
1899 String AccessibilityNodeObject::ariaDescribedByAttribute() const
1901 Vector<Element*> elements;
1902 elementsFromAttribute(elements, aria_describedbyAttr);
1904 return accessibilityDescriptionForElements(elements);
1907 void AccessibilityNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const
1909 elementsFromAttribute(elements, aria_labelledbyAttr);
1910 if (!elements.size())
1911 elementsFromAttribute(elements, aria_labeledbyAttr);
1915 String AccessibilityNodeObject::ariaLabeledByAttribute() const
1917 Vector<Element*> elements;
1918 ariaLabeledByElements(elements);
1920 return accessibilityDescriptionForElements(elements);
1923 bool AccessibilityNodeObject::hasAttributesRequiredForInclusion() const
1925 if (AccessibilityObject::hasAttributesRequiredForInclusion())
1928 if (!ariaAccessibilityDescription().isEmpty())
1934 bool AccessibilityNodeObject::canSetFocusAttribute() const
1936 Node* node = this->node();
1943 // NOTE: It would be more accurate to ask the document whether setFocusedElement() would
1944 // do anything. For example, setFocusedElement() will do nothing if the current focused
1945 // node will not relinquish the focus.
1946 if (!is<Element>(node))
1949 Element& element = downcast<Element>(*node);
1951 if (element.isDisabledFormControl())
1954 return element.supportsFocus();
1957 AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const
1959 const AtomicString& ariaRole = getAttribute(roleAttr);
1960 if (ariaRole.isNull() || ariaRole.isEmpty())
1963 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
1965 // ARIA states if an item can get focus, it should not be presentational.
1966 if (role == PresentationalRole && canSetFocusAttribute())
1969 if (role == ButtonRole)
1970 role = buttonRoleType();
1972 if (role == TextAreaRole && !ariaIsMultiline())
1973 role = TextFieldRole;
1975 role = remapAriaRoleDueToParent(role);
1977 // Presentational roles are invalidated by the presence of ARIA attributes.
1978 if (role == PresentationalRole && supportsARIAAttributes())
1987 AccessibilityRole AccessibilityNodeObject::ariaRoleAttribute() const
1992 AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
1994 // Some objects change their role based on their parent.
1995 // However, asking for the unignoredParent calls accessibilityIsIgnored(), which can trigger a loop.
1996 // While inside the call stack of creating an element, we need to avoid accessibilityIsIgnored().
1997 // https://bugs.webkit.org/show_bug.cgi?id=65174
1999 if (role != ListBoxOptionRole && role != MenuItemRole)
2002 for (AccessibilityObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
2003 AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
2005 // Selects and listboxes both have options as child roles, but they map to different roles within WebCore.
2006 if (role == ListBoxOptionRole && parentAriaRole == MenuRole)
2007 return MenuItemRole;
2008 // An aria "menuitem" may map to MenuButton or MenuItem depending on its parent.
2009 if (role == MenuItemRole && parentAriaRole == GroupRole)
2010 return MenuButtonRole;
2012 // If the parent had a different role, then we don't need to continue searching up the chain.
2020 bool AccessibilityNodeObject::canSetSelectedAttribute() const
2022 // Elements that can be selected
2023 switch (roleValue()) {
2025 case RadioButtonRole:
2039 } // namespace WebCore