1/*
2* Copyright (C) 2012, Google Inc. All rights reserved.
3*
4* Redistribution and use in source and binary forms, with or without
5* modification, are permitted provided that the following conditions
6* are met:
7*
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.
16*
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.
27*/
28
29#include "config.h"
30#include "AccessibilityNodeObject.h"
31
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 "Editing.h"
39#include "ElementIterator.h"
40#include "EventNames.h"
41#include "FloatRect.h"
42#include "Frame.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"
63#include "NodeList.h"
64#include "NodeTraversal.h"
65#include "ProgressTracker.h"
66#include "RenderImage.h"
67#include "RenderView.h"
68#include "SVGElement.h"
69#include "Text.h"
70#include "TextControlInnerElements.h"
71#include "UserGestureIndicator.h"
72#include "VisibleUnits.h"
73#include "Widget.h"
74#include <wtf/StdLibExtras.h>
75#include <wtf/text/StringBuilder.h>
76#include <wtf/unicode/CharacterNames.h>
77
78namespace WebCore {
79
80using namespace HTMLNames;
81
82static String accessibleNameForNode(Node* node, Node* labelledbyNode = nullptr);
83
84AccessibilityNodeObject::AccessibilityNodeObject(Node* node)
85 : AccessibilityObject()
86 , m_node(node)
87{
88}
89
90AccessibilityNodeObject::~AccessibilityNodeObject()
91{
92 ASSERT(isDetached());
93}
94
95void AccessibilityNodeObject::init()
96{
97#ifndef NDEBUG
98 ASSERT(!m_initialized);
99 m_initialized = true;
100#endif
101 m_role = determineAccessibilityRole();
102}
103
104Ref<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node)
105{
106 return adoptRef(*new AccessibilityNodeObject(node));
107}
108
109void AccessibilityNodeObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache)
110{
111 // AccessibilityObject calls clearChildren.
112 AccessibilityObject::detach(detachmentType, cache);
113 m_node = nullptr;
114}
115
116void AccessibilityNodeObject::childrenChanged()
117{
118 // This method is meant as a quick way of marking a portion of the accessibility tree dirty.
119 if (!node() && !renderer())
120 return;
121
122 AXObjectCache* cache = axObjectCache();
123 if (!cache)
124 return;
125 cache->postNotification(this, document(), AXObjectCache::AXChildrenChanged);
126
127 // Should make the sub tree dirty so that everything below will be updated correctly.
128 this->setNeedsToUpdateSubtree();
129 bool shouldStopUpdatingParent = false;
130
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();
138
139
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.
142
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
146 // time interval.
147 if (parent->supportsLiveRegion())
148 cache->postLiveRegionChangeNotification(parent);
149
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);
153
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;
157 }
158 }
159}
160
161void AccessibilityNodeObject::updateAccessibilityRole()
162{
163 bool ignoredStatus = accessibilityIsIgnored();
164 m_role = determineAccessibilityRole();
165
166 // The AX hierarchy only needs to be updated if the ignored status of an element has changed.
167 if (ignoredStatus != accessibilityIsIgnored())
168 childrenChanged();
169}
170
171AccessibilityObject* AccessibilityNodeObject::firstChild() const
172{
173 if (!node())
174 return nullptr;
175
176 Node* firstChild = node()->firstChild();
177
178 if (!firstChild)
179 return nullptr;
180
181 auto objectCache = axObjectCache();
182 return objectCache ? objectCache->getOrCreate(firstChild) : nullptr;
183}
184
185AccessibilityObject* AccessibilityNodeObject::lastChild() const
186{
187 if (!node())
188 return nullptr;
189
190 Node* lastChild = node()->lastChild();
191 if (!lastChild)
192 return nullptr;
193
194 auto objectCache = axObjectCache();
195 return objectCache ? objectCache->getOrCreate(lastChild) : nullptr;
196}
197
198AccessibilityObject* AccessibilityNodeObject::previousSibling() const
199{
200 if (!node())
201 return nullptr;
202
203 Node* previousSibling = node()->previousSibling();
204 if (!previousSibling)
205 return nullptr;
206
207 auto objectCache = axObjectCache();
208 return objectCache ? objectCache->getOrCreate(previousSibling) : nullptr;
209}
210
211AccessibilityObject* AccessibilityNodeObject::nextSibling() const
212{
213 if (!node())
214 return nullptr;
215
216 Node* nextSibling = node()->nextSibling();
217 if (!nextSibling)
218 return nullptr;
219
220 auto objectCache = axObjectCache();
221 return objectCache ? objectCache->getOrCreate(nextSibling) : nullptr;
222}
223
224AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const
225{
226 return parentObject();
227}
228
229AccessibilityObject* AccessibilityNodeObject::parentObject() const
230{
231 if (!node())
232 return nullptr;
233
234 Node* parentObj = node()->parentNode();
235 if (!parentObj)
236 return nullptr;
237
238 if (AXObjectCache* cache = axObjectCache())
239 return cache->getOrCreate(parentObj);
240
241 return nullptr;
242}
243
244LayoutRect AccessibilityNodeObject::elementRect() const
245{
246 return boundingBoxRect();
247}
248
249LayoutRect AccessibilityNodeObject::boundingBoxRect() const
250{
251 // AccessibilityNodeObjects have no mechanism yet to return a size or position.
252 // For now, let's return the position of the ancestor that does have a position,
253 // 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
255 LayoutRect boundingBox;
256
257 for (AccessibilityObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) {
258 if (positionProvider->isAccessibilityRenderObject()) {
259 LayoutRect parentRect = positionProvider->elementRect();
260 boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat()))));
261 boundingBox.setLocation(parentRect.location());
262 break;
263 }
264 }
265
266 return boundingBox;
267}
268
269void AccessibilityNodeObject::setNode(Node* node)
270{
271 m_node = node;
272}
273
274Document* AccessibilityNodeObject::document() const
275{
276 if (!node())
277 return nullptr;
278 return &node()->document();
279}
280
281AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole()
282{
283 if (!node())
284 return AccessibilityRole::Unknown;
285
286 if ((m_ariaRole = determineAriaRoleAttribute()) != AccessibilityRole::Unknown)
287 return m_ariaRole;
288
289 if (node()->isLink())
290 return AccessibilityRole::WebCoreLink;
291 if (node()->isTextNode())
292 return AccessibilityRole::StaticText;
293 if (node()->hasTagName(buttonTag))
294 return buttonRoleType();
295 if (is<HTMLInputElement>(*node())) {
296 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
297 if (input.isCheckbox())
298 return AccessibilityRole::CheckBox;
299 if (input.isRadioButton())
300 return AccessibilityRole::RadioButton;
301 if (input.isTextButton())
302 return buttonRoleType();
303 if (input.isRangeControl())
304 return AccessibilityRole::Slider;
305 if (input.isInputTypeHidden())
306 return AccessibilityRole::Ignored;
307 if (input.isSearchField())
308 return AccessibilityRole::SearchField;
309#if ENABLE(INPUT_TYPE_COLOR)
310 if (input.isColorControl())
311 return AccessibilityRole::ColorWell;
312#endif
313 return AccessibilityRole::TextField;
314 }
315 if (node()->hasTagName(selectTag)) {
316 HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node());
317 return selectElement.multiple() ? AccessibilityRole::ListBox : AccessibilityRole::PopUpButton;
318 }
319 if (is<HTMLTextAreaElement>(*node()))
320 return AccessibilityRole::TextArea;
321 if (headingLevel())
322 return AccessibilityRole::Heading;
323 if (node()->hasTagName(blockquoteTag))
324 return AccessibilityRole::Blockquote;
325 if (node()->hasTagName(divTag))
326 return AccessibilityRole::Div;
327 if (node()->hasTagName(pTag))
328 return AccessibilityRole::Paragraph;
329 if (is<HTMLLabelElement>(*node()))
330 return AccessibilityRole::Label;
331 if (is<Element>(*node()) && downcast<Element>(*node()).isFocusable())
332 return AccessibilityRole::Group;
333
334 return AccessibilityRole::Unknown;
335}
336
337void AccessibilityNodeObject::addChildren()
338{
339 // If the need to add more children in addition to existing children arises,
340 // childrenChanged should have been called, leaving the object with no children.
341 ASSERT(!m_haveChildren);
342
343 if (!m_node)
344 return;
345
346 m_haveChildren = true;
347
348 // The only time we add children from the DOM tree to a node with a renderer is when it's a canvas.
349 if (renderer() && !m_node->hasTagName(canvasTag))
350 return;
351
352 auto objectCache = axObjectCache();
353 if (!objectCache)
354 return;
355
356 for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
357 addChild(objectCache->getOrCreate(child));
358
359 m_subtreeDirty = false;
360}
361
362bool AccessibilityNodeObject::canHaveChildren() const
363{
364 // If this is an AccessibilityRenderObject, then it's okay if this object
365 // doesn't have a node - there are some renderers that don't have associated
366 // nodes, like scroll areas and css-generated text.
367 if (!node() && !isAccessibilityRenderObject())
368 return false;
369
370 // When <noscript> is not being used (its renderer() == 0), ignore its children.
371 if (node() && !renderer() && node()->hasTagName(noscriptTag))
372 return false;
373
374 // Elements that should not have children
375 switch (roleValue()) {
376 case AccessibilityRole::Image:
377 case AccessibilityRole::Button:
378 case AccessibilityRole::PopUpButton:
379 case AccessibilityRole::CheckBox:
380 case AccessibilityRole::RadioButton:
381 case AccessibilityRole::Tab:
382 case AccessibilityRole::ToggleButton:
383 case AccessibilityRole::StaticText:
384 case AccessibilityRole::ListBoxOption:
385 case AccessibilityRole::ScrollBar:
386 case AccessibilityRole::ProgressIndicator:
387 case AccessibilityRole::Switch:
388 case AccessibilityRole::MenuItemCheckbox:
389 case AccessibilityRole::MenuItemRadio:
390 case AccessibilityRole::Splitter:
391 case AccessibilityRole::Meter:
392 return false;
393 case AccessibilityRole::DocumentMath:
394#if ENABLE(MATHML)
395 return node()->isMathMLElement();
396#endif
397 return false;
398 default:
399 return true;
400 }
401}
402
403bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const
404{
405#ifndef NDEBUG
406 // Double-check that an AccessibilityObject is never accessed before
407 // it's been initialized.
408 ASSERT(m_initialized);
409#endif
410
411 // Handle non-rendered text that is exposed through aria-hidden=false.
412 if (m_node && m_node->isTextNode() && !renderer()) {
413 // Fallback content in iframe nodes should be ignored.
414 if (m_node->parentNode() && m_node->parentNode()->hasTagName(iframeTag) && m_node->parentNode()->renderer())
415 return true;
416
417 // Whitespace only text elements should be ignored when they have no renderer.
418 String string = stringValue().stripWhiteSpace().simplifyWhiteSpace();
419 if (!string.length())
420 return true;
421 }
422
423 AccessibilityObjectInclusion decision = defaultObjectInclusion();
424 if (decision == AccessibilityObjectInclusion::IncludeObject)
425 return false;
426 if (decision == AccessibilityObjectInclusion::IgnoreObject)
427 return true;
428 // If this element is within a parent that cannot have children, it should not be exposed.
429 if (isDescendantOfBarrenParent())
430 return true;
431
432 if (roleValue() == AccessibilityRole::Ignored)
433 return true;
434
435 return m_role == AccessibilityRole::Unknown;
436}
437
438bool AccessibilityNodeObject::canvasHasFallbackContent() const
439{
440 Node* node = this->node();
441 if (!is<HTMLCanvasElement>(node))
442 return false;
443 HTMLCanvasElement& canvasElement = downcast<HTMLCanvasElement>(*node);
444 // If it has any children that are elements, we'll assume it might be fallback
445 // content. If it has no children or its only children are not elements
446 // (e.g. just text nodes), it doesn't have fallback content.
447 return childrenOfType<Element>(canvasElement).first();
448}
449
450bool AccessibilityNodeObject::isImageButton() const
451{
452 return isNativeImage() && isButton();
453}
454
455bool AccessibilityNodeObject::isNativeTextControl() const
456{
457 Node* node = this->node();
458 if (!node)
459 return false;
460
461 if (is<HTMLTextAreaElement>(*node))
462 return true;
463
464 if (is<HTMLInputElement>(*node)) {
465 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
466 return input.isText() || input.isNumberField();
467 }
468
469 return false;
470}
471
472bool AccessibilityNodeObject::isSearchField() const
473{
474 Node* node = this->node();
475 if (!node)
476 return false;
477
478 if (roleValue() == AccessibilityRole::SearchField)
479 return true;
480
481 if (!is<HTMLInputElement>(*node))
482 return false;
483
484 auto& inputElement = downcast<HTMLInputElement>(*node);
485
486 // Some websites don't label their search fields as such. However, they will
487 // use the word "search" in either the form or input type. This won't catch every case,
488 // but it will catch google.com for example.
489
490 // Check the node name of the input type, sometimes it's "search".
491 const AtomicString& nameAttribute = getAttribute(nameAttr);
492 if (nameAttribute.containsIgnoringASCIICase("search"))
493 return true;
494
495 // Check the form action and the name, which will sometimes be "search".
496 auto* form = inputElement.form();
497 if (form && (form->name().containsIgnoringASCIICase("search") || form->action().containsIgnoringASCIICase("search")))
498 return true;
499
500 return false;
501}
502
503bool AccessibilityNodeObject::isNativeImage() const
504{
505 Node* node = this->node();
506 if (!node)
507 return false;
508
509 if (is<HTMLImageElement>(*node))
510 return true;
511
512 if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag))
513 return true;
514
515 if (is<HTMLInputElement>(*node)) {
516 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
517 return input.isImageButton();
518 }
519
520 return false;
521}
522
523bool AccessibilityNodeObject::isImage() const
524{
525 return roleValue() == AccessibilityRole::Image;
526}
527
528bool AccessibilityNodeObject::isPasswordField() const
529{
530 auto* node = this->node();
531 if (!is<HTMLInputElement>(node))
532 return false;
533
534 if (ariaRoleAttribute() != AccessibilityRole::Unknown)
535 return false;
536
537 return downcast<HTMLInputElement>(*node).isPasswordField();
538}
539
540AccessibilityObject* AccessibilityNodeObject::passwordFieldOrContainingPasswordField()
541{
542 Node* node = this->node();
543 if (!node)
544 return nullptr;
545
546 if (is<HTMLInputElement>(*node) && downcast<HTMLInputElement>(*node).isPasswordField())
547 return this;
548
549 auto* element = node->shadowHost();
550 if (!is<HTMLInputElement>(element))
551 return nullptr;
552
553 if (auto* cache = axObjectCache())
554 return cache->getOrCreate(element);
555
556 return nullptr;
557}
558
559bool AccessibilityNodeObject::isInputImage() const
560{
561 Node* node = this->node();
562 if (is<HTMLInputElement>(node) && roleValue() == AccessibilityRole::Button) {
563 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
564 return input.isImageButton();
565 }
566
567 return false;
568}
569
570bool AccessibilityNodeObject::isProgressIndicator() const
571{
572 return roleValue() == AccessibilityRole::ProgressIndicator || roleValue() == AccessibilityRole::Meter;
573}
574
575bool AccessibilityNodeObject::isSlider() const
576{
577 return roleValue() == AccessibilityRole::Slider;
578}
579
580bool AccessibilityNodeObject::isMenuRelated() const
581{
582 switch (roleValue()) {
583 case AccessibilityRole::Menu:
584 case AccessibilityRole::MenuBar:
585 case AccessibilityRole::MenuButton:
586 case AccessibilityRole::MenuItem:
587 case AccessibilityRole::MenuItemCheckbox:
588 case AccessibilityRole::MenuItemRadio:
589 return true;
590 default:
591 return false;
592 }
593}
594
595bool AccessibilityNodeObject::isMenu() const
596{
597 return roleValue() == AccessibilityRole::Menu;
598}
599
600bool AccessibilityNodeObject::isMenuBar() const
601{
602 return roleValue() == AccessibilityRole::MenuBar;
603}
604
605bool AccessibilityNodeObject::isMenuButton() const
606{
607 return roleValue() == AccessibilityRole::MenuButton;
608}
609
610bool AccessibilityNodeObject::isMenuItem() const
611{
612 switch (roleValue()) {
613 case AccessibilityRole::MenuItem:
614 case AccessibilityRole::MenuItemRadio:
615 case AccessibilityRole::MenuItemCheckbox:
616 return true;
617 default:
618 return false;
619 }
620}
621
622bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const
623{
624 Node* node = this->node();
625 if (!is<HTMLInputElement>(node))
626 return false;
627
628 auto& input = downcast<HTMLInputElement>(*node);
629 return input.isCheckbox() || input.isRadioButton();
630}
631
632bool AccessibilityNodeObject::isEnabled() const
633{
634 // ARIA says that the disabled status applies to the current element and all descendant elements.
635 for (AccessibilityObject* object = const_cast<AccessibilityNodeObject*>(this); object; object = object->parentObject()) {
636 const AtomicString& disabledStatus = object->getAttribute(aria_disabledAttr);
637 if (equalLettersIgnoringASCIICase(disabledStatus, "true"))
638 return false;
639 if (equalLettersIgnoringASCIICase(disabledStatus, "false"))
640 break;
641 }
642
643 if (roleValue() == AccessibilityRole::HorizontalRule)
644 return false;
645
646 Node* node = this->node();
647 if (!is<Element>(node))
648 return true;
649
650 return !downcast<Element>(*node).isDisabledFormControl();
651}
652
653bool AccessibilityNodeObject::isIndeterminate() const
654{
655 return equalLettersIgnoringASCIICase(getAttribute(indeterminateAttr), "true");
656}
657
658bool AccessibilityNodeObject::isPressed() const
659{
660 if (!isButton())
661 return false;
662
663 Node* node = this->node();
664 if (!node)
665 return false;
666
667 // If this is an toggle button, check the aria-pressed attribute rather than node()->active()
668 if (isToggleButton())
669 return equalLettersIgnoringASCIICase(getAttribute(aria_pressedAttr), "true");
670
671 if (!is<Element>(*node))
672 return false;
673 return downcast<Element>(*node).active();
674}
675
676bool AccessibilityNodeObject::isChecked() const
677{
678 Node* node = this->node();
679 if (!node)
680 return false;
681
682 // First test for native checkedness semantics
683 if (is<HTMLInputElement>(*node))
684 return downcast<HTMLInputElement>(*node).shouldAppearChecked();
685
686 // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute
687 bool validRole = false;
688 switch (ariaRoleAttribute()) {
689 case AccessibilityRole::RadioButton:
690 case AccessibilityRole::CheckBox:
691 case AccessibilityRole::MenuItem:
692 case AccessibilityRole::MenuItemCheckbox:
693 case AccessibilityRole::MenuItemRadio:
694 case AccessibilityRole::Switch:
695 validRole = true;
696 break;
697 default:
698 break;
699 }
700
701 if (validRole && equalLettersIgnoringASCIICase(getAttribute(aria_checkedAttr), "true"))
702 return true;
703
704 return false;
705}
706
707bool AccessibilityNodeObject::isHovered() const
708{
709 Node* node = this->node();
710 return is<Element>(node) && downcast<Element>(*node).hovered();
711}
712
713bool AccessibilityNodeObject::isMultiSelectable() const
714{
715 const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
716 if (equalLettersIgnoringASCIICase(ariaMultiSelectable, "true"))
717 return true;
718 if (equalLettersIgnoringASCIICase(ariaMultiSelectable, "false"))
719 return false;
720
721 return node() && node()->hasTagName(selectTag) && downcast<HTMLSelectElement>(*node()).multiple();
722}
723
724bool AccessibilityNodeObject::isRequired() const
725{
726 // Explicit aria-required values should trump native required attributes.
727 const AtomicString& requiredValue = getAttribute(aria_requiredAttr);
728 if (equalLettersIgnoringASCIICase(requiredValue, "true"))
729 return true;
730 if (equalLettersIgnoringASCIICase(requiredValue, "false"))
731 return false;
732
733 Node* n = this->node();
734 if (is<HTMLFormControlElement>(n))
735 return downcast<HTMLFormControlElement>(*n).isRequired();
736
737 return false;
738}
739
740bool AccessibilityNodeObject::supportsRequiredAttribute() const
741{
742 switch (roleValue()) {
743 case AccessibilityRole::Button:
744 return isFileUploadButton();
745 case AccessibilityRole::Cell:
746 case AccessibilityRole::ColumnHeader:
747 case AccessibilityRole::CheckBox:
748 case AccessibilityRole::ComboBox:
749 case AccessibilityRole::Grid:
750 case AccessibilityRole::GridCell:
751 case AccessibilityRole::Incrementor:
752 case AccessibilityRole::ListBox:
753 case AccessibilityRole::PopUpButton:
754 case AccessibilityRole::RadioButton:
755 case AccessibilityRole::RadioGroup:
756 case AccessibilityRole::RowHeader:
757 case AccessibilityRole::Slider:
758 case AccessibilityRole::SpinButton:
759 case AccessibilityRole::TableHeaderContainer:
760 case AccessibilityRole::TextArea:
761 case AccessibilityRole::TextField:
762 case AccessibilityRole::ToggleButton:
763 return true;
764 default:
765 return false;
766 }
767}
768
769int AccessibilityNodeObject::headingLevel() const
770{
771 // headings can be in block flow and non-block flow
772 Node* node = this->node();
773 if (!node)
774 return false;
775
776 if (isHeading()) {
777 int ariaLevel = getAttribute(aria_levelAttr).toInt();
778 if (ariaLevel > 0)
779 return ariaLevel;
780 }
781
782 if (node->hasTagName(h1Tag))
783 return 1;
784
785 if (node->hasTagName(h2Tag))
786 return 2;
787
788 if (node->hasTagName(h3Tag))
789 return 3;
790
791 if (node->hasTagName(h4Tag))
792 return 4;
793
794 if (node->hasTagName(h5Tag))
795 return 5;
796
797 if (node->hasTagName(h6Tag))
798 return 6;
799
800 // The implicit value of aria-level is 2 for the heading role.
801 // https://www.w3.org/TR/wai-aria-1.1/#heading
802 if (ariaRoleAttribute() == AccessibilityRole::Heading)
803 return 2;
804
805 return 0;
806}
807
808String AccessibilityNodeObject::valueDescription() const
809{
810 if (!isRangeControl())
811 return String();
812
813 return getAttribute(aria_valuetextAttr).string();
814}
815
816float AccessibilityNodeObject::valueForRange() const
817{
818 if (is<HTMLInputElement>(node())) {
819 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
820 if (input.isRangeControl())
821 return input.valueAsNumber();
822 }
823
824 if (!isRangeControl())
825 return 0.0f;
826
827 // In ARIA 1.1, the implicit value for aria-valuenow on a spin button is 0.
828 // For other roles, it is half way between aria-valuemin and aria-valuemax.
829 auto& value = getAttribute(aria_valuenowAttr);
830 if (!value.isEmpty())
831 return value.toFloat();
832
833 return isSpinButton() ? 0 : (minValueForRange() + maxValueForRange()) / 2;
834}
835
836float AccessibilityNodeObject::maxValueForRange() const
837{
838 if (is<HTMLInputElement>(node())) {
839 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
840 if (input.isRangeControl())
841 return input.maximum();
842 }
843
844 if (!isRangeControl())
845 return 0.0f;
846
847 auto& value = getAttribute(aria_valuemaxAttr);
848 if (!value.isEmpty())
849 return value.toFloat();
850
851 // In ARIA 1.1, the implicit value for aria-valuemax on a spin button
852 // is that there is no maximum value. For other roles, it is 100.
853 return isSpinButton() ? std::numeric_limits<float>::max() : 100.0f;
854}
855
856float AccessibilityNodeObject::minValueForRange() const
857{
858 if (is<HTMLInputElement>(node())) {
859 HTMLInputElement& input = downcast<HTMLInputElement>(*node());
860 if (input.isRangeControl())
861 return input.minimum();
862 }
863
864 if (!isRangeControl())
865 return 0.0f;
866
867 auto& value = getAttribute(aria_valueminAttr);
868 if (!value.isEmpty())
869 return value.toFloat();
870
871 // In ARIA 1.1, the implicit value for aria-valuemin on a spin button
872 // is that there is no minimum value. For other roles, it is 0.
873 return isSpinButton() ? -std::numeric_limits<float>::max() : 0.0f;
874}
875
876float AccessibilityNodeObject::stepValueForRange() const
877{
878 return getAttribute(stepAttr).toFloat();
879}
880
881bool AccessibilityNodeObject::isHeading() const
882{
883 return roleValue() == AccessibilityRole::Heading;
884}
885
886bool AccessibilityNodeObject::isLink() const
887{
888 return roleValue() == AccessibilityRole::WebCoreLink;
889}
890
891bool AccessibilityNodeObject::isControl() const
892{
893 Node* node = this->node();
894 if (!node)
895 return false;
896
897 return is<HTMLFormControlElement>(*node) || AccessibilityObject::isARIAControl(ariaRoleAttribute()) || roleValue() == AccessibilityRole::Button;
898}
899
900bool AccessibilityNodeObject::isFieldset() const
901{
902 Node* node = this->node();
903 if (!node)
904 return false;
905
906 return node->hasTagName(fieldsetTag);
907}
908
909bool AccessibilityNodeObject::isGroup() const
910{
911 AccessibilityRole role = roleValue();
912 return role == AccessibilityRole::Group || role == AccessibilityRole::TextGroup || role == AccessibilityRole::ApplicationGroup || role == AccessibilityRole::ApplicationTextGroup;
913}
914
915AccessibilityObject* AccessibilityNodeObject::selectedRadioButton()
916{
917 if (!isRadioGroup())
918 return nullptr;
919
920 // Find the child radio button that is selected (ie. the intValue == 1).
921 for (const auto& child : children()) {
922 if (child->roleValue() == AccessibilityRole::RadioButton && child->checkboxOrRadioValue() == AccessibilityButtonState::On)
923 return child.get();
924 }
925 return nullptr;
926}
927
928AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
929{
930 if (!isTabList())
931 return nullptr;
932
933 // FIXME: Is this valid? ARIA tab items support aria-selected; not aria-checked.
934 // Find the child tab item that is selected (ie. the intValue == 1).
935 AccessibilityObject::AccessibilityChildrenVector tabs;
936 tabChildren(tabs);
937
938 for (const auto& child : children()) {
939 if (child->isTabItem() && (child->isChecked() || child->isSelected()))
940 return child.get();
941 }
942 return nullptr;
943}
944
945AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const
946{
947 if (isNativeCheckboxOrRadio())
948 return isIndeterminate() ? AccessibilityButtonState::Mixed : isChecked() ? AccessibilityButtonState::On : AccessibilityButtonState::Off;
949
950 return AccessibilityObject::checkboxOrRadioValue();
951}
952
953Element* AccessibilityNodeObject::anchorElement() const
954{
955 Node* node = this->node();
956 if (!node)
957 return nullptr;
958
959 AXObjectCache* cache = axObjectCache();
960 if (!cache)
961 return nullptr;
962
963 // search up the DOM tree for an anchor element
964 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
965 for ( ; node; node = node->parentNode()) {
966 if (is<HTMLAnchorElement>(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isLink()))
967 return downcast<Element>(node);
968 }
969
970 return nullptr;
971}
972
973static bool isNodeActionElement(Node* node)
974{
975 if (is<HTMLInputElement>(*node)) {
976 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
977 if (!input.isDisabledFormControl() && (input.isRadioButton() || input.isCheckbox() || input.isTextButton() || input.isFileUpload() || input.isImageButton()))
978 return true;
979 } else if (node->hasTagName(buttonTag) || node->hasTagName(selectTag))
980 return true;
981
982 return false;
983}
984
985static Element* nativeActionElement(Node* start)
986{
987 if (!start)
988 return nullptr;
989
990 // Do a deep-dive to see if any nodes should be used as the action element.
991 // We have to look at Nodes, since this method should only be called on objects that do not have children (like buttons).
992 // It solves the problem when authors put role="button" on a group and leave the actual button inside the group.
993
994 for (Node* child = start->firstChild(); child; child = child->nextSibling()) {
995 if (isNodeActionElement(child))
996 return downcast<Element>(child);
997
998 if (Element* subChild = nativeActionElement(child))
999 return subChild;
1000 }
1001 return nullptr;
1002}
1003
1004Element* AccessibilityNodeObject::actionElement() const
1005{
1006 Node* node = this->node();
1007 if (!node)
1008 return nullptr;
1009
1010 if (isNodeActionElement(node))
1011 return downcast<Element>(node);
1012
1013 if (AccessibilityObject::isARIAInput(ariaRoleAttribute()))
1014 return downcast<Element>(node);
1015
1016 switch (roleValue()) {
1017 case AccessibilityRole::Button:
1018 case AccessibilityRole::PopUpButton:
1019 case AccessibilityRole::ToggleButton:
1020 case AccessibilityRole::Tab:
1021 case AccessibilityRole::MenuItem:
1022 case AccessibilityRole::MenuItemCheckbox:
1023 case AccessibilityRole::MenuItemRadio:
1024 case AccessibilityRole::ListItem:
1025 // Check if the author is hiding the real control element inside the ARIA element.
1026 if (Element* nativeElement = nativeActionElement(node))
1027 return nativeElement;
1028 return downcast<Element>(node);
1029 default:
1030 break;
1031 }
1032
1033 Element* elt = anchorElement();
1034 if (!elt)
1035 elt = mouseButtonListener();
1036 return elt;
1037}
1038
1039Element* AccessibilityNodeObject::mouseButtonListener(MouseButtonListenerResultFilter filter) const
1040{
1041 Node* node = this->node();
1042 if (!node)
1043 return nullptr;
1044
1045 // check if our parent is a mouse button listener
1046 // FIXME: Do the continuation search like anchorElement does
1047 for (auto& element : elementLineage(is<Element>(*node) ? downcast<Element>(node) : node->parentElement())) {
1048 // 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.
1049 // It can cause false positives, where every piece of text is labeled as accepting press actions.
1050 if (element.hasTagName(bodyTag) && isStaticText() && filter == ExcludeBodyElement)
1051 break;
1052
1053 if (element.hasEventListeners(eventNames().clickEvent) || element.hasEventListeners(eventNames().mousedownEvent) || element.hasEventListeners(eventNames().mouseupEvent))
1054 return &element;
1055 }
1056
1057 return nullptr;
1058}
1059
1060bool AccessibilityNodeObject::isDescendantOfBarrenParent() const
1061{
1062 if (!m_isIgnoredFromParentData.isNull())
1063 return m_isIgnoredFromParentData.isDescendantOfBarrenParent;
1064
1065 for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) {
1066 if (!object->canHaveChildren())
1067 return true;
1068 }
1069
1070 return false;
1071}
1072
1073void AccessibilityNodeObject::alterSliderValue(bool increase)
1074{
1075 if (roleValue() != AccessibilityRole::Slider)
1076 return;
1077
1078 auto element = this->element();
1079 if (!element || element->isDisabledFormControl())
1080 return;
1081
1082 if (!getAttribute(stepAttr).isEmpty())
1083 changeValueByStep(increase);
1084 else
1085 changeValueByPercent(increase ? 5 : -5);
1086}
1087
1088void AccessibilityNodeObject::increment()
1089{
1090 UserGestureIndicator gestureIndicator(ProcessingUserGesture, document());
1091 alterSliderValue(true);
1092}
1093
1094void AccessibilityNodeObject::decrement()
1095{
1096 UserGestureIndicator gestureIndicator(ProcessingUserGesture, document());
1097 alterSliderValue(false);
1098}
1099
1100void AccessibilityNodeObject::changeValueByStep(bool increase)
1101{
1102 float step = stepValueForRange();
1103 float value = valueForRange();
1104
1105 value += increase ? step : -step;
1106
1107 setValue(String::numberToStringFixedPrecision(value));
1108
1109 auto objectCache = axObjectCache();
1110 if (objectCache)
1111 objectCache->postNotification(node(), AXObjectCache::AXValueChanged);
1112}
1113
1114void AccessibilityNodeObject::changeValueByPercent(float percentChange)
1115{
1116 float range = maxValueForRange() - minValueForRange();
1117 float step = range * (percentChange / 100);
1118 float value = valueForRange();
1119
1120 // Make sure the specified percent will cause a change of one integer step or larger.
1121 if (std::abs(step) < 1)
1122 step = std::abs(percentChange) * (1 / percentChange);
1123
1124 value += step;
1125 setValue(String::numberToStringFixedPrecision(value));
1126
1127 auto objectCache = axObjectCache();
1128 if (objectCache)
1129 objectCache->postNotification(node(), AXObjectCache::AXValueChanged);
1130}
1131
1132bool AccessibilityNodeObject::isGenericFocusableElement() const
1133{
1134 if (!canSetFocusAttribute())
1135 return false;
1136
1137 // If it's a control, it's not generic.
1138 if (isControl())
1139 return false;
1140
1141 AccessibilityRole role = roleValue();
1142 if (role == AccessibilityRole::Video || role == AccessibilityRole::Audio)
1143 return false;
1144
1145 // If it has an aria role, it's not generic.
1146 if (m_ariaRole != AccessibilityRole::Unknown)
1147 return false;
1148
1149 // If the content editable attribute is set on this element, that's the reason
1150 // it's focusable, and existing logic should handle this case already - so it's not a
1151 // generic focusable element.
1152
1153 if (hasContentEditableAttributeSet())
1154 return false;
1155
1156 // The web area and body element are both focusable, but existing logic handles these
1157 // cases already, so we don't need to include them here.
1158 if (role == AccessibilityRole::WebArea)
1159 return false;
1160 if (node() && node()->hasTagName(bodyTag))
1161 return false;
1162
1163 // An SVG root is focusable by default, but it's probably not interactive, so don't
1164 // include it. It can still be made accessible by giving it an ARIA role.
1165 if (role == AccessibilityRole::SVGRoot)
1166 return false;
1167
1168 return true;
1169}
1170
1171HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const
1172{
1173 if (!is<HTMLElement>(*element) || !downcast<HTMLElement>(*element).isLabelable())
1174 return nullptr;
1175
1176 const AtomicString& id = element->getIdAttribute();
1177 if (!id.isEmpty()) {
1178 if (HTMLLabelElement* label = element->treeScope().labelElementForId(id))
1179 return label;
1180 }
1181
1182 return ancestorsOfType<HTMLLabelElement>(*element).first();
1183}
1184
1185String AccessibilityNodeObject::ariaAccessibilityDescription() const
1186{
1187 String ariaLabeledBy = ariaLabeledByAttribute();
1188 if (!ariaLabeledBy.isEmpty())
1189 return ariaLabeledBy;
1190
1191 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1192 if (!ariaLabel.isEmpty())
1193 return ariaLabel;
1194
1195 return String();
1196}
1197
1198static Element* siblingWithAriaRole(Node* node, const char* role)
1199{
1200 // FIXME: Either we should add a null check here or change the function to take a reference instead of a pointer.
1201 ContainerNode* parent = node->parentNode();
1202 if (!parent)
1203 return nullptr;
1204
1205 for (auto& sibling : childrenOfType<Element>(*parent)) {
1206 // FIXME: Should skip sibling that is the same as the node.
1207 if (equalIgnoringASCIICase(sibling.attributeWithoutSynchronization(roleAttr), role))
1208 return &sibling;
1209 }
1210
1211 return nullptr;
1212}
1213
1214Element* AccessibilityNodeObject::menuElementForMenuButton() const
1215{
1216 if (ariaRoleAttribute() != AccessibilityRole::MenuButton)
1217 return nullptr;
1218
1219 return siblingWithAriaRole(node(), "menu");
1220}
1221
1222AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const
1223{
1224 if (AXObjectCache* cache = axObjectCache())
1225 return cache->getOrCreate(menuElementForMenuButton());
1226 return nullptr;
1227}
1228
1229Element* AccessibilityNodeObject::menuItemElementForMenu() const
1230{
1231 if (ariaRoleAttribute() != AccessibilityRole::Menu)
1232 return nullptr;
1233
1234 return siblingWithAriaRole(node(), "menuitem");
1235}
1236
1237AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
1238{
1239 AXObjectCache* cache = axObjectCache();
1240 if (!cache)
1241 return nullptr;
1242
1243 Element* menuItem = menuItemElementForMenu();
1244
1245 if (menuItem) {
1246 // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem
1247 AccessibilityObject* menuItemAX = cache->getOrCreate(menuItem);
1248 if (menuItemAX && menuItemAX->isMenuButton())
1249 return menuItemAX;
1250 }
1251 return nullptr;
1252}
1253
1254AccessibilityObject* AccessibilityNodeObject::captionForFigure() const
1255{
1256 if (!isFigureElement())
1257 return nullptr;
1258
1259 AXObjectCache* cache = axObjectCache();
1260 if (!cache)
1261 return nullptr;
1262
1263 Node* node = this->node();
1264 for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
1265 if (child->hasTagName(figcaptionTag))
1266 return cache->getOrCreate(child);
1267 }
1268 return nullptr;
1269}
1270
1271bool AccessibilityNodeObject::usesAltTagForTextComputation() const
1272{
1273 return isImage() || isInputImage() || isNativeImage() || isCanvas() || (node() && node()->hasTagName(imgTag));
1274}
1275
1276bool AccessibilityNodeObject::isLabelable() const
1277{
1278 Node* node = this->node();
1279 if (!node)
1280 return false;
1281
1282 return is<HTMLInputElement>(*node) || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl() || isProgressIndicator() || isMeter();
1283}
1284
1285String AccessibilityNodeObject::textForLabelElement(Element* element) const
1286{
1287 String result = String();
1288 if (!is<HTMLLabelElement>(*element))
1289 return result;
1290
1291 auto objectCache = axObjectCache();
1292 if (!objectCache)
1293 return result;
1294
1295 HTMLLabelElement* label = downcast<HTMLLabelElement>(element);
1296 // Check to see if there's aria-labelledby attribute on the label element.
1297 if (AccessibilityObject* labelObject = objectCache->getOrCreate(label))
1298 result = labelObject->ariaLabeledByAttribute();
1299
1300 return !result.isEmpty() ? result : accessibleNameForNode(label);
1301}
1302
1303void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder) const
1304{
1305 Node* node = this->node();
1306 if (!node)
1307 return;
1308
1309 if (isLabelable()) {
1310 if (HTMLLabelElement* label = labelForElement(downcast<Element>(node))) {
1311 String innerText = textForLabelElement(label);
1312
1313 auto objectCache = axObjectCache();
1314 // Only use the <label> text if there's no ARIA override.
1315 if (objectCache && !innerText.isEmpty() && !ariaAccessibilityDescription())
1316 textOrder.append(AccessibilityText(innerText, isMeter() ? AccessibilityTextSource::Alternative : AccessibilityTextSource::LabelByElement, objectCache->getOrCreate(label)));
1317 return;
1318 }
1319 }
1320
1321 AccessibilityObject* titleUIElement = this->titleUIElement();
1322 if (titleUIElement)
1323 textOrder.append(AccessibilityText(String(), AccessibilityTextSource::LabelByElement, titleUIElement));
1324}
1325
1326void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
1327{
1328 if (isWebArea()) {
1329 String webAreaText = alternativeTextForWebArea();
1330 if (!webAreaText.isEmpty())
1331 textOrder.append(AccessibilityText(webAreaText, AccessibilityTextSource::Alternative));
1332 return;
1333 }
1334
1335 ariaLabeledByText(textOrder);
1336
1337 const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
1338 if (!ariaLabel.isEmpty())
1339 textOrder.append(AccessibilityText(ariaLabel, AccessibilityTextSource::Alternative));
1340
1341 if (usesAltTagForTextComputation()) {
1342 if (is<RenderImage>(renderer())) {
1343 String renderAltText = downcast<RenderImage>(*renderer()).altText();
1344
1345 // RenderImage will return title as a fallback from altText, but we don't want title here because we consider that in helpText.
1346 if (!renderAltText.isEmpty() && renderAltText != getAttribute(titleAttr)) {
1347 textOrder.append(AccessibilityText(renderAltText, AccessibilityTextSource::Alternative));
1348 return;
1349 }
1350 }
1351 // Images should use alt as long as the attribute is present, even if empty.
1352 // Otherwise, it should fallback to other methods, like the title attribute.
1353 const AtomicString& alt = getAttribute(altAttr);
1354 if (!alt.isEmpty())
1355 textOrder.append(AccessibilityText(alt, AccessibilityTextSource::Alternative));
1356 }
1357
1358 Node* node = this->node();
1359 if (!node)
1360 return;
1361
1362 auto objectCache = axObjectCache();
1363 // The fieldset element derives its alternative text from the first associated legend element if one is available.
1364 if (objectCache && is<HTMLFieldSetElement>(*node)) {
1365 AccessibilityObject* object = objectCache->getOrCreate(downcast<HTMLFieldSetElement>(*node).legend());
1366 if (object && !object->isHidden())
1367 textOrder.append(AccessibilityText(accessibleNameForNode(object->node()), AccessibilityTextSource::Alternative));
1368 }
1369
1370 // The figure element derives its alternative text from the first associated figcaption element if one is available.
1371 if (isFigureElement()) {
1372 AccessibilityObject* captionForFigure = this->captionForFigure();
1373 if (captionForFigure && !captionForFigure->isHidden())
1374 textOrder.append(AccessibilityText(accessibleNameForNode(captionForFigure->node()), AccessibilityTextSource::Alternative));
1375 }
1376
1377 // Tree items missing a label are labeled by all child elements.
1378 if (isTreeItem() && ariaLabel.isEmpty() && ariaLabeledByAttribute().isEmpty())
1379 textOrder.append(AccessibilityText(accessibleNameForNode(node), AccessibilityTextSource::Alternative));
1380
1381#if ENABLE(MATHML)
1382 if (node->isMathMLElement())
1383 textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AccessibilityTextSource::Alternative));
1384#endif
1385}
1386
1387void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) const
1388{
1389 Node* node = this->node();
1390 if (!node)
1391 return;
1392
1393 bool isInputTag = is<HTMLInputElement>(*node);
1394 if (isInputTag) {
1395 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
1396 if (input.isTextButton()) {
1397 textOrder.append(AccessibilityText(input.valueWithDefault(), AccessibilityTextSource::Visible));
1398 return;
1399 }
1400 }
1401
1402 // If this node isn't rendered, there's no inner text we can extract from a select element.
1403 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1404 return;
1405
1406 bool useTextUnderElement = false;
1407
1408 switch (roleValue()) {
1409 case AccessibilityRole::PopUpButton:
1410 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1411 if (node->hasTagName(selectTag))
1412 break;
1413 FALLTHROUGH;
1414 case AccessibilityRole::Button:
1415 case AccessibilityRole::ToggleButton:
1416 case AccessibilityRole::CheckBox:
1417 case AccessibilityRole::ListBoxOption:
1418 // MacOS does not expect native <li> elements to expose label information, it only expects leaf node elements to do that.
1419#if !PLATFORM(COCOA)
1420 case AccessibilityRole::ListItem:
1421#endif
1422 case AccessibilityRole::MenuButton:
1423 case AccessibilityRole::MenuItem:
1424 case AccessibilityRole::MenuItemCheckbox:
1425 case AccessibilityRole::MenuItemRadio:
1426 case AccessibilityRole::RadioButton:
1427 case AccessibilityRole::Switch:
1428 case AccessibilityRole::Tab:
1429 useTextUnderElement = true;
1430 break;
1431 default:
1432 break;
1433 }
1434
1435 // If it's focusable but it's not content editable or a known control type, then it will appear to
1436 // the user as a single atomic object, so we should use its text as the default title.
1437 if (isHeading() || isLink())
1438 useTextUnderElement = true;
1439
1440 if (isOutput())
1441 useTextUnderElement = true;
1442
1443 if (useTextUnderElement) {
1444 AccessibilityTextUnderElementMode mode;
1445
1446 // Headings often include links as direct children. Those links need to be included in text under element.
1447 if (isHeading())
1448 mode.includeFocusableContent = true;
1449
1450 String text = textUnderElement(mode);
1451 if (!text.isEmpty())
1452 textOrder.append(AccessibilityText(text, AccessibilityTextSource::Children));
1453 }
1454}
1455
1456void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) const
1457{
1458 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1459 if (!ariaHelp.isEmpty())
1460 textOrder.append(AccessibilityText(ariaHelp, AccessibilityTextSource::Help));
1461
1462 String describedBy = ariaDescribedByAttribute();
1463 if (!describedBy.isEmpty())
1464 textOrder.append(AccessibilityText(describedBy, AccessibilityTextSource::Summary));
1465 else if (isControl()) {
1466 // For controls, use their fieldset parent's described-by text if available.
1467 auto matchFunc = [] (const AccessibilityObject& object) {
1468 return object.isFieldset() && !object.ariaDescribedByAttribute().isEmpty();
1469 };
1470 if (const auto* parent = AccessibilityObject::matchedParent(*this, false, WTFMove(matchFunc)))
1471 textOrder.append(AccessibilityText(parent->ariaDescribedByAttribute(), AccessibilityTextSource::Summary));
1472 }
1473
1474 // Summary attribute used as help text on tables.
1475 const AtomicString& summary = getAttribute(summaryAttr);
1476 if (!summary.isEmpty())
1477 textOrder.append(AccessibilityText(summary, AccessibilityTextSource::Summary));
1478
1479 // The title attribute should be used as help text unless it is already being used as descriptive text.
1480 // However, when the title attribute is the only text alternative provided, it may be exposed as the
1481 // descriptive text. This is problematic in the case of meters because the HTML spec suggests authors
1482 // can expose units through this attribute. Therefore, if the element is a meter, change its source
1483 // type to AccessibilityTextSource::Help.
1484 const AtomicString& title = getAttribute(titleAttr);
1485 if (!title.isEmpty()) {
1486 if (!isMeter() && !roleIgnoresTitle())
1487 textOrder.append(AccessibilityText(title, AccessibilityTextSource::TitleTag));
1488 else
1489 textOrder.append(AccessibilityText(title, AccessibilityTextSource::Help));
1490 }
1491}
1492
1493void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textOrder) const
1494{
1495 titleElementText(textOrder);
1496 alternativeText(textOrder);
1497 visibleText(textOrder);
1498 helpText(textOrder);
1499
1500 String placeholder = placeholderValue();
1501 if (!placeholder.isEmpty())
1502 textOrder.append(AccessibilityText(placeholder, AccessibilityTextSource::Placeholder));
1503}
1504
1505void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
1506{
1507 String ariaLabeledBy = ariaLabeledByAttribute();
1508 if (!ariaLabeledBy.isEmpty()) {
1509 auto objectCache = axObjectCache();
1510 if (!objectCache)
1511 return;
1512
1513 Vector<Element*> elements;
1514 ariaLabeledByElements(elements);
1515
1516 Vector<RefPtr<AccessibilityObject>> axElements;
1517 for (const auto& element : elements)
1518 axElements.append(objectCache->getOrCreate(element));
1519
1520 textOrder.append(AccessibilityText(ariaLabeledBy, AccessibilityTextSource::Alternative, WTFMove(axElements)));
1521 }
1522}
1523
1524String AccessibilityNodeObject::alternativeTextForWebArea() const
1525{
1526 // The WebArea description should follow this order:
1527 // aria-label on the <html>
1528 // title on the <html>
1529 // <title> inside the <head> (of it was set through JS)
1530 // name on the <html>
1531 // For iframes:
1532 // aria-label on the <iframe>
1533 // title on the <iframe>
1534 // name on the <iframe>
1535
1536 Document* document = this->document();
1537 if (!document)
1538 return String();
1539
1540 // Check if the HTML element has an aria-label for the webpage.
1541 if (Element* documentElement = document->documentElement()) {
1542 const AtomicString& ariaLabel = documentElement->attributeWithoutSynchronization(aria_labelAttr);
1543 if (!ariaLabel.isEmpty())
1544 return ariaLabel;
1545 }
1546
1547 if (auto* owner = document->ownerElement()) {
1548 if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
1549 const AtomicString& title = owner->attributeWithoutSynchronization(titleAttr);
1550 if (!title.isEmpty())
1551 return title;
1552 }
1553 return owner->getNameAttribute();
1554 }
1555
1556 String documentTitle = document->title();
1557 if (!documentTitle.isEmpty())
1558 return documentTitle;
1559
1560 if (auto* body = document->bodyOrFrameset())
1561 return body->getNameAttribute();
1562
1563 return String();
1564}
1565
1566String AccessibilityNodeObject::accessibilityDescription() const
1567{
1568 // Static text should not have a description, it should only have a stringValue.
1569 if (roleValue() == AccessibilityRole::StaticText)
1570 return String();
1571
1572 String ariaDescription = ariaAccessibilityDescription();
1573 if (!ariaDescription.isEmpty())
1574 return ariaDescription;
1575
1576 if (usesAltTagForTextComputation()) {
1577 // Images should use alt as long as the attribute is present, even if empty.
1578 // Otherwise, it should fallback to other methods, like the title attribute.
1579 const AtomicString& alt = getAttribute(altAttr);
1580 if (!alt.isNull())
1581 return alt;
1582 }
1583
1584#if ENABLE(MATHML)
1585 if (is<MathMLElement>(m_node))
1586 return getAttribute(MathMLNames::alttextAttr);
1587#endif
1588
1589 // An element's descriptive text is comprised of title() (what's visible on the screen) and accessibilityDescription() (other descriptive text).
1590 // Both are used to generate what a screen reader speaks.
1591 // If this point is reached (i.e. there's no accessibilityDescription) and there's no title(), we should fallback to using the title attribute.
1592 // 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).
1593 // 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.
1594 if (title().isEmpty() && !roleIgnoresTitle())
1595 return getAttribute(titleAttr);
1596
1597 return String();
1598}
1599
1600// Returns whether the role was not intended to play a semantically meaningful part of the
1601// accessibility hierarchy. This applies to generic groups like <div>'s with no role value set.
1602bool AccessibilityNodeObject::roleIgnoresTitle() const
1603{
1604 if (ariaRoleAttribute() != AccessibilityRole::Unknown)
1605 return false;
1606
1607 switch (roleValue()) {
1608 case AccessibilityRole::Div:
1609 case AccessibilityRole::Unknown:
1610 return true;
1611 default:
1612 return false;
1613 }
1614}
1615
1616String AccessibilityNodeObject::helpText() const
1617{
1618 Node* node = this->node();
1619 if (!node)
1620 return String();
1621
1622 const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
1623 if (!ariaHelp.isEmpty())
1624 return ariaHelp;
1625
1626 String describedBy = ariaDescribedByAttribute();
1627 if (!describedBy.isEmpty())
1628 return describedBy;
1629
1630 String description = accessibilityDescription();
1631 for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
1632 if (is<HTMLElement>(*ancestor)) {
1633 HTMLElement& element = downcast<HTMLElement>(*ancestor);
1634 const AtomicString& summary = element.getAttribute(summaryAttr);
1635 if (!summary.isEmpty())
1636 return summary;
1637
1638 // The title attribute should be used as help text unless it is already being used as descriptive text.
1639 const AtomicString& title = element.getAttribute(titleAttr);
1640 if (!title.isEmpty() && description != title)
1641 return title;
1642 }
1643
1644 auto objectCache = axObjectCache();
1645 if (!objectCache)
1646 return String();
1647
1648 // Only take help text from an ancestor element if its a group or an unknown role. If help was
1649 // added to those kinds of elements, it is likely it was meant for a child element.
1650 if (AccessibilityObject* axObj = objectCache->getOrCreate(ancestor)) {
1651 if (!axObj->isGroup() && axObj->roleValue() != AccessibilityRole::Unknown)
1652 break;
1653 }
1654 }
1655
1656 return String();
1657}
1658
1659unsigned AccessibilityNodeObject::hierarchicalLevel() const
1660{
1661 Node* node = this->node();
1662 if (!is<Element>(node))
1663 return 0;
1664 Element& element = downcast<Element>(*node);
1665 const AtomicString& ariaLevel = element.attributeWithoutSynchronization(aria_levelAttr);
1666 if (!ariaLevel.isEmpty())
1667 return ariaLevel.toInt();
1668
1669 // Only tree item will calculate its level through the DOM currently.
1670 if (roleValue() != AccessibilityRole::TreeItem)
1671 return 0;
1672
1673 // Hierarchy leveling starts at 1, to match the aria-level spec.
1674 // We measure tree hierarchy by the number of groups that the item is within.
1675 unsigned level = 1;
1676 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
1677 AccessibilityRole parentRole = parent->ariaRoleAttribute();
1678 if (parentRole == AccessibilityRole::ApplicationGroup)
1679 level++;
1680 else if (parentRole == AccessibilityRole::Tree)
1681 break;
1682 }
1683
1684 return level;
1685}
1686
1687void AccessibilityNodeObject::setIsExpanded(bool expand)
1688{
1689 if (is<HTMLDetailsElement>(node())) {
1690 auto& details = downcast<HTMLDetailsElement>(*node());
1691 if (expand != details.isOpen())
1692 details.toggleOpen();
1693 }
1694}
1695
1696// When building the textUnderElement for an object, determine whether or not
1697// we should include the inner text of this given descendant object or skip it.
1698static bool shouldUseAccessibilityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode)
1699{
1700 // Do not use any heuristic if we are explicitly asking to include all the children.
1701 if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)
1702 return true;
1703
1704 // Consider this hypothetical example:
1705 // <div tabindex=0>
1706 // <h2>
1707 // Table of contents
1708 // </h2>
1709 // <a href="#start">Jump to start of book</a>
1710 // <ul>
1711 // <li><a href="#1">Chapter 1</a></li>
1712 // <li><a href="#1">Chapter 2</a></li>
1713 // </ul>
1714 // </div>
1715 //
1716 // The goal is to return a reasonable title for the outer container div, because
1717 // it's focusable - but without making its title be the full inner text, which is
1718 // quite long. As a heuristic, skip links, controls, and elements that are usually
1719 // containers with lots of children.
1720
1721 // ARIA states that certain elements are not allowed to expose their children content for name calculation.
1722 if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren
1723 && !obj->accessibleNameDerivesFromContent())
1724 return false;
1725
1726 if (equalLettersIgnoringASCIICase(obj->getAttribute(aria_hiddenAttr), "true"))
1727 return false;
1728
1729 // If something doesn't expose any children, then we can always take the inner text content.
1730 // This is what we want when someone puts an <a> inside a <button> for example.
1731 if (obj->isDescendantOfBarrenParent())
1732 return true;
1733
1734 // Skip focusable children, so we don't include the text of links and controls.
1735 if (obj->canSetFocusAttribute() && !mode.includeFocusableContent)
1736 return false;
1737
1738 // Skip big container elements like lists, tables, etc.
1739 if (is<AccessibilityList>(*obj))
1740 return false;
1741
1742 if (is<AccessibilityTable>(*obj) && downcast<AccessibilityTable>(*obj).isExposableThroughAccessibility())
1743 return false;
1744
1745 if (obj->isTree() || obj->isCanvas())
1746 return false;
1747
1748 return true;
1749}
1750
1751static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, const String& childText)
1752{
1753 if (!builder.length() || !childText.length())
1754 return false;
1755
1756 // We don't need to add an additional space before or after a line break.
1757 return !(isHTMLLineBreak(childText[0]) || isHTMLLineBreak(builder[builder.length() - 1]));
1758}
1759
1760static void appendNameToStringBuilder(StringBuilder& builder, const String& text)
1761{
1762 if (shouldAddSpaceBeforeAppendingNextElement(builder, text))
1763 builder.append(' ');
1764 builder.append(text);
1765}
1766
1767String AccessibilityNodeObject::textUnderElement(AccessibilityTextUnderElementMode mode) const
1768{
1769 Node* node = this->node();
1770 if (is<Text>(node))
1771 return downcast<Text>(*node).wholeText();
1772
1773 bool isAriaVisible = AccessibilityObject::matchedParent(*this, true, [] (const AccessibilityObject& object) {
1774 return equalLettersIgnoringASCIICase(object.getAttribute(aria_hiddenAttr), "false");
1775 }) != nullptr;
1776
1777 // The Accname specification states that if the current node is hidden, and not directly
1778 // referenced by aria-labelledby or aria-describedby, and is not a host language text
1779 // alternative, the empty string should be returned.
1780 if (isDOMHidden() && !isAriaVisible && !is<HTMLLabelElement>(node) && (node && !ancestorsOfType<HTMLCanvasElement>(*node).first())) {
1781 AccessibilityObject::AccessibilityChildrenVector labelFor;
1782 AccessibilityObject::AccessibilityChildrenVector descriptionFor;
1783 ariaLabelledByReferencingElements(labelFor);
1784 ariaDescribedByReferencingElements(descriptionFor);
1785 if (!labelFor.size() && !descriptionFor.size())
1786 return String();
1787 }
1788
1789 StringBuilder builder;
1790 for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
1791 if (mode.ignoredChildNode && child->node() == mode.ignoredChildNode)
1792 continue;
1793
1794 bool shouldDeriveNameFromAuthor = (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren && !child->accessibleNameDerivesFromContent());
1795 if (shouldDeriveNameFromAuthor) {
1796 appendNameToStringBuilder(builder, accessibleNameForNode(child->node()));
1797 continue;
1798 }
1799
1800 if (!shouldUseAccessibilityObjectInnerText(child, mode))
1801 continue;
1802
1803 if (is<AccessibilityNodeObject>(*child)) {
1804 // We should ignore the child if it's labeled by this node.
1805 // This could happen when this node labels multiple child nodes and we didn't
1806 // skip in the above ignoredChildNode check.
1807 Vector<Element*> labeledByElements;
1808 downcast<AccessibilityNodeObject>(*child).ariaLabeledByElements(labeledByElements);
1809 if (labeledByElements.contains(node))
1810 continue;
1811
1812 Vector<AccessibilityText> textOrder;
1813 downcast<AccessibilityNodeObject>(*child).alternativeText(textOrder);
1814 if (textOrder.size() > 0 && textOrder[0].text.length()) {
1815 appendNameToStringBuilder(builder, textOrder[0].text);
1816 continue;
1817 }
1818 }
1819
1820 String childText = child->textUnderElement(mode);
1821 if (childText.length())
1822 appendNameToStringBuilder(builder, childText);
1823 }
1824
1825 return builder.toString().stripWhiteSpace().simplifyWhiteSpace(isHTMLSpaceButNotLineBreak);
1826}
1827
1828String AccessibilityNodeObject::title() const
1829{
1830 Node* node = this->node();
1831 if (!node)
1832 return String();
1833
1834 bool isInputTag = is<HTMLInputElement>(*node);
1835 if (isInputTag) {
1836 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
1837 if (input.isTextButton())
1838 return input.valueWithDefault();
1839 }
1840
1841 if (isLabelable()) {
1842 HTMLLabelElement* label = labelForElement(downcast<Element>(node));
1843 // Use the label text as the title if 1) the title element is NOT an exposed element and 2) there's no ARIA override.
1844 if (label && !exposesTitleUIElement() && !ariaAccessibilityDescription().length())
1845 return textForLabelElement(label);
1846 }
1847
1848 // If this node isn't rendered, there's no inner text we can extract from a select element.
1849 if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
1850 return String();
1851
1852 switch (roleValue()) {
1853 case AccessibilityRole::PopUpButton:
1854 // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
1855 if (node->hasTagName(selectTag))
1856 return String();
1857 FALLTHROUGH;
1858 case AccessibilityRole::Button:
1859 case AccessibilityRole::ToggleButton:
1860 case AccessibilityRole::CheckBox:
1861 case AccessibilityRole::ListBoxOption:
1862 case AccessibilityRole::ListItem:
1863 case AccessibilityRole::MenuButton:
1864 case AccessibilityRole::MenuItem:
1865 case AccessibilityRole::MenuItemCheckbox:
1866 case AccessibilityRole::MenuItemRadio:
1867 case AccessibilityRole::RadioButton:
1868 case AccessibilityRole::Switch:
1869 case AccessibilityRole::Tab:
1870 return textUnderElement();
1871 // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>.
1872 case AccessibilityRole::SVGRoot:
1873 return String();
1874 default:
1875 break;
1876 }
1877
1878 if (isLink())
1879 return textUnderElement();
1880 if (isHeading())
1881 return textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeSkipIgnoredChildren, true));
1882
1883 return String();
1884}
1885
1886String AccessibilityNodeObject::text() const
1887{
1888 // If this is a user defined static text, use the accessible name computation.
1889 if (isARIAStaticText()) {
1890 Vector<AccessibilityText> textOrder;
1891 alternativeText(textOrder);
1892 if (textOrder.size() > 0 && textOrder[0].text.length())
1893 return textOrder[0].text;
1894 }
1895
1896 if (!isTextControl())
1897 return String();
1898
1899 Node* node = this->node();
1900 if (!node)
1901 return String();
1902
1903 if (isNativeTextControl() && is<HTMLTextFormControlElement>(*node))
1904 return downcast<HTMLTextFormControlElement>(*node).value();
1905
1906 if (!node->isElementNode())
1907 return String();
1908
1909 return downcast<Element>(node)->innerText();
1910}
1911
1912String AccessibilityNodeObject::stringValue() const
1913{
1914 Node* node = this->node();
1915 if (!node)
1916 return String();
1917
1918 if (isARIAStaticText()) {
1919 String staticText = text();
1920 if (!staticText.length())
1921 staticText = textUnderElement();
1922 return staticText;
1923 }
1924
1925 if (node->isTextNode())
1926 return textUnderElement();
1927
1928 if (node->hasTagName(selectTag)) {
1929 HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node);
1930 int selectedIndex = selectElement.selectedIndex();
1931 const Vector<HTMLElement*>& listItems = selectElement.listItems();
1932 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
1933 const AtomicString& overriddenDescription = listItems[selectedIndex]->attributeWithoutSynchronization(aria_labelAttr);
1934 if (!overriddenDescription.isNull())
1935 return overriddenDescription;
1936 }
1937 if (!selectElement.multiple())
1938 return selectElement.value();
1939 return String();
1940 }
1941
1942 if (isTextControl())
1943 return text();
1944
1945 // FIXME: We might need to implement a value here for more types
1946 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
1947 // this would require subclassing or making accessibilityAttributeNames do something other than return a
1948 // single static array.
1949 return String();
1950}
1951
1952void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const
1953{
1954 r = 0;
1955 g = 0;
1956 b = 0;
1957
1958#if ENABLE(INPUT_TYPE_COLOR)
1959 if (!isColorWell())
1960 return;
1961
1962 if (!is<HTMLInputElement>(node()))
1963 return;
1964
1965 auto color = downcast<HTMLInputElement>(*node()).valueAsColor();
1966 r = color.red();
1967 g = color.green();
1968 b = color.blue();
1969#endif
1970}
1971
1972// This function implements the ARIA accessible name as described by the Mozilla
1973// ARIA Implementer's Guide.
1974static String accessibleNameForNode(Node* node, Node* labelledbyNode)
1975{
1976 ASSERT(node);
1977 if (!is<Element>(node))
1978 return String();
1979
1980 Element& element = downcast<Element>(*node);
1981 const AtomicString& ariaLabel = element.attributeWithoutSynchronization(aria_labelAttr);
1982 if (!ariaLabel.isEmpty())
1983 return ariaLabel;
1984
1985 const AtomicString& alt = element.attributeWithoutSynchronization(altAttr);
1986 if (!alt.isEmpty())
1987 return alt;
1988
1989 // If the node can be turned into an AX object, we can use standard name computation rules.
1990 // If however, the node cannot (because there's no renderer e.g.) fallback to using the basic text underneath.
1991 AccessibilityObject* axObject = node->document().axObjectCache()->getOrCreate(node);
1992 if (axObject) {
1993 String valueDescription = axObject->valueDescription();
1994 if (!valueDescription.isEmpty())
1995 return valueDescription;
1996
1997 // The Accname specification states that if the name is being calculated for a combobox
1998 // or listbox inside a labeling element, return the text alternative of the chosen option.
1999 AccessibilityObject::AccessibilityChildrenVector children;
2000 if (axObject->isListBox())
2001 axObject->selectedChildren(children);
2002 else if (axObject->isComboBox()) {
2003 for (const auto& child : axObject->children()) {
2004 if (child->isListBox()) {
2005 child->selectedChildren(children);
2006 break;
2007 }
2008 }
2009 }
2010
2011 StringBuilder builder;
2012 String childText;
2013 for (const auto& child : children)
2014 appendNameToStringBuilder(builder, accessibleNameForNode(child->node()));
2015
2016 childText = builder.toString();
2017 if (!childText.isEmpty())
2018 return childText;
2019 }
2020
2021 if (is<HTMLInputElement>(*node))
2022 return downcast<HTMLInputElement>(*node).value();
2023
2024 String text;
2025 if (axObject) {
2026 if (axObject->accessibleNameDerivesFromContent())
2027 text = axObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren, true, labelledbyNode));
2028 } else
2029 text = element.innerText().simplifyWhiteSpace();
2030
2031 if (!text.isEmpty())
2032 return text;
2033
2034 const AtomicString& title = element.attributeWithoutSynchronization(titleAttr);
2035 if (!title.isEmpty())
2036 return title;
2037
2038 return String();
2039}
2040
2041String AccessibilityNodeObject::accessibilityDescriptionForChildren() const
2042{
2043 Node* node = this->node();
2044 if (!node)
2045 return String();
2046
2047 AXObjectCache* cache = axObjectCache();
2048 if (!cache)
2049 return String();
2050
2051 StringBuilder builder;
2052 for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
2053 if (!is<Element>(child))
2054 continue;
2055
2056 if (AccessibilityObject* axObject = cache->getOrCreate(child)) {
2057 String description = axObject->ariaLabeledByAttribute();
2058 if (description.isEmpty())
2059 description = accessibleNameForNode(child);
2060 appendNameToStringBuilder(builder, description);
2061 }
2062 }
2063
2064 return builder.toString();
2065}
2066
2067String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
2068{
2069 StringBuilder builder;
2070 unsigned size = elements.size();
2071 for (unsigned i = 0; i < size; ++i)
2072 appendNameToStringBuilder(builder, accessibleNameForNode(elements[i], node()));
2073 return builder.toString();
2074}
2075
2076String AccessibilityNodeObject::ariaDescribedByAttribute() const
2077{
2078 Vector<Element*> elements;
2079 elementsFromAttribute(elements, aria_describedbyAttr);
2080
2081 return accessibilityDescriptionForElements(elements);
2082}
2083
2084void AccessibilityNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const
2085{
2086 elementsFromAttribute(elements, aria_labelledbyAttr);
2087 if (!elements.size())
2088 elementsFromAttribute(elements, aria_labeledbyAttr);
2089}
2090
2091
2092String AccessibilityNodeObject::ariaLabeledByAttribute() const
2093{
2094 Vector<Element*> elements;
2095 ariaLabeledByElements(elements);
2096
2097 return accessibilityDescriptionForElements(elements);
2098}
2099
2100bool AccessibilityNodeObject::hasAttributesRequiredForInclusion() const
2101{
2102 if (AccessibilityObject::hasAttributesRequiredForInclusion())
2103 return true;
2104
2105 // Avoid calculating the actual description here, which is expensive.
2106 // 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.
2107 if (getAttribute(aria_labelledbyAttr).length() || getAttribute(aria_labeledbyAttr).length() || getAttribute(aria_labelAttr).length())
2108 return true;
2109
2110 return false;
2111}
2112
2113bool AccessibilityNodeObject::canSetFocusAttribute() const
2114{
2115 Node* node = this->node();
2116 if (!node)
2117 return false;
2118
2119 if (isWebArea())
2120 return true;
2121
2122 // NOTE: It would be more accurate to ask the document whether setFocusedElement() would
2123 // do anything. For example, setFocusedElement() will do nothing if the current focused
2124 // node will not relinquish the focus.
2125 if (!is<Element>(node))
2126 return false;
2127
2128 Element& element = downcast<Element>(*node);
2129
2130 if (element.isDisabledFormControl())
2131 return false;
2132
2133 return element.supportsFocus();
2134}
2135
2136bool AccessibilityNodeObject::canSetValueAttribute() const
2137{
2138 Node* node = this->node();
2139 if (!node)
2140 return false;
2141
2142 // The host-language readonly attribute trumps aria-readonly.
2143 if (is<HTMLTextAreaElement>(*node))
2144 return !downcast<HTMLTextAreaElement>(*node).isReadOnly();
2145 if (is<HTMLInputElement>(*node)) {
2146 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
2147 if (input.isTextField())
2148 return !input.isReadOnly();
2149 }
2150
2151 String readOnly = readOnlyValue();
2152 if (!readOnly.isEmpty())
2153 return readOnly == "true" ? false : true;
2154
2155 if (isNonNativeTextControl())
2156 return true;
2157
2158 if (isMeter())
2159 return false;
2160
2161 if (isProgressIndicator() || isSlider() || isScrollbar())
2162 return true;
2163
2164#if PLATFORM(GTK)
2165 // In ATK, input types which support aria-readonly are treated as having a
2166 // settable value if the user can modify the widget's value or its state.
2167 if (supportsReadOnly())
2168 return true;
2169
2170 if (isRadioButton()) {
2171 auto radioGroup = radioGroupAncestor();
2172 return radioGroup ? radioGroup->readOnlyValue() != "true" : true;
2173 }
2174#endif
2175
2176 if (isWebArea()) {
2177 Document* document = this->document();
2178 if (!document)
2179 return false;
2180
2181 if (HTMLElement* body = document->bodyOrFrameset()) {
2182 if (body->hasEditableStyle())
2183 return true;
2184 }
2185
2186 return document->hasEditableStyle();
2187 }
2188
2189 return node->hasEditableStyle();
2190}
2191
2192AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const
2193{
2194 const AtomicString& ariaRole = getAttribute(roleAttr);
2195 if (ariaRole.isNull() || ariaRole.isEmpty())
2196 return AccessibilityRole::Unknown;
2197
2198 AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
2199
2200 // ARIA states if an item can get focus, it should not be presentational.
2201 if (role == AccessibilityRole::Presentational && canSetFocusAttribute())
2202 return AccessibilityRole::Unknown;
2203
2204 if (role == AccessibilityRole::Button)
2205 role = buttonRoleType();
2206
2207 if (role == AccessibilityRole::TextArea && !ariaIsMultiline())
2208 role = AccessibilityRole::TextField;
2209
2210 role = remapAriaRoleDueToParent(role);
2211
2212 // Presentational roles are invalidated by the presence of ARIA attributes.
2213 if (role == AccessibilityRole::Presentational && supportsARIAAttributes())
2214 role = AccessibilityRole::Unknown;
2215
2216 // The ARIA spec states, "Authors must give each element with role region a brief label that
2217 // describes the purpose of the content in the region." The Core AAM states, "Special case:
2218 // if the region does not have an accessible name, do not expose the element as a landmark.
2219 // Use the native host language role of the element instead."
2220 if (role == AccessibilityRole::LandmarkRegion && !hasAttribute(aria_labelAttr) && !hasAttribute(aria_labelledbyAttr))
2221 role = AccessibilityRole::Unknown;
2222
2223 if (static_cast<int>(role))
2224 return role;
2225
2226 return AccessibilityRole::Unknown;
2227}
2228
2229AccessibilityRole AccessibilityNodeObject::ariaRoleAttribute() const
2230{
2231 return m_ariaRole;
2232}
2233
2234AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
2235{
2236 // Some objects change their role based on their parent.
2237 // However, asking for the unignoredParent calls accessibilityIsIgnored(), which can trigger a loop.
2238 // While inside the call stack of creating an element, we need to avoid accessibilityIsIgnored().
2239 // https://bugs.webkit.org/show_bug.cgi?id=65174
2240
2241 if (role != AccessibilityRole::ListBoxOption && role != AccessibilityRole::MenuItem)
2242 return role;
2243
2244 for (AccessibilityObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
2245 AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
2246
2247 // Selects and listboxes both have options as child roles, but they map to different roles within WebCore.
2248 if (role == AccessibilityRole::ListBoxOption && parentAriaRole == AccessibilityRole::Menu)
2249 return AccessibilityRole::MenuItem;
2250 // An aria "menuitem" may map to MenuButton or MenuItem depending on its parent.
2251 if (role == AccessibilityRole::MenuItem && parentAriaRole == AccessibilityRole::ApplicationGroup)
2252 return AccessibilityRole::MenuButton;
2253
2254 // If the parent had a different role, then we don't need to continue searching up the chain.
2255 if (parentAriaRole != AccessibilityRole::Unknown)
2256 break;
2257 }
2258
2259 return role;
2260}
2261
2262bool AccessibilityNodeObject::canSetSelectedAttribute() const
2263{
2264 // Elements that can be selected
2265 switch (roleValue()) {
2266 case AccessibilityRole::Cell:
2267 case AccessibilityRole::GridCell:
2268 case AccessibilityRole::RowHeader:
2269 case AccessibilityRole::Row:
2270 case AccessibilityRole::TabList:
2271 case AccessibilityRole::Tab:
2272 case AccessibilityRole::TreeGrid:
2273 case AccessibilityRole::TreeItem:
2274 case AccessibilityRole::Tree:
2275 case AccessibilityRole::MenuItemCheckbox:
2276 case AccessibilityRole::MenuItemRadio:
2277 case AccessibilityRole::MenuItem:
2278 return isEnabled();
2279 default:
2280 return false;
2281 }
2282}
2283
2284} // namespace WebCore
2285