Bug Summary

File:Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/Source/WebCore/accessibility/AXCoreObject.cpp
Warning:line 2057, column 28
Call argument for parameter 'u' is uncounted and unsafe

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple arm64-apple-macosx15.5.0 -O2 -Wundef-prefix=TARGET_OS_ -Werror=undef-prefix -Wdeprecated-objc-isa-usage -Werror=deprecated-objc-isa-usage -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name UnifiedSource106.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=osx -analyzer-checker=security.insecureAPI.decodeValueOfObjCType -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -mframe-pointer=non-leaf -ffp-contract=on -fno-rounding-math -target-sdk-version=15.5 -fcompatibility-qualified-id-block-type-checking -fvisibility-inlines-hidden-static-local-var -fno-modulemap-allow-subdirectory-search -fdefine-target-os-macros -enable-tlsdesc -target-cpu apple-m1 -target-feature +v8.4a -target-feature +aes -target-feature +altnzcv -target-feature +ccdp -target-feature +ccpp -target-feature +complxnum -target-feature +crc -target-feature +dotprod -target-feature +flagm -target-feature +fp-armv8 -target-feature +fp16fml -target-feature +fptoint -target-feature +fullfp16 -target-feature +jsconv -target-feature +lse -target-feature +neon -target-feature +pauth -target-feature +perfmon -target-feature +predres -target-feature +ras -target-feature +rcpc -target-feature +rdm -target-feature +sb -target-feature +sha2 -target-feature +sha3 -target-feature +specrestrict -target-feature +ssbs -target-abi darwinpcs -dwarf-ext-refs -fmodule-format=obj -debug-info-kind=standalone -dwarf-version=5 -debugger-tuning=lldb -fdebug-compilation-dir=/Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/Source/WebCore -target-linker-version 1167.5 -fcoverage-compilation-dir=/Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/Source/WebCore -resource-dir /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/llvm-project/build/lib/clang/21 -dependency-file /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/scan-build-output/StaticAnalyzer/WebCore/WebCore/normal/arm64/UnifiedSource106.d -skip-unused-modulemap-deps -MT dependencies -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk -iquote /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release/WebCore.build/WebCore-generated-files.hmap -iquote /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release/WebCore.build/WebCore-project-headers.hmap -isystem /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/Source/WebCore/PAL/ThirdParty/dav1d/include -iframework /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/System/Library/PrivateFrameworks -iframework /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/System/Library/Frameworks -include-pch /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/PrecompiledHeaders/SharedPrecompiledHeaders/12902642326230122134/WebCorePrefix.h.gch -D _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE -D CLANG_WEBKIT_BRANCH=1 -D NDEBUG -D BUILDING_WEBKIT -D GL_SILENCE_DEPRECATION=1 -D GLES_SILENCE_DEPRECATION=1 -D __clang_analyzer__ -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release/WebCore.build/WebCore-own-target-headers.hmap -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release/WebCore.build/WebCore-all-target-headers.hmap -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/Release/include -I PAL -I ForwardingHeaders -I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/usr/include/libxslt -I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/usr/include/libxml2 -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/Release/DerivedSources/WebCore -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/Release/usr/local/include -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/Release/usr/local/include/WebKitAdditions -I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/usr/local/include/WebKitAdditions -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/Release/usr/local/include -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitLibraries/SDKs/macosx15.0-additions.sdk/usr/local/include -I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/usr/local/include -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/Release/usr/local/include/webrtc -I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/usr/local/include/webrtc -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/Release/usr/local/include/webm -I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/usr/local/include/webm -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/PAL.build/PAL.build/DerivedSources -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/PAL.build/Release/PAL.build/DerivedSources -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/Source/WebCore -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release/WebCore.build/DerivedSources-normal/arm64 -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release/WebCore.build/DerivedSources/arm64 -I /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release/WebCore.build/DerivedSources -F/Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/Release -I/usr/local/include -stdlib=libc++ -internal-isystem /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/usr/include/c++/v1 -internal-isystem /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/usr/local/include -internal-isystem /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/llvm-project/build/lib/clang/21/include -internal-externc-isystem /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/usr/include -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/System/Library/Frameworks -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/System/Library/SubFrameworks -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/Library/Frameworks -Wno-trigraphs -Werror -Wno-missing-field-initializers -Wmissing-prototypes -Wunreachable-code -Wnon-virtual-dtor -Wno-overloaded-virtual -Wno-exit-time-destructors -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wno-float-conversion -Wnon-literal-null-conversion -Wobjc-literal-conversion -Wsign-compare -Wno-shorten-64-to-32 -Wnewline-eof -Wno-c++11-extensions -Wno-implicit-fallthrough -Wdeprecated-declarations -Winvalid-offsetof -Wno-sign-conversion -Winfinite-recursion -Wmove -Wcomma -Wblock-capture-autoreleasing -Wstrict-prototypes -Wrange-loop-analysis -Wno-semicolon-before-method-body -Wall -Wc99-designator -Wconditional-uninitialized -Wextra -Wdeprecated-enum-enum-conversion -Wdeprecated-enum-float-conversion -Wenum-float-conversion -Wfinal-dtor-non-final-class -Wformat=2 -Wmisleading-indentation -Wpointer-to-int-cast -Wreorder-init-list -Wundef -Wunused-but-set-variable -Wvla -Werror=unguarded-availability -Wno-elaborated-enum-base -Wcast-qual -Wchar-subscripts -Wextra-tokens -Winit-self -Wmissing-noreturn -Wpacked -Wpointer-arith -Wredundant-decls -Wwrite-strings -Wexit-time-destructors -Wglobal-constructors -Wtautological-compare -Wimplicit-fallthrough -Wvla -Wno-unknown-warning-option -Wliteral-conversion -Wthread-safety -Wno-profile-instr-out-of-date -Wno-profile-instr-unprofiled -Wunsafe-buffer-usage -std=c++2b -fdeprecated-macro -ferror-limit 19 -fmacro-backtrace-limit=0 -fvisibility=hidden -fvisibility-inlines-hidden -stack-protector 1 -fblocks -fencode-extended-block-signature -fno-rtti -fregister-global-dtors-with-atexit -fgnuc-version=4.2.1 -fno-cxx-modules -fno-implicit-modules -fskip-odr-check-in-gmf -fpascal-strings -fmax-type-align=16 -fdiagnostics-show-note-include-stack -vectorize-loops -vectorize-slp -fsafe-buffer-usage-suggestions -analyzer-output=html -analyzer-config report-in-main-source-file=true -analyzer-config nullability:NoDiagnoseCallsToSystemHeaders=true -analyzer-checker optin.osx.cocoa.localizability.NonLocalizedStringChecker -analyzer-checker security.insecureAPI.UncheckedReturn -analyzer-checker security.insecureAPI.getpw -analyzer-checker security.insecureAPI.gets -analyzer-checker security.insecureAPI.mkstemp -analyzer-checker security.insecureAPI.mktemp -analyzer-disable-checker security.insecureAPI.rand -analyzer-disable-checker security.insecureAPI.strcpy -analyzer-checker security.insecureAPI.vfork -analyzer-disable-checker alpha,apiModeling,core,cplusplus,deadcode,debug,fuchsia,nullability,optin,osx,security,unix,webkit -analyzer-checker alpha.webkit.ForwardDeclChecker,alpha.webkit.MemoryUnsafeCastChecker,alpha.webkit.NoDeleteChecker,alpha.webkit.NoUncheckedPtrMemberChecker,alpha.webkit.NoUnretainedMemberChecker,alpha.webkit.RetainPtrCtorAdoptChecker,alpha.webkit.UncheckedCallArgsChecker,alpha.webkit.UncheckedLocalVarsChecker,alpha.webkit.UncountedCallArgsChecker,alpha.webkit.UncountedLocalVarsChecker,alpha.webkit.UnretainedCallArgsChecker,alpha.webkit.UnretainedLambdaCapturesChecker,alpha.webkit.UnretainedLocalVarsChecker,webkit.NoUncountedMemberChecker,webkit.RefCntblBaseVirtualDtor,webkit.UncountedLambdaCapturesChecker -analyzer-config max-nodes=10000000 -analyzer-config verbose-report-filename=true -fdwarf2-cfi-asm -o /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/scan-build-output/StaticAnalyzer/WebCore/WebCore/normal/arm64/UnifiedSource106.plist -x c++ /Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/WebKitBuild/Release/DerivedSources/WebCore/unified-sources/UnifiedSource106.cpp
1/*
2 * Copyright (C) 2023-2025 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 "AXCoreObject.h"
31
32#include "AXLoggerBase.h"
33#include "AXObjectCache.h"
34#include "AXSearchManager.h"
35#include "AXTreeStoreInlines.h"
36#include "AXUtilities.h"
37#include "DocumentView.h"
38#include "HTMLAreaElement.h"
39#include "LocalFrameView.h"
40#include "Logging.h"
41#include "RenderObjectStyle.h"
42#include "RenderStyle+GettersInlines.h"
43#include "Settings.h"
44#include "TextDecorationPainter.h"
45#include <wtf/Deque.h>
46#include <wtf/text/MakeString.h>
47
48namespace WebCore {
49
50bool AXCoreObject::isList() const
51{
52 auto role = this->role();
53 return role == AccessibilityRole::List || role == AccessibilityRole::DescriptionList;
54}
55
56bool AXCoreObject::isFileUploadButton() const
57{
58 std::optional type = inputType();
59 return type ? *type == InputType::Type::File : false;
60}
61
62bool AXCoreObject::isMenuRelated() const
63{
64 switch (role()) {
65 case AccessibilityRole::Menu:
66 case AccessibilityRole::MenuBar:
67 case AccessibilityRole::MenuItem:
68 case AccessibilityRole::MenuItemCheckbox:
69 case AccessibilityRole::MenuItemRadio:
70 return true;
71 default:
72 return false;
73 }
74}
75
76bool AXCoreObject::isMenuItem() const
77{
78 switch (role()) {
79 case AccessibilityRole::MenuItem:
80 case AccessibilityRole::MenuItemRadio:
81 case AccessibilityRole::MenuItemCheckbox:
82 return true;
83 default:
84 return false;
85 }
86}
87
88bool AXCoreObject::isInputImage() const
89{
90 if (role() != AccessibilityRole::Button)
91 return false;
92
93 std::optional type = inputType();
94 return type ? *type == InputType::Type::Image : false;
95}
96
97bool AXCoreObject::isControl() const
98{
99 switch (role()) {
100 case AccessibilityRole::Button:
101 case AccessibilityRole::Checkbox:
102 case AccessibilityRole::ColorWell:
103 case AccessibilityRole::ComboBox:
104 case AccessibilityRole::DateTime:
105 case AccessibilityRole::LandmarkSearch:
106 case AccessibilityRole::ListBox:
107 case AccessibilityRole::PopUpButton:
108 case AccessibilityRole::RadioButton:
109 case AccessibilityRole::SearchField:
110 case AccessibilityRole::Slider:
111 case AccessibilityRole::SliderThumb:
112 case AccessibilityRole::Switch:
113 case AccessibilityRole::TextArea:
114 case AccessibilityRole::TextField:
115 case AccessibilityRole::ToggleButton:
116 return true;
117 default:
118 return isFieldset();
119 }
120}
121
122bool AXCoreObject::isImplicitlyInteractive() const
123{
124 switch (role()) {
125 case AccessibilityRole::Button:
126 case AccessibilityRole::Checkbox:
127 case AccessibilityRole::ColorWell:
128 case AccessibilityRole::ComboBox:
129 case AccessibilityRole::DateTime:
130 case AccessibilityRole::Details:
131 case AccessibilityRole::LandmarkSearch:
132 case AccessibilityRole::Link:
133 case AccessibilityRole::ListBox:
134 case AccessibilityRole::ListBoxOption:
135 case AccessibilityRole::MenuItemCheckbox:
136 case AccessibilityRole::MenuItemRadio:
137 case AccessibilityRole::MenuListOption:
138 case AccessibilityRole::MenuListPopup:
139 case AccessibilityRole::PopUpButton:
140 case AccessibilityRole::RadioButton:
141 case AccessibilityRole::SearchField:
142 case AccessibilityRole::Slider:
143 case AccessibilityRole::SliderThumb:
144 case AccessibilityRole::SpinButton:
145 case AccessibilityRole::SpinButtonPart:
146 case AccessibilityRole::Switch:
147 case AccessibilityRole::Tab:
148 case AccessibilityRole::TextArea:
149 case AccessibilityRole::TextField:
150 case AccessibilityRole::ToggleButton:
151 return true;
152 default:
153 return false;
154 }
155}
156
157bool AXCoreObject::isLandmark() const
158{
159 switch (role()) {
160 case AccessibilityRole::Form:
161 case AccessibilityRole::LandmarkBanner:
162 case AccessibilityRole::LandmarkComplementary:
163 case AccessibilityRole::LandmarkContentInfo:
164 case AccessibilityRole::LandmarkDocRegion:
165 case AccessibilityRole::LandmarkMain:
166 case AccessibilityRole::LandmarkNavigation:
167 case AccessibilityRole::LandmarkRegion:
168 case AccessibilityRole::LandmarkSearch:
169 return true;
170 default:
171 return false;
172 }
173}
174
175bool AXCoreObject::isGroup() const
176{
177 switch (role()) {
178 case AccessibilityRole::Group:
179 case AccessibilityRole::TextGroup:
180 return true;
181 default:
182 return false;
183 }
184}
185
186bool AXCoreObject::isImageMapLink() const
187{
188 RefPtr element = this->element();
189 return element && is<HTMLAreaElement>(*element);
190}
191
192bool AXCoreObject::hasHighlighting() const
193{
194 for (RefPtr ancestor = this; ancestor; ancestor = ancestor->parentObject()) {
195 if (ancestor->hasMarkTag())
196 return true;
197 }
198 return false;
199}
200
201bool AXCoreObject::hasGridRole() const
202{
203 auto role = this->role();
204 return role == AccessibilityRole::Grid || role == AccessibilityRole::TreeGrid;
205}
206
207bool AXCoreObject::hasCellRole() const
208{
209 auto role = this->role();
210 return role == AccessibilityRole::Cell || role == AccessibilityRole::GridCell || role == AccessibilityRole::ColumnHeader || role == AccessibilityRole::RowHeader;
211}
212
213bool AXCoreObject::hasCellOrRowRole() const
214{
215 return hasCellRole() || role() == AccessibilityRole::Row;
216}
217
218bool AXCoreObject::isButton() const
219{
220 switch (role()) {
221 case AccessibilityRole::Button:
222 case AccessibilityRole::PopUpButton:
223 case AccessibilityRole::ToggleButton:
224 return true;
225 default:
226 return false;
227 }
228}
229
230bool AXCoreObject::isTextControl() const
231{
232 switch (role()) {
233 case AccessibilityRole::ComboBox:
234 case AccessibilityRole::SearchField:
235 case AccessibilityRole::TextArea:
236 case AccessibilityRole::TextField:
237 return true;
238 default:
239 return false;
240 }
241}
242
243ListBoxInterpretation AXCoreObject::listBoxInterpretation() const
244{
245 if (role() != AccessibilityRole::ListBox)
246 return ListBoxInterpretation::NotListBox;
247
248 Deque<Ref<AXCoreObject>, /* inlineCapacity */ 100> queue;
249 for (Ref child : const_cast<AXCoreObject*>(this)->childrenIncludingIgnored())
250 queue.append(WTF::move(child));
251
252 unsigned iterations = 0;
253 bool foundListItem = false;
254 while (!queue.isEmpty()) {
255 Ref current = queue.takeFirst();
256
257 // Technically, per ARIA, the only valid children of listboxes are options, or groups containing options.
258 // But be permissive and call this listbox valid if it has at least one option.
259 if (current->isListBoxOption())
260 return ListBoxInterpretation::ActuallyListBox;
261 if (current->isListItem())
262 foundListItem = true;
263
264 // If we've checked 10 children and found a list item but no options, treat it as a static list.
265 if (iterations > 10 && foundListItem)
266 return ListBoxInterpretation::ActuallyStaticList;
267
268 // Don't iterate forever in case someone added role="listbox" to some high-level element.
269 // If we haven't found an option after checking 200 objects, this probably isn't valid anyways.
270 if (iterations >= 250)
271 break;
272 ++iterations;
273
274 if (current->isGroup() || current->isIgnored()) {
275 for (Ref child : current->childrenIncludingIgnored())
276 queue.append(WTF::move(child));
277 }
278 }
279 return foundListItem ? ListBoxInterpretation::ActuallyStaticList : ListBoxInterpretation::InvalidListBox;
280}
281
282AXCoreObject::AccessibilityChildrenVector AXCoreObject::tabChildren()
283{
284 if (role() != AccessibilityRole::TabList)
285 return { };
286
287 AXCoreObject::AccessibilityChildrenVector result;
288 for (const auto& child : unignoredChildren()) {
289 if (child->isTabItem())
290 result.append(child);
291 }
292 return result;
293}
294
295#if ENABLE(INCLUDE_IGNORED_IN_CORE_AX_TREE)(defined 1 && 1)
296static bool isValidChildForTable(AXCoreObject& object)
297{
298 auto role = object.role();
299 // Tables can only have these roles as exposed-to-AT children.
300 return role == AccessibilityRole::Row || role == AccessibilityRole::Column || role == AccessibilityRole::TableHeaderContainer || role == AccessibilityRole::Caption;
301}
302
303AXCoreObject::AccessibilityChildrenVector AXCoreObject::unignoredChildren(bool updateChildrenIfNeeded)
304{
305 if (onlyAddsUnignoredChildren())
306 return children(updateChildrenIfNeeded);
307
308 // The unignored children of this object are generated by iterating over its children, ignored or not,
309 // and finding the first unignored descendant object of that child (or itself, if unignored).
310 RefPtr<AXCoreObject> parent = nullptr;
311 const AXCoreObject::AccessibilityChildrenVector* siblings = nullptr;
312 bool isExposedTable = isExposableTable();
313 AXCoreObject::AccessibilityChildrenVector unignoredChildren;
314 const auto& children = childrenIncludingIgnored(updateChildrenIfNeeded);
315
316 RefPtr descendant = children.size() ? children[0].ptr() : nullptr;
317 while (descendant && descendant != this) {
318 bool childIsValid = !isExposedTable || isValidChildForTable(*descendant);
319 if (!childIsValid || descendant->isIgnored()) {
320 descendant = descendant->nextInPreOrder(updateChildrenIfNeeded, /* stayWithin */ this);
321 parent = nullptr;
322 continue;
323 }
324 unignoredChildren.append(*descendant);
325
326 constexpr size_t maxChildrenFailsafe = 100000;
327 if (unignoredChildren.size() >= maxChildrenFailsafe) [[unlikely]] {
328 // This should never happen in a well-formed accessibility tree, so we must
329 // be looping infinitely.
330 ASSERT_NOT_REACHED()((void)0);
331 return unignoredChildren;
332 }
333
334 while (descendant && descendant != this) {
335 if (!parent) {
336 parent = descendant->parentObject();
337 if (!parent) {
338 siblings = nullptr;
339 break;
340 }
341 siblings = &parent->childrenIncludingIgnored();
342 }
343
344 unsigned nextSiblingIndex = descendant->indexInParent() + 1;
345 if (RefPtr nextSibling = nextSiblingIndex < siblings->size() ? (*siblings)[nextSiblingIndex].ptr() : nullptr) {
346 descendant = WTF::move(nextSibling);
347 break;
348 }
349 // The descendant didn't have a next sibling, so ascend to its parent.
350 descendant = WTF::move(parent);
351 parent = nullptr;
352 }
353 }
354 return unignoredChildren;
355}
356
357bool AXCoreObject::hasUnignoredChild()
358{
359 const auto& children = childrenIncludingIgnored(/* updateChildrenIfNeeded */ true);
360 RefPtr descendant = children.size() ? children[0].ptr() : nullptr;
361 if (onlyAddsUnignoredChildren())
362 return descendant;
363
364 bool isExposedTable = isExposableTable();
365 while (descendant && descendant != this) {
366 bool childIsValid = !isExposedTable || isValidChildForTable(*descendant);
367 if (childIsValid && !descendant->isIgnored())
368 return true;
369 descendant = descendant->nextInPreOrder(/* updateChildrenIfNeeded */ true, /* stayWithin */ this);
370 }
371 return false;
372}
373
374#endif // ENABLE(INCLUDE_IGNORED_IN_CORE_AX_TREE)
375
376static AXCoreObject::AccessibilityChildrenVector childrenAfterStitching(AXCoreObject::AccessibilityChildrenVector&& children)
377{
378 children.removeAllMatching([] (const auto& child) {
379 if (!child->hasStitchableRole())
380 return false;
381
382 std::optional stitchedIntoID = child->stitchedIntoID();
383 return stitchedIntoID && *stitchedIntoID != child->objectID();
384 });
385
386 return children;
387}
388
389
390#if !ENABLE(INCLUDE_IGNORED_IN_CORE_AX_TREE)(defined 1 && 1)
391static AXCoreObject::AccessibilityChildrenVector childrenAfterStitching(const AXCoreObject::AccessibilityChildrenVector& children)
392{
393 auto childrenCopy = children;
394 return childrenAfterStitching(WTF::move(childrenCopy));
395}
396#endif // !ENABLE(INCLUDE_IGNORED_IN_CORE_AX_TREE)
397
398AXCoreObject::AccessibilityChildrenVector AXCoreObject::stitchedUnignoredChildren()
399{
400 return childrenAfterStitching(unignoredChildren());
401}
402
403std::optional<AXStitchGroup> AXCoreObject::stitchGroupIfRepresentative() const
404{
405 std::optional stitchGroup = this->stitchGroup();
406 if (!stitchGroup || stitchGroup->representativeID() != objectID() || stitchGroup->isEmpty())
407 return { };
408 return stitchGroup;
409}
410
411AXCoreObject* AXCoreObject::blockFlowAncestor() const
412{
413 return Accessibility::findAncestor(*this, /* includeSelf */ false, [] (const auto& ancestor) {
414 return ancestor.isBlockFlow();
415 });
416}
417
418std::optional<AXStitchGroup> AXCoreObject::stitchGroupFromGroups(const Vector<AXStitchGroup>* groups, IncludeGroupMembers includeGroupMembers) const
419{
420 if (!groups)
421 return { };
422
423 AXID thisAXID = objectID();
424 for (const auto& group : *groups) {
425 // Stitching zero or one elements doesn't make sense, so ensure our group is two or larger.
426 AX_ASSERT(group.members().size() >= 2)((void)0);
427
428 if (group.members().contains(thisAXID)) {
429 if (includeGroupMembers == IncludeGroupMembers::No) {
430 // If the caller doesn't need the group we belong to, don't bother doing the copy.
431 return std::optional(AXStitchGroup { { }, group.representativeID() });
432 }
433 return std::optional(group);
434 }
435 }
436 return { };
437}
438
439AXCoreObject::AccessibilityChildrenVector AXCoreObject::crossFrameUnignoredChildren()
440{
441 AXCoreObject::AccessibilityChildrenVector result = stitchedUnignoredChildren();
442
443#if ENABLE_ACCESSIBILITY_LOCAL_FRAME0
444 if (result.isEmpty()) {
445 if (RefPtr crossFrameChild = crossFrameChildObject())
446 result.append(*crossFrameChild);
447 } else {
448 for (size_t i = 0; i < result.size(); i++) {
449 if (auto* crossFrameChild = result[i]->crossFrameChildObject())
450 result[i] = *crossFrameChild;
451 }
452 }
453#endif
454
455 return result;
456}
457
458AXCoreObject* AXCoreObject::crossFrameParentObjectUnignored() const
459{
460 if (SUPPRESS_UNCOUNTED_LOCAL[[clang::suppress]] auto* result = parentObjectUnignored())
461 return result;
462#if ENABLE_ACCESSIBILITY_LOCAL_FRAME0
463 return crossFrameParentObject();
464#endif
465 return nullptr;
466}
467
468AXCoreObject::AccessibilityChildrenVector AXCoreObject::crossFrameChildrenIncludingIgnored(bool updateChildrenIfNeeded)
469{
470 AXCoreObject::AccessibilityChildrenVector result = childrenIncludingIgnored(updateChildrenIfNeeded);
471
472#if ENABLE_ACCESSIBILITY_LOCAL_FRAME0
473 if (result.isEmpty()) {
474 AXCoreObject* crossFrameChild = crossFrameChildObject();
475 if (crossFrameChild)
476 result.append(*crossFrameChild);
477 }
478#endif
479
480 return result;
481}
482
483bool AXCoreObject::crossFrameIsAncestorOfObject(const AXCoreObject& axObject) const
484{
485 return this == &axObject || axObject.crossFrameIsDescendantOfObject(*this);
486}
487
488bool AXCoreObject::crossFrameIsDescendantOfObject(const AXCoreObject& axObject) const
489{
490 return Accessibility::crossFrameFindAncestor<AXCoreObject>(*this, false, [&axObject] (const AXCoreObject& object) {
491 return &object == &axObject;
492 }) != nullptr;
493}
494
495AXCoreObject* AXCoreObject::parentObjectIncludingCrossFrame() const
496{
497 if (SUPPRESS_UNCOUNTED_LOCAL[[clang::suppress]] auto* parent = parentObject())
498 return parent;
499#if ENABLE_ACCESSIBILITY_LOCAL_FRAME0
500 return crossFrameParentObject();
501#else
502 return nullptr;
503#endif
504}
505
506#ifndef NDEBUG1
507void AXCoreObject::verifyChildrenIndexInParent(const AccessibilityChildrenVector& children) const
508{
509 if (!shouldSetChildIndexInParent()) {
510 // Due to known irregularities in how the accessibility tree is built, we don't want to
511 // do this verification for some types of objects, as it will always fail. At the time this
512 // was written, this is specifically table columns and table header containers, which insert
513 // cells as their children despite not being their "true" parent.
514 return;
515 }
516
517 for (unsigned i = 0; i < children.size(); i++)
518 AX_ASSERT(children[i]->indexInParent() == i)((void)0);
519}
520#endif
521
522RefPtr<AXCoreObject> AXCoreObject::nextInPreOrder(bool updateChildrenIfNeeded, AXCoreObject* stayWithin)
523{
524 return nextInPreOrder(updateChildrenIfNeeded, stayWithin, false);
525}
526
527RefPtr<AXCoreObject> AXCoreObject::nextInPreOrder(bool updateChildrenIfNeeded , AXCoreObject* stayWithin, bool includeCrossFrame)
528{
529 const auto& children = includeCrossFrame ? crossFrameChildrenIncludingIgnored(updateChildrenIfNeeded) : childrenIncludingIgnored(updateChildrenIfNeeded);
530
531 if (!children.isEmpty()) {
532 auto role = this->role();
533 if (role != AccessibilityRole::Column && role != AccessibilityRole::TableHeaderContainer) {
534 // Table columns and header containers add cells despite not being their "true" parent (which are the rows).
535 // Don't allow a pre-order traversal of these object types to return cells to avoid an infinite loop.
536 return children[0].copyRef();
537 }
538 }
539
540 if (stayWithin == this)
541 return nullptr;
542
543 RefPtr current = this;
544 RefPtr next = nextSiblingIncludingIgnored(updateChildrenIfNeeded, includeCrossFrame);
545 for (; !next; next = current->nextSiblingIncludingIgnored(updateChildrenIfNeeded, includeCrossFrame)) {
546#if ENABLE(INCLUDE_IGNORED_IN_CORE_AX_TREE)(defined 1 && 1)
547 current = includeCrossFrame ? current->parentObjectIncludingCrossFrame() : current->parentObject();
548#else
549 current = includeCrossFrame ? current->crossFrameParentObjectUnignored() : current->parentObjectUnignored();
550#endif
551
552 if (!current || stayWithin == current)
553 return nullptr;
554 }
555 return next;
556}
557
558RefPtr<AXCoreObject> AXCoreObject::previousInPreOrder(bool updateChildrenIfNeeded, AXCoreObject* stayWithin)
559{
560 if (stayWithin == this)
561 return nullptr;
562
563 if (RefPtr sibling = previousSiblingIncludingIgnored(updateChildrenIfNeeded)) {
564 const auto& children = sibling->childrenIncludingIgnored(updateChildrenIfNeeded);
565 if (children.size())
566 return sibling->deepestLastChildIncludingIgnored(updateChildrenIfNeeded);
567 return sibling;
568 }
569 return parentObject();
570}
571
572AXCoreObject* AXCoreObject::deepestLastChildIncludingIgnored(bool updateChildrenIfNeeded)
573{
574 const auto& children = childrenIncludingIgnored(updateChildrenIfNeeded);
575 if (children.isEmpty())
576 return nullptr;
577
578 Ref deepestChild = children[children.size() - 1];
579 while (true) {
580 const auto& descendants = deepestChild->childrenIncludingIgnored(updateChildrenIfNeeded);
581 if (descendants.isEmpty())
582 break;
583 deepestChild = descendants[descendants.size() - 1];
584 }
585 return deepestChild.unsafePtr();
586}
587
588size_t AXCoreObject::indexInSiblings(const AccessibilityChildrenVector& siblings) const
589{
590 unsigned indexOfThis = indexInParent();
591 if (indexOfThis >= siblings.size() || siblings[indexOfThis]->objectID() != objectID()) [[unlikely]] {
592 // If this happens, the accessibility tree is an incorrect state.
593 AX_ASSERT_NOT_REACHED()((void)0);
594
595 return siblings.findIf([this] (const Ref<AXCoreObject>& object) {
596 return object.ptr() == this;
597 });
598 }
599 return indexOfThis;
600}
601
602AXCoreObject* AXCoreObject::nextSiblingIncludingIgnored(bool updateChildrenIfNeeded) const
603{
604 return nextSiblingIncludingIgnored(updateChildrenIfNeeded, /* crossFrame = */ false);
605}
606
607AXCoreObject* AXCoreObject::nextSiblingIncludingIgnored(bool updateChildrenIfNeeded, bool includeCrossFrame) const
608{
609#if ENABLE(INCLUDE_IGNORED_IN_CORE_AX_TREE)(defined 1 && 1)
610 RefPtr parent = parentObject();
611#else
612 RefPtr parent = parentObjectUnignored();
613#endif
614 if (!parent)
615 return nullptr;
616
617 const auto& siblings = includeCrossFrame ? parent->crossFrameChildrenIncludingIgnored(updateChildrenIfNeeded) : parent->childrenIncludingIgnored(updateChildrenIfNeeded);
618 size_t indexOfThis = indexInSiblings(siblings);
619 if (indexOfThis == notFound)
620 return nullptr;
621
622 return indexOfThis + 1 < siblings.size() ? siblings[indexOfThis + 1].unsafePtr() : nullptr;
623}
624
625AXCoreObject* AXCoreObject::previousSiblingIncludingIgnored(bool updateChildrenIfNeeded)
626{
627 RefPtr parent = parentObject();
628 if (!parent)
629 return nullptr;
630
631 const auto& siblings = parent->childrenIncludingIgnored(updateChildrenIfNeeded);
632 size_t indexOfThis = indexInSiblings(siblings);
633 if (indexOfThis == notFound)
634 return nullptr;
635
636 return indexOfThis >= 1 ? siblings[indexOfThis - 1].ptr() : nullptr;
637}
638
639AXCoreObject* AXCoreObject::nextUnignoredSibling(bool updateChildrenIfNeeded, AXCoreObject* unignoredParent) const
640{
641 // In some contexts, we may have already computed the `unignoredParent`, which is what this parameter is.
642 // Ensure this is actually our parent.
643 AX_ASSERT(unignoredParent == parentObjectUnignored())((void)0);
644
645 RefPtr parent = unignoredParent ? unignoredParent : parentObjectUnignored();
646 if (!parent)
647 return nullptr;
648 const auto& siblings = parent->unignoredChildren(updateChildrenIfNeeded);
649 size_t indexOfThis = siblings.findIf([this] (const Ref<AXCoreObject>& object) {
650 return object.ptr() == this;
651 });
652 if (indexOfThis == notFound)
653 return nullptr;
654
655 return indexOfThis + 1 < siblings.size() ? siblings[indexOfThis + 1].unsafePtr() : nullptr;
656}
657
658AXCoreObject* AXCoreObject::nextSiblingIncludingIgnoredOrParent() const
659{
660 if (auto* nextSibling = nextSiblingIncludingIgnored(/* updateChildrenIfNeeded */ true))
661 return nextSibling;
662 return parentObject();
663}
664
665String AXCoreObject::autoCompleteValue() const
666{
667 String explicitValue = explicitAutoCompleteValue();
668 return explicitValue.isEmpty() ? "none"_s : explicitValue;
669}
670
671String AXCoreObject::invalidStatus() const
672{
673 auto explicitValue = explicitInvalidStatus();
674 // "false" is the default if no invalid status is explicitly provided (e.g. via aria-invalid).
675 return explicitValue.isEmpty() ? "false"_s : explicitValue;
676}
677
678AXCoreObject::AccessibilityChildrenVector AXCoreObject::contents()
679{
680 if (isTabList())
681 return tabChildren();
682
683 if (isScrollArea()) {
684 // A scroll view's contents are everything except the scroll bars.
685 AccessibilityChildrenVector nonScrollbarChildren;
686 for (const auto& child : stitchedUnignoredChildren()) {
687 if (!child->isScrollbar())
688 nonScrollbarChildren.append(child);
689 }
690 return nonScrollbarChildren;
691 }
692 return { };
693}
694
695AXCoreObject::AccessibilityChildrenVector AXCoreObject::ariaTreeItemContent()
696{
697 AccessibilityChildrenVector result;
698 // The content of a treeitem excludes other treeitems or their containing groups.
699 for (const auto& child : unignoredChildren()) {
700 if (!child->isGroup() && child->role() != AccessibilityRole::TreeItem)
701 result.append(child);
702 }
703 return result;
704}
705
706String AXCoreObject::currentValue() const
707{
708 switch (currentState()) {
709 case AccessibilityCurrentState::False:
710 return "false"_s;
711 case AccessibilityCurrentState::Page:
712 return "page"_s;
713 case AccessibilityCurrentState::Step:
714 return "step"_s;
715 case AccessibilityCurrentState::Location:
716 return "location"_s;
717 case AccessibilityCurrentState::Time:
718 return "time"_s;
719 case AccessibilityCurrentState::Date:
720 return "date"_s;
721 default:
722 case AccessibilityCurrentState::True:
723 return "true"_s;
724 }
725}
726
727AXCoreObject::AXValue AXCoreObject::value()
728{
729 if (supportsRangeValue())
730 return valueForRange();
731
732 if (role() == AccessibilityRole::SliderThumb) {
733 RefPtr parent = parentObject();
734 return parent ? parent->valueForRange() : 0.0f;
735 }
736
737 if (isHeading())
738 return headingLevel();
739
740 if (supportsCheckedState())
741 return checkboxOrRadioValue();
742
743 if (role() == AccessibilityRole::Summary)
744 return isExpanded();
745
746 // Radio groups return the selected radio button as the AXValue.
747 if (isRadioGroup())
748 return selectedRadioButton();
749
750 if (isTabList())
751 return selectedTabItem();
752
753 if (isTabItem())
754 return isSelected();
755
756 if (isDateTime())
757 return dateTimeValue();
758
759 if (isColorWell()) {
760 auto color = convertColor<SRGBA<float>>(colorValue()).resolved();
761 auto channel = [](float number) {
762 return FormattedNumber::fixedPrecision(number, 6, TrailingZerosPolicy::Keep);
763 };
764 return color.alpha == 1
765 ? makeString("rgb "_s, channel(color.red), ' ', channel(color.green), ' ', channel(color.blue), " 1"_s)
766 : makeString("rgb "_s, channel(color.red), ' ', channel(color.green), ' ', channel(color.blue), ' ', channel(color.alpha));
767 }
768
769 return stringValue();
770}
771
772AXCoreObject* AXCoreObject::selectedRadioButton()
773{
774 if (!isRadioGroup())
775 return nullptr;
776
777 // Find the child radio button that is selected (ie. the intValue == 1).
778 for (const auto& child : unignoredChildren()) {
779 if (child->role() == AccessibilityRole::RadioButton && child->checkboxOrRadioValue() == AccessibilityButtonState::On)
780 return child.ptr();
781 }
782 return nullptr;
783}
784
785AXCoreObject* AXCoreObject::selectedTabItem()
786{
787 if (!isTabList())
788 return nullptr;
789
790 // FIXME: Is this valid? ARIA tab items support aria-selected; not aria-checked.
791 // Find the child tab item that is selected (ie. the intValue == 1).
792 for (const auto& child : unignoredChildren()) {
793 if (child->isTabItem() && (child->isChecked() || child->isSelected()))
794 return child.ptr();
795 }
796 return nullptr;
797}
798
799bool AXCoreObject::canHaveSelectedChildren() const
800{
801 switch (role()) {
802 // These roles are containers whose children support aria-selected:
803 case AccessibilityRole::Grid:
804 case AccessibilityRole::ListBox:
805 case AccessibilityRole::TabList:
806 case AccessibilityRole::Tree:
807 case AccessibilityRole::TreeGrid:
808 case AccessibilityRole::List:
809 // These roles are containers whose children are treated as selected by assistive
810 // technologies. We can get the "selected" item via aria-activedescendant or the
811 // focused element.
812 case AccessibilityRole::Menu:
813 case AccessibilityRole::MenuBar:
814 case AccessibilityRole::ComboBox:
815#if USE(ATSPI)(defined USE_ATSPI && USE_ATSPI)
816 case AccessibilityRole::MenuListPopup:
817#endif
818 return true;
819 default:
820 return false;
821 }
822}
823
824AXCoreObject::AccessibilityChildrenVector AXCoreObject::selectedChildren()
825{
826 if (!canHaveSelectedChildren())
827 return { };
828
829 switch (role()) {
830 case AccessibilityRole::ComboBox:
831 if (RefPtr descendant = activeDescendant())
832 return { { descendant.releaseNonNull() } };
833 break;
834 case AccessibilityRole::ListBox:
835 return listboxSelectedChildren();
836 case AccessibilityRole::Grid:
837 case AccessibilityRole::Tree:
838 case AccessibilityRole::TreeGrid:
839 return selectedRows();
840 case AccessibilityRole::TabList:
841 if (RefPtr selectedTab = selectedTabItem())
842 return { { selectedTab.releaseNonNull() } };
843 break;
844 case AccessibilityRole::List:
845 return selectedListItems();
846 case AccessibilityRole::Menu:
847 case AccessibilityRole::MenuBar:
848 if (RefPtr descendant = activeDescendant())
849 return { { descendant.releaseNonNull() } };
850 if (RefPtr focusedElement = focusedUIElement())
851 return { { focusedElement.releaseNonNull() } };
852 break;
853 case AccessibilityRole::MenuListPopup: {
854 AccessibilityChildrenVector selectedItems;
855 for (const auto& child : unignoredChildren()) {
856 if (child->isSelected())
857 selectedItems.append(child);
858 }
859 return selectedItems;
860 }
861 default:
862 AX_ASSERT_NOT_REACHED()((void)0);
863 break;
864 }
865 return { };
866}
867
868AXCoreObject::AccessibilityChildrenVector AXCoreObject::listboxSelectedChildren()
869{
870 AX_ASSERT(role() == AccessibilityRole::ListBox)((void)0);
871
872 AccessibilityChildrenVector result;
873 bool isMulti = isMultiSelectable();
874 for (const auto& child : unignoredChildren()) {
875 if (!child->isListBoxOption() || !child->isSelected())
876 continue;
877
878 result.append(child);
879 if (!isMulti)
880 return result;
881 }
882 return result;
883}
884
885AXCoreObject::AccessibilityChildrenVector AXCoreObject::selectedRows()
886{
887 AX_ASSERT(role() == AccessibilityRole::Grid || role() == AccessibilityRole::Tree || role() == AccessibilityRole::TreeGrid)((void)0);
888
889 bool isMulti = isMultiSelectable();
890
891 AccessibilityChildrenVector result;
892 // Prefer active descendant over aria-selected.
893 RefPtr activeDescendant = this->activeDescendant();
894 if (activeDescendant && (activeDescendant->isTreeItem() || activeDescendant->isExposedTableRow())) {
895 result.append(*activeDescendant);
896 if (!isMulti)
897 return result;
898 }
899
900 auto rowsIteration = [&](const auto& rows) {
901 for (auto& row : rows) {
902 if (row->isSelected() || row->isActiveDescendantOfFocusedContainer()) {
903 result.append(row);
904 if (!isMulti)
905 break;
906 }
907 }
908 };
909
910 if (isTree())
911 rowsIteration(ariaTreeRows());
912 else if (isExposableTable() && supportsSelectedRows())
913 rowsIteration(rows());
914 return result;
915}
916
917AXCoreObject::AccessibilityChildrenVector AXCoreObject::selectedListItems()
918{
919 AX_ASSERT(role() == AccessibilityRole::List)((void)0);
920
921 AccessibilityChildrenVector selectedListItems;
922 for (const auto& child : unignoredChildren()) {
923 if (child->isListItem() && child->isSelected())
924 selectedListItems.append(child);
925 }
926 return selectedListItems;
927}
928
929void AXCoreObject::ariaTreeRows(AccessibilityChildrenVector& rows, AccessibilityChildrenVector& ancestors)
930{
931 auto ownedObjects = this->ownedObjects();
932 ancestors.append(*this);
933
934 // The ordering of rows is first DOM children *not* in aria-owns, followed by all specified
935 // in aria-owns.
936 for (const auto& child : unignoredChildren()) {
937 // Add tree items as the rows.
938 if (child->role() == AccessibilityRole::TreeItem) {
939 // Child appears both as a direct child and aria-owns, we should use the ordering as
940 // described in aria-owns for this child.
941 if (ownedObjects.contains(child))
942 continue;
943
944 // The result set may already contain the child through aria-owns. For example,
945 // a treeitem sitting under the tree root, which is owned elsewhere in the tree.
946 if (rows.contains(child))
947 continue;
948
949 rows.append(child);
950 }
951
952 // Now see if this item also has rows hiding inside of it.
953 child->ariaTreeRows(rows, ancestors);
954 }
955
956 // Now go through the aria-owns elements.
957 for (const auto& child : ownedObjects) {
958 // Avoid a circular reference via aria-owns by checking if our parent
959 // path includes this child. Currently, looking up the aria-owns parent
960 // path itself could be expensive, so we track it separately.
961 if (ancestors.contains(child))
962 continue;
963
964 // Add tree items as the rows.
965 if (child->role() == AccessibilityRole::TreeItem) {
966 // Hopefully a flow that does not occur often in practice, but if someone were to include
967 // the owned child ealier in the top level of the tree, then reference via aria-owns later,
968 // move it to the right place.
969 if (rows.contains(child))
970 rows.removeFirst(child);
971
972 rows.append(child);
973 }
974
975 // Now see if this item also has rows hiding inside of it.
976 child->ariaTreeRows(rows, ancestors);
977 }
978
979 ancestors.removeLast();
980}
981
982AXCoreObject::AccessibilityChildrenVector AXCoreObject::ariaTreeRows()
983{
984 AccessibilityChildrenVector rows;
985 AccessibilityChildrenVector ancestors;
986 ariaTreeRows(rows, ancestors);
987 return rows;
988}
989
990bool AXCoreObject::isActiveDescendantOfFocusedContainer() const
991{
992 auto containers = activeDescendantOfObjects();
993 for (auto& container : containers) {
994 if (container->isFocused())
995 return true;
996 }
997
998 return false;
999}
1000
1001// ARIA spec: User agents must not expose the aria-roledescription property if the element to which aria-roledescription is applied does not have a valid WAI-ARIA role or does not have an implicit WAI-ARIA role semantic.
1002bool AXCoreObject::supportsARIARoleDescription() const
1003{
1004 switch (role()) {
1005 case AccessibilityRole::Generic:
1006 case AccessibilityRole::Unknown:
1007 return false;
1008 default:
1009 return true;
1010 }
1011}
1012
1013bool AXCoreObject::supportsRangeValue() const
1014{
1015 return isProgressIndicator()
1016 || isSlider()
1017 || isScrollbar()
1018 || isSpinButton()
1019 || (isSplitter() && canSetFocusAttribute())
1020 || hasAttachmentTag();
1021}
1022
1023bool AXCoreObject::supportsRequiredAttribute() const
1024{
1025 switch (role()) {
1026 case AccessibilityRole::Button:
1027 return isFileUploadButton();
1028 case AccessibilityRole::Cell:
1029 case AccessibilityRole::Checkbox:
1030 case AccessibilityRole::ComboBox:
1031 case AccessibilityRole::Grid:
1032 case AccessibilityRole::GridCell:
1033 case AccessibilityRole::ListBox:
1034 case AccessibilityRole::PopUpButton:
1035 case AccessibilityRole::RadioButton:
1036 case AccessibilityRole::RadioGroup:
1037 case AccessibilityRole::Slider:
1038 case AccessibilityRole::SpinButton:
1039 case AccessibilityRole::Switch:
1040 case AccessibilityRole::TextArea:
1041 case AccessibilityRole::TextField:
1042 case AccessibilityRole::ToggleButton:
1043 return true;
1044 default:
1045 return isRowHeader() || isColumnHeader();
1046 }
1047}
1048
1049bool AXCoreObject::isRootWebArea() const
1050{
1051 if (role() != AccessibilityRole::WebArea)
1052 return false;
1053
1054 RefPtr parent = parentObject();
1055 // If the parent is a scroll area, and the scroll area has no parent, we are at the root web area.
1056 return parent && parent->role() == AccessibilityRole::ScrollArea && !parent->parentObject();
1057}
1058
1059bool AXCoreObject::isRadioInput() const
1060{
1061 std::optional type = inputType();
1062 return type ? *type == InputType::Type::Radio : false;
1063}
1064
1065String AXCoreObject::popupValue() const
1066{
1067 String explicitValue = explicitPopupValue();
1068 if (!explicitValue.isEmpty())
1069 return explicitValue;
1070
1071 // In ARIA 1.1, the implicit value for combobox became "listbox."
1072 if (isComboBox())
1073 return "listbox"_s;
1074
1075 // The spec states that "User agents must treat any value of aria-haspopup that is not
1076 // included in the list of allowed values, including an empty string, as if the value
1077 // false had been provided."
1078 return "false"_s;
1079}
1080
1081bool AXCoreObject::hasPopup() const
1082{
1083 return !equalLettersIgnoringASCIICase(popupValue(), "false"_s);
1084}
1085
1086bool AXCoreObject::selfOrAncestorLinkHasPopup() const
1087{
1088 if (hasPopup())
1089 return true;
1090
1091 for (RefPtr ancestor = parentObject(); ancestor; ancestor = ancestor->parentObject()) {
1092 // If this logic gets updated (e.g. we no longer check isLink()), make sure to also update
1093 // -[WebAccessibilityObjectWrapperMac accessibilityAttributeNames].
1094 if (ancestor->isLink() && ancestor->hasPopup())
1095 return true;
1096 }
1097 return false;
1098}
1099
1100std::optional<AccessibilityOrientation> AXCoreObject::defaultOrientation() const
1101{
1102 switch (role()) {
1103 case AccessibilityRole::DescriptionList:
1104 case AccessibilityRole::List:
1105 case AccessibilityRole::ListBox:
1106 case AccessibilityRole::Menu:
1107 case AccessibilityRole::ScrollBar:
1108 case AccessibilityRole::Tree:
1109 return { AccessibilityOrientation::Vertical };
1110 case AccessibilityRole::MenuBar:
1111 case AccessibilityRole::Slider:
1112 case AccessibilityRole::Splitter:
1113 case AccessibilityRole::TabList:
1114 case AccessibilityRole::Toolbar:
1115 return { AccessibilityOrientation::Horizontal };
1116 case AccessibilityRole::ComboBox:
1117 case AccessibilityRole::RadioGroup:
1118 case AccessibilityRole::TreeGrid:
1119 return { AccessibilityOrientation::Undefined };
1120 default:
1121 return std::nullopt;
1122 }
1123}
1124
1125AccessibilityOrientation AXCoreObject::orientation() const
1126{
1127 if (std::optional orientation = explicitOrientation())
1128 return *orientation;
1129
1130 // In ARIA 1.1, the implicit value of aria-orientation changed from horizontal
1131 // to undefined on all roles that don't have their own role-specific values. In
1132 // addition, the implicit value of combobox became undefined.
1133 if (std::optional defaultOrientation = this->defaultOrientation())
1134 return *defaultOrientation;
1135
1136 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width;
1137 auto size = this->size();
1138 if (size.width() > size.height())
1139 return AccessibilityOrientation::Horizontal;
1140 if (size.height() > size.width())
1141 return AccessibilityOrientation::Vertical;
1142
1143 return AccessibilityOrientation::Undefined;
1144}
1145
1146AccessibilitySortDirection AXCoreObject::sortDirectionIncludingAncestors() const
1147{
1148 for (RefPtr ancestor = this; ancestor; ancestor = ancestor->parentObject()) {
1149 auto direction = ancestor->sortDirection();
1150 if (direction != AccessibilitySortDirection::Invalid)
1151 return direction;
1152 }
1153 return AccessibilitySortDirection::Invalid;
1154}
1155
1156unsigned AXCoreObject::tableLevel() const
1157{
1158 if (!isTable())
1159 return 0;
1160
1161 unsigned level = 0;
1162 RefPtr current = exposedTableAncestor(true /* includeSelf */);
1163 while (current) {
1164 level++;
1165 current = current->exposedTableAncestor(false);
1166 }
1167 return level;
1168}
1169
1170AXCoreObject* AXCoreObject::columnHeader()
1171{
1172 if (!isTableColumn())
1173 return nullptr;
1174
1175 RefPtr parent = parentObject();
1176 if (!parent || !parent->isExposableTable())
1177 return nullptr;
1178
1179 for (const auto& cell : unignoredChildren()) {
1180 if (cell->isColumnHeader())
1181 return cell.ptr();
1182 }
1183 return nullptr;
1184}
1185
1186AXCoreObject* AXCoreObject::rowHeader()
1187{
1188 const auto& rowChildren = unignoredChildren();
1189 if (rowChildren.isEmpty())
1190 return nullptr;
1191
1192 bool isARIAGridRow = this->isARIAGridRow();
1193
1194 Ref firstCell = rowChildren[0].get();
1195 if (!isARIAGridRow && !firstCell->hasElementName(ElementName::HTML_th))
1196 return nullptr;
1197
1198 // Verify that the row header is not part of an entire row of headers.
1199 // In that case, it is unlikely this is a row header (for non-grid rows).
1200 for (const auto& child : rowChildren) {
1201 // We found a non-header cell, so this is not an entire row of headers -- return the original header cell.
1202 if (!isARIAGridRow && !child->hasElementName(ElementName::HTML_th))
1203 return firstCell.unsafePtr();
1204
1205 // For grid rows, the first header encountered is the row header.
1206 if (isARIAGridRow && child->isRowHeader())
1207 return child.ptr();
1208 }
1209 return nullptr;
1210}
1211
1212AXCoreObject::AccessibilityChildrenVector AXCoreObject::columnHeaders()
1213{
1214 AccessibilityChildrenVector headers;
1215 if (hasCellRole()) {
1216 RefPtr parentTable = exposedTableAncestor();
1217 if (!parentTable)
1218 return { };
1219
1220 // Choose columnHeaders as the place where the "headers" attribute is reported.
1221 headers = relatedObjects(AXRelation::Headers);
1222 // If the headers attribute returned valid values, then do not further search for column headers.
1223 if (!headers.isEmpty())
1224 return headers;
1225
1226 auto rowRange = rowIndexRange();
1227 auto colRange = columnIndexRange();
1228 auto rowGroupAncestor = rowGroupAncestorID();
1229 for (unsigned row = 0; row < rowRange.first; row++) {
1230 RefPtr tableCell = parentTable->cellForColumnAndRow(colRange.first, row);
1231 if (!tableCell || tableCell == this || headers.contains(Ref { *tableCell }))
1232 continue;
1233
1234 if (tableCell->cellScope() == "colgroup"_s && tableCell->rowGroupAncestorID() == rowGroupAncestor)
1235 headers.append(tableCell.releaseNonNull());
1236 else if (tableCell->isColumnHeader())
1237 headers.append(tableCell.releaseNonNull());
1238 }
1239 } else if (isTable()) {
1240 auto columns = this->columns();
1241 for (const auto& column : columns) {
1242 if (RefPtr header = column->columnHeader())
1243 headers.append(header.releaseNonNull());
1244 }
1245 }
1246 return headers;
1247}
1248
1249std::optional<AXID> AXCoreObject::rowGroupAncestorID() const
1250{
1251 if (!hasCellRole())
1252 return { };
1253
1254 RefPtr rowGroup = Accessibility::findAncestor<AXCoreObject>(*this, /* includeSelf */ false, [] (const auto& ancestor) {
1255 return ancestor.hasRowGroupTag();
1256 });
1257
1258 if (!rowGroup)
1259 return std::nullopt;
1260 return rowGroup->objectID();
1261}
1262
1263bool AXCoreObject::isTableCellInSameRowGroup(AXCoreObject& otherTableCell)
1264{
1265 auto ancestorID = rowGroupAncestorID();
1266 return ancestorID && *ancestorID == otherTableCell.rowGroupAncestorID();
1267}
1268
1269bool AXCoreObject::isTableCellInSameColGroup(AXCoreObject* tableCell)
1270{
1271 if (!tableCell)
1272 return false;
1273
1274 auto columnRange = columnIndexRange();
1275 auto otherColumnRange = tableCell->columnIndexRange();
1276
1277 return columnRange.first <= otherColumnRange.first + otherColumnRange.second;
1278}
1279
1280bool AXCoreObject::isReplacedElement() const
1281{
1282 // FIXME: Should this include <legend> and form control elements like TextIterator::isRendererReplacedElement does?
1283 switch (role()) {
1284 case AccessibilityRole::Audio:
1285 case AccessibilityRole::Image:
1286 case AccessibilityRole::Meter:
1287 case AccessibilityRole::ProgressIndicator:
1288 case AccessibilityRole::Video:
1289 return true;
1290 default:
1291 return isWidget() || hasAttachmentTag();
1292 }
1293}
1294
1295AXCoreObject::AccessibilityChildrenVector AXCoreObject::revealableContainers()
1296{
1297 AXCoreObject::AccessibilityChildrenVector revealableContainers;
1298
1299 auto isCollapsedDetails = [this] {
1300 return role() == AccessibilityRole::Details && !isExpanded();
1301 };
1302 if (isHiddenUntilFoundContainer() || isCollapsedDetails())
1303 revealableContainers.append(*this);
1304
1305 if (role() == AccessibilityRole::Summary) {
1306 // When rendered, the summary element is the thing that assistive technologies can actually
1307 // navigate to. Return our containing details as the revealable container so we can search
1308 // inside the details subtree for revealable text.
1309 if (RefPtr details = detailsAncestor(); details && !details->isExpanded())
1310 revealableContainers.append(details.releaseNonNull());
1311 }
1312 return revealableContainers;
1313}
1314
1315bool AXCoreObject::containsOnlyStaticText() const
1316{
1317 bool hasText = false;
1318 RefPtr nonTextDescendant = Accessibility::findUnignoredDescendant(const_cast<AXCoreObject&>(*this), /* includeSelf */ false, [&] (auto& descendant) {
1319 if (descendant.isGroup()) {
1320 // Skip through groups to keep looking for text.
1321 return false;
1322 }
1323
1324 if (descendant.isStaticText()) {
1325 hasText = hasText || !descendant.isIgnored();
1326 return false;
1327 }
1328 return true;
1329 });
1330 return hasText && !nonTextDescendant;
1331}
1332
1333String AXCoreObject::roleDescription()
1334{
1335 if (hasAttachmentTag())
1336 return AXAttachmentRoleText();
1337
1338 // aria-roledescription takes precedence over any other rule.
1339 if (supportsARIARoleDescription()) {
1340 auto roleDescription = ariaRoleDescription();
1341 if (!roleDescription.isEmpty())
1342 return roleDescription;
1343 }
1344
1345 auto roleDescription = rolePlatformDescription();
1346 if (!roleDescription.isEmpty())
1347 return roleDescription;
1348
1349 if (role() == AccessibilityRole::Figure)
1350 return AXFigureText();
1351
1352 if (role() == AccessibilityRole::Suggestion)
1353 return AXSuggestionRoleDescriptionText();
1354
1355 return { };
1356}
1357
1358String AXCoreObject::ariaLandmarkRoleDescription() const
1359{
1360 switch (role()) {
1361 case AccessibilityRole::Form:
1362 return AXARIAContentGroupText("ARIALandmarkForm"_s);
1363 case AccessibilityRole::LandmarkBanner:
1364 return AXARIAContentGroupText("ARIALandmarkBanner"_s);
1365 case AccessibilityRole::LandmarkComplementary:
1366 return AXARIAContentGroupText("ARIALandmarkComplementary"_s);
1367 case AccessibilityRole::LandmarkContentInfo:
1368 return AXARIAContentGroupText("ARIALandmarkContentInfo"_s);
1369 case AccessibilityRole::LandmarkMain:
1370 return AXARIAContentGroupText("ARIALandmarkMain"_s);
1371 case AccessibilityRole::LandmarkNavigation:
1372 return AXARIAContentGroupText("ARIALandmarkNavigation"_s);
1373 case AccessibilityRole::LandmarkDocRegion:
1374 case AccessibilityRole::LandmarkRegion:
1375 return AXARIAContentGroupText("ARIALandmarkRegion"_s);
1376 case AccessibilityRole::LandmarkSearch:
1377 return AXARIAContentGroupText("ARIALandmarkSearch"_s);
1378 case AccessibilityRole::SectionFooter:
1379 return AXARIAContentGroupText("ARIASectionFooter"_s);
1380 case AccessibilityRole::SectionHeader:
1381 return AXARIAContentGroupText("ARIASectionHeader"_s);
1382 case AccessibilityRole::ApplicationAlert:
1383 return AXARIAContentGroupText("ARIAApplicationAlert"_s);
1384 case AccessibilityRole::ApplicationAlertDialog:
1385 return AXARIAContentGroupText("ARIAApplicationAlertDialog"_s);
1386 case AccessibilityRole::ApplicationDialog:
1387 return AXARIAContentGroupText("ARIAApplicationDialog"_s);
1388 case AccessibilityRole::ApplicationLog:
1389 return AXARIAContentGroupText("ARIAApplicationLog"_s);
1390 case AccessibilityRole::ApplicationMarquee:
1391 return AXARIAContentGroupText("ARIAApplicationMarquee"_s);
1392 case AccessibilityRole::ApplicationStatus:
1393 return AXARIAContentGroupText("ARIAApplicationStatus"_s);
1394 case AccessibilityRole::ApplicationTimer:
1395 return AXARIAContentGroupText("ARIAApplicationTimer"_s);
1396 case AccessibilityRole::Document:
1397 return AXARIAContentGroupText("ARIADocument"_s);
1398 case AccessibilityRole::DocumentArticle:
1399 return AXARIAContentGroupText("ARIADocumentArticle"_s);
1400 case AccessibilityRole::DocumentMath:
1401 return AXARIAContentGroupText("ARIADocumentMath"_s);
1402 case AccessibilityRole::DocumentNote:
1403 return AXARIAContentGroupText("ARIADocumentNote"_s);
1404 case AccessibilityRole::UserInterfaceTooltip:
1405 return AXARIAContentGroupText("ARIAUserInterfaceTooltip"_s);
1406 case AccessibilityRole::TabPanel:
1407 return AXARIAContentGroupText("ARIATabPanel"_s);
1408 case AccessibilityRole::WebApplication:
1409 return AXARIAContentGroupText("ARIAWebApplication"_s);
1410 default:
1411 return { };
1412 }
1413}
1414
1415bool AXCoreObject::supportsDatetimeAttribute() const
1416{
1417 auto elementName = this->elementName();
1418 return elementName == ElementName::HTML_ins || elementName == ElementName::HTML_del || elementName == ElementName::HTML_time;
1419}
1420
1421unsigned AXCoreObject::blockquoteLevel() const
1422{
1423 unsigned level = 0;
1424 for (RefPtr ancestor = parentObject(); ancestor; ancestor = ancestor->parentObject()) {
1425 if (ancestor->role() == AccessibilityRole::Blockquote)
1426 ++level;
1427 }
1428 return level;
1429}
1430
1431unsigned AXCoreObject::headingLevel() const
1432{
1433 if (isHeading()) {
1434 unsigned level = ariaLevel();
1435 if (level > 0)
1436 return level;
1437 }
1438
1439 auto elementName = this->elementName();
1440 if (elementName == ElementName::HTML_h1)
1441 return 1;
1442 if (elementName == ElementName::HTML_h2)
1443 return 2;
1444 if (elementName == ElementName::HTML_h3)
1445 return 3;
1446 if (elementName == ElementName::HTML_h4)
1447 return 4;
1448 if (elementName == ElementName::HTML_h5)
1449 return 5;
1450 if (elementName == ElementName::HTML_h6)
1451 return 6;
1452 return 0;
1453}
1454
1455unsigned AXCoreObject::hierarchicalLevel() const
1456{
1457 unsigned level = ariaLevel();
1458 if (level > 0)
1459 return level;
1460
1461 // Only tree item will calculate its level through the DOM currently.
1462 if (role() != AccessibilityRole::TreeItem)
1463 return 0;
1464
1465 // Hierarchy leveling starts at 1, to match the aria-level spec.
1466 // We measure tree hierarchy by the number of groups that the item is within.
1467 level = 1;
1468 for (RefPtr ancestor = parentObject(); ancestor; ancestor = ancestor->parentObject()) {
1469 auto ancestorRole = ancestor->role();
1470 if (ancestorRole == AccessibilityRole::Group)
1471 level++;
1472 else if (ancestorRole == AccessibilityRole::Tree)
1473 break;
1474 }
1475
1476 return level;
1477}
1478
1479bool AXCoreObject::supportsPressAction() const
1480{
1481 if (role() == AccessibilityRole::Presentational || hasPointerEventsNone())
1482 return false;
1483
1484 if (isImplicitlyInteractive() || hasClickHandler())
1485 return true;
1486
1487 if ((isStaticText() || isImage()) && !isIgnored()) {
1488 // These roles are not interactive, but sometimes authors expose click handlers on them or ancestors without
1489 // other appropriate ARIA markup indicating interactivity (e.g. by applying role="button"). We can repair these
1490 // scenarios by checking for a clickable ancestor. But want to do so selectively, as naively exposing press on
1491 // every text can be annoying as some screenreaders read "clickable" for each static text.
1492 bool foundCursor = hasCursorPointer();
1493
1494 RefPtr clickableAncestor = Accessibility::findAncestor(*this, /* includeSelf */ true, /* matchFunction */ [&foundCursor] (const auto& ancestor) {
1495 if (!foundCursor)
1496 foundCursor = ancestor.hasCursorPointer() || ancestor.showsCursorOnHover();
1497 return ancestor.hasClickHandler();
1498 }, /* stopTraversalFunction */ [] (const auto& ancestor) {
1499 // Stop traversing and return nullptr if we walk over an implicitly interactive element on our
1500 // way to the click handler, as we can rely on the semantics of that element to imply pressability.
1501 // Also stop when encountering the body or main to avoid exposing pressability for everything in
1502 // web apps that implement an event-delegation mechanism.
1503 auto role = ancestor.role();
1504 return ancestor.isImplicitlyInteractive() || role == AccessibilityRole::LandmarkMain || role == AccessibilityRole::Presentational || ancestor.hasBodyTag();
1505 });
1506
1507 if (!clickableAncestor)
1508 return false;
1509
1510 if (!foundCursor) {
1511 // If the author hasn't provided a pointer cursor, the visual experience also doesn't express
1512 // pressability, so return.
1513 return false;
1514 }
1515
1516 unsigned matches = 0;
1517 unsigned candidatesChecked = 0;
1518 RefPtr candidate = clickableAncestor;
1519 while ((candidate = candidate->nextInPreOrder(/* updateChildren */ true, /* stayWithin */ clickableAncestor.get()))) {
1520 if (candidate->isStaticText() || candidate->isControl() || candidate->isImage() || candidate->isHeading() || candidate->isLink()) {
1521 if (!candidate->isIgnored()) {
1522 if (!matches && this != candidate.get()) {
1523 // Only support press action for the first descendant. Some ATs, like VoiceOver, use the result of this function
1524 // to read "clickable", but reading it for every descendant of the clickable ancestor would be excessive.
1525 return false;
1526 }
1527 ++matches;
1528 }
1529
1530 static constexpr unsigned MAX_MATCHES = 6;
1531 if (matches >= MAX_MATCHES) {
1532 // If something has more than the arbitrarily-chosen number of valid matches,
1533 // this click handler is probably too coarse to be useful.
1534 return false;
1535 }
1536 }
1537
1538 ++candidatesChecked;
1539 static constexpr unsigned MAX_CANDIDATES = 256;
1540 if (candidatesChecked > MAX_CANDIDATES) {
1541 // If we've walked over the arbitrarily-chosen max number of potential candidates,
1542 // this click handler is probably too coarse to be useful, and too much traversing
1543 // can harm performance.
1544 return false;
1545 }
1546 }
1547
1548 // If we get here, and matches is greater than zero, we can assume we were the first matching
1549 // candidate for the click handler, and that there weren't too many matches or candidates checked.
1550 return matches > 0;
1551 }
1552 return false;
1553}
1554
1555bool AXCoreObject::supportsActiveDescendant() const
1556{
1557 switch (role()) {
1558 case AccessibilityRole::ComboBox:
1559 case AccessibilityRole::Grid:
1560 case AccessibilityRole::List:
1561 case AccessibilityRole::ListBox:
1562 case AccessibilityRole::TextArea:
1563 case AccessibilityRole::TextField:
1564 case AccessibilityRole::Tree:
1565 case AccessibilityRole::TreeGrid:
1566 return true;
1567 default:
1568 return false;
1569 }
1570}
1571
1572AXCoreObject* AXCoreObject::activeDescendant() const
1573{
1574 auto activeDescendants = relatedObjects(AXRelation::ActiveDescendant);
1575 AX_ASSERT(activeDescendants.size() <= 1)((void)0);
1576 if (!activeDescendants.isEmpty())
1577 return activeDescendants[0].unsafePtr();
1578 return nullptr;
1579}
1580
1581AXCoreObject* AXCoreObject::selfOrFirstTextDescendant()
1582{
1583 return Accessibility::findUnignoredDescendant(*this, /* includeSelf */ true, [] (auto& descendant) {
1584 return descendant.isStaticText();
1585 });
1586}
1587
1588AXCoreObject::AccessibilityChildrenVector AXCoreObject::selectedCells()
1589{
1590 if (!isTable())
1591 return { };
1592
1593 AXCoreObject::AccessibilityChildrenVector selectedCells;
1594 for (auto& cell : cells()) {
1595 if (cell->isSelected())
1596 selectedCells.append(cell);
1597 }
1598
1599 if (RefPtr activeDescendant = this->activeDescendant()) {
1600 if (activeDescendant->isExposedTableCell() && !selectedCells.contains(Ref { *activeDescendant }))
1601 selectedCells.append(activeDescendant.releaseNonNull());
1602 }
1603 return selectedCells;
1604}
1605
1606String AXCoreObject::languageIncludingAncestors() const
1607{
1608 auto language = this->language();
1609 if (!language.isEmpty())
1610 return language;
1611
1612 RefPtr parent = parentObject();
1613 return parent ? parent->languageIncludingAncestors() : nullAtom();
1614}
1615
1616#if PLATFORM(COCOA)(defined 1 && 1)
1617static bool isVisibleText(AccessibilityTextSource textSource)
1618{
1619 switch (textSource) {
1620 case AccessibilityTextSource::Visible:
1621 case AccessibilityTextSource::Children:
1622 case AccessibilityTextSource::LabelByElement:
1623 case AccessibilityTextSource::Heading:
1624 return true;
1625 case AccessibilityTextSource::Alternative:
1626 case AccessibilityTextSource::Summary:
1627 case AccessibilityTextSource::Help:
1628 case AccessibilityTextSource::TitleTag:
1629 case AccessibilityTextSource::Placeholder:
1630 case AccessibilityTextSource::Title:
1631 case AccessibilityTextSource::Subtitle:
1632 case AccessibilityTextSource::Action:
1633 return false;
1634 }
1635}
1636
1637static bool isDescriptiveText(AccessibilityTextSource textSource)
1638{
1639 switch (textSource) {
1640 case AccessibilityTextSource::Alternative:
1641 case AccessibilityTextSource::Visible:
1642 case AccessibilityTextSource::Children:
1643 case AccessibilityTextSource::LabelByElement:
1644 return true;
1645 case AccessibilityTextSource::Summary:
1646 case AccessibilityTextSource::Help:
1647 case AccessibilityTextSource::TitleTag:
1648 case AccessibilityTextSource::Placeholder:
1649 case AccessibilityTextSource::Title:
1650 case AccessibilityTextSource::Subtitle:
1651 case AccessibilityTextSource::Action:
1652 case AccessibilityTextSource::Heading:
1653 return false;
1654 }
1655}
1656
1657String AXCoreObject::descriptionAttributeValue(Vector<AccessibilityText>* computedText) const
1658{
1659 if (isStaticText()) {
1660 // Static text objects shouldn't return a description. Their content is communicated via AXValue.
1661 return { };
1662 }
1663
1664 Vector<AccessibilityText> textOrder;
1665 if (!computedText) {
1666 accessibilityText(textOrder);
1667 computedText = &textOrder;
1668 }
1669
1670 // Determine if any visible text is available, which influences our usage of title tag.
1671 bool visibleTextAvailable = false;
1672 for (const auto& text : *computedText) {
1673 if (isVisibleText(text.textSource) && !text.text.isEmpty()) {
1674 visibleTextAvailable = true;
1675 break;
1676 }
1677 }
1678
1679 StringBuilder returnText;
1680 for (const auto& text : *computedText) {
1681 if (text.textSource == AccessibilityTextSource::Alternative || text.textSource == AccessibilityTextSource::Heading) {
1682 returnText.append(text.text);
1683 break;
1684 }
1685
1686 switch (text.textSource) {
1687 // These are sub-components of one element (Attachment) that are re-combined in OSX and iOS.
1688 case AccessibilityTextSource::Title:
1689 case AccessibilityTextSource::Subtitle:
1690 case AccessibilityTextSource::Action: {
1691 if (!text.text.length())
1692 break;
1693 if (returnText.length())
1694 returnText.append(", "_s);
1695 returnText.append(text.text);
1696 break;
1697 }
1698 default:
1699 break;
1700 }
1701
1702 if (text.textSource == AccessibilityTextSource::TitleTag && !visibleTextAvailable) {
1703 returnText.append(text.text);
1704 break;
1705 }
1706 }
1707
1708 return returnText.toString();
1709}
1710
1711String AXCoreObject::helpTextAttributeValue() const
1712{
1713 Vector<AccessibilityText> textOrder;
1714 accessibilityText(textOrder);
1715
1716 // Determine if any descriptive text is available, which influences our usage of title tag.
1717 bool descriptiveTextAvailable = false;
1718 for (const auto& text : textOrder) {
1719 if (isDescriptiveText(text.textSource) && !text.text.isEmpty()) {
1720 descriptiveTextAvailable = true;
1721 break;
1722 }
1723 }
1724
1725 for (const auto& text : textOrder) {
1726 if (text.textSource == AccessibilityTextSource::Help || text.textSource == AccessibilityTextSource::Summary)
1727 return text.text;
1728
1729 // If an element does NOT have other descriptive text the title tag should be used as its descriptive text.
1730 // But, if those ARE available, then the title tag should be used for help text instead.
1731 if (text.textSource == AccessibilityTextSource::TitleTag && descriptiveTextAvailable)
1732 return text.text;
1733 }
1734
1735 return { };
1736}
1737#endif // PLATFORM(COCOA)
1738
1739String AXCoreObject::title(Vector<AccessibilityText>* computedText) const
1740{
1741 if (isWebArea()) {
1742 String title = webAreaTitle();
1743 if (!title.isEmpty())
1744 return title;
1745 }
1746
1747 // Static text should communicate its content through AXValue.
1748 // Meter elements should communicate their content via AXValueDescription.
1749 if (isStaticText() || isMeter())
1750 return { };
1751
1752 // A file upload button presents a challenge because it has button text and a value, but the
1753 // API doesn't support this paradigm.
1754 // The compromise is to return the button type in the role description and the value of the file path in the title
1755 if (isFileUploadButton() && fileUploadButtonReturnsValueInTitle())
1756 return stringValue();
1757
1758 Vector<AccessibilityText> textOrder;
1759 if (!computedText) {
1760 accessibilityText(textOrder);
1761 computedText = &textOrder;
1762 }
1763
1764 if (elementName() == ElementName::HTML_area && isLink()) {
1765 String summaryText;
1766 for (auto& text : *computedText) {
1767 if (text.textSource == AccessibilityTextSource::TitleTag)
1768 return text.text;
1769 if (text.textSource == AccessibilityTextSource::Summary)
1770 summaryText = WTF::move(text.text);
1771 }
1772 return summaryText;
1773 }
1774
1775 for (const auto& text : *computedText) {
1776 // If we have alternative text, then we should not expose a title.
1777 if (text.textSource == AccessibilityTextSource::Alternative || text.textSource == AccessibilityTextSource::Heading)
1778 break;
1779
1780 // Once we encounter visible text, or the text from our children that should be used foremost.
1781 if (text.textSource == AccessibilityTextSource::Visible || text.textSource == AccessibilityTextSource::Children)
1782 return text.text;
1783
1784 // If there's an element that labels this object and it's not exposed, then we should use
1785 // that text as our title.
1786 if (text.textSource == AccessibilityTextSource::LabelByElement)
1787 return text.text;
1788 }
1789 return { };
1790}
1791
1792AXCoreObject* AXCoreObject::titleUIElement() const
1793{
1794 auto labels = relatedObjects(AXRelation::LabeledBy);
1795#if PLATFORM(COCOA)(defined 1 && 1)
1796 // We impose the restriction that if there is more than one label, then we should return none.
1797 // FIXME: the behavior should be the same in all platforms.
1798 return labels.size() == 1 ? labels.first().unsafePtr() : nullptr;
1799#else
1800 return labels.size() ? labels.first().unsafePtr() : nullptr;
1801#endif
1802}
1803
1804String AXCoreObject::expandedTextValue() const
1805{
1806 if (isStaticText()) {
1807 if (RefPtr parent = parentObject()) {
1808 auto parentName = parent->elementName();
1809 if (parentName == ElementName::HTML_abbr || parentName == ElementName::HTML_acronym)
1810 return parent->titleAttribute();
1811 }
1812 }
1813 return hasCellRole() ? abbreviation() : String();
1814}
1815
1816bool AXCoreObject::supportsExpandedTextValue() const
1817{
1818 if (isStaticText()) {
1819 if (RefPtr parent = parentObject()) {
1820 auto parentName = parent->elementName();
1821 return parentName == ElementName::HTML_abbr || parentName == ElementName::HTML_acronym;
1822 }
1823 }
1824 return hasCellRole() && !abbreviation().isEmpty();
1825}
1826
1827AXCoreObject::AccessibilityChildrenVector AXCoreObject::linkedObjects() const
1828{
1829 auto linkedObjects = flowToObjects();
1830
1831 if (isLink()) {
1832 if (RefPtr linkedAXElement = internalLinkElement())
1833 linkedObjects.append(linkedAXElement.releaseNonNull());
1834 } else if (isRadioButton())
1835 appendRadioButtonGroupMembers(linkedObjects);
1836
1837 linkedObjects.appendVector(controlledObjects());
1838 linkedObjects.appendVector(ownedObjects());
1839
1840 linkedObjects.removeAllMatching([] (const auto& object) {
1841 return object->isIgnored();
1842 });
1843 return linkedObjects;
1844}
1845
1846void AXCoreObject::appendRadioButtonDescendants(AXCoreObject& parent, AccessibilityChildrenVector& linkedUIElements) const
1847{
1848 for (const auto& child : parent.unignoredChildren()) {
1849 if (child->isRadioButton())
1850 linkedUIElements.append(child);
1851 else
1852 appendRadioButtonDescendants(child.get(), linkedUIElements);
1853 }
1854}
1855
1856void AXCoreObject::appendRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const
1857{
1858 if (!isRadioButton())
1859 return;
1860
1861 if (isRadioInput()) {
1862 for (auto& radioSibling : radioButtonGroup())
1863 linkedUIElements.append(radioSibling);
1864 } else {
1865 // If we didn't find any radio button siblings with the traditional naming, lets search for a radio group role and find its children.
1866 for (RefPtr parent = parentObject(); parent; parent = parent->parentObject()) {
1867 if (parent->role() == AccessibilityRole::RadioGroup) {
1868 appendRadioButtonDescendants(*parent, linkedUIElements);
1869 break;
1870 }
1871 }
1872 }
1873}
1874
1875AXCoreObject* AXCoreObject::parentObjectUnignored() const
1876{
1877 if (role() == AccessibilityRole::Row) {
1878 if (auto* table = exposedTableAncestor())
1879 return table;
1880 }
1881
1882 return Accessibility::findAncestor<AXCoreObject>(*this, false, [&] (const AXCoreObject& object) {
1883 return !object.isIgnored();
1884 });
1885}
1886
1887AXCoreObject* AXCoreObject::detailsAncestor() const
1888{
1889 return Accessibility::findAncestor<AXCoreObject>(*this, false, [&] (const AXCoreObject& object) {
1890 return object.role() == AccessibilityRole::Details;
1891 });
1892}
1893
1894// This function implements a fast way to determine our ordering relative to |other|: find the
1895// ancestor we share, then compare the index-in-parent of the next lowest descendant of each us
1896// and |other|. Take this example:
1897/*
1898 A
1899 / \
1900 B C
1901 / / \
1902 D E F
1903 \
1904 G
1905*/
1906// (A has two children, B and C. B has child D. C has children E and F. F has child G.)
1907//
1908// Imagine we want to determine the ordering of D relative to G. They share ancestor A. D's descends from B
1909// who is index 0 in shared ancestor A. G descends from C, who is index 1 in the shared ancestor. This lets
1910// us know B (who has ancestor index 0) comes before G (who has ancestor index 1).
1911//
1912// This is significantly more performant than simply doing a pre-order traversal from |this| to |other|.
1913// On html.spec.whatwg.org, 1800 runs of this method took 4.4ms total, while 1800 runs of a pre-order
1914// traversal to determine ordering took 48.6 seconds total. This algorithm is also faster on more "average",
1915// smaller-accessibility-tree pages:
1916// - YouTube: 0.45ms vs 18.2ms in 237 comparisons
1917// - Wikipedia: 3.8ms vs. 18.7ms in 270 comparisons
1918std::partial_ordering AXCoreObject::partialOrder(const AXCoreObject& other)
1919{
1920 if (objectID() == other.objectID())
1921 return std::partial_ordering::equivalent;
1922
1923 RefPtr current = this;
1924 RefPtr otherCurrent = other;
1925
1926 auto orderingFromIndices = [&] (unsigned ourAncestorIndex, unsigned otherAncestorIndex) {
1927 if (ourAncestorIndex < otherAncestorIndex)
1928 return std::partial_ordering::less;
1929 if (ourAncestorIndex > otherAncestorIndex)
1930 return std::partial_ordering::greater;
1931
1932 // This can be hit on accessibility/dynamic-text-stitching.html with --accessibility-isolated-tree.
1933 AX_BROKEN_ASSERT_NOT_REACHED()do { if (isAccessibilityLogChannelEnabled()) { do { if (__builtin_expect
(!!(!(false)), 0)) clang diagnostic push clang diagnostic ignored
"-Wunknown-warning-option" clang diagnostic ignored "-Wunsafe-buffer-usage"
clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
clang diagnostic ignored "-Wunsafe-buffer-usage-in-format-attr-call"
[[clang::suppress]] __extension__({ os_log_t _log_tmp = (LogAccessibility
.osLogChannel); os_log_type_t _type_tmp = OS_LOG_TYPE_ERROR; if
(os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__({
clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("BROKEN ASSERTION FAILED in %s(%d) : %s\n"), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG4"
) = "BROKEN ASSERTION FAILED in %s(%d) : %s\n"; uint8_t _Alignas
(16) __attribute__((uninitialized)) _os_fmt_buf[__builtin_os_log_format_buffer_size
("BROKEN ASSERTION FAILED in %s(%d) : %s\n", "/Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/Source/WebCore/accessibility/AXCoreObject.cpp"
, 1933, __PRETTY_FUNCTION__)]; _os_log_error_impl(&__dso_handle
, _log_tmp, _type_tmp, _os_fmt_str, (uint8_t *)__builtin_os_log_format
(_os_fmt_buf, "BROKEN ASSERTION FAILED in %s(%d) : %s\n", "/Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/Source/WebCore/accessibility/AXCoreObject.cpp"
, 1933, __PRETTY_FUNCTION__), (uint32_t)sizeof(_os_fmt_buf)) clang
diagnostic pop ; }); } }) clang diagnostic pop ; } while (0)
; } } while (0)
;
1934 return std::partial_ordering::equivalent;
1935 };
1936
1937 // I got into an infinite loop here that I wasn't able to reproduce again in order to debug.
1938 // For now, add a failsafe and AX_ASSERT() so we can try to debug the root cause when it does
1939 // happen again.
1940 unsigned failsafeCounter = 0;
1941 // This variable is 2 times the max render tree depth because the accessibility tree can be
1942 // larger than the the render tree, e.g. when considering things like scroll views which don't
1943 // have renderers but are in the accessibility tree. This huge limit ensures that we don't
1944 // activate the failsafe loop exit unless something is 100% wrong.
1945 static constexpr unsigned maxIterations = Settings::defaultMaximumRenderTreeDepth * 2;
1946
1947 // ListHashSet chosen intentionally because it has O(1) lookup time. This is important
1948 // because we need to repeatedly query these lists, once every time we find a new ancestor.
1949 ListHashSet<Ref<AXCoreObject>> ourAncestors;
1950 ListHashSet<Ref<AXCoreObject>> otherAncestors;
1951 while (failsafeCounter < maxIterations && (current || otherCurrent)) {
1952 ++failsafeCounter;
1953
1954 if (RefPtr maybeParent = current ? current->parentObject() : nullptr) {
1955 AX_ASSERT(current != maybeParent)((void)0);
1956
1957 if (maybeParent == &other) {
1958 // We are a descendant of the other object, so we come after it.
1959 return std::partial_ordering::greater;
1960 }
1961
1962 Ref parent = maybeParent.releaseNonNull();
1963 if (auto iterator = otherAncestors.find(parent); iterator != otherAncestors.end()) {
1964 // If ourAncestors is empty (it has zero size), that means the shared ancestor is |current|'s
1965 // parent, and thus the index to use is |current| position in the shared parent's children.
1966 unsigned ourAncestorIndex = ourAncestors.size() ? ourAncestors.takeLast()->indexInParent() : current->indexInParent();
1967 --iterator;
1968 // Similarly, it's possible the shared ancestor was the direct parent of |otherCurrent| —
1969 // determine this by checking whether iterator == otherAncestors.end() after moving back one
1970 // element.
1971 unsigned otherAncestorIndex = iterator != otherAncestors.end() ? (*iterator)->indexInParent() : other.indexInParent();
1972
1973 return orderingFromIndices(ourAncestorIndex, otherAncestorIndex);
1974 }
1975 current = parent.ptr();
1976 AX_ASSERT(!ourAncestors.contains(parent))((void)0);
1977 ourAncestors.appendOrMoveToLast(WTF::move(parent));
1978 }
1979
1980 if (RefPtr maybeParent = otherCurrent ? otherCurrent->parentObject() : nullptr) {
1981 AX_ASSERT(otherCurrent != maybeParent)((void)0);
1982
1983 if (maybeParent == this) {
1984 // The other object is a descendant of ours, so we come before it in tree-order.
1985 return std::partial_ordering::less;
1986 }
1987
1988 Ref parent = maybeParent.releaseNonNull();
1989 if (auto iterator = ourAncestors.find(parent); iterator != ourAncestors.end()) {
1990 unsigned otherAncestorIndex = otherAncestors.size() ? otherAncestors.takeLast()->indexInParent() : otherCurrent->indexInParent();
1991 --iterator;
1992 unsigned ourAncestorIndex = iterator != ourAncestors.end() ? (*iterator)->indexInParent() : indexInParent();
1993
1994 return orderingFromIndices(ourAncestorIndex, otherAncestorIndex);
1995 }
1996 otherCurrent = parent.ptr();
1997 AX_ASSERT(!otherAncestors.contains(parent))((void)0);
1998 otherAncestors.appendOrMoveToLast(WTF::move(parent));
1999 }
2000 }
2001
2002 // Reproducible with ITM + ENABLE_ACCESSIBILITY_LOCAL_FRAME in accessibility/mac/ordered-textmarker-crash.html.
2003 AX_BROKEN_ASSERT(failsafeCounter < maxIterations)do { if (isAccessibilityLogChannelEnabled()) { do { if (__builtin_expect
(!!(!(failsafeCounter < maxIterations)), 0)) clang diagnostic
push clang diagnostic ignored "-Wunknown-warning-option" clang
diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (LogAccessibility
.osLogChannel); os_log_type_t _type_tmp = OS_LOG_TYPE_ERROR; if
(os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__({
clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("BROKEN ASSERTION FAILED in %s(%d) : %s\n"), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG5"
) = "BROKEN ASSERTION FAILED in %s(%d) : %s\n"; uint8_t _Alignas
(16) __attribute__((uninitialized)) _os_fmt_buf[__builtin_os_log_format_buffer_size
("BROKEN ASSERTION FAILED in %s(%d) : %s\n", "/Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/Source/WebCore/accessibility/AXCoreObject.cpp"
, 2003, __PRETTY_FUNCTION__)]; _os_log_error_impl(&__dso_handle
, _log_tmp, _type_tmp, _os_fmt_str, (uint8_t *)__builtin_os_log_format
(_os_fmt_buf, "BROKEN ASSERTION FAILED in %s(%d) : %s\n", "/Volumes/Data/worker/macOS-Safer-CPP-Checks-EWS/build/Source/WebCore/accessibility/AXCoreObject.cpp"
, 2003, __PRETTY_FUNCTION__), (uint32_t)sizeof(_os_fmt_buf)) clang
diagnostic pop ; }); } }) clang diagnostic pop ; } while (0)
; } } while (0)
;
2004 // If we pass the above ASSERT but hit this one, it means we didn't loop infinitely,
2005 // but also did not find a shared ancestor between the two objects, which shouldn't ever happen.
2006 AX_ASSERT_NOT_REACHED()((void)0);
2007 return std::partial_ordering::unordered;
2008}
2009
2010// LineDecorationStyle implementations.
2011
2012LineDecorationStyle::LineDecorationStyle(RenderObject& renderer)
2013{
2014 const CheckedRef style = renderer.style();
2015 auto decor = style->textDecorationLineInEffect();
2016 if (decor.containsAny({ Style::TextDecorationLine::Flag::Underline, Style::TextDecorationLine::Flag::LineThrough })) {
2017 auto decorationStyles = TextDecorationPainter::stylesForRenderer(renderer, decor);
2018 if (decor.hasUnderline()) {
2019 hasUnderline = true;
2020 underlineColor = decorationStyles.underline.color;
2021 }
2022
2023 if (decor.hasLineThrough()) {
2024 hasLinethrough = true;
2025 linethroughColor = decorationStyles.linethrough.color;
2026 }
2027 }
2028}
2029
2030String LineDecorationStyle::debugDescription() const
2031{
2032 return makeString(
2033 "{"_s,
2034 "hasUnderline: "_s, hasUnderline,
2035 ", underlineColor: "_s, underlineColor.debugDescription(),
2036 ", hasLinethrough: "_s, hasLinethrough,
2037 ", linethroughColor: "_s, linethroughColor.debugDescription(),
2038 "}"_s
2039 );
2040}
2041
2042String AXCoreObject::infoStringForTesting()
2043{
2044 return makeString("Role: "_s, rolePlatformString(), ", Value: "_s, stringValue());
2045}
2046
2047AXCoreObject::AccessibilityChildrenVector AXCoreObject::findMatchingObjectsWithin(AccessibilitySearchCriteria&& criteria)
2048{
2049 criteria.anchorObject = this;
2050 auto stream = AXSearchManager().findMatchingObjectsAsStream(WTF::move(criteria));
2051
2052 // Extract local objects from the stream. Remote frame entries are ignored since
2053 // callers of this function expect AccessibilityChildrenVector.
2054 AccessibilityChildrenVector results;
2055 for (const auto& entry : stream.entries()) {
2056 if (entry.isLocalResult() && entry.object())
2057 results.append(*entry.object());
Call argument for parameter 'u' is uncounted and unsafe
2058 }
2059 return results;
2060}
2061
2062namespace Accessibility {
2063
2064#if !PLATFORM(MAC)(defined 1 && 1) && !USE(ATSPI)(defined USE_ATSPI && USE_ATSPI)
2065// FIXME: implement in other platforms.
2066PlatformRoleMap createPlatformRoleMap() { return PlatformRoleMap(); }
2067#endif
2068
2069void initializeRoleMap()
2070{
2071 roleToPlatformString(AccessibilityRole::Button);
2072}
2073
2074String roleToPlatformString(AccessibilityRole role)
2075{
2076 // This map can be read from multiple threads at once. This is fine,
2077 // as we don't mutate it after creation, and never allow it to be destroyed.
2078 // The only thing we need to make thread-safe is its initialization, accomplished
2079 // by expliciting initializing the map before the accessibility thread is started.
2080 static NeverDestroyed<PlatformRoleMap> roleMap = createPlatformRoleMap();
2081 return roleMap->get(enumToUnderlyingType(role));
2082}
2083
2084bool inRenderTreeOrStyleUpdate(const Document& document)
2085{
2086 if (document.inStyleRecalc() || document.inRenderTreeUpdate())
2087 return true;
2088 auto* view = document.view();
2089 return view && view->layoutContext().isInRenderTreeLayout();
2090}
2091
2092Color defaultColor()
2093{
2094 static NeverDestroyed<Color> color = Color().toColorTypeLossy<SRGBA<uint8_t>>();
2095 return color.get();
2096}
2097
2098bool performCustomActionPress(AXTreeID treeID, AXID targetID)
2099{
2100 return retrieveValueFromMainThreadWithTimeoutAndDefault([treeID, targetID] () -> bool {
2101 if (WeakPtr<AXObjectCache> cache = AXTreeStore<AXObjectCache>::axObjectCacheForID(treeID)) {
2102 if (RefPtr object = cache->objectForID(targetID))
2103 return object->press();
2104 }
2105 return false;
2106 }, InteractiveTimeout, false);
2107}
2108
2109} // namespace Accessibility
2110
2111} // namespace WebCore