1/*
2 * Copyright (C) 2008-2009, 2011, 2017 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "AccessibilityObject.h"
31
32#include "AXObjectCache.h"
33#include "AccessibilityRenderObject.h"
34#include "AccessibilityScrollView.h"
35#include "AccessibilityTable.h"
36#include "Chrome.h"
37#include "ChromeClient.h"
38#include "DOMTokenList.h"
39#include "Editing.h"
40#include "Editor.h"
41#include "ElementIterator.h"
42#include "Event.h"
43#include "EventDispatcher.h"
44#include "EventHandler.h"
45#include "EventNames.h"
46#include "FloatRect.h"
47#include "FocusController.h"
48#include "Frame.h"
49#include "FrameLoader.h"
50#include "FrameSelection.h"
51#include "HTMLDetailsElement.h"
52#include "HTMLFormControlElement.h"
53#include "HTMLInputElement.h"
54#include "HTMLMediaElement.h"
55#include "HTMLNames.h"
56#include "HTMLParserIdioms.h"
57#include "HTMLTextAreaElement.h"
58#include "HitTestResult.h"
59#include "LocalizedStrings.h"
60#include "MathMLNames.h"
61#include "NodeList.h"
62#include "NodeTraversal.h"
63#include "Page.h"
64#include "RenderImage.h"
65#include "RenderLayer.h"
66#include "RenderListItem.h"
67#include "RenderListMarker.h"
68#include "RenderMenuList.h"
69#include "RenderText.h"
70#include "RenderTextControl.h"
71#include "RenderTheme.h"
72#include "RenderView.h"
73#include "RenderWidget.h"
74#include "RenderedPosition.h"
75#include "RuntimeEnabledFeatures.h"
76#include "Settings.h"
77#include "TextCheckerClient.h"
78#include "TextCheckingHelper.h"
79#include "TextIterator.h"
80#include "UserGestureIndicator.h"
81#include "VisibleUnits.h"
82#include <wtf/NeverDestroyed.h>
83#include <wtf/StdLibExtras.h>
84#include <wtf/text/StringBuilder.h>
85#include <wtf/text/StringView.h>
86#include <wtf/text/WTFString.h>
87#include <wtf/unicode/CharacterNames.h>
88
89namespace WebCore {
90
91using namespace HTMLNames;
92
93AccessibilityObject::~AccessibilityObject()
94{
95 ASSERT(isDetached());
96}
97
98void AccessibilityObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache)
99{
100 // Menu close events need to notify the platform. No element is used in the notification because it's a destruction event.
101 if (detachmentType == AccessibilityDetachmentType::ElementDestroyed && roleValue() == AccessibilityRole::Menu && cache)
102 cache->postNotification(nullptr, &cache->document(), AXObjectCache::AXMenuClosed);
103
104 // Clear any children and call detachFromParent on them so that
105 // no children are left with dangling pointers to their parent.
106 clearChildren();
107
108#if HAVE(ACCESSIBILITY)
109 setWrapper(nullptr);
110#endif
111}
112
113bool AccessibilityObject::isDetached() const
114{
115#if HAVE(ACCESSIBILITY)
116 return !wrapper();
117#else
118 return true;
119#endif
120}
121
122bool AccessibilityObject::isAccessibilityObjectSearchMatchAtIndex(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria, size_t index)
123{
124 switch (criteria->searchKeys[index]) {
125 // The AccessibilitySearchKey::AnyType matches any non-null AccessibilityObject.
126 case AccessibilitySearchKey::AnyType:
127 return true;
128
129 case AccessibilitySearchKey::Article:
130 return axObject->roleValue() == AccessibilityRole::DocumentArticle;
131
132 case AccessibilitySearchKey::BlockquoteSameLevel:
133 return criteria->startObject
134 && axObject->isBlockquote()
135 && axObject->blockquoteLevel() == criteria->startObject->blockquoteLevel();
136
137 case AccessibilitySearchKey::Blockquote:
138 return axObject->isBlockquote();
139
140 case AccessibilitySearchKey::BoldFont:
141 return axObject->hasBoldFont();
142
143 case AccessibilitySearchKey::Button:
144 return axObject->isButton();
145
146 case AccessibilitySearchKey::CheckBox:
147 return axObject->isCheckbox();
148
149 case AccessibilitySearchKey::Control:
150 return axObject->isControl();
151
152 case AccessibilitySearchKey::DifferentType:
153 return criteria->startObject
154 && axObject->roleValue() != criteria->startObject->roleValue();
155
156 case AccessibilitySearchKey::FontChange:
157 return criteria->startObject
158 && !axObject->hasSameFont(criteria->startObject->renderer());
159
160 case AccessibilitySearchKey::FontColorChange:
161 return criteria->startObject
162 && !axObject->hasSameFontColor(criteria->startObject->renderer());
163
164 case AccessibilitySearchKey::Frame:
165 return axObject->isWebArea();
166
167 case AccessibilitySearchKey::Graphic:
168 return axObject->isImage();
169
170 case AccessibilitySearchKey::HeadingLevel1:
171 return axObject->headingLevel() == 1;
172
173 case AccessibilitySearchKey::HeadingLevel2:
174 return axObject->headingLevel() == 2;
175
176 case AccessibilitySearchKey::HeadingLevel3:
177 return axObject->headingLevel() == 3;
178
179 case AccessibilitySearchKey::HeadingLevel4:
180 return axObject->headingLevel() == 4;
181
182 case AccessibilitySearchKey::HeadingLevel5:
183 return axObject->headingLevel() == 5;
184
185 case AccessibilitySearchKey::HeadingLevel6:
186 return axObject->headingLevel() == 6;
187
188 case AccessibilitySearchKey::HeadingSameLevel:
189 return criteria->startObject
190 && axObject->isHeading()
191 && axObject->headingLevel() == criteria->startObject->headingLevel();
192
193 case AccessibilitySearchKey::Heading:
194 return axObject->isHeading();
195
196 case AccessibilitySearchKey::Highlighted:
197 return axObject->hasHighlighting();
198
199 case AccessibilitySearchKey::KeyboardFocusable:
200 return axObject->isKeyboardFocusable();
201
202 case AccessibilitySearchKey::ItalicFont:
203 return axObject->hasItalicFont();
204
205 case AccessibilitySearchKey::Landmark:
206 return axObject->isLandmark();
207
208 case AccessibilitySearchKey::Link: {
209 bool isLink = axObject->isLink();
210#if PLATFORM(IOS_FAMILY)
211 if (!isLink)
212 isLink = axObject->isDescendantOfRole(AccessibilityRole::WebCoreLink);
213#endif
214 return isLink;
215 }
216
217 case AccessibilitySearchKey::List:
218 return axObject->isList();
219
220 case AccessibilitySearchKey::LiveRegion:
221 return axObject->supportsLiveRegion();
222
223 case AccessibilitySearchKey::MisspelledWord:
224 return axObject->hasMisspelling();
225
226 case AccessibilitySearchKey::Outline:
227 return axObject->isTree();
228
229 case AccessibilitySearchKey::PlainText:
230 return axObject->hasPlainText();
231
232 case AccessibilitySearchKey::RadioGroup:
233 return axObject->isRadioGroup();
234
235 case AccessibilitySearchKey::SameType:
236 return criteria->startObject
237 && axObject->roleValue() == criteria->startObject->roleValue();
238
239 case AccessibilitySearchKey::StaticText:
240 return axObject->isStaticText();
241
242 case AccessibilitySearchKey::StyleChange:
243 return criteria->startObject
244 && !axObject->hasSameStyle(criteria->startObject->renderer());
245
246 case AccessibilitySearchKey::TableSameLevel:
247 return criteria->startObject
248 && is<AccessibilityTable>(*axObject) && downcast<AccessibilityTable>(*axObject).isExposableThroughAccessibility()
249 && downcast<AccessibilityTable>(*axObject).tableLevel() == criteria->startObject->tableLevel();
250
251 case AccessibilitySearchKey::Table:
252 return is<AccessibilityTable>(*axObject) && downcast<AccessibilityTable>(*axObject).isExposableThroughAccessibility();
253
254 case AccessibilitySearchKey::TextField:
255 return axObject->isTextControl();
256
257 case AccessibilitySearchKey::Underline:
258 return axObject->hasUnderline();
259
260 case AccessibilitySearchKey::UnvisitedLink:
261 return axObject->isUnvisited();
262
263 case AccessibilitySearchKey::VisitedLink:
264 return axObject->isVisited();
265
266 default:
267 return false;
268 }
269}
270
271bool AccessibilityObject::isAccessibilityObjectSearchMatch(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria)
272{
273 if (!axObject || !criteria)
274 return false;
275
276 size_t length = criteria->searchKeys.size();
277 for (size_t i = 0; i < length; ++i) {
278 if (isAccessibilityObjectSearchMatchAtIndex(axObject, criteria, i)) {
279 if (criteria->visibleOnly && !axObject->isOnscreen())
280 return false;
281 return true;
282 }
283 }
284 return false;
285}
286
287bool AccessibilityObject::isAccessibilityTextSearchMatch(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria)
288{
289 if (!axObject || !criteria)
290 return false;
291
292 return axObject->accessibilityObjectContainsText(&criteria->searchText);
293}
294
295bool AccessibilityObject::accessibilityObjectContainsText(String* text) const
296{
297 // If text is null or empty we return true.
298 return !text
299 || text->isEmpty()
300 || findPlainText(title(), *text, CaseInsensitive)
301 || findPlainText(accessibilityDescription(), *text, CaseInsensitive)
302 || findPlainText(stringValue(), *text, CaseInsensitive);
303}
304
305// ARIA marks elements as having their accessible name derive from either their contents, or their author provide name.
306bool AccessibilityObject::accessibleNameDerivesFromContent() const
307{
308 // First check for objects specifically identified by ARIA.
309 switch (ariaRoleAttribute()) {
310 case AccessibilityRole::ApplicationAlert:
311 case AccessibilityRole::ApplicationAlertDialog:
312 case AccessibilityRole::ApplicationDialog:
313 case AccessibilityRole::ApplicationGroup:
314 case AccessibilityRole::ApplicationLog:
315 case AccessibilityRole::ApplicationMarquee:
316 case AccessibilityRole::ApplicationStatus:
317 case AccessibilityRole::ApplicationTimer:
318 case AccessibilityRole::ComboBox:
319 case AccessibilityRole::Definition:
320 case AccessibilityRole::Document:
321 case AccessibilityRole::DocumentArticle:
322 case AccessibilityRole::DocumentMath:
323 case AccessibilityRole::DocumentNote:
324 case AccessibilityRole::LandmarkRegion:
325 case AccessibilityRole::LandmarkDocRegion:
326 case AccessibilityRole::Form:
327 case AccessibilityRole::Grid:
328 case AccessibilityRole::Group:
329 case AccessibilityRole::Image:
330 case AccessibilityRole::List:
331 case AccessibilityRole::ListBox:
332 case AccessibilityRole::LandmarkBanner:
333 case AccessibilityRole::LandmarkComplementary:
334 case AccessibilityRole::LandmarkContentInfo:
335 case AccessibilityRole::LandmarkNavigation:
336 case AccessibilityRole::LandmarkMain:
337 case AccessibilityRole::LandmarkSearch:
338 case AccessibilityRole::Menu:
339 case AccessibilityRole::MenuBar:
340 case AccessibilityRole::ProgressIndicator:
341 case AccessibilityRole::Meter:
342 case AccessibilityRole::RadioGroup:
343 case AccessibilityRole::ScrollBar:
344 case AccessibilityRole::Slider:
345 case AccessibilityRole::SpinButton:
346 case AccessibilityRole::Splitter:
347 case AccessibilityRole::Table:
348 case AccessibilityRole::TabList:
349 case AccessibilityRole::TabPanel:
350 case AccessibilityRole::TextArea:
351 case AccessibilityRole::TextField:
352 case AccessibilityRole::Toolbar:
353 case AccessibilityRole::TreeGrid:
354 case AccessibilityRole::Tree:
355 case AccessibilityRole::WebApplication:
356 return false;
357 default:
358 break;
359 }
360
361 // Now check for generically derived elements now that we know the element does not match a specific ARIA role.
362 switch (roleValue()) {
363 case AccessibilityRole::Slider:
364 case AccessibilityRole::ListBox:
365 return false;
366 default:
367 break;
368 }
369
370 return true;
371}
372
373String AccessibilityObject::computedLabel()
374{
375 // This method is being called by WebKit inspector, which may happen at any time, so we need to update our backing store now.
376 // Also hold onto this object in case updateBackingStore deletes this node.
377 RefPtr<AccessibilityObject> protectedThis(this);
378 updateBackingStore();
379 Vector<AccessibilityText> text;
380 accessibilityText(text);
381 if (text.size())
382 return text[0].text;
383 return String();
384}
385
386bool AccessibilityObject::isBlockquote() const
387{
388 return roleValue() == AccessibilityRole::Blockquote;
389}
390
391bool AccessibilityObject::isTextControl() const
392{
393 switch (roleValue()) {
394 case AccessibilityRole::ComboBox:
395 case AccessibilityRole::SearchField:
396 case AccessibilityRole::TextArea:
397 case AccessibilityRole::TextField:
398 return true;
399 default:
400 return false;
401 }
402}
403
404bool AccessibilityObject::isARIATextControl() const
405{
406 return ariaRoleAttribute() == AccessibilityRole::TextArea || ariaRoleAttribute() == AccessibilityRole::TextField || ariaRoleAttribute() == AccessibilityRole::SearchField;
407}
408
409bool AccessibilityObject::isNonNativeTextControl() const
410{
411 return (isARIATextControl() || hasContentEditableAttributeSet()) && !isNativeTextControl();
412}
413
414bool AccessibilityObject::isLandmark() const
415{
416 switch (roleValue()) {
417 case AccessibilityRole::LandmarkBanner:
418 case AccessibilityRole::LandmarkComplementary:
419 case AccessibilityRole::LandmarkContentInfo:
420 case AccessibilityRole::LandmarkDocRegion:
421 case AccessibilityRole::LandmarkMain:
422 case AccessibilityRole::LandmarkNavigation:
423 case AccessibilityRole::LandmarkRegion:
424 case AccessibilityRole::LandmarkSearch:
425 return true;
426 default:
427 return false;
428 }
429}
430
431bool AccessibilityObject::hasMisspelling() const
432{
433 if (!node())
434 return false;
435
436 Frame* frame = node()->document().frame();
437 if (!frame)
438 return false;
439
440 Editor& editor = frame->editor();
441
442 TextCheckerClient* textChecker = editor.textChecker();
443 if (!textChecker)
444 return false;
445
446 bool isMisspelled = false;
447
448 if (unifiedTextCheckerEnabled(frame)) {
449 Vector<TextCheckingResult> results;
450 checkTextOfParagraph(*textChecker, stringValue(), TextCheckingType::Spelling, results, frame->selection().selection());
451 if (!results.isEmpty())
452 isMisspelled = true;
453 return isMisspelled;
454 }
455
456 int misspellingLength = 0;
457 int misspellingLocation = -1;
458 textChecker->checkSpellingOfString(stringValue(), &misspellingLocation, &misspellingLength);
459 if (misspellingLength || misspellingLocation != -1)
460 isMisspelled = true;
461
462 return isMisspelled;
463}
464
465unsigned AccessibilityObject::blockquoteLevel() const
466{
467 unsigned level = 0;
468 for (Node* elementNode = node(); elementNode; elementNode = elementNode->parentNode()) {
469 if (elementNode->hasTagName(blockquoteTag))
470 ++level;
471 }
472
473 return level;
474}
475
476AccessibilityObject* AccessibilityObject::parentObjectUnignored() const
477{
478 return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, false, [] (const AccessibilityObject& object) {
479 return !object.accessibilityIsIgnored();
480 }));
481}
482
483AccessibilityObject* AccessibilityObject::previousSiblingUnignored(int limit) const
484{
485 AccessibilityObject* previous;
486 ASSERT(limit >= 0);
487 for (previous = previousSibling(); previous && previous->accessibilityIsIgnored(); previous = previous->previousSibling()) {
488 limit--;
489 if (limit <= 0)
490 break;
491 }
492 return previous;
493}
494
495FloatRect AccessibilityObject::convertFrameToSpace(const FloatRect& frameRect, AccessibilityConversionSpace conversionSpace) const
496{
497 ASSERT(isMainThread());
498
499 // Find the appropriate scroll view to use to convert the contents to the window.
500 const auto parentAccessibilityScrollView = ancestorAccessibilityScrollView(false /* includeSelf */);
501 auto* parentScrollView = parentAccessibilityScrollView ? parentAccessibilityScrollView->scrollView() : nullptr;
502
503 auto snappedFrameRect = snappedIntRect(IntRect(frameRect));
504 if (parentScrollView)
505 snappedFrameRect = parentScrollView->contentsToRootView(snappedFrameRect);
506
507 if (conversionSpace == AccessibilityConversionSpace::Screen) {
508 auto page = this->page();
509 if (!page)
510 return snappedFrameRect;
511
512 // If we have an empty chrome client (like SVG) then we should use the page
513 // of the scroll view parent to help us get to the screen rect.
514 if (parentAccessibilityScrollView && page->chrome().client().isEmptyChromeClient())
515 page = parentAccessibilityScrollView->page();
516
517 snappedFrameRect = page->chrome().rootViewToAccessibilityScreen(snappedFrameRect);
518 }
519
520 return snappedFrameRect;
521}
522
523FloatRect AccessibilityObject::relativeFrame() const
524{
525 return convertFrameToSpace(elementRect(), AccessibilityConversionSpace::Page);
526}
527
528AccessibilityObject* AccessibilityObject::nextSiblingUnignored(int limit) const
529{
530 AccessibilityObject* next;
531 ASSERT(limit >= 0);
532 for (next = nextSibling(); next && next->accessibilityIsIgnored(); next = next->nextSibling()) {
533 limit--;
534 if (limit <= 0)
535 break;
536 }
537 return next;
538}
539
540AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node)
541{
542 if (!node)
543 return nullptr;
544
545 AXObjectCache* cache = node->document().axObjectCache();
546 if (!cache)
547 return nullptr;
548
549 AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer());
550 while (accessibleObject && accessibleObject->accessibilityIsIgnored()) {
551 node = NodeTraversal::next(*node);
552
553 while (node && !node->renderer())
554 node = NodeTraversal::nextSkippingChildren(*node);
555
556 if (!node)
557 return nullptr;
558
559 accessibleObject = cache->getOrCreate(node->renderer());
560 }
561
562 return accessibleObject;
563}
564
565bool AccessibilityObject::isDescendantOfRole(AccessibilityRole role) const
566{
567 return AccessibilityObject::matchedParent(*this, false, [&role] (const AccessibilityObject& object) {
568 return object.roleValue() == role;
569 }) != nullptr;
570}
571
572static void appendAccessibilityObject(AccessibilityObject* object, AccessibilityObject::AccessibilityChildrenVector& results)
573{
574 // Find the next descendant of this attachment object so search can continue through frames.
575 if (object->isAttachment()) {
576 Widget* widget = object->widgetForAttachmentView();
577 if (!is<FrameView>(widget))
578 return;
579
580 Document* document = downcast<FrameView>(*widget).frame().document();
581 if (!document || !document->hasLivingRenderTree())
582 return;
583
584 object = object->axObjectCache()->getOrCreate(document);
585 }
586
587 if (object)
588 results.append(object);
589}
590
591void AccessibilityObject::insertChild(AccessibilityObject* child, unsigned index)
592{
593 if (!child)
594 return;
595
596 // If the parent is asking for this child's children, then either it's the first time (and clearing is a no-op),
597 // or its visibility has changed. In the latter case, this child may have a stale child cached.
598 // This can prevent aria-hidden changes from working correctly. Hence, whenever a parent is getting children, ensure data is not stale.
599 // Only clear the child's children when we know it's in the updating chain in order to avoid unnecessary work.
600 if (child->needsToUpdateChildren() || m_subtreeDirty) {
601 child->clearChildren();
602 // Pass m_subtreeDirty flag down to the child so that children cache gets reset properly.
603 if (m_subtreeDirty)
604 child->setNeedsToUpdateSubtree();
605 } else {
606 // For some reason the grand children might be detached so that we need to regenerate the
607 // children list of this child.
608 for (const auto& grandChild : child->children(false)) {
609 if (grandChild->isDetachedFromParent()) {
610 child->clearChildren();
611 break;
612 }
613 }
614 }
615
616 setIsIgnoredFromParentDataForChild(child);
617 if (child->accessibilityIsIgnored()) {
618 const auto& children = child->children();
619 size_t length = children.size();
620 for (size_t i = 0; i < length; ++i)
621 m_children.insert(index + i, children[i]);
622 } else {
623 ASSERT(child->parentObject() == this);
624 m_children.insert(index, child);
625 }
626
627 // Reset the child's m_isIgnoredFromParentData since we are done adding that child and its children.
628 child->clearIsIgnoredFromParentData();
629}
630
631void AccessibilityObject::addChild(AccessibilityObject* child)
632{
633 insertChild(child, m_children.size());
634}
635
636static void appendChildrenToArray(AccessibilityObject* object, bool isForward, AccessibilityObject* startObject, AccessibilityObject::AccessibilityChildrenVector& results)
637{
638 // A table's children includes elements whose own children are also the table's children (due to the way the Mac exposes tables).
639 // The rows from the table should be queried, since those are direct descendants of the table, and they contain content.
640 const auto& searchChildren = is<AccessibilityTable>(*object) && downcast<AccessibilityTable>(*object).isExposableThroughAccessibility() ? downcast<AccessibilityTable>(*object).rows() : object->children();
641
642 size_t childrenSize = searchChildren.size();
643
644 size_t startIndex = isForward ? childrenSize : 0;
645 size_t endIndex = isForward ? 0 : childrenSize;
646
647 // If the startObject is ignored, we should use an accessible sibling as a start element instead.
648 if (startObject && startObject->accessibilityIsIgnored() && startObject->isDescendantOfObject(object)) {
649 AccessibilityObject* parentObject = startObject->parentObject();
650 // Go up the parent chain to find the highest ancestor that's also being ignored.
651 while (parentObject && parentObject->accessibilityIsIgnored()) {
652 if (parentObject == object)
653 break;
654 startObject = parentObject;
655 parentObject = parentObject->parentObject();
656 }
657 // Get the un-ignored sibling based on the search direction, and update the searchPosition.
658 while (startObject && startObject->accessibilityIsIgnored())
659 startObject = isForward ? startObject->previousSibling() : startObject->nextSibling();
660 }
661
662 size_t searchPosition = startObject ? searchChildren.find(startObject) : WTF::notFound;
663
664 if (searchPosition != WTF::notFound) {
665 if (isForward)
666 endIndex = searchPosition + 1;
667 else
668 endIndex = searchPosition;
669 }
670
671 // This is broken into two statements so that it's easier read.
672 if (isForward) {
673 for (size_t i = startIndex; i > endIndex; i--)
674 appendAccessibilityObject(searchChildren.at(i - 1).get(), results);
675 } else {
676 for (size_t i = startIndex; i < endIndex; i++)
677 appendAccessibilityObject(searchChildren.at(i).get(), results);
678 }
679}
680
681// Returns true if the number of results is now >= the number of results desired.
682bool AccessibilityObject::objectMatchesSearchCriteriaWithResultLimit(AccessibilityObject* object, AccessibilitySearchCriteria* criteria, AccessibilityChildrenVector& results)
683{
684 if (isAccessibilityObjectSearchMatch(object, criteria) && isAccessibilityTextSearchMatch(object, criteria)) {
685 results.append(object);
686
687 // Enough results were found to stop searching.
688 if (results.size() >= criteria->resultsLimit)
689 return true;
690 }
691
692 return false;
693}
694
695void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* criteria, AccessibilityChildrenVector& results)
696{
697 ASSERT(criteria);
698
699 if (!criteria)
700 return;
701
702 if (AXObjectCache* cache = axObjectCache())
703 cache->startCachingComputedObjectAttributesUntilTreeMutates();
704
705 // This search mechanism only searches the elements before/after the starting object.
706 // It does this by stepping up the parent chain and at each level doing a DFS.
707
708 // If there's no start object, it means we want to search everything.
709 AccessibilityObject* startObject = criteria->startObject;
710 if (!startObject)
711 startObject = this;
712
713 bool isForward = criteria->searchDirection == AccessibilitySearchDirection::Next;
714
715 // The first iteration of the outer loop will examine the children of the start object for matches. However, when
716 // iterating backwards, the start object children should not be considered, so the loop is skipped ahead. We make an
717 // exception when no start object was specified because we want to search everything regardless of search direction.
718 AccessibilityObject* previousObject = nullptr;
719 if (!isForward && startObject != this) {
720 previousObject = startObject;
721 startObject = startObject->parentObjectUnignored();
722 }
723
724 // The outer loop steps up the parent chain each time (unignored is important here because otherwise elements would be searched twice)
725 for (AccessibilityObject* stopSearchElement = parentObjectUnignored(); startObject && startObject != stopSearchElement; startObject = startObject->parentObjectUnignored()) {
726
727 // Only append the children after/before the previous element, so that the search does not check elements that are
728 // already behind/ahead of start element.
729 AccessibilityChildrenVector searchStack;
730 if (!criteria->immediateDescendantsOnly || startObject == this)
731 appendChildrenToArray(startObject, isForward, previousObject, searchStack);
732
733 // This now does a DFS at the current level of the parent.
734 while (!searchStack.isEmpty()) {
735 AccessibilityObject* searchObject = searchStack.last().get();
736 searchStack.removeLast();
737
738 if (objectMatchesSearchCriteriaWithResultLimit(searchObject, criteria, results))
739 break;
740
741 if (!criteria->immediateDescendantsOnly)
742 appendChildrenToArray(searchObject, isForward, 0, searchStack);
743 }
744
745 if (results.size() >= criteria->resultsLimit)
746 break;
747
748 // When moving backwards, the parent object needs to be checked, because technically it's "before" the starting element.
749 if (!isForward && startObject != this && objectMatchesSearchCriteriaWithResultLimit(startObject, criteria, results))
750 break;
751
752 previousObject = startObject;
753 }
754}
755
756// Returns the range that is fewer positions away from the reference range.
757// NOTE: The after range is expected to ACTUALLY be after the reference range and the before
758// range is expected to ACTUALLY be before. These are not checked for performance reasons.
759static RefPtr<Range> rangeClosestToRange(RefPtr<Range> const& referenceRange, RefPtr<Range>&& afterRange, RefPtr<Range>&& beforeRange)
760{
761 if (!referenceRange)
762 return nullptr;
763
764 // The treeScope for shadow nodes may not be the same scope as another element in a document.
765 // Comparisons may fail in that case, which are expected behavior and should not assert.
766 if (afterRange && (referenceRange->endPosition().isNull() || ((afterRange->startPosition().anchorNode()->compareDocumentPosition(*referenceRange->endPosition().anchorNode()) & Node::DOCUMENT_POSITION_DISCONNECTED) == Node::DOCUMENT_POSITION_DISCONNECTED)))
767 return nullptr;
768 ASSERT(!afterRange || afterRange->compareBoundaryPoints(Range::START_TO_START, *referenceRange).releaseReturnValue() >= 0);
769
770 if (beforeRange && (referenceRange->startPosition().isNull() || ((beforeRange->endPosition().anchorNode()->compareDocumentPosition(*referenceRange->startPosition().anchorNode()) & Node::DOCUMENT_POSITION_DISCONNECTED) == Node::DOCUMENT_POSITION_DISCONNECTED)))
771 return nullptr;
772 ASSERT(!beforeRange || beforeRange->compareBoundaryPoints(Range::START_TO_START, *referenceRange).releaseReturnValue() <= 0);
773
774 if (!afterRange && !beforeRange)
775 return nullptr;
776 if (afterRange && !beforeRange)
777 return WTFMove(afterRange);
778 if (!afterRange && beforeRange)
779 return WTFMove(beforeRange);
780
781 unsigned positionsToAfterRange = Position::positionCountBetweenPositions(afterRange->startPosition(), referenceRange->endPosition());
782 unsigned positionsToBeforeRange = Position::positionCountBetweenPositions(beforeRange->endPosition(), referenceRange->startPosition());
783
784 return positionsToAfterRange < positionsToBeforeRange ? afterRange : beforeRange;
785}
786
787RefPtr<Range> AccessibilityObject::rangeOfStringClosestToRangeInDirection(Range* referenceRange, AccessibilitySearchDirection searchDirection, Vector<String> const& searchStrings) const
788{
789 Frame* frame = this->frame();
790 if (!frame)
791 return nullptr;
792
793 if (!referenceRange)
794 return nullptr;
795
796 bool isBackwardSearch = searchDirection == AccessibilitySearchDirection::Previous;
797 FindOptions findOptions { AtWordStarts, AtWordEnds, CaseInsensitive, StartInSelection };
798 if (isBackwardSearch)
799 findOptions.add(FindOptionFlag::Backwards);
800
801 RefPtr<Range> closestStringRange = nullptr;
802 for (const auto& searchString : searchStrings) {
803 if (RefPtr<Range> searchStringRange = frame->editor().rangeOfString(searchString, referenceRange, findOptions)) {
804 if (!closestStringRange)
805 closestStringRange = searchStringRange;
806 else {
807 // If searching backward, use the trailing range edges to correctly determine which
808 // range is closest. Similarly, if searching forward, use the leading range edges.
809 Position closestStringPosition = isBackwardSearch ? closestStringRange->endPosition() : closestStringRange->startPosition();
810 Position searchStringPosition = isBackwardSearch ? searchStringRange->endPosition() : searchStringRange->startPosition();
811
812 int closestPositionOffset = closestStringPosition.computeOffsetInContainerNode();
813 int searchPositionOffset = searchStringPosition.computeOffsetInContainerNode();
814 Node* closestContainerNode = closestStringPosition.containerNode();
815 Node* searchContainerNode = searchStringPosition.containerNode();
816
817 short result = Range::compareBoundaryPoints(closestContainerNode, closestPositionOffset, searchContainerNode, searchPositionOffset).releaseReturnValue();
818 if ((!isBackwardSearch && result > 0) || (isBackwardSearch && result < 0))
819 closestStringRange = searchStringRange;
820 }
821 }
822 }
823 return closestStringRange;
824}
825
826// Returns the range of the entire document if there is no selection.
827RefPtr<Range> AccessibilityObject::selectionRange() const
828{
829 Frame* frame = this->frame();
830 if (!frame)
831 return nullptr;
832
833 const VisibleSelection& selection = frame->selection().selection();
834 if (!selection.isNone())
835 return selection.firstRange();
836
837 return Range::create(*frame->document());
838}
839
840RefPtr<Range> AccessibilityObject::elementRange() const
841{
842 return AXObjectCache::rangeForNodeContents(node());
843}
844
845RefPtr<Range> AccessibilityObject::findTextRange(Vector<String> const& searchStrings, RefPtr<Range> const& start, AccessibilitySearchTextDirection direction) const
846{
847 RefPtr<Range> found;
848 if (direction == AccessibilitySearchTextDirection::Forward)
849 found = rangeOfStringClosestToRangeInDirection(start.get(), AccessibilitySearchDirection::Next, searchStrings);
850 else if (direction == AccessibilitySearchTextDirection::Backward)
851 found = rangeOfStringClosestToRangeInDirection(start.get(), AccessibilitySearchDirection::Previous, searchStrings);
852 else if (direction == AccessibilitySearchTextDirection::Closest) {
853 auto foundAfter = rangeOfStringClosestToRangeInDirection(start.get(), AccessibilitySearchDirection::Next, searchStrings);
854 auto foundBefore = rangeOfStringClosestToRangeInDirection(start.get(), AccessibilitySearchDirection::Previous, searchStrings);
855 found = rangeClosestToRange(start.get(), WTFMove(foundAfter), WTFMove(foundBefore));
856 }
857
858 if (found) {
859 // If the search started within a text control, ensure that the result is inside that element.
860 if (element() && element()->isTextField()) {
861 if (!found->startContainer().isDescendantOrShadowDescendantOf(element())
862 || !found->endContainer().isDescendantOrShadowDescendantOf(element()))
863 return nullptr;
864 }
865 }
866 return found;
867}
868
869Vector<RefPtr<Range>> AccessibilityObject::findTextRanges(AccessibilitySearchTextCriteria const& criteria) const
870{
871 Vector<RefPtr<Range>> result;
872
873 // Determine start range.
874 RefPtr<Range> startRange;
875 if (criteria.start == AccessibilitySearchTextStartFrom::Selection)
876 startRange = selectionRange();
877 else
878 startRange = elementRange();
879
880 if (startRange) {
881 // Collapse the range to the start unless searching from the end of the doc or searching backwards.
882 if (criteria.start == AccessibilitySearchTextStartFrom::Begin)
883 startRange->collapse(true);
884 else if (criteria.start == AccessibilitySearchTextStartFrom::End)
885 startRange->collapse(false);
886 else
887 startRange->collapse(criteria.direction != AccessibilitySearchTextDirection::Backward);
888 } else
889 return result;
890
891 RefPtr<Range> found;
892 switch (criteria.direction) {
893 case AccessibilitySearchTextDirection::Forward:
894 case AccessibilitySearchTextDirection::Backward:
895 case AccessibilitySearchTextDirection::Closest:
896 found = findTextRange(criteria.searchStrings, startRange, criteria.direction);
897 if (found)
898 result.append(found);
899 break;
900 case AccessibilitySearchTextDirection::All: {
901 auto findAll = [&](AccessibilitySearchTextDirection dir) {
902 found = findTextRange(criteria.searchStrings, startRange, dir);
903 while (found) {
904 result.append(found);
905 found = findTextRange(criteria.searchStrings, found, dir);
906 }
907 };
908 findAll(AccessibilitySearchTextDirection::Forward);
909 findAll(AccessibilitySearchTextDirection::Backward);
910 break;
911 }
912 }
913
914 return result;
915}
916
917Vector<String> AccessibilityObject::performTextOperation(AccessibilityTextOperation const& operation)
918{
919 Vector<String> result;
920
921 if (operation.textRanges.isEmpty())
922 return result;
923
924 Frame* frame = this->frame();
925 if (!frame)
926 return result;
927
928 for (auto textRange : operation.textRanges) {
929 if (!frame->selection().setSelectedRange(textRange.get(), DOWNSTREAM, FrameSelection::ShouldCloseTyping::Yes))
930 continue;
931
932 String text = textRange->text();
933 String replacementString = operation.replacementText;
934 bool replaceSelection = false;
935 switch (operation.type) {
936 case AccessibilityTextOperationType::Capitalize:
937 replacementString = capitalize(text, ' '); // FIXME: Needs to take locale into account to work correctly.
938 replaceSelection = true;
939 break;
940 case AccessibilityTextOperationType::Uppercase:
941 replacementString = text.convertToUppercaseWithoutLocale(); // FIXME: Needs locale to work correctly.
942 replaceSelection = true;
943 break;
944 case AccessibilityTextOperationType::Lowercase:
945 replacementString = text.convertToLowercaseWithoutLocale(); // FIXME: Needs locale to work correctly.
946 replaceSelection = true;
947 break;
948 case AccessibilityTextOperationType::Replace: {
949 replaceSelection = true;
950 // When applying find and replace activities, we want to match the capitalization of the replaced text,
951 // (unless we're replacing with an abbreviation.)
952 if (text.length() > 0
953 && replacementString.length() > 2
954 && replacementString != replacementString.convertToUppercaseWithoutLocale()) {
955 if (text[0] == u_toupper(text[0]))
956 replacementString = capitalize(replacementString, ' '); // FIXME: Needs to take locale into account to work correctly.
957 else
958 replacementString = replacementString.convertToLowercaseWithoutLocale(); // FIXME: Needs locale to work correctly.
959 }
960 break;
961 }
962 case AccessibilityTextOperationType::Select:
963 break;
964 }
965
966 // A bit obvious, but worth noting the API contract for this method is that we should
967 // return the replacement string when replacing, but the selected string if not.
968 if (replaceSelection) {
969 frame->editor().replaceSelectionWithText(replacementString, Editor::SelectReplacement::Yes, Editor::SmartReplace::Yes);
970 result.append(replacementString);
971 } else
972 result.append(text);
973 }
974
975 return result;
976}
977
978bool AccessibilityObject::hasAttributesRequiredForInclusion() const
979{
980 // These checks are simplified in the interest of execution speed.
981 if (!getAttribute(aria_helpAttr).isEmpty()
982 || !getAttribute(aria_describedbyAttr).isEmpty()
983 || !getAttribute(altAttr).isEmpty()
984 || !getAttribute(titleAttr).isEmpty())
985 return true;
986
987#if ENABLE(MATHML)
988 if (!getAttribute(MathMLNames::alttextAttr).isEmpty())
989 return true;
990#endif
991
992 return false;
993}
994
995bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole)
996{
997 return ariaRole == AccessibilityRole::RadioButton || ariaRole == AccessibilityRole::CheckBox || ariaRole == AccessibilityRole::TextField || ariaRole == AccessibilityRole::Switch || ariaRole == AccessibilityRole::SearchField;
998}
999
1000bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole)
1001{
1002 return isARIAInput(ariaRole) || ariaRole == AccessibilityRole::TextArea || ariaRole == AccessibilityRole::Button || ariaRole == AccessibilityRole::ComboBox || ariaRole == AccessibilityRole::Slider || ariaRole == AccessibilityRole::ListBox;
1003}
1004
1005bool AccessibilityObject::isRangeControl() const
1006{
1007 switch (roleValue()) {
1008 case AccessibilityRole::Meter:
1009 case AccessibilityRole::ProgressIndicator:
1010 case AccessibilityRole::Slider:
1011 case AccessibilityRole::ScrollBar:
1012 case AccessibilityRole::SpinButton:
1013 return true;
1014 case AccessibilityRole::Splitter:
1015 return canSetFocusAttribute();
1016 default:
1017 return false;
1018 }
1019}
1020
1021bool AccessibilityObject::isMeter() const
1022{
1023 if (ariaRoleAttribute() == AccessibilityRole::Meter)
1024 return true;
1025
1026#if ENABLE(METER_ELEMENT)
1027 RenderObject* renderer = this->renderer();
1028 return renderer && renderer->isMeter();
1029#else
1030 return false;
1031#endif
1032}
1033
1034IntPoint AccessibilityObject::clickPoint()
1035{
1036 LayoutRect rect = elementRect();
1037 return roundedIntPoint(LayoutPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2));
1038}
1039
1040IntRect AccessibilityObject::boundingBoxForQuads(RenderObject* obj, const Vector<FloatQuad>& quads)
1041{
1042 ASSERT(obj);
1043 if (!obj)
1044 return IntRect();
1045
1046 FloatRect result;
1047 for (const auto& quad : quads) {
1048 FloatRect r = quad.enclosingBoundingBox();
1049 if (!r.isEmpty()) {
1050 if (obj->style().hasAppearance())
1051 obj->theme().adjustRepaintRect(*obj, r);
1052 result.unite(r);
1053 }
1054 }
1055 return snappedIntRect(LayoutRect(result));
1056}
1057
1058bool AccessibilityObject::press()
1059{
1060 // The presence of the actionElement will confirm whether we should even attempt a press.
1061 Element* actionElem = actionElement();
1062 if (!actionElem)
1063 return false;
1064 if (Frame* f = actionElem->document().frame())
1065 f->loader().resetMultipleFormSubmissionProtection();
1066
1067 // Hit test at this location to determine if there is a sub-node element that should act
1068 // as the target of the action.
1069 Element* hitTestElement = nullptr;
1070 Document* document = this->document();
1071 if (document) {
1072 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AccessibilityHitTest);
1073 HitTestResult hitTestResult(clickPoint());
1074 document->hitTest(request, hitTestResult);
1075 if (auto* innerNode = hitTestResult.innerNode()) {
1076 if (auto* shadowHost = innerNode->shadowHost())
1077 hitTestElement = shadowHost;
1078 else if (is<Element>(*innerNode))
1079 hitTestElement = &downcast<Element>(*innerNode);
1080 else
1081 hitTestElement = innerNode->parentElement();
1082 }
1083 }
1084
1085 // Prefer the actionElement instead of this node, if the actionElement is inside this node.
1086 Element* pressElement = this->element();
1087 if (!pressElement || actionElem->isDescendantOf(*pressElement))
1088 pressElement = actionElem;
1089
1090 ASSERT(pressElement);
1091 // Prefer the hit test element, if it is inside the target element.
1092 if (hitTestElement && hitTestElement->isDescendantOf(*pressElement))
1093 pressElement = hitTestElement;
1094
1095 UserGestureIndicator gestureIndicator(ProcessingUserGesture, document);
1096
1097 bool dispatchedTouchEvent = false;
1098#if PLATFORM(IOS_FAMILY)
1099 if (hasTouchEventListener())
1100 dispatchedTouchEvent = dispatchTouchEvent();
1101#endif
1102 if (!dispatchedTouchEvent)
1103 pressElement->accessKeyAction(true);
1104
1105 return true;
1106}
1107
1108bool AccessibilityObject::dispatchTouchEvent()
1109{
1110#if ENABLE(IOS_TOUCH_EVENTS)
1111 if (auto* frame = mainFrame())
1112 return frame->eventHandler().dispatchSimulatedTouchEvent(clickPoint());
1113#endif
1114 return false;
1115}
1116
1117Frame* AccessibilityObject::frame() const
1118{
1119 Node* node = this->node();
1120 return node ? node->document().frame() : nullptr;
1121}
1122
1123Frame* AccessibilityObject::mainFrame() const
1124{
1125 Document* document = topDocument();
1126 if (!document)
1127 return nullptr;
1128
1129 Frame* frame = document->frame();
1130 if (!frame)
1131 return nullptr;
1132
1133 return &frame->mainFrame();
1134}
1135
1136Document* AccessibilityObject::topDocument() const
1137{
1138 if (!document())
1139 return nullptr;
1140 return &document()->topDocument();
1141}
1142
1143String AccessibilityObject::language() const
1144{
1145 const AtomString& lang = getAttribute(langAttr);
1146 if (!lang.isEmpty())
1147 return lang;
1148
1149 AccessibilityObject* parent = parentObject();
1150
1151 // as a last resort, fall back to the content language specified in the meta tag
1152 if (!parent) {
1153 Document* doc = document();
1154 if (doc)
1155 return doc->contentLanguage();
1156 return nullAtom();
1157 }
1158
1159 return parent->language();
1160}
1161
1162VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& visiblePos1, const VisiblePosition& visiblePos2) const
1163{
1164 if (visiblePos1.isNull() || visiblePos2.isNull())
1165 return VisiblePositionRange();
1166
1167 // If there's no common tree scope between positions, return early.
1168 if (!commonTreeScope(visiblePos1.deepEquivalent().deprecatedNode(), visiblePos2.deepEquivalent().deprecatedNode()))
1169 return VisiblePositionRange();
1170
1171 VisiblePosition startPos;
1172 VisiblePosition endPos;
1173 bool alreadyInOrder;
1174
1175 // upstream is ordered before downstream for the same position
1176 if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM)
1177 alreadyInOrder = false;
1178
1179 // use selection order to see if the positions are in order
1180 else
1181 alreadyInOrder = VisibleSelection(visiblePos1, visiblePos2).isBaseFirst();
1182
1183 if (alreadyInOrder) {
1184 startPos = visiblePos1;
1185 endPos = visiblePos2;
1186 } else {
1187 startPos = visiblePos2;
1188 endPos = visiblePos1;
1189 }
1190
1191 return VisiblePositionRange(startPos, endPos);
1192}
1193
1194VisiblePositionRange AccessibilityObject::positionOfLeftWord(const VisiblePosition& visiblePos) const
1195{
1196 VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary);
1197 VisiblePosition endPosition = endOfWord(startPosition);
1198 return VisiblePositionRange(startPosition, endPosition);
1199}
1200
1201VisiblePositionRange AccessibilityObject::positionOfRightWord(const VisiblePosition& visiblePos) const
1202{
1203 VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary);
1204 VisiblePosition endPosition = endOfWord(startPosition);
1205 return VisiblePositionRange(startPosition, endPosition);
1206}
1207
1208static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition)
1209{
1210 // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line.
1211 // So let's update the position to include that.
1212 VisiblePosition tempPosition;
1213 VisiblePosition startPosition = visiblePosition;
1214 while (true) {
1215 tempPosition = startPosition.previous();
1216 if (tempPosition.isNull())
1217 break;
1218 Position p = tempPosition.deepEquivalent();
1219 RenderObject* renderer = p.deprecatedNode()->renderer();
1220 if (!renderer || (renderer->isRenderBlock() && !p.deprecatedEditingOffset()))
1221 break;
1222 if (!RenderedPosition(tempPosition).isNull())
1223 break;
1224 startPosition = tempPosition;
1225 }
1226
1227 return startPosition;
1228}
1229
1230VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const
1231{
1232 if (visiblePos.isNull())
1233 return VisiblePositionRange();
1234
1235 // make a caret selection for the position before marker position (to make sure
1236 // we move off of a line start)
1237 VisiblePosition prevVisiblePos = visiblePos.previous();
1238 if (prevVisiblePos.isNull())
1239 return VisiblePositionRange();
1240
1241 VisiblePosition startPosition = startOfLine(prevVisiblePos);
1242
1243 // keep searching for a valid line start position. Unless the VisiblePosition is at the very beginning, there should
1244 // always be a valid line range. However, startOfLine will return null for position next to a floating object,
1245 // since floating object doesn't really belong to any line.
1246 // This check will reposition the marker before the floating object, to ensure we get a line start.
1247 if (startPosition.isNull()) {
1248 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
1249 prevVisiblePos = prevVisiblePos.previous();
1250 startPosition = startOfLine(prevVisiblePos);
1251 }
1252 } else
1253 startPosition = updateAXLineStartForVisiblePosition(startPosition);
1254
1255 VisiblePosition endPosition = endOfLine(prevVisiblePos);
1256 return VisiblePositionRange(startPosition, endPosition);
1257}
1258
1259VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const
1260{
1261 if (visiblePos.isNull())
1262 return VisiblePositionRange();
1263
1264 // make sure we move off of a line end
1265 VisiblePosition nextVisiblePos = visiblePos.next();
1266 if (nextVisiblePos.isNull())
1267 return VisiblePositionRange();
1268
1269 VisiblePosition startPosition = startOfLine(nextVisiblePos);
1270
1271 // fetch for a valid line start position
1272 if (startPosition.isNull()) {
1273 startPosition = visiblePos;
1274 nextVisiblePos = nextVisiblePos.next();
1275 } else
1276 startPosition = updateAXLineStartForVisiblePosition(startPosition);
1277
1278 VisiblePosition endPosition = endOfLine(nextVisiblePos);
1279
1280 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
1281 // Unless the VisiblePosition is at the very end, there should always be a valid line range. However, endOfLine will
1282 // return null for position by a floating object, since floating object doesn't really belong to any line.
1283 // This check will reposition the marker after the floating object, to ensure we get a line end.
1284 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
1285 nextVisiblePos = nextVisiblePos.next();
1286 endPosition = endOfLine(nextVisiblePos);
1287 }
1288
1289 return VisiblePositionRange(startPosition, endPosition);
1290}
1291
1292VisiblePositionRange AccessibilityObject::sentenceForPosition(const VisiblePosition& visiblePos) const
1293{
1294 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
1295 // Related? <rdar://problem/3927736> Text selection broken in 8A336
1296 VisiblePosition startPosition = startOfSentence(visiblePos);
1297 VisiblePosition endPosition = endOfSentence(startPosition);
1298 return VisiblePositionRange(startPosition, endPosition);
1299}
1300
1301VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const
1302{
1303 VisiblePosition startPosition = startOfParagraph(visiblePos);
1304 VisiblePosition endPosition = endOfParagraph(startPosition);
1305 return VisiblePositionRange(startPosition, endPosition);
1306}
1307
1308static VisiblePosition startOfStyleRange(const VisiblePosition& visiblePos)
1309{
1310 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer();
1311 RenderObject* startRenderer = renderer;
1312 auto* style = &renderer->style();
1313
1314 // traverse backward by renderer to look for style change
1315 for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) {
1316 // skip non-leaf nodes
1317 if (r->firstChildSlow())
1318 continue;
1319
1320 // stop at style change
1321 if (&r->style() != style)
1322 break;
1323
1324 // remember match
1325 startRenderer = r;
1326 }
1327
1328 return firstPositionInOrBeforeNode(startRenderer->node());
1329}
1330
1331static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos)
1332{
1333 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer();
1334 RenderObject* endRenderer = renderer;
1335 const RenderStyle& style = renderer->style();
1336
1337 // traverse forward by renderer to look for style change
1338 for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) {
1339 // skip non-leaf nodes
1340 if (r->firstChildSlow())
1341 continue;
1342
1343 // stop at style change
1344 if (&r->style() != &style)
1345 break;
1346
1347 // remember match
1348 endRenderer = r;
1349 }
1350
1351 return lastPositionInOrAfterNode(endRenderer->node());
1352}
1353
1354VisiblePositionRange AccessibilityObject::styleRangeForPosition(const VisiblePosition& visiblePos) const
1355{
1356 if (visiblePos.isNull())
1357 return VisiblePositionRange();
1358
1359 return VisiblePositionRange(startOfStyleRange(visiblePos), endOfStyleRange(visiblePos));
1360}
1361
1362// NOTE: Consider providing this utility method as AX API
1363VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const PlainTextRange& range) const
1364{
1365 unsigned textLength = getLengthForTextRange();
1366 if (range.start + range.length > textLength)
1367 return VisiblePositionRange();
1368
1369 VisiblePosition startPosition = visiblePositionForIndex(range.start);
1370 startPosition.setAffinity(DOWNSTREAM);
1371 VisiblePosition endPosition = visiblePositionForIndex(range.start + range.length);
1372 return VisiblePositionRange(startPosition, endPosition);
1373}
1374
1375RefPtr<Range> AccessibilityObject::rangeForPlainTextRange(const PlainTextRange& range) const
1376{
1377 unsigned textLength = getLengthForTextRange();
1378 if (range.start + range.length > textLength)
1379 return nullptr;
1380
1381 if (AXObjectCache* cache = axObjectCache()) {
1382 CharacterOffset start = cache->characterOffsetForIndex(range.start, this);
1383 CharacterOffset end = cache->characterOffsetForIndex(range.start + range.length, this);
1384 return cache->rangeForUnorderedCharacterOffsets(start, end);
1385 }
1386 return nullptr;
1387}
1388
1389VisiblePositionRange AccessibilityObject::lineRangeForPosition(const VisiblePosition& visiblePosition) const
1390{
1391 VisiblePosition startPosition = startOfLine(visiblePosition);
1392 VisiblePosition endPosition = endOfLine(visiblePosition);
1393 return VisiblePositionRange(startPosition, endPosition);
1394}
1395
1396bool AccessibilityObject::replacedNodeNeedsCharacter(Node* replacedNode)
1397{
1398 // we should always be given a rendered node and a replaced node, but be safe
1399 // replaced nodes are either attachments (widgets) or images
1400 if (!replacedNode || !isRendererReplacedElement(replacedNode->renderer()) || replacedNode->isTextNode())
1401 return false;
1402
1403 // create an AX object, but skip it if it is not supposed to be seen
1404 AccessibilityObject* object = replacedNode->renderer()->document().axObjectCache()->getOrCreate(replacedNode);
1405 if (object->accessibilityIsIgnored())
1406 return false;
1407
1408 return true;
1409}
1410
1411// Finds a RenderListItem parent give a node.
1412static RenderListItem* renderListItemContainerForNode(Node* node)
1413{
1414 for (; node; node = node->parentNode()) {
1415 RenderBoxModelObject* renderer = node->renderBoxModelObject();
1416 if (is<RenderListItem>(renderer))
1417 return downcast<RenderListItem>(renderer);
1418 }
1419 return nullptr;
1420}
1421
1422static String listMarkerTextForNode(Node* node)
1423{
1424 RenderListItem* listItem = renderListItemContainerForNode(node);
1425 if (!listItem)
1426 return String();
1427
1428 // If this is in a list item, we need to manually add the text for the list marker
1429 // because a RenderListMarker does not have a Node equivalent and thus does not appear
1430 // when iterating text.
1431 return listItem->markerTextWithSuffix();
1432}
1433
1434// Returns the text associated with a list marker if this node is contained within a list item.
1435String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart)
1436{
1437 // If the range does not contain the start of the line, the list marker text should not be included.
1438 if (!isStartOfLine(visiblePositionStart))
1439 return String();
1440
1441 // We should speak the list marker only for the first line.
1442 RenderListItem* listItem = renderListItemContainerForNode(node);
1443 if (!listItem)
1444 return String();
1445 if (!inSameLine(visiblePositionStart, firstPositionInNode(&listItem->element())))
1446 return String();
1447
1448 return listMarkerTextForNode(node);
1449}
1450
1451String AccessibilityObject::stringForRange(RefPtr<Range> range) const
1452{
1453 if (!range)
1454 return String();
1455
1456 TextIterator it(range.get());
1457 if (it.atEnd())
1458 return String();
1459
1460 StringBuilder builder;
1461 for (; !it.atEnd(); it.advance()) {
1462 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
1463 if (it.text().length()) {
1464 // Add a textual representation for list marker text.
1465 // Don't add list marker text for new line character.
1466 if (it.text().length() != 1 || !isSpaceOrNewline(it.text()[0]))
1467 builder.append(listMarkerTextForNodeAndPosition(it.node(), VisiblePosition(range->startPosition())));
1468 it.appendTextToStringBuilder(builder);
1469 } else {
1470 // locate the node and starting offset for this replaced range
1471 Node& node = it.range()->startContainer();
1472 ASSERT(&node == &it.range()->endContainer());
1473 int offset = it.range()->startOffset();
1474 if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset)))
1475 builder.append(objectReplacementCharacter);
1476 }
1477 }
1478
1479 return builder.toString();
1480}
1481
1482String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange)
1483{
1484 if (visiblePositionRange.isNull())
1485 return String();
1486
1487 StringBuilder builder;
1488 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
1489 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
1490 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
1491 if (it.text().length()) {
1492 // Add a textual representation for list marker text.
1493 builder.append(listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start));
1494 it.appendTextToStringBuilder(builder);
1495 } else {
1496 // locate the node and starting offset for this replaced range
1497 Node& node = it.range()->startContainer();
1498 ASSERT(&node == &it.range()->endContainer());
1499 int offset = it.range()->startOffset();
1500 if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset)))
1501 builder.append(objectReplacementCharacter);
1502 }
1503 }
1504
1505 return builder.toString();
1506}
1507
1508int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
1509{
1510 // FIXME: Multi-byte support
1511 if (visiblePositionRange.isNull())
1512 return -1;
1513
1514 int length = 0;
1515 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
1516 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
1517 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
1518 if (it.text().length())
1519 length += it.text().length();
1520 else {
1521 // locate the node and starting offset for this replaced range
1522 Node& node = it.range()->startContainer();
1523 ASSERT(&node == &it.range()->endContainer());
1524 int offset = it.range()->startOffset();
1525
1526 if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset)))
1527 ++length;
1528 }
1529 }
1530
1531 return length;
1532}
1533
1534VisiblePosition AccessibilityObject::visiblePositionForBounds(const IntRect& rect, AccessibilityVisiblePositionForBounds visiblePositionForBounds) const
1535{
1536 if (rect.isEmpty())
1537 return VisiblePosition();
1538
1539 auto* mainFrame = this->mainFrame();
1540 if (!mainFrame)
1541 return VisiblePosition();
1542
1543 // FIXME: Add support for right-to-left languages.
1544 IntPoint corner = (visiblePositionForBounds == AccessibilityVisiblePositionForBounds::First) ? rect.minXMinYCorner() : rect.maxXMaxYCorner();
1545 VisiblePosition position = mainFrame->visiblePositionForPoint(corner);
1546
1547 if (rect.contains(position.absoluteCaretBounds().center()))
1548 return position;
1549
1550 // If the initial position is located outside the bounds adjust it incrementally as needed.
1551 VisiblePosition nextPosition = position.next();
1552 VisiblePosition previousPosition = position.previous();
1553 while (nextPosition.isNotNull() || previousPosition.isNotNull()) {
1554 if (rect.contains(nextPosition.absoluteCaretBounds().center()))
1555 return nextPosition;
1556 if (rect.contains(previousPosition.absoluteCaretBounds().center()))
1557 return previousPosition;
1558
1559 nextPosition = nextPosition.next();
1560 previousPosition = previousPosition.previous();
1561 }
1562
1563 return VisiblePosition();
1564}
1565
1566VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const
1567{
1568 if (visiblePos.isNull())
1569 return VisiblePosition();
1570
1571 // make sure we move off of a word end
1572 VisiblePosition nextVisiblePos = visiblePos.next();
1573 if (nextVisiblePos.isNull())
1574 return VisiblePosition();
1575
1576 return endOfWord(nextVisiblePos, LeftWordIfOnBoundary);
1577}
1578
1579VisiblePosition AccessibilityObject::previousWordStart(const VisiblePosition& visiblePos) const
1580{
1581 if (visiblePos.isNull())
1582 return VisiblePosition();
1583
1584 // make sure we move off of a word start
1585 VisiblePosition prevVisiblePos = visiblePos.previous();
1586 if (prevVisiblePos.isNull())
1587 return VisiblePosition();
1588
1589 return startOfWord(prevVisiblePos, RightWordIfOnBoundary);
1590}
1591
1592VisiblePosition AccessibilityObject::nextLineEndPosition(const VisiblePosition& visiblePos) const
1593{
1594 if (visiblePos.isNull())
1595 return VisiblePosition();
1596
1597 // to make sure we move off of a line end
1598 VisiblePosition nextVisiblePos = visiblePos.next();
1599 if (nextVisiblePos.isNull())
1600 return VisiblePosition();
1601
1602 VisiblePosition endPosition = endOfLine(nextVisiblePos);
1603
1604 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
1605 // There are cases like when the position is next to a floating object that'll return null for end of line. This code will avoid returning null.
1606 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
1607 nextVisiblePos = nextVisiblePos.next();
1608 endPosition = endOfLine(nextVisiblePos);
1609 }
1610
1611 return endPosition;
1612}
1613
1614VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const
1615{
1616 if (visiblePos.isNull())
1617 return VisiblePosition();
1618
1619 // make sure we move off of a line start
1620 VisiblePosition prevVisiblePos = visiblePos.previous();
1621 if (prevVisiblePos.isNull())
1622 return VisiblePosition();
1623
1624 VisiblePosition startPosition = startOfLine(prevVisiblePos);
1625
1626 // as long as the position hasn't reached the beginning of the doc, keep searching for a valid line start position
1627 // There are cases like when the position is next to a floating object that'll return null for start of line. This code will avoid returning null.
1628 if (startPosition.isNull()) {
1629 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
1630 prevVisiblePos = prevVisiblePos.previous();
1631 startPosition = startOfLine(prevVisiblePos);
1632 }
1633 } else
1634 startPosition = updateAXLineStartForVisiblePosition(startPosition);
1635
1636 return startPosition;
1637}
1638
1639VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const
1640{
1641 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
1642 // Related? <rdar://problem/3927736> Text selection broken in 8A336
1643 if (visiblePos.isNull())
1644 return VisiblePosition();
1645
1646 // make sure we move off of a sentence end
1647 VisiblePosition nextVisiblePos = visiblePos.next();
1648 if (nextVisiblePos.isNull())
1649 return VisiblePosition();
1650
1651 // an empty line is considered a sentence. If it's skipped, then the sentence parser will not
1652 // see this empty line. Instead, return the end position of the empty line.
1653 VisiblePosition endPosition;
1654
1655 String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get());
1656 if (lineString.isEmpty())
1657 endPosition = nextVisiblePos;
1658 else
1659 endPosition = endOfSentence(nextVisiblePos);
1660
1661 return endPosition;
1662}
1663
1664VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const
1665{
1666 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
1667 // Related? <rdar://problem/3927736> Text selection broken in 8A336
1668 if (visiblePos.isNull())
1669 return VisiblePosition();
1670
1671 // make sure we move off of a sentence start
1672 VisiblePosition previousVisiblePos = visiblePos.previous();
1673 if (previousVisiblePos.isNull())
1674 return VisiblePosition();
1675
1676 // treat empty line as a separate sentence.
1677 VisiblePosition startPosition;
1678
1679 String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get());
1680 if (lineString.isEmpty())
1681 startPosition = previousVisiblePos;
1682 else
1683 startPosition = startOfSentence(previousVisiblePos);
1684
1685 return startPosition;
1686}
1687
1688VisiblePosition AccessibilityObject::nextParagraphEndPosition(const VisiblePosition& visiblePos) const
1689{
1690 if (visiblePos.isNull())
1691 return VisiblePosition();
1692
1693 // make sure we move off of a paragraph end
1694 VisiblePosition nextPos = visiblePos.next();
1695 if (nextPos.isNull())
1696 return VisiblePosition();
1697
1698 return endOfParagraph(nextPos);
1699}
1700
1701VisiblePosition AccessibilityObject::previousParagraphStartPosition(const VisiblePosition& visiblePos) const
1702{
1703 if (visiblePos.isNull())
1704 return VisiblePosition();
1705
1706 // make sure we move off of a paragraph start
1707 VisiblePosition previousPos = visiblePos.previous();
1708 if (previousPos.isNull())
1709 return VisiblePosition();
1710
1711 return startOfParagraph(previousPos);
1712}
1713
1714AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const
1715{
1716 if (visiblePos.isNull())
1717 return nullptr;
1718
1719 RenderObject* obj = visiblePos.deepEquivalent().deprecatedNode()->renderer();
1720 if (!obj)
1721 return nullptr;
1722
1723 return obj->document().axObjectCache()->getOrCreate(obj);
1724}
1725
1726// If you call node->hasEditableStyle() since that will return true if an ancestor is editable.
1727// This only returns true if this is the element that actually has the contentEditable attribute set.
1728bool AccessibilityObject::hasContentEditableAttributeSet() const
1729{
1730 return contentEditableAttributeIsEnabled(element());
1731}
1732
1733bool AccessibilityObject::supportsReadOnly() const
1734{
1735 AccessibilityRole role = roleValue();
1736
1737 return role == AccessibilityRole::CheckBox
1738 || role == AccessibilityRole::ColumnHeader
1739 || role == AccessibilityRole::ComboBox
1740 || role == AccessibilityRole::Grid
1741 || role == AccessibilityRole::GridCell
1742 || role == AccessibilityRole::ListBox
1743 || role == AccessibilityRole::MenuItemCheckbox
1744 || role == AccessibilityRole::MenuItemRadio
1745 || role == AccessibilityRole::RadioGroup
1746 || role == AccessibilityRole::RowHeader
1747 || role == AccessibilityRole::SearchField
1748 || role == AccessibilityRole::Slider
1749 || role == AccessibilityRole::SpinButton
1750 || role == AccessibilityRole::Switch
1751 || role == AccessibilityRole::TextField
1752 || role == AccessibilityRole::TreeGrid
1753 || isPasswordField();
1754}
1755
1756String AccessibilityObject::readOnlyValue() const
1757{
1758 if (!hasAttribute(aria_readonlyAttr))
1759 return ariaRoleAttribute() != AccessibilityRole::Unknown && supportsReadOnly() ? "false" : String();
1760
1761 return getAttribute(aria_readonlyAttr).string().convertToASCIILowercase();
1762}
1763
1764bool AccessibilityObject::supportsAutoComplete() const
1765{
1766 return (isComboBox() || isARIATextControl()) && hasAttribute(aria_autocompleteAttr);
1767}
1768
1769String AccessibilityObject::autoCompleteValue() const
1770{
1771 const AtomString& autoComplete = getAttribute(aria_autocompleteAttr);
1772 if (equalLettersIgnoringASCIICase(autoComplete, "inline")
1773 || equalLettersIgnoringASCIICase(autoComplete, "list")
1774 || equalLettersIgnoringASCIICase(autoComplete, "both"))
1775 return autoComplete;
1776
1777 return "none";
1778}
1779
1780bool AccessibilityObject::contentEditableAttributeIsEnabled(Element* element)
1781{
1782 if (!element)
1783 return false;
1784
1785 const AtomString& contentEditableValue = element->attributeWithoutSynchronization(contenteditableAttr);
1786 if (contentEditableValue.isNull())
1787 return false;
1788
1789 // Both "true" (case-insensitive) and the empty string count as true.
1790 return contentEditableValue.isEmpty() || equalLettersIgnoringASCIICase(contentEditableValue, "true");
1791}
1792
1793#if HAVE(ACCESSIBILITY)
1794int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const
1795{
1796 if (visiblePos.isNull() || !node())
1797 return -1;
1798
1799 // If the position is not in the same editable region as this AX object, return -1.
1800 Node* containerNode = visiblePos.deepEquivalent().containerNode();
1801 if (!containerNode->containsIncludingShadowDOM(node()) && !node()->containsIncludingShadowDOM(containerNode))
1802 return -1;
1803
1804 int lineCount = -1;
1805 VisiblePosition currentVisiblePos = visiblePos;
1806 VisiblePosition savedVisiblePos;
1807
1808 // move up until we get to the top
1809 // FIXME: This only takes us to the top of the rootEditableElement, not the top of the
1810 // top document.
1811 do {
1812 savedVisiblePos = currentVisiblePos;
1813 VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0, HasEditableAXRole);
1814 currentVisiblePos = prevVisiblePos;
1815 ++lineCount;
1816 } while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos)));
1817
1818 return lineCount;
1819}
1820#endif
1821
1822// NOTE: Consider providing this utility method as AX API
1823PlainTextRange AccessibilityObject::plainTextRangeForVisiblePositionRange(const VisiblePositionRange& positionRange) const
1824{
1825 int index1 = index(positionRange.start);
1826 int index2 = index(positionRange.end);
1827 if (index1 < 0 || index2 < 0 || index1 > index2)
1828 return PlainTextRange();
1829
1830 return PlainTextRange(index1, index2 - index1);
1831}
1832
1833// The composed character range in the text associated with this accessibility object that
1834// is specified by the given screen coordinates. This parameterized attribute returns the
1835// complete range of characters (including surrogate pairs of multi-byte glyphs) at the given
1836// screen coordinates.
1837// NOTE: This varies from AppKit when the point is below the last line. AppKit returns an
1838// an error in that case. We return textControl->text().length(), 1. Does this matter?
1839PlainTextRange AccessibilityObject::doAXRangeForPosition(const IntPoint& point) const
1840{
1841 int i = index(visiblePositionForPoint(point));
1842 if (i < 0)
1843 return PlainTextRange();
1844
1845 return PlainTextRange(i, 1);
1846}
1847
1848// Given a character index, the range of text associated with this accessibility object
1849// over which the style in effect at that character index applies.
1850PlainTextRange AccessibilityObject::doAXStyleRangeForIndex(unsigned index) const
1851{
1852 VisiblePositionRange range = styleRangeForPosition(visiblePositionForIndex(index, false));
1853 return plainTextRangeForVisiblePositionRange(range);
1854}
1855
1856// Given an indexed character, the line number of the text associated with this accessibility
1857// object that contains the character.
1858unsigned AccessibilityObject::doAXLineForIndex(unsigned index)
1859{
1860 return lineForPosition(visiblePositionForIndex(index, false));
1861}
1862
1863#if HAVE(ACCESSIBILITY)
1864void AccessibilityObject::updateBackingStore()
1865{
1866 if (!axObjectCache())
1867 return;
1868
1869 // Updating the layout may delete this object.
1870 RefPtr<AccessibilityObject> protectedThis(this);
1871 if (auto* document = this->document()) {
1872 if (!document->view()->layoutContext().isInRenderTreeLayout() && !document->inRenderTreeUpdate() && !document->inStyleRecalc())
1873 document->updateLayoutIgnorePendingStylesheets();
1874 }
1875
1876 if (auto cache = axObjectCache())
1877 cache->performDeferredCacheUpdate();
1878
1879 updateChildrenIfNecessary();
1880}
1881#endif
1882
1883const AccessibilityScrollView* AccessibilityObject::ancestorAccessibilityScrollView(bool includeSelf) const
1884{
1885 return downcast<AccessibilityScrollView>(AccessibilityObject::matchedParent(*this, includeSelf, [] (const auto& object) {
1886 return is<AccessibilityScrollView>(object);
1887 }));
1888}
1889
1890ScrollView* AccessibilityObject::scrollViewAncestor() const
1891{
1892 if (auto parentScrollView = ancestorAccessibilityScrollView(true/* includeSelf */))
1893 return parentScrollView->scrollView();
1894
1895 return nullptr;
1896}
1897
1898Document* AccessibilityObject::document() const
1899{
1900 FrameView* frameView = documentFrameView();
1901 if (!frameView)
1902 return nullptr;
1903
1904 return frameView->frame().document();
1905}
1906
1907Page* AccessibilityObject::page() const
1908{
1909 Document* document = this->document();
1910 if (!document)
1911 return nullptr;
1912 return document->page();
1913}
1914
1915FrameView* AccessibilityObject::documentFrameView() const
1916{
1917 const AccessibilityObject* object = this;
1918 while (object && !object->isAccessibilityRenderObject())
1919 object = object->parentObject();
1920
1921 if (!object)
1922 return nullptr;
1923
1924 return object->documentFrameView();
1925}
1926
1927#if HAVE(ACCESSIBILITY)
1928const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::children(bool updateChildrenIfNeeded)
1929{
1930 if (updateChildrenIfNeeded)
1931 updateChildrenIfNecessary();
1932
1933 return m_children;
1934}
1935#endif
1936
1937void AccessibilityObject::updateChildrenIfNecessary()
1938{
1939 if (!hasChildren()) {
1940 // Enable the cache in case we end up adding a lot of children, we don't want to recompute axIsIgnored each time.
1941 AXAttributeCacheEnabler enableCache(axObjectCache());
1942 addChildren();
1943 }
1944}
1945
1946void AccessibilityObject::clearChildren()
1947{
1948 // Some objects have weak pointers to their parents and those associations need to be detached.
1949 for (const auto& child : m_children)
1950 child->detachFromParent();
1951
1952 m_children.clear();
1953 m_haveChildren = false;
1954}
1955
1956AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node)
1957{
1958 RenderObject* obj = node->renderer();
1959 if (!obj)
1960 return nullptr;
1961
1962 RefPtr<AccessibilityObject> axObj = obj->document().axObjectCache()->getOrCreate(obj);
1963 Element* anchor = axObj->anchorElement();
1964 if (!anchor)
1965 return nullptr;
1966
1967 RenderObject* anchorRenderer = anchor->renderer();
1968 if (!anchorRenderer)
1969 return nullptr;
1970
1971 return anchorRenderer->document().axObjectCache()->getOrCreate(anchorRenderer);
1972}
1973
1974AccessibilityObject* AccessibilityObject::headingElementForNode(Node* node)
1975{
1976 if (!node)
1977 return nullptr;
1978
1979 RenderObject* renderObject = node->renderer();
1980 if (!renderObject)
1981 return nullptr;
1982
1983 AccessibilityObject* axObject = renderObject->document().axObjectCache()->getOrCreate(renderObject);
1984
1985 return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*axObject, true, [] (const AccessibilityObject& object) {
1986 return object.roleValue() == AccessibilityRole::Heading;
1987 }));
1988}
1989
1990const AccessibilityObject* AccessibilityObject::matchedParent(const AccessibilityObject& object, bool includeSelf, const WTF::Function<bool(const AccessibilityObject&)>& matches)
1991{
1992 const AccessibilityObject* parent = includeSelf ? &object : object.parentObject();
1993 for (; parent; parent = parent->parentObject()) {
1994 if (matches(*parent))
1995 return parent;
1996 }
1997 return nullptr;
1998}
1999
2000void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result)
2001{
2002 for (const auto& child : children()) {
2003 // Add tree items as the rows.
2004 if (child->roleValue() == AccessibilityRole::TreeItem)
2005 result.append(child);
2006
2007 // Now see if this item also has rows hiding inside of it.
2008 child->ariaTreeRows(result);
2009 }
2010}
2011
2012void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result)
2013{
2014 // The ARIA tree item content are the item that are not other tree items or their containing groups.
2015 for (const auto& child : children()) {
2016 if (!child->isGroup() && child->roleValue() != AccessibilityRole::TreeItem)
2017 result.append(child);
2018 }
2019}
2020
2021void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result)
2022{
2023 for (const auto& obj : children()) {
2024 // Add tree items as the rows.
2025 if (obj->roleValue() == AccessibilityRole::TreeItem)
2026 result.append(obj);
2027 // If it's not a tree item, then descend into the group to find more tree items.
2028 else
2029 obj->ariaTreeRows(result);
2030 }
2031}
2032
2033const String AccessibilityObject::defaultLiveRegionStatusForRole(AccessibilityRole role)
2034{
2035 switch (role) {
2036 case AccessibilityRole::ApplicationAlertDialog:
2037 case AccessibilityRole::ApplicationAlert:
2038 return "assertive"_s;
2039 case AccessibilityRole::ApplicationLog:
2040 case AccessibilityRole::ApplicationStatus:
2041 return "polite"_s;
2042 case AccessibilityRole::ApplicationTimer:
2043 case AccessibilityRole::ApplicationMarquee:
2044 return "off"_s;
2045 default:
2046 return nullAtom();
2047 }
2048}
2049
2050#if HAVE(ACCESSIBILITY)
2051const String& AccessibilityObject::actionVerb() const
2052{
2053#if !PLATFORM(IOS_FAMILY)
2054 // FIXME: Need to add verbs for select elements.
2055 static NeverDestroyed<const String> buttonAction(AXButtonActionVerb());
2056 static NeverDestroyed<const String> textFieldAction(AXTextFieldActionVerb());
2057 static NeverDestroyed<const String> radioButtonAction(AXRadioButtonActionVerb());
2058 static NeverDestroyed<const String> checkedCheckBoxAction(AXCheckedCheckBoxActionVerb());
2059 static NeverDestroyed<const String> uncheckedCheckBoxAction(AXUncheckedCheckBoxActionVerb());
2060 static NeverDestroyed<const String> linkAction(AXLinkActionVerb());
2061 static NeverDestroyed<const String> menuListAction(AXMenuListActionVerb());
2062 static NeverDestroyed<const String> menuListPopupAction(AXMenuListPopupActionVerb());
2063 static NeverDestroyed<const String> listItemAction(AXListItemActionVerb());
2064
2065 switch (roleValue()) {
2066 case AccessibilityRole::Button:
2067 case AccessibilityRole::ToggleButton:
2068 return buttonAction;
2069 case AccessibilityRole::TextField:
2070 case AccessibilityRole::TextArea:
2071 return textFieldAction;
2072 case AccessibilityRole::RadioButton:
2073 return radioButtonAction;
2074 case AccessibilityRole::CheckBox:
2075 case AccessibilityRole::Switch:
2076 return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction;
2077 case AccessibilityRole::Link:
2078 case AccessibilityRole::WebCoreLink:
2079 return linkAction;
2080 case AccessibilityRole::PopUpButton:
2081 return menuListAction;
2082 case AccessibilityRole::MenuListPopup:
2083 return menuListPopupAction;
2084 case AccessibilityRole::ListItem:
2085 return listItemAction;
2086 default:
2087 return nullAtom();
2088 }
2089#else
2090 return nullAtom();
2091#endif
2092}
2093#endif
2094
2095bool AccessibilityObject::ariaIsMultiline() const
2096{
2097 return equalLettersIgnoringASCIICase(getAttribute(aria_multilineAttr), "true");
2098}
2099
2100String AccessibilityObject::invalidStatus() const
2101{
2102 String grammarValue = "grammar"_s;
2103 String falseValue = "false"_s;
2104 String spellingValue = "spelling"_s;
2105 String trueValue = "true"_s;
2106 String undefinedValue = "undefined"_s;
2107
2108 // aria-invalid can return false (default), grammar, spelling, or true.
2109 String ariaInvalid = stripLeadingAndTrailingHTMLSpaces(getAttribute(aria_invalidAttr));
2110
2111 if (ariaInvalid.isEmpty()) {
2112 // We should expose invalid status for input types.
2113 Node* node = this->node();
2114 if (node && is<HTMLInputElement>(*node)) {
2115 HTMLInputElement& input = downcast<HTMLInputElement>(*node);
2116 if (input.hasBadInput() || input.typeMismatch())
2117 return trueValue;
2118 }
2119 return falseValue;
2120 }
2121
2122 // If "false", "undefined" [sic, string value], empty, or missing, return "false".
2123 if (ariaInvalid == falseValue || ariaInvalid == undefinedValue)
2124 return falseValue;
2125 // Besides true/false/undefined, the only tokens defined by WAI-ARIA 1.0...
2126 // ...for @aria-invalid are "grammar" and "spelling".
2127 if (ariaInvalid == grammarValue)
2128 return grammarValue;
2129 if (ariaInvalid == spellingValue)
2130 return spellingValue;
2131 // Any other non empty string should be treated as "true".
2132 return trueValue;
2133}
2134
2135bool AccessibilityObject::supportsCurrent() const
2136{
2137 return hasAttribute(aria_currentAttr);
2138}
2139
2140AccessibilityCurrentState AccessibilityObject::currentState() const
2141{
2142 // aria-current can return false (default), true, page, step, location, date or time.
2143 String currentStateValue = stripLeadingAndTrailingHTMLSpaces(getAttribute(aria_currentAttr));
2144
2145 // If "false", empty, or missing, return false state.
2146 if (currentStateValue.isEmpty() || currentStateValue == "false")
2147 return AccessibilityCurrentState::False;
2148
2149 if (currentStateValue == "page")
2150 return AccessibilityCurrentState::Page;
2151 if (currentStateValue == "step")
2152 return AccessibilityCurrentState::Step;
2153 if (currentStateValue == "location")
2154 return AccessibilityCurrentState::Location;
2155 if (currentStateValue == "date")
2156 return AccessibilityCurrentState::Date;
2157 if (currentStateValue == "time")
2158 return AccessibilityCurrentState::Time;
2159
2160 // Any value not included in the list of allowed values should be treated as "true".
2161 return AccessibilityCurrentState::True;
2162}
2163
2164String AccessibilityObject::currentValue() const
2165{
2166 switch (currentState()) {
2167 case AccessibilityCurrentState::False:
2168 return "false";
2169 case AccessibilityCurrentState::Page:
2170 return "page";
2171 case AccessibilityCurrentState::Step:
2172 return "step";
2173 case AccessibilityCurrentState::Location:
2174 return "location";
2175 case AccessibilityCurrentState::Time:
2176 return "time";
2177 case AccessibilityCurrentState::Date:
2178 return "date";
2179 default:
2180 case AccessibilityCurrentState::True:
2181 return "true";
2182 }
2183}
2184
2185bool AccessibilityObject::isModalDescendant(Node* modalNode) const
2186{
2187 Node* node = this->node();
2188 if (!modalNode || !node)
2189 return false;
2190
2191 if (node == modalNode)
2192 return true;
2193
2194 // ARIA 1.1 aria-modal, indicates whether an element is modal when displayed.
2195 // For the decendants of the modal object, they should also be considered as aria-modal=true.
2196 return node->isDescendantOf(*modalNode);
2197}
2198
2199bool AccessibilityObject::isModalNode() const
2200{
2201 if (AXObjectCache* cache = axObjectCache())
2202 return node() && cache->modalNode() == node();
2203
2204 return false;
2205}
2206
2207bool AccessibilityObject::ignoredFromModalPresence() const
2208{
2209 // We shouldn't ignore the top node.
2210 if (!node() || !node()->parentNode())
2211 return false;
2212
2213 AXObjectCache* cache = axObjectCache();
2214 if (!cache)
2215 return false;
2216
2217 // modalNode is the current displayed modal dialog.
2218 Node* modalNode = cache->modalNode();
2219 if (!modalNode)
2220 return false;
2221
2222 // We only want to ignore the objects within the same frame as the modal dialog.
2223 if (modalNode->document().frame() != this->frame())
2224 return false;
2225
2226 return !isModalDescendant(modalNode);
2227}
2228
2229bool AccessibilityObject::hasTagName(const QualifiedName& tagName) const
2230{
2231 Node* node = this->node();
2232 return is<Element>(node) && downcast<Element>(*node).hasTagName(tagName);
2233}
2234
2235bool AccessibilityObject::hasAttribute(const QualifiedName& attribute) const
2236{
2237 Node* node = this->node();
2238 if (!is<Element>(node))
2239 return false;
2240
2241 return downcast<Element>(*node).hasAttributeWithoutSynchronization(attribute);
2242}
2243
2244const AtomString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const
2245{
2246 if (auto* element = this->element())
2247 return element->attributeWithoutSynchronization(attribute);
2248 return nullAtom();
2249}
2250
2251bool AccessibilityObject::replaceTextInRange(const String& replacementString, const PlainTextRange& range)
2252{
2253 if (!renderer() || !is<Element>(node()))
2254 return false;
2255
2256 auto& element = downcast<Element>(*renderer()->node());
2257
2258 // We should use the editor's insertText to mimic typing into the field.
2259 // Also only do this when the field is in editing mode.
2260 auto& frame = renderer()->frame();
2261 if (element.shouldUseInputMethod()) {
2262 frame.selection().setSelectedRange(rangeForPlainTextRange(range).get(), DOWNSTREAM, FrameSelection::ShouldCloseTyping::Yes);
2263 frame.editor().replaceSelectionWithText(replacementString, Editor::SelectReplacement::No, Editor::SmartReplace::No);
2264 return true;
2265 }
2266
2267 if (is<HTMLInputElement>(element)) {
2268 downcast<HTMLInputElement>(element).setRangeText(replacementString, range.start, range.length, "");
2269 return true;
2270 }
2271 if (is<HTMLTextAreaElement>(element)) {
2272 downcast<HTMLTextAreaElement>(element).setRangeText(replacementString, range.start, range.length, "");
2273 return true;
2274 }
2275
2276 return false;
2277}
2278
2279// Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width;
2280AccessibilityOrientation AccessibilityObject::orientation() const
2281{
2282 LayoutRect bounds = elementRect();
2283 if (bounds.size().width() > bounds.size().height())
2284 return AccessibilityOrientation::Horizontal;
2285 if (bounds.size().height() > bounds.size().width())
2286 return AccessibilityOrientation::Vertical;
2287
2288 return AccessibilityOrientation::Undefined;
2289}
2290
2291bool AccessibilityObject::isDescendantOfObject(const AccessibilityObject* axObject) const
2292{
2293 if (!axObject || !axObject->hasChildren())
2294 return false;
2295
2296 return AccessibilityObject::matchedParent(*this, false, [axObject] (const AccessibilityObject& object) {
2297 return &object == axObject;
2298 }) != nullptr;
2299}
2300
2301bool AccessibilityObject::isAncestorOfObject(const AccessibilityObject* axObject) const
2302{
2303 if (!axObject)
2304 return false;
2305
2306 return this == axObject || axObject->isDescendantOfObject(this);
2307}
2308
2309AccessibilityObject* AccessibilityObject::firstAnonymousBlockChild() const
2310{
2311 for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
2312 if (child->renderer() && child->renderer()->isAnonymousBlock())
2313 return child;
2314 }
2315 return nullptr;
2316}
2317
2318using ARIARoleMap = HashMap<String, AccessibilityRole, ASCIICaseInsensitiveHash>;
2319using ARIAReverseRoleMap = HashMap<AccessibilityRole, String, DefaultHash<int>::Hash, WTF::UnsignedWithZeroKeyHashTraits<int>>;
2320
2321static ARIARoleMap* gAriaRoleMap = nullptr;
2322static ARIAReverseRoleMap* gAriaReverseRoleMap = nullptr;
2323
2324struct RoleEntry {
2325 String ariaRole;
2326 AccessibilityRole webcoreRole;
2327};
2328
2329static void initializeRoleMap()
2330{
2331 if (gAriaRoleMap)
2332 return;
2333 ASSERT(!gAriaReverseRoleMap);
2334
2335 const RoleEntry roles[] = {
2336 { "alert", AccessibilityRole::ApplicationAlert },
2337 { "alertdialog", AccessibilityRole::ApplicationAlertDialog },
2338 { "application", AccessibilityRole::WebApplication },
2339 { "article", AccessibilityRole::DocumentArticle },
2340 { "banner", AccessibilityRole::LandmarkBanner },
2341 { "blockquote", AccessibilityRole::Blockquote },
2342 { "button", AccessibilityRole::Button },
2343 { "caption", AccessibilityRole::Caption },
2344 { "checkbox", AccessibilityRole::CheckBox },
2345 { "complementary", AccessibilityRole::LandmarkComplementary },
2346 { "contentinfo", AccessibilityRole::LandmarkContentInfo },
2347 { "dialog", AccessibilityRole::ApplicationDialog },
2348 { "directory", AccessibilityRole::Directory },
2349 // The 'doc-*' roles are defined the ARIA DPUB mobile: https://www.w3.org/TR/dpub-aam-1.0/
2350 // Editor's draft is currently at https://rawgit.com/w3c/aria/master/dpub-aam/dpub-aam.html
2351 { "doc-abstract", AccessibilityRole::ApplicationTextGroup },
2352 { "doc-acknowledgments", AccessibilityRole::LandmarkDocRegion },
2353 { "doc-afterword", AccessibilityRole::LandmarkDocRegion },
2354 { "doc-appendix", AccessibilityRole::LandmarkDocRegion },
2355 { "doc-backlink", AccessibilityRole::WebCoreLink },
2356 { "doc-biblioentry", AccessibilityRole::ListItem },
2357 { "doc-bibliography", AccessibilityRole::LandmarkDocRegion },
2358 { "doc-biblioref", AccessibilityRole::WebCoreLink },
2359 { "doc-chapter", AccessibilityRole::LandmarkDocRegion },
2360 { "doc-colophon", AccessibilityRole::ApplicationTextGroup },
2361 { "doc-conclusion", AccessibilityRole::LandmarkDocRegion },
2362 { "doc-cover", AccessibilityRole::Image },
2363 { "doc-credit", AccessibilityRole::ApplicationTextGroup },
2364 { "doc-credits", AccessibilityRole::LandmarkDocRegion },
2365 { "doc-dedication", AccessibilityRole::ApplicationTextGroup },
2366 { "doc-endnote", AccessibilityRole::ListItem },
2367 { "doc-endnotes", AccessibilityRole::LandmarkDocRegion },
2368 { "doc-epigraph", AccessibilityRole::ApplicationTextGroup },
2369 { "doc-epilogue", AccessibilityRole::LandmarkDocRegion },
2370 { "doc-errata", AccessibilityRole::LandmarkDocRegion },
2371 { "doc-example", AccessibilityRole::ApplicationTextGroup },
2372 { "doc-footnote", AccessibilityRole::Footnote },
2373 { "doc-foreword", AccessibilityRole::LandmarkDocRegion },
2374 { "doc-glossary", AccessibilityRole::LandmarkDocRegion },
2375 { "doc-glossref", AccessibilityRole::WebCoreLink },
2376 { "doc-index", AccessibilityRole::LandmarkNavigation },
2377 { "doc-introduction", AccessibilityRole::LandmarkDocRegion },
2378 { "doc-noteref", AccessibilityRole::WebCoreLink },
2379 { "doc-notice", AccessibilityRole::DocumentNote },
2380 { "doc-pagebreak", AccessibilityRole::Splitter },
2381 { "doc-pagelist", AccessibilityRole::LandmarkNavigation },
2382 { "doc-part", AccessibilityRole::LandmarkDocRegion },
2383 { "doc-preface", AccessibilityRole::LandmarkDocRegion },
2384 { "doc-prologue", AccessibilityRole::LandmarkDocRegion },
2385 { "doc-pullquote", AccessibilityRole::ApplicationTextGroup },
2386 { "doc-qna", AccessibilityRole::ApplicationTextGroup },
2387 { "doc-subtitle", AccessibilityRole::Heading },
2388 { "doc-tip", AccessibilityRole::DocumentNote },
2389 { "doc-toc", AccessibilityRole::LandmarkNavigation },
2390 { "figure", AccessibilityRole::Figure },
2391 // The mappings for 'graphics-*' roles are defined in this spec: https://w3c.github.io/graphics-aam/
2392 { "graphics-document", AccessibilityRole::GraphicsDocument },
2393 { "graphics-object", AccessibilityRole::GraphicsObject },
2394 { "graphics-symbol", AccessibilityRole::GraphicsSymbol },
2395 { "grid", AccessibilityRole::Grid },
2396 { "gridcell", AccessibilityRole::GridCell },
2397 { "table", AccessibilityRole::Table },
2398 { "cell", AccessibilityRole::Cell },
2399 { "columnheader", AccessibilityRole::ColumnHeader },
2400 { "combobox", AccessibilityRole::ComboBox },
2401 { "definition", AccessibilityRole::Definition },
2402 { "document", AccessibilityRole::Document },
2403 { "feed", AccessibilityRole::Feed },
2404 { "form", AccessibilityRole::Form },
2405 { "rowheader", AccessibilityRole::RowHeader },
2406 { "group", AccessibilityRole::ApplicationGroup },
2407 { "heading", AccessibilityRole::Heading },
2408 { "img", AccessibilityRole::Image },
2409 { "link", AccessibilityRole::WebCoreLink },
2410 { "list", AccessibilityRole::List },
2411 { "listitem", AccessibilityRole::ListItem },
2412 { "listbox", AccessibilityRole::ListBox },
2413 { "log", AccessibilityRole::ApplicationLog },
2414 { "main", AccessibilityRole::LandmarkMain },
2415 { "marquee", AccessibilityRole::ApplicationMarquee },
2416 { "math", AccessibilityRole::DocumentMath },
2417 { "menu", AccessibilityRole::Menu },
2418 { "menubar", AccessibilityRole::MenuBar },
2419 { "menuitem", AccessibilityRole::MenuItem },
2420 { "menuitemcheckbox", AccessibilityRole::MenuItemCheckbox },
2421 { "menuitemradio", AccessibilityRole::MenuItemRadio },
2422 { "meter", AccessibilityRole::Meter },
2423 { "none", AccessibilityRole::Presentational },
2424 { "note", AccessibilityRole::DocumentNote },
2425 { "navigation", AccessibilityRole::LandmarkNavigation },
2426 { "option", AccessibilityRole::ListBoxOption },
2427 { "paragraph", AccessibilityRole::Paragraph },
2428 { "presentation", AccessibilityRole::Presentational },
2429 { "progressbar", AccessibilityRole::ProgressIndicator },
2430 { "radio", AccessibilityRole::RadioButton },
2431 { "radiogroup", AccessibilityRole::RadioGroup },
2432 { "region", AccessibilityRole::LandmarkRegion },
2433 { "row", AccessibilityRole::Row },
2434 { "rowgroup", AccessibilityRole::RowGroup },
2435 { "scrollbar", AccessibilityRole::ScrollBar },
2436 { "search", AccessibilityRole::LandmarkSearch },
2437 { "searchbox", AccessibilityRole::SearchField },
2438 { "separator", AccessibilityRole::Splitter },
2439 { "slider", AccessibilityRole::Slider },
2440 { "spinbutton", AccessibilityRole::SpinButton },
2441 { "status", AccessibilityRole::ApplicationStatus },
2442 { "switch", AccessibilityRole::Switch },
2443 { "tab", AccessibilityRole::Tab },
2444 { "tablist", AccessibilityRole::TabList },
2445 { "tabpanel", AccessibilityRole::TabPanel },
2446 { "text", AccessibilityRole::StaticText },
2447 { "textbox", AccessibilityRole::TextArea },
2448 { "term", AccessibilityRole::Term },
2449 { "timer", AccessibilityRole::ApplicationTimer },
2450 { "toolbar", AccessibilityRole::Toolbar },
2451 { "tooltip", AccessibilityRole::UserInterfaceTooltip },
2452 { "tree", AccessibilityRole::Tree },
2453 { "treegrid", AccessibilityRole::TreeGrid },
2454 { "treeitem", AccessibilityRole::TreeItem }
2455 };
2456
2457 gAriaRoleMap = new ARIARoleMap;
2458 gAriaReverseRoleMap = new ARIAReverseRoleMap;
2459 size_t roleLength = WTF_ARRAY_LENGTH(roles);
2460 for (size_t i = 0; i < roleLength; ++i) {
2461 gAriaRoleMap->set(roles[i].ariaRole, roles[i].webcoreRole);
2462 gAriaReverseRoleMap->set(static_cast<int>(roles[i].webcoreRole), roles[i].ariaRole);
2463 }
2464}
2465
2466static ARIARoleMap& ariaRoleMap()
2467{
2468 initializeRoleMap();
2469 return *gAriaRoleMap;
2470}
2471
2472static ARIAReverseRoleMap& reverseAriaRoleMap()
2473{
2474 initializeRoleMap();
2475 return *gAriaReverseRoleMap;
2476}
2477
2478AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value)
2479{
2480 if (value.isNull() || value.isEmpty())
2481 return AccessibilityRole::Unknown;
2482
2483 for (auto roleName : StringView(value).split(' ')) {
2484 AccessibilityRole role = ariaRoleMap().get<ASCIICaseInsensitiveStringViewHashTranslator>(roleName);
2485 if (static_cast<int>(role))
2486 return role;
2487 }
2488 return AccessibilityRole::Unknown;
2489}
2490
2491String AccessibilityObject::computedRoleString() const
2492{
2493 // FIXME: Need a few special cases that aren't in the RoleMap: option, etc. http://webkit.org/b/128296
2494 AccessibilityRole role = roleValue();
2495
2496 // We do not compute a role string for generic block elements with user-agent assigned roles.
2497 if (role == AccessibilityRole::Group || role == AccessibilityRole::TextGroup)
2498 return "";
2499
2500 // We do compute a role string for block elements with author-provided roles.
2501 if (role == AccessibilityRole::ApplicationTextGroup
2502 || role == AccessibilityRole::Footnote
2503 || role == AccessibilityRole::GraphicsObject)
2504 return reverseAriaRoleMap().get(static_cast<int>(AccessibilityRole::ApplicationGroup));
2505
2506 if (role == AccessibilityRole::GraphicsDocument)
2507 return reverseAriaRoleMap().get(static_cast<int>(AccessibilityRole::Document));
2508
2509 if (role == AccessibilityRole::GraphicsSymbol)
2510 return reverseAriaRoleMap().get(static_cast<int>(AccessibilityRole::Image));
2511
2512 if (role == AccessibilityRole::HorizontalRule)
2513 return reverseAriaRoleMap().get(static_cast<int>(AccessibilityRole::Splitter));
2514
2515 if (role == AccessibilityRole::PopUpButton || role == AccessibilityRole::ToggleButton)
2516 return reverseAriaRoleMap().get(static_cast<int>(AccessibilityRole::Button));
2517
2518 if (role == AccessibilityRole::LandmarkDocRegion)
2519 return reverseAriaRoleMap().get(static_cast<int>(AccessibilityRole::LandmarkRegion));
2520
2521 return reverseAriaRoleMap().get(static_cast<int>(role));
2522}
2523
2524bool AccessibilityObject::hasHighlighting() const
2525{
2526 for (Node* node = this->node(); node; node = node->parentNode()) {
2527 if (node->hasTagName(markTag))
2528 return true;
2529 }
2530
2531 return false;
2532}
2533
2534String AccessibilityObject::roleDescription() const
2535{
2536 return stripLeadingAndTrailingHTMLSpaces(getAttribute(aria_roledescriptionAttr));
2537}
2538
2539bool nodeHasPresentationRole(Node* node)
2540{
2541 return nodeHasRole(node, "presentation") || nodeHasRole(node, "none");
2542}
2543
2544bool AccessibilityObject::supportsPressAction() const
2545{
2546 if (isButton())
2547 return true;
2548 if (roleValue() == AccessibilityRole::Details)
2549 return true;
2550
2551 Element* actionElement = this->actionElement();
2552 if (!actionElement)
2553 return false;
2554
2555 // [Bug: 136247] Heuristic: element handlers that have more than one accessible descendant should not be exposed as supporting press.
2556 if (actionElement != element()) {
2557 if (AccessibilityObject* axObj = axObjectCache()->getOrCreate(actionElement)) {
2558 AccessibilityChildrenVector results;
2559 // Search within for immediate descendants that are static text. If we find more than one
2560 // then this is an event delegator actionElement and we should expose the press action.
2561 Vector<AccessibilitySearchKey> keys({ AccessibilitySearchKey::StaticText, AccessibilitySearchKey::Control, AccessibilitySearchKey::Graphic, AccessibilitySearchKey::Heading, AccessibilitySearchKey::Link });
2562 AccessibilitySearchCriteria criteria(axObj, AccessibilitySearchDirection::Next, emptyString(), 2, false, false);
2563 criteria.searchKeys = keys;
2564 axObj->findMatchingObjects(&criteria, results);
2565 if (results.size() > 1)
2566 return false;
2567 }
2568 }
2569
2570 // [Bug: 133613] Heuristic: If the action element is presentational, we shouldn't expose press as a supported action.
2571 return !nodeHasPresentationRole(actionElement);
2572}
2573
2574bool AccessibilityObject::supportsDatetimeAttribute() const
2575{
2576 return hasTagName(insTag) || hasTagName(delTag) || hasTagName(timeTag);
2577}
2578
2579const AtomString& AccessibilityObject::datetimeAttributeValue() const
2580{
2581 return getAttribute(datetimeAttr);
2582}
2583
2584const AtomString& AccessibilityObject::linkRelValue() const
2585{
2586 return getAttribute(relAttr);
2587}
2588
2589const String AccessibilityObject::keyShortcutsValue() const
2590{
2591 return getAttribute(aria_keyshortcutsAttr);
2592}
2593
2594Element* AccessibilityObject::element() const
2595{
2596 Node* node = this->node();
2597 if (is<Element>(node))
2598 return downcast<Element>(node);
2599 return nullptr;
2600}
2601
2602bool AccessibilityObject::isValueAutofillAvailable() const
2603{
2604 if (!isNativeTextControl())
2605 return false;
2606
2607 Node* node = this->node();
2608 if (!is<HTMLInputElement>(node))
2609 return false;
2610
2611 return downcast<HTMLInputElement>(*node).isAutoFillAvailable() || downcast<HTMLInputElement>(*node).autoFillButtonType() != AutoFillButtonType::None;
2612}
2613
2614AutoFillButtonType AccessibilityObject::valueAutofillButtonType() const
2615{
2616 if (!isValueAutofillAvailable())
2617 return AutoFillButtonType::None;
2618
2619 return downcast<HTMLInputElement>(*this->node()).autoFillButtonType();
2620}
2621
2622bool AccessibilityObject::isValueAutofilled() const
2623{
2624 if (!isNativeTextControl())
2625 return false;
2626
2627 Node* node = this->node();
2628 if (!is<HTMLInputElement>(node))
2629 return false;
2630
2631 return downcast<HTMLInputElement>(*node).isAutoFilled();
2632}
2633
2634const String AccessibilityObject::placeholderValue() const
2635{
2636 const AtomString& placeholder = getAttribute(placeholderAttr);
2637 if (!placeholder.isEmpty())
2638 return placeholder;
2639
2640 const AtomString& ariaPlaceholder = getAttribute(aria_placeholderAttr);
2641 if (!ariaPlaceholder.isEmpty())
2642 return ariaPlaceholder;
2643
2644 return nullAtom();
2645}
2646
2647bool AccessibilityObject::isInsideLiveRegion(bool excludeIfOff) const
2648{
2649 return liveRegionAncestor(excludeIfOff);
2650}
2651
2652AccessibilityObject* AccessibilityObject::liveRegionAncestor(bool excludeIfOff) const
2653{
2654 return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, true, [excludeIfOff] (const AccessibilityObject& object) {
2655 return object.supportsLiveRegion(excludeIfOff);
2656 }));
2657}
2658
2659bool AccessibilityObject::supportsARIAAttributes() const
2660{
2661 // This returns whether the element supports any global ARIA attributes.
2662 return supportsLiveRegion()
2663 || supportsARIADragging()
2664 || supportsARIADropping()
2665 || supportsARIAOwns()
2666 || hasAttribute(aria_atomicAttr)
2667 || hasAttribute(aria_busyAttr)
2668 || hasAttribute(aria_controlsAttr)
2669 || hasAttribute(aria_currentAttr)
2670 || hasAttribute(aria_describedbyAttr)
2671 || hasAttribute(aria_detailsAttr)
2672 || hasAttribute(aria_disabledAttr)
2673 || hasAttribute(aria_errormessageAttr)
2674 || hasAttribute(aria_flowtoAttr)
2675 || hasAttribute(aria_haspopupAttr)
2676 || hasAttribute(aria_invalidAttr)
2677 || hasAttribute(aria_labelAttr)
2678 || hasAttribute(aria_labelledbyAttr)
2679 || hasAttribute(aria_relevantAttr);
2680}
2681
2682bool AccessibilityObject::liveRegionStatusIsEnabled(const AtomString& liveRegionStatus)
2683{
2684 return equalLettersIgnoringASCIICase(liveRegionStatus, "polite") || equalLettersIgnoringASCIICase(liveRegionStatus, "assertive");
2685}
2686
2687bool AccessibilityObject::supportsLiveRegion(bool excludeIfOff) const
2688{
2689 const AtomString& liveRegionStatusValue = liveRegionStatus();
2690 return excludeIfOff ? liveRegionStatusIsEnabled(liveRegionStatusValue) : !liveRegionStatusValue.isEmpty();
2691}
2692
2693AccessibilityObjectInterface* AccessibilityObject::elementAccessibilityHitTest(const IntPoint& point) const
2694{
2695 // Send the hit test back into the sub-frame if necessary.
2696 if (isAttachment()) {
2697 Widget* widget = widgetForAttachmentView();
2698 // Normalize the point for the widget's bounds.
2699 if (widget && widget->isFrameView()) {
2700 if (AXObjectCache* cache = axObjectCache())
2701 return cache->getOrCreate(widget)->accessibilityHitTest(IntPoint(point - widget->frameRect().location()));
2702 }
2703 }
2704
2705 // Check if there are any mock elements that need to be handled.
2706 for (const auto& child : m_children) {
2707 if (child->isMockObject() && child->elementRect().contains(point))
2708 return child->elementAccessibilityHitTest(point);
2709 }
2710
2711 return const_cast<AccessibilityObject*>(this);
2712}
2713
2714AXObjectCache* AccessibilityObject::axObjectCache() const
2715{
2716 auto* document = this->document();
2717 return document ? document->axObjectCache() : nullptr;
2718}
2719
2720AccessibilityObjectInterface* AccessibilityObject::focusedUIElement() const
2721{
2722 auto* page = this->page();
2723 return page ? AXObjectCache::focusedUIElementForPage(page) : nullptr;
2724}
2725
2726AccessibilitySortDirection AccessibilityObject::sortDirection() const
2727{
2728 AccessibilityRole role = roleValue();
2729 if (role != AccessibilityRole::RowHeader && role != AccessibilityRole::ColumnHeader)
2730 return AccessibilitySortDirection::Invalid;
2731
2732 const AtomString& sortAttribute = getAttribute(aria_sortAttr);
2733 if (equalLettersIgnoringASCIICase(sortAttribute, "ascending"))
2734 return AccessibilitySortDirection::Ascending;
2735 if (equalLettersIgnoringASCIICase(sortAttribute, "descending"))
2736 return AccessibilitySortDirection::Descending;
2737 if (equalLettersIgnoringASCIICase(sortAttribute, "other"))
2738 return AccessibilitySortDirection::Other;
2739
2740 return AccessibilitySortDirection::None;
2741}
2742
2743bool AccessibilityObject::supportsRangeValue() const
2744{
2745 return isProgressIndicator()
2746 || isSlider()
2747 || isScrollbar()
2748 || isSpinButton()
2749 || (isSplitter() && canSetFocusAttribute())
2750 || isAttachmentElement();
2751}
2752
2753bool AccessibilityObject::supportsHasPopup() const
2754{
2755 return hasAttribute(aria_haspopupAttr) || isComboBox();
2756}
2757
2758String AccessibilityObject::hasPopupValue() const
2759{
2760 const AtomString& hasPopup = getAttribute(aria_haspopupAttr);
2761 if (equalLettersIgnoringASCIICase(hasPopup, "true")
2762 || equalLettersIgnoringASCIICase(hasPopup, "dialog")
2763 || equalLettersIgnoringASCIICase(hasPopup, "grid")
2764 || equalLettersIgnoringASCIICase(hasPopup, "listbox")
2765 || equalLettersIgnoringASCIICase(hasPopup, "menu")
2766 || equalLettersIgnoringASCIICase(hasPopup, "tree"))
2767 return hasPopup;
2768
2769 // In ARIA 1.1, the implicit value for combobox became "listbox."
2770 if (isComboBox() && hasPopup.isEmpty())
2771 return "listbox";
2772
2773 // The spec states that "User agents must treat any value of aria-haspopup that is not
2774 // included in the list of allowed values, including an empty string, as if the value
2775 // false had been provided."
2776 return "false";
2777}
2778
2779bool AccessibilityObject::supportsSetSize() const
2780{
2781 return hasAttribute(aria_setsizeAttr);
2782}
2783
2784bool AccessibilityObject::supportsPosInSet() const
2785{
2786 return hasAttribute(aria_posinsetAttr);
2787}
2788
2789int AccessibilityObject::setSize() const
2790{
2791 return getAttribute(aria_setsizeAttr).toInt();
2792}
2793
2794int AccessibilityObject::posInSet() const
2795{
2796 return getAttribute(aria_posinsetAttr).toInt();
2797}
2798
2799const AtomString& AccessibilityObject::identifierAttribute() const
2800{
2801 return getAttribute(idAttr);
2802}
2803
2804void AccessibilityObject::classList(Vector<String>& classList) const
2805{
2806 Node* node = this->node();
2807 if (!is<Element>(node))
2808 return;
2809
2810 Element* element = downcast<Element>(node);
2811 DOMTokenList& list = element->classList();
2812 unsigned length = list.length();
2813 for (unsigned k = 0; k < length; k++)
2814 classList.append(list.item(k).string());
2815}
2816
2817bool AccessibilityObject::supportsPressed() const
2818{
2819 const AtomString& expanded = getAttribute(aria_pressedAttr);
2820 return equalLettersIgnoringASCIICase(expanded, "true") || equalLettersIgnoringASCIICase(expanded, "false");
2821}
2822
2823bool AccessibilityObject::supportsExpanded() const
2824{
2825 // Undefined values should not result in this attribute being exposed to ATs according to ARIA.
2826 const AtomString& expanded = getAttribute(aria_expandedAttr);
2827 if (equalLettersIgnoringASCIICase(expanded, "true") || equalLettersIgnoringASCIICase(expanded, "false"))
2828 return true;
2829 switch (roleValue()) {
2830 case AccessibilityRole::ComboBox:
2831 case AccessibilityRole::DisclosureTriangle:
2832 case AccessibilityRole::Details:
2833 return true;
2834 default:
2835 return false;
2836 }
2837}
2838
2839bool AccessibilityObject::isExpanded() const
2840{
2841 if (equalLettersIgnoringASCIICase(getAttribute(aria_expandedAttr), "true"))
2842 return true;
2843
2844 if (is<HTMLDetailsElement>(node()))
2845 return downcast<HTMLDetailsElement>(node())->isOpen();
2846
2847 // Summary element should use its details parent's expanded status.
2848 if (isSummary()) {
2849 if (const AccessibilityObject* parent = AccessibilityObject::matchedParent(*this, false, [] (const AccessibilityObject& object) {
2850 return is<HTMLDetailsElement>(object.node());
2851 }))
2852 return parent->isExpanded();
2853 }
2854
2855 return false;
2856}
2857
2858bool AccessibilityObject::supportsChecked() const
2859{
2860 switch (roleValue()) {
2861 case AccessibilityRole::CheckBox:
2862 case AccessibilityRole::MenuItemCheckbox:
2863 case AccessibilityRole::MenuItemRadio:
2864 case AccessibilityRole::RadioButton:
2865 case AccessibilityRole::Switch:
2866 return true;
2867 default:
2868 return false;
2869 }
2870}
2871
2872AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const
2873{
2874 // If this is a real checkbox or radio button, AccessibilityRenderObject will handle.
2875 // If it's an ARIA checkbox, radio, or switch the aria-checked attribute should be used.
2876 // If it's a toggle button, the aria-pressed attribute is consulted.
2877
2878 if (isToggleButton()) {
2879 const AtomString& ariaPressed = getAttribute(aria_pressedAttr);
2880 if (equalLettersIgnoringASCIICase(ariaPressed, "true"))
2881 return AccessibilityButtonState::On;
2882 if (equalLettersIgnoringASCIICase(ariaPressed, "mixed"))
2883 return AccessibilityButtonState::Mixed;
2884 return AccessibilityButtonState::Off;
2885 }
2886
2887 const AtomString& result = getAttribute(aria_checkedAttr);
2888 if (equalLettersIgnoringASCIICase(result, "true"))
2889 return AccessibilityButtonState::On;
2890 if (equalLettersIgnoringASCIICase(result, "mixed")) {
2891 // ARIA says that radio, menuitemradio, and switch elements must NOT expose button state mixed.
2892 AccessibilityRole ariaRole = ariaRoleAttribute();
2893 if (ariaRole == AccessibilityRole::RadioButton || ariaRole == AccessibilityRole::MenuItemRadio || ariaRole == AccessibilityRole::Switch)
2894 return AccessibilityButtonState::Off;
2895 return AccessibilityButtonState::Mixed;
2896 }
2897
2898 if (isIndeterminate())
2899 return AccessibilityButtonState::Mixed;
2900
2901 return AccessibilityButtonState::Off;
2902}
2903
2904// This is a 1-dimensional scroll offset helper function that's applied
2905// separately in the horizontal and vertical directions, because the
2906// logic is the same. The goal is to compute the best scroll offset
2907// in order to make an object visible within a viewport.
2908//
2909// If the object is already fully visible, returns the same scroll
2910// offset.
2911//
2912// In case the whole object cannot fit, you can specify a
2913// subfocus - a smaller region within the object that should
2914// be prioritized. If the whole object can fit, the subfocus is
2915// ignored.
2916//
2917// If possible, the object and subfocus are centered within the
2918// viewport.
2919//
2920// Example 1: the object is already visible, so nothing happens.
2921// +----------Viewport---------+
2922// +---Object---+
2923// +--SubFocus--+
2924//
2925// Example 2: the object is not fully visible, so it's centered
2926// within the viewport.
2927// Before:
2928// +----------Viewport---------+
2929// +---Object---+
2930// +--SubFocus--+
2931//
2932// After:
2933// +----------Viewport---------+
2934// +---Object---+
2935// +--SubFocus--+
2936//
2937// Example 3: the object is larger than the viewport, so the
2938// viewport moves to show as much of the object as possible,
2939// while also trying to center the subfocus.
2940// Before:
2941// +----------Viewport---------+
2942// +---------------Object--------------+
2943// +-SubFocus-+
2944//
2945// After:
2946// +----------Viewport---------+
2947// +---------------Object--------------+
2948// +-SubFocus-+
2949//
2950// When constraints cannot be fully satisfied, the min
2951// (left/top) position takes precedence over the max (right/bottom).
2952//
2953// Note that the return value represents the ideal new scroll offset.
2954// This may be out of range - the calling function should clip this
2955// to the available range.
2956static int computeBestScrollOffset(int currentScrollOffset, int subfocusMin, int subfocusMax, int objectMin, int objectMax, int viewportMin, int viewportMax)
2957{
2958 int viewportSize = viewportMax - viewportMin;
2959
2960 // If the object size is larger than the viewport size, consider
2961 // only a portion that's as large as the viewport, centering on
2962 // the subfocus as much as possible.
2963 if (objectMax - objectMin > viewportSize) {
2964 // Since it's impossible to fit the whole object in the
2965 // viewport, exit now if the subfocus is already within the viewport.
2966 if (subfocusMin - currentScrollOffset >= viewportMin && subfocusMax - currentScrollOffset <= viewportMax)
2967 return currentScrollOffset;
2968
2969 // Subfocus must be within focus.
2970 subfocusMin = std::max(subfocusMin, objectMin);
2971 subfocusMax = std::min(subfocusMax, objectMax);
2972
2973 // Subfocus must be no larger than the viewport size; favor top/left.
2974 if (subfocusMax - subfocusMin > viewportSize)
2975 subfocusMax = subfocusMin + viewportSize;
2976
2977 // Compute the size of an object centered on the subfocus, the size of the viewport.
2978 int centeredObjectMin = (subfocusMin + subfocusMax - viewportSize) / 2;
2979 int centeredObjectMax = centeredObjectMin + viewportSize;
2980
2981 objectMin = std::max(objectMin, centeredObjectMin);
2982 objectMax = std::min(objectMax, centeredObjectMax);
2983 }
2984
2985 // Exit now if the focus is already within the viewport.
2986 if (objectMin - currentScrollOffset >= viewportMin
2987 && objectMax - currentScrollOffset <= viewportMax)
2988 return currentScrollOffset;
2989
2990 // Center the object in the viewport.
2991 return (objectMin + objectMax - viewportMin - viewportMax) / 2;
2992}
2993
2994bool AccessibilityObject::isOnscreen() const
2995{
2996 bool isOnscreen = true;
2997
2998 // To figure out if the element is onscreen, we start by building of a stack starting with the
2999 // element, and then include every scrollable parent in the hierarchy.
3000 Vector<const AccessibilityObject*> objects;
3001
3002 objects.append(this);
3003 for (AccessibilityObject* parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) {
3004 if (parentObject->getScrollableAreaIfScrollable())
3005 objects.append(parentObject);
3006 }
3007
3008 // Now, go back through that chain and make sure each inner object is within the
3009 // visible bounds of the outer object.
3010 size_t levels = objects.size() - 1;
3011
3012 for (size_t i = levels; i >= 1; i--) {
3013 const AccessibilityObject* outer = objects[i];
3014 const AccessibilityObject* inner = objects[i - 1];
3015 // FIXME: unclear if we need LegacyIOSDocumentVisibleRect.
3016 const IntRect outerRect = i < levels ? snappedIntRect(outer->boundingBoxRect()) : outer->getScrollableAreaIfScrollable()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
3017 const IntRect innerRect = snappedIntRect(inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect());
3018
3019 if (!outerRect.intersects(innerRect)) {
3020 isOnscreen = false;
3021 break;
3022 }
3023 }
3024
3025 return isOnscreen;
3026}
3027
3028void AccessibilityObject::scrollToMakeVisible() const
3029{
3030 scrollToMakeVisible({ SelectionRevealMode::Reveal, ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded, ShouldAllowCrossOriginScrolling::Yes });
3031}
3032
3033void AccessibilityObject::scrollToMakeVisible(const ScrollRectToVisibleOptions& options) const
3034{
3035 if (isScrollView() && parentObject())
3036 parentObject()->scrollToMakeVisible();
3037
3038 if (auto* renderer = this->renderer())
3039 renderer->scrollRectToVisible(boundingBoxRect(), false, options);
3040}
3041
3042void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocus) const
3043{
3044 // Search up the parent chain until we find the first one that's scrollable.
3045 AccessibilityObject* scrollParent = parentObject();
3046 ScrollableArea* scrollableArea;
3047 for (scrollableArea = nullptr;
3048 scrollParent && !(scrollableArea = scrollParent->getScrollableAreaIfScrollable());
3049 scrollParent = scrollParent->parentObject()) { }
3050 if (!scrollableArea)
3051 return;
3052
3053 LayoutRect objectRect = boundingBoxRect();
3054 IntPoint scrollPosition = scrollableArea->scrollPosition();
3055 // FIXME: unclear if we need LegacyIOSDocumentVisibleRect.
3056 IntRect scrollVisibleRect = scrollableArea->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
3057
3058 if (!scrollParent->isScrollView()) {
3059 objectRect.moveBy(scrollPosition);
3060 objectRect.moveBy(-snappedIntRect(scrollParent->elementRect()).location());
3061 }
3062
3063 int desiredX = computeBestScrollOffset(
3064 scrollPosition.x(),
3065 objectRect.x() + subfocus.x(), objectRect.x() + subfocus.maxX(),
3066 objectRect.x(), objectRect.maxX(),
3067 0, scrollVisibleRect.width());
3068 int desiredY = computeBestScrollOffset(
3069 scrollPosition.y(),
3070 objectRect.y() + subfocus.y(), objectRect.y() + subfocus.maxY(),
3071 objectRect.y(), objectRect.maxY(),
3072 0, scrollVisibleRect.height());
3073
3074 scrollParent->scrollTo(IntPoint(desiredX, desiredY));
3075
3076 // Convert the subfocus into the coordinates of the scroll parent.
3077 IntRect newSubfocus = subfocus;
3078 IntRect newElementRect = snappedIntRect(elementRect());
3079 IntRect scrollParentRect = snappedIntRect(scrollParent->elementRect());
3080 newSubfocus.move(newElementRect.x(), newElementRect.y());
3081 newSubfocus.move(-scrollParentRect.x(), -scrollParentRect.y());
3082
3083 // Recursively make sure the scroll parent itself is visible.
3084 if (scrollParent->parentObject())
3085 scrollParent->scrollToMakeVisibleWithSubFocus(newSubfocus);
3086}
3087
3088void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const
3089{
3090 // Search up the parent chain and create a vector of all scrollable parent objects
3091 // and ending with this object itself.
3092 Vector<const AccessibilityObject*> objects;
3093
3094 objects.append(this);
3095 for (AccessibilityObject* parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) {
3096 if (parentObject->getScrollableAreaIfScrollable())
3097 objects.append(parentObject);
3098 }
3099
3100 objects.reverse();
3101
3102 // Start with the outermost scrollable (the main window) and try to scroll the
3103 // next innermost object to the given point.
3104 int offsetX = 0, offsetY = 0;
3105 IntPoint point = globalPoint;
3106 size_t levels = objects.size() - 1;
3107 for (size_t i = 0; i < levels; i++) {
3108 const AccessibilityObject* outer = objects[i];
3109 const AccessibilityObject* inner = objects[i + 1];
3110
3111 ScrollableArea* scrollableArea = outer->getScrollableAreaIfScrollable();
3112
3113 LayoutRect innerRect = inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect();
3114 LayoutRect objectRect = innerRect;
3115 IntPoint scrollPosition = scrollableArea->scrollPosition();
3116
3117 // Convert the object rect into local coordinates.
3118 objectRect.move(offsetX, offsetY);
3119 if (!outer->isAccessibilityScrollView())
3120 objectRect.move(scrollPosition.x(), scrollPosition.y());
3121
3122 int desiredX = computeBestScrollOffset(
3123 0,
3124 objectRect.x(), objectRect.maxX(),
3125 objectRect.x(), objectRect.maxX(),
3126 point.x(), point.x());
3127 int desiredY = computeBestScrollOffset(
3128 0,
3129 objectRect.y(), objectRect.maxY(),
3130 objectRect.y(), objectRect.maxY(),
3131 point.y(), point.y());
3132 outer->scrollTo(IntPoint(desiredX, desiredY));
3133
3134 if (outer->isAccessibilityScrollView() && !inner->isAccessibilityScrollView()) {
3135 // If outer object we just scrolled is a scroll view (main window or iframe) but the
3136 // inner object is not, keep track of the coordinate transformation to apply to
3137 // future nested calculations.
3138 scrollPosition = scrollableArea->scrollPosition();
3139 offsetX -= (scrollPosition.x() + point.x());
3140 offsetY -= (scrollPosition.y() + point.y());
3141 point.move(scrollPosition.x() - innerRect.x(),
3142 scrollPosition.y() - innerRect.y());
3143 } else if (inner->isAccessibilityScrollView()) {
3144 // Otherwise, if the inner object is a scroll view, reset the coordinate transformation.
3145 offsetX = 0;
3146 offsetY = 0;
3147 }
3148 }
3149}
3150
3151void AccessibilityObject::scrollAreaAndAncestor(std::pair<ScrollableArea*, AccessibilityObject*>& scrollers) const
3152{
3153 // Search up the parent chain until we find the first one that's scrollable.
3154 scrollers.first = nullptr;
3155 for (scrollers.second = parentObject(); scrollers.second; scrollers.second = scrollers.second->parentObject()) {
3156 if ((scrollers.first = scrollers.second->getScrollableAreaIfScrollable()))
3157 break;
3158 }
3159}
3160
3161ScrollableArea* AccessibilityObject::scrollableAreaAncestor() const
3162{
3163 std::pair<ScrollableArea*, AccessibilityObject*> scrollers;
3164 scrollAreaAndAncestor(scrollers);
3165 return scrollers.first;
3166}
3167
3168IntPoint AccessibilityObject::scrollPosition() const
3169{
3170 if (auto scroller = scrollableAreaAncestor())
3171 return scroller->scrollPosition();
3172
3173 return IntPoint();
3174}
3175
3176IntRect AccessibilityObject::scrollVisibleContentRect() const
3177{
3178 if (auto scroller = scrollableAreaAncestor())
3179 return scroller->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
3180
3181 return IntRect();
3182}
3183
3184IntSize AccessibilityObject::scrollContentsSize() const
3185{
3186 if (auto scroller = scrollableAreaAncestor())
3187 return scroller->contentsSize();
3188
3189 return IntSize();
3190}
3191
3192bool AccessibilityObject::scrollByPage(ScrollByPageDirection direction) const
3193{
3194 std::pair<ScrollableArea*, AccessibilityObject*> scrollers;
3195 scrollAreaAndAncestor(scrollers);
3196 ScrollableArea* scrollableArea = scrollers.first;
3197 AccessibilityObject* scrollParent = scrollers.second;
3198
3199 if (!scrollableArea)
3200 return false;
3201
3202 IntPoint scrollPosition = scrollableArea->scrollPosition();
3203 IntPoint newScrollPosition = scrollPosition;
3204 IntSize scrollSize = scrollableArea->contentsSize();
3205 IntRect scrollVisibleRect = scrollableArea->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
3206 switch (direction) {
3207 case ScrollByPageDirection::Right: {
3208 int scrollAmount = scrollVisibleRect.size().width();
3209 int newX = scrollPosition.x() - scrollAmount;
3210 newScrollPosition.setX(std::max(newX, 0));
3211 break;
3212 }
3213 case ScrollByPageDirection::Left: {
3214 int scrollAmount = scrollVisibleRect.size().width();
3215 int newX = scrollAmount + scrollPosition.x();
3216 int maxX = scrollSize.width() - scrollAmount;
3217 newScrollPosition.setX(std::min(newX, maxX));
3218 break;
3219 }
3220 case ScrollByPageDirection::Up: {
3221 int scrollAmount = scrollVisibleRect.size().height();
3222 int newY = scrollPosition.y() - scrollAmount;
3223 newScrollPosition.setY(std::max(newY, 0));
3224 break;
3225 }
3226 case ScrollByPageDirection::Down: {
3227 int scrollAmount = scrollVisibleRect.size().height();
3228 int newY = scrollAmount + scrollPosition.y();
3229 int maxY = scrollSize.height() - scrollAmount;
3230 newScrollPosition.setY(std::min(newY, maxY));
3231 break;
3232 }
3233 }
3234
3235 if (newScrollPosition != scrollPosition) {
3236 scrollParent->scrollTo(newScrollPosition);
3237 document()->updateLayoutIgnorePendingStylesheets();
3238 return true;
3239 }
3240
3241 return false;
3242}
3243
3244
3245bool AccessibilityObject::lastKnownIsIgnoredValue()
3246{
3247 if (m_lastKnownIsIgnoredValue == AccessibilityObjectInclusion::DefaultBehavior)
3248 m_lastKnownIsIgnoredValue = accessibilityIsIgnored() ? AccessibilityObjectInclusion::IgnoreObject : AccessibilityObjectInclusion::IncludeObject;
3249
3250 return m_lastKnownIsIgnoredValue == AccessibilityObjectInclusion::IgnoreObject;
3251}
3252
3253void AccessibilityObject::setLastKnownIsIgnoredValue(bool isIgnored)
3254{
3255 m_lastKnownIsIgnoredValue = isIgnored ? AccessibilityObjectInclusion::IgnoreObject : AccessibilityObjectInclusion::IncludeObject;
3256}
3257
3258void AccessibilityObject::notifyIfIgnoredValueChanged()
3259{
3260 bool isIgnored = accessibilityIsIgnored();
3261 if (lastKnownIsIgnoredValue() != isIgnored) {
3262 if (AXObjectCache* cache = axObjectCache())
3263 cache->childrenChanged(parentObject());
3264 setLastKnownIsIgnoredValue(isIgnored);
3265 }
3266}
3267
3268bool AccessibilityObject::pressedIsPresent() const
3269{
3270 return !getAttribute(aria_pressedAttr).isEmpty();
3271}
3272
3273TextIteratorBehavior AccessibilityObject::textIteratorBehaviorForTextRange() const
3274{
3275 TextIteratorBehavior behavior = TextIteratorIgnoresStyleVisibility;
3276
3277#if USE(ATK)
3278 // We need to emit replaced elements for GTK, and present
3279 // them with the 'object replacement character' (0xFFFC).
3280 behavior = static_cast<TextIteratorBehavior>(behavior | TextIteratorEmitsObjectReplacementCharacters);
3281#endif
3282
3283 return behavior;
3284}
3285
3286AccessibilityRole AccessibilityObject::buttonRoleType() const
3287{
3288 // If aria-pressed is present, then it should be exposed as a toggle button.
3289 // http://www.w3.org/TR/wai-aria/states_and_properties#aria-pressed
3290 if (pressedIsPresent())
3291 return AccessibilityRole::ToggleButton;
3292 if (hasPopup())
3293 return AccessibilityRole::PopUpButton;
3294 // We don't contemplate AccessibilityRole::RadioButton, as it depends on the input
3295 // type.
3296
3297 return AccessibilityRole::Button;
3298}
3299
3300bool AccessibilityObject::isButton() const
3301{
3302 AccessibilityRole role = roleValue();
3303
3304 return role == AccessibilityRole::Button || role == AccessibilityRole::PopUpButton || role == AccessibilityRole::ToggleButton;
3305}
3306
3307bool AccessibilityObject::accessibilityIsIgnoredByDefault() const
3308{
3309 return defaultObjectInclusion() == AccessibilityObjectInclusion::IgnoreObject;
3310}
3311
3312// ARIA component of hidden definition.
3313// http://www.w3.org/TR/wai-aria/terms#def_hidden
3314bool AccessibilityObject::isAXHidden() const
3315{
3316 return AccessibilityObject::matchedParent(*this, true, [] (const AccessibilityObject& object) {
3317 return equalLettersIgnoringASCIICase(object.getAttribute(aria_hiddenAttr), "true");
3318 }) != nullptr;
3319}
3320
3321// DOM component of hidden definition.
3322// http://www.w3.org/TR/wai-aria/terms#def_hidden
3323bool AccessibilityObject::isDOMHidden() const
3324{
3325 RenderObject* renderer = this->renderer();
3326 if (!renderer)
3327 return true;
3328
3329 const RenderStyle& style = renderer->style();
3330 return style.display() == DisplayType::None || style.visibility() != Visibility::Visible;
3331}
3332
3333bool AccessibilityObject::isShowingValidationMessage() const
3334{
3335 if (is<HTMLFormControlElement>(node()))
3336 return downcast<HTMLFormControlElement>(*node()).isShowingValidationMessage();
3337 return false;
3338}
3339
3340String AccessibilityObject::validationMessage() const
3341{
3342 if (is<HTMLFormControlElement>(node()))
3343 return downcast<HTMLFormControlElement>(*node()).validationMessage();
3344 return String();
3345}
3346
3347AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const
3348{
3349 bool useParentData = !m_isIgnoredFromParentData.isNull();
3350
3351 if (useParentData ? m_isIgnoredFromParentData.isAXHidden : isAXHidden())
3352 return AccessibilityObjectInclusion::IgnoreObject;
3353
3354 if (ignoredFromModalPresence())
3355 return AccessibilityObjectInclusion::IgnoreObject;
3356
3357 if (useParentData ? m_isIgnoredFromParentData.isPresentationalChildOfAriaRole : isPresentationalChildOfAriaRole())
3358 return AccessibilityObjectInclusion::IgnoreObject;
3359
3360 return accessibilityPlatformIncludesObject();
3361}
3362
3363bool AccessibilityObject::accessibilityIsIgnored() const
3364{
3365 AXComputedObjectAttributeCache* attributeCache = nullptr;
3366 AXObjectCache* cache = axObjectCache();
3367 if (cache)
3368 attributeCache = cache->computedObjectAttributeCache();
3369
3370 if (attributeCache) {
3371 AccessibilityObjectInclusion ignored = attributeCache->getIgnored(axObjectID());
3372 switch (ignored) {
3373 case AccessibilityObjectInclusion::IgnoreObject:
3374 return true;
3375 case AccessibilityObjectInclusion::IncludeObject:
3376 return false;
3377 case AccessibilityObjectInclusion::DefaultBehavior:
3378 break;
3379 }
3380 }
3381
3382 bool result = computeAccessibilityIsIgnored();
3383
3384 // In case computing axIsIgnored disables attribute caching, we should refetch the object to see if it exists.
3385 if (cache && (attributeCache = cache->computedObjectAttributeCache()))
3386 attributeCache->setIgnored(axObjectID(), result ? AccessibilityObjectInclusion::IgnoreObject : AccessibilityObjectInclusion::IncludeObject);
3387
3388 return result;
3389}
3390
3391void AccessibilityObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const
3392{
3393 Node* node = this->node();
3394 if (!node || !node->isElementNode())
3395 return;
3396
3397 TreeScope& treeScope = node->treeScope();
3398
3399 const AtomString& idList = getAttribute(attribute);
3400 if (idList.isEmpty())
3401 return;
3402
3403 auto spaceSplitString = SpaceSplitString(idList, false);
3404 size_t length = spaceSplitString.size();
3405 for (size_t i = 0; i < length; ++i) {
3406 if (auto* idElement = treeScope.getElementById(spaceSplitString[i]))
3407 elements.append(idElement);
3408 }
3409}
3410
3411#if PLATFORM(COCOA)
3412bool AccessibilityObject::preventKeyboardDOMEventDispatch() const
3413{
3414 Frame* frame = this->frame();
3415 return frame && frame->settings().preventKeyboardDOMEventDispatch();
3416}
3417
3418void AccessibilityObject::setPreventKeyboardDOMEventDispatch(bool on)
3419{
3420 Frame* frame = this->frame();
3421 if (!frame)
3422 return;
3423 frame->settings().setPreventKeyboardDOMEventDispatch(on);
3424}
3425#endif
3426
3427AccessibilityObject* AccessibilityObject::focusableAncestor()
3428{
3429 return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, true, [] (const AccessibilityObject& object) {
3430 return object.canSetFocusAttribute();
3431 }));
3432}
3433
3434AccessibilityObject* AccessibilityObject::editableAncestor()
3435{
3436 return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, true, [] (const AccessibilityObject& object) {
3437 return object.isTextControl();
3438 }));
3439}
3440
3441AccessibilityObject* AccessibilityObject::highestEditableAncestor()
3442{
3443 AccessibilityObject* editableAncestor = this->editableAncestor();
3444 AccessibilityObject* previousEditableAncestor = nullptr;
3445 while (editableAncestor) {
3446 if (editableAncestor == previousEditableAncestor) {
3447 if (AccessibilityObject* parent = editableAncestor->parentObject()) {
3448 editableAncestor = parent->editableAncestor();
3449 continue;
3450 }
3451 break;
3452 }
3453 previousEditableAncestor = editableAncestor;
3454 editableAncestor = editableAncestor->editableAncestor();
3455 }
3456 return previousEditableAncestor;
3457}
3458
3459AccessibilityObject* AccessibilityObject::radioGroupAncestor() const
3460{
3461 return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, false, [] (const AccessibilityObject& object) {
3462 return object.isRadioGroup();
3463 }));
3464}
3465
3466bool AccessibilityObject::isStyleFormatGroup() const
3467{
3468 Node* node = this->node();
3469 if (!node)
3470 return false;
3471
3472 return node->hasTagName(kbdTag) || node->hasTagName(codeTag)
3473 || node->hasTagName(preTag) || node->hasTagName(sampTag)
3474 || node->hasTagName(varTag) || node->hasTagName(citeTag)
3475 || node->hasTagName(insTag) || node->hasTagName(delTag)
3476 || node->hasTagName(supTag) || node->hasTagName(subTag);
3477}
3478
3479bool AccessibilityObject::isSubscriptStyleGroup() const
3480{
3481 Node* node = this->node();
3482 return node && node->hasTagName(subTag);
3483}
3484
3485bool AccessibilityObject::isSuperscriptStyleGroup() const
3486{
3487 Node* node = this->node();
3488 return node && node->hasTagName(supTag);
3489}
3490
3491bool AccessibilityObject::isFigureElement() const
3492{
3493 Node* node = this->node();
3494 return node && node->hasTagName(figureTag);
3495}
3496
3497bool AccessibilityObject::isKeyboardFocusable() const
3498{
3499 if (auto element = this->element())
3500 return element->isFocusable();
3501 return false;
3502}
3503
3504bool AccessibilityObject::isOutput() const
3505{
3506 Node* node = this->node();
3507 return node && node->hasTagName(outputTag);
3508}
3509
3510bool AccessibilityObject::isContainedByPasswordField() const
3511{
3512 Node* node = this->node();
3513 if (!node)
3514 return false;
3515
3516 if (ariaRoleAttribute() != AccessibilityRole::Unknown)
3517 return false;
3518
3519 Element* element = node->shadowHost();
3520 return is<HTMLInputElement>(element) && downcast<HTMLInputElement>(*element).isPasswordField();
3521}
3522
3523AccessibilityObject* AccessibilityObject::selectedListItem()
3524{
3525 for (const auto& child : children()) {
3526 if (child->isListItem() && (child->isSelected() || child->isActiveDescendantOfFocusedContainer()))
3527 return child.get();
3528 }
3529
3530 return nullptr;
3531}
3532
3533void AccessibilityObject::ariaElementsFromAttribute(AccessibilityChildrenVector& children, const QualifiedName& attributeName) const
3534{
3535 Vector<Element*> elements;
3536 elementsFromAttribute(elements, attributeName);
3537 AXObjectCache* cache = axObjectCache();
3538 for (const auto& element : elements) {
3539 if (AccessibilityObject* axObject = cache->getOrCreate(element))
3540 children.append(axObject);
3541 }
3542}
3543
3544void AccessibilityObject::ariaElementsReferencedByAttribute(AccessibilityChildrenVector& elements, const QualifiedName& attribute) const
3545{
3546 auto id = identifierAttribute();
3547 if (id.isEmpty())
3548 return;
3549
3550 AXObjectCache* cache = axObjectCache();
3551 if (!cache)
3552 return;
3553
3554 for (auto& element : descendantsOfType<Element>(node()->treeScope().rootNode())) {
3555 const AtomString& idList = element.attributeWithoutSynchronization(attribute);
3556 if (!SpaceSplitString(idList, false).contains(id))
3557 continue;
3558
3559 if (AccessibilityObject* axObject = cache->getOrCreate(&element))
3560 elements.append(axObject);
3561 }
3562}
3563
3564bool AccessibilityObject::isActiveDescendantOfFocusedContainer() const
3565{
3566 AccessibilityChildrenVector containers;
3567 ariaActiveDescendantReferencingElements(containers);
3568 for (auto& container : containers) {
3569 if (container->isFocused())
3570 return true;
3571 }
3572
3573 return false;
3574}
3575
3576void AccessibilityObject::ariaActiveDescendantReferencingElements(AccessibilityChildrenVector& containers) const
3577{
3578 ariaElementsReferencedByAttribute(containers, aria_activedescendantAttr);
3579}
3580
3581void AccessibilityObject::ariaControlsElements(AccessibilityChildrenVector& ariaControls) const
3582{
3583 ariaElementsFromAttribute(ariaControls, aria_controlsAttr);
3584}
3585
3586void AccessibilityObject::ariaControlsReferencingElements(AccessibilityChildrenVector& controllers) const
3587{
3588 ariaElementsReferencedByAttribute(controllers, aria_controlsAttr);
3589}
3590
3591void AccessibilityObject::ariaDescribedByElements(AccessibilityChildrenVector& ariaDescribedBy) const
3592{
3593 ariaElementsFromAttribute(ariaDescribedBy, aria_describedbyAttr);
3594}
3595
3596void AccessibilityObject::ariaDescribedByReferencingElements(AccessibilityChildrenVector& describers) const
3597{
3598 ariaElementsReferencedByAttribute(describers, aria_describedbyAttr);
3599}
3600
3601void AccessibilityObject::ariaDetailsElements(AccessibilityChildrenVector& ariaDetails) const
3602{
3603 ariaElementsFromAttribute(ariaDetails, aria_detailsAttr);
3604}
3605
3606void AccessibilityObject::ariaDetailsReferencingElements(AccessibilityChildrenVector& detailsFor) const
3607{
3608 ariaElementsReferencedByAttribute(detailsFor, aria_detailsAttr);
3609}
3610
3611void AccessibilityObject::ariaErrorMessageElements(AccessibilityChildrenVector& ariaErrorMessage) const
3612{
3613 ariaElementsFromAttribute(ariaErrorMessage, aria_errormessageAttr);
3614}
3615
3616void AccessibilityObject::ariaErrorMessageReferencingElements(AccessibilityChildrenVector& errorMessageFor) const
3617{
3618 ariaElementsReferencedByAttribute(errorMessageFor, aria_errormessageAttr);
3619}
3620
3621void AccessibilityObject::ariaFlowToElements(AccessibilityChildrenVector& flowTo) const
3622{
3623 ariaElementsFromAttribute(flowTo, aria_flowtoAttr);
3624}
3625
3626void AccessibilityObject::ariaFlowToReferencingElements(AccessibilityChildrenVector& flowFrom) const
3627{
3628 ariaElementsReferencedByAttribute(flowFrom, aria_flowtoAttr);
3629}
3630
3631void AccessibilityObject::ariaLabelledByElements(AccessibilityChildrenVector& ariaLabelledBy) const
3632{
3633 ariaElementsFromAttribute(ariaLabelledBy, aria_labelledbyAttr);
3634 if (!ariaLabelledBy.size())
3635 ariaElementsFromAttribute(ariaLabelledBy, aria_labeledbyAttr);
3636}
3637
3638void AccessibilityObject::ariaLabelledByReferencingElements(AccessibilityChildrenVector& labels) const
3639{
3640 ariaElementsReferencedByAttribute(labels, aria_labelledbyAttr);
3641 if (!labels.size())
3642 ariaElementsReferencedByAttribute(labels, aria_labeledbyAttr);
3643}
3644
3645void AccessibilityObject::ariaOwnsElements(AccessibilityChildrenVector& axObjects) const
3646{
3647 ariaElementsFromAttribute(axObjects, aria_ownsAttr);
3648}
3649
3650void AccessibilityObject::ariaOwnsReferencingElements(AccessibilityChildrenVector& owners) const
3651{
3652 ariaElementsReferencedByAttribute(owners, aria_ownsAttr);
3653}
3654
3655void AccessibilityObject::setIsIgnoredFromParentDataForChild(AccessibilityObject* child)
3656{
3657 if (!child)
3658 return;
3659
3660 if (child->parentObject() != this) {
3661 child->clearIsIgnoredFromParentData();
3662 return;
3663 }
3664
3665 AccessibilityIsIgnoredFromParentData result = AccessibilityIsIgnoredFromParentData(this);
3666 if (!m_isIgnoredFromParentData.isNull()) {
3667 result.isAXHidden = m_isIgnoredFromParentData.isAXHidden || equalLettersIgnoringASCIICase(child->getAttribute(aria_hiddenAttr), "true");
3668 result.isPresentationalChildOfAriaRole = m_isIgnoredFromParentData.isPresentationalChildOfAriaRole || ariaRoleHasPresentationalChildren();
3669 result.isDescendantOfBarrenParent = m_isIgnoredFromParentData.isDescendantOfBarrenParent || !canHaveChildren();
3670 } else {
3671 result.isAXHidden = child->isAXHidden();
3672 result.isPresentationalChildOfAriaRole = child->isPresentationalChildOfAriaRole();
3673 result.isDescendantOfBarrenParent = child->isDescendantOfBarrenParent();
3674 }
3675
3676 child->setIsIgnoredFromParentData(result);
3677}
3678
3679} // namespace WebCore
3680