| 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 *' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 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 | |
| 151 | namespace WebCore { |
| 152 | |
| 153 | using namespace HTMLNames; |
| 154 | |
| 155 | static Node* nodeForRenderer(RenderObject& renderer) |
| 156 | { |
| 157 | if (!renderer.isRenderView()) [[likely]] |
| 158 | return renderer.node(); |
| 159 | return &renderer.document(); |
| 160 | } |
| 161 | |
| 162 | AccessibilityRenderObject::AccessibilityRenderObject(AXID axID, RenderObject& renderer, AXObjectCache& cache) |
| 163 | : AccessibilityNodeObject(axID, nodeForRenderer(renderer), cache) |
| 164 | , m_renderer(renderer) |
| 165 | { |
| 166 | } |
| 167 | |
| 168 | AccessibilityRenderObject::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 | |
| 175 | AccessibilityRenderObject::~AccessibilityRenderObject() |
| 176 | { |
| 177 | AX_ASSERT(isDetached())((void)0); |
| 178 | } |
| 179 | |
| 180 | Ref<AccessibilityRenderObject> AccessibilityRenderObject::create(AXID axID, RenderObject& renderer, AXObjectCache& cache) |
| 181 | { |
| 182 | return adoptRef(*new AccessibilityRenderObject(axID, renderer, cache)); |
| 183 | } |
| 184 | |
| 185 | Ref<AccessibilityRenderObject> AccessibilityRenderObject::create(AXID axID, Node& node, AXObjectCache& cache) |
| 186 | { |
| 187 | return adoptRef(*new AccessibilityRenderObject(axID, node, cache)); |
| 188 | } |
| 189 | |
| 190 | void AccessibilityRenderObject::detachRemoteParts(AccessibilityDetachmentType detachmentType) |
| 191 | { |
| 192 | AccessibilityNodeObject::detachRemoteParts(detachmentType); |
| 193 | |
| 194 | detachRemoteSVGRoot(); |
| 195 | m_renderer = nullptr; |
| 196 | } |
| 197 | |
| 198 | static inline bool isInlineWithContinuation(RenderObject& renderer) |
| 199 | { |
| 200 | auto* renderInline = dynamicDowncast<RenderInline>(renderer); |
| 201 | return renderInline && renderInline->continuation(); |
| 202 | } |
| 203 | |
| 204 | static 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 | |
| 220 | static 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 | |
| 240 | static 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 | |
| 257 | AccessibilityObject* 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 | |
| 277 | AccessibilityObject* 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 | |
| 293 | static 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 | |
| 308 | static 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 | |
| 323 | static 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 | |
| 350 | static inline bool firstChildIsInlineContinuation(RenderElement& renderer) |
| 351 | { |
| 352 | CheckedPtr renderInline = dynamicDowncast<RenderInline>(renderer.firstChild()); |
| 353 | return renderInline && renderInline->isContinuation(); |
| 354 | } |
| 355 | |
| 356 | AccessibilityObject* 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 | |
| 393 | static inline bool lastChildHasContinuation(RenderElement& renderer) |
| 394 | { |
| 395 | CheckedPtr child = renderer.lastChild(); |
| 396 | return child && isInlineWithContinuation(*child); |
| 397 | } |
| 398 | |
| 399 | AccessibilityObject* 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 | |
| 477 | static 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 | |
| 488 | RenderObject* 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 | |
| 531 | AccessibilityObject* 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 | |
| 588 | AccessibilityObject* AccessibilityRenderObject::crossFrameParentObject() const |
| 589 | { |
| 590 | return nullptr; |
| 591 | } |
| 592 | |
| 593 | AccessibilityObject* AccessibilityRenderObject::crossFrameChildObject() const |
| 594 | { |
| 595 | return nullptr; |
| 596 | } |
| 597 | |
| 598 | #endif // ENABLE_ACCESSIBILITY_LOCAL_FRAME |
| 599 | |
| 600 | bool 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) |
| 614 | bool AccessibilityRenderObject::isAttachmentElement() const |
| 615 | { |
| 616 | return is<HTMLAttachmentElement>(node()); |
| 617 | } |
| 618 | #endif |
| 619 | |
| 620 | bool 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 | |
| 632 | Element* 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 | |
| 669 | String 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 | |
| 751 | bool 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 | |
| 780 | String 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 | |
| 864 | bool 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. |
| 871 | void 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 | |
| 881 | LayoutRect 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 | |
| 948 | bool AccessibilityRenderObject::isNonLayerSVGObject() const |
| 949 | { |
| 950 | auto* renderer = this->renderer(); |
| 951 | return renderer ? is<RenderSVGInlineText>(renderer) || is<LegacyRenderSVGModelObject>(renderer) : false; |
| 952 | } |
| 953 | |
| 954 | bool AccessibilityRenderObject::supportsPath() const |
| 955 | { |
| 956 | return is<RenderText>(renderer()) || (renderer() && renderer()->isRenderOrLegacyRenderSVGShape()); |
| 957 | } |
| 958 | |
| 959 | Path 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) |
| 1037 | String 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 | |
| 1076 | void 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 | |
| 1087 | AccessibilityObject* 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 | |
| 1131 | bool 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 | |
| 1156 | AccessibilityObject* AccessibilityRenderObject::containingTree() const |
| 1157 | { |
| 1158 | return Accessibility::findAncestor<AccessibilityObject>(*this, false, [] (const auto& ancestor) { |
| 1159 | return ancestor.isTree(); |
| 1160 | }); |
| 1161 | } |
| 1162 | |
| 1163 | static 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 | |
| 1177 | static 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 | |
| 1186 | bool 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 | |
| 1539 | int AccessibilityRenderObject::layoutCount() const |
| 1540 | { |
| 1541 | CheckedPtr view = dynamicDowncast<RenderView>(m_renderer.get()); |
| 1542 | return view ? view->frameView().layoutUpdateCount() : 0; |
| 1543 | } |
| 1544 | |
| 1545 | CharacterRange 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 | |
| 1560 | String 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 | |
| 1575 | CharacterRange 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 ) |
| 1589 | AXTextRuns 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 | |
| 1799 | AXTextRunLineID AccessibilityRenderObject::listMarkerLineID() const |
| 1800 | { |
| 1801 | AX_ASSERT(role() == AccessibilityRole::ListMarker)((void)0); |
| 1802 | return { renderer() ? renderer()->containingBlock() : nullptr, 0 }; |
| 1803 | } |
| 1804 | |
| 1805 | String AccessibilityRenderObject::listMarkerText() const |
| 1806 | { |
| 1807 | CheckedPtr marker = dynamicDowncast<RenderListMarker>(renderer()); |
| 1808 | return marker ? marker->textWithSuffix() : String(); |
| 1809 | } |
| 1810 | #endif // ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| 1811 | |
| 1812 | int 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 | |
| 1842 | static 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 | |
| 1851 | static void clearTextSelectionIntent(AXObjectCache* cache) |
| 1852 | { |
| 1853 | if (!cache) |
| 1854 | return; |
| 1855 | cache->setTextSelectionIntent(AXTextStateChangeIntent()); |
| 1856 | cache->setIsSynchronizingSelection(false); |
| 1857 | } |
| 1858 | |
| 1859 | void 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 | |
| 1891 | URL AccessibilityRenderObject::url() const |
| 1892 | { |
| 1893 | if (m_renderer && isWebArea()) |
| 1894 | return m_renderer->document().url(); |
| 1895 | return AccessibilityNodeObject::url(); |
| 1896 | } |
| 1897 | |
| 1898 | bool 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 | |
| 1932 | bool 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 | |
| 1944 | Document* AccessibilityRenderObject::document() const |
| 1945 | { |
| 1946 | if (!m_renderer) |
| 1947 | return AccessibilityNodeObject::document(); |
| 1948 | return &m_renderer->document(); |
| 1949 | } |
| 1950 | |
| 1951 | bool AccessibilityRenderObject::isWidget() const |
| 1952 | { |
| 1953 | return widget(); |
| 1954 | } |
| 1955 | |
| 1956 | Widget* AccessibilityRenderObject::widget() const |
| 1957 | { |
| 1958 | RefPtr renderWidget = dynamicDowncast<RenderWidget>(renderer()); |
| 1959 | return renderWidget ? renderWidget->widget() : nullptr; |
| 1960 | } |
| 1961 | |
| 1962 | AccessibilityObject* AccessibilityRenderObject::associatedImageObject(HTMLMapElement& map) const |
| 1963 | { |
| 1964 | CheckedPtr cache = axObjectCache(); |
| 1965 | return cache ? cache->getOrCreate(map.imageElement().get()) : nullptr; |
| 1966 | } |
| 1967 | |
| 1968 | AXCoreObject::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 | |
| 2003 | LocalFrameView* AccessibilityRenderObject::documentFrameView() const |
| 2004 | { |
| 2005 | if (!m_renderer) |
| 2006 | return AccessibilityNodeObject::documentFrameView(); |
| 2007 | |
| 2008 | return &m_renderer->view().frameView(); |
| 2009 | } |
| 2010 | |
| 2011 | Widget* 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 | |
| 2019 | VisiblePosition 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 | |
| 2033 | int 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 | |
| 2045 | RefPtr<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 | |
| 2060 | bool 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 | |
| 2067 | bool 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 | |
| 2084 | void 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 |
| 2159 | VisiblePosition 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 |
| 2175 | int 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 | |
| 2186 | static 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. |
| 2212 | CharacterRange 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. |
| 2244 | CharacterRange 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. |
| 2257 | String 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. |
| 2267 | IntRect AccessibilityRenderObject::doAXBoundsForRange(const CharacterRange& range) const |
| 2268 | { |
| 2269 | if (allowsTextRanges()) |
| 2270 | return boundsForVisiblePositionRange(visiblePositionRangeForRange(range)); |
| 2271 | return IntRect(); |
| 2272 | } |
| 2273 | |
| 2274 | IntRect 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 | |
| 2284 | AccessibilityObject* 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 | |
| 2299 | RefPtr<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 | |
| 2309 | RefPtr<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 | |
| 2340 | RefPtr<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 | |
| 2393 | bool 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 | |
| 2419 | AccessibilityObject* 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 | |
| 2436 | bool AccessibilityRenderObject::shouldIgnoreAttributeRole() const |
| 2437 | { |
| 2438 | if (hasTreeItemRole()) |
| 2439 | return hasRareData() && !rareData()->isTreeItemValid(); |
| 2440 | |
| 2441 | return m_ariaRole == AccessibilityRole::Document && hasContentEditableAttributeSet(); |
| 2442 | } |
| 2443 | |
| 2444 | AccessibilityRole 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 | |
| 2585 | std::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 | |
| 2617 | bool 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 | |
| 2665 | void 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 | |
| 2686 | void 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 | |
| 2701 | bool AccessibilityRenderObject::isSVGImage() const |
| 2702 | { |
| 2703 | return remoteSVGRootElement(CreateIfNecessary::Yes); |
| 2704 | } |
| 2705 | |
| 2706 | void AccessibilityRenderObject::detachRemoteSVGRoot() |
| 2707 | { |
| 2708 | if (RefPtr root = remoteSVGRootElement(CreateIfNecessary::No)) |
| 2709 | root->setParent(nullptr); |
| 2710 | } |
| 2711 | |
| 2712 | AccessibilitySVGObject* 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 | |
| 2753 | void 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 | |
| 2772 | void 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. |
| 2788 | void 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. |
| 2805 | void 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) |
| 2873 | void 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 | |
| 2882 | RenderObject* 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 | |
| 2891 | void 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 | |
| 2933 | void 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 | |
| 3079 | void 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 | |
| 3096 | bool AccessibilityRenderObject::hasBoldFont() const |
| 3097 | { |
| 3098 | if (!m_renderer) |
| 3099 | return false; |
| 3100 | |
| 3101 | return isFontWeightBold(m_renderer->style().fontDescription().weight()); |
| 3102 | } |
| 3103 | |
| 3104 | bool AccessibilityRenderObject::hasItalicFont() const |
| 3105 | { |
| 3106 | if (!m_renderer) |
| 3107 | return false; |
| 3108 | |
| 3109 | return m_renderer->style().fontStyle().isConsideredItalic(); |
| 3110 | } |
| 3111 | |
| 3112 | bool 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 | |
| 3126 | bool 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) |
| 3136 | bool AccessibilityRenderObject::isApplePayButton() const |
| 3137 | { |
| 3138 | if (!m_renderer) |
| 3139 | return false; |
| 3140 | return m_renderer->style().usedAppearance() == StyleAppearance::ApplePayButton; |
| 3141 | } |
| 3142 | |
| 3143 | ApplePayButtonType AccessibilityRenderObject::applePayButtonType() const |
| 3144 | { |
| 3145 | if (CheckedPtr renderElement = dynamicDowncast<RenderElement>(renderer())) |
| 3146 | return renderElement->style().applePayButtonType(); |
| 3147 | return ApplePayButtonType::Plain; |
| 3148 | } |
| 3149 | #endif |
| 3150 | |
| 3151 | bool 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 | |
| 3160 | bool 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 | |
| 3169 | bool AccessibilityRenderObject::hasUnderline() const |
| 3170 | { |
| 3171 | if (!m_renderer) |
| 3172 | return false; |
| 3173 | |
| 3174 | return m_renderer->style().textDecorationLineInEffect().hasUnderline(); |
| 3175 | } |
| 3176 | |
| 3177 | String 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 | |
| 3191 | ScrollableArea* 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 | |
| 3206 | void 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 | |
| 3218 | FloatRect 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) |
| 3229 | bool 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) |
| 3238 | String AccessibilityRenderObject::interactiveVideoDuration() const |
| 3239 | { |
| 3240 | return AccessibilityMediaHelpers::interactiveVideoDuration(mediaElement()); |
| 3241 | } |
| 3242 | |
| 3243 | void AccessibilityRenderObject::toggleMute() |
| 3244 | { |
| 3245 | AccessibilityMediaHelpers::toggleMute(mediaElement()); |
| 3246 | } |
| 3247 | |
| 3248 | bool AccessibilityRenderObject::isPlaying() const |
| 3249 | { |
| 3250 | return AccessibilityMediaHelpers::isPlaying(mediaElement()); |
| 3251 | } |
| 3252 | |
| 3253 | bool AccessibilityRenderObject::isMuted() const |
| 3254 | { |
| 3255 | return AccessibilityMediaHelpers::isMuted(mediaElement()); |
| 3256 | } |
| 3257 | |
| 3258 | bool AccessibilityRenderObject::isMediaObject() const |
| 3259 | { |
| 3260 | return is<HTMLMediaElement>(node()); |
| 3261 | } |
| 3262 | |
| 3263 | bool AccessibilityRenderObject::isAutoplayEnabled() const |
| 3264 | { |
| 3265 | return AccessibilityMediaHelpers::isAutoplayEnabled(mediaElement()); |
| 3266 | } |
| 3267 | |
| 3268 | void 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 |