AX: Move node-dependent role computation to AccessibilityNodeObject from Accessibilit...
[webkit.git] / Source / WebCore / accessibility / AccessibilityRenderObject.cpp
blob27e378ab049cc04874a5a972e06ed9d5e4f5ed97
1 /*
2 * Copyright (C) 2008-2020 Apple 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
6 * are met:
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.
29 #include "config.h"
30 #include "AccessibilityRenderObject.h"
32 #include "AXLogger.h"
33 #include "AXObjectCache.h"
34 #include "AccessibilityImageMapLink.h"
35 #include "AccessibilityListBox.h"
36 #include "AccessibilitySVGRoot.h"
37 #include "AccessibilitySpinButton.h"
38 #include "AccessibilityTable.h"
39 #include "CachedImage.h"
40 #include "DocumentSVG.h"
41 #include "Editing.h"
42 #include "Editor.h"
43 #include "EditorClient.h"
44 #include "ElementIterator.h"
45 #include "EventHandler.h"
46 #include "FloatRect.h"
47 #include "Frame.h"
48 #include "FrameLoader.h"
49 #include "FrameSelection.h"
50 #include "GeometryUtilities.h"
51 #include "HTMLAreaElement.h"
52 #include "HTMLBRElement.h"
53 #include "HTMLDetailsElement.h"
54 #include "HTMLFormElement.h"
55 #include "HTMLFrameElementBase.h"
56 #include "HTMLImageElement.h"
57 #include "HTMLInputElement.h"
58 #include "HTMLLabelElement.h"
59 #include "HTMLMapElement.h"
60 #include "HTMLMeterElement.h"
61 #include "HTMLNames.h"
62 #include "HTMLOptionElement.h"
63 #include "HTMLOptionsCollection.h"
64 #include "HTMLParserIdioms.h"
65 #include "HTMLSelectElement.h"
66 #include "HTMLSummaryElement.h"
67 #include "HTMLTableElement.h"
68 #include "HTMLTextAreaElement.h"
69 #include "HTMLVideoElement.h"
70 #include "HitTestRequest.h"
71 #include "HitTestResult.h"
72 #include "Image.h"
73 #include "LegacyRenderSVGRoot.h"
74 #include "LegacyRenderSVGShape.h"
75 #include "LocalizedStrings.h"
76 #include "NodeList.h"
77 #include "Page.h"
78 #include "PathUtilities.h"
79 #include "PluginViewBase.h"
80 #include "ProgressTracker.h"
81 #include "Range.h"
82 #include "RenderButton.h"
83 #include "RenderFileUploadControl.h"
84 #include "RenderHTMLCanvas.h"
85 #include "RenderImage.h"
86 #include "RenderInline.h"
87 #include "RenderIterator.h"
88 #include "RenderLayer.h"
89 #include "RenderLayerScrollableArea.h"
90 #include "RenderLineBreak.h"
91 #include "RenderListBox.h"
92 #include "RenderListItem.h"
93 #include "RenderListMarker.h"
94 #include "RenderMathMLBlock.h"
95 #include "RenderMenuList.h"
96 #include "RenderSVGRoot.h"
97 #include "RenderSVGShape.h"
98 #include "RenderTableCell.h"
99 #include "RenderText.h"
100 #include "RenderTextControl.h"
101 #include "RenderTextControlSingleLine.h"
102 #include "RenderTextFragment.h"
103 #include "RenderTheme.h"
104 #include "RenderView.h"
105 #include "RenderWidget.h"
106 #include "RenderedPosition.h"
107 #include "SVGDocument.h"
108 #include "SVGElementTypeHelpers.h"
109 #include "SVGImage.h"
110 #include "SVGSVGElement.h"
111 #include "Text.h"
112 #include "TextControlInnerElements.h"
113 #include "TextIterator.h"
114 #include "VisibleUnits.h"
115 #include <wtf/NeverDestroyed.h>
116 #include <wtf/StdLibExtras.h>
117 #include <wtf/unicode/CharacterNames.h>
119 namespace WebCore {
121 using namespace HTMLNames;
123 AccessibilityRenderObject::AccessibilityRenderObject(RenderObject* renderer)
124 : AccessibilityNodeObject(renderer->node())
125 , m_renderer(renderer)
127 #ifndef NDEBUG
128 m_renderer->setHasAXObject(true);
129 #endif
132 AccessibilityRenderObject::~AccessibilityRenderObject()
134 ASSERT(isDetached());
137 void AccessibilityRenderObject::init()
139 AccessibilityNodeObject::init();
142 Ref<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer)
144 return adoptRef(*new AccessibilityRenderObject(renderer));
147 void AccessibilityRenderObject::detachRemoteParts(AccessibilityDetachmentType detachmentType)
149 AccessibilityNodeObject::detachRemoteParts(detachmentType);
151 detachRemoteSVGRoot();
153 #ifndef NDEBUG
154 if (m_renderer)
155 m_renderer->setHasAXObject(false);
156 #endif
157 m_renderer = nullptr;
160 RenderBoxModelObject* AccessibilityRenderObject::renderBoxModelObject() const
162 if (!is<RenderBoxModelObject>(renderer()))
163 return nullptr;
164 return downcast<RenderBoxModelObject>(renderer());
167 void AccessibilityRenderObject::setRenderer(RenderObject* renderer)
169 m_renderer = renderer;
170 setNode(renderer->node());
173 static inline bool isInlineWithContinuation(RenderObject& object)
175 return is<RenderInline>(object) && downcast<RenderInline>(object).continuation();
178 static inline RenderObject* firstChildInContinuation(RenderInline& renderer)
180 auto* continuation = renderer.continuation();
182 while (continuation) {
183 if (is<RenderBlock>(*continuation))
184 return continuation;
185 if (RenderObject* child = continuation->firstChild())
186 return child;
187 continuation = downcast<RenderInline>(*continuation).continuation();
190 return nullptr;
193 static inline RenderObject* firstChildConsideringContinuation(RenderObject& renderer)
195 RenderObject* firstChild = renderer.firstChildSlow();
197 // We don't want to include the end of a continuation as the firstChild of the
198 // anonymous parent, because everything has already been linked up via continuation.
199 // CSS first-letter selector is an example of this case.
200 if (renderer.isAnonymous() && is<RenderInline>(firstChild) && downcast<RenderInline>(*firstChild).isContinuation())
201 firstChild = nullptr;
203 if (!firstChild && isInlineWithContinuation(renderer))
204 firstChild = firstChildInContinuation(downcast<RenderInline>(renderer));
206 return firstChild;
210 static inline RenderObject* lastChildConsideringContinuation(RenderObject& renderer)
212 if (!is<RenderInline>(renderer) && !is<RenderBlock>(renderer))
213 return &renderer;
215 RenderObject* lastChild = downcast<RenderBoxModelObject>(renderer).lastChild();
216 for (auto* current = &downcast<RenderBoxModelObject>(renderer); current; ) {
217 if (RenderObject* newLastChild = current->lastChild())
218 lastChild = newLastChild;
220 current = current->inlineContinuation();
223 return lastChild;
226 AccessibilityObject* AccessibilityRenderObject::firstChild() const
228 if (!m_renderer)
229 return nullptr;
231 RenderObject* firstChild = firstChildConsideringContinuation(*m_renderer);
233 // If an object can't have children, then it is using this method to help
234 // calculate some internal property (like its description).
235 // In this case, it should check the Node level for children in case they're
236 // not rendered (like a <meter> element).
237 if (!firstChild && !canHaveChildren())
238 return AccessibilityNodeObject::firstChild();
240 auto objectCache = axObjectCache();
241 return objectCache ? objectCache->getOrCreate(firstChild) : nullptr;
244 AccessibilityObject* AccessibilityRenderObject::lastChild() const
246 if (!m_renderer)
247 return nullptr;
249 RenderObject* lastChild = lastChildConsideringContinuation(*m_renderer);
251 if (!lastChild && !canHaveChildren())
252 return AccessibilityNodeObject::lastChild();
254 auto objectCache = axObjectCache();
255 return objectCache ? objectCache->getOrCreate(lastChild) : nullptr;
258 static inline RenderInline* startOfContinuations(RenderObject& renderer)
260 if (!is<RenderElement>(renderer))
261 return nullptr;
262 auto& renderElement = downcast<RenderElement>(renderer);
263 if (is<RenderInline>(renderElement) && renderElement.isContinuation() && is<RenderInline>(renderElement.element()->renderer()))
264 return downcast<RenderInline>(renderer.node()->renderer());
266 // Blocks with a previous continuation always have a next continuation
267 if (is<RenderBlock>(renderElement) && downcast<RenderBlock>(renderElement).inlineContinuation())
268 return downcast<RenderInline>(downcast<RenderBlock>(renderElement).inlineContinuation()->element()->renderer());
270 return nullptr;
273 static inline RenderObject* endOfContinuations(RenderObject& renderer)
275 if (!is<RenderInline>(renderer) && !is<RenderBlock>(renderer))
276 return &renderer;
278 auto* previous = &downcast<RenderBoxModelObject>(renderer);
279 for (auto* current = previous; current; ) {
280 previous = current;
281 current = current->inlineContinuation();
284 return previous;
288 static inline RenderObject* childBeforeConsideringContinuations(RenderInline* renderer, RenderObject* child)
290 RenderObject* previous = nullptr;
291 for (RenderBoxModelObject* currentContainer = renderer; currentContainer; ) {
292 if (is<RenderInline>(*currentContainer)) {
293 auto* current = currentContainer->firstChild();
294 while (current) {
295 if (current == child)
296 return previous;
297 previous = current;
298 current = current->nextSibling();
301 currentContainer = currentContainer->continuation();
302 } else if (is<RenderBlock>(*currentContainer)) {
303 if (currentContainer == child)
304 return previous;
306 previous = currentContainer;
307 currentContainer = currentContainer->inlineContinuation();
311 ASSERT_NOT_REACHED();
312 return nullptr;
315 static inline bool firstChildIsInlineContinuation(RenderElement& renderer)
317 RenderObject* child = renderer.firstChild();
318 return is<RenderInline>(child) && downcast<RenderInline>(*child).isContinuation();
321 AccessibilityObject* AccessibilityRenderObject::previousSibling() const
323 if (!m_renderer)
324 return nullptr;
326 RenderObject* previousSibling = nullptr;
328 // Case 1: The node is a block and is an inline's continuation. In that case, the inline's
329 // last child is our previous sibling (or further back in the continuation chain)
330 RenderInline* startOfConts;
331 if (is<RenderBox>(*m_renderer) && (startOfConts = startOfContinuations(*m_renderer)))
332 previousSibling = childBeforeConsideringContinuations(startOfConts, renderer());
334 // Case 2: Anonymous block parent of the end of a continuation - skip all the way to before
335 // the parent of the start, since everything in between will be linked up via the continuation.
336 else if (m_renderer->isAnonymousBlock() && firstChildIsInlineContinuation(downcast<RenderBlock>(*m_renderer))) {
337 RenderBlock& renderBlock = downcast<RenderBlock>(*m_renderer);
338 auto* firstParent = startOfContinuations(*renderBlock.firstChild())->parent();
339 ASSERT(firstParent);
340 while (firstChildIsInlineContinuation(*firstParent))
341 firstParent = startOfContinuations(*firstParent->firstChild())->parent();
342 previousSibling = firstParent->previousSibling();
345 // Case 3: The node has an actual previous sibling
346 else if (RenderObject* ps = m_renderer->previousSibling())
347 previousSibling = ps;
349 // Case 4: This node has no previous siblings, but its parent is an inline,
350 // and is another node's inline continutation. Follow the continuation chain.
351 else if (is<RenderInline>(m_renderer->parent()) && (startOfConts = startOfContinuations(*m_renderer->parent())))
352 previousSibling = childBeforeConsideringContinuations(startOfConts, m_renderer->parent()->firstChild());
354 if (!previousSibling)
355 return nullptr;
357 if (auto* objectCache = axObjectCache())
358 return objectCache->getOrCreate(previousSibling);
360 return nullptr;
363 static inline bool lastChildHasContinuation(RenderElement& renderer)
365 RenderObject* child = renderer.lastChild();
366 return child && isInlineWithContinuation(*child);
369 AccessibilityObject* AccessibilityRenderObject::nextSibling() const
371 if (!m_renderer || is<RenderView>(*m_renderer))
372 return nullptr;
374 RenderObject* nextSibling = nullptr;
376 // Case 1: node is a block and has an inline continuation. Next sibling is the inline continuation's
377 // first child.
378 RenderInline* inlineContinuation;
379 if (is<RenderBlock>(*m_renderer) && (inlineContinuation = downcast<RenderBlock>(*m_renderer).inlineContinuation()))
380 nextSibling = firstChildConsideringContinuation(*inlineContinuation);
382 // Case 2: Anonymous block parent of the start of a continuation - skip all the way to
383 // after the parent of the end, since everything in between will be linked up via the continuation.
384 else if (m_renderer->isAnonymousBlock() && lastChildHasContinuation(downcast<RenderBlock>(*m_renderer))) {
385 RenderElement* lastParent = endOfContinuations(*downcast<RenderBlock>(*m_renderer).lastChild())->parent();
386 ASSERT(lastParent);
387 while (lastChildHasContinuation(*lastParent))
388 lastParent = endOfContinuations(*lastParent->lastChild())->parent();
389 nextSibling = lastParent->nextSibling();
392 // Case 3: node has an actual next sibling
393 else if (RenderObject* ns = m_renderer->nextSibling())
394 nextSibling = ns;
396 // Case 4: node is an inline with a continuation. Next sibling is the next sibling of the end
397 // of the continuation chain.
398 else if (isInlineWithContinuation(*m_renderer))
399 nextSibling = endOfContinuations(*m_renderer)->nextSibling();
401 // Case 5: node has no next sibling, and its parent is an inline with a continuation.
402 // Case 5.1: After case 4, (the element was inline w/ continuation but had no sibling), then check it's parent.
403 if (!nextSibling && m_renderer->parent() && isInlineWithContinuation(*m_renderer->parent())) {
404 auto& continuation = *downcast<RenderInline>(*m_renderer->parent()).continuation();
406 // Case 5a: continuation is a block - in this case the block itself is the next sibling.
407 if (is<RenderBlock>(continuation))
408 nextSibling = &continuation;
409 // Case 5b: continuation is an inline - in this case the inline's first child is the next sibling
410 else
411 nextSibling = firstChildConsideringContinuation(continuation);
413 // After case 4, there are chances that nextSibling has the same node as the current renderer,
414 // which might lead to adding the same child repeatedly.
415 if (nextSibling && nextSibling->node() == m_renderer->node()) {
416 if (AccessibilityObject* nextObj = axObjectCache()->getOrCreate(nextSibling))
417 return nextObj->nextSibling();
421 if (!nextSibling)
422 return nullptr;
424 auto* objectCache = axObjectCache();
425 if (!objectCache)
426 return nullptr;
428 // Make sure next sibling has the same parent.
429 auto* nextObject = objectCache->getOrCreate(nextSibling);
430 if (nextObject && nextObject->parentObject() != this->parentObject())
431 return nullptr;
433 return nextObject;
436 static RenderBoxModelObject* nextContinuation(RenderObject& renderer)
438 if (is<RenderInline>(renderer) && !renderer.isReplacedOrInlineBlock())
439 return downcast<RenderInline>(renderer).continuation();
440 if (is<RenderBlock>(renderer))
441 return downcast<RenderBlock>(renderer).inlineContinuation();
442 return nullptr;
445 RenderObject* AccessibilityRenderObject::renderParentObject() const
447 if (!m_renderer)
448 return nullptr;
450 RenderElement* parent = m_renderer->parent();
452 // Case 1: node is a block and is an inline's continuation. Parent
453 // is the start of the continuation chain.
454 RenderInline* startOfConts = nullptr;
455 RenderObject* firstChild = nullptr;
456 if (is<RenderBlock>(*m_renderer) && (startOfConts = startOfContinuations(*m_renderer)))
457 parent = startOfConts;
459 // Case 2: node's parent is an inline which is some node's continuation; parent is
460 // the earliest node in the continuation chain.
461 else if (is<RenderInline>(parent) && (startOfConts = startOfContinuations(*parent)))
462 parent = startOfConts;
464 // Case 3: The first sibling is the beginning of a continuation chain. Find the origin of that continuation.
465 else if (parent && (firstChild = parent->firstChild()) && firstChild->node()) {
466 // Get the node's renderer and follow that continuation chain until the first child is found
467 RenderObject* nodeRenderFirstChild = firstChild->node()->renderer();
468 while (nodeRenderFirstChild != firstChild) {
469 for (RenderObject* contsTest = nodeRenderFirstChild; contsTest; contsTest = nextContinuation(*contsTest)) {
470 if (contsTest == firstChild) {
471 parent = nodeRenderFirstChild->parent();
472 break;
475 RenderObject* parentFirstChild = parent->firstChild();
476 if (firstChild == parentFirstChild)
477 break;
478 firstChild = parentFirstChild;
479 if (!firstChild->node())
480 break;
481 nodeRenderFirstChild = firstChild->node()->renderer();
485 return parent;
488 AccessibilityObject* AccessibilityRenderObject::parentObjectIfExists() const
490 AXObjectCache* cache = axObjectCache();
491 if (!cache)
492 return nullptr;
494 // WebArea's parent should be the scroll view containing it.
495 if (m_renderer && isWebArea())
496 return cache->get(&m_renderer->view().frameView());
498 return cache->get(renderParentObject());
501 AccessibilityObject* AccessibilityRenderObject::parentObject() const
503 if (!m_renderer)
504 return nullptr;
506 if (ariaRoleAttribute() == AccessibilityRole::MenuBar)
507 return axObjectCache()->getOrCreate(m_renderer->parent());
509 // menuButton and its corresponding menu are DOM siblings, but Accessibility needs them to be parent/child
510 if (ariaRoleAttribute() == AccessibilityRole::Menu) {
511 AccessibilityObject* parent = menuButtonForMenu();
512 if (parent)
513 return parent;
516 AXObjectCache* cache = axObjectCache();
517 if (!cache)
518 return nullptr;
520 RenderObject* parentObj = renderParentObject();
521 if (parentObj)
522 return cache->getOrCreate(parentObj);
524 // WebArea's parent should be the scroll view containing it.
525 if (isWebArea())
526 return cache->getOrCreate(&m_renderer->view().frameView());
528 return nullptr;
531 bool AccessibilityRenderObject::isAttachment() const
533 RenderBoxModelObject* renderer = renderBoxModelObject();
534 if (!renderer)
535 return false;
537 // WebKit2 plugins need to be treated differently than attachments, so return false here.
538 // Only WebKit1 plugins have an associated platformWidget.
539 if (is<PluginViewBase>(widget()) && !widget()->platformWidget())
540 return false;
542 // Widgets are the replaced elements that we represent to AX as attachments
543 bool isWidget = renderer->isWidget();
545 return isWidget && ariaRoleAttribute() == AccessibilityRole::Unknown;
548 bool AccessibilityRenderObject::isOffScreen() const
550 if (!m_renderer)
551 return true;
553 IntRect contentRect = snappedIntRect(m_renderer->absoluteClippedOverflowRectForSpatialNavigation());
554 // FIXME: unclear if we need LegacyIOSDocumentVisibleRect.
555 IntRect viewRect = m_renderer->view().frameView().visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
556 viewRect.intersect(contentRect);
557 return viewRect.isEmpty();
560 Element* AccessibilityRenderObject::anchorElement() const
562 if (!m_renderer)
563 return nullptr;
565 AXObjectCache* cache = axObjectCache();
566 if (!cache)
567 return nullptr;
569 RenderObject* currentRenderer;
571 // Search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though.
572 for (currentRenderer = renderer(); currentRenderer && !currentRenderer->node(); currentRenderer = currentRenderer->parent()) {
573 if (currentRenderer->isAnonymousBlock()) {
574 if (RenderObject* continuation = downcast<RenderBlock>(*currentRenderer).continuation())
575 return cache->getOrCreate(continuation)->anchorElement();
579 // bail if none found
580 if (!currentRenderer)
581 return nullptr;
583 // search up the DOM tree for an anchor element
584 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
585 for (Node* node = currentRenderer->node(); node; node = node->parentNode()) {
586 if (is<HTMLAnchorElement>(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isLink()))
587 return downcast<Element>(node);
590 return nullptr;
593 String AccessibilityRenderObject::helpText() const
595 if (!m_renderer)
596 return String();
598 const AtomString& ariaHelp = getAttribute(aria_helpAttr);
599 if (!ariaHelp.isEmpty())
600 return ariaHelp;
602 String describedBy = ariaDescribedByAttribute();
603 if (!describedBy.isEmpty())
604 return describedBy;
606 String description = accessibilityDescription();
607 for (RenderObject* ancestor = renderer(); ancestor; ancestor = ancestor->parent()) {
608 if (is<HTMLElement>(ancestor->node())) {
609 HTMLElement& element = downcast<HTMLElement>(*ancestor->node());
610 const AtomString& summary = element.getAttribute(summaryAttr);
611 if (!summary.isEmpty())
612 return summary;
614 // The title attribute should be used as help text unless it is already being used as descriptive text.
615 const AtomString& title = element.getAttribute(titleAttr);
616 if (!title.isEmpty() && description != title)
617 return title;
620 // Only take help text from an ancestor element if its a group or an unknown role. If help was
621 // added to those kinds of elements, it is likely it was meant for a child element.
622 if (AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor)) {
623 if (!axObj->isGroup() && axObj->roleValue() != AccessibilityRole::Unknown)
624 break;
628 return String();
631 String AccessibilityRenderObject::textUnderElement(AccessibilityTextUnderElementMode mode) const
633 if (!m_renderer)
634 return String();
636 if (is<RenderFileUploadControl>(*m_renderer))
637 return downcast<RenderFileUploadControl>(*m_renderer).buttonValue();
639 // Reflect when a content author has explicitly marked a line break.
640 if (m_renderer->isBR())
641 return "\n"_s;
643 if (shouldGetTextFromNode(mode))
644 return AccessibilityNodeObject::textUnderElement(mode);
646 // We use a text iterator for text objects AND for those cases where we are
647 // explicitly asking for the full text under a given element.
648 if (is<RenderText>(*m_renderer) || mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren) {
649 // If possible, use a text iterator to get the text, so that whitespace
650 // is handled consistently.
651 Document* nodeDocument = nullptr;
652 std::optional<SimpleRange> textRange;
653 if (Node* node = m_renderer->node()) {
654 nodeDocument = &node->document();
655 textRange = makeRangeSelectingNodeContents(*node);
656 } else {
657 // For anonymous blocks, we work around not having a direct node to create a range from
658 // defining one based in the two external positions defining the boundaries of the subtree.
659 RenderObject* firstChildRenderer = m_renderer->firstChildSlow();
660 RenderObject* lastChildRenderer = m_renderer->lastChildSlow();
661 if (firstChildRenderer && firstChildRenderer->node() && lastChildRenderer && lastChildRenderer->node()) {
662 // We define the start and end positions for the range as the ones right before and after
663 // the first and the last nodes in the DOM tree that is wrapped inside the anonymous block.
664 auto& firstNodeInBlock = *firstChildRenderer->node();
665 nodeDocument = &firstNodeInBlock.document();
666 textRange = makeSimpleRange(positionInParentBeforeNode(&firstNodeInBlock), positionInParentAfterNode(lastChildRenderer->node()));
670 if (nodeDocument && textRange) {
671 if (Frame* frame = nodeDocument->frame()) {
672 // catch stale WebCoreAXObject (see <rdar://problem/3960196>)
673 if (frame->document() != nodeDocument)
674 return { };
676 // Renderers referenced by accessibility objects could get destroyed if TextIterator ends up triggering
677 // a style update or layout here. See also AXObjectCache::deferTextChangedIfNeeded().
678 if (nodeDocument->childNeedsStyleRecalc())
679 return { };
680 ASSERT_WITH_SECURITY_IMPLICATION(!nodeDocument->view()->layoutContext().isInRenderTreeLayout());
682 return plainText(*textRange, textIteratorBehaviorForTextRange());
686 // Sometimes text fragments don't have Nodes associated with them (like when
687 // CSS content is used to insert text or when a RenderCounter is used.)
688 if (is<RenderText>(*m_renderer)) {
689 RenderText& renderTextObject = downcast<RenderText>(*m_renderer);
690 if (is<RenderTextFragment>(renderTextObject)) {
691 RenderTextFragment& renderTextFragment = downcast<RenderTextFragment>(renderTextObject);
692 // The alt attribute may be set on a text fragment through CSS, which should be honored.
693 const String& altText = renderTextFragment.altText();
694 if (!altText.isEmpty())
695 return altText;
696 return renderTextFragment.contentString();
699 return renderTextObject.text();
703 return AccessibilityNodeObject::textUnderElement(mode);
706 bool AccessibilityRenderObject::shouldGetTextFromNode(AccessibilityTextUnderElementMode mode) const
708 if (!m_renderer)
709 return false;
711 // AccessibilityRenderObject::textUnderElement() gets the text of anonymous blocks by using
712 // the child nodes to define positions. CSS tables and their anonymous descendants lack
713 // children with nodes.
714 if (m_renderer->isAnonymous() && m_renderer->isTablePart())
715 return mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren;
717 // AccessibilityRenderObject::textUnderElement() calls rangeOfContents() to create the text
718 // range. rangeOfContents() does not include CSS-generated content.
719 if (m_renderer->isBeforeOrAfterContent())
720 return true;
721 if (Node* node = m_renderer->node()) {
722 Node* firstChild = node->pseudoAwareFirstChild();
723 Node* lastChild = node->pseudoAwareLastChild();
724 if ((firstChild && firstChild->isPseudoElement()) || (lastChild && lastChild->isPseudoElement()))
725 return true;
728 return false;
731 Node* AccessibilityRenderObject::node() const
733 if (!m_renderer)
734 return nullptr;
735 if (m_renderer->isRenderView())
736 return &m_renderer->document();
737 return m_renderer->node();
740 String AccessibilityRenderObject::stringValue() const
742 if (!m_renderer)
743 return String();
745 if (isPasswordField())
746 return passwordFieldValue();
748 RenderBoxModelObject* cssBox = renderBoxModelObject();
750 if (isARIAStaticText()) {
751 String staticText = text();
752 if (!staticText.length())
753 staticText = textUnderElement();
754 return staticText;
757 if (is<RenderText>(*m_renderer))
758 return textUnderElement();
760 if (is<RenderMenuList>(cssBox)) {
761 // RenderMenuList will go straight to the text() of its selected item.
762 // This has to be overridden in the case where the selected item has an ARIA label.
763 HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*m_renderer->node());
764 int selectedIndex = selectElement.selectedIndex();
765 const Vector<HTMLElement*>& listItems = selectElement.listItems();
766 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
767 const AtomString& overriddenDescription = listItems[selectedIndex]->attributeWithoutSynchronization(aria_labelAttr);
768 if (!overriddenDescription.isNull())
769 return overriddenDescription;
771 return downcast<RenderMenuList>(*m_renderer).text();
774 if (is<RenderListMarker>(*m_renderer)) {
775 #if USE(ATSPI)
776 return downcast<RenderListMarker>(*m_renderer).textWithSuffix().toString();
777 #else
778 return downcast<RenderListMarker>(*m_renderer).textWithoutSuffix().toString();
779 #endif
782 if (isWebArea())
783 return String();
785 if (isTextControl())
786 return text();
788 #if PLATFORM(IOS_FAMILY)
789 if (isInputTypePopupButton())
790 return textUnderElement();
791 #endif
793 if (is<RenderFileUploadControl>(*m_renderer))
794 return downcast<RenderFileUploadControl>(*m_renderer).fileTextValue();
796 // FIXME: We might need to implement a value here for more types
797 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
798 // this would require subclassing or making accessibilityAttributeNames do something other than return a
799 // single static array.
800 return String();
803 bool AccessibilityRenderObject::canHavePlainText() const
805 return isARIAStaticText() || is<RenderText>(*m_renderer) || isTextControl();
808 // The boundingBox for elements within the remote SVG element needs to be offset by its position
809 // within the parent page, otherwise they are in relative coordinates only.
810 void AccessibilityRenderObject::offsetBoundingBoxForRemoteSVGElement(LayoutRect& rect) const
812 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
813 if (parent->isAccessibilitySVGRoot()) {
814 rect.moveBy(parent->parentObject()->boundingBoxRect().location());
815 break;
820 LayoutRect AccessibilityRenderObject::boundingBoxRect() const
822 RenderObject* obj = renderer();
824 if (!obj)
825 return LayoutRect();
827 if (obj->node()) // If we are a continuation, we want to make sure to use the primary renderer.
828 obj = obj->node()->renderer();
830 // absoluteFocusRingQuads will query the hierarchy below this element, which for large webpages can be very slow.
831 // For a web area, which will have the most elements of any element, absoluteQuads should be used.
832 // We should also use absoluteQuads for SVG elements, otherwise transforms won't be applied.
833 Vector<FloatQuad> quads;
834 bool isSVGRoot = false;
836 if (obj->isSVGRootOrLegacySVGRoot())
837 isSVGRoot = true;
839 if (is<RenderText>(*obj))
840 quads = downcast<RenderText>(*obj).absoluteQuadsClippedToEllipsis();
841 else if (isWebArea() || isSVGRoot)
842 obj->absoluteQuads(quads);
843 else
844 obj->absoluteFocusRingQuads(quads);
846 LayoutRect result = boundingBoxForQuads(obj, quads);
848 Document* document = this->document();
849 if (document && document->isSVGDocument())
850 offsetBoundingBoxForRemoteSVGElement(result);
852 // The size of the web area should be the content size, not the clipped size.
853 if (isWebArea())
854 result.setSize(obj->view().frameView().contentsSize());
856 return result;
859 LayoutRect AccessibilityRenderObject::checkboxOrRadioRect() const
861 if (!m_renderer)
862 return LayoutRect();
864 HTMLLabelElement* label = labelForElement(downcast<Element>(m_renderer->node()));
865 if (!label || !label->renderer())
866 return boundingBoxRect();
868 LayoutRect labelRect = axObjectCache()->getOrCreate(label)->elementRect();
869 labelRect.unite(boundingBoxRect());
870 return labelRect;
873 LayoutRect AccessibilityRenderObject::elementRect() const
875 // a checkbox or radio button should encompass its label
876 if (isCheckboxOrRadio())
877 return checkboxOrRadioRect();
879 return boundingBoxRect();
882 bool AccessibilityRenderObject::supportsPath() const
884 return is<RenderText>(renderer()) || (renderer() && renderer()->isSVGShapeOrLegacySVGShape());
887 Path AccessibilityRenderObject::elementPath() const
889 if (is<RenderText>(renderer())) {
890 Vector<IntRect> rects;
891 downcast<RenderText>(*m_renderer).absoluteRects(rects, flooredLayoutPoint(m_renderer->localToAbsolute()));
892 // If only 1 rect, don't compute path since the bounding rect will be good enough.
893 if (rects.size() < 2)
894 return Path();
896 // Compute the path only if this is the last part of a line followed by the beginning of the next line.
897 const auto& style = m_renderer->style();
898 bool rightToLeftText = style.direction() == TextDirection::RTL;
899 static const int xTolerance = 5;
900 static const int yTolerance = 5;
901 bool needsPath = false;
902 IntRect unionRect = rects[0];
903 for (size_t i = 1; i < rects.size(); ++i) {
904 needsPath = abs(rects[i].y() - unionRect.maxY()) < yTolerance // This rect is in a new line.
905 && (rightToLeftText ? rects[i].x() - unionRect.x() > xTolerance
906 : unionRect.x() - rects[i].x() > xTolerance); // And this rect is to right/left of all previous rects.
908 if (needsPath)
909 break;
911 unionRect.unite(rects[i]);
913 if (!needsPath)
914 return Path();
916 float outlineOffset = style.outlineOffset();
917 float deviceScaleFactor = m_renderer->document().deviceScaleFactor();
918 Vector<FloatRect> pixelSnappedRects;
919 for (auto rect : rects) {
920 rect.inflate(outlineOffset);
921 pixelSnappedRects.append(snapRectToDevicePixels(rect, deviceScaleFactor));
924 return PathUtilities::pathWithShrinkWrappedRects(pixelSnappedRects, 0);
927 if (is<LegacyRenderSVGShape>(renderer()) && downcast<LegacyRenderSVGShape>(*m_renderer).hasPath()) {
928 Path path = downcast<LegacyRenderSVGShape>(*m_renderer).path();
930 // The SVG path is in terms of the parent's bounding box. The path needs to be offset to frame coordinates.
931 if (auto svgRoot = ancestorsOfType<LegacyRenderSVGRoot>(*m_renderer).first()) {
932 LayoutPoint parentOffset = axObjectCache()->getOrCreate(&*svgRoot)->elementRect().location();
933 path.transform(AffineTransform().translate(parentOffset.x(), parentOffset.y()));
936 return path;
939 #if ENABLE(LAYER_BASED_SVG_ENGINE)
940 if (is<RenderSVGShape>(renderer()) && downcast<RenderSVGShape>(*m_renderer).hasPath()) {
941 Path path = downcast<RenderSVGShape>(*m_renderer).path();
943 // The SVG path is in terms of the parent's bounding box. The path needs to be offset to frame coordinates.
944 if (auto svgRoot = ancestorsOfType<RenderSVGRoot>(*m_renderer).first()) {
945 LayoutPoint parentOffset = axObjectCache()->getOrCreate(&*svgRoot)->elementRect().location();
946 path.transform(AffineTransform().translate(parentOffset.x(), parentOffset.y()));
949 return path;
951 #endif
953 return Path();
956 IntPoint AccessibilityRenderObject::linkClickPoint()
958 ASSERT(isLink());
959 /* A link bounding rect can contain points that are not part of the link.
960 For instance, a link that starts at the end of a line and finishes at the
961 beginning of the next line will have a bounding rect that includes the
962 entire two lines. In such a case, the middle point of the bounding rect
963 may not belong to the link element and thus may not activate the link.
964 Hence, return the middle point of the first character in the link if exists.
966 if (auto range = elementRange()) {
967 auto start = VisiblePosition { makeContainerOffsetPosition(range->start) };
968 auto end = nextVisiblePosition(start);
969 if (contains<ComposedTree>(*range, makeBoundaryPoint(end)))
970 return { boundsForRange(*makeSimpleRange(start, end)).center() };
972 return AccessibilityObject::clickPoint();
975 IntPoint AccessibilityRenderObject::clickPoint()
977 // Headings are usually much wider than their textual content. If the mid point is used, often it can be wrong.
978 if (isHeading() && children().size() == 1)
979 return children().first()->clickPoint();
981 if (isLink())
982 return linkClickPoint();
984 // use the default position unless this is an editable web area, in which case we use the selection bounds.
985 if (!isWebArea() || !canSetValueAttribute())
986 return AccessibilityObject::clickPoint();
988 return boundsForVisiblePositionRange(selection()).center();
991 AccessibilityObject* AccessibilityRenderObject::internalLinkElement() const
993 auto element = anchorElement();
995 // Right now, we do not support ARIA links as internal link elements
996 if (!is<HTMLAnchorElement>(element))
997 return nullptr;
998 auto& anchor = downcast<HTMLAnchorElement>(*element);
1000 auto linkURL = anchor.href();
1001 auto fragmentIdentifier = linkURL.fragmentIdentifier();
1002 if (fragmentIdentifier.isEmpty())
1003 return nullptr;
1005 // check if URL is the same as current URL
1006 auto documentURL = m_renderer->document().url();
1007 if (!equalIgnoringFragmentIdentifier(documentURL, linkURL))
1008 return nullptr;
1010 auto linkedNode = m_renderer->document().findAnchor(fragmentIdentifier);
1011 if (!linkedNode)
1012 return nullptr;
1014 // The element we find may not be accessible, so find the first accessible object.
1015 return firstAccessibleObjectFromNode(linkedNode);
1018 OptionSet<SpeakAs> AccessibilityRenderObject::speakAsProperty() const
1020 if (!m_renderer)
1021 return AccessibilityObject::speakAsProperty();
1023 return m_renderer->style().speakAs();
1026 void AccessibilityRenderObject::addRadioButtonGroupChildren(AXCoreObject* parent, AccessibilityChildrenVector& linkedUIElements) const
1028 for (const auto& child : parent->children()) {
1029 if (child->roleValue() == AccessibilityRole::RadioButton)
1030 linkedUIElements.append(child);
1031 else
1032 addRadioButtonGroupChildren(child.get(), linkedUIElements);
1036 void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const
1038 if (roleValue() != AccessibilityRole::RadioButton)
1039 return;
1041 Node* node = this->node();
1042 if (is<HTMLInputElement>(node)) {
1043 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
1044 for (auto& radioSibling : input.radioButtonGroup()) {
1045 if (AccessibilityObject* object = axObjectCache()->getOrCreate(radioSibling.ptr()))
1046 linkedUIElements.append(object);
1048 } else {
1049 // If we didn't find any radio button siblings with the traditional naming, lets search for a radio group role and find its children.
1050 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
1051 if (parent->roleValue() == AccessibilityRole::RadioGroup)
1052 addRadioButtonGroupChildren(parent, linkedUIElements);
1057 // linked ui elements could be all the related radio buttons in a group
1058 // or an internal anchor connection
1059 void AccessibilityRenderObject::linkedUIElements(AccessibilityChildrenVector& linkedUIElements) const
1061 ariaFlowToElements(linkedUIElements);
1063 if (isLink()) {
1064 AccessibilityObject* linkedAXElement = internalLinkElement();
1065 if (linkedAXElement)
1066 linkedUIElements.append(linkedAXElement);
1069 if (roleValue() == AccessibilityRole::RadioButton)
1070 addRadioButtonGroupMembers(linkedUIElements);
1073 bool AccessibilityRenderObject::hasPopup() const
1075 // Return true if this has the aria-haspopup attribute, or if it has an ancestor of type link with the aria-haspopup attribute.
1076 return Accessibility::findAncestor<AccessibilityObject>(*this, true, [this] (const AccessibilityObject& object) {
1077 return (this == &object) ? !equalLettersIgnoringASCIICase(object.popupValue(), "false")
1078 : object.isLink() && !equalLettersIgnoringASCIICase(object.popupValue(), "false");
1082 bool AccessibilityRenderObject::supportsDropping() const
1084 return determineDropEffects().size();
1087 bool AccessibilityRenderObject::supportsDragging() const
1089 const AtomString& grabbed = getAttribute(aria_grabbedAttr);
1090 return equalLettersIgnoringASCIICase(grabbed, "true") || equalLettersIgnoringASCIICase(grabbed, "false") || hasAttribute(draggableAttr);
1093 bool AccessibilityRenderObject::isGrabbed()
1095 #if ENABLE(DRAG_SUPPORT)
1096 if (mainFrame() && mainFrame()->eventHandler().draggingElement() == element())
1097 return true;
1098 #endif
1100 return elementAttributeValue(aria_grabbedAttr);
1103 Vector<String> AccessibilityRenderObject::determineDropEffects() const
1105 // Order is aria-dropeffect, dropzone, webkitdropzone
1106 const AtomString& dropEffects = getAttribute(aria_dropeffectAttr);
1107 if (!dropEffects.isEmpty()) {
1108 String dropEffectsString = dropEffects.string();
1109 dropEffectsString.replace('\n', ' ');
1110 return dropEffectsString.split(' ');
1113 auto dropzone = getAttribute(dropzoneAttr);
1114 if (!dropzone.isEmpty())
1115 return Vector<String> { dropzone };
1117 auto webkitdropzone = getAttribute(webkitdropzoneAttr);
1118 if (!webkitdropzone.isEmpty())
1119 return Vector<String> { webkitdropzone };
1121 return { };
1124 #if ENABLE(APPLE_PAY)
1125 String AccessibilityRenderObject::applePayButtonDescription() const
1127 switch (applePayButtonType()) {
1128 case ApplePayButtonType::Plain:
1129 return AXApplePayPlainLabel();
1130 case ApplePayButtonType::Buy:
1131 return AXApplePayBuyLabel();
1132 case ApplePayButtonType::SetUp:
1133 return AXApplePaySetupLabel();
1134 case ApplePayButtonType::Donate:
1135 return AXApplePayDonateLabel();
1136 case ApplePayButtonType::CheckOut:
1137 return AXApplePayCheckOutLabel();
1138 case ApplePayButtonType::Book:
1139 return AXApplePayBookLabel();
1140 case ApplePayButtonType::Subscribe:
1141 return AXApplePaySubscribeLabel();
1142 #if ENABLE(APPLE_PAY_NEW_BUTTON_TYPES)
1143 case ApplePayButtonType::Reload:
1144 return AXApplePayReloadLabel();
1145 case ApplePayButtonType::AddMoney:
1146 return AXApplePayAddMoneyLabel();
1147 case ApplePayButtonType::TopUp:
1148 return AXApplePayTopUpLabel();
1149 case ApplePayButtonType::Order:
1150 return AXApplePayOrderLabel();
1151 case ApplePayButtonType::Rent:
1152 return AXApplePayRentLabel();
1153 case ApplePayButtonType::Support:
1154 return AXApplePaySupportLabel();
1155 case ApplePayButtonType::Contribute:
1156 return AXApplePayContributeLabel();
1157 case ApplePayButtonType::Tip:
1158 return AXApplePayTipLabel();
1159 #endif
1162 #endif
1164 void AccessibilityRenderObject::titleElementText(Vector<AccessibilityText>& textOrder) const
1166 #if ENABLE(APPLE_PAY)
1167 if (isApplePayButton()) {
1168 textOrder.append(AccessibilityText(applePayButtonDescription(), AccessibilityTextSource::Alternative));
1169 return;
1171 #endif
1173 AccessibilityNodeObject::titleElementText(textOrder);
1176 AccessibilityObject* AccessibilityRenderObject::titleUIElement() const
1178 if (!m_renderer || !exposesTitleUIElement())
1179 return nullptr;
1181 // if isFieldset is true, the renderer is guaranteed to be a RenderFieldset
1182 if (isFieldset())
1183 return axObjectCache()->getOrCreate(downcast<RenderBlock>(*m_renderer).findFieldsetLegend(RenderBlock::FieldsetIncludeFloatingOrOutOfFlow));
1185 if (isFigureElement())
1186 return captionForFigure();
1188 Node* node = m_renderer->node();
1189 if (!is<Element>(node))
1190 return nullptr;
1191 HTMLLabelElement* label = labelForElement(downcast<Element>(node));
1192 if (label && label->renderer())
1193 return axObjectCache()->getOrCreate(label);
1195 return nullptr;
1198 bool AccessibilityRenderObject::isAllowedChildOfTree() const
1200 // Determine if this is in a tree. If so, we apply special behavior to make it work like an AXOutline.
1201 AccessibilityObject* axObj = parentObject();
1202 bool isInTree = false;
1203 bool isTreeItemDescendant = false;
1204 while (axObj) {
1205 if (axObj->roleValue() == AccessibilityRole::TreeItem)
1206 isTreeItemDescendant = true;
1207 if (axObj->isTree()) {
1208 isInTree = true;
1209 break;
1211 axObj = axObj->parentObject();
1214 // If the object is in a tree, only tree items should be exposed (and the children of tree items).
1215 if (isInTree) {
1216 AccessibilityRole role = roleValue();
1217 if (role != AccessibilityRole::TreeItem && role != AccessibilityRole::StaticText && !isTreeItemDescendant)
1218 return false;
1220 return true;
1223 static AccessibilityObjectInclusion objectInclusionFromAltText(const String& altText)
1225 // Don't ignore an image that has an alt tag.
1226 if (!altText.isAllSpecialCharacters<isHTMLSpace>())
1227 return AccessibilityObjectInclusion::IncludeObject;
1229 // The informal standard is to ignore images with zero-length alt strings:
1230 // https://www.w3.org/WAI/tutorials/images/decorative/.
1231 if (!altText.isNull())
1232 return AccessibilityObjectInclusion::IgnoreObject;
1234 return AccessibilityObjectInclusion::DefaultBehavior;
1237 AccessibilityObjectInclusion AccessibilityRenderObject::defaultObjectInclusion() const
1239 // The following cases can apply to any element that's a subclass of AccessibilityRenderObject.
1241 if (!m_renderer)
1242 return AccessibilityObjectInclusion::IgnoreObject;
1244 if (m_renderer->style().visibility() != Visibility::Visible) {
1245 // aria-hidden is meant to override visibility as the determinant in AX hierarchy inclusion.
1246 if (equalLettersIgnoringASCIICase(getAttribute(aria_hiddenAttr), "false"))
1247 return AccessibilityObjectInclusion::DefaultBehavior;
1249 return AccessibilityObjectInclusion::IgnoreObject;
1252 return AccessibilityObject::defaultObjectInclusion();
1255 static bool webAreaIsPresentational(RenderObject* renderer)
1257 if (!renderer || !is<RenderView>(*renderer))
1258 return false;
1260 if (auto ownerElement = renderer->document().ownerElement())
1261 return nodeHasPresentationRole(ownerElement);
1263 return false;
1266 bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const
1268 #ifndef NDEBUG
1269 ASSERT(m_initialized);
1270 #endif
1272 if (!m_renderer)
1273 return true;
1275 // Check first if any of the common reasons cause this element to be ignored.
1276 // Then process other use cases that need to be applied to all the various roles
1277 // that AccessibilityRenderObjects take on.
1278 AccessibilityObjectInclusion decision = defaultObjectInclusion();
1279 if (decision == AccessibilityObjectInclusion::IncludeObject)
1280 return false;
1281 if (decision == AccessibilityObjectInclusion::IgnoreObject)
1282 return true;
1284 // If this element is within a parent that cannot have children, it should not be exposed.
1285 if (isDescendantOfBarrenParent())
1286 return true;
1288 if (roleValue() == AccessibilityRole::Ignored)
1289 return true;
1291 if (roleValue() == AccessibilityRole::Presentational || inheritsPresentationalRole())
1292 return true;
1294 // WebAreas should be ignored if their iframe container is marked as presentational.
1295 if (webAreaIsPresentational(renderer()))
1296 return true;
1298 // An ARIA tree can only have tree items and static text as children.
1299 if (!isAllowedChildOfTree())
1300 return true;
1302 // Allow the platform to decide if the attachment is ignored or not.
1303 if (isAttachment())
1304 return accessibilityIgnoreAttachment();
1306 #if PLATFORM(COCOA)
1307 // If this widget has an underlying AX object, don't ignore it.
1308 if (widget() && widget()->accessibilityObject())
1309 return false;
1310 #endif
1312 // ignore popup menu items because AppKit does
1313 if (m_renderer && ancestorsOfType<RenderMenuList>(*m_renderer).first())
1314 return true;
1316 // https://webkit.org/b/161276 Getting the controlObject might cause the m_renderer to be nullptr.
1317 if (!m_renderer)
1318 return true;
1320 if (m_renderer->isBR())
1321 return true;
1323 if (is<RenderText>(*m_renderer)) {
1324 // static text beneath MenuItems and MenuButtons are just reported along with the menu item, so it's ignored on an individual level
1325 AXCoreObject* parent = parentObjectUnignored();
1327 // Walking up the parent chain might reset the m_renderer.
1328 if (!m_renderer)
1329 return true;
1331 if (parent && (parent->isMenuItem() || parent->ariaRoleAttribute() == AccessibilityRole::MenuButton))
1332 return true;
1333 auto& renderText = downcast<RenderText>(*m_renderer);
1334 if (!renderText.hasRenderedText())
1335 return true;
1337 if (renderText.parent()->isFirstLetter())
1338 return true;
1340 // static text beneath TextControls is reported along with the text control text so it's ignored.
1341 for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
1342 if (parent->roleValue() == AccessibilityRole::TextField)
1343 return true;
1346 // Walking up the parent chain might reset the m_renderer.
1347 if (!m_renderer)
1348 return true;
1350 // The alt attribute may be set on a text fragment through CSS, which should be honored.
1351 if (is<RenderTextFragment>(renderText)) {
1352 AccessibilityObjectInclusion altTextInclusion = objectInclusionFromAltText(downcast<RenderTextFragment>(renderText).altText());
1353 if (altTextInclusion == AccessibilityObjectInclusion::IgnoreObject)
1354 return true;
1355 if (altTextInclusion == AccessibilityObjectInclusion::IncludeObject)
1356 return false;
1359 // text elements that are just empty whitespace should not be returned
1360 return renderText.text().isAllSpecialCharacters<isHTMLSpace>();
1363 if (isHeading())
1364 return false;
1366 if (isLink())
1367 return false;
1369 if (isLandmark())
1370 return false;
1372 // all controls are accessible
1373 if (isControl())
1374 return false;
1376 if (isFigureElement())
1377 return false;
1379 switch (roleValue()) {
1380 case AccessibilityRole::Audio:
1381 case AccessibilityRole::DescriptionListTerm:
1382 case AccessibilityRole::DescriptionListDetail:
1383 case AccessibilityRole::Details:
1384 case AccessibilityRole::DocumentArticle:
1385 case AccessibilityRole::Footer:
1386 case AccessibilityRole::LandmarkRegion:
1387 case AccessibilityRole::ListItem:
1388 case AccessibilityRole::Time:
1389 case AccessibilityRole::Video:
1390 return false;
1391 default:
1392 break;
1395 if (isImage()) {
1396 // If the image can take focus, it should not be ignored, lest the user not be able to interact with something important.
1397 if (canSetFocusAttribute())
1398 return false;
1400 // webkit.org/b/173870 - If an image has other alternative text, don't ignore it if alt text is empty.
1401 // This means we should process title and aria-label first.
1403 // If an image has the title or label attributes, accessibility should be lenient and allow it to appear in the hierarchy (according to WAI-ARIA).
1404 if (!getAttribute(titleAttr).isEmpty() || !getAttribute(aria_labelAttr).isEmpty())
1405 return false;
1407 // First check the RenderImage's altText (which can be set through a style sheet, or come from the Element).
1408 // However, if this is not a native image, fallback to the attribute on the Element.
1409 AccessibilityObjectInclusion altTextInclusion = AccessibilityObjectInclusion::DefaultBehavior;
1410 bool isRenderImage = is<RenderImage>(renderer());
1411 if (isRenderImage)
1412 altTextInclusion = objectInclusionFromAltText(downcast<RenderImage>(*m_renderer).altText());
1413 else
1414 altTextInclusion = objectInclusionFromAltText(getAttribute(altAttr).string());
1416 if (altTextInclusion == AccessibilityObjectInclusion::IgnoreObject)
1417 return true;
1418 if (altTextInclusion == AccessibilityObjectInclusion::IncludeObject)
1419 return false;
1421 if (isRenderImage) {
1422 // check for one-dimensional image
1423 RenderImage& image = downcast<RenderImage>(*m_renderer);
1424 if (image.height() <= 1 || image.width() <= 1)
1425 return true;
1427 // check whether rendered image was stretched from one-dimensional file image
1428 if (image.cachedImage()) {
1429 LayoutSize imageSize = image.cachedImage()->imageSizeForRenderer(&image, image.view().zoomFactor());
1430 return imageSize.height() <= 1 || imageSize.width() <= 1;
1433 return false;
1436 if (ariaRoleAttribute() != AccessibilityRole::Unknown)
1437 return false;
1439 if (roleValue() == AccessibilityRole::HorizontalRule)
1440 return false;
1442 // don't ignore labels, because they serve as TitleUIElements
1443 Node* node = m_renderer->node();
1444 if (is<HTMLLabelElement>(node))
1445 return false;
1447 // Anything that is content editable should not be ignored.
1448 // However, one cannot just call node->hasEditableStyle() since that will ask if its parents
1449 // are also editable. Only the top level content editable region should be exposed.
1450 if (hasContentEditableAttributeSet())
1451 return false;
1453 // if this element has aria attributes on it, it should not be ignored.
1454 if (supportsARIAAttributes())
1455 return false;
1457 #if ENABLE(MATHML)
1458 // First check if this is a special case within the math tree that needs to be ignored.
1459 if (isIgnoredElementWithinMathTree())
1460 return true;
1461 // Otherwise all other math elements are in the tree.
1462 if (isMathElement())
1463 return false;
1464 #endif
1466 if (is<RenderBlockFlow>(*m_renderer) && m_renderer->childrenInline() && !canSetFocusAttribute())
1467 return !downcast<RenderBlockFlow>(*m_renderer).hasLines() && !mouseButtonListener();
1469 if (isCanvas()) {
1470 if (canvasHasFallbackContent())
1471 return false;
1473 if (is<RenderBox>(*m_renderer)) {
1474 auto& canvasBox = downcast<RenderBox>(*m_renderer);
1475 if (canvasBox.height() <= 1 || canvasBox.width() <= 1)
1476 return true;
1478 // Otherwise fall through; use presence of help text, title, or description to decide.
1481 if (m_renderer->isListMarker()) {
1482 AXCoreObject* parent = parentObjectUnignored();
1483 return parent && !parent->isListItem();
1486 if (isWebArea())
1487 return false;
1489 // The render tree of meter includes a RenderBlock (meter) and a RenderMeter (div).
1490 // We expose the latter and thus should ignore the former. However, if the author
1491 // includes a title attribute on the element, hasAttributesRequiredForInclusion()
1492 // will return true, potentially resulting in a redundant accessible object.
1493 if (is<HTMLMeterElement>(node))
1494 return true;
1496 // Using the presence of an accessible name to decide an element's visibility is not
1497 // as definitive as previous checks, so this should remain as one of the last.
1498 if (hasAttributesRequiredForInclusion())
1499 return false;
1501 // Don't ignore generic focusable elements like <div tabindex=0>
1502 // unless they're completely empty, with no children.
1503 if (isGenericFocusableElement() && node->firstChild())
1504 return false;
1506 // <span> tags are inline tags and not meant to convey information if they have no other aria
1507 // information on them. If we don't ignore them, they may emit signals expected to come from
1508 // their parent. In addition, because included spans are AccessibilityRole::Group objects, and AccessibilityRole::Group
1509 // objects are often containers with meaningful information, the inclusion of a span can have
1510 // the side effect of causing the immediate parent accessible to be ignored. This is especially
1511 // problematic for platforms which have distinct roles for textual block elements.
1512 if (node && node->hasTagName(spanTag))
1513 return true;
1515 // Other non-ignored host language elements
1516 if (node && node->hasTagName(dfnTag))
1517 return false;
1519 if (isStyleFormatGroup())
1520 return false;
1522 // Make sure that ruby containers are not ignored.
1523 if (m_renderer->isRubyRun() || m_renderer->isRubyBlock() || m_renderer->isRubyInline())
1524 return false;
1526 // Find out if this element is inside of a label element.
1527 // If so, it may be ignored because it's the label for a checkbox or radio button.
1528 auto* controlObject = correspondingControlForLabelElement();
1529 if (controlObject && controlObject->isCheckboxOrRadio() && !controlObject->titleUIElement())
1530 return true;
1532 // By default, objects should be ignored so that the AX hierarchy is not
1533 // filled with unnecessary items.
1534 return true;
1537 bool AccessibilityRenderObject::isLoaded() const
1539 return m_renderer ? !m_renderer->document().parser() : false;
1542 double AccessibilityRenderObject::loadingProgress() const
1544 if (!m_renderer)
1545 return 0;
1547 if (isLoaded())
1548 return 1.0;
1550 return m_renderer->page().progress().estimatedProgress();
1553 int AccessibilityRenderObject::layoutCount() const
1555 if (!m_renderer || !is<RenderView>(*m_renderer))
1556 return 0;
1557 return downcast<RenderView>(*m_renderer).frameView().layoutContext().layoutCount();
1560 String AccessibilityRenderObject::text() const
1562 if (isPasswordField())
1563 return passwordFieldValue();
1565 return AccessibilityNodeObject::text();
1568 int AccessibilityRenderObject::textLength() const
1570 ASSERT(isTextControl());
1572 if (isPasswordField())
1573 return passwordFieldValue().length();
1575 return text().length();
1578 PlainTextRange AccessibilityRenderObject::documentBasedSelectedTextRange() const
1580 auto selectedVisiblePositionRange = this->selectedVisiblePositionRange();
1581 if (selectedVisiblePositionRange.isNull())
1582 return { };
1584 int start = indexForVisiblePosition(selectedVisiblePositionRange.start);
1585 int end = indexForVisiblePosition(selectedVisiblePositionRange.end);
1586 return PlainTextRange(start, end - start);
1589 String AccessibilityRenderObject::selectedText() const
1591 ASSERT(isTextControl());
1593 if (isPasswordField())
1594 return String(); // need to return something distinct from empty string
1596 if (isNativeTextControl()) {
1597 HTMLTextFormControlElement& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement();
1598 return textControl.selectedText();
1601 return doAXStringForRange(documentBasedSelectedTextRange());
1604 String AccessibilityRenderObject::accessKey() const
1606 if (!m_renderer)
1607 return String();
1609 Node* node = m_renderer->node();
1610 if (!is<Element>(node))
1611 return String();
1613 return downcast<Element>(*node).attributeWithoutSynchronization(accesskeyAttr);
1616 VisibleSelection AccessibilityRenderObject::selection() const
1618 return m_renderer ? m_renderer->frame().selection().selection() : VisibleSelection();
1621 PlainTextRange AccessibilityRenderObject::selectedTextRange() const
1623 ASSERT(isTextControl());
1625 if (isPasswordField())
1626 return PlainTextRange();
1628 // Use the text control native range if it's a native object.
1629 if (isNativeTextControl()) {
1630 auto& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement();
1631 return PlainTextRange(textControl.selectionStart(), textControl.selectionEnd() - textControl.selectionStart());
1634 return documentBasedSelectedTextRange();
1637 int AccessibilityRenderObject::insertionPointLineNumber() const
1639 ASSERT(isTextControl());
1641 // Use the text control native range if it's a native object.
1642 if (isNativeTextControl()) {
1643 auto& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement();
1644 int start = textControl.selectionStart();
1645 int end = textControl.selectionEnd();
1647 // If the selection range is not a collapsed range, we don't know whether the insertion point is the start or the end, thus return -1.
1648 // FIXME: for non-collapsed selection, determine the insertion point based on the TextFieldSelectionDirection.
1649 if (start != end)
1650 return -1;
1652 return lineForPosition(textControl.visiblePositionForIndex(start));
1655 auto* frame = this->frame();
1656 if (!frame)
1657 return -1;
1659 auto selectedTextRange = frame->selection().selection().firstRange();
1660 // If the selection range is not a collapsed range, we don't know whether the insertion point is the start or the end, thus return -1.
1661 if (!selectedTextRange || !selectedTextRange->collapsed())
1662 return -1;
1664 return lineForPosition(makeDeprecatedLegacyPosition(selectedTextRange->start));
1667 static void setTextSelectionIntent(AXObjectCache* cache, AXTextStateChangeType type)
1669 if (!cache)
1670 return;
1671 AXTextStateChangeIntent intent(type, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown, false });
1672 cache->setTextSelectionIntent(intent);
1673 cache->setIsSynchronizingSelection(true);
1676 static void clearTextSelectionIntent(AXObjectCache* cache)
1678 if (!cache)
1679 return;
1680 cache->setTextSelectionIntent(AXTextStateChangeIntent());
1681 cache->setIsSynchronizingSelection(false);
1684 void AccessibilityRenderObject::setSelectedTextRange(const PlainTextRange& range)
1686 setTextSelectionIntent(axObjectCache(), range.length ? AXTextStateChangeTypeSelectionExtend : AXTextStateChangeTypeSelectionMove);
1688 auto* client = m_renderer ? m_renderer->document().editor().client() : nullptr;
1689 if (client)
1690 client->willChangeSelectionForAccessibility();
1692 if (isNativeTextControl()) {
1693 HTMLTextFormControlElement& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement();
1694 textControl.setSelectionRange(range.start, range.start + range.length);
1695 } else if (m_renderer) {
1696 ASSERT(node());
1697 auto& node = *this->node();
1698 auto elementRange = this->elementRange();
1699 auto start = visiblePositionForIndexUsingCharacterIterator(node, range.start);
1700 if (!contains<ComposedTree>(*elementRange, makeBoundaryPoint(start)))
1701 start = makeContainerOffsetPosition(elementRange->start);
1702 auto end = visiblePositionForIndexUsingCharacterIterator(node, range.start + range.length);
1703 if (!contains<ComposedTree>(*elementRange, makeBoundaryPoint(end)))
1704 end = makeContainerOffsetPosition(elementRange->start);
1705 m_renderer->frame().selection().setSelection(VisibleSelection(start, end), FrameSelection::defaultSetSelectionOptions(UserTriggered));
1708 clearTextSelectionIntent(axObjectCache());
1710 if (client)
1711 client->didChangeSelectionForAccessibility();
1714 URL AccessibilityRenderObject::url() const
1716 auto* node = this->node();
1717 if (isLink() && is<HTMLAnchorElement>(node)) {
1718 if (HTMLAnchorElement* anchor = downcast<HTMLAnchorElement>(anchorElement()))
1719 return anchor->href();
1722 if (m_renderer && isWebArea())
1723 return m_renderer->document().url();
1725 if (isImage() && is<HTMLImageElement>(node))
1726 return downcast<HTMLImageElement>(node)->src();
1728 if (isInputImage() && is<HTMLInputElement>(node))
1729 return downcast<HTMLInputElement>(node)->src();
1731 #if ENABLE(VIDEO)
1732 if (isVideo() && is<HTMLVideoElement>(node))
1733 return downcast<HTMLVideoElement>(node)->currentSrc();
1734 #endif
1736 return URL();
1739 bool AccessibilityRenderObject::isUnvisited() const
1741 if (!m_renderer)
1742 return true;
1744 // FIXME: Is it a privacy violation to expose unvisited information to accessibility APIs?
1745 return m_renderer->style().isLink() && m_renderer->style().insideLink() == InsideLink::InsideUnvisited;
1748 bool AccessibilityRenderObject::isVisited() const
1750 if (!m_renderer)
1751 return false;
1753 // FIXME: Is it a privacy violation to expose visited information to accessibility APIs?
1754 return m_renderer->style().isLink() && m_renderer->style().insideLink() == InsideLink::InsideVisited;
1757 void AccessibilityRenderObject::setElementAttributeValue(const QualifiedName& attributeName, bool value)
1759 if (!m_renderer)
1760 return;
1762 Node* node = m_renderer->node();
1763 if (!is<Element>(node))
1764 return;
1766 downcast<Element>(*node).setAttribute(attributeName, (value) ? "true" : "false");
1769 bool AccessibilityRenderObject::elementAttributeValue(const QualifiedName& attributeName) const
1771 if (!m_renderer)
1772 return false;
1774 return equalLettersIgnoringASCIICase(getAttribute(attributeName), "true");
1777 bool AccessibilityRenderObject::isSelected() const
1779 if (!m_renderer)
1780 return false;
1782 if (!m_renderer->node())
1783 return false;
1785 if (equalLettersIgnoringASCIICase(getAttribute(aria_selectedAttr), "true"))
1786 return true;
1788 if (isTabItem() && isTabItemSelected())
1789 return true;
1791 // Menu items are considered selectable by assistive technologies
1792 if (isMenuItem())
1793 return isFocused() || parentObjectUnignored()->activeDescendant() == this;
1795 return false;
1798 bool AccessibilityRenderObject::isTabItemSelected() const
1800 if (!isTabItem() || !m_renderer)
1801 return false;
1803 Node* node = m_renderer->node();
1804 if (!node || !node->isElementNode())
1805 return false;
1807 // The ARIA spec says a tab item can also be selected if it is aria-labeled by a tabpanel
1808 // that has keyboard focus inside of it, or if a tabpanel in its aria-controls list has KB
1809 // focus inside of it.
1810 AccessibilityObject* focusedElement = static_cast<AccessibilityObject*>(focusedUIElement());
1811 if (!focusedElement)
1812 return false;
1814 Vector<Element*> elements;
1815 elementsFromAttribute(elements, aria_controlsAttr);
1817 AXObjectCache* cache = axObjectCache();
1818 if (!cache)
1819 return false;
1821 for (const auto& element : elements) {
1822 AccessibilityObject* tabPanel = cache->getOrCreate(element);
1824 // A tab item should only control tab panels.
1825 if (!tabPanel || tabPanel->roleValue() != AccessibilityRole::TabPanel)
1826 continue;
1828 AccessibilityObject* checkFocusElement = focusedElement;
1829 // Check if the focused element is a descendant of the element controlled by the tab item.
1830 while (checkFocusElement) {
1831 if (tabPanel == checkFocusElement)
1832 return true;
1833 checkFocusElement = checkFocusElement->parentObject();
1837 return false;
1840 bool AccessibilityRenderObject::isFocused() const
1842 if (!m_renderer)
1843 return false;
1845 Document& document = m_renderer->document();
1847 Element* focusedElement = document.focusedElement();
1848 if (!focusedElement)
1849 return false;
1851 // A web area is represented by the Document node in the DOM tree, which isn't focusable.
1852 // Check instead if the frame's selection controller is focused
1853 if (focusedElement == m_renderer->node()
1854 || (roleValue() == AccessibilityRole::WebArea && document.frame()->selection().isFocusedAndActive()))
1855 return true;
1857 return false;
1860 void AccessibilityRenderObject::setFocused(bool on)
1862 // Call the base class setFocused to ensure the view is focused and active.
1863 AccessibilityObject::setFocused(on);
1865 if (!canSetFocusAttribute())
1866 return;
1868 Document* document = this->document();
1869 Node* node = this->node();
1871 if (!on || !is<Element>(node)) {
1872 document->setFocusedElement(nullptr);
1873 return;
1876 // When a node is told to set focus, that can cause it to be deallocated, which means that doing
1877 // anything else inside this object will crash. To fix this, we added a RefPtr to protect this object
1878 // long enough for duration.
1879 RefPtr<AccessibilityObject> protectedThis(this);
1881 // If this node is already the currently focused node, then calling focus() won't do anything.
1882 // That is a problem when focus is removed from the webpage to chrome, and then returns.
1883 // In these cases, we need to do what keyboard and mouse focus do, which is reset focus first.
1884 if (document->focusedElement() == node)
1885 document->setFocusedElement(nullptr);
1887 // If we return from setFocusedElement and our element has been removed from a tree, axObjectCache() may be null.
1888 if (AXObjectCache* cache = axObjectCache()) {
1889 cache->setIsSynchronizingSelection(true);
1890 downcast<Element>(*node).focus();
1891 cache->setIsSynchronizingSelection(false);
1895 void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& selectedRows)
1897 // Setting selected only makes sense in trees and tables (and tree-tables).
1898 AccessibilityRole role = roleValue();
1899 if (role != AccessibilityRole::Tree && role != AccessibilityRole::TreeGrid && role != AccessibilityRole::Table && role != AccessibilityRole::Grid)
1900 return;
1902 bool isMulti = isMultiSelectable();
1903 unsigned count = selectedRows.size();
1904 if (count > 1 && !isMulti)
1905 count = 1;
1907 for (const auto& selectedRow : selectedRows)
1908 selectedRow->setSelected(true);
1911 bool AccessibilityRenderObject::setValue(const String& string)
1913 if (!m_renderer || !is<Element>(m_renderer->node()))
1914 return false;
1916 Element& element = downcast<Element>(*m_renderer->node());
1917 RenderObject& renderer = *m_renderer;
1919 // We should use the editor's insertText to mimic typing into the field.
1920 // Also only do this when the field is in editing mode.
1921 if (Frame* frame = renderer.document().frame()) {
1922 Editor& editor = frame->editor();
1923 if (element.shouldUseInputMethod()) {
1924 editor.clearText();
1925 editor.insertText(string, nullptr);
1926 return true;
1929 // FIXME: Do we want to do anything here for ARIA textboxes?
1930 if (renderer.isTextField() && is<HTMLInputElement>(element)) {
1931 downcast<HTMLInputElement>(element).setValue(string);
1932 return true;
1934 if (renderer.isTextArea() && is<HTMLTextAreaElement>(element)) {
1935 downcast<HTMLTextAreaElement>(element).setValue(string);
1936 return true;
1939 return false;
1942 bool AccessibilityRenderObject::supportsARIAOwns() const
1944 if (!m_renderer)
1945 return false;
1946 const AtomString& ariaOwns = getAttribute(aria_ownsAttr);
1948 return !ariaOwns.isEmpty();
1951 RenderView* AccessibilityRenderObject::topRenderer() const
1953 Document* topDoc = topDocument();
1954 if (!topDoc)
1955 return nullptr;
1957 return topDoc->renderView();
1960 Document* AccessibilityRenderObject::document() const
1962 if (!m_renderer)
1963 return nullptr;
1964 return &m_renderer->document();
1967 bool AccessibilityRenderObject::isWidget() const
1969 return widget();
1972 Widget* AccessibilityRenderObject::widget() const
1974 return is<RenderWidget>(m_renderer.get()) ? downcast<RenderWidget>(*m_renderer).widget() : nullptr;
1977 AccessibilityObject* AccessibilityRenderObject::accessibilityParentForImageMap(HTMLMapElement* map) const
1979 // find an image that is using this map
1980 if (!map)
1981 return nullptr;
1983 HTMLImageElement* imageElement = map->imageElement();
1984 if (!imageElement)
1985 return nullptr;
1987 if (AXObjectCache* cache = axObjectCache())
1988 return cache->getOrCreate(imageElement);
1990 return nullptr;
1993 AXCoreObject::AccessibilityChildrenVector AccessibilityRenderObject::documentLinks()
1995 if (!m_renderer)
1996 return { };
1998 AccessibilityChildrenVector result;
1999 Document& document = m_renderer->document();
2000 Ref<HTMLCollection> links = document.links();
2001 for (unsigned i = 0; auto* current = links->item(i); ++i) {
2002 if (auto* renderer = current->renderer()) {
2003 RefPtr<AccessibilityObject> axObject = document.axObjectCache()->getOrCreate(renderer);
2004 ASSERT(axObject);
2005 if (!axObject->accessibilityIsIgnored() && axObject->isLink())
2006 result.append(axObject);
2007 } else {
2008 auto* parent = current->parentNode();
2009 if (is<HTMLAreaElement>(*current) && is<HTMLMapElement>(parent)) {
2010 auto* parentImage = downcast<HTMLMapElement>(parent)->imageElement();
2011 auto* parentImageRenderer = parentImage ? parentImage->renderer() : nullptr;
2012 if (auto* parentImageAxObject = document.axObjectCache()->getOrCreate(parentImageRenderer)) {
2013 for (const auto& child : parentImageAxObject->children()) {
2014 if (is<AccessibilityImageMapLink>(child) && !result.contains(child))
2015 result.append(child);
2017 } else {
2018 // We couldn't retrieve the already existing image-map links from the parent image, so create a new one.
2019 ASSERT_NOT_REACHED("Unexpectedly missing image-map link parent AX object.");
2020 auto& areaObject = downcast<AccessibilityImageMapLink>(*axObjectCache()->create(AccessibilityRole::ImageMapLink));
2021 auto& map = downcast<HTMLMapElement>(*parent);
2022 areaObject.setHTMLAreaElement(downcast<HTMLAreaElement>(current));
2023 areaObject.setHTMLMapElement(&map);
2024 areaObject.setParent(accessibilityParentForImageMap(&map));
2025 result.append(&areaObject);
2031 return result;
2034 FrameView* AccessibilityRenderObject::documentFrameView() const
2036 if (!m_renderer)
2037 return nullptr;
2039 // this is the RenderObject's Document's Frame's FrameView
2040 return &m_renderer->view().frameView();
2043 Widget* AccessibilityRenderObject::widgetForAttachmentView() const
2045 if (!isAttachment())
2046 return nullptr;
2047 return downcast<RenderWidget>(*m_renderer).widget();
2050 // This function is like a cross-platform version of - (WebCoreTextMarkerRange*)textMarkerRange. It returns
2051 // a Range that we can convert to a WebCoreTextMarkerRange in the Obj-C file
2052 VisiblePositionRange AccessibilityRenderObject::visiblePositionRange() const
2054 if (!m_renderer)
2055 return VisiblePositionRange();
2057 Node* node = m_renderer->node();
2058 if (!node)
2059 return VisiblePositionRange();
2061 VisiblePosition startPos = firstPositionInOrBeforeNode(node);
2062 VisiblePosition endPos = lastPositionInOrAfterNode(node);
2064 // the VisiblePositions are equal for nodes like buttons, so adjust for that
2065 // FIXME: Really? [button, 0] and [button, 1] are distinct (before and after the button)
2066 // I expect this code is only hit for things like empty divs? In which case I don't think
2067 // the behavior is correct here -- eseidel
2068 if (startPos == endPos) {
2069 endPos = endPos.next();
2070 if (endPos.isNull())
2071 endPos = startPos;
2074 return { WTFMove(startPos), WTFMove(endPos) };
2077 VisiblePositionRange AccessibilityRenderObject::visiblePositionRangeForLine(unsigned lineCount) const
2079 if (!lineCount || !m_renderer)
2080 return VisiblePositionRange();
2082 // iterate over the lines
2083 // FIXME: This is wrong when lineNumber is lineCount+1, because nextLinePosition takes you to the last offset of the last line.
2084 VisiblePosition position = m_renderer->view().positionForPoint(IntPoint(), nullptr);
2085 while (--lineCount) {
2086 auto previousLinePosition = position;
2087 position = nextLinePosition(position, 0);
2088 if (position.isNull() || position == previousLinePosition)
2089 return VisiblePositionRange();
2092 // make a caret selection for the marker position, then extend it to the line
2093 // NOTE: Ignores results of sel.modify because it returns false when starting at an empty line.
2094 // The resulting selection in that case will be a caret at position.
2095 FrameSelection selection;
2096 selection.setSelection(position);
2097 selection.modify(FrameSelection::AlterationExtend, SelectionDirection::Right, TextGranularity::LineBoundary);
2098 return selection.selection();
2101 VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(int index) const
2103 if (!m_renderer)
2104 return VisiblePosition();
2106 if (isNativeTextControl())
2107 return downcast<RenderTextControl>(*m_renderer).textFormControlElement().visiblePositionForIndex(index);
2109 if (!allowsTextRanges() && !is<RenderText>(*m_renderer))
2110 return VisiblePosition();
2112 Node* node = m_renderer->node();
2113 if (!node)
2114 return VisiblePosition();
2116 #if USE(ATSPI)
2117 // We need to consider replaced elements for GTK, as they will be presented with the 'object replacement character' (0xFFFC).
2118 return WebCore::visiblePositionForIndex(index, node, TextIteratorBehavior::EmitsObjectReplacementCharacters);
2119 #else
2120 return visiblePositionForIndexUsingCharacterIterator(*node, index);
2121 #endif
2124 int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& position) const
2126 if (!m_renderer)
2127 return 0;
2129 if (isNativeTextControl())
2130 return downcast<RenderTextControl>(*m_renderer).textFormControlElement().indexForVisiblePosition(position);
2132 if (!allowsTextRanges() && !is<RenderText>(*m_renderer))
2133 return 0;
2135 Node* node = m_renderer->node();
2136 if (!node)
2137 return 0;
2139 // We need to consider replaced elements for GTK, as they will be
2140 // presented with the 'object replacement character' (0xFFFC).
2141 TextIteratorBehaviors behaviors;
2142 #if USE(ATSPI)
2143 behaviors.add(TextIteratorBehavior::EmitsObjectReplacementCharacters);
2144 #elif USE(ATK)
2145 behaviors.add(TextIteratorBehavior::EmitsCharactersBetweenAllVisiblePositions);
2146 #endif
2148 return WebCore::indexForVisiblePosition(*node, position, behaviors);
2151 Element* AccessibilityRenderObject::rootEditableElementForPosition(const Position& position) const
2153 // Find the root editable or pseudo-editable (i.e. having an editable ARIA role) element.
2154 Element* result = nullptr;
2156 Element* rootEditableElement = position.rootEditableElement();
2158 for (Element* e = position.element(); e && e != rootEditableElement; e = e->parentElement()) {
2159 if (nodeIsTextControl(e))
2160 result = e;
2161 if (e->hasTagName(bodyTag))
2162 break;
2165 if (result)
2166 return result;
2168 return rootEditableElement;
2171 bool AccessibilityRenderObject::nodeIsTextControl(const Node* node) const
2173 if (!node)
2174 return false;
2176 if (AXObjectCache* cache = axObjectCache()) {
2177 if (AccessibilityObject* axObjectForNode = cache->getOrCreate(const_cast<Node*>(node)))
2178 return axObjectForNode->isTextControl();
2181 return false;
2184 static IntRect boundsForRects(const LayoutRect& rect1, const LayoutRect& rect2, const SimpleRange& dataRange)
2186 LayoutRect ourRect = rect1;
2187 ourRect.unite(rect2);
2189 // If the rectangle spans lines and contains multiple text characters, use the range's bounding box intead.
2190 if (rect1.maxY() != rect2.maxY() && characterCount(dataRange) > 1) {
2191 if (auto boundingBox = unionRect(RenderObject::absoluteTextRects(dataRange)); !boundingBox.isEmpty())
2192 ourRect = boundingBox;
2195 return snappedIntRect(ourRect);
2198 IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
2200 if (visiblePositionRange.isNull())
2201 return IntRect();
2203 // Create a mutable VisiblePositionRange.
2204 VisiblePositionRange range(visiblePositionRange);
2205 LayoutRect rect1 = range.start.absoluteCaretBounds();
2206 LayoutRect rect2 = range.end.absoluteCaretBounds();
2208 // readjust for position at the edge of a line. This is to exclude line rect that doesn't need to be accounted in the range bounds
2209 if (rect2.y() != rect1.y()) {
2210 VisiblePosition endOfFirstLine = endOfLine(range.start);
2211 if (range.start == endOfFirstLine) {
2212 range.start.setAffinity(Affinity::Downstream);
2213 rect1 = range.start.absoluteCaretBounds();
2215 if (range.end == endOfFirstLine) {
2216 range.end.setAffinity(Affinity::Upstream);
2217 rect2 = range.end.absoluteCaretBounds();
2221 return boundsForRects(rect1, rect2, *makeSimpleRange(range));
2224 IntRect AccessibilityRenderObject::boundsForRange(const SimpleRange& range) const
2226 auto cache = axObjectCache();
2227 if (!cache)
2228 return { };
2230 auto start = cache->startOrEndCharacterOffsetForRange(range, true);
2231 auto end = cache->startOrEndCharacterOffsetForRange(range, false);
2233 auto rect1 = cache->absoluteCaretBoundsForCharacterOffset(start);
2234 auto rect2 = cache->absoluteCaretBoundsForCharacterOffset(end);
2236 // Readjust for position at the edge of a line. This is to exclude line rect that doesn't need to be accounted in the range bounds.
2237 if (rect2.y() != rect1.y()) {
2238 auto endOfFirstLine = cache->endCharacterOffsetOfLine(start);
2239 if (start.isEqual(endOfFirstLine)) {
2240 start = cache->nextCharacterOffset(start, false);
2241 rect1 = cache->absoluteCaretBoundsForCharacterOffset(start);
2243 if (end.isEqual(endOfFirstLine)) {
2244 end = cache->previousCharacterOffset(end, false);
2245 rect2 = cache->absoluteCaretBoundsForCharacterOffset(end);
2249 return boundsForRects(rect1, rect2, range);
2252 bool AccessibilityRenderObject::isVisiblePositionRangeInDifferentDocument(const VisiblePositionRange& range) const
2254 if (range.start.isNull() || range.end.isNull())
2255 return false;
2257 VisibleSelection newSelection = VisibleSelection(range.start, range.end);
2258 if (Document* newSelectionDocument = newSelection.base().document()) {
2259 if (RefPtr<Frame> newSelectionFrame = newSelectionDocument->frame()) {
2260 Frame* frame = this->frame();
2261 if (!frame || (newSelectionFrame != frame && newSelectionDocument != frame->document()))
2262 return true;
2266 return false;
2269 VisiblePositionRange AccessibilityRenderObject::selectedVisiblePositionRange() const
2271 if (!m_renderer)
2272 return { };
2274 auto selection = m_renderer->frame().selection().selection();
2275 if (selection.isNone())
2276 return { };
2277 return selection;
2280 void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePositionRange& range) const
2282 if (range.isNull())
2283 return;
2285 // In WebKit1, when the top web area sets the selection to be an input element in an iframe, the caret will disappear.
2286 // FrameSelection::setSelectionWithoutUpdatingAppearance is setting the selection on the new frame in this case, and causing this behavior.
2287 if (isWebArea() && parentObject() && parentObject()->isAttachment()
2288 && isVisiblePositionRangeInDifferentDocument(range))
2289 return;
2291 auto* client = m_renderer ? m_renderer->document().editor().client() : nullptr;
2292 if (client)
2293 client->willChangeSelectionForAccessibility();
2295 if (isNativeTextControl()) {
2296 // isNativeTextControl returns true only if this->node() is<HTMLTextAreaElement> or is<HTMLInputElement>.
2297 // Since both HTMLTextAreaElement and HTMLInputElement derive from HTMLTextFormControlElement, it is safe to downcast here.
2298 auto* textControl = downcast<HTMLTextFormControlElement>(node());
2299 int start = textControl->indexForVisiblePosition(range.start);
2300 int end = textControl->indexForVisiblePosition(range.end);
2302 // For ranges entirely contained in textControl, the start or end position may not be inside textControl.innerTextElement.
2303 // This would cause that the above indexes will be 0, leading to an incorrect selected range
2304 // (see HTMLTextFormControlElement::indexForVisiblePosition). This is
2305 // the case when range is obtained from AXObjectCache::rangeForNodeContents
2306 // for the HTMLTextFormControlElement.
2307 // Thus, the following corrects the start and end indexes in such a case..
2308 if (range.start.deepEquivalent().anchorNode() == range.end.deepEquivalent().anchorNode()
2309 && range.start.deepEquivalent().anchorNode() == textControl) {
2310 if (auto innerText = textControl->innerTextElement()) {
2311 auto textControlRange = makeVisiblePositionRange(AXObjectCache::rangeForNodeContents(*textControl));
2312 auto innerRange = makeVisiblePositionRange(AXObjectCache::rangeForNodeContents(*innerText));
2314 if (range.start.equals(textControlRange.end))
2315 start = textControl->value().length();
2316 else if (range.start <= innerRange.start)
2317 start = 0;
2319 if (range.end >= innerRange.end
2320 || range.end.equals(textControlRange.end))
2321 end = textControl->value().length();
2325 setTextSelectionIntent(axObjectCache(), start == end ? AXTextStateChangeTypeSelectionMove : AXTextStateChangeTypeSelectionExtend);
2326 textControl->setSelectionRange(start, end);
2327 } else if (m_renderer) {
2328 // Make selection and tell the document to use it. If it's zero length, then move to that position.
2329 if (range.start == range.end) {
2330 setTextSelectionIntent(axObjectCache(), AXTextStateChangeTypeSelectionMove);
2332 auto start = range.start;
2333 if (auto elementRange = this->elementRange()) {
2334 if (!contains<ComposedTree>(*elementRange, makeBoundaryPoint(start)))
2335 start = makeContainerOffsetPosition(elementRange->start);
2338 m_renderer->frame().selection().moveTo(start, UserTriggered);
2339 } else {
2340 setTextSelectionIntent(axObjectCache(), AXTextStateChangeTypeSelectionExtend);
2342 VisibleSelection newSelection = VisibleSelection(range.start, range.end);
2343 m_renderer->frame().selection().setSelection(newSelection, FrameSelection::defaultSetSelectionOptions(UserTriggered));
2347 clearTextSelectionIntent(axObjectCache());
2349 if (client)
2350 client->didChangeSelectionForAccessibility();
2353 VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoint& point) const
2355 if (!m_renderer)
2356 return VisiblePosition();
2358 // convert absolute point to view coordinates
2359 RenderView* renderView = topRenderer();
2360 if (!renderView)
2361 return VisiblePosition();
2363 #if PLATFORM(COCOA)
2364 FrameView* frameView = &renderView->frameView();
2365 #endif
2367 Node* innerNode = nullptr;
2369 // Locate the node containing the point
2370 // FIXME: Remove this loop and instead add HitTestRequest::Type::AllowVisibleChildFrameContentOnly to the hit test request type.
2371 LayoutPoint pointResult;
2372 while (1) {
2373 LayoutPoint pointToUse;
2374 #if PLATFORM(MAC)
2375 pointToUse = frameView->screenToContents(point);
2376 #else
2377 pointToUse = point;
2378 #endif
2379 constexpr OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active };
2380 HitTestResult result { pointToUse };
2381 renderView->document().hitTest(hitType, result);
2382 innerNode = result.innerNode();
2383 if (!innerNode)
2384 return VisiblePosition();
2386 RenderObject* renderer = innerNode->renderer();
2387 if (!renderer)
2388 return VisiblePosition();
2390 pointResult = result.localPoint();
2392 // done if hit something other than a widget
2393 if (!is<RenderWidget>(*renderer))
2394 break;
2396 // descend into widget (FRAME, IFRAME, OBJECT...)
2397 Widget* widget = downcast<RenderWidget>(*renderer).widget();
2398 if (!is<FrameView>(widget))
2399 break;
2400 Frame& frame = downcast<FrameView>(*widget).frame();
2401 renderView = frame.document()->renderView();
2402 #if PLATFORM(COCOA)
2403 frameView = downcast<FrameView>(widget);
2404 #endif
2407 return innerNode->renderer()->positionForPoint(pointResult, nullptr);
2410 // NOTE: Consider providing this utility method as AX API
2411 VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const
2413 if (!isTextControl())
2414 return VisiblePosition();
2416 // lastIndexOK specifies whether the position after the last character is acceptable
2417 if (indexValue >= text().length()) {
2418 if (!lastIndexOK || indexValue > text().length())
2419 return VisiblePosition();
2421 VisiblePosition position = visiblePositionForIndex(indexValue);
2422 position.setAffinity(Affinity::Downstream);
2423 return position;
2426 // NOTE: Consider providing this utility method as AX API
2427 int AccessibilityRenderObject::index(const VisiblePosition& position) const
2429 if (position.isNull() || !isTextControl())
2430 return -1;
2432 if (renderObjectContainsPosition(renderer(), position.deepEquivalent()))
2433 return indexForVisiblePosition(position);
2435 return -1;
2438 void AccessibilityRenderObject::lineBreaks(Vector<int>& lineBreaks) const
2440 if (!isTextControl())
2441 return;
2443 VisiblePosition visiblePos = visiblePositionForIndex(0);
2444 VisiblePosition savedVisiblePos = visiblePos;
2445 visiblePos = nextLinePosition(visiblePos, 0);
2446 while (!visiblePos.isNull() && visiblePos != savedVisiblePos) {
2447 lineBreaks.append(indexForVisiblePosition(visiblePos));
2448 savedVisiblePos = visiblePos;
2449 visiblePos = nextLinePosition(visiblePos, 0);
2453 static bool isHardLineBreak(const VisiblePosition& position)
2455 if (!isEndOfLine(position))
2456 return false;
2458 auto next = position.next();
2460 auto lineBreakRange = makeSimpleRange(position, next);
2461 if (!lineBreakRange)
2462 return false;
2464 TextIterator it(*lineBreakRange);
2465 if (it.atEnd())
2466 return false;
2468 if (is<HTMLBRElement>(it.node()))
2469 return true;
2471 if (it.node() != position.deepEquivalent().anchorNode())
2472 return false;
2474 return it.text().length() == 1 && it.text()[0] == '\n';
2477 // Given a line number, the range of characters of the text associated with this accessibility
2478 // object that contains the line number.
2479 PlainTextRange AccessibilityRenderObject::doAXRangeForLine(unsigned lineNumber) const
2481 if (!isTextControl())
2482 return { };
2484 // Iterate to the specified line.
2485 auto lineStart = visiblePositionForIndex(0);
2486 for (unsigned lineCount = lineNumber; lineCount; --lineCount) {
2487 auto nextLineStart = nextLinePosition(lineStart, 0);
2488 if (nextLineStart.isNull() || nextLineStart == lineStart)
2489 return { };
2490 lineStart = nextLineStart;
2493 // Get the end of the line based on the starting position.
2494 auto lineEnd = endOfLine(lineStart);
2496 int index1 = indexForVisiblePosition(lineStart);
2497 int index2 = indexForVisiblePosition(lineEnd);
2499 if (isHardLineBreak(lineEnd))
2500 ++index2;
2502 if (index1 < 0 || index2 < 0 || index2 <= index1)
2503 return { };
2505 return { static_cast<unsigned>(index1), static_cast<unsigned>(index2 - index1) };
2508 // The composed character range in the text associated with this accessibility object that
2509 // is specified by the given index value. This parameterized attribute returns the complete
2510 // range of characters (including surrogate pairs of multi-byte glyphs) at the given index.
2511 PlainTextRange AccessibilityRenderObject::doAXRangeForIndex(unsigned index) const
2513 if (!isTextControl())
2514 return PlainTextRange();
2516 String elementText = text();
2517 if (!elementText.length() || index > elementText.length() - 1)
2518 return PlainTextRange();
2520 return PlainTextRange(index, 1);
2523 // A substring of the text associated with this accessibility object that is
2524 // specified by the given character range.
2525 String AccessibilityRenderObject::doAXStringForRange(const PlainTextRange& range) const
2527 if (!range.length)
2528 return String();
2530 if (!isTextControl())
2531 return String();
2533 String elementText = isPasswordField() ? passwordFieldValue() : text();
2534 return elementText.substring(range.start, range.length);
2537 // The bounding rectangle of the text associated with this accessibility object that is
2538 // specified by the given range. This is the bounding rectangle a sighted user would see
2539 // on the display screen, in pixels.
2540 IntRect AccessibilityRenderObject::doAXBoundsForRange(const PlainTextRange& range) const
2542 if (allowsTextRanges())
2543 return boundsForVisiblePositionRange(visiblePositionRangeForRange(range));
2544 return IntRect();
2547 IntRect AccessibilityRenderObject::doAXBoundsForRangeUsingCharacterOffset(const PlainTextRange& characterRange) const
2549 if (!allowsTextRanges())
2550 return { };
2551 auto range = rangeForPlainTextRange(characterRange);
2552 if (!range)
2553 return { };
2554 return boundsForRange(*range);
2557 AXCoreObject* AccessibilityRenderObject::accessibilityImageMapHitTest(HTMLAreaElement* area, const IntPoint& point) const
2559 if (!area)
2560 return nullptr;
2562 auto* mapParent = ancestorsOfType<HTMLMapElement>(*area).first();
2563 if (!mapParent)
2564 return nullptr;
2566 auto* parent = accessibilityParentForImageMap(mapParent);
2567 if (!parent)
2568 return nullptr;
2570 for (const auto& child : parent->children()) {
2571 if (child->elementRect().contains(point))
2572 return child.get();
2575 return nullptr;
2578 AXCoreObject* AccessibilityRenderObject::remoteSVGElementHitTest(const IntPoint& point) const
2580 AccessibilityObject* remote = remoteSVGRootElement(Create);
2581 if (!remote)
2582 return nullptr;
2584 IntSize offset = point - roundedIntPoint(boundingBoxRect().location());
2585 return remote->accessibilityHitTest(IntPoint(offset));
2588 AXCoreObject* AccessibilityRenderObject::elementAccessibilityHitTest(const IntPoint& point) const
2590 if (isSVGImage())
2591 return remoteSVGElementHitTest(point);
2593 return AccessibilityObject::elementAccessibilityHitTest(point);
2596 static bool shouldUseShadowHostForHitTesting(Node* shadowHost)
2598 // We need to allow automation of mouse events on video tags.
2599 return shadowHost && !shadowHost->hasTagName(videoTag);
2602 AXCoreObject* AccessibilityRenderObject::accessibilityHitTest(const IntPoint& point) const
2604 if (!m_renderer || !m_renderer->hasLayer())
2605 return nullptr;
2607 m_renderer->document().updateLayout();
2609 if (!m_renderer || !m_renderer->hasLayer())
2610 return nullptr;
2612 RenderLayer* layer = downcast<RenderBox>(*m_renderer).layer();
2614 constexpr OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active, HitTestRequest::Type::AccessibilityHitTest };
2615 HitTestResult hitTestResult { point };
2616 layer->hitTest(hitType, hitTestResult);
2617 Node* node = hitTestResult.innerNode();
2618 if (!node)
2619 return nullptr;
2620 Node* shadowAncestorNode = node->shadowHost();
2621 if (shouldUseShadowHostForHitTesting(shadowAncestorNode))
2622 node = shadowAncestorNode;
2623 ASSERT(node);
2625 if (is<HTMLAreaElement>(*node))
2626 return accessibilityImageMapHitTest(downcast<HTMLAreaElement>(node), point);
2628 if (is<HTMLOptionElement>(*node))
2629 node = downcast<HTMLOptionElement>(*node).ownerSelectElement();
2631 RenderObject* obj = node->renderer();
2632 if (!obj)
2633 return nullptr;
2635 AXCoreObject* result = obj->document().axObjectCache()->getOrCreate(obj);
2636 result->updateChildrenIfNecessary();
2638 // Allow the element to perform any hit-testing it might need to do to reach non-render children.
2639 result = static_cast<AccessibilityObject*>(result->elementAccessibilityHitTest(point));
2641 if (result && result->accessibilityIsIgnored()) {
2642 // If this element is the label of a control, a hit test should return the control.
2643 auto* controlObject = result->correspondingControlForLabelElement();
2644 if (controlObject && !controlObject->titleUIElement())
2645 return controlObject;
2647 result = result->parentObjectUnignored();
2650 return result;
2653 bool AccessibilityRenderObject::shouldNotifyActiveDescendant() const
2655 #if USE(ATK)
2656 // According to the Core AAM spec, ATK expects object:state-changed:focused notifications
2657 // whenever the active descendant changes.
2658 return true;
2659 #endif
2660 // We want to notify that the combo box has changed its active descendant,
2661 // but we do not want to change the focus, because focus should remain with the combo box.
2662 if (isComboBox())
2663 return true;
2665 return shouldFocusActiveDescendant();
2668 bool AccessibilityRenderObject::shouldFocusActiveDescendant() const
2670 switch (ariaRoleAttribute()) {
2671 case AccessibilityRole::ApplicationGroup:
2672 case AccessibilityRole::ListBox:
2673 case AccessibilityRole::Menu:
2674 case AccessibilityRole::MenuBar:
2675 case AccessibilityRole::RadioGroup:
2676 case AccessibilityRole::Row:
2677 case AccessibilityRole::PopUpButton:
2678 case AccessibilityRole::Meter:
2679 case AccessibilityRole::ProgressIndicator:
2680 case AccessibilityRole::Toolbar:
2681 case AccessibilityRole::Outline:
2682 case AccessibilityRole::Tree:
2683 case AccessibilityRole::Grid:
2684 /* FIXME: replace these with actual roles when they are added to AccessibilityRole
2685 composite
2686 alert
2687 alertdialog
2688 status
2689 timer
2691 return true;
2692 default:
2693 return false;
2697 AccessibilityObject* AccessibilityRenderObject::activeDescendant() const
2699 if (!m_renderer)
2700 return nullptr;
2702 const AtomString& activeDescendantAttrStr = getAttribute(aria_activedescendantAttr);
2703 if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty())
2704 return nullptr;
2705 Element* element = this->element();
2706 if (!element)
2707 return nullptr;
2709 Element* target = element->treeScope().getElementById(activeDescendantAttrStr);
2710 if (!target)
2711 return nullptr;
2713 if (AXObjectCache* cache = axObjectCache()) {
2714 AccessibilityObject* obj = cache->getOrCreate(target);
2715 if (obj && obj->isAccessibilityRenderObject())
2716 // an activedescendant is only useful if it has a renderer, because that's what's needed to post the notification
2717 return obj;
2720 return nullptr;
2723 RenderObject* AccessibilityRenderObject::targetElementForActiveDescendant(const QualifiedName& attributeName, AccessibilityObject* activeDescendant) const
2725 AccessibilityObject::AccessibilityChildrenVector elements;
2726 ariaElementsFromAttribute(elements, attributeName);
2727 for (const auto& element : elements) {
2728 if (activeDescendant->isDescendantOfObject(element.get()))
2729 return element->renderer();
2732 return nullptr;
2735 void AccessibilityRenderObject::handleActiveDescendantChanged()
2737 Element* element = downcast<Element>(renderer()->node());
2738 if (!element)
2739 return;
2740 if (!renderer()->frame().selection().isFocusedAndActive() || renderer()->document().focusedElement() != element)
2741 return;
2743 auto* activeDescendant = this->activeDescendant();
2744 if (activeDescendant && shouldNotifyActiveDescendant()) {
2745 auto* targetRenderer = renderer();
2747 #if PLATFORM(COCOA)
2748 // If the combobox's activeDescendant is inside another object, the target element should be that parent.
2749 if (isComboBox()) {
2750 if (auto* ariaOwner = targetElementForActiveDescendant(aria_ownsAttr, activeDescendant))
2751 targetRenderer = ariaOwner;
2752 else if (auto* ariaController = targetElementForActiveDescendant(aria_controlsAttr, activeDescendant))
2753 targetRenderer = ariaController;
2755 #endif
2757 renderer()->document().axObjectCache()->postNotification(targetRenderer, AXObjectCache::AXActiveDescendantChanged);
2761 bool AccessibilityRenderObject::renderObjectIsObservable(RenderObject& renderer) const
2763 // AX clients will listen for AXValueChange on a text control.
2764 if (is<RenderTextControl>(renderer))
2765 return true;
2767 // AX clients will listen for AXSelectedChildrenChanged on listboxes.
2768 Node* node = renderer.node();
2769 if (!node)
2770 return false;
2772 if (nodeHasRole(node, "listbox") || (is<RenderBoxModelObject>(renderer) && downcast<RenderBoxModelObject>(renderer).isListBox()))
2773 return true;
2775 // Textboxes should send out notifications.
2776 if (nodeHasRole(node, "textbox") || (is<Element>(*node) && contentEditableAttributeIsEnabled(downcast<Element>(node))))
2777 return true;
2779 return false;
2782 AccessibilityObject* AccessibilityRenderObject::observableObject() const
2784 // Find the object going up the parent chain that is used in accessibility to monitor certain notifications.
2785 for (RenderObject* renderer = this->renderer(); renderer && renderer->node(); renderer = renderer->parent()) {
2786 if (renderObjectIsObservable(*renderer)) {
2787 if (AXObjectCache* cache = axObjectCache())
2788 return cache->getOrCreate(renderer);
2792 return nullptr;
2795 String AccessibilityRenderObject::expandedTextValue() const
2797 if (AccessibilityObject* parent = parentObject()) {
2798 if (parent->hasTagName(abbrTag) || parent->hasTagName(acronymTag))
2799 return parent->getAttribute(titleAttr);
2802 return String();
2805 bool AccessibilityRenderObject::supportsExpandedTextValue() const
2807 if (roleValue() == AccessibilityRole::StaticText) {
2808 if (AccessibilityObject* parent = parentObject())
2809 return parent->hasTagName(abbrTag) || parent->hasTagName(acronymTag);
2812 return false;
2815 bool AccessibilityRenderObject::shouldIgnoreAttributeRole() const
2817 if (m_ariaRole == AccessibilityRole::Document
2818 && hasContentEditableAttributeSet())
2819 return true;
2820 return false;
2823 AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole()
2825 AXTRACE("AccessibilityRenderObject::determineAccessibilityRole");
2826 if (!m_renderer)
2827 return AccessibilityRole::Unknown;
2829 #if ENABLE(APPLE_PAY)
2830 if (isApplePayButton())
2831 return AccessibilityRole::Button;
2832 #endif
2834 // Sometimes we need to ignore the attribute role. Like if a tree is malformed,
2835 // we want to ignore the treeitem's attribute role.
2836 if ((m_ariaRole = determineAriaRoleAttribute()) != AccessibilityRole::Unknown && !shouldIgnoreAttributeRole())
2837 return m_ariaRole;
2839 Node* node = m_renderer->node();
2840 RenderBoxModelObject* cssBox = renderBoxModelObject();
2842 if (cssBox && cssBox->isListItem())
2843 return AccessibilityRole::ListItem;
2844 if (m_renderer->isListMarker())
2845 return AccessibilityRole::ListMarker;
2846 if (m_renderer->isText())
2847 return AccessibilityRole::StaticText;
2848 if (is<HTMLImageElement>(node) && downcast<HTMLImageElement>(*node).hasAttributeWithoutSynchronization(usemapAttr))
2849 return AccessibilityRole::ImageMap;
2850 if (cssBox && cssBox->isImage()) {
2851 if (is<HTMLInputElement>(node))
2852 return hasPopup() ? AccessibilityRole::PopUpButton : AccessibilityRole::Button;
2854 if (auto* svgRoot = remoteSVGRootElement(Create)) {
2855 if (svgRoot->hasAccessibleContent())
2856 return AccessibilityRole::SVGRoot;
2858 return AccessibilityRole::Image;
2861 if (cssBox && cssBox->isRenderView())
2862 return AccessibilityRole::WebArea;
2863 if (cssBox && cssBox->isTextField()) {
2864 if (is<HTMLInputElement>(node))
2865 return downcast<HTMLInputElement>(*node).isSearchField() ? AccessibilityRole::SearchField : AccessibilityRole::TextField;
2867 if (cssBox && cssBox->isTextArea())
2868 return AccessibilityRole::TextArea;
2869 if (cssBox && cssBox->isMenuList())
2870 return AccessibilityRole::PopUpButton;
2872 if (m_renderer->isSVGRootOrLegacySVGRoot())
2873 return AccessibilityRole::SVGRoot;
2875 // Check for Ruby elements
2876 if (m_renderer->isRubyText())
2877 return AccessibilityRole::RubyText;
2878 if (m_renderer->isRubyBase())
2879 return AccessibilityRole::RubyBase;
2880 if (m_renderer->isRubyRun())
2881 return AccessibilityRole::RubyRun;
2882 if (m_renderer->isRubyBlock())
2883 return AccessibilityRole::RubyBlock;
2884 if (m_renderer->isRubyInline())
2885 return AccessibilityRole::RubyInline;
2887 // This return value is what will be used if AccessibilityTableCell determines
2888 // the cell should not be treated as a cell (e.g. because it is a layout table.
2889 if (is<RenderTableCell>(renderer()))
2890 return AccessibilityRole::TextGroup;
2891 // Table sections should be ignored.
2892 if (m_renderer->isTableSection())
2893 return AccessibilityRole::Ignored;
2895 auto treatStyleFormatGroupAsInline = is<RenderInline>(*m_renderer) ? TreatStyleFormatGroupAsInline::Yes : TreatStyleFormatGroupAsInline::No;
2896 auto roleFromNode = determineAccessibilityRoleFromNode(treatStyleFormatGroupAsInline);
2897 if (roleFromNode != AccessibilityRole::Unknown)
2898 return roleFromNode;
2900 if (m_renderer->isRenderBlockFlow())
2901 return m_renderer->isAnonymousBlock() ? AccessibilityRole::TextGroup : AccessibilityRole::Group;
2903 // InlineRole is the final fallback before assigning AccessibilityRole::Unknown to an object. It makes it
2904 // possible to distinguish truly unknown objects from non-focusable inline text elements
2905 // which have an event handler or attribute suggesting possible inclusion by the platform.
2906 if (is<RenderInline>(*m_renderer)
2907 && (hasAttributesRequiredForInclusion()
2908 || (node && node->hasEventListeners())
2909 || (supportsDatetimeAttribute() && !getAttribute(datetimeAttr).isEmpty())))
2910 return AccessibilityRole::Inline;
2912 return AccessibilityRole::Unknown;
2915 AccessibilityOrientation AccessibilityRenderObject::orientation() const
2917 const AtomString& ariaOrientation = getAttribute(aria_orientationAttr);
2918 if (equalLettersIgnoringASCIICase(ariaOrientation, "horizontal"))
2919 return AccessibilityOrientation::Horizontal;
2920 if (equalLettersIgnoringASCIICase(ariaOrientation, "vertical"))
2921 return AccessibilityOrientation::Vertical;
2922 if (equalLettersIgnoringASCIICase(ariaOrientation, "undefined"))
2923 return AccessibilityOrientation::Undefined;
2925 // In ARIA 1.1, the implicit value of aria-orientation changed from horizontal
2926 // to undefined on all roles that don't have their own role-specific values. In
2927 // addition, the implicit value of combobox became undefined.
2928 if (isComboBox() || isRadioGroup() || isTreeGrid())
2929 return AccessibilityOrientation::Undefined;
2931 if (isScrollbar() || isListBox() || isMenu() || isTree())
2932 return AccessibilityOrientation::Vertical;
2934 if (isMenuBar() || isSplitter() || isTabList() || isToolbar() || isSlider())
2935 return AccessibilityOrientation::Horizontal;
2937 return AccessibilityObject::orientation();
2940 bool AccessibilityRenderObject::inheritsPresentationalRole() const
2942 // ARIA states if an item can get focus, it should not be presentational.
2943 if (canSetFocusAttribute())
2944 return false;
2946 // ARIA spec says that when a parent object is presentational, and it has required child elements,
2947 // those child elements are also presentational. For example, <li> becomes presentational from <ul>.
2948 // http://www.w3.org/WAI/PF/aria/complete#presentation
2950 Span<decltype(aTag)* const> parentTags;
2951 switch (roleValue()) {
2952 case AccessibilityRole::ListItem:
2953 case AccessibilityRole::ListMarker: {
2954 static constexpr std::array listItemParents { &dlTag, &olTag, &ulTag };
2955 parentTags = listItemParents;
2956 break;
2958 case AccessibilityRole::GridCell:
2959 case AccessibilityRole::Cell: {
2960 static constexpr std::array tableCellParents { &tableTag };
2961 parentTags = tableCellParents;
2962 break;
2964 default:
2965 // Not all elements need to do the following check, only ones that are required children.
2966 return false;
2969 for (auto* parent = parentObject(); parent; parent = parent->parentObject()) {
2970 if (!is<AccessibilityRenderObject>(*parent))
2971 continue;
2973 Node* node = downcast<AccessibilityRenderObject>(*parent).node();
2974 if (!is<Element>(node))
2975 continue;
2977 // If native tag of the parent element matches an acceptable name, then return
2978 // based on its presentational status.
2979 auto& name = downcast<Element>(*node).tagQName();
2980 if (std::any_of(parentTags.begin(), parentTags.end(), [&name] (auto* possibleName) { return possibleName->get() == name; }))
2981 return parent->roleValue() == AccessibilityRole::Presentational;
2984 return false;
2987 bool AccessibilityRenderObject::isPresentationalChildOfAriaRole() const
2989 // Walk the parent chain looking for a parent that has presentational children
2990 AccessibilityObject* parent;
2991 for (parent = parentObject(); parent && !parent->ariaRoleHasPresentationalChildren(); parent = parent->parentObject())
2994 return parent;
2997 bool AccessibilityRenderObject::ariaRoleHasPresentationalChildren() const
2999 switch (m_ariaRole) {
3000 case AccessibilityRole::Button:
3001 case AccessibilityRole::Slider:
3002 case AccessibilityRole::Image:
3003 case AccessibilityRole::ProgressIndicator:
3004 case AccessibilityRole::SpinButton:
3005 // case SeparatorRole:
3006 return true;
3007 default:
3008 return false;
3012 bool AccessibilityRenderObject::canSetExpandedAttribute() const
3014 if (roleValue() == AccessibilityRole::Details)
3015 return true;
3017 return supportsExpanded();
3020 bool AccessibilityRenderObject::canSetTextRangeAttributes() const
3022 return isTextControl();
3025 void AccessibilityRenderObject::clearChildren()
3027 AccessibilityObject::clearChildren();
3028 m_childrenDirty = false;
3031 void AccessibilityRenderObject::addImageMapChildren()
3033 RenderBoxModelObject* cssBox = renderBoxModelObject();
3034 if (!is<RenderImage>(cssBox))
3035 return;
3037 HTMLMapElement* map = downcast<RenderImage>(*cssBox).imageMap();
3038 if (!map)
3039 return;
3041 for (auto& area : descendantsOfType<HTMLAreaElement>(*map)) {
3042 // add an <area> element for this child if it has a link
3043 if (!area.isLink())
3044 continue;
3045 auto& areaObject = downcast<AccessibilityImageMapLink>(*axObjectCache()->create(AccessibilityRole::ImageMapLink));
3046 areaObject.setHTMLAreaElement(&area);
3047 areaObject.setHTMLMapElement(map);
3048 areaObject.setParent(this);
3049 if (!areaObject.accessibilityIsIgnored())
3050 addChild(&areaObject);
3051 else
3052 axObjectCache()->remove(areaObject.objectID());
3056 void AccessibilityRenderObject::updateChildrenIfNecessary()
3058 if (needsToUpdateChildren())
3059 clearChildren();
3061 AccessibilityObject::updateChildrenIfNecessary();
3064 void AccessibilityRenderObject::addTextFieldChildren()
3066 Node* node = this->node();
3067 if (!is<HTMLInputElement>(node))
3068 return;
3070 HTMLElement* spinButtonElement = downcast<HTMLInputElement>(*node).innerSpinButtonElement();
3071 if (!is<SpinButtonElement>(spinButtonElement))
3072 return;
3074 auto& axSpinButton = downcast<AccessibilitySpinButton>(*axObjectCache()->create(AccessibilityRole::SpinButton));
3075 axSpinButton.setSpinButtonElement(downcast<SpinButtonElement>(spinButtonElement));
3076 axSpinButton.setParent(this);
3077 addChild(&axSpinButton);
3080 bool AccessibilityRenderObject::isSVGImage() const
3082 return remoteSVGRootElement(Create);
3085 void AccessibilityRenderObject::detachRemoteSVGRoot()
3087 if (AccessibilitySVGRoot* root = remoteSVGRootElement(Retrieve))
3088 root->setParent(nullptr);
3091 AccessibilitySVGRoot* AccessibilityRenderObject::remoteSVGRootElement(CreationChoice createIfNecessary) const
3093 if (!is<RenderImage>(renderer()))
3094 return nullptr;
3096 CachedImage* cachedImage = downcast<RenderImage>(*m_renderer).cachedImage();
3097 if (!cachedImage)
3098 return nullptr;
3100 Image* image = cachedImage->image();
3101 if (!is<SVGImage>(image))
3102 return nullptr;
3104 FrameView* frameView = downcast<SVGImage>(*image).frameView();
3105 if (!frameView)
3106 return nullptr;
3107 Frame& frame = frameView->frame();
3109 Document* document = frame.document();
3110 if (!is<SVGDocument>(document))
3111 return nullptr;
3113 auto rootElement = DocumentSVG::rootElement(*document);
3114 if (!rootElement)
3115 return nullptr;
3116 RenderObject* rendererRoot = rootElement->renderer();
3117 if (!rendererRoot)
3118 return nullptr;
3120 AXObjectCache* cache = frame.document()->axObjectCache();
3121 if (!cache)
3122 return nullptr;
3124 AccessibilityObject* rootSVGObject = createIfNecessary == Create ? cache->getOrCreate(rendererRoot) : cache->get(rendererRoot);
3126 ASSERT(!createIfNecessary || rootSVGObject);
3127 if (!is<AccessibilitySVGRoot>(rootSVGObject))
3128 return nullptr;
3130 return downcast<AccessibilitySVGRoot>(rootSVGObject);
3133 void AccessibilityRenderObject::addRemoteSVGChildren()
3135 AccessibilitySVGRoot* root = remoteSVGRootElement(Create);
3136 if (!root)
3137 return;
3139 // In order to connect the AX hierarchy from the SVG root element from the loaded resource
3140 // the parent must be set, because there's no other way to get back to who created the image.
3141 root->setParent(this);
3142 addChild(root);
3145 void AccessibilityRenderObject::addCanvasChildren()
3147 // Add the unrendered canvas children as AX nodes, unless we're not using a canvas renderer
3148 // because JS is disabled for example.
3149 if (!node() || !node()->hasTagName(canvasTag) || (renderer() && !renderer()->isCanvas()))
3150 return;
3152 // If it's a canvas, it won't have rendered children, but it might have accessible fallback content.
3153 // Clear m_childrenInitialized because AccessibilityNodeObject::addChildren will expect it to be false.
3154 ASSERT(!m_children.size());
3155 m_childrenInitialized = false;
3156 AccessibilityNodeObject::addChildren();
3159 void AccessibilityRenderObject::addAttachmentChildren()
3161 if (!isAttachment())
3162 return;
3164 // FrameView's need to be inserted into the AX hierarchy when encountered.
3165 Widget* widget = widgetForAttachmentView();
3166 if (!widget || !widget->isFrameView())
3167 return;
3169 addChild(axObjectCache()->getOrCreate(widget));
3172 #if PLATFORM(COCOA)
3173 void AccessibilityRenderObject::updateAttachmentViewParents()
3175 // Only the unignored parent should set the attachment parent, because that's what is reflected in the AX
3176 // hierarchy to the client.
3177 if (accessibilityIsIgnored())
3178 return;
3180 for (const auto& child : m_children) {
3181 if (child->isAttachment())
3182 child->overrideAttachmentParent(this);
3185 #endif
3187 // Hidden children are those that are not rendered or visible, but are specifically marked as aria-hidden=false,
3188 // meaning that they should be exposed to the AX hierarchy.
3189 void AccessibilityRenderObject::addHiddenChildren()
3191 Node* node = this->node();
3192 if (!node)
3193 return;
3195 // First do a quick run through to determine if we have any hidden nodes (most often we will not).
3196 // If we do have hidden nodes, we need to determine where to insert them so they match DOM order as close as possible.
3197 bool shouldInsertHiddenNodes = false;
3198 for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
3199 if (!child->renderer() && isNodeAriaVisible(child)) {
3200 shouldInsertHiddenNodes = true;
3201 break;
3205 if (!shouldInsertHiddenNodes)
3206 return;
3208 // Iterate through all of the children, including those that may have already been added, and
3209 // try to insert hidden nodes in the correct place in the DOM order.
3210 unsigned insertionIndex = 0;
3211 for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
3212 if (child->renderer()) {
3213 // Find out where the last render sibling is located within m_children.
3214 AXCoreObject* childObject = axObjectCache()->get(child->renderer());
3215 if (childObject && childObject->accessibilityIsIgnored()) {
3216 auto& children = childObject->children();
3217 if (children.size())
3218 childObject = children.last().get();
3219 else
3220 childObject = nullptr;
3223 if (childObject)
3224 insertionIndex = m_children.find(childObject) + 1;
3225 continue;
3228 if (!isNodeAriaVisible(child))
3229 continue;
3231 unsigned previousSize = m_children.size();
3232 if (insertionIndex > previousSize)
3233 insertionIndex = previousSize;
3235 insertChild(axObjectCache()->getOrCreate(child), insertionIndex);
3236 insertionIndex += (m_children.size() - previousSize);
3240 void AccessibilityRenderObject::updateRoleAfterChildrenCreation()
3242 AXTRACE("AccessibilityRenderObject::updateRoleAfterChildrenCreation");
3243 // If a menu does not have valid menuitem children, it should not be exposed as a menu.
3244 auto role = roleValue();
3245 if (role == AccessibilityRole::Menu) {
3246 // Elements marked as menus must have at least one menu item child.
3247 size_t menuItemCount = 0;
3248 for (const auto& child : children()) {
3249 if (child->isMenuItem()) {
3250 menuItemCount++;
3251 break;
3255 if (!menuItemCount)
3256 m_role = AccessibilityRole::Group;
3258 if (role == AccessibilityRole::SVGRoot && !children().size())
3259 m_role = AccessibilityRole::Image;
3262 void AccessibilityRenderObject::addChildren()
3264 // If the need to add more children in addition to existing children arises,
3265 // childrenChanged should have been called, leaving the object with no children.
3266 ASSERT(!m_childrenInitialized);
3268 m_childrenInitialized = true;
3270 if (!canHaveChildren())
3271 return;
3273 for (RefPtr<AccessibilityObject> obj = firstChild(); obj; obj = obj->nextSibling())
3274 addChild(obj.get());
3276 m_subtreeDirty = false;
3278 addHiddenChildren();
3279 addAttachmentChildren();
3280 addImageMapChildren();
3281 addTextFieldChildren();
3282 addCanvasChildren();
3283 addRemoteSVGChildren();
3285 #if PLATFORM(COCOA)
3286 updateAttachmentViewParents();
3287 #endif
3289 updateRoleAfterChildrenCreation();
3292 bool AccessibilityRenderObject::canHaveChildren() const
3294 if (!m_renderer)
3295 return false;
3297 return AccessibilityNodeObject::canHaveChildren();
3300 const String AccessibilityRenderObject::liveRegionStatus() const
3302 const AtomString& liveRegionStatus = getAttribute(aria_liveAttr);
3303 // These roles have implicit live region status.
3304 if (liveRegionStatus.isEmpty())
3305 return defaultLiveRegionStatusForRole(roleValue());
3307 return liveRegionStatus;
3310 const String AccessibilityRenderObject::liveRegionRelevant() const
3312 static MainThreadNeverDestroyed<const AtomString> defaultLiveRegionRelevant("additions text", AtomString::ConstructFromLiteral);
3313 const AtomString& relevant = getAttribute(aria_relevantAttr);
3315 // Default aria-relevant = "additions text".
3316 if (relevant.isEmpty())
3317 return "additions text";
3319 return relevant;
3322 bool AccessibilityRenderObject::liveRegionAtomic() const
3324 const AtomString& atomic = getAttribute(aria_atomicAttr);
3325 if (equalLettersIgnoringASCIICase(atomic, "true"))
3326 return true;
3327 if (equalLettersIgnoringASCIICase(atomic, "false"))
3328 return false;
3330 // WAI-ARIA "alert" and "status" roles have an implicit aria-atomic value of true.
3331 switch (roleValue()) {
3332 case AccessibilityRole::ApplicationAlert:
3333 case AccessibilityRole::ApplicationStatus:
3334 return true;
3335 default:
3336 return false;
3340 bool AccessibilityRenderObject::isBusy() const
3342 return elementAttributeValue(aria_busyAttr);
3345 bool AccessibilityRenderObject::canHaveSelectedChildren() const
3347 switch (roleValue()) {
3348 // These roles are containers whose children support aria-selected:
3349 case AccessibilityRole::Grid:
3350 case AccessibilityRole::ListBox:
3351 case AccessibilityRole::TabList:
3352 case AccessibilityRole::Tree:
3353 case AccessibilityRole::TreeGrid:
3354 case AccessibilityRole::List:
3355 // These roles are containers whose children are treated as selected by assistive
3356 // technologies. We can get the "selected" item via aria-activedescendant or the
3357 // focused element.
3358 case AccessibilityRole::Menu:
3359 case AccessibilityRole::MenuBar:
3360 return true;
3361 default:
3362 return false;
3366 void AccessibilityRenderObject::ariaSelectedRows(AccessibilityChildrenVector& result)
3368 // Determine which rows are selected.
3369 bool isMulti = isMultiSelectable();
3371 // Prefer active descendant over aria-selected.
3372 AccessibilityObject* activeDesc = activeDescendant();
3373 if (activeDesc && (activeDesc->isTreeItem() || activeDesc->isTableRow())) {
3374 result.append(activeDesc);
3375 if (!isMulti)
3376 return;
3379 // Get all the rows.
3380 auto rowsIteration = [&](const auto& rows) {
3381 for (auto& row : rows) {
3382 if (row->isSelected() || row->isActiveDescendantOfFocusedContainer()) {
3383 result.append(row);
3384 if (!isMulti)
3385 break;
3389 if (isTree()) {
3390 AccessibilityChildrenVector allRows;
3391 ariaTreeRows(allRows);
3392 rowsIteration(allRows);
3393 } else if (is<AccessibilityTable>(*this)) {
3394 auto& thisTable = downcast<AccessibilityTable>(*this);
3395 if (thisTable.isExposable() && thisTable.supportsSelectedRows())
3396 rowsIteration(thisTable.rows());
3400 void AccessibilityRenderObject::ariaListboxSelectedChildren(AccessibilityChildrenVector& result)
3402 bool isMulti = isMultiSelectable();
3404 for (const auto& child : children()) {
3405 // Every child should have aria-role option, and if so, check for selected attribute/state.
3406 if (child->ariaRoleAttribute() == AccessibilityRole::ListBoxOption && (child->isSelected() || child->isActiveDescendantOfFocusedContainer())) {
3407 result.append(child);
3408 if (!isMulti)
3409 return;
3414 void AccessibilityRenderObject::selectedChildren(AccessibilityChildrenVector& result)
3416 ASSERT(result.isEmpty());
3418 if (!canHaveSelectedChildren())
3419 return;
3421 switch (roleValue()) {
3422 case AccessibilityRole::ListBox:
3423 // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes
3424 ariaListboxSelectedChildren(result);
3425 return;
3426 case AccessibilityRole::Grid:
3427 case AccessibilityRole::Tree:
3428 case AccessibilityRole::TreeGrid:
3429 ariaSelectedRows(result);
3430 return;
3431 case AccessibilityRole::TabList:
3432 if (AXCoreObject* selectedTab = selectedTabItem())
3433 result.append(selectedTab);
3434 return;
3435 case AccessibilityRole::List:
3436 if (auto* selectedListItemChild = selectedListItem())
3437 result.append(selectedListItemChild);
3438 return;
3439 case AccessibilityRole::Menu:
3440 case AccessibilityRole::MenuBar:
3441 if (AccessibilityObject* descendant = activeDescendant()) {
3442 result.append(descendant);
3443 return;
3445 if (AccessibilityObject* focusedElement = static_cast<AccessibilityObject*>(focusedUIElement())) {
3446 result.append(focusedElement);
3447 return;
3449 return;
3450 default:
3451 ASSERT_NOT_REACHED();
3455 void AccessibilityRenderObject::ariaListboxVisibleChildren(AccessibilityChildrenVector& result)
3457 if (!childrenInitialized())
3458 addChildren();
3460 for (const auto& child : children()) {
3461 if (child->isOffScreen())
3462 result.append(child);
3466 void AccessibilityRenderObject::visibleChildren(AccessibilityChildrenVector& result)
3468 ASSERT(result.isEmpty());
3470 // Only listboxes are asked for their visible children.
3471 // Native list boxes would be AccessibilityListBoxes, so only check for aria list boxes.
3472 if (ariaRoleAttribute() != AccessibilityRole::ListBox)
3473 return;
3474 return ariaListboxVisibleChildren(result);
3477 void AccessibilityRenderObject::tabChildren(AccessibilityChildrenVector& result)
3479 if (roleValue() != AccessibilityRole::TabList)
3480 return;
3482 for (const auto& child : children()) {
3483 if (child->isTabItem())
3484 result.append(child);
3488 void AccessibilityRenderObject::setAccessibleName(const AtomString& name)
3490 // Setting the accessible name can store the value in the DOM
3491 if (!m_renderer)
3492 return;
3494 Node* node = nullptr;
3495 // For web areas, set the aria-label on the HTML element.
3496 if (isWebArea())
3497 node = m_renderer->document().documentElement();
3498 else
3499 node = m_renderer->node();
3501 if (is<Element>(node))
3502 downcast<Element>(*node).setAttribute(aria_labelAttr, name);
3505 static bool isLinkable(const AccessibilityRenderObject& object)
3507 if (!object.renderer())
3508 return false;
3510 // See https://wiki.mozilla.org/Accessibility/AT-Windows-API for the elements
3511 // Mozilla considers linkable.
3512 return object.isLink() || object.isImage() || object.renderer()->isText();
3515 String AccessibilityRenderObject::stringValueForMSAA() const
3517 if (isLinkable(*this)) {
3518 Element* anchor = anchorElement();
3519 if (is<HTMLAnchorElement>(anchor))
3520 return downcast<HTMLAnchorElement>(*anchor).href().string();
3523 return stringValue();
3526 bool AccessibilityRenderObject::isLinked() const
3528 if (!isLinkable(*this))
3529 return false;
3531 Element* anchor = anchorElement();
3532 if (!is<HTMLAnchorElement>(anchor))
3533 return false;
3535 return !downcast<HTMLAnchorElement>(*anchor).href().isEmpty();
3538 bool AccessibilityRenderObject::hasBoldFont() const
3540 if (!m_renderer)
3541 return false;
3543 return isFontWeightBold(m_renderer->style().fontDescription().weight());
3546 bool AccessibilityRenderObject::hasItalicFont() const
3548 if (!m_renderer)
3549 return false;
3551 return isItalic(m_renderer->style().fontDescription().italic());
3554 bool AccessibilityRenderObject::hasPlainText() const
3556 if (!m_renderer)
3557 return false;
3559 if (!canHavePlainText())
3560 return false;
3562 const RenderStyle& style = m_renderer->style();
3563 return style.fontDescription().weight() == normalWeightValue()
3564 && !isItalic(style.fontDescription().italic())
3565 && style.textDecorationsInEffect().isEmpty();
3568 bool AccessibilityRenderObject::hasSameFont(const AXCoreObject& object) const
3570 auto* renderer = object.renderer();
3571 if (!m_renderer || !renderer)
3572 return false;
3574 return m_renderer->style().fontDescription().families() == renderer->style().fontDescription().families();
3577 #if ENABLE(APPLE_PAY)
3578 bool AccessibilityRenderObject::isApplePayButton() const
3580 if (!m_renderer)
3581 return false;
3582 return m_renderer->style().effectiveAppearance() == ApplePayButtonPart;
3585 ApplePayButtonType AccessibilityRenderObject::applePayButtonType() const
3587 if (!m_renderer)
3588 return ApplePayButtonType::Plain;
3589 return m_renderer->style().applePayButtonType();
3591 #endif
3593 bool AccessibilityRenderObject::hasSameFontColor(const AXCoreObject& object) const
3595 auto* renderer = object.renderer();
3596 if (!m_renderer || !renderer)
3597 return false;
3599 return m_renderer->style().visitedDependentColor(CSSPropertyColor) == renderer->style().visitedDependentColor(CSSPropertyColor);
3602 bool AccessibilityRenderObject::hasSameStyle(const AXCoreObject& object) const
3604 auto* renderer = object.renderer();
3605 if (!m_renderer || !renderer)
3606 return false;
3608 return m_renderer->style() == renderer->style();
3611 bool AccessibilityRenderObject::hasUnderline() const
3613 if (!m_renderer)
3614 return false;
3616 return m_renderer->style().textDecorationsInEffect().contains(TextDecorationLine::Underline);
3619 String AccessibilityRenderObject::nameForMSAA() const
3621 if (m_renderer && m_renderer->isText())
3622 return textUnderElement();
3624 return title();
3627 static bool shouldReturnTagNameAsRoleForMSAA(const Element& element)
3629 return element.hasTagName(abbrTag) || element.hasTagName(acronymTag)
3630 || element.hasTagName(blockquoteTag) || element.hasTagName(ddTag)
3631 || element.hasTagName(dlTag) || element.hasTagName(dtTag)
3632 || element.hasTagName(formTag) || element.hasTagName(frameTag)
3633 || element.hasTagName(h1Tag) || element.hasTagName(h2Tag)
3634 || element.hasTagName(h3Tag) || element.hasTagName(h4Tag)
3635 || element.hasTagName(h5Tag) || element.hasTagName(h6Tag)
3636 || element.hasTagName(iframeTag) || element.hasTagName(qTag)
3637 || element.hasTagName(tbodyTag) || element.hasTagName(tfootTag)
3638 || element.hasTagName(theadTag);
3641 String AccessibilityRenderObject::stringRoleForMSAA() const
3643 if (!m_renderer)
3644 return String();
3646 Node* node = m_renderer->node();
3647 if (!is<Element>(node))
3648 return String();
3650 Element& element = downcast<Element>(*node);
3651 if (!shouldReturnTagNameAsRoleForMSAA(element))
3652 return String();
3654 return element.tagName();
3657 String AccessibilityRenderObject::positionalDescriptionForMSAA() const
3659 // See "positional descriptions",
3660 // https://wiki.mozilla.org/Accessibility/AT-Windows-API
3661 if (isHeading())
3662 return makeString('L', headingLevel());
3664 // FIXME: Add positional descriptions for other elements.
3665 return String();
3668 String AccessibilityRenderObject::descriptionForMSAA() const
3670 String description = positionalDescriptionForMSAA();
3671 if (!description.isEmpty())
3672 return description;
3674 description = accessibilityDescription();
3675 if (!description.isEmpty()) {
3676 // From the Mozilla MSAA implementation:
3677 // "Signal to screen readers that this description is speakable and is not
3678 // a formatted positional information description. Don't localize the
3679 // 'Description: ' part of this string, it will be parsed out by assistive
3680 // technologies."
3681 return "Description: " + description;
3684 return String();
3687 static AccessibilityRole msaaRoleForRenderer(const RenderObject* renderer)
3689 if (!renderer)
3690 return AccessibilityRole::Unknown;
3692 if (is<RenderText>(*renderer))
3693 return AccessibilityRole::EditableText;
3695 if (is<RenderListItem>(*renderer))
3696 return AccessibilityRole::ListItem;
3698 return AccessibilityRole::Unknown;
3701 AccessibilityRole AccessibilityRenderObject::roleValueForMSAA() const
3703 if (m_roleForMSAA != AccessibilityRole::Unknown)
3704 return m_roleForMSAA;
3706 m_roleForMSAA = msaaRoleForRenderer(renderer());
3708 if (m_roleForMSAA == AccessibilityRole::Unknown)
3709 m_roleForMSAA = roleValue();
3711 return m_roleForMSAA;
3714 String AccessibilityRenderObject::passwordFieldValue() const
3716 ASSERT(isPasswordField());
3718 // Look for the RenderText object in the RenderObject tree for this input field.
3719 RenderObject* renderer = node()->renderer();
3720 while (renderer && !is<RenderText>(renderer))
3721 renderer = downcast<RenderElement>(*renderer).firstChild();
3723 if (!is<RenderText>(renderer))
3724 return String();
3726 // Return the text that is actually being rendered in the input field.
3727 return downcast<RenderText>(*renderer).textWithoutConvertingBackslashToYenSymbol();
3730 ScrollableArea* AccessibilityRenderObject::getScrollableAreaIfScrollable() const
3732 // If the parent is a scroll view, then this object isn't really scrollable, the parent ScrollView should handle the scrolling.
3733 if (auto* parent = parentObject()) {
3734 if (parent->isScrollView())
3735 return nullptr;
3738 if (!is<RenderBox>(renderer()))
3739 return nullptr;
3741 auto& box = downcast<RenderBox>(*m_renderer);
3742 if (!box.canBeScrolledAndHasScrollableArea())
3743 return nullptr;
3745 return box.layer() ? box.layer()->scrollableArea() : nullptr;
3748 void AccessibilityRenderObject::scrollTo(const IntPoint& point) const
3750 if (!is<RenderBox>(renderer()))
3751 return;
3753 auto& box = downcast<RenderBox>(*m_renderer);
3754 if (!box.canBeScrolledAndHasScrollableArea())
3755 return;
3757 // FIXME: is point a ScrollOffset or ScrollPosition? Test in RTL overflow.
3758 ASSERT(box.layer());
3759 ASSERT(box.layer()->scrollableArea());
3760 box.layer()->scrollableArea()->scrollToOffset(point);
3763 #if ENABLE(MATHML)
3764 bool AccessibilityRenderObject::isIgnoredElementWithinMathTree() const
3766 // We ignore anonymous boxes inserted into RenderMathMLBlocks to honor CSS rules.
3767 // See https://www.w3.org/TR/css3-box/#block-level0
3768 return m_renderer && m_renderer->isAnonymous() && m_renderer->parent() && is<RenderMathMLBlock>(m_renderer->parent());
3770 #endif
3772 } // namespace WebCore