| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2008 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 Computer, 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 "core/accessibility/AXRenderObject.h" | |
| 31 | |
| 32 #include "bindings/core/v8/ExceptionStatePlaceholder.h" | |
| 33 #include "core/InputTypeNames.h" | |
| 34 #include "core/accessibility/AXImageMapLink.h" | |
| 35 #include "core/accessibility/AXInlineTextBox.h" | |
| 36 #include "core/accessibility/AXObjectCacheImpl.h" | |
| 37 #include "core/accessibility/AXSVGRoot.h" | |
| 38 #include "core/accessibility/AXSpinButton.h" | |
| 39 #include "core/accessibility/AXTable.h" | |
| 40 #include "core/dom/ElementTraversal.h" | |
| 41 #include "core/dom/shadow/ShadowRoot.h" | |
| 42 #include "core/editing/FrameSelection.h" | |
| 43 #include "core/editing/RenderedPosition.h" | |
| 44 #include "core/editing/TextIterator.h" | |
| 45 #include "core/editing/VisibleUnits.h" | |
| 46 #include "core/editing/htmlediting.h" | |
| 47 #include "core/frame/LocalFrame.h" | |
| 48 #include "core/frame/Settings.h" | |
| 49 #include "core/html/HTMLImageElement.h" | |
| 50 #include "core/html/HTMLLabelElement.h" | |
| 51 #include "core/html/HTMLOptionElement.h" | |
| 52 #include "core/html/HTMLSelectElement.h" | |
| 53 #include "core/html/HTMLTextAreaElement.h" | |
| 54 #include "core/html/shadow/ShadowElementNames.h" | |
| 55 #include "core/loader/ProgressTracker.h" | |
| 56 #include "core/page/Page.h" | |
| 57 #include "core/rendering/HitTestResult.h" | |
| 58 #include "core/rendering/RenderFieldset.h" | |
| 59 #include "core/rendering/RenderFileUploadControl.h" | |
| 60 #include "core/rendering/RenderHTMLCanvas.h" | |
| 61 #include "core/rendering/RenderImage.h" | |
| 62 #include "core/rendering/RenderInline.h" | |
| 63 #include "core/rendering/RenderLayer.h" | |
| 64 #include "core/rendering/RenderListMarker.h" | |
| 65 #include "core/rendering/RenderMenuList.h" | |
| 66 #include "core/rendering/RenderPart.h" | |
| 67 #include "core/rendering/RenderTextControlSingleLine.h" | |
| 68 #include "core/rendering/RenderTextFragment.h" | |
| 69 #include "core/rendering/RenderView.h" | |
| 70 #include "core/svg/SVGDocumentExtensions.h" | |
| 71 #include "core/svg/SVGSVGElement.h" | |
| 72 #include "core/svg/graphics/SVGImage.h" | |
| 73 #include "platform/text/PlatformLocale.h" | |
| 74 #include "wtf/StdLibExtras.h" | |
| 75 | |
| 76 using blink::WebLocalizedString; | |
| 77 | |
| 78 namespace blink { | |
| 79 | |
| 80 using namespace HTMLNames; | |
| 81 | |
| 82 static inline RenderObject* firstChildInContinuation(const RenderInline& rendere
r) | |
| 83 { | |
| 84 RenderBoxModelObject* r = renderer.continuation(); | |
| 85 | |
| 86 while (r) { | |
| 87 if (r->isRenderBlock()) | |
| 88 return r; | |
| 89 if (RenderObject* child = r->slowFirstChild()) | |
| 90 return child; | |
| 91 r = toRenderInline(r)->continuation(); | |
| 92 } | |
| 93 | |
| 94 return 0; | |
| 95 } | |
| 96 | |
| 97 static inline bool isInlineWithContinuation(RenderObject* object) | |
| 98 { | |
| 99 if (!object->isBoxModelObject()) | |
| 100 return false; | |
| 101 | |
| 102 RenderBoxModelObject* renderer = toRenderBoxModelObject(object); | |
| 103 if (!renderer->isRenderInline()) | |
| 104 return false; | |
| 105 | |
| 106 return toRenderInline(renderer)->continuation(); | |
| 107 } | |
| 108 | |
| 109 static inline RenderObject* firstChildConsideringContinuation(RenderObject* rend
erer) | |
| 110 { | |
| 111 RenderObject* firstChild = renderer->slowFirstChild(); | |
| 112 | |
| 113 if (!firstChild && isInlineWithContinuation(renderer)) | |
| 114 firstChild = firstChildInContinuation(toRenderInline(*renderer)); | |
| 115 | |
| 116 return firstChild; | |
| 117 } | |
| 118 | |
| 119 static inline RenderInline* startOfContinuations(RenderObject* r) | |
| 120 { | |
| 121 if (r->isInlineElementContinuation()) { | |
| 122 return toRenderInline(r->node()->renderer()); | |
| 123 } | |
| 124 | |
| 125 // Blocks with a previous continuation always have a next continuation | |
| 126 if (r->isRenderBlock() && toRenderBlock(r)->inlineElementContinuation()) | |
| 127 return toRenderInline(toRenderBlock(r)->inlineElementContinuation()->nod
e()->renderer()); | |
| 128 | |
| 129 return 0; | |
| 130 } | |
| 131 | |
| 132 static inline RenderObject* endOfContinuations(RenderObject* renderer) | |
| 133 { | |
| 134 RenderObject* prev = renderer; | |
| 135 RenderObject* cur = renderer; | |
| 136 | |
| 137 if (!cur->isRenderInline() && !cur->isRenderBlock()) | |
| 138 return renderer; | |
| 139 | |
| 140 while (cur) { | |
| 141 prev = cur; | |
| 142 if (cur->isRenderInline()) { | |
| 143 cur = toRenderInline(cur)->inlineElementContinuation(); | |
| 144 ASSERT(cur || !toRenderInline(prev)->continuation()); | |
| 145 } else { | |
| 146 cur = toRenderBlock(cur)->inlineElementContinuation(); | |
| 147 } | |
| 148 } | |
| 149 | |
| 150 return prev; | |
| 151 } | |
| 152 | |
| 153 static inline bool lastChildHasContinuation(RenderObject* renderer) | |
| 154 { | |
| 155 RenderObject* lastChild = renderer->slowLastChild(); | |
| 156 return lastChild && isInlineWithContinuation(lastChild); | |
| 157 } | |
| 158 | |
| 159 static RenderBoxModelObject* nextContinuation(RenderObject* renderer) | |
| 160 { | |
| 161 ASSERT(renderer); | |
| 162 if (renderer->isRenderInline() && !renderer->isReplaced()) | |
| 163 return toRenderInline(renderer)->continuation(); | |
| 164 if (renderer->isRenderBlock()) | |
| 165 return toRenderBlock(renderer)->inlineElementContinuation(); | |
| 166 return 0; | |
| 167 } | |
| 168 | |
| 169 AXRenderObject::AXRenderObject(RenderObject* renderer) | |
| 170 : AXNodeObject(renderer->node()) | |
| 171 , m_renderer(renderer) | |
| 172 , m_cachedElementRectDirty(true) | |
| 173 { | |
| 174 #if ENABLE(ASSERT) | |
| 175 m_renderer->setHasAXObject(true); | |
| 176 #endif | |
| 177 } | |
| 178 | |
| 179 PassRefPtr<AXRenderObject> AXRenderObject::create(RenderObject* renderer) | |
| 180 { | |
| 181 return adoptRef(new AXRenderObject(renderer)); | |
| 182 } | |
| 183 | |
| 184 AXRenderObject::~AXRenderObject() | |
| 185 { | |
| 186 ASSERT(isDetached()); | |
| 187 } | |
| 188 | |
| 189 LayoutRect AXRenderObject::elementRect() const | |
| 190 { | |
| 191 if (!m_explicitElementRect.isEmpty()) | |
| 192 return m_explicitElementRect; | |
| 193 if (!m_renderer) | |
| 194 return LayoutRect(); | |
| 195 if (!m_renderer->isBox()) | |
| 196 return computeElementRect(); | |
| 197 | |
| 198 for (const AXObject* obj = this; obj; obj = obj->parentObject()) { | |
| 199 if (obj->isAXRenderObject()) | |
| 200 toAXRenderObject(obj)->checkCachedElementRect(); | |
| 201 } | |
| 202 for (const AXObject* obj = this; obj; obj = obj->parentObject()) { | |
| 203 if (obj->isAXRenderObject()) | |
| 204 toAXRenderObject(obj)->updateCachedElementRect(); | |
| 205 } | |
| 206 | |
| 207 return m_cachedElementRect; | |
| 208 } | |
| 209 | |
| 210 void AXRenderObject::setRenderer(RenderObject* renderer) | |
| 211 { | |
| 212 m_renderer = renderer; | |
| 213 setNode(renderer->node()); | |
| 214 } | |
| 215 | |
| 216 RenderBoxModelObject* AXRenderObject::renderBoxModelObject() const | |
| 217 { | |
| 218 if (!m_renderer || !m_renderer->isBoxModelObject()) | |
| 219 return 0; | |
| 220 return toRenderBoxModelObject(m_renderer); | |
| 221 } | |
| 222 | |
| 223 Document* AXRenderObject::topDocument() const | |
| 224 { | |
| 225 if (!document()) | |
| 226 return 0; | |
| 227 return &document()->topDocument(); | |
| 228 } | |
| 229 | |
| 230 bool AXRenderObject::shouldNotifyActiveDescendant() const | |
| 231 { | |
| 232 // We want to notify that the combo box has changed its active descendant, | |
| 233 // but we do not want to change the focus, because focus should remain with
the combo box. | |
| 234 if (isComboBox()) | |
| 235 return true; | |
| 236 | |
| 237 return shouldFocusActiveDescendant(); | |
| 238 } | |
| 239 | |
| 240 ScrollableArea* AXRenderObject::getScrollableAreaIfScrollable() const | |
| 241 { | |
| 242 // If the parent is a FrameView, then this object isn't really scrollable; t
he parent should handle the scrolling. | |
| 243 if (parentObject() && parentObject()->isAXScrollView()) | |
| 244 return 0; | |
| 245 | |
| 246 if (!m_renderer || !m_renderer->isBox()) | |
| 247 return 0; | |
| 248 | |
| 249 RenderBox* box = toRenderBox(m_renderer); | |
| 250 if (!box->canBeScrolledAndHasScrollableArea()) | |
| 251 return 0; | |
| 252 | |
| 253 return box->scrollableArea(); | |
| 254 } | |
| 255 | |
| 256 AccessibilityRole AXRenderObject::determineAccessibilityRole() | |
| 257 { | |
| 258 if (!m_renderer) | |
| 259 return UnknownRole; | |
| 260 | |
| 261 m_ariaRole = determineAriaRoleAttribute(); | |
| 262 | |
| 263 Node* node = m_renderer->node(); | |
| 264 AccessibilityRole ariaRole = ariaRoleAttribute(); | |
| 265 if (ariaRole != UnknownRole) | |
| 266 return ariaRole; | |
| 267 | |
| 268 RenderBoxModelObject* cssBox = renderBoxModelObject(); | |
| 269 | |
| 270 if (node && node->isLink()) { | |
| 271 if (cssBox && cssBox->isImage()) | |
| 272 return ImageMapRole; | |
| 273 return LinkRole; | |
| 274 } | |
| 275 if ((cssBox && cssBox->isListItem()) || isHTMLLIElement(node)) | |
| 276 return ListItemRole; | |
| 277 if (m_renderer->isListMarker()) | |
| 278 return ListMarkerRole; | |
| 279 if (isHTMLButtonElement(node)) | |
| 280 return buttonRoleType(); | |
| 281 if (isHTMLDetailsElement(node)) | |
| 282 return DetailsRole; | |
| 283 if (isHTMLSummaryElement(node)) { | |
| 284 if (node->parentElement() && isHTMLDetailsElement(node->parentElement())
) | |
| 285 return DisclosureTriangleRole; | |
| 286 return UnknownRole; | |
| 287 } | |
| 288 if (isHTMLLegendElement(node)) | |
| 289 return LegendRole; | |
| 290 if (m_renderer->isText()) | |
| 291 return StaticTextRole; | |
| 292 if (cssBox && cssBox->isImage()) { | |
| 293 if (isHTMLInputElement(node)) | |
| 294 return ariaHasPopup() ? PopUpButtonRole : ButtonRole; | |
| 295 if (isSVGImage()) | |
| 296 return SVGRootRole; | |
| 297 return ImageRole; | |
| 298 } | |
| 299 | |
| 300 // Note: if JavaScript is disabled, the renderer won't be a RenderHTMLCanvas
. | |
| 301 if (isHTMLCanvasElement(node) && m_renderer->isCanvas()) | |
| 302 return CanvasRole; | |
| 303 | |
| 304 if (cssBox && cssBox->isRenderView()) | |
| 305 return WebAreaRole; | |
| 306 | |
| 307 if (cssBox && cssBox->isTextField()) | |
| 308 return TextFieldRole; | |
| 309 | |
| 310 if (cssBox && cssBox->isTextArea()) | |
| 311 return TextAreaRole; | |
| 312 | |
| 313 if (isHTMLInputElement(node)) { | |
| 314 HTMLInputElement& input = toHTMLInputElement(*node); | |
| 315 const AtomicString& type = input.type(); | |
| 316 if (type == InputTypeNames::button) { | |
| 317 if ((node->parentNode() && isHTMLMenuElement(node->parentNode())) ||
(parentObject() && parentObject()->roleValue() == MenuRole)) | |
| 318 return MenuItemRole; | |
| 319 return buttonRoleType(); | |
| 320 } | |
| 321 if (type == InputTypeNames::checkbox) { | |
| 322 if ((node->parentNode() && isHTMLMenuElement(node->parentNode())) ||
(parentObject() && parentObject()->roleValue() == MenuRole)) | |
| 323 return MenuItemCheckBoxRole; | |
| 324 return CheckBoxRole; | |
| 325 } | |
| 326 if (type == InputTypeNames::date) | |
| 327 return DateRole; | |
| 328 if (type == InputTypeNames::datetime | |
| 329 || type == InputTypeNames::datetime_local | |
| 330 || type == InputTypeNames::month | |
| 331 || type == InputTypeNames::week) | |
| 332 return DateTimeRole; | |
| 333 if (type == InputTypeNames::radio) { | |
| 334 if ((node->parentNode() && isHTMLMenuElement(node->parentNode())) ||
(parentObject() && parentObject()->roleValue() == MenuRole)) | |
| 335 return MenuItemRadioRole; | |
| 336 return RadioButtonRole; | |
| 337 } | |
| 338 if (input.isTextButton()) | |
| 339 return buttonRoleType(); | |
| 340 if (type == InputTypeNames::color) | |
| 341 return ColorWellRole; | |
| 342 if (type == InputTypeNames::time) | |
| 343 return TimeRole; | |
| 344 } | |
| 345 | |
| 346 if (isFileUploadButton()) | |
| 347 return ButtonRole; | |
| 348 | |
| 349 if (cssBox && cssBox->isMenuList()) | |
| 350 return PopUpButtonRole; | |
| 351 | |
| 352 if (headingLevel()) | |
| 353 return HeadingRole; | |
| 354 | |
| 355 if (m_renderer->isSVGImage()) | |
| 356 return ImageRole; | |
| 357 if (m_renderer->isSVGRoot()) | |
| 358 return SVGRootRole; | |
| 359 | |
| 360 if (node && node->hasTagName(ddTag)) | |
| 361 return DescriptionListDetailRole; | |
| 362 | |
| 363 if (node && node->hasTagName(dlTag)) | |
| 364 return DescriptionListRole; | |
| 365 | |
| 366 if (node && node->hasTagName(dtTag)) | |
| 367 return DescriptionListTermRole; | |
| 368 | |
| 369 if (node && (node->hasTagName(rpTag) || node->hasTagName(rtTag))) | |
| 370 return AnnotationRole; | |
| 371 | |
| 372 // Table sections should be ignored. | |
| 373 if (m_renderer->isTableSection()) | |
| 374 return IgnoredRole; | |
| 375 | |
| 376 if (m_renderer->isHR()) | |
| 377 return HorizontalRuleRole; | |
| 378 | |
| 379 if (isHTMLOutputElement(node)) | |
| 380 return StatusRole; | |
| 381 | |
| 382 if (isHTMLParagraphElement(node)) | |
| 383 return ParagraphRole; | |
| 384 | |
| 385 if (isHTMLLabelElement(node)) | |
| 386 return LabelRole; | |
| 387 | |
| 388 if (isHTMLRubyElement(node)) | |
| 389 return RubyRole; | |
| 390 | |
| 391 if (isHTMLDivElement(node)) | |
| 392 return DivRole; | |
| 393 | |
| 394 if (isHTMLMeterElement(node)) | |
| 395 return MeterRole; | |
| 396 | |
| 397 if (isHTMLFormElement(node)) | |
| 398 return FormRole; | |
| 399 | |
| 400 if (node && node->hasTagName(articleTag)) | |
| 401 return ArticleRole; | |
| 402 | |
| 403 if (node && node->hasTagName(blockquoteTag)) | |
| 404 return BlockquoteRole; | |
| 405 | |
| 406 if (node && node->hasTagName(mainTag)) | |
| 407 return MainRole; | |
| 408 | |
| 409 if (node && node->hasTagName(navTag)) | |
| 410 return NavigationRole; | |
| 411 | |
| 412 if (node && node->hasTagName(asideTag)) | |
| 413 return ComplementaryRole; | |
| 414 | |
| 415 if (node && node->hasTagName(preTag)) | |
| 416 return PreRole; | |
| 417 | |
| 418 if (node && node->hasTagName(sectionTag)) | |
| 419 return RegionRole; | |
| 420 | |
| 421 if (node && node->hasTagName(addressTag)) | |
| 422 return ContentInfoRole; | |
| 423 | |
| 424 if (node && node->hasTagName(dialogTag)) | |
| 425 return DialogRole; | |
| 426 | |
| 427 // The HTML element should not be exposed as an element. That's what the Ren
derView element does. | |
| 428 if (isHTMLHtmlElement(node)) | |
| 429 return IgnoredRole; | |
| 430 | |
| 431 if (node && node->hasTagName(iframeTag)) | |
| 432 return IframeRole; | |
| 433 | |
| 434 if (isEmbeddedObject()) | |
| 435 return EmbeddedObjectRole; | |
| 436 | |
| 437 if (node && node->hasTagName(figcaptionTag)) | |
| 438 return FigcaptionRole; | |
| 439 | |
| 440 if (node && node->hasTagName(figureTag)) | |
| 441 return FigureRole; | |
| 442 | |
| 443 // There should only be one banner/contentInfo per page. If header/footer ar
e being used within an article or section | |
| 444 // then it should not be exposed as whole page's banner/contentInfo | |
| 445 if (node && node->hasTagName(headerTag) && !isDescendantOfElementType(articl
eTag) && !isDescendantOfElementType(sectionTag)) | |
| 446 return BannerRole; | |
| 447 if (node && node->hasTagName(footerTag) && !isDescendantOfElementType(articl
eTag) && !isDescendantOfElementType(sectionTag)) | |
| 448 return FooterRole; | |
| 449 | |
| 450 if (isHTMLAnchorElement(node) && isClickable()) | |
| 451 return LinkRole; | |
| 452 | |
| 453 if (m_renderer->isRenderBlockFlow()) | |
| 454 return GroupRole; | |
| 455 | |
| 456 // If the element does not have role, but it has ARIA attributes, accessibil
ity should fallback to exposing it as a group. | |
| 457 if (supportsARIAAttributes()) | |
| 458 return GroupRole; | |
| 459 | |
| 460 return UnknownRole; | |
| 461 } | |
| 462 | |
| 463 void AXRenderObject::init() | |
| 464 { | |
| 465 AXNodeObject::init(); | |
| 466 } | |
| 467 | |
| 468 void AXRenderObject::detach() | |
| 469 { | |
| 470 AXNodeObject::detach(); | |
| 471 | |
| 472 detachRemoteSVGRoot(); | |
| 473 | |
| 474 #if ENABLE(ASSERT) | |
| 475 if (m_renderer) | |
| 476 m_renderer->setHasAXObject(false); | |
| 477 #endif | |
| 478 m_renderer = 0; | |
| 479 } | |
| 480 | |
| 481 // | |
| 482 // Check object role or purpose. | |
| 483 // | |
| 484 | |
| 485 bool AXRenderObject::isAttachment() const | |
| 486 { | |
| 487 RenderBoxModelObject* renderer = renderBoxModelObject(); | |
| 488 if (!renderer) | |
| 489 return false; | |
| 490 // Widgets are the replaced elements that we represent to AX as attachments | |
| 491 bool isRenderPart = renderer->isRenderPart(); | |
| 492 ASSERT(!isRenderPart || (renderer->isReplaced() && !isImage())); | |
| 493 return isRenderPart; | |
| 494 } | |
| 495 | |
| 496 bool AXRenderObject::isFileUploadButton() const | |
| 497 { | |
| 498 return m_renderer && isHTMLInputElement(m_renderer->node()) && toHTMLInputEl
ement(*m_renderer->node()).type() == InputTypeNames::file; | |
| 499 } | |
| 500 | |
| 501 static bool isLinkable(const AXObject& object) | |
| 502 { | |
| 503 if (!object.renderer()) | |
| 504 return false; | |
| 505 | |
| 506 // See https://wiki.mozilla.org/Accessibility/AT-Windows-API for the element
s | |
| 507 // Mozilla considers linkable. | |
| 508 return object.isLink() || object.isImage() || object.renderer()->isText(); | |
| 509 } | |
| 510 | |
| 511 bool AXRenderObject::isLinked() const | |
| 512 { | |
| 513 if (!isLinkable(*this)) | |
| 514 return false; | |
| 515 | |
| 516 Element* anchor = anchorElement(); | |
| 517 if (!isHTMLAnchorElement(anchor)) | |
| 518 return false; | |
| 519 | |
| 520 return !toHTMLAnchorElement(*anchor).href().isEmpty(); | |
| 521 } | |
| 522 | |
| 523 bool AXRenderObject::isLoaded() const | |
| 524 { | |
| 525 return !m_renderer->document().parser(); | |
| 526 } | |
| 527 | |
| 528 bool AXRenderObject::isOffScreen() const | |
| 529 { | |
| 530 ASSERT(m_renderer); | |
| 531 IntRect contentRect = pixelSnappedIntRect(m_renderer->absoluteClippedOverflo
wRect()); | |
| 532 FrameView* view = m_renderer->frame()->view(); | |
| 533 IntRect viewRect = view->visibleContentRect(); | |
| 534 viewRect.intersect(contentRect); | |
| 535 return viewRect.isEmpty(); | |
| 536 } | |
| 537 | |
| 538 bool AXRenderObject::isReadOnly() const | |
| 539 { | |
| 540 ASSERT(m_renderer); | |
| 541 | |
| 542 if (isWebArea()) { | |
| 543 Document& document = m_renderer->document(); | |
| 544 HTMLElement* body = document.body(); | |
| 545 if (body && body->hasEditableStyle()) | |
| 546 return false; | |
| 547 | |
| 548 return !document.hasEditableStyle(); | |
| 549 } | |
| 550 | |
| 551 return AXNodeObject::isReadOnly(); | |
| 552 } | |
| 553 | |
| 554 bool AXRenderObject::isVisited() const | |
| 555 { | |
| 556 // FIXME: Is it a privacy violation to expose visited information to accessi
bility APIs? | |
| 557 return m_renderer->style()->isLink() && m_renderer->style()->insideLink() ==
InsideVisitedLink; | |
| 558 } | |
| 559 | |
| 560 // | |
| 561 // Check object state. | |
| 562 // | |
| 563 | |
| 564 bool AXRenderObject::isFocused() const | |
| 565 { | |
| 566 if (!m_renderer) | |
| 567 return false; | |
| 568 | |
| 569 Document& document = m_renderer->document(); | |
| 570 Element* focusedElement = document.focusedElement(); | |
| 571 if (!focusedElement) | |
| 572 return false; | |
| 573 | |
| 574 // A web area is represented by the Document node in the DOM tree, which isn
't focusable. | |
| 575 // Check instead if the frame's selection controller is focused | |
| 576 if (focusedElement == m_renderer->node() | |
| 577 || (roleValue() == WebAreaRole && document.frame()->selection().isFocuse
dAndActive())) | |
| 578 return true; | |
| 579 | |
| 580 return false; | |
| 581 } | |
| 582 | |
| 583 bool AXRenderObject::isSelected() const | |
| 584 { | |
| 585 if (!m_renderer) | |
| 586 return false; | |
| 587 | |
| 588 Node* node = m_renderer->node(); | |
| 589 if (!node) | |
| 590 return false; | |
| 591 | |
| 592 const AtomicString& ariaSelected = getAttribute(aria_selectedAttr); | |
| 593 if (equalIgnoringCase(ariaSelected, "true")) | |
| 594 return true; | |
| 595 | |
| 596 if (isTabItem() && isTabItemSelected()) | |
| 597 return true; | |
| 598 | |
| 599 return false; | |
| 600 } | |
| 601 | |
| 602 // | |
| 603 // Whether objects are ignored, i.e. not included in the tree. | |
| 604 // | |
| 605 | |
| 606 AXObjectInclusion AXRenderObject::defaultObjectInclusion() const | |
| 607 { | |
| 608 // The following cases can apply to any element that's a subclass of AXRende
rObject. | |
| 609 | |
| 610 if (!m_renderer) | |
| 611 return IgnoreObject; | |
| 612 | |
| 613 if (m_renderer->style()->visibility() != VISIBLE) { | |
| 614 // aria-hidden is meant to override visibility as the determinant in AX
hierarchy inclusion. | |
| 615 if (equalIgnoringCase(getAttribute(aria_hiddenAttr), "false")) | |
| 616 return DefaultBehavior; | |
| 617 | |
| 618 return IgnoreObject; | |
| 619 } | |
| 620 | |
| 621 return AXObject::defaultObjectInclusion(); | |
| 622 } | |
| 623 | |
| 624 bool AXRenderObject::computeAccessibilityIsIgnored() const | |
| 625 { | |
| 626 #if ENABLE(ASSERT) | |
| 627 ASSERT(m_initialized); | |
| 628 #endif | |
| 629 | |
| 630 // Check first if any of the common reasons cause this element to be ignored
. | |
| 631 // Then process other use cases that need to be applied to all the various r
oles | |
| 632 // that AXRenderObjects take on. | |
| 633 AXObjectInclusion decision = defaultObjectInclusion(); | |
| 634 if (decision == IncludeObject) | |
| 635 return false; | |
| 636 if (decision == IgnoreObject) | |
| 637 return true; | |
| 638 | |
| 639 // If this element is within a parent that cannot have children, it should n
ot be exposed. | |
| 640 if (isDescendantOfBarrenParent()) | |
| 641 return true; | |
| 642 | |
| 643 if (roleValue() == IgnoredRole) | |
| 644 return true; | |
| 645 | |
| 646 if ((roleValue() == NoneRole || roleValue() == PresentationalRole) || inheri
tsPresentationalRole()) | |
| 647 return true; | |
| 648 | |
| 649 // An ARIA tree can only have tree items and static text as children. | |
| 650 if (!isAllowedChildOfTree()) | |
| 651 return true; | |
| 652 | |
| 653 // TODO: we should refactor this - but right now this is necessary to make | |
| 654 // sure scroll areas stay in the tree. | |
| 655 if (isAttachment()) | |
| 656 return false; | |
| 657 | |
| 658 // ignore popup menu items because AppKit does | |
| 659 for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->p
arent()) { | |
| 660 if (parent->isBoxModelObject() && toRenderBoxModelObject(parent)->isMenu
List()) | |
| 661 return true; | |
| 662 } | |
| 663 | |
| 664 // find out if this element is inside of a label element. | |
| 665 // if so, it may be ignored because it's the label for a checkbox or radio b
utton | |
| 666 AXObject* controlObject = correspondingControlForLabelElement(); | |
| 667 if (controlObject && !controlObject->exposesTitleUIElement() && controlObjec
t->isCheckboxOrRadio()) | |
| 668 return true; | |
| 669 | |
| 670 // NOTE: BRs always have text boxes now, so the text box check here can be r
emoved | |
| 671 if (m_renderer->isText()) { | |
| 672 // static text beneath MenuItems and MenuButtons are just reported along
with the menu item, so it's ignored on an individual level | |
| 673 AXObject* parent = parentObjectUnignored(); | |
| 674 if (parent && (parent->ariaRoleAttribute() == MenuItemRole || parent->ar
iaRoleAttribute() == MenuButtonRole)) | |
| 675 return true; | |
| 676 RenderText* renderText = toRenderText(m_renderer); | |
| 677 if (m_renderer->isBR() || !renderText->firstTextBox()) | |
| 678 return true; | |
| 679 | |
| 680 // Don't ignore static text in editable text controls. | |
| 681 for (AXObject* parent = parentObject(); parent; parent = parent->parentO
bject()) { | |
| 682 if (parent->roleValue() == TextFieldRole || parent->roleValue() == T
extAreaRole) | |
| 683 return false; | |
| 684 } | |
| 685 | |
| 686 // text elements that are just empty whitespace should not be returned | |
| 687 // FIXME(dmazzoni): we probably shouldn't ignore this if the style is 'p
re', or similar... | |
| 688 return renderText->text().impl()->containsOnlyWhitespace(); | |
| 689 } | |
| 690 | |
| 691 if (isHeading()) | |
| 692 return false; | |
| 693 | |
| 694 if (isLandmarkRelated()) | |
| 695 return false; | |
| 696 | |
| 697 if (isLink()) | |
| 698 return false; | |
| 699 | |
| 700 // all controls are accessible | |
| 701 if (isControl()) | |
| 702 return false; | |
| 703 | |
| 704 if (ariaRoleAttribute() != UnknownRole) | |
| 705 return false; | |
| 706 | |
| 707 // don't ignore labels, because they serve as TitleUIElements | |
| 708 Node* node = m_renderer->node(); | |
| 709 if (isHTMLLabelElement(node)) | |
| 710 return false; | |
| 711 | |
| 712 // Anything that is content editable should not be ignored. | |
| 713 // However, one cannot just call node->hasEditableStyle() since that will as
k if its parents | |
| 714 // are also editable. Only the top level content editable region should be e
xposed. | |
| 715 if (hasContentEditableAttributeSet()) | |
| 716 return false; | |
| 717 | |
| 718 // List items play an important role in defining the structure of lists. The
y should not be ignored. | |
| 719 if (roleValue() == ListItemRole) | |
| 720 return false; | |
| 721 | |
| 722 if (roleValue() == BlockquoteRole) | |
| 723 return false; | |
| 724 | |
| 725 if (roleValue() == DialogRole) | |
| 726 return false; | |
| 727 | |
| 728 if (roleValue() == FigcaptionRole) | |
| 729 return false; | |
| 730 | |
| 731 if (roleValue() == FigureRole) | |
| 732 return false; | |
| 733 | |
| 734 if (roleValue() == DetailsRole) | |
| 735 return false; | |
| 736 | |
| 737 if (roleValue() == MeterRole) | |
| 738 return false; | |
| 739 | |
| 740 if (roleValue() == RubyRole) | |
| 741 return false; | |
| 742 | |
| 743 // if this element has aria attributes on it, it should not be ignored. | |
| 744 if (supportsARIAAttributes()) | |
| 745 return false; | |
| 746 | |
| 747 // <span> tags are inline tags and not meant to convey information if they h
ave no other aria | |
| 748 // information on them. If we don't ignore them, they may emit signals expec
ted to come from | |
| 749 // their parent. In addition, because included spans are GroupRole objects,
and GroupRole | |
| 750 // objects are often containers with meaningful information, the inclusion o
f a span can have | |
| 751 // the side effect of causing the immediate parent accessible to be ignored.
This is especially | |
| 752 // problematic for platforms which have distinct roles for textual block ele
ments. | |
| 753 if (isHTMLSpanElement(node)) | |
| 754 return true; | |
| 755 | |
| 756 if (m_renderer->isRenderBlockFlow() && m_renderer->childrenInline() && !canS
etFocusAttribute()) | |
| 757 return !toRenderBlockFlow(m_renderer)->firstLineBox() && !mouseButtonLis
tener(); | |
| 758 | |
| 759 // ignore images seemingly used as spacers | |
| 760 if (isImage()) { | |
| 761 | |
| 762 // If the image can take focus, it should not be ignored, lest the user
not be able to interact with something important. | |
| 763 if (canSetFocusAttribute()) | |
| 764 return false; | |
| 765 | |
| 766 if (node && node->isElementNode()) { | |
| 767 Element* elt = toElement(node); | |
| 768 const AtomicString& alt = elt->getAttribute(altAttr); | |
| 769 // don't ignore an image that has an alt tag | |
| 770 if (!alt.string().containsOnlyWhitespace()) | |
| 771 return false; | |
| 772 // informal standard is to ignore images with zero-length alt string
s | |
| 773 if (!alt.isNull()) | |
| 774 return true; | |
| 775 } | |
| 776 | |
| 777 if (isNativeImage() && m_renderer->isImage()) { | |
| 778 // check for one-dimensional image | |
| 779 RenderImage* image = toRenderImage(m_renderer); | |
| 780 if (image->height() <= 1 || image->width() <= 1) | |
| 781 return true; | |
| 782 | |
| 783 // check whether rendered image was stretched from one-dimensional f
ile image | |
| 784 if (image->cachedImage()) { | |
| 785 LayoutSize imageSize = image->cachedImage()->imageSizeForRendere
r(m_renderer, image->view()->zoomFactor()); | |
| 786 return imageSize.height() <= 1 || imageSize.width() <= 1; | |
| 787 } | |
| 788 } | |
| 789 return false; | |
| 790 } | |
| 791 | |
| 792 if (isCanvas()) { | |
| 793 if (canvasHasFallbackContent()) | |
| 794 return false; | |
| 795 RenderHTMLCanvas* canvas = toRenderHTMLCanvas(m_renderer); | |
| 796 if (canvas->height() <= 1 || canvas->width() <= 1) | |
| 797 return true; | |
| 798 // Otherwise fall through; use presence of help text, title, or descript
ion to decide. | |
| 799 } | |
| 800 | |
| 801 if (isWebArea() || m_renderer->isListMarker()) | |
| 802 return false; | |
| 803 | |
| 804 // Using the help text, title or accessibility description (so we | |
| 805 // check if there's some kind of accessible name for the element) | |
| 806 // to decide an element's visibility is not as definitive as | |
| 807 // previous checks, so this should remain as one of the last. | |
| 808 // | |
| 809 // These checks are simplified in the interest of execution speed; | |
| 810 // for example, any element having an alt attribute will make it | |
| 811 // not ignored, rather than just images. | |
| 812 if (!getAttribute(aria_helpAttr).isEmpty() || !getAttribute(aria_describedby
Attr).isEmpty() || !getAttribute(altAttr).isEmpty() || !getAttribute(titleAttr).
isEmpty()) | |
| 813 return false; | |
| 814 | |
| 815 // Don't ignore generic focusable elements like <div tabindex=0> | |
| 816 // unless they're completely empty, with no children. | |
| 817 if (isGenericFocusableElement() && node->hasChildren()) | |
| 818 return false; | |
| 819 | |
| 820 if (!ariaAccessibilityDescription().isEmpty()) | |
| 821 return false; | |
| 822 | |
| 823 // By default, objects should be ignored so that the AX hierarchy is not | |
| 824 // filled with unnecessary items. | |
| 825 return true; | |
| 826 } | |
| 827 | |
| 828 // | |
| 829 // Properties of static elements. | |
| 830 // | |
| 831 | |
| 832 const AtomicString& AXRenderObject::accessKey() const | |
| 833 { | |
| 834 Node* node = m_renderer->node(); | |
| 835 if (!node) | |
| 836 return nullAtom; | |
| 837 if (!node->isElementNode()) | |
| 838 return nullAtom; | |
| 839 return toElement(node)->getAttribute(accesskeyAttr); | |
| 840 } | |
| 841 | |
| 842 AccessibilityOrientation AXRenderObject::orientation() const | |
| 843 { | |
| 844 const AtomicString& ariaOrientation = getAttribute(aria_orientationAttr); | |
| 845 if (equalIgnoringCase(ariaOrientation, "horizontal")) | |
| 846 return AccessibilityOrientationHorizontal; | |
| 847 if (equalIgnoringCase(ariaOrientation, "vertical")) | |
| 848 return AccessibilityOrientationVertical; | |
| 849 | |
| 850 return AXObject::orientation(); | |
| 851 } | |
| 852 | |
| 853 String AXRenderObject::text() const | |
| 854 { | |
| 855 if (isPasswordFieldAndShouldHideValue()) | |
| 856 return String(); | |
| 857 | |
| 858 return AXNodeObject::text(); | |
| 859 } | |
| 860 | |
| 861 int AXRenderObject::textLength() const | |
| 862 { | |
| 863 if (!isTextControl()) | |
| 864 return -1; | |
| 865 | |
| 866 if (isPasswordFieldAndShouldHideValue()) | |
| 867 return -1; // need to return something distinct from 0 | |
| 868 | |
| 869 return text().length(); | |
| 870 } | |
| 871 | |
| 872 KURL AXRenderObject::url() const | |
| 873 { | |
| 874 if (isAnchor() && isHTMLAnchorElement(m_renderer->node())) { | |
| 875 if (HTMLAnchorElement* anchor = toHTMLAnchorElement(anchorElement())) | |
| 876 return anchor->href(); | |
| 877 } | |
| 878 | |
| 879 if (isWebArea()) | |
| 880 return m_renderer->document().url(); | |
| 881 | |
| 882 if (isImage() && isHTMLImageElement(m_renderer->node())) | |
| 883 return toHTMLImageElement(*m_renderer->node()).src(); | |
| 884 | |
| 885 if (isInputImage()) | |
| 886 return toHTMLInputElement(m_renderer->node())->src(); | |
| 887 | |
| 888 return KURL(); | |
| 889 } | |
| 890 | |
| 891 // | |
| 892 // Properties of interactive elements. | |
| 893 // | |
| 894 | |
| 895 static String queryString(WebLocalizedString::Name name) | |
| 896 { | |
| 897 return Locale::defaultLocale().queryString(name); | |
| 898 } | |
| 899 | |
| 900 String AXRenderObject::actionVerb() const | |
| 901 { | |
| 902 switch (roleValue()) { | |
| 903 case ButtonRole: | |
| 904 case ToggleButtonRole: | |
| 905 return queryString(WebLocalizedString::AXButtonActionVerb); | |
| 906 case TextFieldRole: | |
| 907 case TextAreaRole: | |
| 908 return queryString(WebLocalizedString::AXTextFieldActionVerb); | |
| 909 case RadioButtonRole: | |
| 910 return queryString(WebLocalizedString::AXRadioButtonActionVerb); | |
| 911 case CheckBoxRole: | |
| 912 return queryString(isChecked() ? WebLocalizedString::AXCheckedCheckBoxAc
tionVerb : WebLocalizedString::AXUncheckedCheckBoxActionVerb); | |
| 913 case LinkRole: | |
| 914 return queryString(WebLocalizedString::AXLinkActionVerb); | |
| 915 default: | |
| 916 return emptyString(); | |
| 917 } | |
| 918 } | |
| 919 | |
| 920 String AXRenderObject::stringValue() const | |
| 921 { | |
| 922 if (!m_renderer) | |
| 923 return String(); | |
| 924 | |
| 925 if (isPasswordFieldAndShouldHideValue()) | |
| 926 return String(); | |
| 927 | |
| 928 RenderBoxModelObject* cssBox = renderBoxModelObject(); | |
| 929 | |
| 930 if (ariaRoleAttribute() == StaticTextRole) { | |
| 931 String staticText = text(); | |
| 932 if (!staticText.length()) | |
| 933 staticText = textUnderElement(); | |
| 934 return staticText; | |
| 935 } | |
| 936 | |
| 937 if (m_renderer->isText()) | |
| 938 return textUnderElement(); | |
| 939 | |
| 940 if (cssBox && cssBox->isMenuList()) { | |
| 941 // RenderMenuList will go straight to the text() of its selected item. | |
| 942 // This has to be overridden in the case where the selected item has an
ARIA label. | |
| 943 HTMLSelectElement* selectElement = toHTMLSelectElement(m_renderer->node(
)); | |
| 944 int selectedIndex = selectElement->selectedIndex(); | |
| 945 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = se
lectElement->listItems(); | |
| 946 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems
.size()) { | |
| 947 const AtomicString& overriddenDescription = listItems[selectedIndex]
->fastGetAttribute(aria_labelAttr); | |
| 948 if (!overriddenDescription.isNull()) | |
| 949 return overriddenDescription; | |
| 950 } | |
| 951 return toRenderMenuList(m_renderer)->text(); | |
| 952 } | |
| 953 | |
| 954 if (m_renderer->isListMarker()) | |
| 955 return toRenderListMarker(m_renderer)->text(); | |
| 956 | |
| 957 if (isWebArea()) { | |
| 958 // FIXME: Why would a renderer exist when the Document isn't attached to
a frame? | |
| 959 if (m_renderer->frame()) | |
| 960 return String(); | |
| 961 | |
| 962 ASSERT_NOT_REACHED(); | |
| 963 } | |
| 964 | |
| 965 if (isTextControl()) | |
| 966 return text(); | |
| 967 | |
| 968 if (m_renderer->isFileUploadControl()) | |
| 969 return toRenderFileUploadControl(m_renderer)->fileTextValue(); | |
| 970 | |
| 971 // FIXME: We might need to implement a value here for more types | |
| 972 // FIXME: It would be better not to advertise a value at all for the types f
or which we don't implement one; | |
| 973 // this would require subclassing or making accessibilityAttributeNames do s
omething other than return a | |
| 974 // single static array. | |
| 975 return String(); | |
| 976 } | |
| 977 | |
| 978 // | |
| 979 // ARIA attributes. | |
| 980 // | |
| 981 | |
| 982 AXObject* AXRenderObject::activeDescendant() const | |
| 983 { | |
| 984 if (!m_renderer) | |
| 985 return 0; | |
| 986 | |
| 987 if (m_renderer->node() && !m_renderer->node()->isElementNode()) | |
| 988 return 0; | |
| 989 | |
| 990 Element* element = toElement(m_renderer->node()); | |
| 991 if (!element) | |
| 992 return 0; | |
| 993 | |
| 994 const AtomicString& activeDescendantAttrStr = element->getAttribute(aria_act
ivedescendantAttr); | |
| 995 if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty()) | |
| 996 return 0; | |
| 997 | |
| 998 Element* target = element->treeScope().getElementById(activeDescendantAttrSt
r); | |
| 999 if (!target) | |
| 1000 return 0; | |
| 1001 | |
| 1002 AXObject* obj = axObjectCache()->getOrCreate(target); | |
| 1003 | |
| 1004 // An activedescendant is only useful if it has a renderer, because that's w
hat's needed to post the notification. | |
| 1005 if (obj && obj->isAXRenderObject()) | |
| 1006 return obj; | |
| 1007 | |
| 1008 return 0; | |
| 1009 } | |
| 1010 | |
| 1011 void AXRenderObject::accessibilityChildrenFromAttribute(QualifiedName attr, Acce
ssibilityChildrenVector& children) const | |
| 1012 { | |
| 1013 WillBeHeapVector<RawPtrWillBeMember<Element> > elements; | |
| 1014 elementsFromAttribute(elements, attr); | |
| 1015 | |
| 1016 AXObjectCacheImpl* cache = axObjectCache(); | |
| 1017 unsigned count = elements.size(); | |
| 1018 for (unsigned k = 0; k < count; ++k) { | |
| 1019 Element* element = elements[k]; | |
| 1020 AXObject* child = cache->getOrCreate(element); | |
| 1021 if (child) | |
| 1022 children.append(child); | |
| 1023 } | |
| 1024 } | |
| 1025 | |
| 1026 void AXRenderObject::ariaFlowToElements(AccessibilityChildrenVector& flowTo) con
st | |
| 1027 { | |
| 1028 accessibilityChildrenFromAttribute(aria_flowtoAttr, flowTo); | |
| 1029 } | |
| 1030 | |
| 1031 void AXRenderObject::ariaControlsElements(AccessibilityChildrenVector& controls)
const | |
| 1032 { | |
| 1033 accessibilityChildrenFromAttribute(aria_controlsAttr, controls); | |
| 1034 } | |
| 1035 | |
| 1036 void AXRenderObject::ariaDescribedbyElements(AccessibilityChildrenVector& descri
bedby) const | |
| 1037 { | |
| 1038 accessibilityChildrenFromAttribute(aria_describedbyAttr, describedby); | |
| 1039 } | |
| 1040 | |
| 1041 void AXRenderObject::ariaLabelledbyElements(AccessibilityChildrenVector& labelle
dby) const | |
| 1042 { | |
| 1043 accessibilityChildrenFromAttribute(aria_labelledbyAttr, labelledby); | |
| 1044 } | |
| 1045 | |
| 1046 void AXRenderObject::ariaOwnsElements(AccessibilityChildrenVector& owns) const | |
| 1047 { | |
| 1048 accessibilityChildrenFromAttribute(aria_ownsAttr, owns); | |
| 1049 } | |
| 1050 | |
| 1051 bool AXRenderObject::ariaHasPopup() const | |
| 1052 { | |
| 1053 return elementAttributeValue(aria_haspopupAttr); | |
| 1054 } | |
| 1055 | |
| 1056 bool AXRenderObject::ariaRoleHasPresentationalChildren() const | |
| 1057 { | |
| 1058 switch (m_ariaRole) { | |
| 1059 case ButtonRole: | |
| 1060 case SliderRole: | |
| 1061 case ImageRole: | |
| 1062 case ProgressIndicatorRole: | |
| 1063 case SpinButtonRole: | |
| 1064 // case SeparatorRole: | |
| 1065 return true; | |
| 1066 default: | |
| 1067 return false; | |
| 1068 } | |
| 1069 } | |
| 1070 | |
| 1071 bool AXRenderObject::isPresentationalChildOfAriaRole() const | |
| 1072 { | |
| 1073 // Walk the parent chain looking for a parent that has presentational childr
en | |
| 1074 AXObject* parent; | |
| 1075 for (parent = parentObject(); parent && !parent->ariaRoleHasPresentationalCh
ildren(); parent = parent->parentObject()) | |
| 1076 { } | |
| 1077 | |
| 1078 return parent; | |
| 1079 } | |
| 1080 | |
| 1081 bool AXRenderObject::shouldFocusActiveDescendant() const | |
| 1082 { | |
| 1083 switch (ariaRoleAttribute()) { | |
| 1084 case ComboBoxRole: | |
| 1085 case GridRole: | |
| 1086 case GroupRole: | |
| 1087 case ListBoxRole: | |
| 1088 case MenuRole: | |
| 1089 case MenuBarRole: | |
| 1090 case OutlineRole: | |
| 1091 case PopUpButtonRole: | |
| 1092 case ProgressIndicatorRole: | |
| 1093 case RadioGroupRole: | |
| 1094 case RowRole: | |
| 1095 case TabListRole: | |
| 1096 case ToolbarRole: | |
| 1097 case TreeRole: | |
| 1098 case TreeGridRole: | |
| 1099 return true; | |
| 1100 default: | |
| 1101 return false; | |
| 1102 } | |
| 1103 } | |
| 1104 | |
| 1105 bool AXRenderObject::supportsARIADragging() const | |
| 1106 { | |
| 1107 const AtomicString& grabbed = getAttribute(aria_grabbedAttr); | |
| 1108 return equalIgnoringCase(grabbed, "true") || equalIgnoringCase(grabbed, "fal
se"); | |
| 1109 } | |
| 1110 | |
| 1111 bool AXRenderObject::supportsARIADropping() const | |
| 1112 { | |
| 1113 const AtomicString& dropEffect = getAttribute(aria_dropeffectAttr); | |
| 1114 return !dropEffect.isEmpty(); | |
| 1115 } | |
| 1116 | |
| 1117 bool AXRenderObject::supportsARIAFlowTo() const | |
| 1118 { | |
| 1119 return !getAttribute(aria_flowtoAttr).isEmpty(); | |
| 1120 } | |
| 1121 | |
| 1122 bool AXRenderObject::supportsARIAOwns() const | |
| 1123 { | |
| 1124 if (!m_renderer) | |
| 1125 return false; | |
| 1126 const AtomicString& ariaOwns = getAttribute(aria_ownsAttr); | |
| 1127 | |
| 1128 return !ariaOwns.isEmpty(); | |
| 1129 } | |
| 1130 | |
| 1131 // | |
| 1132 // ARIA live-region features. | |
| 1133 // | |
| 1134 | |
| 1135 const AtomicString& AXRenderObject::liveRegionStatus() const | |
| 1136 { | |
| 1137 DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusAssertive, ("asserti
ve", AtomicString::ConstructFromLiteral)); | |
| 1138 DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusPolite, ("polite", A
tomicString::ConstructFromLiteral)); | |
| 1139 DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusOff, ("off", AtomicS
tring::ConstructFromLiteral)); | |
| 1140 | |
| 1141 const AtomicString& liveRegionStatus = getAttribute(aria_liveAttr); | |
| 1142 // These roles have implicit live region status. | |
| 1143 if (liveRegionStatus.isEmpty()) { | |
| 1144 switch (roleValue()) { | |
| 1145 case AlertDialogRole: | |
| 1146 case AlertRole: | |
| 1147 return liveRegionStatusAssertive; | |
| 1148 case LogRole: | |
| 1149 case StatusRole: | |
| 1150 return liveRegionStatusPolite; | |
| 1151 case TimerRole: | |
| 1152 case MarqueeRole: | |
| 1153 return liveRegionStatusOff; | |
| 1154 default: | |
| 1155 break; | |
| 1156 } | |
| 1157 } | |
| 1158 | |
| 1159 return liveRegionStatus; | |
| 1160 } | |
| 1161 | |
| 1162 const AtomicString& AXRenderObject::liveRegionRelevant() const | |
| 1163 { | |
| 1164 DEFINE_STATIC_LOCAL(const AtomicString, defaultLiveRegionRelevant, ("additio
ns text", AtomicString::ConstructFromLiteral)); | |
| 1165 const AtomicString& relevant = getAttribute(aria_relevantAttr); | |
| 1166 | |
| 1167 // Default aria-relevant = "additions text". | |
| 1168 if (relevant.isEmpty()) | |
| 1169 return defaultLiveRegionRelevant; | |
| 1170 | |
| 1171 return relevant; | |
| 1172 } | |
| 1173 | |
| 1174 bool AXRenderObject::liveRegionAtomic() const | |
| 1175 { | |
| 1176 return elementAttributeValue(aria_atomicAttr); | |
| 1177 } | |
| 1178 | |
| 1179 bool AXRenderObject::liveRegionBusy() const | |
| 1180 { | |
| 1181 return elementAttributeValue(aria_busyAttr); | |
| 1182 } | |
| 1183 | |
| 1184 // | |
| 1185 // Accessibility Text. | |
| 1186 // | |
| 1187 | |
| 1188 String AXRenderObject::textUnderElement() const | |
| 1189 { | |
| 1190 if (!m_renderer) | |
| 1191 return String(); | |
| 1192 | |
| 1193 if (m_renderer->isFileUploadControl()) | |
| 1194 return toRenderFileUploadControl(m_renderer)->buttonValue(); | |
| 1195 | |
| 1196 if (m_renderer->isText()) | |
| 1197 return toRenderText(m_renderer)->plainText(); | |
| 1198 | |
| 1199 return AXNodeObject::textUnderElement(); | |
| 1200 } | |
| 1201 | |
| 1202 // | |
| 1203 // Accessibility Text - (To be deprecated). | |
| 1204 // | |
| 1205 | |
| 1206 String AXRenderObject::helpText() const | |
| 1207 { | |
| 1208 if (!m_renderer) | |
| 1209 return String(); | |
| 1210 | |
| 1211 const AtomicString& ariaHelp = getAttribute(aria_helpAttr); | |
| 1212 if (!ariaHelp.isEmpty()) | |
| 1213 return ariaHelp; | |
| 1214 | |
| 1215 String describedBy = ariaDescribedByAttribute(); | |
| 1216 if (!describedBy.isEmpty()) | |
| 1217 return describedBy; | |
| 1218 | |
| 1219 String description = accessibilityDescription(); | |
| 1220 for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) { | |
| 1221 if (curr->node() && curr->node()->isHTMLElement()) { | |
| 1222 const AtomicString& summary = toElement(curr->node())->getAttribute(
summaryAttr); | |
| 1223 if (!summary.isEmpty()) | |
| 1224 return summary; | |
| 1225 | |
| 1226 // The title attribute should be used as help text unless it is alre
ady being used as descriptive text. | |
| 1227 const AtomicString& title = toElement(curr->node())->getAttribute(ti
tleAttr); | |
| 1228 if (!title.isEmpty() && description != title) | |
| 1229 return title; | |
| 1230 } | |
| 1231 | |
| 1232 // Only take help text from an ancestor element if its a group or an unk
nown role. If help was | |
| 1233 // added to those kinds of elements, it is likely it was meant for a chi
ld element. | |
| 1234 AXObject* axObj = axObjectCache()->getOrCreate(curr); | |
| 1235 if (axObj) { | |
| 1236 AccessibilityRole role = axObj->roleValue(); | |
| 1237 if (role != GroupRole && role != UnknownRole) | |
| 1238 break; | |
| 1239 } | |
| 1240 } | |
| 1241 | |
| 1242 return String(); | |
| 1243 } | |
| 1244 | |
| 1245 // | |
| 1246 // Position and size. | |
| 1247 // | |
| 1248 | |
| 1249 void AXRenderObject::checkCachedElementRect() const | |
| 1250 { | |
| 1251 if (m_cachedElementRectDirty) | |
| 1252 return; | |
| 1253 | |
| 1254 if (!m_renderer) | |
| 1255 return; | |
| 1256 | |
| 1257 if (!m_renderer->isBox()) | |
| 1258 return; | |
| 1259 | |
| 1260 bool dirty = false; | |
| 1261 RenderBox* box = toRenderBox(m_renderer); | |
| 1262 if (box->frameRect() != m_cachedFrameRect) | |
| 1263 dirty = true; | |
| 1264 | |
| 1265 if (box->canBeScrolledAndHasScrollableArea()) { | |
| 1266 ScrollableArea* scrollableArea = box->scrollableArea(); | |
| 1267 if (scrollableArea && scrollableArea->scrollPosition() != m_cachedScroll
Position) | |
| 1268 dirty = true; | |
| 1269 } | |
| 1270 | |
| 1271 if (dirty) | |
| 1272 markCachedElementRectDirty(); | |
| 1273 } | |
| 1274 | |
| 1275 void AXRenderObject::updateCachedElementRect() const | |
| 1276 { | |
| 1277 if (!m_cachedElementRectDirty) | |
| 1278 return; | |
| 1279 | |
| 1280 if (!m_renderer) | |
| 1281 return; | |
| 1282 | |
| 1283 if (!m_renderer->isBox()) | |
| 1284 return; | |
| 1285 | |
| 1286 RenderBox* box = toRenderBox(m_renderer); | |
| 1287 m_cachedFrameRect = box->frameRect(); | |
| 1288 | |
| 1289 if (box->canBeScrolledAndHasScrollableArea()) { | |
| 1290 ScrollableArea* scrollableArea = box->scrollableArea(); | |
| 1291 if (scrollableArea) | |
| 1292 m_cachedScrollPosition = scrollableArea->scrollPosition(); | |
| 1293 } | |
| 1294 | |
| 1295 m_cachedElementRect = computeElementRect(); | |
| 1296 m_cachedElementRectDirty = false; | |
| 1297 } | |
| 1298 | |
| 1299 void AXRenderObject::markCachedElementRectDirty() const | |
| 1300 { | |
| 1301 if (m_cachedElementRectDirty) | |
| 1302 return; | |
| 1303 | |
| 1304 // Marks children recursively, if this element changed. | |
| 1305 m_cachedElementRectDirty = true; | |
| 1306 for (AXObject* child = firstChild(); child; child = child->nextSibling()) | |
| 1307 child->markCachedElementRectDirty(); | |
| 1308 } | |
| 1309 | |
| 1310 IntPoint AXRenderObject::clickPoint() | |
| 1311 { | |
| 1312 // Headings are usually much wider than their textual content. If the mid po
int is used, often it can be wrong. | |
| 1313 if (isHeading() && children().size() == 1) | |
| 1314 return children()[0]->clickPoint(); | |
| 1315 | |
| 1316 // use the default position unless this is an editable web area, in which ca
se we use the selection bounds. | |
| 1317 if (!isWebArea() || isReadOnly()) | |
| 1318 return AXObject::clickPoint(); | |
| 1319 | |
| 1320 IntRect bounds = pixelSnappedIntRect(elementRect()); | |
| 1321 return IntPoint(bounds.x() + (bounds.width() / 2), bounds.y() - (bounds.heig
ht() / 2)); | |
| 1322 } | |
| 1323 | |
| 1324 // | |
| 1325 // Hit testing. | |
| 1326 // | |
| 1327 | |
| 1328 AXObject* AXRenderObject::accessibilityHitTest(const IntPoint& point) const | |
| 1329 { | |
| 1330 if (!m_renderer || !m_renderer->hasLayer()) | |
| 1331 return 0; | |
| 1332 | |
| 1333 RenderLayer* layer = toRenderBox(m_renderer)->layer(); | |
| 1334 | |
| 1335 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active); | |
| 1336 HitTestResult hitTestResult = HitTestResult(point); | |
| 1337 layer->hitTest(request, hitTestResult); | |
| 1338 if (!hitTestResult.innerNode()) | |
| 1339 return 0; | |
| 1340 | |
| 1341 Node* node = hitTestResult.innerNode(); | |
| 1342 | |
| 1343 // Allow the hit test to return media control buttons. | |
| 1344 if (node->isInShadowTree() && (!isHTMLInputElement(*node) || !node->isMediaC
ontrolElement())) | |
| 1345 node = node->shadowHost(); | |
| 1346 | |
| 1347 if (isHTMLAreaElement(node)) | |
| 1348 return accessibilityImageMapHitTest(toHTMLAreaElement(node), point); | |
| 1349 | |
| 1350 if (isHTMLOptionElement(node)) | |
| 1351 node = toHTMLOptionElement(*node).ownerSelectElement(); | |
| 1352 | |
| 1353 RenderObject* obj = node->renderer(); | |
| 1354 if (!obj) | |
| 1355 return 0; | |
| 1356 | |
| 1357 AXObject* result = toAXObjectCacheImpl(obj->document().axObjectCache())->get
OrCreate(obj); | |
| 1358 result->updateChildrenIfNecessary(); | |
| 1359 | |
| 1360 // Allow the element to perform any hit-testing it might need to do to reach
non-render children. | |
| 1361 result = result->elementAccessibilityHitTest(point); | |
| 1362 | |
| 1363 if (result && result->accessibilityIsIgnored()) { | |
| 1364 // If this element is the label of a control, a hit test should return t
he control. | |
| 1365 if (result->isAXRenderObject()) { | |
| 1366 AXObject* controlObject = toAXRenderObject(result)->correspondingCon
trolForLabelElement(); | |
| 1367 if (controlObject && !controlObject->exposesTitleUIElement()) | |
| 1368 return controlObject; | |
| 1369 } | |
| 1370 | |
| 1371 result = result->parentObjectUnignored(); | |
| 1372 } | |
| 1373 | |
| 1374 return result; | |
| 1375 } | |
| 1376 | |
| 1377 AXObject* AXRenderObject::elementAccessibilityHitTest(const IntPoint& point) con
st | |
| 1378 { | |
| 1379 if (isSVGImage()) | |
| 1380 return remoteSVGElementHitTest(point); | |
| 1381 | |
| 1382 return AXObject::elementAccessibilityHitTest(point); | |
| 1383 } | |
| 1384 | |
| 1385 // | |
| 1386 // High-level accessibility tree access. | |
| 1387 // | |
| 1388 | |
| 1389 AXObject* AXRenderObject::computeParent() const | |
| 1390 { | |
| 1391 if (!m_renderer) | |
| 1392 return 0; | |
| 1393 | |
| 1394 if (ariaRoleAttribute() == MenuBarRole) | |
| 1395 return axObjectCache()->getOrCreate(m_renderer->parent()); | |
| 1396 | |
| 1397 // menuButton and its corresponding menu are DOM siblings, but Accessibility
needs them to be parent/child | |
| 1398 if (ariaRoleAttribute() == MenuRole) { | |
| 1399 AXObject* parent = menuButtonForMenu(); | |
| 1400 if (parent) | |
| 1401 return parent; | |
| 1402 } | |
| 1403 | |
| 1404 RenderObject* parentObj = renderParentObject(); | |
| 1405 if (parentObj) | |
| 1406 return axObjectCache()->getOrCreate(parentObj); | |
| 1407 | |
| 1408 // WebArea's parent should be the scroll view containing it. | |
| 1409 if (isWebArea()) | |
| 1410 return axObjectCache()->getOrCreate(m_renderer->frame()->view()); | |
| 1411 | |
| 1412 return 0; | |
| 1413 } | |
| 1414 | |
| 1415 AXObject* AXRenderObject::computeParentIfExists() const | |
| 1416 { | |
| 1417 if (!m_renderer) | |
| 1418 return 0; | |
| 1419 | |
| 1420 if (ariaRoleAttribute() == MenuBarRole) | |
| 1421 return axObjectCache()->get(m_renderer->parent()); | |
| 1422 | |
| 1423 // menuButton and its corresponding menu are DOM siblings, but Accessibility
needs them to be parent/child | |
| 1424 if (ariaRoleAttribute() == MenuRole) { | |
| 1425 AXObject* parent = menuButtonForMenu(); | |
| 1426 if (parent) | |
| 1427 return parent; | |
| 1428 } | |
| 1429 | |
| 1430 RenderObject* parentObj = renderParentObject(); | |
| 1431 if (parentObj) | |
| 1432 return axObjectCache()->get(parentObj); | |
| 1433 | |
| 1434 // WebArea's parent should be the scroll view containing it. | |
| 1435 if (isWebArea()) | |
| 1436 return axObjectCache()->get(m_renderer->frame()->view()); | |
| 1437 | |
| 1438 return 0; | |
| 1439 } | |
| 1440 | |
| 1441 // | |
| 1442 // Low-level accessibility tree exploration, only for use within the accessibili
ty module. | |
| 1443 // | |
| 1444 | |
| 1445 AXObject* AXRenderObject::firstChild() const | |
| 1446 { | |
| 1447 if (!m_renderer) | |
| 1448 return 0; | |
| 1449 | |
| 1450 RenderObject* firstChild = firstChildConsideringContinuation(m_renderer); | |
| 1451 | |
| 1452 if (!firstChild) | |
| 1453 return 0; | |
| 1454 | |
| 1455 return axObjectCache()->getOrCreate(firstChild); | |
| 1456 } | |
| 1457 | |
| 1458 AXObject* AXRenderObject::nextSibling() const | |
| 1459 { | |
| 1460 if (!m_renderer) | |
| 1461 return 0; | |
| 1462 | |
| 1463 RenderObject* nextSibling = 0; | |
| 1464 | |
| 1465 RenderInline* inlineContinuation = m_renderer->isRenderBlock() ? toRenderBlo
ck(m_renderer)->inlineElementContinuation() : 0; | |
| 1466 if (inlineContinuation) { | |
| 1467 // Case 1: node is a block and has an inline continuation. Next sibling
is the inline continuation's first child. | |
| 1468 nextSibling = firstChildConsideringContinuation(inlineContinuation); | |
| 1469 } else if (m_renderer->isAnonymousBlock() && lastChildHasContinuation(m_rend
erer)) { | |
| 1470 // Case 2: Anonymous block parent of the start of a continuation - skip
all the way to | |
| 1471 // after the parent of the end, since everything in between will be link
ed up via the continuation. | |
| 1472 RenderObject* lastParent = endOfContinuations(toRenderBlock(m_renderer)-
>lastChild())->parent(); | |
| 1473 while (lastChildHasContinuation(lastParent)) | |
| 1474 lastParent = endOfContinuations(lastParent->slowLastChild())->parent
(); | |
| 1475 nextSibling = lastParent->nextSibling(); | |
| 1476 } else if (RenderObject* ns = m_renderer->nextSibling()) { | |
| 1477 // Case 3: node has an actual next sibling | |
| 1478 nextSibling = ns; | |
| 1479 } else if (isInlineWithContinuation(m_renderer)) { | |
| 1480 // Case 4: node is an inline with a continuation. Next sibling is the ne
xt sibling of the end | |
| 1481 // of the continuation chain. | |
| 1482 nextSibling = endOfContinuations(m_renderer)->nextSibling(); | |
| 1483 } else if (isInlineWithContinuation(m_renderer->parent())) { | |
| 1484 // Case 5: node has no next sibling, and its parent is an inline with a
continuation. | |
| 1485 RenderObject* continuation = toRenderInline(m_renderer->parent())->conti
nuation(); | |
| 1486 | |
| 1487 if (continuation->isRenderBlock()) { | |
| 1488 // Case 5a: continuation is a block - in this case the block itself
is the next sibling. | |
| 1489 nextSibling = continuation; | |
| 1490 } else { | |
| 1491 // Case 5b: continuation is an inline - in this case the inline's fi
rst child is the next sibling. | |
| 1492 nextSibling = firstChildConsideringContinuation(continuation); | |
| 1493 } | |
| 1494 } | |
| 1495 | |
| 1496 if (!nextSibling) | |
| 1497 return 0; | |
| 1498 | |
| 1499 return axObjectCache()->getOrCreate(nextSibling); | |
| 1500 } | |
| 1501 | |
| 1502 void AXRenderObject::addChildren() | |
| 1503 { | |
| 1504 // If the need to add more children in addition to existing children arises, | |
| 1505 // childrenChanged should have been called, leaving the object with no child
ren. | |
| 1506 ASSERT(!m_haveChildren); | |
| 1507 | |
| 1508 m_haveChildren = true; | |
| 1509 | |
| 1510 if (!canHaveChildren()) | |
| 1511 return; | |
| 1512 | |
| 1513 for (RefPtr<AXObject> obj = firstChild(); obj; obj = obj->nextSibling()) | |
| 1514 addChild(obj.get()); | |
| 1515 | |
| 1516 addHiddenChildren(); | |
| 1517 addAttachmentChildren(); | |
| 1518 addPopupChildren(); | |
| 1519 addImageMapChildren(); | |
| 1520 addTextFieldChildren(); | |
| 1521 addCanvasChildren(); | |
| 1522 addRemoteSVGChildren(); | |
| 1523 addInlineTextBoxChildren(); | |
| 1524 | |
| 1525 for (unsigned i = 0; i < m_children.size(); ++i) { | |
| 1526 if (!m_children[i].get()->cachedParentObject()) | |
| 1527 m_children[i].get()->setParent(this); | |
| 1528 } | |
| 1529 } | |
| 1530 | |
| 1531 bool AXRenderObject::canHaveChildren() const | |
| 1532 { | |
| 1533 if (!m_renderer) | |
| 1534 return false; | |
| 1535 | |
| 1536 return AXNodeObject::canHaveChildren(); | |
| 1537 } | |
| 1538 | |
| 1539 void AXRenderObject::updateChildrenIfNecessary() | |
| 1540 { | |
| 1541 if (needsToUpdateChildren()) | |
| 1542 clearChildren(); | |
| 1543 | |
| 1544 AXObject::updateChildrenIfNecessary(); | |
| 1545 } | |
| 1546 | |
| 1547 void AXRenderObject::clearChildren() | |
| 1548 { | |
| 1549 AXObject::clearChildren(); | |
| 1550 m_childrenDirty = false; | |
| 1551 } | |
| 1552 | |
| 1553 AXObject* AXRenderObject::observableObject() const | |
| 1554 { | |
| 1555 // Find the object going up the parent chain that is used in accessibility t
o monitor certain notifications. | |
| 1556 for (RenderObject* renderer = m_renderer; renderer && renderer->node(); rend
erer = renderer->parent()) { | |
| 1557 if (renderObjectIsObservable(renderer)) | |
| 1558 return axObjectCache()->getOrCreate(renderer); | |
| 1559 } | |
| 1560 | |
| 1561 return 0; | |
| 1562 } | |
| 1563 | |
| 1564 // | |
| 1565 // Properties of the object's owning document or page. | |
| 1566 // | |
| 1567 | |
| 1568 double AXRenderObject::estimatedLoadingProgress() const | |
| 1569 { | |
| 1570 if (!m_renderer) | |
| 1571 return 0; | |
| 1572 | |
| 1573 if (isLoaded()) | |
| 1574 return 1.0; | |
| 1575 | |
| 1576 if (LocalFrame* frame = m_renderer->document().frame()) | |
| 1577 return frame->loader().progress().estimatedProgress(); | |
| 1578 return 0; | |
| 1579 } | |
| 1580 | |
| 1581 // | |
| 1582 // DOM and Render tree access. | |
| 1583 // | |
| 1584 | |
| 1585 Node* AXRenderObject::node() const | |
| 1586 { | |
| 1587 return m_renderer ? m_renderer->node() : 0; | |
| 1588 } | |
| 1589 | |
| 1590 Document* AXRenderObject::document() const | |
| 1591 { | |
| 1592 if (!m_renderer) | |
| 1593 return 0; | |
| 1594 return &m_renderer->document(); | |
| 1595 } | |
| 1596 | |
| 1597 FrameView* AXRenderObject::documentFrameView() const | |
| 1598 { | |
| 1599 if (!m_renderer) | |
| 1600 return 0; | |
| 1601 | |
| 1602 // this is the RenderObject's Document's LocalFrame's FrameView | |
| 1603 return m_renderer->document().view(); | |
| 1604 } | |
| 1605 | |
| 1606 Element* AXRenderObject::anchorElement() const | |
| 1607 { | |
| 1608 if (!m_renderer) | |
| 1609 return 0; | |
| 1610 | |
| 1611 AXObjectCacheImpl* cache = axObjectCache(); | |
| 1612 RenderObject* currRenderer; | |
| 1613 | |
| 1614 // Search up the render tree for a RenderObject with a DOM node. Defer to an
earlier continuation, though. | |
| 1615 for (currRenderer = m_renderer; currRenderer && !currRenderer->node(); currR
enderer = currRenderer->parent()) { | |
| 1616 if (currRenderer->isAnonymousBlock()) { | |
| 1617 RenderObject* continuation = toRenderBlock(currRenderer)->continuati
on(); | |
| 1618 if (continuation) | |
| 1619 return cache->getOrCreate(continuation)->anchorElement(); | |
| 1620 } | |
| 1621 } | |
| 1622 | |
| 1623 // bail if none found | |
| 1624 if (!currRenderer) | |
| 1625 return 0; | |
| 1626 | |
| 1627 // search up the DOM tree for an anchor element | |
| 1628 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElem
ent | |
| 1629 Node* node = currRenderer->node(); | |
| 1630 for ( ; node; node = node->parentNode()) { | |
| 1631 if (isHTMLAnchorElement(*node) || (node->renderer() && cache->getOrCreat
e(node->renderer())->isAnchor())) | |
| 1632 return toElement(node); | |
| 1633 } | |
| 1634 | |
| 1635 return 0; | |
| 1636 } | |
| 1637 | |
| 1638 Widget* AXRenderObject::widgetForAttachmentView() const | |
| 1639 { | |
| 1640 if (!isAttachment()) | |
| 1641 return 0; | |
| 1642 return toRenderPart(m_renderer)->widget(); | |
| 1643 } | |
| 1644 | |
| 1645 // | |
| 1646 // Selected text. | |
| 1647 // | |
| 1648 | |
| 1649 AXObject::PlainTextRange AXRenderObject::selectedTextRange() const | |
| 1650 { | |
| 1651 if (!isTextControl()) | |
| 1652 return PlainTextRange(); | |
| 1653 | |
| 1654 if (isPasswordFieldAndShouldHideValue()) | |
| 1655 return PlainTextRange(); | |
| 1656 | |
| 1657 AccessibilityRole ariaRole = ariaRoleAttribute(); | |
| 1658 if (isNativeTextControl() && ariaRole == UnknownRole && m_renderer->isTextCo
ntrol()) { | |
| 1659 HTMLTextFormControlElement* textControl = toRenderTextControl(m_renderer
)->textFormControlElement(); | |
| 1660 return PlainTextRange(textControl->selectionStart(), textControl->select
ionEnd() - textControl->selectionStart()); | |
| 1661 } | |
| 1662 | |
| 1663 if (ariaRole == UnknownRole) | |
| 1664 return PlainTextRange(); | |
| 1665 | |
| 1666 return ariaSelectedTextRange(); | |
| 1667 } | |
| 1668 | |
| 1669 VisibleSelection AXRenderObject::selection() const | |
| 1670 { | |
| 1671 return m_renderer->frame()->selection().selection(); | |
| 1672 } | |
| 1673 | |
| 1674 // | |
| 1675 // Modify or take an action on an object. | |
| 1676 // | |
| 1677 | |
| 1678 void AXRenderObject::setSelectedTextRange(const PlainTextRange& range) | |
| 1679 { | |
| 1680 if (isNativeTextControl() && m_renderer->isTextControl()) { | |
| 1681 HTMLTextFormControlElement* textControl = toRenderTextControl(m_renderer
)->textFormControlElement(); | |
| 1682 textControl->setSelectionRange(range.start, range.start + range.length); | |
| 1683 return; | |
| 1684 } | |
| 1685 | |
| 1686 Document& document = m_renderer->document(); | |
| 1687 LocalFrame* frame = document.frame(); | |
| 1688 if (!frame) | |
| 1689 return; | |
| 1690 Node* node = m_renderer->node(); | |
| 1691 frame->selection().setSelection(VisibleSelection(Position(node, range.start,
Position::PositionIsOffsetInAnchor), | |
| 1692 Position(node, range.start + range.length, Position::PositionIsOffsetInA
nchor), DOWNSTREAM)); | |
| 1693 } | |
| 1694 | |
| 1695 void AXRenderObject::setValue(const String& string) | |
| 1696 { | |
| 1697 if (!node() || !node()->isElementNode()) | |
| 1698 return; | |
| 1699 if (!m_renderer || !m_renderer->isBoxModelObject()) | |
| 1700 return; | |
| 1701 | |
| 1702 RenderBoxModelObject* renderer = toRenderBoxModelObject(m_renderer); | |
| 1703 if (renderer->isTextField() && isHTMLInputElement(*node())) | |
| 1704 toHTMLInputElement(*node()).setValue(string); | |
| 1705 else if (renderer->isTextArea() && isHTMLTextAreaElement(*node())) | |
| 1706 toHTMLTextAreaElement(*node()).setValue(string); | |
| 1707 } | |
| 1708 | |
| 1709 // FIXME: This function should use an IntSize to avoid the conversion below. | |
| 1710 void AXRenderObject::scrollTo(const IntPoint& point) const | |
| 1711 { | |
| 1712 if (!m_renderer || !m_renderer->isBox()) | |
| 1713 return; | |
| 1714 | |
| 1715 RenderBox* box = toRenderBox(m_renderer); | |
| 1716 if (!box->canBeScrolledAndHasScrollableArea()) | |
| 1717 return; | |
| 1718 | |
| 1719 box->scrollToOffset(IntSize(point.x(), point.y())); | |
| 1720 } | |
| 1721 | |
| 1722 // | |
| 1723 // Notifications that this object may have changed. | |
| 1724 // | |
| 1725 | |
| 1726 void AXRenderObject::handleActiveDescendantChanged() | |
| 1727 { | |
| 1728 Element* element = toElement(renderer()->node()); | |
| 1729 if (!element) | |
| 1730 return; | |
| 1731 Document& doc = renderer()->document(); | |
| 1732 if (!doc.frame()->selection().isFocusedAndActive() || doc.focusedElement() !
= element) | |
| 1733 return; | |
| 1734 AXRenderObject* activedescendant = toAXRenderObject(activeDescendant()); | |
| 1735 | |
| 1736 if (activedescendant && shouldNotifyActiveDescendant()) | |
| 1737 toAXObjectCacheImpl(doc.axObjectCache())->postNotification(m_renderer, A
XObjectCacheImpl::AXActiveDescendantChanged, true); | |
| 1738 } | |
| 1739 | |
| 1740 void AXRenderObject::handleAriaExpandedChanged() | |
| 1741 { | |
| 1742 // Find if a parent of this object should handle aria-expanded changes. | |
| 1743 AXObject* containerParent = this->parentObject(); | |
| 1744 while (containerParent) { | |
| 1745 bool foundParent = false; | |
| 1746 | |
| 1747 switch (containerParent->roleValue()) { | |
| 1748 case TreeRole: | |
| 1749 case TreeGridRole: | |
| 1750 case GridRole: | |
| 1751 case TableRole: | |
| 1752 case BrowserRole: | |
| 1753 foundParent = true; | |
| 1754 break; | |
| 1755 default: | |
| 1756 break; | |
| 1757 } | |
| 1758 | |
| 1759 if (foundParent) | |
| 1760 break; | |
| 1761 | |
| 1762 containerParent = containerParent->parentObject(); | |
| 1763 } | |
| 1764 | |
| 1765 // Post that the row count changed. | |
| 1766 if (containerParent) | |
| 1767 axObjectCache()->postNotification(containerParent, document(), AXObjectC
acheImpl::AXRowCountChanged, true); | |
| 1768 | |
| 1769 // Post that the specific row either collapsed or expanded. | |
| 1770 AccessibilityExpanded expanded = isExpanded(); | |
| 1771 if (!expanded) | |
| 1772 return; | |
| 1773 | |
| 1774 if (roleValue() == RowRole || roleValue() == TreeItemRole) { | |
| 1775 AXObjectCacheImpl::AXNotification notification = AXObjectCacheImpl::AXRo
wExpanded; | |
| 1776 if (expanded == ExpandedCollapsed) | |
| 1777 notification = AXObjectCacheImpl::AXRowCollapsed; | |
| 1778 | |
| 1779 axObjectCache()->postNotification(this, document(), notification, true); | |
| 1780 } | |
| 1781 } | |
| 1782 | |
| 1783 void AXRenderObject::textChanged() | |
| 1784 { | |
| 1785 if (!m_renderer) | |
| 1786 return; | |
| 1787 | |
| 1788 Settings* settings = document()->settings(); | |
| 1789 if (settings && settings->inlineTextBoxAccessibilityEnabled() && roleValue()
== StaticTextRole) | |
| 1790 childrenChanged(); | |
| 1791 | |
| 1792 // Do this last - AXNodeObject::textChanged posts live region announcements, | |
| 1793 // and we should update the inline text boxes first. | |
| 1794 AXNodeObject::textChanged(); | |
| 1795 } | |
| 1796 | |
| 1797 // | |
| 1798 // Text metrics. Most of these should be deprecated, needs major cleanup. | |
| 1799 // | |
| 1800 | |
| 1801 // NOTE: Consider providing this utility method as AX API | |
| 1802 int AXRenderObject::index(const VisiblePosition& position) const | |
| 1803 { | |
| 1804 if (position.isNull() || !isTextControl()) | |
| 1805 return -1; | |
| 1806 | |
| 1807 if (renderObjectContainsPosition(m_renderer, position.deepEquivalent())) | |
| 1808 return indexForVisiblePosition(position); | |
| 1809 | |
| 1810 return -1; | |
| 1811 } | |
| 1812 | |
| 1813 VisiblePosition AXRenderObject::visiblePositionForIndex(int index) const | |
| 1814 { | |
| 1815 if (!m_renderer) | |
| 1816 return VisiblePosition(); | |
| 1817 | |
| 1818 if (isNativeTextControl() && m_renderer->isTextControl()) | |
| 1819 return toRenderTextControl(m_renderer)->textFormControlElement()->visibl
ePositionForIndex(index); | |
| 1820 | |
| 1821 if (!allowsTextRanges() && !m_renderer->isText()) | |
| 1822 return VisiblePosition(); | |
| 1823 | |
| 1824 Node* node = m_renderer->node(); | |
| 1825 if (!node) | |
| 1826 return VisiblePosition(); | |
| 1827 | |
| 1828 if (index <= 0) | |
| 1829 return VisiblePosition(firstPositionInOrBeforeNode(node), DOWNSTREAM); | |
| 1830 | |
| 1831 Position start, end; | |
| 1832 bool selected = Range::selectNodeContents(node, start, end); | |
| 1833 if (!selected) | |
| 1834 return VisiblePosition(); | |
| 1835 | |
| 1836 CharacterIterator it(start, end); | |
| 1837 it.advance(index - 1); | |
| 1838 return VisiblePosition(Position(it.endContainer(), it.endOffset(), Position:
:PositionIsOffsetInAnchor), UPSTREAM); | |
| 1839 } | |
| 1840 | |
| 1841 int AXRenderObject::indexForVisiblePosition(const VisiblePosition& pos) const | |
| 1842 { | |
| 1843 if (isNativeTextControl() && m_renderer->isTextControl()) { | |
| 1844 HTMLTextFormControlElement* textControl = toRenderTextControl(m_renderer
)->textFormControlElement(); | |
| 1845 return textControl->indexForVisiblePosition(pos); | |
| 1846 } | |
| 1847 | |
| 1848 if (!isTextControl()) | |
| 1849 return 0; | |
| 1850 | |
| 1851 Node* node = m_renderer->node(); | |
| 1852 if (!node) | |
| 1853 return 0; | |
| 1854 | |
| 1855 Position indexPosition = pos.deepEquivalent(); | |
| 1856 if (indexPosition.isNull() || highestEditableRoot(indexPosition, HasEditable
AXRole) != node) | |
| 1857 return 0; | |
| 1858 | |
| 1859 RefPtrWillBeRawPtr<Range> range = Range::create(m_renderer->document()); | |
| 1860 range->setStart(node, 0, IGNORE_EXCEPTION); | |
| 1861 range->setEnd(indexPosition, IGNORE_EXCEPTION); | |
| 1862 | |
| 1863 return TextIterator::rangeLength(range.get()); | |
| 1864 } | |
| 1865 | |
| 1866 void AXRenderObject::addInlineTextBoxChildren() | |
| 1867 { | |
| 1868 Settings* settings = document()->settings(); | |
| 1869 if (!settings || !settings->inlineTextBoxAccessibilityEnabled()) | |
| 1870 return; | |
| 1871 | |
| 1872 if (!renderer() || !renderer()->isText()) | |
| 1873 return; | |
| 1874 | |
| 1875 if (renderer()->needsLayout()) { | |
| 1876 // If a RenderText needs layout, its inline text boxes are either | |
| 1877 // nonexistent or invalid, so defer until the layout happens and | |
| 1878 // the renderer calls AXObjectCacheImpl::inlineTextBoxesUpdated. | |
| 1879 return; | |
| 1880 } | |
| 1881 | |
| 1882 RenderText* renderText = toRenderText(renderer()); | |
| 1883 for (RefPtr<AbstractInlineTextBox> box = renderText->firstAbstractInlineText
Box(); box.get(); box = box->nextInlineTextBox()) { | |
| 1884 AXObject* axObject = axObjectCache()->getOrCreate(box.get()); | |
| 1885 if (!axObject->accessibilityIsIgnored()) | |
| 1886 m_children.append(axObject); | |
| 1887 } | |
| 1888 } | |
| 1889 | |
| 1890 void AXRenderObject::lineBreaks(Vector<int>& lineBreaks) const | |
| 1891 { | |
| 1892 if (!isTextControl()) | |
| 1893 return; | |
| 1894 | |
| 1895 VisiblePosition visiblePos = visiblePositionForIndex(0); | |
| 1896 VisiblePosition savedVisiblePos = visiblePos; | |
| 1897 visiblePos = nextLinePosition(visiblePos, 0); | |
| 1898 while (!visiblePos.isNull() && visiblePos != savedVisiblePos) { | |
| 1899 lineBreaks.append(indexForVisiblePosition(visiblePos)); | |
| 1900 savedVisiblePos = visiblePos; | |
| 1901 visiblePos = nextLinePosition(visiblePos, 0); | |
| 1902 } | |
| 1903 } | |
| 1904 | |
| 1905 // | |
| 1906 // Private. | |
| 1907 // | |
| 1908 | |
| 1909 bool AXRenderObject::isAllowedChildOfTree() const | |
| 1910 { | |
| 1911 // Determine if this is in a tree. If so, we apply special behavior to make
it work like an AXOutline. | |
| 1912 AXObject* axObj = parentObject(); | |
| 1913 bool isInTree = false; | |
| 1914 while (axObj) { | |
| 1915 if (axObj->isTree()) { | |
| 1916 isInTree = true; | |
| 1917 break; | |
| 1918 } | |
| 1919 axObj = axObj->parentObject(); | |
| 1920 } | |
| 1921 | |
| 1922 // If the object is in a tree, only tree items should be exposed (and the ch
ildren of tree items). | |
| 1923 if (isInTree) { | |
| 1924 AccessibilityRole role = roleValue(); | |
| 1925 if (role != TreeItemRole && role != StaticTextRole) | |
| 1926 return false; | |
| 1927 } | |
| 1928 return true; | |
| 1929 } | |
| 1930 | |
| 1931 void AXRenderObject::ariaListboxSelectedChildren(AccessibilityChildrenVector& re
sult) | |
| 1932 { | |
| 1933 bool isMulti = isMultiSelectable(); | |
| 1934 | |
| 1935 AccessibilityChildrenVector childObjects = children(); | |
| 1936 unsigned childrenSize = childObjects.size(); | |
| 1937 for (unsigned k = 0; k < childrenSize; ++k) { | |
| 1938 // Every child should have aria-role option, and if so, check for select
ed attribute/state. | |
| 1939 AXObject* child = childObjects[k].get(); | |
| 1940 if (child->isSelected() && child->ariaRoleAttribute() == ListBoxOptionRo
le) { | |
| 1941 result.append(child); | |
| 1942 if (!isMulti) | |
| 1943 return; | |
| 1944 } | |
| 1945 } | |
| 1946 } | |
| 1947 | |
| 1948 AXObject::PlainTextRange AXRenderObject::ariaSelectedTextRange() const | |
| 1949 { | |
| 1950 Node* node = m_renderer->node(); | |
| 1951 if (!node) | |
| 1952 return PlainTextRange(); | |
| 1953 | |
| 1954 VisibleSelection visibleSelection = selection(); | |
| 1955 RefPtrWillBeRawPtr<Range> currentSelectionRange = visibleSelection.toNormali
zedRange(); | |
| 1956 if (!currentSelectionRange || !currentSelectionRange->intersectsNode(node, I
GNORE_EXCEPTION)) | |
| 1957 return PlainTextRange(); | |
| 1958 | |
| 1959 int start = indexForVisiblePosition(visibleSelection.visibleStart()); | |
| 1960 int end = indexForVisiblePosition(visibleSelection.visibleEnd()); | |
| 1961 | |
| 1962 return PlainTextRange(start, end - start); | |
| 1963 } | |
| 1964 | |
| 1965 bool AXRenderObject::nodeIsTextControl(const Node* node) const | |
| 1966 { | |
| 1967 if (!node) | |
| 1968 return false; | |
| 1969 | |
| 1970 const AXObject* axObjectForNode = axObjectCache()->getOrCreate(const_cast<No
de*>(node)); | |
| 1971 if (!axObjectForNode) | |
| 1972 return false; | |
| 1973 | |
| 1974 return axObjectForNode->isTextControl(); | |
| 1975 } | |
| 1976 | |
| 1977 bool AXRenderObject::isTabItemSelected() const | |
| 1978 { | |
| 1979 if (!isTabItem() || !m_renderer) | |
| 1980 return false; | |
| 1981 | |
| 1982 Node* node = m_renderer->node(); | |
| 1983 if (!node || !node->isElementNode()) | |
| 1984 return false; | |
| 1985 | |
| 1986 // The ARIA spec says a tab item can also be selected if it is aria-labeled
by a tabpanel | |
| 1987 // that has keyboard focus inside of it, or if a tabpanel in its aria-contro
ls list has KB | |
| 1988 // focus inside of it. | |
| 1989 AXObject* focusedElement = focusedUIElement(); | |
| 1990 if (!focusedElement) | |
| 1991 return false; | |
| 1992 | |
| 1993 WillBeHeapVector<RawPtrWillBeMember<Element> > elements; | |
| 1994 elementsFromAttribute(elements, aria_controlsAttr); | |
| 1995 | |
| 1996 unsigned count = elements.size(); | |
| 1997 for (unsigned k = 0; k < count; ++k) { | |
| 1998 Element* element = elements[k]; | |
| 1999 AXObject* tabPanel = axObjectCache()->getOrCreate(element); | |
| 2000 | |
| 2001 // A tab item should only control tab panels. | |
| 2002 if (!tabPanel || tabPanel->roleValue() != TabPanelRole) | |
| 2003 continue; | |
| 2004 | |
| 2005 AXObject* checkFocusElement = focusedElement; | |
| 2006 // Check if the focused element is a descendant of the element controlle
d by the tab item. | |
| 2007 while (checkFocusElement) { | |
| 2008 if (tabPanel == checkFocusElement) | |
| 2009 return true; | |
| 2010 checkFocusElement = checkFocusElement->parentObject(); | |
| 2011 } | |
| 2012 } | |
| 2013 | |
| 2014 return false; | |
| 2015 } | |
| 2016 | |
| 2017 AXObject* AXRenderObject::accessibilityImageMapHitTest(HTMLAreaElement* area, co
nst IntPoint& point) const | |
| 2018 { | |
| 2019 if (!area) | |
| 2020 return 0; | |
| 2021 | |
| 2022 AXObject* parent = axObjectCache()->getOrCreate(area->imageElement()); | |
| 2023 if (!parent) | |
| 2024 return 0; | |
| 2025 | |
| 2026 AXObject::AccessibilityChildrenVector children = parent->children(); | |
| 2027 unsigned count = children.size(); | |
| 2028 for (unsigned k = 0; k < count; ++k) { | |
| 2029 if (children[k]->elementRect().contains(point)) | |
| 2030 return children[k].get(); | |
| 2031 } | |
| 2032 | |
| 2033 return 0; | |
| 2034 } | |
| 2035 | |
| 2036 bool AXRenderObject::renderObjectIsObservable(RenderObject* renderer) const | |
| 2037 { | |
| 2038 // AX clients will listen for AXValueChange on a text control. | |
| 2039 if (renderer->isTextControl()) | |
| 2040 return true; | |
| 2041 | |
| 2042 // AX clients will listen for AXSelectedChildrenChanged on listboxes. | |
| 2043 Node* node = renderer->node(); | |
| 2044 if (nodeHasRole(node, "listbox") || (renderer->isBoxModelObject() && toRende
rBoxModelObject(renderer)->isListBox())) | |
| 2045 return true; | |
| 2046 | |
| 2047 // Textboxes should send out notifications. | |
| 2048 if (nodeHasRole(node, "textbox")) | |
| 2049 return true; | |
| 2050 | |
| 2051 return false; | |
| 2052 } | |
| 2053 | |
| 2054 RenderObject* AXRenderObject::renderParentObject() const | |
| 2055 { | |
| 2056 if (!m_renderer) | |
| 2057 return 0; | |
| 2058 | |
| 2059 RenderObject* startOfConts = m_renderer->isRenderBlock() ? startOfContinuati
ons(m_renderer) : 0; | |
| 2060 if (startOfConts) { | |
| 2061 // Case 1: node is a block and is an inline's continuation. Parent | |
| 2062 // is the start of the continuation chain. | |
| 2063 return startOfConts; | |
| 2064 } | |
| 2065 | |
| 2066 RenderObject* parent = m_renderer->parent(); | |
| 2067 startOfConts = parent && parent->isRenderInline() ? startOfContinuations(par
ent) : 0; | |
| 2068 if (startOfConts) { | |
| 2069 // Case 2: node's parent is an inline which is some node's continuation;
parent is | |
| 2070 // the earliest node in the continuation chain. | |
| 2071 return startOfConts; | |
| 2072 } | |
| 2073 | |
| 2074 RenderObject* firstChild = parent ? parent->slowFirstChild() : 0; | |
| 2075 if (firstChild && firstChild->node()) { | |
| 2076 // Case 3: The first sibling is the beginning of a continuation chain. F
ind the origin of that continuation. | |
| 2077 // Get the node's renderer and follow that continuation chain until the
first child is found. | |
| 2078 for (RenderObject* nodeRenderFirstChild = firstChild->node()->renderer()
; nodeRenderFirstChild != firstChild; nodeRenderFirstChild = firstChild->node()-
>renderer()) { | |
| 2079 for (RenderObject* contsTest = nodeRenderFirstChild; contsTest; cont
sTest = nextContinuation(contsTest)) { | |
| 2080 if (contsTest == firstChild) { | |
| 2081 parent = nodeRenderFirstChild->parent(); | |
| 2082 break; | |
| 2083 } | |
| 2084 } | |
| 2085 RenderObject* newFirstChild = parent->slowFirstChild(); | |
| 2086 if (firstChild == newFirstChild) | |
| 2087 break; | |
| 2088 firstChild = newFirstChild; | |
| 2089 if (!firstChild->node()) | |
| 2090 break; | |
| 2091 } | |
| 2092 } | |
| 2093 | |
| 2094 return parent; | |
| 2095 } | |
| 2096 | |
| 2097 bool AXRenderObject::isDescendantOfElementType(const HTMLQualifiedName& tagName)
const | |
| 2098 { | |
| 2099 for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->p
arent()) { | |
| 2100 if (parent->node() && parent->node()->hasTagName(tagName)) | |
| 2101 return true; | |
| 2102 } | |
| 2103 return false; | |
| 2104 } | |
| 2105 | |
| 2106 bool AXRenderObject::isSVGImage() const | |
| 2107 { | |
| 2108 return remoteSVGRootElement(); | |
| 2109 } | |
| 2110 | |
| 2111 void AXRenderObject::detachRemoteSVGRoot() | |
| 2112 { | |
| 2113 if (AXSVGRoot* root = remoteSVGRootElement()) | |
| 2114 root->setParent(0); | |
| 2115 } | |
| 2116 | |
| 2117 AXSVGRoot* AXRenderObject::remoteSVGRootElement() const | |
| 2118 { | |
| 2119 if (!m_renderer || !m_renderer->isRenderImage()) | |
| 2120 return 0; | |
| 2121 | |
| 2122 ImageResource* cachedImage = toRenderImage(m_renderer)->cachedImage(); | |
| 2123 if (!cachedImage) | |
| 2124 return 0; | |
| 2125 | |
| 2126 Image* image = cachedImage->image(); | |
| 2127 if (!image || !image->isSVGImage()) | |
| 2128 return 0; | |
| 2129 | |
| 2130 FrameView* frameView = toSVGImage(image)->frameView(); | |
| 2131 if (!frameView) | |
| 2132 return 0; | |
| 2133 Document* doc = frameView->frame().document(); | |
| 2134 if (!doc || !doc->isSVGDocument()) | |
| 2135 return 0; | |
| 2136 | |
| 2137 Settings* settings = doc->settings(); | |
| 2138 if (settings && !settings->accessibilityEnabled()) | |
| 2139 settings->setAccessibilityEnabled(true); | |
| 2140 | |
| 2141 SVGSVGElement* rootElement = doc->accessSVGExtensions().rootElement(); | |
| 2142 if (!rootElement) | |
| 2143 return 0; | |
| 2144 RenderObject* rendererRoot = rootElement->renderer(); | |
| 2145 if (!rendererRoot) | |
| 2146 return 0; | |
| 2147 | |
| 2148 AXObject* rootSVGObject = toAXObjectCacheImpl(doc->axObjectCache())->getOrCr
eate(rendererRoot); | |
| 2149 | |
| 2150 // In order to connect the AX hierarchy from the SVG root element from the l
oaded resource | |
| 2151 // the parent must be set, because there's no other way to get back to who c
reated the image. | |
| 2152 ASSERT(rootSVGObject && rootSVGObject->isAXSVGRoot()); | |
| 2153 if (!rootSVGObject->isAXSVGRoot()) | |
| 2154 return 0; | |
| 2155 | |
| 2156 return toAXSVGRoot(rootSVGObject); | |
| 2157 } | |
| 2158 | |
| 2159 AXObject* AXRenderObject::remoteSVGElementHitTest(const IntPoint& point) const | |
| 2160 { | |
| 2161 AXObject* remote = remoteSVGRootElement(); | |
| 2162 if (!remote) | |
| 2163 return 0; | |
| 2164 | |
| 2165 IntSize offset = point - roundedIntPoint(elementRect().location()); | |
| 2166 return remote->accessibilityHitTest(IntPoint(offset)); | |
| 2167 } | |
| 2168 | |
| 2169 // The boundingBox for elements within the remote SVG element needs to be offset
by its position | |
| 2170 // within the parent page, otherwise they are in relative coordinates only. | |
| 2171 void AXRenderObject::offsetBoundingBoxForRemoteSVGElement(LayoutRect& rect) cons
t | |
| 2172 { | |
| 2173 for (AXObject* parent = parentObject(); parent; parent = parent->parentObjec
t()) { | |
| 2174 if (parent->isAXSVGRoot()) { | |
| 2175 rect.moveBy(parent->parentObject()->elementRect().location()); | |
| 2176 break; | |
| 2177 } | |
| 2178 } | |
| 2179 } | |
| 2180 | |
| 2181 // Hidden children are those that are not rendered or visible, but are specifica
lly marked as aria-hidden=false, | |
| 2182 // meaning that they should be exposed to the AX hierarchy. | |
| 2183 void AXRenderObject::addHiddenChildren() | |
| 2184 { | |
| 2185 Node* node = this->node(); | |
| 2186 if (!node) | |
| 2187 return; | |
| 2188 | |
| 2189 // First do a quick run through to determine if we have any hidden nodes (mo
st often we will not). | |
| 2190 // If we do have hidden nodes, we need to determine where to insert them so
they match DOM order as close as possible. | |
| 2191 bool shouldInsertHiddenNodes = false; | |
| 2192 for (Node* child = node->firstChild(); child; child = child->nextSibling())
{ | |
| 2193 if (!child->renderer() && isNodeAriaVisible(child)) { | |
| 2194 shouldInsertHiddenNodes = true; | |
| 2195 break; | |
| 2196 } | |
| 2197 } | |
| 2198 | |
| 2199 if (!shouldInsertHiddenNodes) | |
| 2200 return; | |
| 2201 | |
| 2202 // Iterate through all of the children, including those that may have alread
y been added, and | |
| 2203 // try to insert hidden nodes in the correct place in the DOM order. | |
| 2204 unsigned insertionIndex = 0; | |
| 2205 for (Node* child = node->firstChild(); child; child = child->nextSibling())
{ | |
| 2206 if (child->renderer()) { | |
| 2207 // Find out where the last render sibling is located within m_childr
en. | |
| 2208 AXObject* childObject = axObjectCache()->get(child->renderer()); | |
| 2209 if (childObject && childObject->accessibilityIsIgnored()) { | |
| 2210 AccessibilityChildrenVector children = childObject->children(); | |
| 2211 if (children.size()) | |
| 2212 childObject = children.last().get(); | |
| 2213 else | |
| 2214 childObject = 0; | |
| 2215 } | |
| 2216 | |
| 2217 if (childObject) | |
| 2218 insertionIndex = m_children.find(childObject) + 1; | |
| 2219 continue; | |
| 2220 } | |
| 2221 | |
| 2222 if (!isNodeAriaVisible(child)) | |
| 2223 continue; | |
| 2224 | |
| 2225 unsigned previousSize = m_children.size(); | |
| 2226 if (insertionIndex > previousSize) | |
| 2227 insertionIndex = previousSize; | |
| 2228 | |
| 2229 insertChild(axObjectCache()->getOrCreate(child), insertionIndex); | |
| 2230 insertionIndex += (m_children.size() - previousSize); | |
| 2231 } | |
| 2232 } | |
| 2233 | |
| 2234 void AXRenderObject::addTextFieldChildren() | |
| 2235 { | |
| 2236 Node* node = this->node(); | |
| 2237 if (!isHTMLInputElement(node)) | |
| 2238 return; | |
| 2239 | |
| 2240 HTMLInputElement& input = toHTMLInputElement(*node); | |
| 2241 Element* spinButtonElement = input.userAgentShadowRoot()->getElementById(Sha
dowElementNames::spinButton()); | |
| 2242 if (!spinButtonElement || !spinButtonElement->isSpinButtonElement()) | |
| 2243 return; | |
| 2244 | |
| 2245 AXSpinButton* axSpinButton = toAXSpinButton(axObjectCache()->getOrCreate(Spi
nButtonRole)); | |
| 2246 axSpinButton->setSpinButtonElement(toSpinButtonElement(spinButtonElement)); | |
| 2247 axSpinButton->setParent(this); | |
| 2248 m_children.append(axSpinButton); | |
| 2249 } | |
| 2250 | |
| 2251 void AXRenderObject::addImageMapChildren() | |
| 2252 { | |
| 2253 RenderBoxModelObject* cssBox = renderBoxModelObject(); | |
| 2254 if (!cssBox || !cssBox->isRenderImage()) | |
| 2255 return; | |
| 2256 | |
| 2257 HTMLMapElement* map = toRenderImage(cssBox)->imageMap(); | |
| 2258 if (!map) | |
| 2259 return; | |
| 2260 | |
| 2261 for (HTMLAreaElement& area : Traversal<HTMLAreaElement>::descendantsOf(*map)
) { | |
| 2262 // add an <area> element for this child if it has a link | |
| 2263 if (area.isLink()) { | |
| 2264 AXImageMapLink* areaObject = toAXImageMapLink(axObjectCache()->getOr
Create(ImageMapLinkRole)); | |
| 2265 areaObject->setHTMLAreaElement(&area); | |
| 2266 areaObject->setHTMLMapElement(map); | |
| 2267 areaObject->setParent(this); | |
| 2268 if (!areaObject->accessibilityIsIgnored()) | |
| 2269 m_children.append(areaObject); | |
| 2270 else | |
| 2271 axObjectCache()->remove(areaObject->axObjectID()); | |
| 2272 } | |
| 2273 } | |
| 2274 } | |
| 2275 | |
| 2276 void AXRenderObject::addCanvasChildren() | |
| 2277 { | |
| 2278 if (!isHTMLCanvasElement(node())) | |
| 2279 return; | |
| 2280 | |
| 2281 // If it's a canvas, it won't have rendered children, but it might have acce
ssible fallback content. | |
| 2282 // Clear m_haveChildren because AXNodeObject::addChildren will expect it to
be false. | |
| 2283 ASSERT(!m_children.size()); | |
| 2284 m_haveChildren = false; | |
| 2285 AXNodeObject::addChildren(); | |
| 2286 } | |
| 2287 | |
| 2288 void AXRenderObject::addAttachmentChildren() | |
| 2289 { | |
| 2290 if (!isAttachment()) | |
| 2291 return; | |
| 2292 | |
| 2293 // FrameView's need to be inserted into the AX hierarchy when encountered. | |
| 2294 Widget* widget = widgetForAttachmentView(); | |
| 2295 if (!widget || !widget->isFrameView()) | |
| 2296 return; | |
| 2297 | |
| 2298 AXObject* axWidget = axObjectCache()->getOrCreate(widget); | |
| 2299 if (!axWidget->accessibilityIsIgnored()) | |
| 2300 m_children.append(axWidget); | |
| 2301 } | |
| 2302 | |
| 2303 void AXRenderObject::addPopupChildren() | |
| 2304 { | |
| 2305 if (!isHTMLInputElement(node())) | |
| 2306 return; | |
| 2307 if (AXObject* axPopup = toHTMLInputElement(node())->popupRootAXObject()) | |
| 2308 m_children.append(axPopup); | |
| 2309 } | |
| 2310 | |
| 2311 void AXRenderObject::addRemoteSVGChildren() | |
| 2312 { | |
| 2313 AXSVGRoot* root = remoteSVGRootElement(); | |
| 2314 if (!root) | |
| 2315 return; | |
| 2316 | |
| 2317 root->setParent(this); | |
| 2318 | |
| 2319 if (root->accessibilityIsIgnored()) { | |
| 2320 AccessibilityChildrenVector children = root->children(); | |
| 2321 unsigned length = children.size(); | |
| 2322 for (unsigned i = 0; i < length; ++i) | |
| 2323 m_children.append(children[i]); | |
| 2324 } else { | |
| 2325 m_children.append(root); | |
| 2326 } | |
| 2327 } | |
| 2328 | |
| 2329 void AXRenderObject::ariaSelectedRows(AccessibilityChildrenVector& result) | |
| 2330 { | |
| 2331 // Get all the rows. | |
| 2332 AccessibilityChildrenVector allRows; | |
| 2333 if (isTree()) | |
| 2334 ariaTreeRows(allRows); | |
| 2335 else if (isAXTable() && toAXTable(this)->supportsSelectedRows()) | |
| 2336 allRows = toAXTable(this)->rows(); | |
| 2337 | |
| 2338 // Determine which rows are selected. | |
| 2339 bool isMulti = isMultiSelectable(); | |
| 2340 | |
| 2341 // Prefer active descendant over aria-selected. | |
| 2342 AXObject* activeDesc = activeDescendant(); | |
| 2343 if (activeDesc && (activeDesc->isTreeItem() || activeDesc->isTableRow())) { | |
| 2344 result.append(activeDesc); | |
| 2345 if (!isMulti) | |
| 2346 return; | |
| 2347 } | |
| 2348 | |
| 2349 unsigned count = allRows.size(); | |
| 2350 for (unsigned k = 0; k < count; ++k) { | |
| 2351 if (allRows[k]->isSelected()) { | |
| 2352 result.append(allRows[k]); | |
| 2353 if (!isMulti) | |
| 2354 break; | |
| 2355 } | |
| 2356 } | |
| 2357 } | |
| 2358 | |
| 2359 bool AXRenderObject::elementAttributeValue(const QualifiedName& attributeName) c
onst | |
| 2360 { | |
| 2361 if (!m_renderer) | |
| 2362 return false; | |
| 2363 | |
| 2364 return equalIgnoringCase(getAttribute(attributeName), "true"); | |
| 2365 } | |
| 2366 | |
| 2367 bool AXRenderObject::inheritsPresentationalRole() const | |
| 2368 { | |
| 2369 // ARIA states if an item can get focus, it should not be presentational. | |
| 2370 if (canSetFocusAttribute()) | |
| 2371 return false; | |
| 2372 | |
| 2373 // ARIA spec says that when a parent object is presentational, and it has re
quired child elements, | |
| 2374 // those child elements are also presentational. For example, <li> becomes p
resentational from <ul>. | |
| 2375 // http://www.w3.org/WAI/PF/aria/complete#presentation | |
| 2376 if (roleValue() != ListItemRole && roleValue() != ListMarkerRole) | |
| 2377 return false; | |
| 2378 | |
| 2379 AXObject* parent = parentObject(); | |
| 2380 if (!parent->isAXRenderObject()) | |
| 2381 return false; | |
| 2382 | |
| 2383 Node* elementNode = toAXRenderObject(parent)->node(); | |
| 2384 if (!elementNode || !elementNode->isElementNode()) | |
| 2385 return false; | |
| 2386 | |
| 2387 QualifiedName tagName = toElement(elementNode)->tagQName(); | |
| 2388 if (tagName == ulTag || tagName == olTag || tagName == dlTag) | |
| 2389 return (parent->roleValue() == NoneRole || parent->roleValue() == Presen
tationalRole); | |
| 2390 | |
| 2391 return false; | |
| 2392 } | |
| 2393 | |
| 2394 LayoutRect AXRenderObject::computeElementRect() const | |
| 2395 { | |
| 2396 RenderObject* obj = m_renderer; | |
| 2397 | |
| 2398 if (!obj) | |
| 2399 return LayoutRect(); | |
| 2400 | |
| 2401 if (obj->node()) // If we are a continuation, we want to make sure to use th
e primary renderer. | |
| 2402 obj = obj->node()->renderer(); | |
| 2403 | |
| 2404 // absoluteFocusRingBoundingBox will query the hierarchy below this element,
which for large webpages can be very slow. | |
| 2405 // For a web area, which will have the most elements of any element, absolut
eQuads should be used. | |
| 2406 // We should also use absoluteQuads for SVG elements, otherwise transforms w
on't be applied. | |
| 2407 | |
| 2408 LayoutRect result; | |
| 2409 if (obj->isText()) { | |
| 2410 Vector<FloatQuad> quads; | |
| 2411 toRenderText(obj)->absoluteQuads(quads, 0, RenderText::ClipToEllipsis); | |
| 2412 result = boundingBoxForQuads(obj, quads); | |
| 2413 } else if (isWebArea() || obj->isSVGRoot()) { | |
| 2414 result = obj->absoluteBoundingBoxRect(); | |
| 2415 } else { | |
| 2416 result = obj->absoluteFocusRingBoundingBoxRect(); | |
| 2417 } | |
| 2418 | |
| 2419 Document* document = this->document(); | |
| 2420 if (document && document->isSVGDocument()) | |
| 2421 offsetBoundingBoxForRemoteSVGElement(result); | |
| 2422 if (document && document->frame() && document->frame()->pagePopupOwner()) { | |
| 2423 IntPoint popupOrigin = document->view()->contentsToScreen(IntRect()).loc
ation(); | |
| 2424 IntPoint mainOrigin = axObjectCache()->rootObject()->documentFrameView()
->contentsToScreen(IntRect()).location(); | |
| 2425 result.moveBy(IntPoint(popupOrigin - mainOrigin)); | |
| 2426 } | |
| 2427 | |
| 2428 // The size of the web area should be the content size, not the clipped size
. | |
| 2429 if (isWebArea() && obj->frame()->view()) | |
| 2430 result.setSize(obj->frame()->view()->contentsSize()); | |
| 2431 | |
| 2432 // Checkboxes and radio buttons include their label as part of their rect. | |
| 2433 if (isCheckboxOrRadio()) { | |
| 2434 HTMLLabelElement* label = labelForElement(toElement(m_renderer->node()))
; | |
| 2435 if (label && label->renderer()) { | |
| 2436 LayoutRect labelRect = axObjectCache()->getOrCreate(label)->elementR
ect(); | |
| 2437 result.unite(labelRect); | |
| 2438 } | |
| 2439 } | |
| 2440 | |
| 2441 return result; | |
| 2442 } | |
| 2443 | |
| 2444 } // namespace blink | |
| OLD | NEW |