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 "HTMLDetailsElement.h"
48 #include "HTMLFieldSetElement.h"
49 #include "HTMLFormElement.h"
50 #include "HTMLFrameElementBase.h"
51 #include "HTMLImageElement.h"
52 #include "HTMLInputElement.h"
53 #include "HTMLLabelElement.h"
54 #include "HTMLLegendElement.h"
55 #include "HTMLMapElement.h"
56 #include "HTMLNames.h"
57 #include "HTMLOptGroupElement.h"
58 #include "HTMLOptionElement.h"
59 #include "HTMLOptionsCollection.h"
60 #include "HTMLParserIdioms.h"
61 #include "HTMLPlugInImageElement.h"
62 #include "HTMLSelectElement.h"
63 #include "HTMLTextAreaElement.h"
64 #include "HTMLTextFormControlElement.h"
65 #include "HitTestRequest.h"
66 #include "HitTestResult.h"
67 #include "LabelableElement.h"
68 #include "LocalizedStrings.h"
69 #include "MathMLElement.h"
70 #include "MathMLNames.h"
72 #include "NodeTraversal.h"
74 #include "ProgressTracker.h"
75 #include "RenderImage.h"
76 #include "RenderView.h"
77 #include "SVGElement.h"
80 #include "TextControlInnerElements.h"
81 #include "UserGestureIndicator.h"
82 #include "VisibleUnits.h"
84 #include "htmlediting.h"
85 #include <wtf/StdLibExtras.h>
86 #include <wtf/text/StringBuilder.h>
87 #include <wtf/unicode/CharacterNames.h>
91 using namespace HTMLNames;
93 static String accessibleNameForNode(Node* node, Node* labelledbyNode = nullptr);
95 AccessibilityNodeObject::AccessibilityNodeObject(Node* node)
96 : AccessibilityObject()
97 , m_ariaRole(UnknownRole)
98 , m_childrenDirty(false)
99 , m_roleForMSAA(UnknownRole)
101 , m_initialized(false)
107 AccessibilityNodeObject::~AccessibilityNodeObject()
109 ASSERT(isDetached());
112 void AccessibilityNodeObject::init()
115 ASSERT(!m_initialized);
116 m_initialized = true;
118 m_role = determineAccessibilityRole();
121 Ref<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node)
123 return adoptRef(*new AccessibilityNodeObject(node));
126 void AccessibilityNodeObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache)
128 // AccessibilityObject calls clearChildren.
129 AccessibilityObject::detach(detachmentType, cache);
133 void AccessibilityNodeObject::childrenChanged()
135 // This method is meant as a quick way of marking a portion of the accessibility tree dirty.
136 if (!node() && !renderer())
139 AXObjectCache* cache = axObjectCache();
142 cache->postNotification(this, document(), AXObjectCache::AXChildrenChanged);
144 // Go up the accessibility parent chain, but only if the element already exists. This method is
145 // called during render layouts, minimal work should be done.
146 // If AX elements are created now, they could interrogate the render tree while it's in a funky state.
147 // At the same time, process ARIA live region changes.
148 for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) {
149 parent->setNeedsToUpdateChildren();
151 // These notifications always need to be sent because screenreaders are reliant on them to perform.
152 // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update.
154 // If this element supports ARIA live regions, then notify the AT of changes.
155 if (parent->supportsARIALiveRegion())
156 cache->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged);
158 // If this element is an ARIA text control, notify the AT of changes.
159 if ((parent->isARIATextControl() || parent->hasContentEditableAttributeSet()) && !parent->isNativeTextControl())
160 cache->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged);
164 void AccessibilityNodeObject::updateAccessibilityRole()
166 bool ignoredStatus = accessibilityIsIgnored();
167 m_role = determineAccessibilityRole();
169 // The AX hierarchy only needs to be updated if the ignored status of an element has changed.
170 if (ignoredStatus != accessibilityIsIgnored())
174 AccessibilityObject* AccessibilityNodeObject::firstChild() const
179 Node* firstChild = node()->firstChild();
184 return axObjectCache()->getOrCreate(firstChild);
187 AccessibilityObject* AccessibilityNodeObject::lastChild() const
192 Node* lastChild = node()->lastChild();
196 return axObjectCache()->getOrCreate(lastChild);
199 AccessibilityObject* AccessibilityNodeObject::previousSibling() const
204 Node* previousSibling = node()->previousSibling();
205 if (!previousSibling)
208 return axObjectCache()->getOrCreate(previousSibling);
211 AccessibilityObject* AccessibilityNodeObject::nextSibling() const
216 Node* nextSibling = node()->nextSibling();
220 return axObjectCache()->getOrCreate(nextSibling);
223 AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const
225 return parentObject();
228 AccessibilityObject* AccessibilityNodeObject::parentObject() const
233 Node* parentObj = node()->parentNode();
237 if (AXObjectCache* cache = axObjectCache())
238 return cache->getOrCreate(parentObj);
243 LayoutRect AccessibilityNodeObject::elementRect() const
245 return boundingBoxRect();
248 LayoutRect AccessibilityNodeObject::boundingBoxRect() const
250 // AccessibilityNodeObjects have no mechanism yet to return a size or position.
251 // For now, let's return the position of the ancestor that does have a position,
252 // 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.
254 LayoutRect boundingBox;
256 for (AccessibilityObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) {
257 if (positionProvider->isAccessibilityRenderObject()) {
258 LayoutRect parentRect = positionProvider->elementRect();
259 boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat()))));
260 boundingBox.setLocation(parentRect.location());
268 void AccessibilityNodeObject::setNode(Node* node)
273 Document* AccessibilityNodeObject::document() const
277 return &node()->document();
280 AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole()
285 if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole)
288 if (node()->isLink())
289 return WebCoreLinkRole;
290 if (node()->isTextNode())
291 return StaticTextRole;
292 if (node()->hasTagName(buttonTag))
293 return buttonRoleType();
294 if (is<HTMLInputElement>(*node())) {
295 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
296 if (input.isCheckbox())
298 if (input.isRadioButton())
299 return RadioButtonRole;
300 if (input.isTextButton())
301 return buttonRoleType();
302 if (input.isRangeControl())
304 if (input.isInputTypeHidden())
306 if (input.isSearchField())
307 return SearchFieldRole;
309 #if ENABLE(INPUT_TYPE_COLOR)
310 const AtomicString& type = input.getAttribute(typeAttr);
311 if (equalIgnoringCase(type, "color"))
312 return ColorWellRole;
315 return TextFieldRole;
317 if (node()->hasTagName(selectTag)) {
318 HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node());
319 return selectElement.multiple() ? ListBoxRole : PopUpButtonRole;
321 if (is<HTMLTextAreaElement>(*node()))
325 if (node()->hasTagName(blockquoteTag))
326 return BlockquoteRole;
327 if (node()->hasTagName(divTag))
329 if (node()->hasTagName(pTag))
330 return ParagraphRole;
331 if (is<HTMLLabelElement>(*node()))
333 if (is<Element>(*node()) && downcast<Element>(*node()).isFocusable())
339 void AccessibilityNodeObject::insertChild(AccessibilityObject* child, unsigned index)
344 // If the parent is asking for this child's children, then either it's the first time (and clearing is a no-op),
345 // or its visibility has changed. In the latter case, this child may have a stale child cached.
346 // This can prevent aria-hidden changes from working correctly. Hence, whenever a parent is getting children, ensure data is not stale.
347 child->clearChildren();
349 if (child->accessibilityIsIgnored()) {
350 const auto& children = child->children();
351 size_t length = children.size();
352 for (size_t i = 0; i < length; ++i)
353 m_children.insert(index + i, children[i]);
355 ASSERT(child->parentObject() == this);
356 m_children.insert(index, child);
360 void AccessibilityNodeObject::addChild(AccessibilityObject* child)
362 insertChild(child, m_children.size());
365 void AccessibilityNodeObject::addChildren()
367 // If the need to add more children in addition to existing children arises,
368 // childrenChanged should have been called, leaving the object with no children.
369 ASSERT(!m_haveChildren);
374 m_haveChildren = true;
376 // The only time we add children from the DOM tree to a node with a renderer is when it's a canvas.
377 if (renderer() && !m_node->hasTagName(canvasTag))
380 for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
381 addChild(axObjectCache()->getOrCreate(child));
384 bool AccessibilityNodeObject::canHaveChildren() const
386 // If this is an AccessibilityRenderObject, then it's okay if this object
387 // doesn't have a node - there are some renderers that don't have associated
388 // nodes, like scroll areas and css-generated text.
389 if (!node() && !isAccessibilityRenderObject())
392 // When <noscript> is not being used (its renderer() == 0), ignore its children.
393 if (node() && !renderer() && node()->hasTagName(noscriptTag))
396 // Elements that should not have children
397 switch (roleValue()) {
400 case PopUpButtonRole:
402 case RadioButtonRole:
404 case ToggleButtonRole:
406 case ListBoxOptionRole:
408 case ProgressIndicatorRole:
416 bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const
419 // Double-check that an AccessibilityObject is never accessed before
420 // it's been initialized.
421 ASSERT(m_initialized);
424 // Handle non-rendered text that is exposed through aria-hidden=false.
425 if (m_node && m_node->isTextNode() && !renderer()) {
426 // Fallback content in iframe nodes should be ignored.
427 if (m_node->parentNode() && m_node->parentNode()->hasTagName(iframeTag) && m_node->parentNode()->renderer())
430 // Whitespace only text elements should be ignored when they have no renderer.
431 String string = stringValue().stripWhiteSpace().simplifyWhiteSpace();
432 if (!string.length())
436 AccessibilityObjectInclusion decision = defaultObjectInclusion();
437 if (decision == IncludeObject)
439 if (decision == IgnoreObject)
441 // If this element is within a parent that cannot have children, it should not be exposed.
442 if (isDescendantOfBarrenParent())
445 if (roleValue() == IgnoredRole)
448 return m_role == UnknownRole;
451 bool AccessibilityNodeObject::canvasHasFallbackContent() const
453 Node* node = this->node();
454 if (!is<HTMLCanvasElement>(node))
456 HTMLCanvasElement& canvasElement = downcast<HTMLCanvasElement>(*node);
457 // If it has any children that are elements, we'll assume it might be fallback
458 // content. If it has no children or its only children are not elements
459 // (e.g. just text nodes), it doesn't have fallback content.
460 return childrenOfType<Element>(canvasElement).first();
463 bool AccessibilityNodeObject::isImageButton() const
465 return isNativeImage() && isButton();
468 bool AccessibilityNodeObject::isNativeTextControl() const
470 Node* node = this->node();
474 if (is<HTMLTextAreaElement>(*node))
477 if (is<HTMLInputElement>(*node)) {
478 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
479 return input.isText() || input.isNumberField();
485 bool AccessibilityNodeObject::isSearchField() const
487 Node* node = this->node();
491 if (roleValue() == SearchFieldRole)
494 HTMLInputElement* inputElement = node->toInputElement();
498 // Some websites don't label their search fields as such. However, they will
499 // use the word "search" in either the form or input type. This won't catch every case,
500 // but it will catch google.com for example.
502 // Check the node name of the input type, sometimes it's "search".
503 const AtomicString& nameAttribute = getAttribute(nameAttr);
504 if (nameAttribute.contains("search", false))
507 // Check the form action and the name, which will sometimes be "search".
508 HTMLFormElement* form = inputElement->form();
509 if (form && (form->name().contains("search", false) || form->action().contains("search", false)))
515 bool AccessibilityNodeObject::isNativeImage() const
517 Node* node = this->node();
521 if (is<HTMLImageElement>(*node))
524 if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag))
527 if (is<HTMLInputElement>(*node)) {
528 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
529 return input.isImageButton();
535 bool AccessibilityNodeObject::isImage() const
537 return roleValue() == ImageRole;
540 bool AccessibilityNodeObject::isPasswordField() const
542 Node* node = this->node();
543 if (!node || !node->isHTMLElement())
546 if (ariaRoleAttribute() != UnknownRole)
549 HTMLInputElement* inputElement = node->toInputElement();
553 return inputElement->isPasswordField();
556 AccessibilityObject* AccessibilityNodeObject::passwordFieldOrContainingPasswordField()
558 Node* node = this->node();
562 if (HTMLInputElement* inputElement = node->toInputElement()) {
563 if (inputElement->isPasswordField())
567 Element* element = node->shadowHost();
568 if (!element || !is<HTMLInputElement>(element))
571 if (AXObjectCache* cache = axObjectCache())
572 return cache->getOrCreate(element);
577 bool AccessibilityNodeObject::isInputImage() const
579 Node* node = this->node();
580 if (is<HTMLInputElement>(node) && roleValue() == ButtonRole) {
581 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
582 return input.isImageButton();
588 bool AccessibilityNodeObject::isProgressIndicator() const
590 return roleValue() == ProgressIndicatorRole;
593 bool AccessibilityNodeObject::isSlider() const
595 return roleValue() == SliderRole;
598 bool AccessibilityNodeObject::isMenuRelated() const
600 switch (roleValue()) {
605 case MenuItemCheckboxRole:
606 case MenuItemRadioRole:
613 bool AccessibilityNodeObject::isMenu() const
615 return roleValue() == MenuRole;
618 bool AccessibilityNodeObject::isMenuBar() const
620 return roleValue() == MenuBarRole;
623 bool AccessibilityNodeObject::isMenuButton() const
625 return roleValue() == MenuButtonRole;
628 bool AccessibilityNodeObject::isMenuItem() const
630 switch (roleValue()) {
632 case MenuItemRadioRole:
633 case MenuItemCheckboxRole:
640 bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const
642 Node* node = this->node();
646 HTMLInputElement* input = node->toInputElement();
648 return input->isCheckbox() || input->isRadioButton();
653 bool AccessibilityNodeObject::isEnabled() const
655 // ARIA says that the disabled status applies to the current element and all descendant elements.
656 for (AccessibilityObject* object = const_cast<AccessibilityNodeObject*>(this); object; object = object->parentObject()) {
657 const AtomicString& disabledStatus = object->getAttribute(aria_disabledAttr);
658 if (equalIgnoringCase(disabledStatus, "true"))
660 if (equalIgnoringCase(disabledStatus, "false"))
664 if (roleValue() == HorizontalRuleRole)
667 Node* node = this->node();
668 if (!is<Element>(node))
671 return !downcast<Element>(*node).isDisabledFormControl();
674 bool AccessibilityNodeObject::isIndeterminate() const
676 Node* node = this->node();
680 HTMLInputElement* inputElement = node->toInputElement();
684 return inputElement->shouldAppearIndeterminate();
687 bool AccessibilityNodeObject::isPressed() const
692 Node* node = this->node();
696 // If this is an ARIA button, check the aria-pressed attribute rather than node()->active()
697 AccessibilityRole ariaRole = ariaRoleAttribute();
698 if (ariaRole == ButtonRole || ariaRole == ToggleButtonRole) {
699 if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true"))
704 if (!is<Element>(*node))
706 return downcast<Element>(*node).active();
709 bool AccessibilityNodeObject::isChecked() const
711 Node* node = this->node();
715 // First test for native checkedness semantics
716 HTMLInputElement* inputElement = node->toInputElement();
718 return inputElement->shouldAppearChecked();
720 // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute
721 bool validRole = false;
722 switch (ariaRoleAttribute()) {
723 case RadioButtonRole:
726 case MenuItemCheckboxRole:
727 case MenuItemRadioRole:
735 if (validRole && equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
741 bool AccessibilityNodeObject::isHovered() const
743 Node* node = this->node();
744 return is<Element>(node) && downcast<Element>(*node).hovered();
747 bool AccessibilityNodeObject::isMultiSelectable() const
749 const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
750 if (equalIgnoringCase(ariaMultiSelectable, "true"))
752 if (equalIgnoringCase(ariaMultiSelectable, "false"))
755 return node() && node()->hasTagName(selectTag) && downcast<HTMLSelectElement>(*node()).multiple();
758 bool AccessibilityNodeObject::isReadOnly() const
760 Node* node = this->node();
764 if (is<HTMLTextAreaElement>(*node))
765 return downcast<HTMLTextAreaElement>(*node).isReadOnly();
767 if (is<HTMLInputElement>(*node)) {
768 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
769 if (input.isTextField())
770 return input.isReadOnly();
773 return !node->hasEditableStyle();
776 bool AccessibilityNodeObject::isRequired() const
778 // Explicit aria-required values should trump native required attributes.
779 const AtomicString& requiredValue = getAttribute(aria_requiredAttr);
780 if (equalIgnoringCase(requiredValue, "true"))
782 if (equalIgnoringCase(requiredValue, "false"))
785 Node* n = this->node();
786 if (is<HTMLFormControlElement>(n))
787 return downcast<HTMLFormControlElement>(*n).isRequired();
792 bool AccessibilityNodeObject::supportsRequiredAttribute() const
794 switch (roleValue()) {
796 return isFileUploadButton();
798 case ColumnHeaderRole:
802 case IncrementorRole:
804 case PopUpButtonRole:
805 case RadioButtonRole:
810 case TableHeaderContainerRole:
813 case ToggleButtonRole:
820 int AccessibilityNodeObject::headingLevel() const
822 // headings can be in block flow and non-block flow
823 Node* node = this->node();
828 int ariaLevel = getAttribute(aria_levelAttr).toInt();
833 if (node->hasTagName(h1Tag))
836 if (node->hasTagName(h2Tag))
839 if (node->hasTagName(h3Tag))
842 if (node->hasTagName(h4Tag))
845 if (node->hasTagName(h5Tag))
848 if (node->hasTagName(h6Tag))
854 String AccessibilityNodeObject::valueDescription() const
856 if (!isRangeControl())
859 return getAttribute(aria_valuetextAttr).string();
862 float AccessibilityNodeObject::valueForRange() const
864 if (is<HTMLInputElement>(node())) {
865 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
866 if (input.isRangeControl())
867 return input.valueAsNumber();
870 if (!isRangeControl())
873 return getAttribute(aria_valuenowAttr).toFloat();
876 float AccessibilityNodeObject::maxValueForRange() const
878 if (is<HTMLInputElement>(node())) {
879 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
880 if (input.isRangeControl())
881 return input.maximum();
884 if (!isRangeControl())
887 return getAttribute(aria_valuemaxAttr).toFloat();
890 float AccessibilityNodeObject::minValueForRange() const
892 if (is<HTMLInputElement>(node())) {
893 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
894 if (input.isRangeControl())
895 return input.minimum();
898 if (!isRangeControl())
901 return getAttribute(aria_valueminAttr).toFloat();
904 float AccessibilityNodeObject::stepValueForRange() const
906 return getAttribute(stepAttr).toFloat();
909 bool AccessibilityNodeObject::isHeading() const
911 return roleValue() == HeadingRole;
914 bool AccessibilityNodeObject::isLink() const
916 return roleValue() == WebCoreLinkRole;
919 bool AccessibilityNodeObject::isControl() const
921 Node* node = this->node();
925 return is<HTMLFormControlElement>(*node) || AccessibilityObject::isARIAControl(ariaRoleAttribute());
928 bool AccessibilityNodeObject::isFieldset() const
930 Node* node = this->node();
934 return node->hasTagName(fieldsetTag);
937 bool AccessibilityNodeObject::isGroup() const
939 return roleValue() == GroupRole;
942 AccessibilityObject* AccessibilityNodeObject::selectedRadioButton()
947 // Find the child radio button that is selected (ie. the intValue == 1).
948 for (const auto& child : children()) {
949 if (child->roleValue() == RadioButtonRole && child->checkboxOrRadioValue() == ButtonStateOn)
955 AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
960 // Find the child tab item that is selected (ie. the intValue == 1).
961 AccessibilityObject::AccessibilityChildrenVector tabs;
964 for (const auto& child : children()) {
965 if (child->isTabItem() && child->isChecked())
971 AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const
973 if (isNativeCheckboxOrRadio())
974 return isChecked() ? ButtonStateOn : ButtonStateOff;
976 return AccessibilityObject::checkboxOrRadioValue();
979 Element* AccessibilityNodeObject::anchorElement() const
981 Node* node = this->node();
985 AXObjectCache* cache = axObjectCache();
987 // search up the DOM tree for an anchor element
988 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
989 for ( ; node; node = node->parentNode()) {
990 if (is<HTMLAnchorElement>(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isLink()))
991 return downcast<Element>(node);
997 static bool isNodeActionElement(Node* node)
999 if (is<HTMLInputElement>(*node)) {
1000 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
1001 if (!input.isDisabledFormControl() && (input.isRadioButton() || input.isCheckbox() || input.isTextButton() || input.isFileUpload() || input.isImageButton()))
1003 } else if (node->hasTagName(buttonTag) || node->hasTagName(selectTag))
1009 static Element* nativeActionElement(Node* start)
1014 // Do a deep-dive to see if any nodes should be used as the action element.
1015 // We have to look at Nodes, since this method should only be called on objects that do not have children (like buttons).
1016 // It solves the problem when authors put role="button" on a group and leave the actual button inside the group.
1018 for (Node* child = start->firstChild(); child; child = child->nextSibling()) {
1019 if (isNodeActionElement(child))
1020 return downcast<Element>(child);
1022 if (Element* subChild = nativeActionElement(child))
1028 Element* AccessibilityNodeObject::actionElement() const
1030 Node* node = this->node();
1034 if (isNodeActionElement(node))
1035 return downcast<Element>(node);
1037 if (AccessibilityObject::isARIAInput(ariaRoleAttribute()))
1038 return downcast<Element>(node);
1040 switch (roleValue()) {
1042 case PopUpButtonRole:
1043 case ToggleButtonRole:
1046 case MenuItemCheckboxRole:
1047 case MenuItemRadioRole:
1049 // Check if the author is hiding the real control element inside the ARIA element.
1050 if (Element* nativeElement = nativeActionElement(node))
1051 return nativeElement;
1052 return downcast<Element>(node);
1057 Element* elt = anchorElement();
1059 elt = mouseButtonListener();
1063 Element* AccessibilityNodeObject::mouseButtonListener(MouseButtonListenerResultFilter filter) const
1065 Node* node = this->node();
1069 // check if our parent is a mouse button listener
1070 // FIXME: Do the continuation search like anchorElement does
1071 for (auto& element : elementLineage(is<Element>(*node) ? downcast<Element>(node) : node->parentElement())) {
1072 // 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.
1073 // It can cause false positives, where every piece of text is labeled as accepting press actions.
1074 if (element.hasTagName(bodyTag) && isStaticText() && filter == ExcludeBodyElement)
1077 if (element.hasEventListeners(eventNames().clickEvent) || element.hasEventListeners(eventNames().mousedownEvent) || element.hasEventListeners(eventNames().mouseupEvent))
1084 bool AccessibilityNodeObject::isDescendantOfBarrenParent() const
1086 for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) {
1087 if (!object->canHaveChildren())
1094 void AccessibilityNodeObject::alterSliderValue(bool increase)
1096 if (roleValue() != SliderRole)
1099 if (!getAttribute(stepAttr).isEmpty())
1100 changeValueByStep(increase);
1102 changeValueByPercent(increase ? 5 : -5);
1105 void AccessibilityNodeObject::increment()
1107 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture, document());
1108 alterSliderValue(true);
1111 void AccessibilityNodeObject::decrement()
1113 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture, document());
1114 alterSliderValue(false);
1117 void AccessibilityNodeObject::changeValueByStep(bool increase)
1119 float step = stepValueForRange();
1120 float value = valueForRange();
1122 value += increase ? step : -step;
1124 setValue(String::number(value));
1126 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged);
1129 void AccessibilityNodeObject::changeValueByPercent(float percentChange)
1131 float range = maxValueForRange() - minValueForRange();
1132 float step = range * (percentChange / 100);
1133 float value = valueForRange();
1135 // Make sure the specified percent will cause a change of one integer step or larger.
1137 step = fabs(percentChange) * (1 / percentChange);
1140 setValue(String::number(value));
1142 axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged);
1145 bool AccessibilityNodeObject::isGenericFocusableElement() const
1147 if (!canSetFocusAttribute())
1150 // If it's a control, it's not generic.
1154 AccessibilityRole role = roleValue();
1155 if (role == VideoRole || role == AudioRole)
1158 // If it has an aria role, it's not generic.
1159 if (m_ariaRole != UnknownRole)
1162 // If the content editable attribute is set on this element, that's the reason
1163 // it's focusable, and existing logic should handle this case already - so it's not a
1164 // generic focusable element.
1166 if (hasContentEditableAttributeSet())
1169 // The web area and body element are both focusable, but existing logic handles these
1170 // cases already, so we don't need to include them here.
1171 if (role == WebAreaRole)
1173 if (node() && node()->hasTagName(bodyTag))
1176 // An SVG root is focusable by default, but it's probably not interactive, so don't
1177 // include it. It can still be made accessible by giving it an ARIA role.
1178 if (role == SVGRootRole)
1184 HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const
1186 if (!is<HTMLElement>(*element) || !downcast<HTMLElement>(*element).isLabelable())
1189 const AtomicString& id = element->getIdAttribute();
1190 if (!id.isEmpty()) {
1191 if (HTMLLabelElement* label = element->treeScope().labelElementForId(id))
1195 return ancestorsOfType<HTMLLabelElement>(*element).first();
1198 String AccessibilityNodeObject::ariaAccessibilityDescription() const
1200 String ariaLabeledBy = ariaLabeledByAttribute();
1201 if (!ariaLabeledBy.isEmpty())
1202 return ariaLabeledBy;
1204 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1205 if (!ariaLabel.isEmpty())
1211 static Element* siblingWithAriaRole(String role, Node* node)
1213 ContainerNode* parent = node->parentNode();
1217 for (auto& sibling : childrenOfType<Element>(*parent)) {
1218 const AtomicString& siblingAriaRole = sibling.fastGetAttribute(roleAttr);
1219 if (equalIgnoringCase(siblingAriaRole, role))
1226 Element* AccessibilityNodeObject::menuElementForMenuButton() const
1228 if (ariaRoleAttribute() != MenuButtonRole)
1231 return siblingWithAriaRole("menu", node());
1234 AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const
1236 if (AXObjectCache* cache = axObjectCache())
1237 return cache->getOrCreate(menuElementForMenuButton());
1241 Element* AccessibilityNodeObject::menuItemElementForMenu() const
1243 if (ariaRoleAttribute() != MenuRole)
1246 return siblingWithAriaRole("menuitem", node());
1249 AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
1251 AXObjectCache* cache = axObjectCache();
1255 Element* menuItem = menuItemElementForMenu();
1258 // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem
1259 AccessibilityObject* menuItemAX = cache->getOrCreate(menuItem);
1260 if (menuItemAX && menuItemAX->isMenuButton())
1266 bool AccessibilityNodeObject::usesAltTagForTextComputation() const
1268 return isImage() || isInputImage() || isNativeImage() || isCanvas() || (node() && node()->hasTagName(imgTag));
1271 void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder) const
1273 Node* node = this->node();
1277 bool isInputTag = is<HTMLInputElement>(*node);
1278 if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
1279 if (HTMLLabelElement* label = labelForElement(downcast<Element>(node))) {
1280 AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label);
1281 String innerText = label->innerText();
1282 // Only use the <label> text if there's no ARIA override.
1283 if (!innerText.isEmpty() && !ariaAccessibilityDescription())
1284 textOrder.append(AccessibilityText(innerText, LabelByElementText, labelObject));
1289 AccessibilityObject* titleUIElement = this->titleUIElement();
1291 textOrder.append(AccessibilityText(String(), LabelByElementText, titleUIElement));
1294 void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
1297 String webAreaText = alternativeTextForWebArea();
1298 if (!webAreaText.isEmpty())
1299 textOrder.append(AccessibilityText(webAreaText, AlternativeText));
1303 ariaLabeledByText(textOrder);
1305 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1306 if (!ariaLabel.isEmpty())
1307 textOrder.append(AccessibilityText(ariaLabel, AlternativeText));
1309 if (usesAltTagForTextComputation()) {
1310 if (is<RenderImage>(renderer())) {
1311 String renderAltText = downcast<RenderImage>(*renderer()).altText();
1313 // RenderImage will return title as a fallback from altText, but we don't want title here because we consider that in helpText.
1314 if (!renderAltText.isEmpty() && renderAltText != getAttribute(titleAttr)) {
1315 textOrder.append(AccessibilityText(renderAltText, AlternativeText));
1319 // Images should use alt as long as the attribute is present, even if empty.
1320 // Otherwise, it should fallback to other methods, like the title attribute.
1321 const AtomicString& alt = getAttribute(altAttr);
1323 textOrder.append(AccessibilityText(alt, AlternativeText));
1326 Node* node = this->node();
1330 // The fieldset element derives its alternative text from the first associated legend element if one is available.
1331 if (is<HTMLFieldSetElement>(*node)) {
1332 AccessibilityObject* object = axObjectCache()->getOrCreate(downcast<HTMLFieldSetElement>(*node).legend());
1333 if (object && !object->isHidden())
1334 textOrder.append(AccessibilityText(accessibleNameForNode(object->node()), AlternativeText));
1337 // SVG elements all can have a <svg:title> element inside which should act as the descriptive text.
1338 if (node->isSVGElement())
1339 textOrder.append(AccessibilityText(downcast<SVGElement>(*node).title(), AlternativeText));
1342 if (node->isMathMLElement())
1343 textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AlternativeText));
1347 void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) const
1349 Node* node = this->node();
1353 bool isInputTag = is<HTMLInputElement>(*node);
1355 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
1356 if (input.isTextButton()) {
1357 textOrder.append(AccessibilityText(input.valueWithDefault(), VisibleText));
1362 // If this node isn't rendered, there's no inner text we can extract from a select element.
1363 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1366 bool useTextUnderElement = false;
1368 switch (roleValue()) {
1369 case PopUpButtonRole:
1370 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1371 if (node->hasTagName(selectTag))
1375 case ToggleButtonRole:
1377 case ListBoxOptionRole:
1378 // MacOS does not expect native <li> elements to expose label information, it only expects leaf node elements to do that.
1379 #if !PLATFORM(COCOA)
1382 case MenuButtonRole:
1384 case MenuItemCheckboxRole:
1385 case MenuItemRadioRole:
1386 case RadioButtonRole:
1389 case ProgressIndicatorRole:
1390 useTextUnderElement = true;
1396 // If it's focusable but it's not content editable or a known control type, then it will appear to
1397 // the user as a single atomic object, so we should use its text as the default title.
1398 if (isHeading() || isLink())
1399 useTextUnderElement = true;
1401 if (useTextUnderElement) {
1402 AccessibilityTextUnderElementMode mode;
1404 // Headings often include links as direct children. Those links need to be included in text under element.
1406 mode.includeFocusableContent = true;
1408 String text = textUnderElement(mode);
1409 if (!text.isEmpty())
1410 textOrder.append(AccessibilityText(text, ChildrenText));
1414 void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) const
1416 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1417 if (!ariaHelp.isEmpty())
1418 textOrder.append(AccessibilityText(ariaHelp, HelpText));
1420 String describedBy = ariaDescribedByAttribute();
1421 if (!describedBy.isEmpty())
1422 textOrder.append(AccessibilityText(describedBy, SummaryText));
1424 // Summary attribute used as help text on tables.
1425 const AtomicString& summary = getAttribute(summaryAttr);
1426 if (!summary.isEmpty())
1427 textOrder.append(AccessibilityText(summary, SummaryText));
1429 // The title attribute should be used as help text unless it is already being used as descriptive text.
1430 const AtomicString& title = getAttribute(titleAttr);
1431 if (!title.isEmpty())
1432 textOrder.append(AccessibilityText(title, TitleTagText));
1435 void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textOrder)
1437 titleElementText(textOrder);
1438 alternativeText(textOrder);
1439 visibleText(textOrder);
1440 helpText(textOrder);
1442 String placeholder = placeholderValue();
1443 if (!placeholder.isEmpty())
1444 textOrder.append(AccessibilityText(placeholder, PlaceholderText));
1447 void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
1449 String ariaLabeledBy = ariaLabeledByAttribute();
1450 if (!ariaLabeledBy.isEmpty()) {
1451 Vector<Element*> elements;
1452 ariaLabeledByElements(elements);
1454 Vector<RefPtr<AccessibilityObject>> axElements;
1455 for (const auto& element : elements) {
1456 RefPtr<AccessibilityObject> axElement = axObjectCache()->getOrCreate(element);
1457 axElements.append(axElement);
1460 textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, WTF::move(axElements)));
1464 String AccessibilityNodeObject::alternativeTextForWebArea() const
1466 // The WebArea description should follow this order:
1467 // aria-label on the <html>
1468 // title on the <html>
1469 // <title> inside the <head> (of it was set through JS)
1470 // name on the <html>
1472 // aria-label on the <iframe>
1473 // title on the <iframe>
1474 // name on the <iframe>
1476 Document* document = this->document();
1480 // Check if the HTML element has an aria-label for the webpage.
1481 if (Element* documentElement = document->documentElement()) {
1482 const AtomicString& ariaLabel = documentElement->fastGetAttribute(aria_labelAttr);
1483 if (!ariaLabel.isEmpty())
1487 if (auto* owner = document->ownerElement()) {
1488 if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
1489 const AtomicString& title = owner->fastGetAttribute(titleAttr);
1490 if (!title.isEmpty())
1493 return owner->getNameAttribute();
1496 String documentTitle = document->title();
1497 if (!documentTitle.isEmpty())
1498 return documentTitle;
1500 if (auto* body = document->bodyOrFrameset())
1501 return body->getNameAttribute();
1506 String AccessibilityNodeObject::accessibilityDescription() const
1508 // Static text should not have a description, it should only have a stringValue.
1509 if (roleValue() == StaticTextRole)
1512 String ariaDescription = ariaAccessibilityDescription();
1513 if (!ariaDescription.isEmpty())
1514 return ariaDescription;
1516 if (usesAltTagForTextComputation()) {
1517 // Images should use alt as long as the attribute is present, even if empty.
1518 // Otherwise, it should fallback to other methods, like the title attribute.
1519 const AtomicString& alt = getAttribute(altAttr);
1524 // SVG elements all can have a <svg:title> element inside which should act as the descriptive text.
1525 if (m_node && m_node->isSVGElement())
1526 return downcast<SVGElement>(*m_node).title();
1529 if (is<MathMLElement>(m_node))
1530 return getAttribute(MathMLNames::alttextAttr);
1533 // An element's descriptive text is comprised of title() (what's visible on the screen) and accessibilityDescription() (other descriptive text).
1534 // Both are used to generate what a screen reader speaks.
1535 // If this point is reached (i.e. there's no accessibilityDescription) and there's no title(), we should fallback to using the title attribute.
1536 // 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).
1537 if (title().isEmpty())
1538 return getAttribute(titleAttr);
1543 String AccessibilityNodeObject::helpText() const
1545 Node* node = this->node();
1549 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1550 if (!ariaHelp.isEmpty())
1553 String describedBy = ariaDescribedByAttribute();
1554 if (!describedBy.isEmpty())
1557 String description = accessibilityDescription();
1558 for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
1559 if (is<HTMLElement>(*ancestor)) {
1560 HTMLElement& element = downcast<HTMLElement>(*ancestor);
1561 const AtomicString& summary = element.getAttribute(summaryAttr);
1562 if (!summary.isEmpty())
1565 // The title attribute should be used as help text unless it is already being used as descriptive text.
1566 const AtomicString& title = element.getAttribute(titleAttr);
1567 if (!title.isEmpty() && description != title)
1571 // Only take help text from an ancestor element if its a group or an unknown role. If help was
1572 // added to those kinds of elements, it is likely it was meant for a child element.
1573 AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor);
1575 AccessibilityRole role = axObj->roleValue();
1576 if (role != GroupRole && role != UnknownRole)
1584 unsigned AccessibilityNodeObject::hierarchicalLevel() const
1586 Node* node = this->node();
1587 if (!is<Element>(node))
1589 Element& element = downcast<Element>(*node);
1590 const AtomicString& ariaLevel = element.fastGetAttribute(aria_levelAttr);
1591 if (!ariaLevel.isEmpty())
1592 return ariaLevel.toInt();
1594 // Only tree item will calculate its level through the DOM currently.
1595 if (roleValue() != TreeItemRole)
1598 // Hierarchy leveling starts at 1, to match the aria-level spec.
1599 // We measure tree hierarchy by the number of groups that the item is within.
1601 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
1602 AccessibilityRole parentRole = parent->roleValue();
1603 if (parentRole == GroupRole)
1605 else if (parentRole == TreeRole)
1612 void AccessibilityNodeObject::setIsExpanded(bool expand)
1614 if (is<HTMLDetailsElement>(node())) {
1615 HTMLDetailsElement* details = downcast<HTMLDetailsElement>(node());
1616 if ((expand && !details->isOpen()) || (!expand && details->isOpen()))
1617 details->toggleOpen();
1621 // When building the textUnderElement for an object, determine whether or not
1622 // we should include the inner text of this given descendant object or skip it.
1623 static bool shouldUseAccessibilityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode)
1625 // Do not use any heuristic if we are explicitly asking to include all the children.
1626 if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)
1629 // Consider this hypothetical example:
1632 // Table of contents
1634 // <a href="#start">Jump to start of book</a>
1636 // <li><a href="#1">Chapter 1</a></li>
1637 // <li><a href="#1">Chapter 2</a></li>
1641 // The goal is to return a reasonable title for the outer container div, because
1642 // it's focusable - but without making its title be the full inner text, which is
1643 // quite long. As a heuristic, skip links, controls, and elements that are usually
1644 // containers with lots of children.
1646 // ARIA states that certain elements are not allowed to expose their children content for name calculation.
1647 if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren
1648 && !obj->accessibleNameDerivesFromContent())
1651 if (equalIgnoringCase(obj->getAttribute(aria_hiddenAttr), "true"))
1654 // If something doesn't expose any children, then we can always take the inner text content.
1655 // This is what we want when someone puts an <a> inside a <button> for example.
1656 if (obj->isDescendantOfBarrenParent())
1659 // Skip focusable children, so we don't include the text of links and controls.
1660 if (obj->canSetFocusAttribute() && !mode.includeFocusableContent)
1663 // Skip big container elements like lists, tables, etc.
1664 if (is<AccessibilityList>(*obj))
1667 if (is<AccessibilityTable>(*obj) && downcast<AccessibilityTable>(*obj).isExposableThroughAccessibility())
1670 if (obj->isTree() || obj->isCanvas())
1676 static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, const String& childText)
1678 if (!builder.length() || !childText.length())
1681 // We don't need to add an additional space before or after a line break.
1682 return !(isHTMLLineBreak(childText[0]) || isHTMLLineBreak(builder[builder.length() - 1]));
1685 static void appendNameToStringBuilder(StringBuilder& builder, const String& text)
1687 if (shouldAddSpaceBeforeAppendingNextElement(builder, text))
1688 builder.append(' ');
1689 builder.append(text);
1692 String AccessibilityNodeObject::textUnderElement(AccessibilityTextUnderElementMode mode) const
1694 Node* node = this->node();
1696 return downcast<Text>(*node).wholeText();
1698 // The render tree should be stable before going ahead. Otherwise, further uses of the
1699 // TextIterator will force a layout update, potentially altering the accessibility tree
1700 // and leading to crashes in the loop that computes the result text from the children.
1701 ASSERT(!document()->renderView()->layoutState());
1702 ASSERT(!document()->childNeedsStyleRecalc());
1704 StringBuilder builder;
1705 for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
1706 if (mode.ignoredChildNode && child->node() == mode.ignoredChildNode)
1709 bool shouldDeriveNameFromAuthor = (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren && !child->accessibleNameDerivesFromContent());
1710 if (shouldDeriveNameFromAuthor) {
1711 appendNameToStringBuilder(builder, accessibleNameForNode(child->node()));
1715 if (!shouldUseAccessibilityObjectInnerText(child, mode))
1718 if (is<AccessibilityNodeObject>(*child)) {
1719 Vector<AccessibilityText> textOrder;
1720 downcast<AccessibilityNodeObject>(*child).alternativeText(textOrder);
1721 if (textOrder.size() > 0 && textOrder[0].text.length()) {
1722 appendNameToStringBuilder(builder, textOrder[0].text);
1727 String childText = child->textUnderElement(mode);
1728 if (childText.length())
1729 appendNameToStringBuilder(builder, childText);
1732 return builder.toString().stripWhiteSpace().simplifyWhiteSpace(isHTMLSpaceButNotLineBreak);
1735 String AccessibilityNodeObject::title() const
1737 Node* node = this->node();
1741 bool isInputTag = is<HTMLInputElement>(*node);
1743 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
1744 if (input.isTextButton())
1745 return input.valueWithDefault();
1748 if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
1749 HTMLLabelElement* label = labelForElement(downcast<Element>(node));
1750 // Use the label text as the title if 1) the title element is NOT an exposed element and 2) there's no ARIA override.
1751 if (label && !exposesTitleUIElement() && !ariaAccessibilityDescription().length())
1752 return label->innerText();
1755 // If this node isn't rendered, there's no inner text we can extract from a select element.
1756 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1759 switch (roleValue()) {
1760 case PopUpButtonRole:
1761 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1762 if (node->hasTagName(selectTag))
1766 case ToggleButtonRole:
1768 case ListBoxOptionRole:
1770 case MenuButtonRole:
1772 case MenuItemCheckboxRole:
1773 case MenuItemRadioRole:
1774 case RadioButtonRole:
1777 return textUnderElement();
1778 // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>.
1786 return textUnderElement();
1788 return textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeSkipIgnoredChildren, true));
1793 String AccessibilityNodeObject::text() const
1795 // If this is a user defined static text, use the accessible name computation.
1796 if (ariaRoleAttribute() == StaticTextRole) {
1797 Vector<AccessibilityText> textOrder;
1798 alternativeText(textOrder);
1799 if (textOrder.size() > 0 && textOrder[0].text.length())
1800 return textOrder[0].text;
1803 if (!isTextControl())
1806 Node* node = this->node();
1810 if (isNativeTextControl() && is<HTMLTextFormControlElement>(*node))
1811 return downcast<HTMLTextFormControlElement>(*node).value();
1813 if (!node->isElementNode())
1816 return downcast<Element>(node)->innerText();
1819 String AccessibilityNodeObject::stringValue() const
1821 Node* node = this->node();
1825 if (ariaRoleAttribute() == StaticTextRole) {
1826 String staticText = text();
1827 if (!staticText.length())
1828 staticText = textUnderElement();
1832 if (node->isTextNode())
1833 return textUnderElement();
1835 if (node->hasTagName(selectTag)) {
1836 HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node);
1837 int selectedIndex = selectElement.selectedIndex();
1838 const Vector<HTMLElement*>& listItems = selectElement.listItems();
1839 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
1840 const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr);
1841 if (!overriddenDescription.isNull())
1842 return overriddenDescription;
1844 if (!selectElement.multiple())
1845 return selectElement.value();
1849 if (isTextControl())
1852 // FIXME: We might need to implement a value here for more types
1853 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
1854 // this would require subclassing or making accessibilityAttributeNames do something other than return a
1855 // single static array.
1859 void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const
1868 if (!is<HTMLInputElement>(node()))
1871 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
1872 const AtomicString& type = input.getAttribute(typeAttr);
1873 if (!equalIgnoringCase(type, "color"))
1876 // HTMLInputElement::value always returns a string parseable by Color().
1877 Color color(input.value());
1883 // This function implements the ARIA accessible name as described by the Mozilla
1884 // ARIA Implementer's Guide.
1885 static String accessibleNameForNode(Node* node, Node* labelledbyNode)
1888 if (!is<Element>(node))
1891 Element& element = downcast<Element>(*node);
1892 const AtomicString& ariaLabel = element.fastGetAttribute(aria_labelAttr);
1893 if (!ariaLabel.isEmpty())
1896 const AtomicString& alt = element.fastGetAttribute(altAttr);
1900 // If the node can be turned into an AX object, we can use standard name computation rules.
1901 // If however, the node cannot (because there's no renderer e.g.) fallback to using the basic text underneath.
1902 AccessibilityObject* axObject = node->document().axObjectCache()->getOrCreate(node);
1904 String valueDescription = axObject->valueDescription();
1905 if (!valueDescription.isEmpty())
1906 return valueDescription;
1909 if (is<HTMLInputElement>(*node))
1910 return downcast<HTMLInputElement>(*node).value();
1914 if (axObject->accessibleNameDerivesFromContent())
1915 text = axObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren, true, labelledbyNode));
1917 text = element.innerText();
1919 if (!text.isEmpty())
1922 const AtomicString& title = element.fastGetAttribute(titleAttr);
1923 if (!title.isEmpty())
1929 String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
1931 StringBuilder builder;
1932 unsigned size = elements.size();
1933 for (unsigned i = 0; i < size; ++i)
1934 appendNameToStringBuilder(builder, accessibleNameForNode(elements[i], node()));
1935 return builder.toString();
1938 String AccessibilityNodeObject::ariaDescribedByAttribute() const
1940 Vector<Element*> elements;
1941 elementsFromAttribute(elements, aria_describedbyAttr);
1943 return accessibilityDescriptionForElements(elements);
1946 void AccessibilityNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const
1948 elementsFromAttribute(elements, aria_labelledbyAttr);
1949 if (!elements.size())
1950 elementsFromAttribute(elements, aria_labeledbyAttr);
1954 String AccessibilityNodeObject::ariaLabeledByAttribute() const
1956 Vector<Element*> elements;
1957 ariaLabeledByElements(elements);
1959 return accessibilityDescriptionForElements(elements);
1962 bool AccessibilityNodeObject::hasAttributesRequiredForInclusion() const
1964 if (AccessibilityObject::hasAttributesRequiredForInclusion())
1967 if (!ariaAccessibilityDescription().isEmpty())
1973 bool AccessibilityNodeObject::canSetFocusAttribute() const
1975 Node* node = this->node();
1982 // NOTE: It would be more accurate to ask the document whether setFocusedElement() would
1983 // do anything. For example, setFocusedElement() will do nothing if the current focused
1984 // node will not relinquish the focus.
1985 if (!is<Element>(node))
1988 Element& element = downcast<Element>(*node);
1990 if (element.isDisabledFormControl())
1993 return element.supportsFocus();
1996 AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const
1998 const AtomicString& ariaRole = getAttribute(roleAttr);
1999 if (ariaRole.isNull() || ariaRole.isEmpty())
2002 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
2004 // ARIA states if an item can get focus, it should not be presentational.
2005 if (role == PresentationalRole && canSetFocusAttribute())
2008 if (role == ButtonRole)
2009 role = buttonRoleType();
2011 if (role == TextAreaRole && !ariaIsMultiline())
2012 role = TextFieldRole;
2014 role = remapAriaRoleDueToParent(role);
2016 // Presentational roles are invalidated by the presence of ARIA attributes.
2017 if (role == PresentationalRole && supportsARIAAttributes())
2026 AccessibilityRole AccessibilityNodeObject::ariaRoleAttribute() const
2031 AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
2033 // Some objects change their role based on their parent.
2034 // However, asking for the unignoredParent calls accessibilityIsIgnored(), which can trigger a loop.
2035 // While inside the call stack of creating an element, we need to avoid accessibilityIsIgnored().
2036 // https://bugs.webkit.org/show_bug.cgi?id=65174
2038 if (role != ListBoxOptionRole && role != MenuItemRole)
2041 for (AccessibilityObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
2042 AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
2044 // Selects and listboxes both have options as child roles, but they map to different roles within WebCore.
2045 if (role == ListBoxOptionRole && parentAriaRole == MenuRole)
2046 return MenuItemRole;
2047 // An aria "menuitem" may map to MenuButton or MenuItem depending on its parent.
2048 if (role == MenuItemRole && parentAriaRole == GroupRole)
2049 return MenuButtonRole;
2051 // If the parent had a different role, then we don't need to continue searching up the chain.
2059 bool AccessibilityNodeObject::canSetSelectedAttribute() const
2061 // Elements that can be selected
2062 switch (roleValue()) {
2064 case RadioButtonRole:
2078 } // namespace WebCore