Bug Summary

File:Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/accessibility/AccessibilityRenderObject.cpp
Warning:line 2729, column 11
Local variable 'cachedImage' is uncounted and unsafe

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple arm64-apple-ios26.2.0-simulator -O3 -Wundef-prefix=TARGET_OS_ -Werror=undef-prefix -Wdeprecated-objc-isa-usage -Werror=deprecated-objc-isa-usage -Werror=implicit-function-declaration -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name UnifiedSource110.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=26.2 -fdepfile-entry=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/SDKSettings.json -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/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore -target-linker-version 1230.1 -fcoverage-compilation-dir=/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore -resource-dir /Users/buildbot/Library/Developer/Toolchains/swift-webkit.xctoolchain/usr/lib/clang/21 -dependency-file /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/scan-build-output/StaticAnalyzer/WebCore/WebCore/normal/arm64/UnifiedSource110.d -skip-unused-modulemap-deps -MT dependencies -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk -iquote /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/WebCore-generated-files.hmap -iquote /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/WebCore-project-headers.hmap -isystem /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/PAL/ThirdParty/dav1d/include -isystem /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/SDKAdditions/usr/include -iframework /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/SDKAdditions/System/Library/Frameworks -iframework /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitLibraries/SDKs/iphonesimulator26.0-additions.sdk/System/Library/PrivateFrameworks -iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/Frameworks -include-pch /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/PrecompiledHeaders/SharedPrecompiledHeaders/11721353671419897710/WebCorePrefix.h.gch -D _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE -D NDEBUG -D BUILDING_WEBKIT -D GL_SILENCE_DEPRECATION=1 -D GLES_SILENCE_DEPRECATION=1 -D __clang_analyzer__ -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/WebCore-own-target-headers.hmap -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/WebCore-all-target-headers.hmap -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/include -I PAL -I ForwardingHeaders -I /usr/include/libxslt -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/include/libxml2 -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/DerivedSources/WebCore -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/WebKitAdditions -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/WebKitAdditions -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitLibraries/SDKs/iphonesimulator26.0-additions.sdk/usr/local/include -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/webrtc -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/webrtc -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/webm -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/webm -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/PAL.build/PAL.build/DerivedSources -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/PAL.build/Release-iphonesimulator/PAL.build/DerivedSources -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/DerivedSources-normal/arm64 -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/DerivedSources/arm64 -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/DerivedSources -F/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator -F/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator -I/usr/local/include -stdlib=libc++ -internal-isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/include/c++/v1 -internal-isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include -internal-isystem /Users/buildbot/Library/Developer/Toolchains/swift-webkit.xctoolchain/usr/lib/clang/21/include -internal-externc-isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/include -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/Frameworks -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/SubFrameworks -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.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 -Wno-backend-plugin -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/Apple-iOS-26-Safer-CPP-Checks-EWS/build/scan-build-output/StaticAnalyzer/WebCore/WebCore/normal/arm64/UnifiedSource110.plist -x c++ /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/DerivedSources/WebCore/unified-sources/UnifiedSource110.cpp
1/*
2* Copyright (C) 2008-2020 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 "AccessibilityRenderObject.h"
31
32#include "AXImageMapHelpers.h"
33#include "AXListHelpers.h"
34#include "AXLogger.h"
35#include "AXLoggerBase.h"
36#include "AXNotifications.h"
37#include "AXObjectCacheInlines.h"
38#include "AXUtilities.h"
39#include "AccessibilityMediaHelpers.h"
40#include "AccessibilityNodeObjectInlines.h"
41#include "AccessibilityObjectInlines.h"
42#include "AccessibilitySVGObject.h"
43#include "AccessibilitySpinButton.h"
44#include "CachedImage.h"
45#include "Chrome.h"
46#include "ChromeClient.h"
47#include "ComplexTextController.h"
48#include "ComposedTreeIterator.h"
49#include "ContainerNodeInlines.h"
50#include "DocumentSVG.h"
51#include "DocumentView.h"
52#include "Editing.h"
53#include "Editor.h"
54#include "EditorClient.h"
55#include "ElementAncestorIteratorInlines.h"
56#include "EventTargetInlines.h"
57#include "FloatRect.h"
58#include "FocusOptions.h"
59#include "FontCascade.h"
60#include "FrameLoader.h"
61#include "FrameSelection.h"
62#include "HTMLAreaElement.h"
63#include "HTMLAttachmentElement.h"
64#include "HTMLBRElement.h"
65#include "HTMLDetailsElement.h"
66#include "HTMLFormElement.h"
67#include "HTMLFrameElementBase.h"
68#include "HTMLImageElement.h"
69#include "HTMLInputElement.h"
70#include "HTMLLabelElement.h"
71#include "HTMLMapElement.h"
72#include "HTMLMediaElement.h"
73#include "HTMLMeterElement.h"
74#include "HTMLNames.h"
75#include "HTMLOptionElement.h"
76#include "HTMLOptionsCollection.h"
77#include "HTMLSelectElement.h"
78#include "HTMLSummaryElement.h"
79#include "HTMLTableElement.h"
80#include "HTMLTextAreaElement.h"
81#include "HTMLVideoElement.h"
82#include "HitTestRequest.h"
83#include "HitTestResult.h"
84#include "Image.h"
85#include "InlineIteratorBoxInlines.h"
86#include "InlineIteratorLogicalOrderTraversal.h"
87#include "InlineIteratorTextBoxInlines.h"
88#include "LegacyLineLayout.h"
89#include "LegacyRenderSVGRoot.h"
90#include "LegacyRenderSVGShape.h"
91#include "LineSelection.h"
92#include "LocalFrame.h"
93#include "LocalizedStrings.h"
94#include "Logging.h"
95#include "NodeList.h"
96#include "Page.h"
97#include "PathUtilities.h"
98#include "PluginViewBase.h"
99#include "PositionInlines.h"
100#include "ProgressTracker.h"
101#include "Range.h"
102#include "RenderBlockFlowInlines.h"
103#include "RenderButton.h"
104#include "RenderElementInlines.h"
105#include "RenderFileUploadControl.h"
106#include "RenderHTMLCanvas.h"
107#include "RenderImage.h"
108#include "RenderInline.h"
109#include "RenderIterator.h"
110#include "RenderLayer.h"
111#include "RenderLayerScrollableArea.h"
112#include "RenderLineBreak.h"
113#include "RenderListBox.h"
114#include "RenderListItem.h"
115#include "RenderListMarker.h"
116#include "RenderMathMLBlock.h"
117#include "RenderObjectInlines.h"
118#include "RenderSVGInlineText.h"
119#include "RenderSVGRoot.h"
120#include "RenderSVGShape.h"
121#include "RenderTableCell.h"
122#include "RenderText.h"
123#include "RenderTextControl.h"
124#include "RenderTextControlSingleLine.h"
125#include "RenderTextFragment.h"
126#include "RenderTheme.h"
127#include "RenderView.h"
128#include "RenderWidget.h"
129#include "RenderedPosition.h"
130#include "SVGDocument.h"
131#include "SVGElementTypeHelpers.h"
132#include "SVGImage.h"
133#include "SVGSVGElement.h"
134#include "ShadowRootMode.h"
135#include "StylePrimitiveNumericTypes+Evaluation.h"
136#include "Text.h"
137#include "TextControlInnerElements.h"
138#include "TextIterator.h"
139#include "TextRecognitionOptions.h"
140#include "TypedElementDescendantIteratorInlines.h"
141#include "VisibleUnits.h"
142#include "WidthIterator.h"
143#include <algorithm>
144#include <ranges>
145#include <wtf/NeverDestroyed.h>
146#include <wtf/Scope.h>
147#include <wtf/StdLibExtras.h>
148#include <wtf/text/MakeString.h>
149#include <wtf/unicode/CharacterNames.h>
150
151#if ENABLE(APPLE_PAY)(defined 1 && 1)
152#include "ApplePayButtonPart.h"
153#endif
154
155namespace WebCore {
156
157using namespace HTMLNames;
158
159static Node* NODELETE[[clang::annotate_type("webkit.nodelete")]] nodeForRenderer(RenderObject& renderer)
160{
161 if (!renderer.isRenderView()) [[likely]]
162 return renderer.node();
163 return &renderer.document();
164}
165
166AccessibilityRenderObject::AccessibilityRenderObject(AXID axID, RenderObject& renderer, AXObjectCache& cache)
167 : AccessibilityNodeObject(axID, nodeForRenderer(renderer), cache)
168 , m_renderer(renderer)
169{
170}
171
172AccessibilityRenderObject::AccessibilityRenderObject(AXID axID, Node& node, AXObjectCache& cache)
173 : AccessibilityNodeObject(axID, &node, cache)
174{
175 // We should only ever create an instance of this class with a node if that node has no renderer (i.e. because of display:contents).
176 AX_ASSERT(!node.renderer())((void)0);
177}
178
179AccessibilityRenderObject::~AccessibilityRenderObject()
180{
181 AX_ASSERT(isDetached())((void)0);
182}
183
184Ref<AccessibilityRenderObject> AccessibilityRenderObject::create(AXID axID, RenderObject& renderer, AXObjectCache& cache)
185{
186 return adoptRef(*new AccessibilityRenderObject(axID, renderer, cache));
187}
188
189Ref<AccessibilityRenderObject> AccessibilityRenderObject::create(AXID axID, Node& node, AXObjectCache& cache)
190{
191 return adoptRef(*new AccessibilityRenderObject(axID, node, cache));
192}
193
194void AccessibilityRenderObject::detachRemoteParts(AccessibilityDetachmentType detachmentType)
195{
196 AccessibilityNodeObject::detachRemoteParts(detachmentType);
197
198 detachRemoteSVGRoot();
199 m_renderer = nullptr;
200}
201
202static inline bool isInlineWithContinuation(RenderObject& renderer)
203{
204 auto* renderInline = dynamicDowncast<RenderInline>(renderer);
205 return renderInline && renderInline->continuation();
206}
207
208static inline RenderObject* firstChildInContinuation(RenderBoxModelObject& renderer)
209{
210 WeakPtr continuation = renderer.continuation();
211 while (continuation) {
212 if (is<RenderBlock>(*continuation))
213 return continuation.get();
214
215 if (auto* child = continuation->firstChild())
216 return child;
217
218 continuation = continuation->continuation();
219 }
220
221 return nullptr;
222}
223
224static inline CheckedPtr<RenderObject> firstChildConsideringContinuation(RenderObject& renderer)
225{
226 CheckedPtr firstChild = renderer.firstChildSlow();
227
228 // We don't want to include the end of a continuation as the firstChild of the
229 // anonymous parent, because everything has already been linked up via continuation.
230 // CSS first-letter selector is an example of this case.
231 if (renderer.isAnonymous()) {
232 auto* renderInline = dynamicDowncast<RenderInline>(firstChild.get());
233 if (renderInline && renderInline->isContinuation())
234 firstChild = nullptr;
235 }
236
237 if (!firstChild && isInlineWithContinuation(renderer))
238 firstChild = firstChildInContinuation(uncheckedDowncast<RenderInline>(renderer));
239
240 return firstChild;
241}
242
243
244static inline RenderObject* lastChildConsideringContinuation(RenderObject& renderer)
245{
246 if (!isAnyOf<RenderInline, RenderBlock>(renderer))
247 return &renderer;
248
249 auto& boxModelObject = uncheckedDowncast<RenderBoxModelObject>(renderer);
250 WeakPtr lastChild = boxModelObject.lastChild();
251 for (CheckedPtr current = &boxModelObject; current; ) {
252 if (CheckedPtr newLastChild = current->lastChild())
253 lastChild = newLastChild.get();
254
255 current = current->inlineContinuation();
256 }
257
258 return lastChild.get();
259}
260
261AccessibilityObject* AccessibilityRenderObject::firstChild() const
262{
263 if (!m_renderer)
264 return AccessibilityNodeObject::firstChild();
265
266 if (CheckedPtr firstChild = firstChildConsideringContinuation(*m_renderer)) {
267 CheckedPtr cache = axObjectCache();
268 return cache ? cache->getOrCreate(*firstChild) : nullptr;
269 }
270
271 // If an object can't have children, then it is using this method to help
272 // calculate some internal property (like its description).
273 // In this case, it should check the Node level for children in case they're
274 // not rendered (like a <meter> element).
275 if (!canHaveChildren())
276 return AccessibilityNodeObject::firstChild();
277
278 return nullptr;
279}
280
281AccessibilityObject* AccessibilityRenderObject::lastChild() const
282{
283 if (!m_renderer)
284 return AccessibilityNodeObject::lastChild();
285
286 if (CheckedPtr lastChild = lastChildConsideringContinuation(*m_renderer)) {
287 CheckedPtr cache = axObjectCache();
288 return cache ? cache->getOrCreate(lastChild.unsafeGet()) : nullptr;
289 }
290
291 if (!canHaveChildren())
292 return AccessibilityNodeObject::lastChild();
293
294 return nullptr;
295}
296
297static inline RenderInline* startOfContinuations(RenderObject& renderer)
298{
299 WeakPtr renderElement = dynamicDowncast<RenderElement>(renderer);
300 if (!renderElement)
301 return nullptr;
302
303 if (is<RenderInline>(*renderElement) && renderElement->isContinuation() && is<RenderInline>(renderElement->element()->renderer()))
304 return uncheckedDowncast<RenderInline>(renderer.node()->renderer());
305
306 // Blocks with a previous continuation always have a next continuation
307 if (CheckedPtr renderBlock = dynamicDowncast<RenderBlock>(*renderElement); renderBlock && renderBlock->inlineContinuation())
308 return uncheckedDowncast<RenderInline>(renderBlock->inlineContinuation()->element()->renderer());
309 return nullptr;
310}
311
312static inline CheckedPtr<RenderObject> endOfContinuations(RenderObject& renderer)
313{
314 if (!isAnyOf<RenderInline, RenderBlock>(renderer))
315 return &renderer;
316
317 CheckedPtr previous = uncheckedDowncast<RenderBoxModelObject>(&renderer);
318 for (CheckedPtr current = previous; current; ) {
319 previous = current;
320 current = current->inlineContinuation();
321 }
322
323 return previous;
324}
325
326
327static inline CheckedPtr<RenderObject> childBeforeConsideringContinuations(RenderInline* renderer, RenderObject* child)
328{
329 CheckedPtr<RenderObject> previous;
330 for (CheckedPtr currentContainer = CheckedPtr<RenderBoxModelObject>(renderer); currentContainer; ) {
331 if (is<RenderInline>(*currentContainer)) {
332 CheckedPtr current = currentContainer->firstChild();
333 while (current) {
334 if (current == child)
335 return previous;
336 previous = current;
337 current = current->nextSibling();
338 }
339
340 currentContainer = currentContainer->continuation();
341 } else if (is<RenderBlock>(*currentContainer)) {
342 if (currentContainer == child)
343 return previous;
344
345 previous = currentContainer;
346 currentContainer = currentContainer->inlineContinuation();
347 }
348 }
349
350 AX_ASSERT_NOT_REACHED()((void)0);
351 return nullptr;
352}
353
354static inline bool firstChildIsInlineContinuation(RenderElement& renderer)
355{
356 CheckedPtr renderInline = dynamicDowncast<RenderInline>(renderer.firstChild());
357 return renderInline && renderInline->isContinuation();
358}
359
360AccessibilityObject* AccessibilityRenderObject::previousSibling() const
361{
362 if (!m_renderer)
363 return AccessibilityNodeObject::previousSibling();
364
365 CheckedPtr<RenderObject> previousSibling;
366
367 // Case 1: The node is a block and is an inline's continuation. In that case, the inline's
368 // last child is our previous sibling (or further back in the continuation chain)
369 CheckedPtr<RenderInline> startOfConts;
370 WeakPtr renderBlock = dynamicDowncast<RenderBlock>(*m_renderer);
371 if (renderBlock && (startOfConts = startOfContinuations(*renderBlock)))
372 previousSibling = childBeforeConsideringContinuations(startOfConts.get(), renderBlock.get());
373 else if (renderBlock && renderBlock->isAnonymousBlock() && firstChildIsInlineContinuation(*renderBlock)) {
374 // Case 2: Anonymous block parent of the end of a continuation - skip all the way to before
375 // the parent of the start, since everything in between will be linked up via the continuation.
376 CheckedPtr firstParent = startOfContinuations(*renderBlock->firstChild())->parent();
377 AX_ASSERT(firstParent)((void)0);
378 while (firstChildIsInlineContinuation(*firstParent))
379 firstParent = startOfContinuations(*firstParent->firstChild())->parent();
380 previousSibling = firstParent->previousSibling();
381 } else if (RenderObject* ps = m_renderer->previousSibling()) {
382 // Case 3: The node has an actual previous sibling
383 previousSibling = ps;
384 } else if (is<RenderInline>(m_renderer->parent()) && (startOfConts = startOfContinuations(*m_renderer->parent()))) {
385 // Case 4: This node has no previous siblings, but its parent is an inline,
386 // and is another node's inline continutation. Follow the continuation chain.
387 previousSibling = childBeforeConsideringContinuations(startOfConts.get(), m_renderer->parent()->firstChild());
388 }
389
390 if (!previousSibling)
391 return nullptr;
392
393 CheckedPtr cache = axObjectCache();
394 return cache ? cache->getOrCreate(*previousSibling) : nullptr;
395}
396
397static inline bool lastChildHasContinuation(RenderElement& renderer)
398{
399 CheckedPtr child = renderer.lastChild();
400 return child && isInlineWithContinuation(*child);
401}
402
403AccessibilityObject* AccessibilityRenderObject::nextSibling() const
404{
405 if (!m_renderer)
406 return AccessibilityNodeObject::nextSibling();
407
408 if (is<RenderView>(*m_renderer))
409 return nullptr;
410
411 CheckedPtr<RenderObject> nextSibling;
412
413 // Case 1: node is a block and has an inline continuation. Next sibling is the inline continuation's
414 // first child.
415 CheckedPtr<RenderInline> inlineContinuation;
416 WeakPtr renderBlock = dynamicDowncast<RenderBlock>(*m_renderer);
417 if (renderBlock && (inlineContinuation = renderBlock->inlineContinuation()))
418 nextSibling = firstChildConsideringContinuation(*inlineContinuation);
419 else if (renderBlock && renderBlock->isAnonymousBlock() && lastChildHasContinuation(*renderBlock)) {
420 // Case 2: Anonymous block parent of the start of a continuation - skip all the way to
421 // after the parent of the end, since everything in between will be linked up via the continuation.
422 CheckedPtr lastParent = endOfContinuations(*renderBlock->lastChild())->parent();
423 AX_ASSERT(lastParent)((void)0);
424 while (lastChildHasContinuation(*lastParent))
425 lastParent = endOfContinuations(*lastParent->lastChild())->parent();
426 nextSibling = lastParent->nextSibling();
427 } else if (RenderObject* ns = m_renderer->nextSibling())
428 nextSibling = ns;
429 else if (isInlineWithContinuation(*m_renderer)) {
430 // Case 4: node is an inline with a continuation. Next sibling is the next sibling of the end
431 // of the continuation chain.
432 nextSibling = endOfContinuations(*m_renderer)->nextSibling();
433 }
434
435 // Case 5: node has no next sibling, and its parent is an inline with a continuation.
436 // Case 5.1: After case 4, (the element was inline w/ continuation but had no sibling), then check it's parent.
437 if (!nextSibling && m_renderer->parent() && isInlineWithContinuation(*m_renderer->parent())) {
438 CheckedRef continuation = *downcast<RenderInline>(*m_renderer->parent()).continuation();
439
440 // Case 5a: continuation is a block - in this case the block itself is the next sibling.
441 if (is<RenderBlock>(continuation))
442 nextSibling = continuation.ptr();
443 else {
444 // Case 5b: continuation is an inline - in this case the inline's first child is the next sibling
445 nextSibling = firstChildConsideringContinuation(continuation);
446 }
447 }
448
449 if (!nextSibling)
450 return nullptr;
451
452 CheckedPtr cache = axObjectCache();
453 if (!cache)
454 return nullptr;
455
456 // After case 4, there are chances that nextSibling has the same node as the current renderer,
457 // which might lead to looping over the same object repeatedly.
458 if (nextSibling->node() && nextSibling->node() == m_renderer->node()) {
459 if (RefPtr nextObject = cache->getOrCreate(*nextSibling)) {
460 if (nextObject.get() == this) {
461 // WebKit accessibility objects use DOM nodes as the "primary key" (i.e. in m_nodeIdMapping).
462 // This can cause a bit of trouble for continuations, which result in multiple renderers being associated
463 // with the same node. That can cause us to get into this branch — if nextSibling or us is a continuation,
464 // we will be different renderers with the same node, and thus `nextObject` will be us.
465 //
466 // Fallback to walking the DOM in this case to avoid looping infinitely.
467 return AccessibilityNodeObject::nextSibling();
468 }
469 return nextObject->nextSibling();
470 }
471 }
472
473 RefPtr nextObject = cache->getOrCreate(*nextSibling);
474 RefPtr nextAXRenderObject = dynamicDowncast<AccessibilityRenderObject>(nextObject);
475 RefPtr nextRenderParent = nextAXRenderObject ? cache->getOrCreate(nextAXRenderObject->renderParentObject()) : nullptr;
476
477 // Make sure the next sibling has the same render parent.
478 return !nextRenderParent || nextRenderParent == cache->getOrCreate(renderParentObject()) ? nextObject.unsafeGet() : nullptr;
479}
480
481static RenderBoxModelObject* nextContinuation(RenderObject& renderer)
482{
483 if (!renderer.isBlockLevelReplacedOrAtomicInline()) {
484 if (auto* renderInline = dynamicDowncast<RenderInline>(renderer))
485 return renderInline->continuation();
486 }
487
488 auto* renderBlock = dynamicDowncast<RenderBlock>(renderer);
489 return renderBlock ? renderBlock->inlineContinuation() : nullptr;
490}
491
492RenderObject* AccessibilityRenderObject::renderParentObject() const
493{
494 if (!m_renderer)
495 return nullptr;
496
497 CheckedPtr parent = m_renderer->parent();
498
499 // Case 1: node is a block and is an inline's continuation. Parent
500 // is the start of the continuation chain.
501 CheckedPtr<RenderInline> startOfConts;
502 CheckedPtr<RenderObject> firstChild;
503 if (is<RenderBlock>(*m_renderer) && (startOfConts = startOfContinuations(*m_renderer)))
504 parent = startOfConts;
505
506 // Case 2: node's parent is an inline which is some node's continuation; parent is
507 // the earliest node in the continuation chain.
508 else if (is<RenderInline>(parent) && (startOfConts = startOfContinuations(*parent)))
509 parent = startOfConts;
510
511 // Case 3: The first sibling is the beginning of a continuation chain. Find the origin of that continuation.
512 else if (parent && (firstChild = parent->firstChild()) && firstChild->node()) {
513 // Get the node's renderer and follow that continuation chain until the first child is found
514 CheckedPtr nodeRenderFirstChild = firstChild->node()->renderer();
515 while (nodeRenderFirstChild != firstChild) {
516 for (CheckedPtr contsTest = nodeRenderFirstChild; contsTest; contsTest = nextContinuation(*contsTest)) {
517 if (contsTest == firstChild) {
518 parent = nodeRenderFirstChild->parent();
519 break;
520 }
521 }
522 CheckedPtr parentFirstChild = parent->firstChild();
523 if (firstChild == parentFirstChild)
524 break;
525 firstChild = parentFirstChild;
526 if (!firstChild->node())
527 break;
528 nodeRenderFirstChild = firstChild->node()->renderer();
529 }
530 }
531
532 return parent.unsafeGet();
533}
534
535AccessibilityObject* AccessibilityRenderObject::parentObject() const
536{
537 if (auto* ownerParent = ownerParentObject()) [[unlikely]]
538 return ownerParent;
539
540#if USE(ATSPI)(defined USE_ATSPI && USE_ATSPI)
541 // FIXME: Consider removing this ATSPI-only branch with https://bugs.webkit.org/show_bug.cgi?id=282117.
542 if (auto* displayContentsParent = this->displayContentsParent())
543 return displayContentsParent;
544#endif // USE(ATSPI)
545
546 if (!m_renderer)
547 return AccessibilityNodeObject::parentObject();
548
549 WeakPtr cache = axObjectCache();
550 if (!cache)
551 return nullptr;
552
553#if !USE(ATSPI)(defined USE_ATSPI && USE_ATSPI)
554 // FIXME: This compiler directive can be removed after https://bugs.webkit.org/show_bug.cgi?id=282117 is fixed.
555 RefPtr node = this->node();
556 if (RefPtr parentNode = composedParentIgnoringDocumentFragments(node.get())) {
557 if (RefPtr parent = cache->getOrCreate(*parentNode))
558 return parent.unsafeGet();
559 }
560
561 if (CheckedPtr renderElement = dynamicDowncast<RenderElement>(m_renderer.get()); renderElement && renderElement->isBeforeOrAfterContent()) {
562 // In AccessibilityRenderObject::addChildren(), we make the generating element add ::before
563 // and ::after as children, so we must match that here by making sure ::before and ::after regard the
564 // generating element as their parent rather than their "natural" render tree parent. This avoids
565 // a parent-child mismatch which can cause issues for ATs.
566 if (RefPtr parent = cache->getOrCreate(renderElement->generatingElement()))
567 return parent.unsafeGet();
568 }
569#endif // !USE(ATSPI)
570
571 // Expose markers that are not direct children of a list item too.
572 if (m_renderer->isRenderListMarker()) {
573 for (CheckedRef listItemAncestor : ancestorsOfType<RenderListItem>(*m_renderer)) {
574 RefPtr parent = dynamicDowncast<AccessibilityRenderObject>(axObjectCache()->getOrCreate(listItemAncestor));
575 if (parent && parent->markerRenderer() == m_renderer)
576 return parent.unsafeGet();
577 }
578 }
579
580 if (CheckedPtr parentObject = renderParentObject())
581 return cache->getOrCreate(*parentObject);
582
583 // WebArea's parent should be the scroll view containing it.
584 if (isWebArea())
585 return cache->getOrCreate(m_renderer->view().frameView());
586
587 return nullptr;
588}
589
590#if ENABLE_ACCESSIBILITY_LOCAL_FRAME1
591
592AccessibilityObject* AccessibilityRenderObject::crossFrameParentObject() const
593{
594 return nullptr;
595}
596
597AccessibilityObject* AccessibilityRenderObject::crossFrameChildObject() const
598{
599 return nullptr;
600}
601
602#endif // ENABLE_ACCESSIBILITY_LOCAL_FRAME
603
604bool AccessibilityRenderObject::isAttachment() const
605{
606 RefPtr widget = this->widget();
607 // WebKit2 plugins need to be treated differently than attachments, so return false here.
608 // Only WebKit1 plugins have an associated platformWidget.
609 if (is<PluginViewBase>(widget) && !widget->platformWidget())
610 return false;
611
612 CheckedPtr renderer = this->renderer();
613 // Widgets are the replaced elements that we represent to AX as attachments
614 return renderer && renderer->isRenderWidget();
615}
616
617#if ENABLE(ATTACHMENT_ELEMENT)(defined 1 && 1)
618bool AccessibilityRenderObject::isAttachmentElement() const
619{
620 return is<HTMLAttachmentElement>(node());
621}
622#endif
623
624bool AccessibilityRenderObject::isOffScreen() const
625{
626 if (!m_renderer)
627 return true;
628
629 IntRect contentRect = snappedIntRect(m_renderer->absoluteClippedOverflowRectForSpatialNavigation());
630 // FIXME: unclear if we need LegacyIOSDocumentVisibleRect.
631 IntRect viewRect = m_renderer->view().frameView().visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
632 viewRect.intersect(contentRect);
633 return viewRect.isEmpty();
634}
635
636Element* AccessibilityRenderObject::anchorElement() const
637{
638 if (!m_renderer)
639 return AccessibilityNodeObject::anchorElement();
640
641 WeakPtr cache = axObjectCache();
642 if (!cache)
643 return nullptr;
644
645 CheckedPtr<RenderObject> currentRenderer;
646
647 // Search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though.
648 for (currentRenderer = renderer(); currentRenderer && !currentRenderer->node(); currentRenderer = currentRenderer->parent()) {
649 if (CheckedPtr blockRenderer = dynamicDowncast<RenderBlock>(*currentRenderer); blockRenderer && blockRenderer->isAnonymousBlock()) {
650 if (CheckedPtr continuation = blockRenderer->continuation())
651 return cache->getOrCreate(*continuation)->anchorElement();
652 }
653 }
654
655 // Bail if none found.
656 if (!currentRenderer)
657 return nullptr;
658
659 // Search up the DOM tree for an anchor element.
660 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement.
661 for (RefPtr node = currentRenderer->node(); node; node = node->parentNode()) {
662 if (auto* anchor = dynamicDowncast<HTMLAnchorElement>(node.get()))
663 return anchor;
664
665 RefPtr object = cache ? cache->getOrCreate(*node) : nullptr;
666 if (object && object->isLink())
667 return dynamicDowncast<Element>(*node);
668 }
669
670 return nullptr;
671}
672
673String AccessibilityRenderObject::textUnderElement(TextUnderElementMode mode) const
674{
675 // If we are within a hidden context, we don't want to add any text for this object, instead
676 // just fanning out to nodes within our subtree to search for un-hidden nodes.
677 // AccessibilityNodeObject::textUnderElement takes care of this, so call it directly.
678 if (!m_renderer || mode.isHidden())
679 return AccessibilityNodeObject::textUnderElement(WTF::move(mode));
680
681 if (CheckedPtr fileUpload = dynamicDowncast<RenderFileUploadControl>(*m_renderer))
682 return fileUpload->buttonValue();
683
684 if (mode.includeListMarkers == IncludeListMarkerText::Yes) {
685 if (CheckedPtr listMarker = dynamicDowncast<RenderListMarker>(*m_renderer))
686 return listMarker->textWithSuffix();
687 }
688
689 // Reflect when a content author has explicitly marked a line break.
690 if (m_renderer->isBR())
691 return "\n"_s;
692
693 if (shouldGetTextFromNode(mode))
694 return AccessibilityNodeObject::textUnderElement(WTF::move(mode));
695
696 // We use a text iterator for text objects AND for those cases where we are
697 // explicitly asking for the full text under a given element.
698 WeakPtr renderText = dynamicDowncast<RenderText>(*m_renderer);
699 bool includeAllChildren = false;
700#if USE(ATSPI)(defined USE_ATSPI && USE_ATSPI)
701 // Only ATSPI ever sets IncludeAllChildren.
702 includeAllChildren = mode.childrenInclusion == TextUnderElementMode::Children::IncludeAllChildren;
703#endif
704 if (renderText || includeAllChildren) {
705 // If possible, use a text iterator to get the text, so that whitespace
706 // is handled consistently.
707 RefPtr<Document> nodeDocument;
708 std::optional<SimpleRange> textRange;
709 if (RefPtr node = m_renderer->node()) {
710 nodeDocument = node->document();
711 textRange = makeRangeSelectingNodeContents(*node);
712 }
713#if USE(ATSPI)(defined USE_ATSPI && USE_ATSPI)
714 // FIXME: Consider removing this ATSPI-only branch with https://bugs.webkit.org/show_bug.cgi?id=282117.
715 else {
716 // For anonymous blocks, we work around not having a direct node to create a range from
717 // defining one based in the two external positions defining the boundaries of the subtree.
718 RenderObject* firstChildRenderer = m_renderer->firstChildSlow();
719 RenderObject* lastChildRenderer = m_renderer->lastChildSlow();
720 if (firstChildRenderer && firstChildRenderer->node() && lastChildRenderer && lastChildRenderer->node()) {
721 // We define the start and end positions for the range as the ones right before and after
722 // the first and the last nodes in the DOM tree that is wrapped inside the anonymous block.
723 auto& firstNodeInBlock = *firstChildRenderer->node();
724 auto& lastNodeInBlock = *lastChildRenderer->node();
725 nodeDocument = firstNodeInBlock.document();
726 textRange = makeSimpleRange(
727 positionInParentBeforeNode(firstNodeInBlock),
728 positionInParentAfterNode(lastNodeInBlock)
729 );
730 }
731 }
732#endif // USE(ATSPI)
733
734 if (nodeDocument && textRange) {
735 if (RefPtr frame = nodeDocument->frame()) {
736 // catch stale WebCoreAXObject (see <rdar://problem/3960196>)
737 if (frame->document() != nodeDocument.get())
738 return { };
739
740 return plainText(*textRange, textIteratorBehaviorForTextRange());
741 }
742 }
743
744 // Sometimes text fragments don't have Nodes associated with them (like when
745 // CSS content is used to insert text or when a RenderCounter is used.)
746 if (WeakPtr renderTextFragment = dynamicDowncast<RenderTextFragment>(renderText.get())) {
747 // The alt attribute may be set on a text fragment through CSS, which should be honored.
748 if (auto& altText = renderTextFragment->altText(); !altText.isNull())
749 return altText;
750 return renderTextFragment->contentString();
751 }
752 if (renderText)
753 return renderText->text();
754 }
755
756 return AccessibilityNodeObject::textUnderElement(WTF::move(mode));
757}
758
759bool AccessibilityRenderObject::shouldGetTextFromNode(const TextUnderElementMode& mode) const
760{
761 if (!m_renderer)
762 return true;
763
764#if USE(ATSPI)(defined USE_ATSPI && USE_ATSPI)
765 // AccessibilityRenderObject::textUnderElement() gets the text of anonymous blocks by using
766 // the child nodes to define positions. CSS tables and their anonymous descendants lack
767 // children with nodes.
768 if (m_renderer->isAnonymous() && m_renderer->isTablePart())
769 return mode.childrenInclusion == TextUnderElementMode::Children::IncludeAllChildren;
770#else
771 UNUSED_PARAM(mode)(void)mode;
772#endif // USE(ATSPI)
773
774 // AccessibilityRenderObject::textUnderElement() calls rangeOfContents() to create the text
775 // range. rangeOfContents() does not include CSS-generated content.
776 if (CheckedPtr renderElement = dynamicDowncast<RenderElement>(m_renderer.get()); renderElement && renderElement->isBeforeOrAfterContent())
777 return true;
778 if (RefPtr node = m_renderer->node()) {
779 RefPtr firstChild = node->pseudoAwareFirstChild();
780 RefPtr lastChild = node->pseudoAwareLastChild();
781 if ((firstChild && firstChild->isPseudoElement()) || (lastChild && lastChild->isPseudoElement()))
782 return true;
783 }
784
785 return false;
786}
787
788String AccessibilityRenderObject::stringValue() const
789{
790#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
791 if (RefPtr element = mediaElement())
792 return localizedMediaTimeDescription(element->currentTime());
793#endif
794
795 if (isNativeLabel())
796 return isLabelContainingOnlyStaticText() ? textUnderElement() : AccessibilityNodeObject::stringValue();
797
798 if (!m_renderer)
799 return AccessibilityNodeObject::stringValue();
800
801 bool isStaticText = this->isStaticText();
802 if (isStaticText && node()) {
803 // FIXME: Implement text stitching for node-less static text, like CSS generated content.
804 // This is relevant here because only AccessibilityNodeObject::stringValue() knows how to
805 // stitch text. Ideally, we would shrink this implementation into AccessibilityNodeObject,
806 // since that is intended to become the "node and or renderer having" accessibility object class.
807 return AccessibilityNodeObject::stringValue();
808 }
809
810 if (isStaticText || isTextControl() || isSecureField()) {
811 // A combobox is considered a text control, and its value is handled in AXNodeObject.
812 if (isComboBox())
813 return AccessibilityNodeObject::stringValue();
814 return text();
815 }
816
817 // For menu list select elements, get the selected option's aria-label or label.
818 if (RefPtr selectElement = dynamicDowncast<HTMLSelectElement>(node()); selectElement && selectElement->usesMenuList()) {
819 int selectedIndex = selectElement->selectedIndex();
820 const auto& listItems = selectElement->listItems();
821 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
822 if (RefPtr selectedItem = listItems[selectedIndex].get()) {
823 auto overriddenDescription = selectedItem->attributeTrimmedWithDefaultARIA(aria_labelAttr);
824 if (!overriddenDescription.isEmpty())
825 return overriddenDescription;
826 }
827 }
828 if (RefPtr option = selectElement->item(selectedIndex))
829 return option->label();
830 return String();
831 }
832
833#if PLATFORM(COCOA)(defined 1 && 1)
834 if (is<RenderListItem>(m_renderer.get()))
835 return textUnderElement();
836#endif
837
838 if (CheckedPtr renderListMarker = dynamicDowncast<RenderListMarker>(m_renderer.get())) {
839#if USE(ATSPI)(defined USE_ATSPI && USE_ATSPI)
840 return renderListMarker->textWithSuffix();
841#else
842 return renderListMarker->textWithoutSuffix();
843#endif
844 }
845
846 if (isWebArea())
847 return { };
848
849 if (isDateTime()) {
850 if (RefPtr input = dynamicDowncast<HTMLInputElement>(node()))
851 return input->visibleValue();
852
853 // As fallback, gather the static text under this.
854 String value;
855 Accessibility::enumerateUnignoredDescendants(*const_cast<AccessibilityRenderObject*>(this), false, [&value] (const auto& object) {
856 if (object.isStaticText())
857 value = makeString(value, object.stringValue());
858 });
859 return value;
860 }
861
862 if (CheckedPtr renderFileUploadControl = dynamicDowncast<RenderFileUploadControl>(m_renderer.get()))
863 return renderFileUploadControl->fileTextValue();
864
865 // FIXME: We might need to implement a value here for more types
866 // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one;
867 // this would require subclassing or making accessibilityAttributeNames do something other than return a
868 // single static array.
869 return { };
870}
871
872bool AccessibilityRenderObject::canHavePlainText() const
873{
874 return isARIAStaticText() || is<RenderText>(*m_renderer) || isTextControl();
875}
876
877// The boundingBox for elements within the remote SVG element needs to be offset by its position
878// within the parent page, otherwise they are in relative coordinates only.
879void AccessibilityRenderObject::offsetBoundingBoxForRemoteSVGElement(LayoutRect& rect) const
880{
881 for (RefPtr<AccessibilityObject> parent = const_cast<AccessibilityRenderObject*>(this); parent; parent = parent->parentObject()) {
882 if (parent->isAccessibilitySVGRoot()) {
883 rect.moveBy(parent->parentObject()->boundingBoxRect().location());
884 break;
885 }
886 }
887}
888
889LayoutRect AccessibilityRenderObject::boundingBoxRect() const
890{
891 CheckedPtr renderer = this->renderer();
892 if (!renderer)
893 return AccessibilityNodeObject::boundingBoxRect();
894
895 RefPtr node = renderer->node();
896 if (node) // If we are a continuation, we want to make sure to use the primary renderer.
897 renderer = node->renderer();
898
899 // absoluteFocusRingQuads will query the hierarchy below this element, which for large webpages can be very slow.
900 // For a web area, which will have the most elements of any element, absoluteQuads should be used.
901 // We should also use absoluteQuads for SVG elements, otherwise transforms won't be applied.
902 Vector<FloatQuad> quads;
903 bool isSVGRoot = false;
904
905 if (renderer->isRenderOrLegacyRenderSVGRoot())
906 isSVGRoot = true;
907
908 if (CheckedPtr renderText = dynamicDowncast<RenderText>(*renderer)) {
909 std::optional stitchGroup = stitchGroupIfRepresentative();
910 if (!stitchGroup)
911 quads = renderText->absoluteQuadsClippedToEllipsis();
912 else {
913 // |this| is a stitching of multiple objects, so we need to combine all of their bounding boxes.
914
915 CheckedPtr cache = axObjectCache();
916 RefPtr endNode = cache ? lastNode(stitchGroup->members(), *cache) : nullptr;
917 if (endNode) {
918 if (std::optional range = makeSimpleRange(positionBeforeNode(*node), positionAfterNode(*endNode))) {
919 quads = RenderObject::absoluteTextQuads(*range);
920
921 for (AXID axID : stitchGroup->members()) {
922 if (axID == objectID())
923 break;
924 if (RefPtr object = cache->objectForID(axID)) {
925 if (CheckedPtr renderListMarker = dynamicDowncast<RenderListMarker>(object->renderer()))
926 renderListMarker->absoluteFocusRingQuads(quads);
927 }
928 }
929 }
930 }
931
932 if (quads.isEmpty())
933 quads = renderText->absoluteQuadsClippedToEllipsis();
934 }
935 }
936 else if (isWebArea() || isSVGRoot)
937 renderer->absoluteQuads(quads);
938 else
939 renderer->absoluteFocusRingQuads(quads);
940
941 LayoutRect result = boundingBoxForQuads(renderer.get(), quads);
942
943 RefPtr document = this->document();
944 if (document && document->isSVGDocument())
945 offsetBoundingBoxForRemoteSVGElement(result);
946
947 // The size of the web area should be the content size, not the clipped size.
948 if (isWebArea())
949 result.setSize(renderer->view().frameView().contentsSize());
950
951 if (result.isEmpty())
952 return nonEmptyAncestorBoundingBox();
953 return result;
954}
955
956bool AccessibilityRenderObject::isNonLayerSVGObject() const
957{
958 auto* renderer = this->renderer();
959 return renderer ? is<RenderSVGInlineText>(renderer) || is<LegacyRenderSVGModelObject>(renderer) : false;
960}
961
962bool AccessibilityRenderObject::supportsPath() const
963{
964 return is<RenderText>(renderer()) || (renderer() && renderer()->isRenderOrLegacyRenderSVGShape());
965}
966
967Path AccessibilityRenderObject::elementPath() const
968{
969 if (!m_renderer)
970 return AccessibilityNodeObject::elementPath();
971
972 if (CheckedPtr renderText = dynamicDowncast<RenderText>(*m_renderer)) {
973 Vector<LayoutRect> rects;
974 renderText->boundingRects(rects, flooredLayoutPoint(renderText->localToAbsolute()));
975 // If only 1 rect, don't compute path since the bounding rect will be good enough.
976 if (rects.size() < 2)
977 return { };
978
979 // Compute the path only if this is the last part of a line followed by the beginning of the next line.
980 CheckedRef style = renderText->style();
981 bool rightToLeftText = style->writingMode().isBidiRTL();
982 static const auto xTolerance = 5_lu;
983 static const auto yTolerance = 5_lu;
984 bool needsPath = false;
985 auto unionRect = rects[0];
986 for (size_t i = 1; i < rects.size(); ++i) {
987 needsPath = absoluteValue(rects[i].y() - unionRect.maxY()) < yTolerance // This rect is in a new line.
988 && (rightToLeftText ? rects[i].x() - unionRect.x() > xTolerance
989 : unionRect.x() - rects[i].x() > xTolerance); // And this rect is to right/left of all previous rects.
990
991 if (needsPath)
992 break;
993
994 unionRect.unite(rects[i]);
995 }
996 if (!needsPath)
997 return { };
998
999 auto outlineOffset = Style::evaluate<float>(style->usedOutlineOffset(), Style::ZoomNeeded { });
1000 float deviceScaleFactor = renderText->document().deviceScaleFactor();
1001 Vector<FloatRect> pixelSnappedRects;
1002 for (auto rect : rects) {
1003 rect.inflate(outlineOffset);
1004 pixelSnappedRects.append(snapRectToDevicePixels(rect, deviceScaleFactor));
1005 }
1006
1007 return PathUtilities::pathWithShrinkWrappedRects(pixelSnappedRects, 0);
1008 }
1009
1010 if (CheckedPtr renderSVGShape = dynamicDowncast<LegacyRenderSVGShape>(*m_renderer); renderSVGShape && renderSVGShape->hasPath()) {
1011 Path path = renderSVGShape->path();
1012
1013 CheckedPtr cache = axObjectCache();
1014 if (!cache)
1015 return path;
1016
1017 // The SVG path is in terms of the parent's bounding box. The path needs to be offset to frame coordinates.
1018 // FIXME: This seems wrong for SVG inside HTML.
1019 if (CheckedPtr svgRoot = ancestorsOfType<LegacyRenderSVGRoot>(*m_renderer).first()) {
1020 LayoutPoint parentOffset = cache->getOrCreate(&*svgRoot)->elementRect().location();
1021 path.transform(AffineTransform().translate(parentOffset.x(), parentOffset.y()));
1022 }
1023 return path;
1024 }
1025
1026 if (CheckedPtr renderSVGShape = dynamicDowncast<RenderSVGShape>(*m_renderer); renderSVGShape && renderSVGShape->hasPath()) {
1027 Path path = renderSVGShape->path();
1028
1029 CheckedPtr cache = axObjectCache();
1030 if (!cache)
1031 return path;
1032
1033 // The SVG path is in terms of the parent's bounding box. The path needs to be offset to frame coordinates.
1034 if (CheckedPtr svgRoot = ancestorsOfType<RenderSVGRoot>(*m_renderer).first()) {
1035 LayoutPoint parentOffset = cache->getOrCreate(&*svgRoot)->elementRect().location();
1036 path.transform(AffineTransform().translate(parentOffset.x(), parentOffset.y()));
1037 }
1038 return path;
1039 }
1040
1041 return { };
1042}
1043
1044#if ENABLE(APPLE_PAY)(defined 1 && 1)
1045String AccessibilityRenderObject::applePayButtonDescription() const
1046{
1047 switch (applePayButtonType()) {
1048 case ApplePayButtonType::Plain:
1049 return AXApplePayPlainLabel();
1050 case ApplePayButtonType::Buy:
1051 return AXApplePayBuyLabel();
1052 case ApplePayButtonType::SetUp:
1053 return AXApplePaySetupLabel();
1054 case ApplePayButtonType::Donate:
1055 return AXApplePayDonateLabel();
1056 case ApplePayButtonType::CheckOut:
1057 return AXApplePayCheckOutLabel();
1058 case ApplePayButtonType::Book:
1059 return AXApplePayBookLabel();
1060 case ApplePayButtonType::Subscribe:
1061 return AXApplePaySubscribeLabel();
1062#if ENABLE(APPLE_PAY_NEW_BUTTON_TYPES)(defined 1 && 1)
1063 case ApplePayButtonType::Reload:
1064 return AXApplePayReloadLabel();
1065 case ApplePayButtonType::AddMoney:
1066 return AXApplePayAddMoneyLabel();
1067 case ApplePayButtonType::TopUp:
1068 return AXApplePayTopUpLabel();
1069 case ApplePayButtonType::Order:
1070 return AXApplePayOrderLabel();
1071 case ApplePayButtonType::Rent:
1072 return AXApplePayRentLabel();
1073 case ApplePayButtonType::Support:
1074 return AXApplePaySupportLabel();
1075 case ApplePayButtonType::Contribute:
1076 return AXApplePayContributeLabel();
1077 case ApplePayButtonType::Tip:
1078 return AXApplePayTipLabel();
1079#endif
1080 }
1081}
1082#endif
1083
1084void AccessibilityRenderObject::labelText(Vector<AccessibilityText>& textOrder) const
1085{
1086#if ENABLE(APPLE_PAY)(defined 1 && 1)
1087 if (isApplePayButton()) {
1088 textOrder.append(AccessibilityText(applePayButtonDescription(), AccessibilityTextSource::Alternative));
1089 return;
1090 }
1091#endif
1092 AccessibilityNodeObject::labelText(textOrder);
1093}
1094
1095AccessibilityObject* AccessibilityRenderObject::titleUIElement() const
1096{
1097 if (m_renderer && isFieldset())
1098 return axObjectCache()->getOrCreate(dynamicDowncast<RenderBlock>(*m_renderer)->findFieldsetLegend(RenderBlock::FieldsetIncludeFloatingOrOutOfFlow));
1099
1100 if (is<RenderTableCell>(m_renderer.get())) {
1101 // Try to find if the first cell in this row is a <th>. If it is,
1102 // then it can act as the title ui element. (This is only in the
1103 // case when the table is not appearing as an AXTable.)
1104 if (isExposedTableCell())
1105 return nullptr;
1106
1107 // Table cells that are th cannot have title ui elements, since by definition
1108 // they are title ui elements
1109 if (WebCore::elementName(protect(node()).get()) == ElementName::HTML_th)
1110 return nullptr;
1111
1112 CheckedRef renderCell = downcast<RenderTableCell>(*m_renderer);
1113
1114 // If this cell is in the first column, there is no need to continue.
1115 int col = renderCell->col();
1116 if (!col)
1117 return nullptr;
1118
1119 CheckedPtr section = renderCell->section();
1120 if (!section)
1121 return nullptr;
1122
1123 int row = renderCell->rowIndex();
1124 CheckedPtr headerCell = section->primaryCellAt(row, 0);
1125 if (!headerCell || headerCell.get() == renderCell.ptr())
1126 return nullptr;
1127
1128 RefPtr element = headerCell->element();
1129 if (!element || element->elementName() != ElementName::HTML_th)
1130 return nullptr;
1131
1132 CheckedPtr cache = axObjectCache();
1133 return cache ? cache->getOrCreate(*headerCell) : nullptr;
1134 }
1135
1136 return downcast<AccessibilityObject>(AccessibilityNodeObject::titleUIElement());
1137}
1138
1139bool AccessibilityRenderObject::isAllowedChildOfTree() const
1140{
1141 // Determine if this is in a tree. If so, we apply special behavior to make it work like an AXOutline.
1142 RefPtr axObj = parentObject();
1143 bool isInTree = false;
1144 bool isTreeItemDescendant = false;
1145 while (axObj) {
1146 if (axObj->role() == AccessibilityRole::TreeItem)
1147 isTreeItemDescendant = true;
1148 if (axObj->isTree()) {
1149 isInTree = true;
1150 break;
1151 }
1152 axObj = axObj->parentObject();
1153 }
1154
1155 // If the object is in a tree, only tree items should be exposed (and the children of tree items).
1156 if (isInTree) {
1157 auto role = this->role();
1158 if (role != AccessibilityRole::TreeItem && role != AccessibilityRole::StaticText && !isTreeItemDescendant)
1159 return false;
1160 }
1161 return true;
1162}
1163
1164AccessibilityObject* AccessibilityRenderObject::containingTree() const
1165{
1166 return Accessibility::findAncestor<AccessibilityObject>(*this, false, [] (const auto& ancestor) {
1167 return ancestor.isTree();
1168 });
1169}
1170
1171static AccessibilityObjectInclusion objectInclusionFromAltText(const String& altText)
1172{
1173 // Don't ignore an image that has alt text.
1174 if (!altText.containsOnly<isASCIIWhitespace>())
1175 return AccessibilityObjectInclusion::IncludeObject;
1176
1177 // The informal standard is to ignore images with zero-length alt strings:
1178 // https://www.w3.org/WAI/tutorials/images/decorative/.
1179 if (!altText.isNull())
1180 return AccessibilityObjectInclusion::IgnoreObject;
1181
1182 return AccessibilityObjectInclusion::DefaultBehavior;
1183}
1184
1185static bool webAreaIsPresentational(RenderObject* renderer)
1186{
1187 if (!renderer || !is<RenderView>(*renderer))
1188 return false;
1189
1190 RefPtr ownerElement = renderer->document().ownerElement();
1191 return ownerElement && hasPresentationRole(*ownerElement);
1192}
1193
1194bool AccessibilityRenderObject::computeIsIgnored() const
1195{
1196#ifndef NDEBUG1
1197 AX_ASSERT(m_initialized)((void)0);
1198#endif
1199
1200 if (!m_renderer)
1201 return AccessibilityNodeObject::computeIsIgnored();
1202
1203 if (isTree())
1204 return isIgnoredByDefault();
1205
1206 // Check first if any of the common reasons cause this element to be ignored.
1207 // Then process other use cases that need to be applied to all the various roles
1208 // that AccessibilityRenderObjects take on.
1209 AccessibilityObjectInclusion decision = defaultObjectInclusion();
1210 if (decision == AccessibilityObjectInclusion::IncludeObject)
1211 return false;
1212 if (decision == AccessibilityObjectInclusion::IgnoreObject)
1213 return true;
1214
1215 if (role() == AccessibilityRole::Ignored)
1216 return true;
1217
1218 // Needs to happen before the presentational role check, since we want to expose table cells if they are in an exposable table (even if within a presentational role).
1219 if (isTableCell()) {
1220 // Ignore anonymous table cells as long as they're not in a table (ie. when display:table is used).
1221 RefPtr parentTable = this->parentTable();
1222 RefPtr parentElement = parentTable ? parentTable->element() : nullptr;
1223 bool inTable = parentElement && (parentElement->elementName() == ElementName::HTML_table || hasTableRole(*parentElement));
1224 if (!inTable && !element())
1225 return true;
1226
1227 if (isExposedTableCell())
1228 return false;
1229 }
1230
1231 if (ignoredFromPresentationalRole())
1232 return true;
1233
1234 // WebAreas should be ignored if their iframe container is marked as presentational.
1235 if (webAreaIsPresentational(renderer()))
1236 return true;
1237
1238 // An ARIA tree can only have tree items and static text as children.
1239 if (!isAllowedChildOfTree())
1240 return true;
1241
1242 if (isAccessibilityList())
1243 return false;
1244
1245 // Allow the platform to decide if the attachment is ignored or not.
1246 if (isAttachment())
1247 return accessibilityIgnoreAttachment();
1248
1249#if ENABLE(ATTACHMENT_ELEMENT)(defined 1 && 1)
1250 if (isAttachmentElement())
1251 return false;
1252#endif
1253
1254#if PLATFORM(COCOA)(defined 1 && 1)
1255 // If this widget has an underlying AX object, don't ignore it.
1256 if (widget() && widget()->accessibilityObject())
1257 return false;
1258#endif
1259
1260 if (isExposableTable())
1261 return false;
1262
1263 // Ignore popup menu items because AppKit does.
1264 if (RefPtr node = this->node()) {
1265 for (Ref ancestor : ancestorsOfType<HTMLSelectElement>(*node)) {
1266 if (ancestor->usesMenuList())
1267 return true;
1268 }
1269 }
1270
1271 // https://webkit.org/b/161276 Getting the controlObject might cause the m_renderer to be nullptr.
1272 if (!m_renderer)
1273 return true;
1274
1275 if (m_renderer->isBR())
1276 return true;
1277
1278 if (WeakPtr renderText = dynamicDowncast<RenderText>(m_renderer.get())) {
1279 // Text elements with no rendered text, or only whitespace should not be part of the AX tree.
1280 if (!renderText->hasRenderedText()) {
1281 // Layout must be clean to make the right decision here (because hasRenderedText() can return false solely because layout is dirty).
1282 // FIXME: This is triggered when running accessibility/aria-liveregions-attributes.html with --accessibility-isolated-tree.
1283 AX_BROKEN_ASSERT(!renderText->needsLayout() || !renderText->text().length())do { if (isAccessibilityLogChannelEnabled()) { do { if (__builtin_expect
(!!(!(!renderText->needsLayout() || !renderText->text()
.length())), 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_LOG3"
) = "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/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/accessibility/AccessibilityRenderObject.cpp"
, 1283, __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/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/accessibility/AccessibilityRenderObject.cpp"
, 1283, __PRETTY_FUNCTION__), (uint32_t)sizeof(_os_fmt_buf)) clang
diagnostic pop ; }); } }) clang diagnostic pop ; } while (0)
; } } while (0)
;
1284 // If this is a RenderTextFragment with an associated first-letter, the entire text may have been
1285 // consumed by the first-letter pseudo-element. In this case, don't ignore the text node, as its
1286 // text content can still be retrieved from the associated DOM Text node.
1287 if (auto* renderTextFragment = dynamicDowncast<RenderTextFragment>(renderText.get())) {
1288 if (renderTextFragment->firstLetter())
1289 return false;
1290 }
1291 return true;
1292 }
1293
1294 if (renderText->text().containsOnly<isASCIIWhitespace>())
1295 return true;
1296
1297 // The alt attribute may be set on a text fragment through CSS, which should be honored.
1298 if (CheckedPtr renderTextFragment = dynamicDowncast<RenderTextFragment>(renderText.get())) {
1299 auto altTextInclusion = objectInclusionFromAltText(renderTextFragment->altText());
1300 if (altTextInclusion == AccessibilityObjectInclusion::IgnoreObject)
1301 return true;
1302 if (altTextInclusion == AccessibilityObjectInclusion::IncludeObject)
1303 return false;
1304 }
1305
1306 bool checkForIgnored = true;
1307 for (RefPtr ancestor = parentObject(); ancestor; ancestor = ancestor->parentObject()) {
1308 // Static text beneath TextControls is reported along with the text control text so it's ignored.
1309 // FIXME: Why does this not check for the other text-control roles (e.g. textarea)?
1310 if (ancestor->role() == AccessibilityRole::TextField)
1311 return true;
1312
1313 if (checkForIgnored && !ancestor->isIgnored()) {
1314 checkForIgnored = false;
1315 // Static text beneath MenuItems are just reported along with the menu item, so it's ignored on an individual level.
1316 if (ancestor->isMenuItem())
1317 return true;
1318 }
1319 }
1320
1321 // If iterating the ancestry has caused m_renderer to be destroyed, ignore `this`.
1322 return !renderText;
1323 }
1324
1325 if (isHeading())
1326 return false;
1327
1328 if (isLink())
1329 return false;
1330
1331 if (isLandmark())
1332 return false;
1333
1334 // all controls are accessible
1335 if (isControl())
1336 return false;
1337
1338 if (isFigureElement() || isSummary())
1339 return false;
1340
1341 switch (role()) {
1342 case AccessibilityRole::Audio:
1343 case AccessibilityRole::DescriptionListTerm:
1344 case AccessibilityRole::DescriptionListDetail:
1345 case AccessibilityRole::Details:
1346 case AccessibilityRole::DocumentArticle:
1347 case AccessibilityRole::LandmarkRegion:
1348 case AccessibilityRole::ListItem:
1349 case AccessibilityRole::SectionFooter:
1350 case AccessibilityRole::SectionHeader:
1351 case AccessibilityRole::Time:
1352 case AccessibilityRole::Video:
1353 return false;
1354 default:
1355 break;
1356 }
1357
1358 if (isImage()) {
1359 // If the image can take focus, it should not be ignored, lest the user not be able to interact with something important.
1360 if (canSetFocusAttribute())
1361 return false;
1362
1363 // https://github.com/w3c/aria/pull/2224
1364 // If the image is a descendant of a <figure>, don't ignore it due to <figure> potentially participating in its accname.
1365 RefPtr node = m_renderer->node();
1366 for (RefPtr ancestor = node ? node->parentNode() : nullptr; ancestor; ancestor = ancestor->parentNode()) {
1367 if (RefPtr element = dynamicDowncast<HTMLElement>(ancestor); element && element->hasTagName(figureTag))
1368 return false;
1369 }
1370
1371 // First check the RenderImage's altText (which can be set through a style sheet, or come from the Element).
1372 // However, if this is not a native image, fallback to the attribute on the Element.
1373 AccessibilityObjectInclusion altTextInclusion = AccessibilityObjectInclusion::DefaultBehavior;
1374 WeakPtr image = dynamicDowncast<RenderImage>(*m_renderer);
1375 if (image)
1376 altTextInclusion = objectInclusionFromAltText(image->altText());
1377 else
1378 altTextInclusion = objectInclusionFromAltText(altTextFromAttributeOrStyle());
1379
1380 if (altTextInclusion == AccessibilityObjectInclusion::IncludeObject)
1381 return false;
1382
1383 // Per HTML-AAM, aria-label and valid aria-labelledby (referencing elements with
1384 // non-empty text) take precedence over alt="", so check for valid ARIA accname
1385 // before ignoring the image due to empty alt text.
1386 // Note: we intentionally only check ARIA attributes here and not the title attribute,
1387 // because per HTML-AAM, title does not override alt="" for images.
1388 if (RefPtr imageElement = dynamicDowncast<Element>(node.get()); imageElement && hasARIAAccNameAttribute(*imageElement))
1389 return false;
1390
1391 if (altTextInclusion == AccessibilityObjectInclusion::IgnoreObject)
1392 return true;
1393
1394 // If an image has an accname (including from title), it should not be ignored (according to WAI-ARIA).
1395 if (hasAccNameAttribute())
1396 return false;
1397
1398 if (image) {
1399 // check for one-dimensional image
1400 if (image->height() <= 1 || image->width() <= 1)
1401 return true;
1402
1403 // check whether rendered image was stretched from one-dimensional file image
1404 if (image->cachedImage()) {
1405 LayoutSize imageSize = image->cachedImage()->imageSizeForRenderer(image.get(), image->view().zoomFactor());
1406 return imageSize.height() <= 1 || imageSize.width() <= 1;
1407 }
1408 }
1409 return false;
1410 }
1411
1412 // Objects that are between table rows and cells should be ignored, otherwise the table hierarchy will be
1413 // incorrect, preventing the table content from being accessible to ATs.
1414 if (!ancestorFlagsAreInitialized() || isInRow()) {
1415 if (!isTableCell() && canHaveChildren() && ignoredByRowAncestor())
1416 return true;
1417 }
1418
1419 if (ariaRoleAttribute() != AccessibilityRole::Unknown)
1420 return false;
1421
1422 if (role() == AccessibilityRole::HorizontalRule)
1423 return false;
1424
1425 // don't ignore labels, because they serve as TitleUIElements
1426 RefPtr node = m_renderer->node();
1427 if (is<HTMLLabelElement>(node))
1428 return false;
1429
1430 // Anything that is content editable should not be ignored.
1431 // However, one cannot just call node->hasEditableStyle() since that will ask if its parents
1432 // are also editable. Only the top level content editable region should be exposed.
1433 if (hasContentEditableAttributeSet())
1434 return false;
1435
1436 // if this element has aria attributes on it, it should not be ignored.
1437 if (supportsARIAAttributes())
1438 return false;
1439
1440#if ENABLE(MATHML)(defined 1 && 1)
1441 // First check if this is a special case within the math tree that needs to be ignored.
1442 if (isIgnoredElementWithinMathTree())
1443 return true;
1444 // Otherwise all other math elements are in the tree.
1445 if (isMathElement())
1446 return false;
1447#endif
1448
1449 // This logic, originally added in:
1450 // https://github.com/WebKit/WebKit/commit/ddeb923489b58fd890527bf0e432ebe6a477d2ef
1451 // Results in a lot of useless generics being exposed, which is wasteful. We should remove this.
1452 WeakPtr blockFlow = dynamicDowncast<RenderBlockFlow>(*m_renderer);
1453 if (blockFlow && m_renderer->childrenInline() && !canSetFocusAttribute() && !blockFlow->hasBlocksInInlineLayout()) {
1454 // FIXME: Do we really need to check for SVG content here?
1455 auto hasInlineOrSVGContent = blockFlow->hasContentfulInlineLine() || (blockFlow->svgTextLayout() && blockFlow->svgTextLayout()->lineCount());
1456 return !hasInlineOrSVGContent && !clickableSelfOrAncestor();
1457 }
1458
1459 if (isCanvas()) {
1460 if (hasElementDescendant()) {
1461 // Don't ignore canvases with potential fallback content, indicated by
1462 // having at least one element descendant. If it has no children or its
1463 // only children are not elements (e.g. just text nodes), it doesn't have
1464 // fallback content.
1465 return false;
1466 }
1467
1468 if (WeakPtr canvasBox = dynamicDowncast<RenderBox>(*m_renderer)) {
1469 if (canvasBox->height() <= 1 || canvasBox->width() <= 1)
1470 return true;
1471 }
1472 // Otherwise fall through; use presence of help text, title, or description to decide.
1473 }
1474
1475 if (m_renderer->isRenderListMarker()) {
1476 RefPtr parent = parentObjectUnignored();
1477 return parent && !parent->isListItem();
1478 }
1479
1480 if (isWebArea())
1481 return false;
1482
1483 // The render tree of meter includes a RenderBlock (meter) and a RenderMeter (div).
1484 // We expose the latter and thus should ignore the former. However, if the author
1485 // includes a title attribute on the element, hasAttributesRequiredForInclusion()
1486 // will return true, potentially resulting in a redundant accessible object.
1487 if (is<HTMLMeterElement>(node))
1488 return true;
1489
1490 // Using the presence of an accessible name to decide an element's visibility is not
1491 // as definitive as previous checks, so this should remain as one of the last.
1492 if (hasAttributesRequiredForInclusion())
1493 return false;
1494
1495 // Don't ignore generic focusable elements like <div tabindex=0>
1496 // unless they're completely empty, with no children.
1497 if (isGenericFocusableElement() && node->firstChild()) {
1498 RefPtr shadowRoot = node->containingShadowRoot();
1499 if (shadowRoot && shadowRoot->mode() == ShadowRootMode::UserAgent && is<HTMLInputElement>(shadowRoot->host())) {
1500 // We do still want to ignore the user-agent generic div rendered within text inputs.
1501 return true;
1502 }
1503 return false;
1504 }
1505
1506 // <span> tags are inline tags and not meant to convey information if they have no other aria
1507 // information on them. If we don't ignore them, they may emit signals expected to come from
1508 // their parent. In addition, because included spans are AccessibilityRole::Group objects, and AccessibilityRole::Group
1509 // objects are often containers with meaningful information, the inclusion of a span can have
1510 // the side effect of causing the immediate parent accessible to be ignored. This is especially
1511 // problematic for platforms which have distinct roles for textual block elements.
1512 auto elementName = WebCore::elementName(node.get());
1513 if (elementName == ElementName::HTML_span)
1514 return true;
1515
1516 // Other non-ignored host language elements
1517 if (elementName == ElementName::HTML_dfn)
1518 return false;
1519
1520 if (isStyleFormatGroup())
1521 return false;
1522
1523 switch (downcast<RenderElement>(*m_renderer).style().display().value) {
1524 case Style::DisplayType::InlineRuby:
1525 case Style::DisplayType::BlockRuby:
1526 case Style::DisplayType::RubyText:
1527 case Style::DisplayType::RubyBase:
1528 return false;
1529 default:
1530 break;
1531 }
1532
1533 // Find out if this element is inside of a label element.
1534 // If so, it may be ignored because it's the label for a checkbox or radio button.
1535 RefPtr controlObject = controlForLabelElement();
1536 if (controlObject && controlObject->isCheckboxOrRadio() && !controlObject->titleUIElement())
1537 return true;
1538
1539 if (isExposedTableRow())
1540 return isRenderHidden() || ignoredFromPresentationalRole();
1541
1542 // By default, objects should be ignored so that the AX hierarchy is not
1543 // filled with unnecessary items.
1544 return true;
1545}
1546
1547int AccessibilityRenderObject::layoutCount() const
1548{
1549 CheckedPtr view = dynamicDowncast<RenderView>(m_renderer.get());
1550 return view ? view->frameView().layoutUpdateCount() : 0;
1551}
1552
1553CharacterRange AccessibilityRenderObject::documentBasedSelectedTextRange() const
1554{
1555 auto selectedVisiblePositionRange = this->selectedVisiblePositionRange();
1556 if (selectedVisiblePositionRange.isNull())
1557 return { };
1558
1559 int start = indexForVisiblePosition(selectedVisiblePositionRange.start);
1560 if (start < 0)
1561 start = 0;
1562 int end = indexForVisiblePosition(selectedVisiblePositionRange.end);
1563 if (end < 0)
1564 end = 0;
1565 return { static_cast<unsigned>(start), static_cast<unsigned>(end - start) };
1566}
1567
1568String AccessibilityRenderObject::selectedText() const
1569{
1570 AX_ASSERT(isTextControl())((void)0);
1571
1572 if (isSecureField())
1573 return String(); // need to return something distinct from empty string
1574
1575 if (isNativeTextControl() && m_renderer) {
1576 Ref textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement();
1577 return textControl->selectedText();
1578 }
1579
1580 return doAXStringForRange(documentBasedSelectedTextRange());
1581}
1582
1583CharacterRange AccessibilityRenderObject::selectedTextRange() const
1584{
1585 AX_ASSERT(isTextControl())((void)0);
1586
1587 // Use the text control native range if it's a native object.
1588 if (isNativeTextControl() && m_renderer) {
1589 Ref textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement();
1590 return { textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart() };
1591 }
1592
1593 return documentBasedSelectedTextRange();
1594}
1595
1596#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)(defined ENABLE_ACCESSIBILITY_ISOLATED_TREE && ENABLE_ACCESSIBILITY_ISOLATED_TREE
)
1597AXTextRuns AccessibilityRenderObject::textRuns()
1598{
1599 constexpr std::array<uint16_t, 2> lengthOneDomOffsets = { 0, 1 };
1600 CheckedPtr renderer = this->renderer();
1601 if (CheckedPtr renderLineBreak = dynamicDowncast<RenderLineBreak>(renderer.get())) {
1602 auto box = InlineIterator::boxFor(*renderLineBreak);
1603
1604 return AXTextRuns(
1605 renderLineBreak->containingBlock(),
1606 { AXTextRun(box ? box->lineIndex() : 0, /* startIndex */ 0, /* endIndex */ 1, { lengthOneDomOffsets }, { 0 }, 0, 0) },
1607 makeString('\n').isolatedCopy()
1608 );
1609 }
1610
1611 if (is<SVGGraphicsElement>(element())) {
1612 // Match TextIterator (and other browsers) and don't emit text positions for SVG graphics elements.
1613 return { };
1614 }
1615
1616 if (isReplacedElement()) {
1617 CheckedPtr containingBlock = renderer ? renderer->containingBlock() : nullptr;
1618 FloatRect rect = localRect();
1619 uint16_t width = static_cast<uint16_t>(rect.width());
1620 uint16_t height = static_cast<uint16_t>(rect.height());
1621 if (!containingBlock)
1622 return { };
1623
1624 return AXTextRuns(
1625 containingBlock.get(),
1626 { AXTextRun(0, /* startIndex */ 0, /* endIndex */ 1, { lengthOneDomOffsets }, { width }, height, 0) },
1627 String(span(objectReplacementCharacter))
1628 );
1629 }
1630
1631 WeakPtr renderText = dynamicDowncast<RenderText>(renderer.get());
1632 if (!renderText)
1633 return { };
1634
1635 // FIXME: Need to handle PseudoElementType::FirstLetter. Right now, it will be chopped off from the other
1636 // other text in the line, and AccessibilityRenderObject::computeIsIgnored ignores the
1637 // first-letter RenderText, meaning we can't recover it later by combining text across AX objects.
1638
1639 Vector<AXTextRun> runs;
1640 // The string of all runs concatenated together.
1641 StringBuilder fullString;
1642 // The string representing a single run, which is eventually rolled up into |fullString|.
1643 StringBuilder lineString;
1644 Vector<uint16_t> characterWidths;
1645 float distanceFromBoundsInDirection = 0;
1646 // Used to round an accumulated floating point value into an uint16, which is how we store character widths.
1647 float accumulatedDistanceFromStart = 0.0;
1648 float lineHeight = 0.0;
1649
1650 bool isHorizontal = fontOrientation() == FontOrientation::Horizontal;
1651 // Appends text to the current lineString, collapsing whitespace as necessary (similar to how TextIterator::handleTextRun() does).
1652 auto appendToLineString = [&] (const InlineIterator::TextBoxIterator& textBox) {
1653 auto text = textBox->originalText();
1654 if (text.isEmpty())
1655 return;
1656
1657 CheckedRef textBoxStyle = textBox->style();
1658 if (textBoxStyle->textTransform().contains(Style::TextTransformValue::FullSizeKana)) {
1659 // We don't want to serve transformed kana text to AT since it is a visual affordance.
1660 // Using the original text from the renderer provides the untransformed string.
1661 text = textBox->renderer().originalText().substring(textBox->start(), textBox->length());
1662 }
1663
1664 bool collapseTabs = textBoxStyle->collapseWhiteSpace();
1665 bool collapseNewlines = !textBoxStyle->preserveNewline();
1666
1667 auto textRun = textBox->textRun(InlineIterator::TextRunMode::Editing);
1668 auto lineBox = textBox->lineBox();
1669 if (!lineBox)
1670 return;
1671 lineHeight = LineSelection::logicalRect(*lineBox).height();
1672
1673 CheckedPtr renderStyle = style();
1674 if (renderStyle && renderStyle->textAlign() != Style::TextAlign::Left) {
1675 // To serve the appropriate bounds for text, we need to offset them by a text run's position within its associated RenderText.
1676 // Computing this requires the following:
1677 // 1. Get the run's logical offset within the containing block (see note below).
1678 // 2. Add the containing block's position to get an page-relative position.
1679 // 3. Subtract the this object's (RenderText) position to get a distance relative to the RenderText.
1680
1681 // Note: For horizontal text, the contentLogicalLeft property accurately gets us the offset within the containing block.
1682 // ContentLogicalLeft is wrong for vertical orientations, but xPos (only set in vertical mode) provides that same information accurately.
1683 float containingBlockOffset = 0;
1684 if (CheckedPtr containingBlock = renderText->containingBlock())
1685 containingBlockOffset = isHorizontal ? containingBlock->absoluteBoundingBoxRect().x() : containingBlock->absoluteBoundingBoxRect().y();
1686
1687 distanceFromBoundsInDirection = isHorizontal ? lineBox->contentLogicalLeft() + containingBlockOffset - elementRect().x() : -textRun.xPos() + containingBlockOffset - elementRect().y();
1688 }
1689
1690 // Populate GlyphBuffer with all of the glyphs for the text runs, enabling us to measure character widths.
1691 CheckedRef fontCascade = textBox->fontCascade();
1692 GlyphBuffer glyphBuffer;
1693 if (fontCascade->codePath(textRun) == FontCascade::CodePath::Complex) {
1694 ComplexTextController complexTextController(fontCascade, textRun, true, nilnullptr);
1695 complexTextController.advance(text.length(), &glyphBuffer);
1696 } else {
1697 WidthIterator simpleIterator(fontCascade, textRun);
1698 simpleIterator.advance(text.length(), glyphBuffer);
1699 }
1700
1701 // Iterate over the glyphs and populate characterWidths. Sometimes multiple characters correspond to just
1702 // a single glyph so we might need to insert extras zeros into characterWidths for those indexes that
1703 // don't correspond to a glyph.
1704 characterWidths.reserveCapacity(text.length());
1705 unsigned characterIndex = 0;
1706 for (unsigned glyphIndex = 0; glyphIndex < glyphBuffer.size() && characterIndex < text.length(); glyphIndex++) {
1707 unsigned newCharacterIndex = glyphBuffer.uncheckedStringOffsetAt(glyphIndex);
1708 float characterWidth = WebCore::width(glyphBuffer.advanceAt(glyphIndex));
1709 while (characterIndex + 1 < newCharacterIndex && characterIndex < text.length()) {
1710 characterWidths.append(0);
1711 characterIndex++;
1712 }
1713
1714 // Round to integer widths, using the fractional part of accumulatedDistanceFromStart to avoid accumulating rounding errors.
1715 characterWidths.append(static_cast<uint16_t>(characterWidth + fmod(accumulatedDistanceFromStart, 1)));
1716 accumulatedDistanceFromStart += characterWidth;
1717 characterIndex = newCharacterIndex;
1718 }
1719
1720 if (!collapseTabs && !collapseNewlines) {
1721 lineString.append(text);
1722 return;
1723 }
1724
1725 lineString.reserveCapacity(lineString.length() + text.length());
1726 for (unsigned i = 0; i < text.length(); i++) {
1727 char16_t character = text[i];
1728 if (character == '\t' && collapseTabs)
1729 lineString.append(' ');
1730 else if (character == '\n' && collapseNewlines)
1731 lineString.append(' ');
1732 else
1733 lineString.append(character);
1734 }
1735 };
1736
1737 auto domOffset = [] (unsigned value) -> uint16_t {
1738 // It shouldn't be possible for any textbox to have more than 65535 characters.
1739 AX_ASSERT(value <= std::numeric_limits<uint16_t>::max())((void)0);
1740 return static_cast<uint16_t>(value);
1741 };
1742
1743 Vector<std::array<uint16_t, 2>> textRunDomOffsets;
1744 auto [textBox, orderCache] = InlineIterator::firstTextBoxInLogicalOrderFor(*renderText);
1745 size_t currentLineIndex = textBox ? textBox->lineIndex() : 0;
1746
1747 bool containsOnlyASCII = true;
1748
1749 auto updateContainsOnlyASCIIFromLineString = [&] {
1750 if (containsOnlyASCII) {
1751 StringView view = lineString;
1752 containsOnlyASCII = view.containsOnlyASCII();
1753 }
1754 };
1755
1756 while (textBox) {
1757 size_t newLineIndex = textBox->lineIndex();
1758 uint16_t startDOMOffset = domOffset(textBox->minimumCaretOffset());
1759 uint16_t endDOMOffset = domOffset(textBox->maximumCaretOffset());
1760 if (newLineIndex != currentLineIndex) {
1761 // The start and end (exclusive) character indices of this run.
1762 unsigned startIndex = fullString.length();
1763 unsigned endIndex = startIndex + lineString.length();
1764
1765 updateContainsOnlyASCIIFromLineString();
1766
1767 if (textRunDomOffsets.size() && startDOMOffset != textRunDomOffsets.last()[1]) {
1768 // If a space was trimmed in this text run (i.e., there's a gap between the end
1769 // of the current run's DOM offset and the start of the next), add it back.
1770 lineString.append(' ');
1771 ++endIndex;
1772 characterWidths.append(0);
1773 // We also need to account for this in the DOM offset itself, as otherwise we'll
1774 // compute the wrong value when going from rendered-text offset to DOM offset
1775 // (e.g. via AXTextRuns::domOffset()).
1776 textRunDomOffsets.last()[1] += 1;
1777 }
1778 runs.append({ currentLineIndex, startIndex, endIndex, { std::exchange(textRunDomOffsets, { }) }, std::exchange(characterWidths, { }), lineHeight, distanceFromBoundsInDirection });
1779
1780 currentLineIndex = newLineIndex;
1781 // Reset variables used in appendToLineString().
1782 fullString.append(lineString.toString());
1783 lineString.clear();
1784 accumulatedDistanceFromStart = 0.0;
1785 lineHeight = 0.0;
1786 distanceFromBoundsInDirection = 0.0;
1787 }
1788 appendToLineString(textBox);
1789
1790 // Within each iteration of this loop, we are looking at the *next* text box to compare to the current.
1791 // So, we need to set the textRunDomOffsets after the line index comparison, in order to assign the right DOM offsets per text box.
1792 textBox = InlineIterator::nextTextBoxInLogicalOrder(textBox, orderCache);
1793 textRunDomOffsets.append({ startDOMOffset, endDOMOffset });
1794 }
1795
1796 if (!lineString.isEmpty()) {
1797 updateContainsOnlyASCIIFromLineString();
1798
1799 unsigned startIndex = fullString.length();
1800 unsigned endIndex = startIndex + lineString.length();
1801 runs.append({ currentLineIndex, startIndex, endIndex, WTF::move(textRunDomOffsets), std::exchange(characterWidths, { }), lineHeight, distanceFromBoundsInDirection });
1802
1803 fullString.append(lineString.toString());
1804 }
1805 return { renderText->containingBlock(), WTF::move(runs), fullString.toString().isolatedCopy(), containsOnlyASCII };
1806}
1807
1808AXTextRunLineID AccessibilityRenderObject::listMarkerLineID() const
1809{
1810 AX_ASSERT(role() == AccessibilityRole::ListMarker)((void)0);
1811 return { renderer() ? renderer()->containingBlock() : nullptr, 0 };
1812}
1813
1814String AccessibilityRenderObject::listMarkerText() const
1815{
1816 CheckedPtr marker = dynamicDowncast<RenderListMarker>(renderer());
1817 return marker ? marker->textWithSuffix() : String();
1818}
1819#endif // ENABLE(ACCESSIBILITY_ISOLATED_TREE)
1820
1821int AccessibilityRenderObject::insertionPointLineNumber() const
1822{
1823 AX_ASSERT(isTextControl())((void)0);
1824
1825 // Use the text control native range if it's a native object.
1826 if (isNativeTextControl() && m_renderer) {
1827 Ref textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement();
1828 int start = textControl->selectionStart();
1829 int end = textControl->selectionEnd();
1830
1831 // If the selection range is not a collapsed range, we don't know whether the insertion point is the start or the end, thus return -1.
1832 // FIXME: for non-collapsed selection, determine the insertion point based on the TextFieldSelectionDirection.
1833 if (start != end)
1834 return -1;
1835
1836 return lineForPosition(textControl->visiblePositionForIndex(start));
1837 }
1838
1839 RefPtr frame = this->frame();
1840 if (!frame)
1841 return -1;
1842
1843 auto selectedTextRange = frame->selection().selection().firstRange();
1844 // If the selection range is not a collapsed range, we don't know whether the insertion point is the start or the end, thus return -1.
1845 if (!selectedTextRange || !selectedTextRange->collapsed())
1846 return -1;
1847
1848 return lineForPosition(makeDeprecatedLegacyPosition(selectedTextRange->start));
1849}
1850
1851static void setTextSelectionIntent(AXObjectCache* cache, AXTextStateChangeType type)
1852{
1853 if (!cache)
1854 return;
1855 AXTextStateChangeIntent intent(type, AXTextSelection { AXTextSelectionDirection::Discontiguous, AXTextSelectionGranularity::Unknown, false });
1856 cache->setTextSelectionIntent(intent);
1857 cache->setIsSynchronizingSelection(true);
1858}
1859
1860static void clearTextSelectionIntent(AXObjectCache* cache)
1861{
1862 if (!cache)
1863 return;
1864 cache->setTextSelectionIntent(AXTextStateChangeIntent());
1865 cache->setIsSynchronizingSelection(false);
1866}
1867
1868void AccessibilityRenderObject::setSelectedTextRange(CharacterRange&& range)
1869{
1870 setTextSelectionIntent(axObjectCache(), range.length ? AXTextStateChangeType::SelectionExtend : AXTextStateChangeType::SelectionMove);
1871
1872 CheckedPtr client = m_renderer ? m_renderer->document().editor().client() : nullptr;
1873 if (client)
1874 client->willChangeSelectionForAccessibility();
1875
1876 if (isNativeTextControl() && m_renderer) {
1877 Ref textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement();
1878 FocusOptions focusOptions { .preventScroll = true };
1879 textControl->focus(focusOptions);
1880 textControl->setSelectionRange(range.location, range.location + range.length);
1881 } else if (m_renderer) {
1882 AX_ASSERT(node())((void)0);
1883 Ref node = *this->node();
1884 auto elementRange = simpleRange();
1885 auto start = visiblePositionForIndexUsingCharacterIterator(node.get(), range.location);
1886 if (!contains<ComposedTree>(*elementRange, makeBoundaryPoint(start)))
1887 start = makeContainerOffsetPosition(elementRange->start);
1888 auto end = visiblePositionForIndexUsingCharacterIterator(node.get(), range.location + range.length);
1889 if (!contains<ComposedTree>(*elementRange, makeBoundaryPoint(end)))
1890 end = makeContainerOffsetPosition(elementRange->start);
1891 m_renderer->frame().selection().setSelection(VisibleSelection(start, end), FrameSelection::defaultSetSelectionOptions(UserTriggered::Yes));
1892 }
1893
1894 clearTextSelectionIntent(axObjectCache());
1895
1896 if (client)
1897 client->didChangeSelectionForAccessibility();
1898}
1899
1900URL AccessibilityRenderObject::url() const
1901{
1902 if (m_renderer && isWebArea())
1903 return m_renderer->document().url();
1904 return AccessibilityNodeObject::url();
1905}
1906
1907bool AccessibilityRenderObject::setValue(const String& string)
1908{
1909 if (!m_renderer)
1910 return false;
1911
1912 CheckedRef renderer = *m_renderer;
1913
1914 RefPtr element = dynamicDowncast<Element>(renderer->node());
1915 if (!element)
1916 return false;
1917
1918 // We should use the editor's insertText to mimic typing into the field.
1919 // Also only do this when the field is in editing mode.
1920 if (RefPtr frame = renderer->document().frame()) {
1921 Ref editor = frame->editor();
1922 if (element->shouldUseInputMethod()) {
1923 editor->clearText();
1924 editor->insertText(string, nullptr);
1925 return true;
1926 }
1927 }
1928 // FIXME: Do we want to do anything here for ARIA textboxes?
1929 if (RefPtr input = dynamicDowncast<HTMLInputElement>(*element); input && renderer->isRenderTextControlSingleLine()) {
1930 input->setValue(string);
1931 return true;
1932 }
1933 if (RefPtr textarea = dynamicDowncast<HTMLTextAreaElement>(*element); element && renderer->isRenderTextControlMultiLine()) {
1934 textarea->setValue(string);
1935 return true;
1936 }
1937
1938 return false;
1939}
1940
1941bool AccessibilityRenderObject::press()
1942{
1943#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
1944 if (RefPtr mediaElement = this->mediaElement()) {
1945 // We can safely call the internal togglePlayState method, which doesn't check restrictions,
1946 // because this method is only called from user interaction.
1947 return AccessibilityMediaHelpers::press(*mediaElement);
1948 }
1949#endif
1950 return AccessibilityObject::press();
1951}
1952
1953Document* AccessibilityRenderObject::document() const
1954{
1955 if (!m_renderer)
1956 return AccessibilityNodeObject::document();
1957 return &m_renderer->document();
1958}
1959
1960bool AccessibilityRenderObject::isWidget() const
1961{
1962 return widget();
1963}
1964
1965Widget* AccessibilityRenderObject::widget() const
1966{
1967 RefPtr renderWidget = dynamicDowncast<RenderWidget>(renderer());
1968 return renderWidget ? renderWidget->widget() : nullptr;
1969}
1970
1971AccessibilityObject* AccessibilityRenderObject::associatedImageObject(HTMLMapElement& map) const
1972{
1973 CheckedPtr cache = axObjectCache();
1974 return cache ? cache->getOrCreate(map.imageElement().get()) : nullptr;
1975}
1976
1977AXCoreObject::AccessibilityChildrenVector AccessibilityRenderObject::documentLinks()
1978{
1979 if (!m_renderer)
1980 return { };
1981
1982 AccessibilityChildrenVector result;
1983 Ref document = m_renderer->document();
1984 Ref<HTMLCollection> links = document->links();
1985 CheckedPtr cache = document->existingAXObjectCache();
1986 if (!cache)
1987 return { };
1988
1989 for (unsigned i = 0; RefPtr current = links->item(i); ++i) {
1990 if (current->renderer()) {
1991 RefPtr axObject = cache->getOrCreate(*current);
1992 AX_ASSERT(axObject)((void)0);
1993 if (!axObject->isIgnored() && axObject->isLink())
1994 result.append(axObject.releaseNonNull());
1995 } else {
1996 RefPtr parent = current->parentNode();
1997 if (RefPtr parentMap = dynamicDowncast<HTMLMapElement>(parent); parentMap && is<HTMLAreaElement>(*current)) {
1998 RefPtr parentImage = parentMap->imageElement();
1999 if (RefPtr parentImageAxObject = cache->getOrCreate(parentImage.get())) {
2000 for (const auto& child : parentImageAxObject->unignoredChildren()) {
2001 if (child->isImageMapLink() && !result.contains(child))
2002 result.append(child);
2003 }
2004 }
2005 }
2006 }
2007 }
2008
2009 return result;
2010}
2011
2012LocalFrameView* AccessibilityRenderObject::documentFrameView() const
2013{
2014 if (!m_renderer)
2015 return AccessibilityNodeObject::documentFrameView();
2016
2017 return &m_renderer->view().frameView();
2018}
2019
2020Widget* AccessibilityRenderObject::widgetForAttachmentView() const
2021{
2022 if (!isAttachment())
2023 return nullptr;
2024 RefPtr renderWidget = dynamicDowncast<RenderWidget>(*m_renderer);
2025 return renderWidget ? renderWidget->widget() : nullptr;
2026}
2027
2028VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(int index) const
2029{
2030 if (m_renderer) {
2031 if (isNativeTextControl()) {
2032 Ref textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement();
2033 return textControl->visiblePositionForIndex(std::clamp(index, 0, static_cast<int>(textControl->value()->length())));
2034 }
2035
2036 if (!allowsTextRanges() && !is<RenderText>(*m_renderer))
2037 return { };
2038 }
2039 return AccessibilityNodeObject::visiblePositionForIndex(index);
2040}
2041
2042int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& position) const
2043{
2044 if (m_renderer) {
2045 if (isNativeTextControl())
2046 return downcast<RenderTextControl>(*m_renderer).textFormControlElement().indexForVisiblePosition(position);
2047
2048 if (!allowsTextRanges() && !is<RenderText>(*m_renderer))
2049 return 0;
2050 }
2051 return AccessibilityNodeObject::indexForVisiblePosition(position);
2052}
2053
2054RefPtr<Element> AccessibilityRenderObject::rootEditableElementForPosition(const Position& position) const
2055{
2056 // Find the root editable or pseudo-editable (i.e. having an editable ARIA role) element.
2057 RefPtr<Element> result;
2058 RefPtr rootEditableElement = position.rootEditableElement();
2059
2060 for (RefPtr ancestor = position.anchorElementAncestor(); ancestor && ancestor != rootEditableElement; ancestor = ancestor->parentElement()) {
2061 if (elementIsTextControl(*ancestor))
2062 result = ancestor;
2063 if (ancestor->elementName() == ElementName::HTML_body)
2064 break;
2065 }
2066 return result ? result : rootEditableElement;
2067}
2068
2069bool AccessibilityRenderObject::elementIsTextControl(const Element& element) const
2070{
2071 CheckedPtr cache = axObjectCache();
2072 RefPtr axObject = cache ? cache->getOrCreate(const_cast<Element&>(element)) : nullptr;
2073 return axObject && axObject->isTextControl();
2074}
2075
2076bool AccessibilityRenderObject::isVisiblePositionRangeInDifferentDocument(const VisiblePositionRange& range) const
2077{
2078 if (range.start.isNull() || range.end.isNull())
2079 return false;
2080
2081 VisibleSelection newSelection = VisibleSelection(range.start, range.end);
2082 if (RefPtr newSelectionDocument = newSelection.base().document()) {
2083 if (RefPtr newSelectionFrame = newSelectionDocument->frame()) {
2084 RefPtr frame = this->frame();
2085 if (!frame || (newSelectionFrame != frame && newSelectionDocument != frame->document()))
2086 return true;
2087 }
2088 }
2089
2090 return false;
2091}
2092
2093void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePositionRange& range) const
2094{
2095 if (range.isNull())
2096 return;
2097
2098 // In WebKit1, when the top web area sets the selection to be an input element in an iframe, the caret will disappear.
2099 // FrameSelection::setSelectionWithoutUpdatingAppearance is setting the selection on the new frame in this case, and causing this behavior.
2100 if (isWebArea() && parentObject() && parentObject()->isAttachment()
2101 && isVisiblePositionRangeInDifferentDocument(range))
2102 return;
2103
2104 CheckedPtr client = m_renderer ? m_renderer->document().editor().client() : nullptr;
2105 if (client)
2106 client->willChangeSelectionForAccessibility();
2107
2108 if (isNativeTextControl()) {
2109 // isNativeTextControl returns true only if this->node() is<HTMLTextAreaElement> or is<HTMLInputElement>.
2110 // Since both HTMLTextAreaElement and HTMLInputElement derive from HTMLTextFormControlElement, it is safe to downcast here.
2111 Ref textControl = uncheckedDowncast<HTMLTextFormControlElement>(*node());
2112 int start = textControl->indexForVisiblePosition(range.start);
2113 int end = textControl->indexForVisiblePosition(range.end);
2114
2115 // For ranges entirely contained in textControl, the start or end position may not be inside textControl.innerTextElement.
2116 // This would cause that the above indexes will be 0, leading to an incorrect selected range
2117 // (see HTMLTextFormControlElement::indexForVisiblePosition). This is
2118 // the case when range is obtained from AXObjectCache::rangeForNodeContents
2119 // for the HTMLTextFormControlElement.
2120 // Thus, the following corrects the start and end indexes in such a case..
2121 if (range.start.deepEquivalent().anchorNode() == range.end.deepEquivalent().anchorNode()
2122 && range.start.deepEquivalent().anchorNode() == textControl.ptr()) {
2123 if (auto innerText = textControl->innerTextElement()) {
2124 auto textControlRange = makeVisiblePositionRange(AXObjectCache::rangeForNodeContents(textControl.get()));
2125 auto innerRange = makeVisiblePositionRange(AXObjectCache::rangeForNodeContents(*innerText));
2126
2127 if (range.start.equals(textControlRange.end))
2128 start = textControl->value()->length();
2129 else if (range.start <= innerRange.start)
2130 start = 0;
2131
2132 if (range.end >= innerRange.end
2133 || range.end.equals(textControlRange.end))
2134 end = textControl->value()->length();
2135 }
2136 }
2137
2138 setTextSelectionIntent(axObjectCache(), start == end ? AXTextStateChangeType::SelectionMove : AXTextStateChangeType::SelectionExtend);
2139 textControl->focus();
2140 textControl->setSelectionRange(start, end);
2141 } else if (m_renderer) {
2142 // Make selection and tell the document to use it. If it's zero length, then move to that position.
2143 if (range.start == range.end) {
2144 setTextSelectionIntent(axObjectCache(), AXTextStateChangeType::SelectionMove);
2145
2146 auto start = range.start;
2147 if (auto elementRange = simpleRange()) {
2148 if (!contains<ComposedTree>(*elementRange, makeBoundaryPoint(start)))
2149 start = makeContainerOffsetPosition(elementRange->start);
2150 }
2151
2152 m_renderer->frame().selection().moveTo(start, UserTriggered::Yes);
2153 } else {
2154 setTextSelectionIntent(axObjectCache(), AXTextStateChangeType::SelectionExtend);
2155
2156 VisibleSelection newSelection = VisibleSelection(range.start, range.end);
2157 m_renderer->frame().selection().setSelection(newSelection, FrameSelection::defaultSetSelectionOptions(UserTriggered::Yes));
2158 }
2159 }
2160
2161 clearTextSelectionIntent(axObjectCache());
2162
2163 if (client)
2164 client->didChangeSelectionForAccessibility();
2165}
2166
2167// NOTE: Consider providing this utility method as AX API
2168VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const
2169{
2170 if (!isTextControl())
2171 return VisiblePosition();
2172
2173 // lastIndexOK specifies whether the position after the last character is acceptable
2174 if (indexValue >= text().length()) {
2175 if (!lastIndexOK || indexValue > text().length())
2176 return VisiblePosition();
2177 }
2178 VisiblePosition position = visiblePositionForIndex(indexValue);
2179 position.setAffinity(Affinity::Downstream);
2180 return position;
2181}
2182
2183// NOTE: Consider providing this utility method as AX API
2184int AccessibilityRenderObject::index(const VisiblePosition& position) const
2185{
2186 if (position.isNull() || !isTextControl())
2187 return -1;
2188
2189 if (renderObjectContainsPosition(renderer(), position.deepEquivalent()))
2190 return indexForVisiblePosition(position);
2191
2192 return -1;
2193}
2194
2195static bool isHardLineBreak(const VisiblePosition& position)
2196{
2197 if (!isEndOfLine(position))
2198 return false;
2199
2200 auto next = position.next();
2201
2202 auto lineBreakRange = makeSimpleRange(position, next);
2203 if (!lineBreakRange)
2204 return false;
2205
2206 TextIterator it(*lineBreakRange);
2207 if (it.atEnd())
2208 return false;
2209
2210 if (is<HTMLBRElement>(it.node()))
2211 return true;
2212
2213 if (it.node() != position.deepEquivalent().anchorNode())
2214 return false;
2215
2216 return it.text().length() == 1 && it.text()[0] == '\n';
2217}
2218
2219// Given a line number, the range of characters of the text associated with this accessibility
2220// object that contains the line number.
2221CharacterRange AccessibilityRenderObject::doAXRangeForLine(unsigned lineNumber) const
2222{
2223 if (!isTextControl())
2224 return { };
2225
2226 // Iterate to the specified line.
2227 auto lineStart = visiblePositionForIndex(0);
2228 for (unsigned lineCount = lineNumber; lineCount; --lineCount) {
2229 auto nextLineStart = nextLinePosition(lineStart, 0);
2230 if (nextLineStart.isNull() || nextLineStart == lineStart)
2231 return { };
2232 lineStart = nextLineStart;
2233 }
2234
2235 // Get the end of the line based on the starting position.
2236 auto lineEnd = endOfLine(lineStart);
2237
2238 int lineStartIndex = indexForVisiblePosition(lineStart);
2239 int lineEndIndex = indexForVisiblePosition(lineEnd);
2240
2241 if (isHardLineBreak(lineEnd))
2242 ++lineEndIndex;
2243
2244 if (lineStartIndex < 0 || lineEndIndex < 0 || lineEndIndex <= lineStartIndex)
2245 return { };
2246
2247 return { static_cast<unsigned>(lineStartIndex), static_cast<unsigned>(lineEndIndex - lineStartIndex) };
2248}
2249
2250// The composed character range in the text associated with this accessibility object that
2251// is specified by the given index value. This parameterized attribute returns the complete
2252// range of characters (including surrogate pairs of multi-byte glyphs) at the given index.
2253CharacterRange AccessibilityRenderObject::doAXRangeForIndex(unsigned index) const
2254{
2255 if (!isTextControl())
2256 return { };
2257
2258 String elementText = text();
2259 if (!elementText.length() || index > elementText.length() - 1)
2260 return { };
2261 return { index, 1 };
2262}
2263
2264// A substring of the text associated with this accessibility object that is
2265// specified by the given character range.
2266String AccessibilityRenderObject::doAXStringForRange(const CharacterRange& range) const
2267{
2268 if (!range.length || !isTextControl())
2269 return { };
2270 return text().substring(range.location, range.length);
2271}
2272
2273// The bounding rectangle of the text associated with this accessibility object that is
2274// specified by the given range. This is the bounding rectangle a sighted user would see
2275// on the display screen, in pixels.
2276IntRect AccessibilityRenderObject::doAXBoundsForRange(const CharacterRange& range) const
2277{
2278 if (allowsTextRanges())
2279 return boundsForVisiblePositionRange(visiblePositionRangeForRange(range));
2280 return IntRect();
2281}
2282
2283IntRect AccessibilityRenderObject::doAXBoundsForRangeUsingCharacterOffset(const CharacterRange& characterRange) const
2284{
2285 if (!allowsTextRanges())
2286 return { };
2287 auto range = rangeForCharacterRange(characterRange);
2288 if (!range)
2289 return { };
2290 return boundsForRange(*range);
2291}
2292
2293AccessibilityObject* AccessibilityRenderObject::accessibilityImageMapHitTest(HTMLAreaElement& area, const IntPoint& point) const
2294{
2295 RefPtr mapAncestor = ancestorsOfType<HTMLMapElement>(area).first();
2296 RefPtr associatedImage = mapAncestor ? associatedImageObject(*mapAncestor) : nullptr;
2297 if (!associatedImage)
2298 return nullptr;
2299
2300 for (const auto& child : associatedImage->unignoredChildren()) {
2301 if (child->elementRect().contains(point))
2302 return dynamicDowncast<AccessibilityObject>(child.get());
2303 }
2304
2305 return nullptr;
2306}
2307
2308RefPtr<AccessibilityObject> AccessibilityRenderObject::remoteSVGElementHitTest(const IntPoint& point) const
2309{
2310 RefPtr remote = remoteSVGRootElement(CreateIfNecessary::Yes);
2311 if (!remote)
2312 return nullptr;
2313
2314 IntSize offset = point - roundedIntPoint(boundingBoxRect().location());
2315 return downcast<AccessibilityObject>(remote->accessibilityHitTest(IntPoint(offset)).get());
2316}
2317
2318RefPtr<AccessibilityObject> AccessibilityRenderObject::elementAccessibilityHitTest(const IntPoint& point) const
2319{
2320 if (isSVGImage())
2321 return remoteSVGElementHitTest(point);
2322
2323 if (role() == AccessibilityRole::ListBox) {
2324 if (CheckedPtr renderListBox = dynamicDowncast<RenderListBox>(m_renderer.get())) {
2325 LayoutRect parentRect = boundingBoxRect();
2326
2327 RefPtr<AccessibilityObject> listBoxOption;
2328 const auto& children = const_cast<AccessibilityRenderObject*>(this)->unignoredChildren();
2329 unsigned length = children.size();
2330 for (unsigned i = 0; i < length; ++i) {
2331 LayoutRect rect = renderListBox->itemBoundingBoxRect(parentRect.location(), i);
2332 if (rect.contains(point)) {
2333 listBoxOption = downcast<AccessibilityObject>(children[i].get());
2334 break;
2335 }
2336 }
2337
2338 if (listBoxOption && !listBoxOption->isIgnored())
2339 return listBoxOption.unsafeGet();
2340
2341 CheckedPtr cache = axObjectCache();
2342 return cache ? cache->getOrCreate(*renderListBox) : nullptr;
2343 }
2344 }
2345
2346 return AccessibilityObject::elementAccessibilityHitTest(point);
2347}
2348
2349RefPtr<AXCoreObject> AccessibilityRenderObject::accessibilityHitTest(const IntPoint& point) const
2350{
2351 if (!m_renderer || !m_renderer->hasLayer())
2352 return nullptr;
2353
2354 m_renderer->document().updateLayout();
2355 // Layout may have destroyed this renderer or layer, so re-check their presence.
2356 if (!m_renderer || !m_renderer->hasLayer())
2357 return nullptr;
2358
2359 IntPoint adjustedPoint = point;
2360#if !ENABLE(ACCESSIBILITY_LOCAL_FRAME)(defined 1 && 1)
2361 // Adjust point for the remoteFrameOffset.
2362 adjustedPoint.moveBy(-remoteFrameOffset());
2363#endif
2364
2365 constexpr OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active, HitTestRequest::Type::AccessibilityHitTest };
2366 HitTestResult hitTestResult { adjustedPoint };
2367
2368 dynamicDowncast<RenderLayerModelObject>(*m_renderer)->layer()->hitTest(hitType, hitTestResult);
2369 RefPtr node = hitTestResult.innerNode();
2370 if (!node)
2371 return nullptr;
2372
2373 if (RefPtr area = dynamicDowncast<HTMLAreaElement>(*node))
2374 return accessibilityImageMapHitTest(*area, point);
2375
2376 if (RefPtr option = dynamicDowncast<HTMLOptionElement>(*node))
2377 node = option->ownerSelectElement();
2378
2379 CheckedPtr cache = node ? node->document().axObjectCache() : nullptr;
2380 RefPtr result = cache ? cache->getOrCreate(*node) : nullptr;
2381 if (!result)
2382 return nullptr;
2383
2384 result->updateChildrenIfNecessary();
2385 // Allow the element to perform any hit-testing it might need to do to reach non-render children.
2386 result = result->elementAccessibilityHitTest(point);
2387
2388 if (result && result->isIgnored()) {
2389 // If this element is the label of a control, a hit test should return the control.
2390 RefPtr controlObject = result->controlForLabelElement();
2391 if (controlObject && !controlObject->titleUIElement())
2392 return controlObject;
2393
2394 result = result->parentObjectUnignored();
2395 }
2396
2397 if (std::optional stitchedIntoID = result ? result->stitchedIntoID() : std::nullopt) {
2398 if (RefPtr stitchRepresentative = cache->objectForID(*stitchedIntoID))
2399 return stitchRepresentative;
2400 }
2401 return result;
2402}
2403
2404bool AccessibilityRenderObject::renderObjectIsObservable(RenderObject& renderer) const
2405{
2406 // AX clients will listen for AXValueChange on a text control.
2407 if (is<RenderTextControl>(renderer))
2408 return true;
2409
2410 // AX clients will listen for AXSelectedChildrenChanged on listboxes.
2411 RefPtr node = renderer.node();
2412 if (!node)
2413 return false;
2414
2415 RefPtr element = dynamicDowncast<Element>(*node);
2416 if (!element)
2417 return false;
2418
2419 if (hasRole(*element, "listbox"_s))
2420 return true;
2421
2422 // Element-based check for HTMLSelectElement listbox.
2423 if (RefPtr selectElement = dynamicDowncast<HTMLSelectElement>(*element); selectElement && !selectElement->usesMenuList())
2424 return true;
2425
2426 // Textboxes should send out notifications.
2427 return contentEditableAttributeIsEnabled(*element) || hasRole(*element, "textbox"_s);
2428}
2429
2430AccessibilityObject* AccessibilityRenderObject::observableObject() const
2431{
2432 // This allows the table to be the one who sends notifications about tables.
2433 if (RefPtr parentTable = parentTableIfExposedTableRow())
2434 return dynamicDowncast<AccessibilityObject>(parentTable.get());
2435
2436 // Find the object going up the parent chain that is used in accessibility to monitor certain notifications.
2437 for (CheckedPtr renderer = this->renderer(); renderer && renderer->node(); renderer = renderer->parent()) {
2438 if (renderObjectIsObservable(*renderer)) {
2439 if (CheckedPtr cache = axObjectCache())
2440 return cache->getOrCreate(*renderer);
2441 }
2442 }
2443
2444 return nullptr;
2445}
2446
2447bool AccessibilityRenderObject::shouldIgnoreAttributeRole() const
2448{
2449 if (hasTreeItemRole())
2450 return hasRareData() && !rareData()->isTreeItemValid();
2451
2452 return m_ariaRole == AccessibilityRole::Document && hasContentEditableAttributeSet();
2453}
2454
2455AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole()
2456{
2457 // Handle list role determination before checking for a renderer, because we want to use the same codepath in both scenarios.
2458 if (isAccessibilityList()) {
2459 if (!m_childrenDirty && childrenInitialized())
2460 return determineListRoleWithCleanChildren();
2461 return isDescriptionList() ? AccessibilityRole::DescriptionList : AccessibilityRole::List;
2462 }
2463
2464 if (hasTreeItemRole())
2465 ensureRareData().setIsTreeItemValid(containingTree());
2466
2467 if (hasTreeRole())
2468 return isValidTree() ? AccessibilityRole::Tree : AccessibilityRole::Generic;
2469
2470 if (!m_renderer)
2471 return AccessibilityNodeObject::determineAccessibilityRole();
2472
2473 if (m_renderer->isRenderText())
2474 return AccessibilityRole::StaticText;
2475
2476#if ENABLE(APPLE_PAY)(defined 1 && 1)
2477 if (isApplePayButton())
2478 return AccessibilityRole::Button;
2479#endif
2480
2481#if ENABLE(ATTACHMENT_ELEMENT)(defined 1 && 1)
2482 if (isAttachmentElement())
2483 return AccessibilityRole::Button;
2484#endif
2485
2486 // Sometimes we need to ignore the attribute role. Like if a tree is malformed,
2487 // we want to ignore the treeitem's attribute role.
2488 if (m_ariaRole != AccessibilityRole::Unknown && !shouldIgnoreAttributeRole())
2489 return m_ariaRole;
2490
2491 if (isExposableTable())
2492 return AccessibilityRole::Table;
2493
2494 if (isExposedTableRow())
2495 return AccessibilityRole::Row;
2496
2497 RefPtr node = m_renderer->node();
2498 if (m_renderer->isRenderListItem()) {
2499 // The details / summary disclosure triangle is implemented using RenderListItem
2500 // but we want to return `AccessibilityRole::Summary` there, so skip them here.
2501 RefPtr summary = dynamicDowncast<HTMLSummaryElement>(node);
2502 if (!summary || !summary->isActiveSummary())
2503 return AccessibilityRole::ListItem;
2504 }
2505
2506 if (m_renderer->isRenderListMarker())
2507 return AccessibilityRole::ListMarker;
2508 if (m_renderer->isBR())
2509 return AccessibilityRole::LineBreak;
2510 if (RefPtr img = dynamicDowncast<HTMLImageElement>(node); img && img->hasAttributeWithoutSynchronization(usemapAttr))
2511 return AccessibilityRole::ImageMap;
2512 if (m_renderer->isImage()) {
2513 if (is<HTMLInputElement>(node))
2514 return selfOrAncestorLinkHasPopup() ? AccessibilityRole::PopUpButton : AccessibilityRole::Button;
2515
2516 if (RefPtr svgRoot = remoteSVGRootElement(CreateIfNecessary::Yes)) {
2517 if (svgRoot->isRootWithAccessibleContent())
2518 return AccessibilityRole::SVGRoot;
2519 }
2520 return AccessibilityRole::Image;
2521 }
2522
2523 if (m_renderer->isRenderView())
2524 return AccessibilityRole::WebArea;
2525 if (m_renderer->isRenderTextControlSingleLine()) {
2526 if (RefPtr input = dynamicDowncast<HTMLInputElement>(node))
2527 return roleFromInputElement(*input);
2528 }
2529 if (m_renderer->isRenderTextControlMultiLine())
2530 return AccessibilityRole::TextArea;
2531 // Element-based check for HTMLSelectElement with any renderer.
2532 if (RefPtr selectElement = dynamicDowncast<HTMLSelectElement>(node)) {
2533 if (selectElement->usesMenuList())
2534 return selectElement->multiple() ? AccessibilityRole::ListBox : AccessibilityRole::PopUpButton;
2535 return AccessibilityRole::ListBox;
2536 }
2537
2538 if (m_renderer->isRenderOrLegacyRenderSVGRoot())
2539 return AccessibilityRole::SVGRoot;
2540
2541 switch (downcast<RenderElement>(*m_renderer).style().display().value) {
2542 case Style::DisplayType::InlineRuby:
2543 return AccessibilityRole::RubyInline;
2544 case Style::DisplayType::RubyText:
2545 return AccessibilityRole::RubyText;
2546 case Style::DisplayType::BlockRuby:
2547 case Style::DisplayType::RubyBase:
2548 return AccessibilityRole::Group;
2549 default:
2550 break;
2551 }
2552
2553#if USE(ATSPI)(defined USE_ATSPI && USE_ATSPI)
2554 // Non-USE(ATSPI) platforms walk the DOM to build the accessibility tree, and thus never encounter these ignorable
2555 // anonymous renderers.
2556 // FIXME: Consider removing this ATSPI-only branch with https://bugs.webkit.org/show_bug.cgi?id=282117.
2557 if (m_renderer->isAnonymous() && (is<RenderTableCell>(m_renderer.get()) || is<RenderTableRow>(m_renderer.get()) || is<RenderTable>(m_renderer.get())))
2558 return AccessibilityRole::Ignored;
2559#endif // USE(ATSPI)
2560
2561 if (m_renderer->isRenderTableSection())
2562 return AccessibilityRole::Ignored;
2563
2564 auto treatStyleFormatGroupAsInline = is<RenderInline>(*m_renderer) ? TreatStyleFormatGroupAsInline::Yes : TreatStyleFormatGroupAsInline::No;
2565 auto roleFromNode = determineAccessibilityRoleFromNode(treatStyleFormatGroupAsInline);
2566
2567 // Table cells (by default) return a TextGroup role from determineAccessibilityRoleFromNode.
2568 // Before returning that role, check if the table cell is within a valid table, so that the a cell role can be returned.
2569 if (isTableCell()) {
2570 RefPtr parentTable = this->parentTable();
2571 if (parentTable && parentTable->isExposableTable())
2572 return parentTable->hasGridRole() ? AccessibilityRole::GridCell : AccessibilityRole::Cell;
2573 }
2574
2575 if (roleFromNode != AccessibilityRole::Unknown)
2576 return roleFromNode;
2577
2578#if !PLATFORM(COCOA)(defined 1 && 1)
2579 // This block should be deleted for all platforms, but doing so causes a lot of test failures that need to be sorted out.
2580 if (CheckedPtr blockRenderer = dynamicDowncast<RenderBlockFlow>(m_renderer.get()))
2581 return blockRenderer->isAnonymousBlock() ? AccessibilityRole::TextGroup : AccessibilityRole::Generic;
2582#endif
2583
2584 // InlineRole is the final fallback before assigning AccessibilityRole::Unknown to an object. It makes it
2585 // possible to distinguish truly unknown objects from non-focusable inline text elements
2586 // which have an event handler or attribute suggesting possible inclusion by the platform.
2587 if (is<RenderInline>(*m_renderer)
2588 && (hasAttributesRequiredForInclusion()
2589 || (node && node->hasEventListeners())
2590 || (supportsDatetimeAttribute() && !getAttribute(datetimeAttr).isEmpty())))
2591 return AccessibilityRole::Inline;
2592
2593 return AccessibilityRole::Unknown;
2594}
2595
2596std::optional<AXCoreObject::AccessibilityChildrenVector> AccessibilityRenderObject::imageOverlayElements()
2597{
2598 AXTRACE("AccessibilityRenderObject::imageOverlayElements"_s)(void)0;
2599
2600 if (!m_renderer || !toSimpleImage(*m_renderer))
2601 return std::nullopt;
2602
2603 const auto& children = this->unignoredChildren();
2604 if (children.size())
2605 return children;
2606
2607#if ENABLE(IMAGE_ANALYSIS)(defined 1 && 1)
2608 RefPtr page = this->page();
2609 if (!page)
2610 return std::nullopt;
2611
2612 RefPtr element = this->element();
2613 if (!element)
2614 return std::nullopt;
2615
2616 page->chrome().client().requestTextRecognition(*element, { }, [](RefPtr<Element>&& imageOverlayHost) {
2617 if (!imageOverlayHost)
2618 return;
2619
2620 if (CheckedPtr cache = imageOverlayHost->document().existingAXObjectCache())
2621 cache->postNotification(imageOverlayHost.get(), AXNotification::ImageOverlayChanged);
2622 });
2623#endif
2624
2625 return std::nullopt;
2626}
2627
2628bool AccessibilityRenderObject::inheritsPresentationalRole() const
2629{
2630 // ARIA states if an item can get focus, it should not be presentational.
2631 if (canSetFocusAttribute())
2632 return false;
2633
2634 // ARIA spec says that when a parent object is presentational, and it has required child elements,
2635 // those child elements are also presentational. For example, <li> becomes presentational from <ul>.
2636 // http://www.w3.org/WAI/PF/aria/complete#presentation
2637
2638 std::span<decltype(aTag)* const> parentTags;
2639 switch (role()) {
2640 case AccessibilityRole::ListItem:
2641 case AccessibilityRole::ListMarker: {
2642 static constexpr std::array listItemParents { &dlTag, &menuTag, &olTag, &ulTag };
2643 parentTags = listItemParents;
2644 break;
2645 }
2646 case AccessibilityRole::GridCell:
2647 case AccessibilityRole::Cell: {
2648 static constexpr std::array tableCellParents { &tableTag };
2649 parentTags = tableCellParents;
2650 break;
2651 }
2652 default:
2653 // Not all elements need to do the following check, only ones that are required children.
2654 return false;
2655 }
2656
2657 for (RefPtr parent = parentObject(); parent; parent = parent->parentObject()) {
2658 RefPtr accessibilityRenderer = dynamicDowncast<AccessibilityRenderObject>(*parent);
2659 if (!accessibilityRenderer)
2660 continue;
2661
2662 RefPtr node = dynamicDowncast<Element>(accessibilityRenderer->node());
2663 if (!node)
2664 continue;
2665
2666 // If native tag of the parent element matches an acceptable name, then return
2667 // based on its presentational status.
2668 auto& name = node->tagQName();
2669 if (std::ranges::any_of(parentTags, [&name](auto* possibleName) { return possibleName->get() == name; }))
2670 return parent->role() == AccessibilityRole::Presentational;
2671 }
2672
2673 return false;
2674}
2675
2676void AccessibilityRenderObject::addImageMapChildren()
2677{
2678 CheckedPtr renderImage = dynamicDowncast<RenderImage>(renderer());
2679 if (!renderImage)
2680 return;
2681
2682 RefPtr map = renderImage->imageMap();
2683 if (!map)
2684 return;
2685
2686 WeakPtr cache = axObjectCache();
2687 if (!cache)
2688 return;
2689 for (Ref area : descendantsOfType<HTMLAreaElement>(*map)) {
2690 // add an <area> element for this child if it has a link
2691 if (!area->isLink())
2692 continue;
2693 addChild(cache->getOrCreate(area.get()));
2694 }
2695}
2696
2697void AccessibilityRenderObject::addTextFieldChildren()
2698{
2699 RefPtr node = dynamicDowncast<HTMLInputElement>(this->node());
2700 if (!node)
2701 return;
2702
2703 RefPtr spinButtonElement = dynamicDowncast<SpinButtonElement>(node->innerSpinButtonElement());
2704 if (!spinButtonElement)
2705 return;
2706
2707 Ref axSpinButton = axObjectCache()->createSpinButton(*spinButtonElement);
2708 axSpinButton->setParent(this);
2709 addChild(WTF::move(axSpinButton));
2710}
2711
2712bool AccessibilityRenderObject::isSVGImage() const
2713{
2714 return remoteSVGRootElement(CreateIfNecessary::Yes);
2715}
2716
2717void AccessibilityRenderObject::detachRemoteSVGRoot()
2718{
2719 if (RefPtr root = remoteSVGRootElement(CreateIfNecessary::No))
2720 root->setParent(nullptr);
2721}
2722
2723AccessibilitySVGObject* AccessibilityRenderObject::remoteSVGRootElement(CreateIfNecessary createIfNecessary) const
2724{
2725 CheckedPtr renderImage = dynamicDowncast<RenderImage>(renderer());
2726 if (!renderImage)
2727 return nullptr;
2728
2729 auto* cachedImage = renderImage->cachedImage();
Local variable 'cachedImage' is uncounted and unsafe
2730 if (!cachedImage)
2731 return nullptr;
2732
2733 RefPtr svgImage = dynamicDowncast<SVGImage>(cachedImage->image());
2734 if (!svgImage)
2735 return nullptr;
2736
2737 RefPtr frameView = svgImage->frameView();
2738 if (!frameView)
2739 return nullptr;
2740
2741 RefPtr document = frameView->frame().document();
2742 if (!is<SVGDocument>(document))
2743 return nullptr;
2744
2745 auto rootElement = DocumentSVG::rootElement(*document);
2746 if (!rootElement)
2747 return nullptr;
2748
2749 CheckedPtr rendererRoot = rootElement->renderer();
2750 if (!rendererRoot)
2751 return nullptr;
2752
2753 // Use the AXObjectCache from this object, not the one from the SVG document above.
2754 // The SVG document is not connected to the top document of this object, thus it would result in the AX SVG objects to be created in a separate instance of AXObjectCache.
2755 CheckedPtr cache = axObjectCache();
2756 if (!cache)
2757 return nullptr;
2758
2759 RefPtr rootSVGObject = createIfNecessary == CreateIfNecessary::Yes ? cache->getOrCreate(*rendererRoot) : cache->get(rendererRoot.get());
2760 AX_ASSERT(createIfNecessary == CreateIfNecessary::No || rootSVGObject)((void)0);
2761 return dynamicDowncast<AccessibilitySVGObject>(rootSVGObject).unsafeGet();
2762}
2763
2764void AccessibilityRenderObject::addRemoteSVGChildren()
2765{
2766 RefPtr<AccessibilitySVGObject> root = remoteSVGRootElement(CreateIfNecessary::Yes);
2767 if (!root)
2768 return;
2769
2770 // FIXME: It's possible for an SVG that is rendered twice to share renderers. We don't want to add this as a child of both parents
2771 // in this case, as it will create an invalid parent-child relationship in the accessibility tree.
2772 // If it's parent is a WebArea, that is just the default value given when the object was created, so still add the child in that case.
2773 RefPtr parent = root->parentObject();
2774 if (parent && parent->role() != AccessibilityRole::WebArea)
2775 return;
2776
2777 // In order to connect the AX hierarchy from the SVG root element from the loaded resource
2778 // the parent must be set, because there's no other way to get back to who created the image.
2779 root->setParent(this);
2780 addChild(*root);
2781}
2782
2783void AccessibilityRenderObject::addAttachmentChildren()
2784{
2785 if (!isAttachment())
2786 return;
2787
2788 // LocalFrameView's need to be inserted into the AX hierarchy when encountered.
2789 RefPtr widget = widgetForAttachmentView();
2790 if (!widget || !(widget->isLocalFrameView() || widget->isRemoteFrameView()))
2791 return;
2792
2793 if (CheckedPtr cache = axObjectCache())
2794 addChild(cache->getOrCreate(*widget));
2795}
2796
2797#if USE(ATSPI)(defined USE_ATSPI && USE_ATSPI)
2798// FIXME: Consider removing these ATSPI-only functions with https://bugs.webkit.org/show_bug.cgi?id=282117.
2799void AccessibilityRenderObject::addCanvasChildren()
2800{
2801 // Add the unrendered canvas children as AX nodes, unless we're not using a canvas renderer
2802 // because JS is disabled for example.
2803 if (elementName() != ElementName::HTML_canvas || (renderer() && !renderer()->isRenderHTMLCanvas()))
2804 return;
2805
2806 // If it's a canvas, it won't have rendered children, but it might have accessible fallback content.
2807 // Clear m_childrenInitialized because AccessibilityNodeObject::addChildren will expect it to be false.
2808 AX_ASSERT(!m_children.size())((void)0);
2809 m_childrenInitialized = false;
2810 AccessibilityNodeObject::addChildren();
2811}
2812
2813// Some elements don't have an associated render object, meaning they won't be picked up by a walk of the render tree.
2814// For example, elements with `display: contents`, or aria-hidden=true elements that are focused.
2815// This function will find and add these elements to the AX tree.
2816void AccessibilityRenderObject::addNodeOnlyChildren()
2817{
2818 RefPtr node = this->node();
2819 if (!node)
2820 return;
2821
2822 auto nodeHasDisplayContents = [] (Node& node) {
2823 RefPtr element = dynamicDowncast<Element>(node);
2824 return element && element->hasDisplayContents();
2825 };
2826 // First do a quick run through to determine if we have any interesting nodes (most often we will not).
2827 // If we do have any interesting nodes, we need to determine where to insert them so they match DOM order as close as possible.
2828 bool hasNodeOnlyChildren = false;
2829 for (RefPtr child = node->firstChild(); child; child = child->nextSibling()) {
2830 if (child->renderer())
2831 continue;
2832
2833 if (nodeHasDisplayContents(*child) || isNodeFocused(*child)) {
2834 hasNodeOnlyChildren = true;
2835 break;
2836 }
2837 }
2838
2839 if (!hasNodeOnlyChildren)
2840 return;
2841
2842 WeakPtr cache = axObjectCache();
2843 if (!cache)
2844 return;
2845 // FIXME: This algorithm does not work correctly when ENABLE(INCLUDE_IGNORED_IN_CORE_AX_TREE) due to use of m_children, as this algorithm is written assuming m_children only every contains unignored objects.
2846 // Iterate through all of the children, including those that may have already been added, and
2847 // try to insert the nodes in the correct place in the DOM order.
2848 unsigned insertionIndex = 0;
2849 for (RefPtr child = node->firstChild(); child; child = child->nextSibling()) {
2850 if (child->renderer()) {
2851 // Find out where the last render sibling is located within m_children.
2852 RefPtr<AXCoreObject> childObject = cache->get(child->renderer());
2853 if (childObject && childObject->isIgnored()) {
2854 const auto& children = childObject->unignoredChildren();
2855 if (children.size())
2856 childObject = children.last().ptr();
2857 else
2858 childObject = nullptr;
2859 }
2860
2861 if (childObject) {
2862 insertionIndex = m_children.findIf([&childObject] (const Ref<AXCoreObject>& child) {
2863 return child.ptr() == childObject;
2864 });
2865 ++insertionIndex;
2866 }
2867 continue;
2868 }
2869
2870 if (!nodeHasDisplayContents(*child))
2871 continue;
2872
2873 unsigned previousSize = m_children.size();
2874 if (insertionIndex > previousSize)
2875 insertionIndex = previousSize;
2876
2877 insertChild(cache->getOrCreate(*child), insertionIndex);
2878 insertionIndex += (m_children.size() - previousSize);
2879 }
2880}
2881#endif // USE(ATSPI)
2882
2883#if PLATFORM(MAC)(defined WTF_PLATFORM_MAC && WTF_PLATFORM_MAC)
2884void AccessibilityRenderObject::updateAttachmentViewParents()
2885{
2886 // updateChildrenIfNeeded == false because this is called right after we've added children, so we know
2887 // they're clean and don't need updating.
2888 for (const auto& child : children(/* updateChildrenIfNeeded */ false))
2889 downcast<AccessibilityObject>(child)->overrideAttachmentParent(this);
2890}
2891#endif // PLATFORM(MAC)
2892
2893RenderObject* AccessibilityRenderObject::markerRenderer() const
2894{
2895 CheckedPtr renderListItem = dynamicDowncast<RenderListItem>(m_renderer.get());
2896 if (!renderListItem || !isListItem() || isIgnored())
2897 return nullptr;
2898
2899 return renderListItem->markerRenderer();
2900}
2901
2902void AccessibilityRenderObject::updateRoleAfterChildrenCreation()
2903{
2904 // If a menu does not have valid menuitem children, it should not be exposed as a menu.
2905 auto role = this->role();
2906 if (role == AccessibilityRole::Menu) {
2907 // Elements marked as menus must have at least one menu item child.
2908 bool hasMenuItemDescendant = false;
2909 for (const auto& child : unignoredChildren()) {
2910 if (child->isMenuItem()) {
2911 hasMenuItemDescendant = true;
2912 break;
2913 }
2914
2915 // Per the ARIA spec, groups with menuitem children are allowed as children of menus.
2916 // https://w3c.github.io/aria/#menu
2917 if (child->isGroup()) {
2918 for (const auto& grandchild : child->unignoredChildren()) {
2919 if (grandchild->isMenuItem()) {
2920 hasMenuItemDescendant = true;
2921 break;
2922 }
2923 }
2924 }
2925 }
2926
2927 if (!hasMenuItemDescendant)
2928 m_role = AccessibilityRole::Generic;
2929 }
2930 if (role == AccessibilityRole::SVGRoot && unignoredChildren().isEmpty())
2931 m_role = AccessibilityRole::Image;
2932
2933 if (isAccessibilityList()) {
2934 updateRole();
2935 return;
2936 }
2937
2938 if (role != m_role) {
2939 if (CheckedPtr cache = axObjectCache())
2940 cache->handleRoleChanged(*this, role);
2941 }
2942}
2943
2944void AccessibilityRenderObject::addChildren()
2945{
2946 if (!renderer()) [[unlikely]] {
2947 AccessibilityNodeObject::addChildren();
2948 return;
2949 }
2950 // If the need to add more children in addition to existing children arises,
2951 // childrenChanged should have been called, leaving the object with no children.
2952 AX_ASSERT(!m_childrenInitialized)((void)0);
2953 m_childrenInitialized = true;
2954
2955 auto scopeExit = makeScopeExit([this, protectedThis = Ref { *this }] {
2956 m_subtreeDirty = false;
2957 if (isNativeLabel())
2958 m_containsOnlyStaticTextDirty = true;
2959#ifndef NDEBUG1
2960 verifyChildrenIndexInParent();
2961#endif
2962 });
2963
2964#if !ENABLE(INCLUDE_IGNORED_IN_CORE_AX_TREE)(defined ENABLE_INCLUDE_IGNORED_IN_CORE_AX_TREE && ENABLE_INCLUDE_IGNORED_IN_CORE_AX_TREE
)
2965 if (isExposableTable()) {
2966 // See comment in AccessibilityNodeObject::addChildren() explaing this #if ... branch.
2967 addTableChildrenAndCellSlots();
2968 return;
2969 }
2970#endif
2971
2972 auto addChildIfNeeded = [this](AccessibilityObject& object) {
2973#if USE(ATSPI)(defined USE_ATSPI && USE_ATSPI)
2974 // FIXME: Consider removing this ATSPI-only branch with https://bugs.webkit.org/show_bug.cgi?id=282117.
2975 if (object.renderer() && object.renderer()->isRenderListMarker())
2976 return;
2977#endif
2978 addChild(object);
2979 };
2980
2981 WeakPtr cache = axObjectCache();
2982 auto addListItemMarker = [&] () {
2983 if (CheckedPtr marker = markerRenderer(); marker && cache)
2984 addChild(cache->getOrCreate(*marker));
2985 };
2986
2987 auto addListBoxChildrenIfNecessary = [&](Node& node) -> bool {
2988 if (role() == AccessibilityRole::ListBox) {
2989 if (RefPtr selectElement = dynamicDowncast<HTMLSelectElement>(node)) {
2990 for (const auto& listItem : selectElement->listItems())
2991 addChild(cache->getOrCreate(listItem.get()), AccessibilityObject::DescendIfIgnored::No);
2992 return true;
2993 }
2994 }
2995
2996 return false;
2997 };
2998
2999 RefPtr node = dynamicDowncast<ContainerNode>(this->node());
3000
3001#if !USE(ATSPI)(defined USE_ATSPI && USE_ATSPI)
3002 // Non-ATSPI platforms walk the DOM to build the accessibility tree.
3003 // Ideally this would be the case for all platforms, but there are GLib tests that rely on anonymous renderers
3004 // being part of the accessibility tree.
3005 RefPtr element = dynamicDowncast<Element>(node);
3006
3007 // ::before and ::after pseudos should be the first and last children of the element
3008 // that generates them (rather than being siblings to the generating element).
3009 if (RefPtr beforePseudo = element ? element->beforePseudoElement() : nullptr) {
3010 if (RefPtr pseudoObject = cache ? cache->getOrCreate(*beforePseudo) : nullptr)
3011 addChildIfNeeded(*pseudoObject);
3012 }
3013
3014 // If |this| has an associated list marker, it should be the first child (or second if |this| has a ::before pseudo).
3015 addListItemMarker();
3016
3017 if (node && !(element && element->isPseudoElement()) && cache) {
3018 if (addListBoxChildrenIfNecessary(*node))
3019 return;
3020
3021 // If we have a DOM node, use the DOM to find accessible children.
3022 //
3023 // The ComposedTreeIterator is extremely large by default, and will cause a stack
3024 // overflow when building the accessibility tree from a deep DOM. Specify that we
3025 // do not want its internal vector to allocate any space on the stack, in turn
3026 // ensuring its contents go on the heap. We should consider rewriting our algorithm
3027 // to build the accessibility tree to be iterative rather than recursive.
3028 for (Ref child : composedTreeChildren</* InlineContextCapacity */ 0>(*node)) {
3029 if (RefPtr childObject = cache->getOrCreate(child.get()))
3030 addChildIfNeeded(*childObject);
3031 }
3032 } else {
3033 if (CheckedPtr blockRenderer = dynamicDowncast<RenderBlock>(m_renderer.get()); blockRenderer && blockRenderer->isAnonymousBlock())
3034 return;
3035 // If we are a valid anonymous renderer (pseudo-element, list marker), use
3036 // AXChildIterator to walk the render tree / DOM (we may walk between the
3037 // two — reference AccessibilityObject::iterator documentation for more information).
3038 for (Ref object : AXChildIterator(*this))
3039 addChildIfNeeded(WTF::move(object));
3040 }
3041
3042 if (RefPtr afterPseudo = element ? element->afterPseudoElement() : nullptr) {
3043 if (RefPtr pseudoObject = cache ? cache->getOrCreate(*afterPseudo) : nullptr)
3044 addChildIfNeeded(*pseudoObject);
3045 }
3046#else
3047 // USE(ATPSI) within this block. Walk the render tree (primarily -- see comments for AccessibilityObject::iterator)
3048 addListItemMarker();
3049 // to build the accessibility tree.
3050 // FIXME: Consider removing this ATSPI-only branch with https://bugs.webkit.org/show_bug.cgi?id=282117.
3051 if (node && addListBoxChildrenIfNecessary(*node))
3052 return;
3053
3054 for (auto& object : AXChildIterator(*this))
3055 addChildIfNeeded(object);
3056
3057 addNodeOnlyChildren();
3058 addCanvasChildren();
3059#endif // !USE(ATSPI)
3060
3061 addAttachmentChildren();
3062 addImageMapChildren();
3063 addTextFieldChildren();
3064 addRemoteSVGChildren();
3065#if PLATFORM(MAC)(defined WTF_PLATFORM_MAC && WTF_PLATFORM_MAC)
3066 updateAttachmentViewParents();
3067#endif
3068 updateOwnedChildrenIfNecessary();
3069
3070 // Handle aria-colindex for all table rows (whether using owned objects or not).
3071 if (isExposedTableRow()) {
3072 if (std::optional colIndex = axColumnIndex()) {
3073 unsigned index = 0;
3074 for (const auto& child : unignoredChildren()) {
3075 if (RefPtr rowChild = dynamicDowncast<AccessibilityNodeObject>(child); rowChild && rowChild->isTableCell())
3076 rowChild->setAXColIndexFromRow(*colIndex + index);
3077 index++;
3078 }
3079 }
3080 }
3081
3082 updateRoleAfterChildrenCreation();
3083
3084#if ENABLE(INCLUDE_IGNORED_IN_CORE_AX_TREE)(defined ENABLE_INCLUDE_IGNORED_IN_CORE_AX_TREE && ENABLE_INCLUDE_IGNORED_IN_CORE_AX_TREE
)
3085 if (isExposableTable())
3086 addTableChildrenAndCellSlots();
3087#endif
3088}
3089
3090void AccessibilityRenderObject::setAccessibleName(const AtomString& name)
3091{
3092 // Setting the accessible name can store the value in the DOM
3093 if (!m_renderer)
3094 return;
3095
3096 Node* node = nullptr;
3097 // For web areas, set the aria-label on the HTML element.
3098 if (isWebArea())
3099 node = m_renderer->document().documentElement();
3100 else
3101 node = m_renderer->node();
3102
3103 if (RefPtr element = dynamicDowncast<Element>(node))
3104 element->setAttribute(aria_labelAttr, name);
3105}
3106
3107bool AccessibilityRenderObject::hasBoldFont() const
3108{
3109 if (!m_renderer)
3110 return false;
3111
3112 return isFontWeightBold(m_renderer->style().fontDescription().weight());
3113}
3114
3115bool AccessibilityRenderObject::hasItalicFont() const
3116{
3117 if (!m_renderer)
3118 return false;
3119
3120 return m_renderer->style().fontStyle().isConsideredItalic();
3121}
3122
3123bool AccessibilityRenderObject::hasPlainText() const
3124{
3125 if (!m_renderer)
3126 return false;
3127
3128 if (!canHavePlainText())
3129 return false;
3130
3131 CheckedRef style = m_renderer->style();
3132 return style->fontWeight().isNormal()
3133 && !style->fontStyle().isConsideredItalic()
3134 && style->textDecorationLineInEffect().isNone();
3135}
3136
3137bool AccessibilityRenderObject::hasSameFont(AXCoreObject& object)
3138{
3139 CheckedPtr renderer = object.renderer();
3140 if (!m_renderer || !renderer)
3141 return false;
3142
3143 return m_renderer->style().fontDescription().families() == renderer->style().fontDescription().families();
3144}
3145
3146#if ENABLE(APPLE_PAY)(defined 1 && 1)
3147bool AccessibilityRenderObject::isApplePayButton() const
3148{
3149 if (!m_renderer)
3150 return false;
3151 return m_renderer->style().usedAppearance() == StyleAppearance::ApplePayButton;
3152}
3153
3154ApplePayButtonType AccessibilityRenderObject::applePayButtonType() const
3155{
3156 if (CheckedPtr renderElement = dynamicDowncast<RenderElement>(renderer()))
3157 return renderElement->style().applePayButtonType();
3158 return ApplePayButtonType::Plain;
3159}
3160#endif
3161
3162bool AccessibilityRenderObject::hasSameFontColor(AXCoreObject& object)
3163{
3164 CheckedPtr renderer = object.renderer();
3165 if (!m_renderer || !renderer)
3166 return false;
3167
3168 return m_renderer->style().visitedDependentColor() == renderer->style().visitedDependentColor();
3169}
3170
3171bool AccessibilityRenderObject::hasSameStyle(AXCoreObject& object)
3172{
3173 CheckedPtr renderer = object.renderer();
3174 if (!m_renderer || !renderer)
3175 return false;
3176
3177 return m_renderer->style() == renderer->style();
3178}
3179
3180bool AccessibilityRenderObject::hasUnderline() const
3181{
3182 if (!m_renderer)
3183 return false;
3184
3185 return m_renderer->style().textDecorationLineInEffect().hasUnderline();
3186}
3187
3188String AccessibilityRenderObject::secureFieldValue() const
3189{
3190 AX_ASSERT(isSecureField())((void)0);
3191
3192 // Look for the RenderText object in the RenderObject tree for this input field.
3193 CheckedPtr renderer = node()->renderer();
3194 while (renderer && !is<RenderText>(*renderer))
3195 renderer = uncheckedDowncast<RenderElement>(*renderer).firstChild();
3196
3197 CheckedPtr renderText = dynamicDowncast<RenderText>(renderer.get());
3198 // Return the text that is actually being rendered in the input field.
3199 return renderText ? renderText->textWithoutConvertingBackslashToYenSymbol() : String();
3200}
3201
3202ScrollableArea* AccessibilityRenderObject::getScrollableAreaIfScrollable() const
3203{
3204 // If the parent is a scroll view, then this object isn't really scrollable, the parent ScrollView should handle the scrolling.
3205 if (RefPtr parent = parentObject()) {
3206 if (parent->isScrollArea())
3207 return nullptr;
3208 }
3209
3210 CheckedPtr box = dynamicDowncast<RenderBox>(renderer());
3211 if (!box || !box->canBeScrolledAndHasScrollableArea() || !box->layer())
3212 return nullptr;
3213
3214 return box->layer()->scrollableArea();
3215}
3216
3217void AccessibilityRenderObject::scrollTo(const IntPoint& point) const
3218{
3219 CheckedPtr box = dynamicDowncast<RenderBox>(renderer());
3220 if (!box || !box->canBeScrolledAndHasScrollableArea())
3221 return;
3222
3223 // FIXME: is point a ScrollOffset or ScrollPosition? Test in RTL overflow.
3224 AX_ASSERT(box->layer())((void)0);
3225 AX_ASSERT(box->layer()->scrollableArea())((void)0);
3226 box->layer()->scrollableArea()->scrollToOffset(point);
3227}
3228
3229FloatRect AccessibilityRenderObject::localRect() const
3230{
3231 CheckedPtr renderer = this->renderer();
3232 if (CheckedPtr box = dynamicDowncast<RenderBox>(renderer.get()))
3233 return box ? convertFrameToSpace(box->frameRect(), AccessibilityConversionSpace::Page) : FloatRect();
3234
3235 CheckedPtr renderText = dynamicDowncast<RenderText>(renderer.get());
3236 return renderText ? renderText->linesBoundingBox() : FloatRect();
3237}
3238
3239#if ENABLE(MATHML)(defined 1 && 1)
3240bool AccessibilityRenderObject::isIgnoredElementWithinMathTree() const
3241{
3242 // We ignore anonymous boxes inserted into RenderMathMLBlocks to honor CSS rules.
3243 // See https://www.w3.org/TR/css3-box/#block-level0
3244 return m_renderer && m_renderer->isAnonymous() && m_renderer->parent() && is<RenderMathMLBlock>(m_renderer->parent());
3245}
3246#endif
3247
3248#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
3249String AccessibilityRenderObject::interactiveVideoDuration() const
3250{
3251 return AccessibilityMediaHelpers::interactiveVideoDuration(mediaElement());
3252}
3253
3254void AccessibilityRenderObject::toggleMute()
3255{
3256 AccessibilityMediaHelpers::toggleMute(mediaElement());
3257}
3258
3259bool AccessibilityRenderObject::isPlaying() const
3260{
3261 return AccessibilityMediaHelpers::isPlaying(mediaElement());
3262}
3263
3264bool AccessibilityRenderObject::isMuted() const
3265{
3266 return AccessibilityMediaHelpers::isMuted(mediaElement());
3267}
3268
3269bool AccessibilityRenderObject::isMediaObject() const
3270{
3271 return is<HTMLMediaElement>(node());
3272}
3273
3274bool AccessibilityRenderObject::isAutoplayEnabled() const
3275{
3276 return AccessibilityMediaHelpers::isAutoplayEnabled(mediaElement());
3277}
3278
3279void AccessibilityRenderObject::enterFullscreen() const
3280{
3281 AccessibilityMediaHelpers::enterFullscreen(protect(videoElement()));
3282}
3283#endif // PLATFORM(IOS_FAMILY)
3284
3285} // namespace WebCore