Bug Summary

File:Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/accessibility/AccessibilityRenderObject.cpp
Warning:line 3270, column 48
Call argument uses a forward declared type 'HTMLVideoElement *'

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