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"
39 #include "ElementIterator.h"
40 #include "EventNames.h"
41 #include "FloatRect.h"
43 #include "FrameLoader.h"
44 #include "FrameSelection.h"
45 #include "FrameView.h"
46 #include "HTMLCanvasElement.h"
47 #include "HTMLDetailsElement.h"
48 #include "HTMLFieldSetElement.h"
49 #include "HTMLFormElement.h"
50 #include "HTMLImageElement.h"
51 #include "HTMLInputElement.h"
52 #include "HTMLLabelElement.h"
53 #include "HTMLLegendElement.h"
54 #include "HTMLNames.h"
55 #include "HTMLParserIdioms.h"
56 #include "HTMLSelectElement.h"
57 #include "HTMLTextAreaElement.h"
58 #include "HTMLTextFormControlElement.h"
59 #include "LabelableElement.h"
60 #include "LocalizedStrings.h"
61 #include "MathMLElement.h"
62 #include "MathMLNames.h"
64 #include "NodeTraversal.h"
65 #include "ProgressTracker.h"
66 #include "RenderImage.h"
67 #include "RenderView.h"
68 #include "SVGElement.h"
70 #include "TextControlInnerElements.h"
71 #include "UserGestureIndicator.h"
72 #include "VisibleUnits.h"
74 #include <wtf/StdLibExtras.h>
75 #include <wtf/text/StringBuilder.h>
76 #include <wtf/unicode/CharacterNames.h>
80 using namespace HTMLNames;
82 static String accessibleNameForNode(Node* node, Node* labelledbyNode = nullptr);
84 AccessibilityNodeObject::AccessibilityNodeObject(Node* node)
85 : AccessibilityObject()
90 AccessibilityNodeObject::~AccessibilityNodeObject()
95 void AccessibilityNodeObject::init()
98 ASSERT(!m_initialized);
101 m_role = determineAccessibilityRole();
104 Ref<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node)
106 return adoptRef(*new AccessibilityNodeObject(node));
109 void AccessibilityNodeObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache)
111 // AccessibilityObject calls clearChildren.
112 AccessibilityObject::detach(detachmentType, cache);
116 void AccessibilityNodeObject::childrenChanged()
118 // This method is meant as a quick way of marking a portion of the accessibility tree dirty.
119 if (!node() && !renderer())
122 AXObjectCache* cache = axObjectCache();
125 cache->postNotification(this, document(), AXObjectCache::AXChildrenChanged);
127 // Should make the sub tree dirty so that everything below will be updated correctly.
128 this->setNeedsToUpdateSubtree();
129 bool shouldStopUpdatingParent = false;
131 // Go up the accessibility parent chain, but only if the element already exists. This method is
132 // called during render layouts, minimal work should be done.
133 // If AX elements are created now, they could interrogate the render tree while it's in a funky state.
134 // At the same time, process ARIA live region changes.
135 for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) {
136 if (!shouldStopUpdatingParent)
137 parent->setNeedsToUpdateChildren();
140 // These notifications always need to be sent because screenreaders are reliant on them to perform.
141 // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update.
143 // If this element supports ARIA live regions, then notify the AT of changes.
144 // Sometimes this function can be called many times within a short period of time, leading to posting too many AXLiveRegionChanged
145 // notifications. To fix this, we used a timer to make sure we only post one notification for the children changes within a pre-defined
147 if (parent->supportsLiveRegion())
148 cache->postLiveRegionChangeNotification(parent);
150 // If this element is an ARIA text control, notify the AT of changes.
151 if (parent->isNonNativeTextControl()) {
152 cache->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged);
154 // Do not let the parent that's above the editable ancestor update its children
155 // since we already notify the AT of changes.
156 shouldStopUpdatingParent = true;
161 void AccessibilityNodeObject::updateAccessibilityRole()
163 bool ignoredStatus = accessibilityIsIgnored();
164 m_role = determineAccessibilityRole();
166 // The AX hierarchy only needs to be updated if the ignored status of an element has changed.
167 if (ignoredStatus != accessibilityIsIgnored())
171 AccessibilityObject* AccessibilityNodeObject::firstChild() const
176 Node* firstChild = node()->firstChild();
181 return axObjectCache()->getOrCreate(firstChild);
184 AccessibilityObject* AccessibilityNodeObject::lastChild() const
189 Node* lastChild = node()->lastChild();
193 return axObjectCache()->getOrCreate(lastChild);
196 AccessibilityObject* AccessibilityNodeObject::previousSibling() const
201 Node* previousSibling = node()->previousSibling();
202 if (!previousSibling)
205 return axObjectCache()->getOrCreate(previousSibling);
208 AccessibilityObject* AccessibilityNodeObject::nextSibling() const
213 Node* nextSibling = node()->nextSibling();
217 return axObjectCache()->getOrCreate(nextSibling);
220 AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const
222 return parentObject();
225 AccessibilityObject* AccessibilityNodeObject::parentObject() const
230 Node* parentObj = node()->parentNode();
234 if (AXObjectCache* cache = axObjectCache())
235 return cache->getOrCreate(parentObj);
240 LayoutRect AccessibilityNodeObject::elementRect() const
242 return boundingBoxRect();
245 LayoutRect AccessibilityNodeObject::boundingBoxRect() const
247 // AccessibilityNodeObjects have no mechanism yet to return a size or position.
248 // For now, let's return the position of the ancestor that does have a position,
249 // 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.
251 LayoutRect boundingBox;
253 for (AccessibilityObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) {
254 if (positionProvider->isAccessibilityRenderObject()) {
255 LayoutRect parentRect = positionProvider->elementRect();
256 boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat()))));
257 boundingBox.setLocation(parentRect.location());
265 void AccessibilityNodeObject::setNode(Node* node)
270 Document* AccessibilityNodeObject::document() const
274 return &node()->document();
277 AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole()
280 return AccessibilityRole::Unknown;
282 if ((m_ariaRole = determineAriaRoleAttribute()) != AccessibilityRole::Unknown)
285 if (node()->isLink())
286 return AccessibilityRole::WebCoreLink;
287 if (node()->isTextNode())
288 return AccessibilityRole::StaticText;
289 if (node()->hasTagName(buttonTag))
290 return buttonRoleType();
291 if (is<HTMLInputElement>(*node())) {
292 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
293 if (input.isCheckbox())
294 return AccessibilityRole::CheckBox;
295 if (input.isRadioButton())
296 return AccessibilityRole::RadioButton;
297 if (input.isTextButton())
298 return buttonRoleType();
299 if (input.isRangeControl())
300 return AccessibilityRole::Slider;
301 if (input.isInputTypeHidden())
302 return AccessibilityRole::Ignored;
303 if (input.isSearchField())
304 return AccessibilityRole::SearchField;
305 #if ENABLE(INPUT_TYPE_COLOR)
306 if (input.isColorControl())
307 return AccessibilityRole::ColorWell;
309 return AccessibilityRole::TextField;
311 if (node()->hasTagName(selectTag)) {
312 HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node());
313 return selectElement.multiple() ? AccessibilityRole::ListBox : AccessibilityRole::PopUpButton;
315 if (is<HTMLTextAreaElement>(*node()))
316 return AccessibilityRole::TextArea;
318 return AccessibilityRole::Heading;
319 if (node()->hasTagName(blockquoteTag))
320 return AccessibilityRole::Blockquote;
321 if (node()->hasTagName(divTag))
322 return AccessibilityRole::Div;
323 if (node()->hasTagName(pTag))
324 return AccessibilityRole::Paragraph;
325 if (is<HTMLLabelElement>(*node()))
326 return AccessibilityRole::Label;
327 if (is<Element>(*node()) && downcast<Element>(*node()).isFocusable())
328 return AccessibilityRole::Group;
330 return AccessibilityRole::Unknown;
333 void AccessibilityNodeObject::addChildren()
335 // If the need to add more children in addition to existing children arises,
336 // childrenChanged should have been called, leaving the object with no children.
337 ASSERT(!m_haveChildren);
342 m_haveChildren = true;
344 // The only time we add children from the DOM tree to a node with a renderer is when it's a canvas.
345 if (renderer() && !m_node->hasTagName(canvasTag))
348 for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
349 addChild(axObjectCache()->getOrCreate(child));
351 m_subtreeDirty = false;
354 bool AccessibilityNodeObject::canHaveChildren() const
356 // If this is an AccessibilityRenderObject, then it's okay if this object
357 // doesn't have a node - there are some renderers that don't have associated
358 // nodes, like scroll areas and css-generated text.
359 if (!node() && !isAccessibilityRenderObject())
362 // When <noscript> is not being used (its renderer() == 0), ignore its children.
363 if (node() && !renderer() && node()->hasTagName(noscriptTag))
366 // Elements that should not have children
367 switch (roleValue()) {
368 case AccessibilityRole::Image:
369 case AccessibilityRole::Button:
370 case AccessibilityRole::PopUpButton:
371 case AccessibilityRole::CheckBox:
372 case AccessibilityRole::RadioButton:
373 case AccessibilityRole::Tab:
374 case AccessibilityRole::ToggleButton:
375 case AccessibilityRole::StaticText:
376 case AccessibilityRole::ListBoxOption:
377 case AccessibilityRole::ScrollBar:
378 case AccessibilityRole::ProgressIndicator:
379 case AccessibilityRole::Switch:
380 case AccessibilityRole::MenuItemCheckbox:
381 case AccessibilityRole::MenuItemRadio:
382 case AccessibilityRole::Splitter:
384 case AccessibilityRole::DocumentMath:
386 return node()->isMathMLElement();
394 bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const
397 // Double-check that an AccessibilityObject is never accessed before
398 // it's been initialized.
399 ASSERT(m_initialized);
402 // Handle non-rendered text that is exposed through aria-hidden=false.
403 if (m_node && m_node->isTextNode() && !renderer()) {
404 // Fallback content in iframe nodes should be ignored.
405 if (m_node->parentNode() && m_node->parentNode()->hasTagName(iframeTag) && m_node->parentNode()->renderer())
408 // Whitespace only text elements should be ignored when they have no renderer.
409 String string = stringValue().stripWhiteSpace().simplifyWhiteSpace();
410 if (!string.length())
414 AccessibilityObjectInclusion decision = defaultObjectInclusion();
415 if (decision == AccessibilityObjectInclusion::IncludeObject)
417 if (decision == AccessibilityObjectInclusion::IgnoreObject)
419 // If this element is within a parent that cannot have children, it should not be exposed.
420 if (isDescendantOfBarrenParent())
423 if (roleValue() == AccessibilityRole::Ignored)
426 return m_role == AccessibilityRole::Unknown;
429 bool AccessibilityNodeObject::canvasHasFallbackContent() const
431 Node* node = this->node();
432 if (!is<HTMLCanvasElement>(node))
434 HTMLCanvasElement& canvasElement = downcast<HTMLCanvasElement>(*node);
435 // If it has any children that are elements, we'll assume it might be fallback
436 // content. If it has no children or its only children are not elements
437 // (e.g. just text nodes), it doesn't have fallback content.
438 return childrenOfType<Element>(canvasElement).first();
441 bool AccessibilityNodeObject::isImageButton() const
443 return isNativeImage() && isButton();
446 bool AccessibilityNodeObject::isNativeTextControl() const
448 Node* node = this->node();
452 if (is<HTMLTextAreaElement>(*node))
455 if (is<HTMLInputElement>(*node)) {
456 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
457 return input.isText() || input.isNumberField();
463 bool AccessibilityNodeObject::isSearchField() const
465 Node* node = this->node();
469 if (roleValue() == AccessibilityRole::SearchField)
472 if (!is<HTMLInputElement>(*node))
475 auto& inputElement = downcast<HTMLInputElement>(*node);
477 // Some websites don't label their search fields as such. However, they will
478 // use the word "search" in either the form or input type. This won't catch every case,
479 // but it will catch google.com for example.
481 // Check the node name of the input type, sometimes it's "search".
482 const AtomicString& nameAttribute = getAttribute(nameAttr);
483 if (nameAttribute.containsIgnoringASCIICase("search"))
486 // Check the form action and the name, which will sometimes be "search".
487 auto* form = inputElement.form();
488 if (form && (form->name().containsIgnoringASCIICase("search") || form->action().containsIgnoringASCIICase("search")))
494 bool AccessibilityNodeObject::isNativeImage() const
496 Node* node = this->node();
500 if (is<HTMLImageElement>(*node))
503 if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag))
506 if (is<HTMLInputElement>(*node)) {
507 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
508 return input.isImageButton();
514 bool AccessibilityNodeObject::isImage() const
516 return roleValue() == AccessibilityRole::Image;
519 bool AccessibilityNodeObject::isPasswordField() const
521 auto* node = this->node();
522 if (!is<HTMLInputElement>(node))
525 if (ariaRoleAttribute() != AccessibilityRole::Unknown)
528 return downcast<HTMLInputElement>(*node).isPasswordField();
531 AccessibilityObject* AccessibilityNodeObject::passwordFieldOrContainingPasswordField()
533 Node* node = this->node();
537 if (is<HTMLInputElement>(*node) && downcast<HTMLInputElement>(*node).isPasswordField())
540 auto* element = node->shadowHost();
541 if (!is<HTMLInputElement>(element))
544 if (auto* cache = axObjectCache())
545 return cache->getOrCreate(element);
550 bool AccessibilityNodeObject::isInputImage() const
552 Node* node = this->node();
553 if (is<HTMLInputElement>(node) && roleValue() == AccessibilityRole::Button) {
554 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
555 return input.isImageButton();
561 bool AccessibilityNodeObject::isProgressIndicator() const
563 return roleValue() == AccessibilityRole::ProgressIndicator;
566 bool AccessibilityNodeObject::isSlider() const
568 return roleValue() == AccessibilityRole::Slider;
571 bool AccessibilityNodeObject::isMenuRelated() const
573 switch (roleValue()) {
574 case AccessibilityRole::Menu:
575 case AccessibilityRole::MenuBar:
576 case AccessibilityRole::MenuButton:
577 case AccessibilityRole::MenuItem:
578 case AccessibilityRole::MenuItemCheckbox:
579 case AccessibilityRole::MenuItemRadio:
586 bool AccessibilityNodeObject::isMenu() const
588 return roleValue() == AccessibilityRole::Menu;
591 bool AccessibilityNodeObject::isMenuBar() const
593 return roleValue() == AccessibilityRole::MenuBar;
596 bool AccessibilityNodeObject::isMenuButton() const
598 return roleValue() == AccessibilityRole::MenuButton;
601 bool AccessibilityNodeObject::isMenuItem() const
603 switch (roleValue()) {
604 case AccessibilityRole::MenuItem:
605 case AccessibilityRole::MenuItemRadio:
606 case AccessibilityRole::MenuItemCheckbox:
613 bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const
615 Node* node = this->node();
616 if (!is<HTMLInputElement>(node))
619 auto& input = downcast<HTMLInputElement>(*node);
620 return input.isCheckbox() || input.isRadioButton();
623 bool AccessibilityNodeObject::isEnabled() const
625 // ARIA says that the disabled status applies to the current element and all descendant elements.
626 for (AccessibilityObject* object = const_cast<AccessibilityNodeObject*>(this); object; object = object->parentObject()) {
627 const AtomicString& disabledStatus = object->getAttribute(aria_disabledAttr);
628 if (equalLettersIgnoringASCIICase(disabledStatus, "true"))
630 if (equalLettersIgnoringASCIICase(disabledStatus, "false"))
634 if (roleValue() == AccessibilityRole::HorizontalRule)
637 Node* node = this->node();
638 if (!is<Element>(node))
641 return !downcast<Element>(*node).isDisabledFormControl();
644 bool AccessibilityNodeObject::isIndeterminate() const
646 return equalLettersIgnoringASCIICase(getAttribute(indeterminateAttr), "true");
649 bool AccessibilityNodeObject::isPressed() const
654 Node* node = this->node();
658 // If this is an toggle button, check the aria-pressed attribute rather than node()->active()
659 if (isToggleButton())
660 return equalLettersIgnoringASCIICase(getAttribute(aria_pressedAttr), "true");
662 if (!is<Element>(*node))
664 return downcast<Element>(*node).active();
667 bool AccessibilityNodeObject::isChecked() const
669 Node* node = this->node();
673 // First test for native checkedness semantics
674 if (is<HTMLInputElement>(*node))
675 return downcast<HTMLInputElement>(*node).shouldAppearChecked();
677 // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute
678 bool validRole = false;
679 switch (ariaRoleAttribute()) {
680 case AccessibilityRole::RadioButton:
681 case AccessibilityRole::CheckBox:
682 case AccessibilityRole::MenuItem:
683 case AccessibilityRole::MenuItemCheckbox:
684 case AccessibilityRole::MenuItemRadio:
685 case AccessibilityRole::Switch:
692 if (validRole && equalLettersIgnoringASCIICase(getAttribute(aria_checkedAttr), "true"))
698 bool AccessibilityNodeObject::isHovered() const
700 Node* node = this->node();
701 return is<Element>(node) && downcast<Element>(*node).hovered();
704 bool AccessibilityNodeObject::isMultiSelectable() const
706 const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
707 if (equalLettersIgnoringASCIICase(ariaMultiSelectable, "true"))
709 if (equalLettersIgnoringASCIICase(ariaMultiSelectable, "false"))
712 return node() && node()->hasTagName(selectTag) && downcast<HTMLSelectElement>(*node()).multiple();
715 bool AccessibilityNodeObject::isRequired() const
717 // Explicit aria-required values should trump native required attributes.
718 const AtomicString& requiredValue = getAttribute(aria_requiredAttr);
719 if (equalLettersIgnoringASCIICase(requiredValue, "true"))
721 if (equalLettersIgnoringASCIICase(requiredValue, "false"))
724 Node* n = this->node();
725 if (is<HTMLFormControlElement>(n))
726 return downcast<HTMLFormControlElement>(*n).isRequired();
731 bool AccessibilityNodeObject::supportsRequiredAttribute() const
733 switch (roleValue()) {
734 case AccessibilityRole::Button:
735 return isFileUploadButton();
736 case AccessibilityRole::Cell:
737 case AccessibilityRole::ColumnHeader:
738 case AccessibilityRole::CheckBox:
739 case AccessibilityRole::ComboBox:
740 case AccessibilityRole::Grid:
741 case AccessibilityRole::GridCell:
742 case AccessibilityRole::Incrementor:
743 case AccessibilityRole::ListBox:
744 case AccessibilityRole::PopUpButton:
745 case AccessibilityRole::RadioButton:
746 case AccessibilityRole::RadioGroup:
747 case AccessibilityRole::RowHeader:
748 case AccessibilityRole::Slider:
749 case AccessibilityRole::SpinButton:
750 case AccessibilityRole::TableHeaderContainer:
751 case AccessibilityRole::TextArea:
752 case AccessibilityRole::TextField:
753 case AccessibilityRole::ToggleButton:
760 int AccessibilityNodeObject::headingLevel() const
762 // headings can be in block flow and non-block flow
763 Node* node = this->node();
768 int ariaLevel = getAttribute(aria_levelAttr).toInt();
773 if (node->hasTagName(h1Tag))
776 if (node->hasTagName(h2Tag))
779 if (node->hasTagName(h3Tag))
782 if (node->hasTagName(h4Tag))
785 if (node->hasTagName(h5Tag))
788 if (node->hasTagName(h6Tag))
791 // The implicit value of aria-level is 2 for the heading role.
792 // https://www.w3.org/TR/wai-aria-1.1/#heading
793 if (ariaRoleAttribute() == AccessibilityRole::Heading)
799 String AccessibilityNodeObject::valueDescription() const
801 if (!isRangeControl())
804 return getAttribute(aria_valuetextAttr).string();
807 float AccessibilityNodeObject::valueForRange() const
809 if (is<HTMLInputElement>(node())) {
810 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
811 if (input.isRangeControl())
812 return input.valueAsNumber();
815 if (!isRangeControl())
818 // In ARIA 1.1, the implicit value for aria-valuenow on a spin button is 0.
819 // For other roles, it is half way between aria-valuemin and aria-valuemax.
820 auto& value = getAttribute(aria_valuenowAttr);
821 if (!value.isEmpty())
822 return value.toFloat();
824 return isSpinButton() ? 0 : (minValueForRange() + maxValueForRange()) / 2;
827 float AccessibilityNodeObject::maxValueForRange() const
829 if (is<HTMLInputElement>(node())) {
830 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
831 if (input.isRangeControl())
832 return input.maximum();
835 if (!isRangeControl())
838 auto& value = getAttribute(aria_valuemaxAttr);
839 if (!value.isEmpty())
840 return value.toFloat();
842 // In ARIA 1.1, the implicit value for aria-valuemax on a spin button
843 // is that there is no maximum value. For other roles, it is 100.
844 return isSpinButton() ? std::numeric_limits<float>::max() : 100.0f;
847 float AccessibilityNodeObject::minValueForRange() const
849 if (is<HTMLInputElement>(node())) {
850 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
851 if (input.isRangeControl())
852 return input.minimum();
855 if (!isRangeControl())
858 auto& value = getAttribute(aria_valueminAttr);
859 if (!value.isEmpty())
860 return value.toFloat();
862 // In ARIA 1.1, the implicit value for aria-valuemin on a spin button
863 // is that there is no minimum value. For other roles, it is 0.
864 return isSpinButton() ? -std::numeric_limits<float>::max() : 0.0f;
867 float AccessibilityNodeObject::stepValueForRange() const
869 return getAttribute(stepAttr).toFloat();
872 bool AccessibilityNodeObject::isHeading() const
874 return roleValue() == AccessibilityRole::Heading;
877 bool AccessibilityNodeObject::isLink() const
879 return roleValue() == AccessibilityRole::WebCoreLink;
882 bool AccessibilityNodeObject::isControl() const
884 Node* node = this->node();
888 return is<HTMLFormControlElement>(*node) || AccessibilityObject::isARIAControl(ariaRoleAttribute());
891 bool AccessibilityNodeObject::isFieldset() const
893 Node* node = this->node();
897 return node->hasTagName(fieldsetTag);
900 bool AccessibilityNodeObject::isGroup() const
902 AccessibilityRole role = roleValue();
903 return role == AccessibilityRole::Group || role == AccessibilityRole::TextGroup || role == AccessibilityRole::ApplicationGroup || role == AccessibilityRole::ApplicationTextGroup;
906 AccessibilityObject* AccessibilityNodeObject::selectedRadioButton()
911 // Find the child radio button that is selected (ie. the intValue == 1).
912 for (const auto& child : children()) {
913 if (child->roleValue() == AccessibilityRole::RadioButton && child->checkboxOrRadioValue() == AccessibilityButtonState::On)
919 AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
924 // FIXME: Is this valid? ARIA tab items support aria-selected; not aria-checked.
925 // Find the child tab item that is selected (ie. the intValue == 1).
926 AccessibilityObject::AccessibilityChildrenVector tabs;
929 for (const auto& child : children()) {
930 if (child->isTabItem() && (child->isChecked() || child->isSelected()))
936 AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const
938 if (isNativeCheckboxOrRadio())
939 return isIndeterminate() ? AccessibilityButtonState::Mixed : isChecked() ? AccessibilityButtonState::On : AccessibilityButtonState::Off;
941 return AccessibilityObject::checkboxOrRadioValue();
944 Element* AccessibilityNodeObject::anchorElement() const
946 Node* node = this->node();
950 AXObjectCache* cache = axObjectCache();
952 // search up the DOM tree for an anchor element
953 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
954 for ( ; node; node = node->parentNode()) {
955 if (is<HTMLAnchorElement>(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isLink()))
956 return downcast<Element>(node);
962 static bool isNodeActionElement(Node* node)
964 if (is<HTMLInputElement>(*node)) {
965 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
966 if (!input.isDisabledFormControl() && (input.isRadioButton() || input.isCheckbox() || input.isTextButton() || input.isFileUpload() || input.isImageButton()))
968 } else if (node->hasTagName(buttonTag) || node->hasTagName(selectTag))
974 static Element* nativeActionElement(Node* start)
979 // Do a deep-dive to see if any nodes should be used as the action element.
980 // We have to look at Nodes, since this method should only be called on objects that do not have children (like buttons).
981 // It solves the problem when authors put role="button" on a group and leave the actual button inside the group.
983 for (Node* child = start->firstChild(); child; child = child->nextSibling()) {
984 if (isNodeActionElement(child))
985 return downcast<Element>(child);
987 if (Element* subChild = nativeActionElement(child))
993 Element* AccessibilityNodeObject::actionElement() const
995 Node* node = this->node();
999 if (isNodeActionElement(node))
1000 return downcast<Element>(node);
1002 if (AccessibilityObject::isARIAInput(ariaRoleAttribute()))
1003 return downcast<Element>(node);
1005 switch (roleValue()) {
1006 case AccessibilityRole::Button:
1007 case AccessibilityRole::PopUpButton:
1008 case AccessibilityRole::ToggleButton:
1009 case AccessibilityRole::Tab:
1010 case AccessibilityRole::MenuItem:
1011 case AccessibilityRole::MenuItemCheckbox:
1012 case AccessibilityRole::MenuItemRadio:
1013 case AccessibilityRole::ListItem:
1014 // Check if the author is hiding the real control element inside the ARIA element.
1015 if (Element* nativeElement = nativeActionElement(node))
1016 return nativeElement;
1017 return downcast<Element>(node);
1022 Element* elt = anchorElement();
1024 elt = mouseButtonListener();
1028 Element* AccessibilityNodeObject::mouseButtonListener(MouseButtonListenerResultFilter filter) const
1030 Node* node = this->node();
1034 // check if our parent is a mouse button listener
1035 // FIXME: Do the continuation search like anchorElement does
1036 for (auto& element : elementLineage(is<Element>(*node) ? downcast<Element>(node) : node->parentElement())) {
1037 // 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.
1038 // It can cause false positives, where every piece of text is labeled as accepting press actions.
1039 if (element.hasTagName(bodyTag) && isStaticText() && filter == ExcludeBodyElement)
1042 if (element.hasEventListeners(eventNames().clickEvent) || element.hasEventListeners(eventNames().mousedownEvent) || element.hasEventListeners(eventNames().mouseupEvent))
1049 bool AccessibilityNodeObject::isDescendantOfBarrenParent() const
1051 if (!m_isIgnoredFromParentData.isNull())
1052 return m_isIgnoredFromParentData.isDescendantOfBarrenParent;
1054 for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) {
1055 if (!object->canHaveChildren())
1062 void AccessibilityNodeObject::alterSliderValue(bool increase)
1064 if (roleValue() != AccessibilityRole::Slider)
1067 if (!getAttribute(stepAttr).isEmpty())
1068 changeValueByStep(increase);
1070 changeValueByPercent(increase ? 5 : -5);
1073 void AccessibilityNodeObject::increment()
1075 if (dispatchAccessibilityEventWithType(AccessibilityEventType::Increment))
1077 UserGestureIndicator gestureIndicator(ProcessingUserGesture, document());
1078 alterSliderValue(true);
1081 void AccessibilityNodeObject::decrement()
1083 if (dispatchAccessibilityEventWithType(AccessibilityEventType::Decrement))
1085 UserGestureIndicator gestureIndicator(ProcessingUserGesture, 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 == AccessibilityRole::Video || role == AccessibilityRole::Audio)
1130 // If it has an aria role, it's not generic.
1131 if (m_ariaRole != AccessibilityRole::Unknown)
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 == AccessibilityRole::WebArea)
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 == AccessibilityRole::SVGRoot)
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(Node* node, const char* role)
1185 // FIXME: Either we should add a null check here or change the function to take a reference instead of a pointer.
1186 ContainerNode* parent = node->parentNode();
1190 for (auto& sibling : childrenOfType<Element>(*parent)) {
1191 // FIXME: Should skip sibling that is the same as the node.
1192 if (equalIgnoringASCIICase(sibling.attributeWithoutSynchronization(roleAttr), role))
1199 Element* AccessibilityNodeObject::menuElementForMenuButton() const
1201 if (ariaRoleAttribute() != AccessibilityRole::MenuButton)
1204 return siblingWithAriaRole(node(), "menu");
1207 AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const
1209 if (AXObjectCache* cache = axObjectCache())
1210 return cache->getOrCreate(menuElementForMenuButton());
1214 Element* AccessibilityNodeObject::menuItemElementForMenu() const
1216 if (ariaRoleAttribute() != AccessibilityRole::Menu)
1219 return siblingWithAriaRole(node(), "menuitem");
1222 AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
1224 AXObjectCache* cache = axObjectCache();
1228 Element* menuItem = menuItemElementForMenu();
1231 // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem
1232 AccessibilityObject* menuItemAX = cache->getOrCreate(menuItem);
1233 if (menuItemAX && menuItemAX->isMenuButton())
1239 AccessibilityObject* AccessibilityNodeObject::captionForFigure() const
1241 if (!isFigureElement())
1244 AXObjectCache* cache = axObjectCache();
1248 Node* node = this->node();
1249 for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
1250 if (child->hasTagName(figcaptionTag))
1251 return cache->getOrCreate(child);
1256 bool AccessibilityNodeObject::usesAltTagForTextComputation() const
1258 return isImage() || isInputImage() || isNativeImage() || isCanvas() || (node() && node()->hasTagName(imgTag));
1261 bool AccessibilityNodeObject::isLabelable() const
1263 Node* node = this->node();
1267 return is<HTMLInputElement>(*node) || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl() || isProgressIndicator() || isMeter();
1270 String AccessibilityNodeObject::textForLabelElement(Element* element) const
1272 String result = String();
1273 if (!is<HTMLLabelElement>(*element))
1276 HTMLLabelElement* label = downcast<HTMLLabelElement>(element);
1277 // Check to see if there's aria-labelledby attribute on the label element.
1278 if (AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label))
1279 result = labelObject->ariaLabeledByAttribute();
1281 return !result.isEmpty() ? result : accessibleNameForNode(label);
1284 void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder) const
1286 Node* node = this->node();
1290 if (isLabelable()) {
1291 if (HTMLLabelElement* label = labelForElement(downcast<Element>(node))) {
1292 String innerText = textForLabelElement(label);
1294 // Only use the <label> text if there's no ARIA override.
1295 if (!innerText.isEmpty() && !ariaAccessibilityDescription())
1296 textOrder.append(AccessibilityText(innerText, isMeter() ? AccessibilityTextSource::Alternative : AccessibilityTextSource::LabelByElement, axObjectCache()->getOrCreate(label)));
1301 AccessibilityObject* titleUIElement = this->titleUIElement();
1303 textOrder.append(AccessibilityText(String(), AccessibilityTextSource::LabelByElement, titleUIElement));
1306 void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
1309 String webAreaText = alternativeTextForWebArea();
1310 if (!webAreaText.isEmpty())
1311 textOrder.append(AccessibilityText(webAreaText, AccessibilityTextSource::Alternative));
1315 ariaLabeledByText(textOrder);
1317 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1318 if (!ariaLabel.isEmpty())
1319 textOrder.append(AccessibilityText(ariaLabel, AccessibilityTextSource::Alternative));
1321 if (usesAltTagForTextComputation()) {
1322 if (is<RenderImage>(renderer())) {
1323 String renderAltText = downcast<RenderImage>(*renderer()).altText();
1325 // RenderImage will return title as a fallback from altText, but we don't want title here because we consider that in helpText.
1326 if (!renderAltText.isEmpty() && renderAltText != getAttribute(titleAttr)) {
1327 textOrder.append(AccessibilityText(renderAltText, AccessibilityTextSource::Alternative));
1331 // Images should use alt as long as the attribute is present, even if empty.
1332 // Otherwise, it should fallback to other methods, like the title attribute.
1333 const AtomicString& alt = getAttribute(altAttr);
1335 textOrder.append(AccessibilityText(alt, AccessibilityTextSource::Alternative));
1338 Node* node = this->node();
1342 // The fieldset element derives its alternative text from the first associated legend element if one is available.
1343 if (is<HTMLFieldSetElement>(*node)) {
1344 AccessibilityObject* object = axObjectCache()->getOrCreate(downcast<HTMLFieldSetElement>(*node).legend());
1345 if (object && !object->isHidden())
1346 textOrder.append(AccessibilityText(accessibleNameForNode(object->node()), AccessibilityTextSource::Alternative));
1349 // The figure element derives its alternative text from the first associated figcaption element if one is available.
1350 if (isFigureElement()) {
1351 AccessibilityObject* captionForFigure = this->captionForFigure();
1352 if (captionForFigure && !captionForFigure->isHidden())
1353 textOrder.append(AccessibilityText(accessibleNameForNode(captionForFigure->node()), AccessibilityTextSource::Alternative));
1356 // Tree items missing a label are labeled by all child elements.
1357 if (isTreeItem() && ariaLabel.isEmpty() && ariaLabeledByAttribute().isEmpty())
1358 textOrder.append(AccessibilityText(accessibleNameForNode(node), AccessibilityTextSource::Alternative));
1361 if (node->isMathMLElement())
1362 textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AccessibilityTextSource::Alternative));
1366 void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) const
1368 Node* node = this->node();
1372 bool isInputTag = is<HTMLInputElement>(*node);
1374 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
1375 if (input.isTextButton()) {
1376 textOrder.append(AccessibilityText(input.valueWithDefault(), AccessibilityTextSource::Visible));
1381 // If this node isn't rendered, there's no inner text we can extract from a select element.
1382 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1385 bool useTextUnderElement = false;
1387 switch (roleValue()) {
1388 case AccessibilityRole::PopUpButton:
1389 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1390 if (node->hasTagName(selectTag))
1393 case AccessibilityRole::Button:
1394 case AccessibilityRole::ToggleButton:
1395 case AccessibilityRole::CheckBox:
1396 case AccessibilityRole::ListBoxOption:
1397 // MacOS does not expect native <li> elements to expose label information, it only expects leaf node elements to do that.
1398 #if !PLATFORM(COCOA)
1399 case AccessibilityRole::ListItem:
1401 case AccessibilityRole::MenuButton:
1402 case AccessibilityRole::MenuItem:
1403 case AccessibilityRole::MenuItemCheckbox:
1404 case AccessibilityRole::MenuItemRadio:
1405 case AccessibilityRole::RadioButton:
1406 case AccessibilityRole::Switch:
1407 case AccessibilityRole::Tab:
1408 useTextUnderElement = true;
1414 // If it's focusable but it's not content editable or a known control type, then it will appear to
1415 // the user as a single atomic object, so we should use its text as the default title.
1416 if (isHeading() || isLink())
1417 useTextUnderElement = true;
1420 useTextUnderElement = true;
1422 if (useTextUnderElement) {
1423 AccessibilityTextUnderElementMode mode;
1425 // Headings often include links as direct children. Those links need to be included in text under element.
1427 mode.includeFocusableContent = true;
1429 String text = textUnderElement(mode);
1430 if (!text.isEmpty())
1431 textOrder.append(AccessibilityText(text, AccessibilityTextSource::Children));
1435 void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) const
1437 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1438 if (!ariaHelp.isEmpty())
1439 textOrder.append(AccessibilityText(ariaHelp, AccessibilityTextSource::Help));
1441 String describedBy = ariaDescribedByAttribute();
1442 if (!describedBy.isEmpty())
1443 textOrder.append(AccessibilityText(describedBy, AccessibilityTextSource::Summary));
1444 else if (isControl()) {
1445 // For controls, use their fieldset parent's described-by text if available.
1446 auto matchFunc = [] (const AccessibilityObject& object) {
1447 return object.isFieldset() && !object.ariaDescribedByAttribute().isEmpty();
1449 if (const auto* parent = AccessibilityObject::matchedParent(*this, false, WTFMove(matchFunc)))
1450 textOrder.append(AccessibilityText(parent->ariaDescribedByAttribute(), AccessibilityTextSource::Summary));
1453 // Summary attribute used as help text on tables.
1454 const AtomicString& summary = getAttribute(summaryAttr);
1455 if (!summary.isEmpty())
1456 textOrder.append(AccessibilityText(summary, AccessibilityTextSource::Summary));
1458 // The title attribute should be used as help text unless it is already being used as descriptive text.
1459 // However, when the title attribute is the only text alternative provided, it may be exposed as the
1460 // descriptive text. This is problematic in the case of meters because the HTML spec suggests authors
1461 // can expose units through this attribute. Therefore, if the element is a meter, change its source
1462 // type to AccessibilityTextSource::Help.
1463 const AtomicString& title = getAttribute(titleAttr);
1464 if (!title.isEmpty()) {
1465 if (!isMeter() && !roleIgnoresTitle())
1466 textOrder.append(AccessibilityText(title, AccessibilityTextSource::TitleTag));
1468 textOrder.append(AccessibilityText(title, AccessibilityTextSource::Help));
1472 void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textOrder)
1474 titleElementText(textOrder);
1475 alternativeText(textOrder);
1476 visibleText(textOrder);
1477 helpText(textOrder);
1479 String placeholder = placeholderValue();
1480 if (!placeholder.isEmpty())
1481 textOrder.append(AccessibilityText(placeholder, AccessibilityTextSource::Placeholder));
1484 void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
1486 String ariaLabeledBy = ariaLabeledByAttribute();
1487 if (!ariaLabeledBy.isEmpty()) {
1488 Vector<Element*> elements;
1489 ariaLabeledByElements(elements);
1491 Vector<RefPtr<AccessibilityObject>> axElements;
1492 for (const auto& element : elements)
1493 axElements.append(axObjectCache()->getOrCreate(element));
1495 textOrder.append(AccessibilityText(ariaLabeledBy, AccessibilityTextSource::Alternative, WTFMove(axElements)));
1499 String AccessibilityNodeObject::alternativeTextForWebArea() const
1501 // The WebArea description should follow this order:
1502 // aria-label on the <html>
1503 // title on the <html>
1504 // <title> inside the <head> (of it was set through JS)
1505 // name on the <html>
1507 // aria-label on the <iframe>
1508 // title on the <iframe>
1509 // name on the <iframe>
1511 Document* document = this->document();
1515 // Check if the HTML element has an aria-label for the webpage.
1516 if (Element* documentElement = document->documentElement()) {
1517 const AtomicString& ariaLabel = documentElement->attributeWithoutSynchronization(aria_labelAttr);
1518 if (!ariaLabel.isEmpty())
1522 if (auto* owner = document->ownerElement()) {
1523 if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
1524 const AtomicString& title = owner->attributeWithoutSynchronization(titleAttr);
1525 if (!title.isEmpty())
1528 return owner->getNameAttribute();
1531 String documentTitle = document->title();
1532 if (!documentTitle.isEmpty())
1533 return documentTitle;
1535 if (auto* body = document->bodyOrFrameset())
1536 return body->getNameAttribute();
1541 String AccessibilityNodeObject::accessibilityDescription() const
1543 // Static text should not have a description, it should only have a stringValue.
1544 if (roleValue() == AccessibilityRole::StaticText)
1547 String ariaDescription = ariaAccessibilityDescription();
1548 if (!ariaDescription.isEmpty())
1549 return ariaDescription;
1551 if (usesAltTagForTextComputation()) {
1552 // Images should use alt as long as the attribute is present, even if empty.
1553 // Otherwise, it should fallback to other methods, like the title attribute.
1554 const AtomicString& alt = getAttribute(altAttr);
1560 if (is<MathMLElement>(m_node))
1561 return getAttribute(MathMLNames::alttextAttr);
1564 // An element's descriptive text is comprised of title() (what's visible on the screen) and accessibilityDescription() (other descriptive text).
1565 // Both are used to generate what a screen reader speaks.
1566 // If this point is reached (i.e. there's no accessibilityDescription) and there's no title(), we should fallback to using the title attribute.
1567 // 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).
1568 // https://bugs.webkit.org/show_bug.cgi?id=170475: An exception is when the element is semantically unimportant. In those cases, title text should remain as help text.
1569 if (title().isEmpty() && !roleIgnoresTitle())
1570 return getAttribute(titleAttr);
1575 // Returns whether the role was not intended to play a semantically meaningful part of the
1576 // accessibility hierarchy. This applies to generic groups like <div>'s with no role value set.
1577 bool AccessibilityNodeObject::roleIgnoresTitle() const
1579 if (ariaRoleAttribute() != AccessibilityRole::Unknown)
1582 switch (roleValue()) {
1583 case AccessibilityRole::Div:
1584 case AccessibilityRole::Unknown:
1591 String AccessibilityNodeObject::helpText() const
1593 Node* node = this->node();
1597 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1598 if (!ariaHelp.isEmpty())
1601 String describedBy = ariaDescribedByAttribute();
1602 if (!describedBy.isEmpty())
1605 String description = accessibilityDescription();
1606 for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
1607 if (is<HTMLElement>(*ancestor)) {
1608 HTMLElement& element = downcast<HTMLElement>(*ancestor);
1609 const AtomicString& summary = element.getAttribute(summaryAttr);
1610 if (!summary.isEmpty())
1613 // The title attribute should be used as help text unless it is already being used as descriptive text.
1614 const AtomicString& title = element.getAttribute(titleAttr);
1615 if (!title.isEmpty() && description != title)
1619 // Only take help text from an ancestor element if its a group or an unknown role. If help was
1620 // added to those kinds of elements, it is likely it was meant for a child element.
1621 if (AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor)) {
1622 if (!axObj->isGroup() && axObj->roleValue() != AccessibilityRole::Unknown)
1630 unsigned AccessibilityNodeObject::hierarchicalLevel() const
1632 Node* node = this->node();
1633 if (!is<Element>(node))
1635 Element& element = downcast<Element>(*node);
1636 const AtomicString& ariaLevel = element.attributeWithoutSynchronization(aria_levelAttr);
1637 if (!ariaLevel.isEmpty())
1638 return ariaLevel.toInt();
1640 // Only tree item will calculate its level through the DOM currently.
1641 if (roleValue() != AccessibilityRole::TreeItem)
1644 // Hierarchy leveling starts at 1, to match the aria-level spec.
1645 // We measure tree hierarchy by the number of groups that the item is within.
1647 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
1648 AccessibilityRole parentRole = parent->ariaRoleAttribute();
1649 if (parentRole == AccessibilityRole::ApplicationGroup)
1651 else if (parentRole == AccessibilityRole::Tree)
1658 void AccessibilityNodeObject::setIsExpanded(bool expand)
1660 if (is<HTMLDetailsElement>(node())) {
1661 auto& details = downcast<HTMLDetailsElement>(*node());
1662 if (expand != details.isOpen())
1663 details.toggleOpen();
1667 // When building the textUnderElement for an object, determine whether or not
1668 // we should include the inner text of this given descendant object or skip it.
1669 static bool shouldUseAccessibilityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode)
1671 // Do not use any heuristic if we are explicitly asking to include all the children.
1672 if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)
1675 // Consider this hypothetical example:
1678 // Table of contents
1680 // <a href="#start">Jump to start of book</a>
1682 // <li><a href="#1">Chapter 1</a></li>
1683 // <li><a href="#1">Chapter 2</a></li>
1687 // The goal is to return a reasonable title for the outer container div, because
1688 // it's focusable - but without making its title be the full inner text, which is
1689 // quite long. As a heuristic, skip links, controls, and elements that are usually
1690 // containers with lots of children.
1692 // ARIA states that certain elements are not allowed to expose their children content for name calculation.
1693 if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren
1694 && !obj->accessibleNameDerivesFromContent())
1697 if (equalLettersIgnoringASCIICase(obj->getAttribute(aria_hiddenAttr), "true"))
1700 // If something doesn't expose any children, then we can always take the inner text content.
1701 // This is what we want when someone puts an <a> inside a <button> for example.
1702 if (obj->isDescendantOfBarrenParent())
1705 // Skip focusable children, so we don't include the text of links and controls.
1706 if (obj->canSetFocusAttribute() && !mode.includeFocusableContent)
1709 // Skip big container elements like lists, tables, etc.
1710 if (is<AccessibilityList>(*obj))
1713 if (is<AccessibilityTable>(*obj) && downcast<AccessibilityTable>(*obj).isExposableThroughAccessibility())
1716 if (obj->isTree() || obj->isCanvas())
1722 static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, const String& childText)
1724 if (!builder.length() || !childText.length())
1727 // We don't need to add an additional space before or after a line break.
1728 return !(isHTMLLineBreak(childText[0]) || isHTMLLineBreak(builder[builder.length() - 1]));
1731 static void appendNameToStringBuilder(StringBuilder& builder, const String& text)
1733 if (shouldAddSpaceBeforeAppendingNextElement(builder, text))
1734 builder.append(' ');
1735 builder.append(text);
1738 String AccessibilityNodeObject::textUnderElement(AccessibilityTextUnderElementMode mode) const
1740 Node* node = this->node();
1742 return downcast<Text>(*node).wholeText();
1744 // The Accname specification states that if the current node is hidden, and not directly
1745 // referenced by aria-labelledby or aria-describedby, and is not a host language text
1746 // alternative, the empty string should be returned.
1747 if (isHidden() && !is<HTMLLabelElement>(node) && (node && !ancestorsOfType<HTMLCanvasElement>(*node).first())) {
1748 AccessibilityObject::AccessibilityChildrenVector labelFor;
1749 AccessibilityObject::AccessibilityChildrenVector descriptionFor;
1750 ariaLabelledByReferencingElements(labelFor);
1751 ariaDescribedByReferencingElements(descriptionFor);
1752 if (!labelFor.size() && !descriptionFor.size())
1756 StringBuilder builder;
1757 for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
1758 if (mode.ignoredChildNode && child->node() == mode.ignoredChildNode)
1761 bool shouldDeriveNameFromAuthor = (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren && !child->accessibleNameDerivesFromContent());
1762 if (shouldDeriveNameFromAuthor) {
1763 appendNameToStringBuilder(builder, accessibleNameForNode(child->node()));
1767 if (!shouldUseAccessibilityObjectInnerText(child, mode))
1770 if (is<AccessibilityNodeObject>(*child)) {
1771 // We should ignore the child if it's labeled by this node.
1772 // This could happen when this node labels multiple child nodes and we didn't
1773 // skip in the above ignoredChildNode check.
1774 Vector<Element*> labeledByElements;
1775 downcast<AccessibilityNodeObject>(*child).ariaLabeledByElements(labeledByElements);
1776 if (labeledByElements.contains(node))
1779 Vector<AccessibilityText> textOrder;
1780 downcast<AccessibilityNodeObject>(*child).alternativeText(textOrder);
1781 if (textOrder.size() > 0 && textOrder[0].text.length()) {
1782 appendNameToStringBuilder(builder, textOrder[0].text);
1787 String childText = child->textUnderElement(mode);
1788 if (childText.length())
1789 appendNameToStringBuilder(builder, childText);
1792 return builder.toString().stripWhiteSpace().simplifyWhiteSpace(isHTMLSpaceButNotLineBreak);
1795 String AccessibilityNodeObject::title() const
1797 Node* node = this->node();
1801 bool isInputTag = is<HTMLInputElement>(*node);
1803 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
1804 if (input.isTextButton())
1805 return input.valueWithDefault();
1808 if (isLabelable()) {
1809 HTMLLabelElement* label = labelForElement(downcast<Element>(node));
1810 // Use the label text as the title if 1) the title element is NOT an exposed element and 2) there's no ARIA override.
1811 if (label && !exposesTitleUIElement() && !ariaAccessibilityDescription().length())
1812 return textForLabelElement(label);
1815 // If this node isn't rendered, there's no inner text we can extract from a select element.
1816 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1819 switch (roleValue()) {
1820 case AccessibilityRole::PopUpButton:
1821 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1822 if (node->hasTagName(selectTag))
1825 case AccessibilityRole::Button:
1826 case AccessibilityRole::ToggleButton:
1827 case AccessibilityRole::CheckBox:
1828 case AccessibilityRole::ListBoxOption:
1829 case AccessibilityRole::ListItem:
1830 case AccessibilityRole::MenuButton:
1831 case AccessibilityRole::MenuItem:
1832 case AccessibilityRole::MenuItemCheckbox:
1833 case AccessibilityRole::MenuItemRadio:
1834 case AccessibilityRole::RadioButton:
1835 case AccessibilityRole::Switch:
1836 case AccessibilityRole::Tab:
1837 return textUnderElement();
1838 // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>.
1839 case AccessibilityRole::SVGRoot:
1846 return textUnderElement();
1848 return textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeSkipIgnoredChildren, true));
1853 String AccessibilityNodeObject::text() const
1855 // If this is a user defined static text, use the accessible name computation.
1856 if (isARIAStaticText()) {
1857 Vector<AccessibilityText> textOrder;
1858 alternativeText(textOrder);
1859 if (textOrder.size() > 0 && textOrder[0].text.length())
1860 return textOrder[0].text;
1863 if (!isTextControl())
1866 Node* node = this->node();
1870 if (isNativeTextControl() && is<HTMLTextFormControlElement>(*node))
1871 return downcast<HTMLTextFormControlElement>(*node).value();
1873 if (!node->isElementNode())
1876 return downcast<Element>(node)->innerText();
1879 String AccessibilityNodeObject::stringValue() const
1881 Node* node = this->node();
1885 if (isARIAStaticText()) {
1886 String staticText = text();
1887 if (!staticText.length())
1888 staticText = textUnderElement();
1892 if (node->isTextNode())
1893 return textUnderElement();
1895 if (node->hasTagName(selectTag)) {
1896 HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node);
1897 int selectedIndex = selectElement.selectedIndex();
1898 const Vector<HTMLElement*>& listItems = selectElement.listItems();
1899 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
1900 const AtomicString& overriddenDescription = listItems[selectedIndex]->attributeWithoutSynchronization(aria_labelAttr);
1901 if (!overriddenDescription.isNull())
1902 return overriddenDescription;
1904 if (!selectElement.multiple())
1905 return selectElement.value();
1909 if (isTextControl())
1912 // FIXME: We might need to implement a value here for more types
1913 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
1914 // this would require subclassing or making accessibilityAttributeNames do something other than return a
1915 // single static array.
1919 void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const
1925 #if ENABLE(INPUT_TYPE_COLOR)
1929 if (!is<HTMLInputElement>(node()))
1932 auto color = downcast<HTMLInputElement>(*node()).valueAsColor();
1939 // This function implements the ARIA accessible name as described by the Mozilla
1940 // ARIA Implementer's Guide.
1941 static String accessibleNameForNode(Node* node, Node* labelledbyNode)
1944 if (!is<Element>(node))
1947 Element& element = downcast<Element>(*node);
1948 const AtomicString& ariaLabel = element.attributeWithoutSynchronization(aria_labelAttr);
1949 if (!ariaLabel.isEmpty())
1952 const AtomicString& alt = element.attributeWithoutSynchronization(altAttr);
1956 // If the node can be turned into an AX object, we can use standard name computation rules.
1957 // If however, the node cannot (because there's no renderer e.g.) fallback to using the basic text underneath.
1958 AccessibilityObject* axObject = node->document().axObjectCache()->getOrCreate(node);
1960 String valueDescription = axObject->valueDescription();
1961 if (!valueDescription.isEmpty())
1962 return valueDescription;
1964 // The Accname specification states that if the name is being calculated for a combobox
1965 // or listbox inside a labeling element, return the text alternative of the chosen option.
1966 AccessibilityObject::AccessibilityChildrenVector children;
1967 if (axObject->isListBox())
1968 axObject->selectedChildren(children);
1969 else if (axObject->isComboBox()) {
1970 for (const auto& child : axObject->children()) {
1971 if (child->isListBox()) {
1972 child->selectedChildren(children);
1978 StringBuilder builder;
1980 for (const auto& child : children)
1981 appendNameToStringBuilder(builder, accessibleNameForNode(child->node()));
1983 childText = builder.toString();
1984 if (!childText.isEmpty())
1988 if (is<HTMLInputElement>(*node))
1989 return downcast<HTMLInputElement>(*node).value();
1993 if (axObject->accessibleNameDerivesFromContent())
1994 text = axObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren, true, labelledbyNode));
1996 text = element.innerText().simplifyWhiteSpace();
1998 if (!text.isEmpty())
2001 const AtomicString& title = element.attributeWithoutSynchronization(titleAttr);
2002 if (!title.isEmpty())
2008 String AccessibilityNodeObject::accessibilityDescriptionForChildren() const
2010 Node* node = this->node();
2014 AXObjectCache* cache = axObjectCache();
2018 StringBuilder builder;
2019 for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
2020 if (!is<Element>(child))
2023 if (AccessibilityObject* axObject = cache->getOrCreate(child)) {
2024 String description = axObject->ariaLabeledByAttribute();
2025 if (description.isEmpty())
2026 description = accessibleNameForNode(child);
2027 appendNameToStringBuilder(builder, description);
2031 return builder.toString();
2034 String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
2036 StringBuilder builder;
2037 unsigned size = elements.size();
2038 for (unsigned i = 0; i < size; ++i)
2039 appendNameToStringBuilder(builder, accessibleNameForNode(elements[i], node()));
2040 return builder.toString();
2043 String AccessibilityNodeObject::ariaDescribedByAttribute() const
2045 Vector<Element*> elements;
2046 elementsFromAttribute(elements, aria_describedbyAttr);
2048 return accessibilityDescriptionForElements(elements);
2051 void AccessibilityNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const
2053 elementsFromAttribute(elements, aria_labelledbyAttr);
2054 if (!elements.size())
2055 elementsFromAttribute(elements, aria_labeledbyAttr);
2059 String AccessibilityNodeObject::ariaLabeledByAttribute() const
2061 Vector<Element*> elements;
2062 ariaLabeledByElements(elements);
2064 return accessibilityDescriptionForElements(elements);
2067 bool AccessibilityNodeObject::hasAttributesRequiredForInclusion() const
2069 if (AccessibilityObject::hasAttributesRequiredForInclusion())
2072 // Avoid calculating the actual description here, which is expensive.
2073 // This means there might be more accessible elements in the tree if the labelledBy points to invalid elements, but that shouldn't cause any real problems.
2074 if (getAttribute(aria_labelledbyAttr).length() || getAttribute(aria_labeledbyAttr).length() || getAttribute(aria_labelAttr).length())
2080 bool AccessibilityNodeObject::canSetFocusAttribute() const
2082 Node* node = this->node();
2089 // NOTE: It would be more accurate to ask the document whether setFocusedElement() would
2090 // do anything. For example, setFocusedElement() will do nothing if the current focused
2091 // node will not relinquish the focus.
2092 if (!is<Element>(node))
2095 Element& element = downcast<Element>(*node);
2097 if (element.isDisabledFormControl())
2100 return element.supportsFocus();
2103 bool AccessibilityNodeObject::canSetValueAttribute() const
2105 Node* node = this->node();
2109 // The host-language readonly attribute trumps aria-readonly.
2110 if (is<HTMLTextAreaElement>(*node))
2111 return !downcast<HTMLTextAreaElement>(*node).isReadOnly();
2112 if (is<HTMLInputElement>(*node)) {
2113 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
2114 if (input.isTextField())
2115 return !input.isReadOnly();
2118 String readOnly = readOnlyValue();
2119 if (!readOnly.isEmpty())
2120 return readOnly == "true" ? false : true;
2122 if (isNonNativeTextControl())
2128 if (isProgressIndicator() || isSlider() || isScrollbar())
2132 // In ATK, input types which support aria-readonly are treated as having a
2133 // settable value if the user can modify the widget's value or its state.
2134 if (supportsReadOnly())
2137 if (isRadioButton()) {
2138 auto radioGroup = radioGroupAncestor();
2139 return radioGroup ? radioGroup->readOnlyValue() != "true" : true;
2144 Document* document = this->document();
2148 if (HTMLElement* body = document->bodyOrFrameset()) {
2149 if (body->hasEditableStyle())
2153 return document->hasEditableStyle();
2156 return node->hasEditableStyle();
2159 AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const
2161 const AtomicString& ariaRole = getAttribute(roleAttr);
2162 if (ariaRole.isNull() || ariaRole.isEmpty())
2163 return AccessibilityRole::Unknown;
2165 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
2167 // ARIA states if an item can get focus, it should not be presentational.
2168 if (role == AccessibilityRole::Presentational && canSetFocusAttribute())
2169 return AccessibilityRole::Unknown;
2171 if (role == AccessibilityRole::Button)
2172 role = buttonRoleType();
2174 if (role == AccessibilityRole::TextArea && !ariaIsMultiline())
2175 role = AccessibilityRole::TextField;
2177 role = remapAriaRoleDueToParent(role);
2179 // Presentational roles are invalidated by the presence of ARIA attributes.
2180 if (role == AccessibilityRole::Presentational && supportsARIAAttributes())
2181 role = AccessibilityRole::Unknown;
2183 // The ARIA spec states, "Authors must give each element with role region a brief label that
2184 // describes the purpose of the content in the region." The Core AAM states, "Special case:
2185 // if the region does not have an accessible name, do not expose the element as a landmark.
2186 // Use the native host language role of the element instead."
2187 if (role == AccessibilityRole::LandmarkRegion && !hasAttribute(aria_labelAttr) && !hasAttribute(aria_labelledbyAttr))
2188 role = AccessibilityRole::Unknown;
2190 if (static_cast<int>(role))
2193 return AccessibilityRole::Unknown;
2196 AccessibilityRole AccessibilityNodeObject::ariaRoleAttribute() const
2201 AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
2203 // Some objects change their role based on their parent.
2204 // However, asking for the unignoredParent calls accessibilityIsIgnored(), which can trigger a loop.
2205 // While inside the call stack of creating an element, we need to avoid accessibilityIsIgnored().
2206 // https://bugs.webkit.org/show_bug.cgi?id=65174
2208 if (role != AccessibilityRole::ListBoxOption && role != AccessibilityRole::MenuItem)
2211 for (AccessibilityObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
2212 AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
2214 // Selects and listboxes both have options as child roles, but they map to different roles within WebCore.
2215 if (role == AccessibilityRole::ListBoxOption && parentAriaRole == AccessibilityRole::Menu)
2216 return AccessibilityRole::MenuItem;
2217 // An aria "menuitem" may map to MenuButton or MenuItem depending on its parent.
2218 if (role == AccessibilityRole::MenuItem && parentAriaRole == AccessibilityRole::ApplicationGroup)
2219 return AccessibilityRole::MenuButton;
2221 // If the parent had a different role, then we don't need to continue searching up the chain.
2222 if (parentAriaRole != AccessibilityRole::Unknown)
2229 bool AccessibilityNodeObject::canSetSelectedAttribute() const
2231 // Elements that can be selected
2232 switch (roleValue()) {
2233 case AccessibilityRole::Cell:
2234 case AccessibilityRole::GridCell:
2235 case AccessibilityRole::RowHeader:
2236 case AccessibilityRole::Row:
2237 case AccessibilityRole::TabList:
2238 case AccessibilityRole::Tab:
2239 case AccessibilityRole::TreeGrid:
2240 case AccessibilityRole::TreeItem:
2241 case AccessibilityRole::Tree:
2242 case AccessibilityRole::MenuItemCheckbox:
2243 case AccessibilityRole::MenuItemRadio:
2244 case AccessibilityRole::MenuItem:
2251 } // namespace WebCore