1 /* 2 * Copyright (C) 2008, 2009, 2010 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 31 #include "core/accessibility/AXObjectCache.h" 32 33 #include "core/HTMLNames.h" 34 #include "core/accessibility/AXARIAGrid.h" 35 #include "core/accessibility/AXARIAGridCell.h" 36 #include "core/accessibility/AXARIAGridRow.h" 37 #include "core/accessibility/AXImageMapLink.h" 38 #include "core/accessibility/AXInlineTextBox.h" 39 #include "core/accessibility/AXList.h" 40 #include "core/accessibility/AXListBox.h" 41 #include "core/accessibility/AXListBoxOption.h" 42 #include "core/accessibility/AXMediaControls.h" 43 #include "core/accessibility/AXMenuList.h" 44 #include "core/accessibility/AXMenuListOption.h" 45 #include "core/accessibility/AXMenuListPopup.h" 46 #include "core/accessibility/AXProgressIndicator.h" 47 #include "core/accessibility/AXRenderObject.h" 48 #include "core/accessibility/AXSVGRoot.h" 49 #include "core/accessibility/AXScrollView.h" 50 #include "core/accessibility/AXScrollbar.h" 51 #include "core/accessibility/AXSlider.h" 52 #include "core/accessibility/AXSpinButton.h" 53 #include "core/accessibility/AXTable.h" 54 #include "core/accessibility/AXTableCell.h" 55 #include "core/accessibility/AXTableColumn.h" 56 #include "core/accessibility/AXTableHeaderContainer.h" 57 #include "core/accessibility/AXTableRow.h" 58 #include "core/dom/Document.h" 59 #include "core/frame/LocalFrame.h" 60 #include "core/frame/Settings.h" 61 #include "core/html/HTMLAreaElement.h" 62 #include "core/html/HTMLImageElement.h" 63 #include "core/html/HTMLInputElement.h" 64 #include "core/html/HTMLLabelElement.h" 65 #include "core/page/Chrome.h" 66 #include "core/page/ChromeClient.h" 67 #include "core/page/FocusController.h" 68 #include "core/page/Page.h" 69 #include "core/rendering/AbstractInlineTextBox.h" 70 #include "core/rendering/RenderListBox.h" 71 #include "core/rendering/RenderMenuList.h" 72 #include "core/rendering/RenderProgress.h" 73 #include "core/rendering/RenderSlider.h" 74 #include "core/rendering/RenderTable.h" 75 #include "core/rendering/RenderTableCell.h" 76 #include "core/rendering/RenderTableRow.h" 77 #include "core/rendering/RenderView.h" 78 #include "platform/scroll/ScrollView.h" 79 #include "wtf/PassRefPtr.h" 80 81 namespace blink { 82 83 using namespace HTMLNames; 84 85 AXObjectInclusion AXComputedObjectAttributeCache::getIgnored(AXID id) const 86 { 87 HashMap<AXID, CachedAXObjectAttributes>::const_iterator it = m_idMapping.find(id); 88 return it != m_idMapping.end() ? it->value.ignored : DefaultBehavior; 89 } 90 91 void AXComputedObjectAttributeCache::setIgnored(AXID id, AXObjectInclusion inclusion) 92 { 93 HashMap<AXID, CachedAXObjectAttributes>::iterator it = m_idMapping.find(id); 94 if (it != m_idMapping.end()) { 95 it->value.ignored = inclusion; 96 } else { 97 CachedAXObjectAttributes attributes; 98 attributes.ignored = inclusion; 99 m_idMapping.set(id, attributes); 100 } 101 } 102 103 void AXComputedObjectAttributeCache::clear() 104 { 105 m_idMapping.clear(); 106 } 107 108 AXObjectCache::AXObjectCache(Document& document) 109 : m_document(document) 110 , m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired) 111 { 112 m_computedObjectAttributeCache = AXComputedObjectAttributeCache::create(); 113 } 114 115 AXObjectCache::~AXObjectCache() 116 { 117 m_notificationPostTimer.stop(); 118 119 HashMap<AXID, RefPtr<AXObject> >::iterator end = m_objects.end(); 120 for (HashMap<AXID, RefPtr<AXObject> >::iterator it = m_objects.begin(); it != end; ++it) { 121 AXObject* obj = (*it).value.get(); 122 detachWrapper(obj); 123 obj->detach(); 124 removeAXID(obj); 125 } 126 } 127 128 AXObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement) 129 { 130 // Find the corresponding accessibility object for the HTMLAreaElement. This should be 131 // in the list of children for its corresponding image. 132 if (!areaElement) 133 return 0; 134 135 HTMLImageElement* imageElement = areaElement->imageElement(); 136 if (!imageElement) 137 return 0; 138 139 AXObject* axRenderImage = areaElement->document().axObjectCache()->getOrCreate(imageElement); 140 if (!axRenderImage) 141 return 0; 142 143 AXObject::AccessibilityChildrenVector imageChildren = axRenderImage->children(); 144 unsigned count = imageChildren.size(); 145 for (unsigned k = 0; k < count; ++k) { 146 AXObject* child = imageChildren[k].get(); 147 if (!child->isImageMapLink()) 148 continue; 149 150 if (toAXImageMapLink(child)->areaElement() == areaElement) 151 return child; 152 } 153 154 return 0; 155 } 156 157 AXObject* AXObjectCache::focusedUIElementForPage(const Page* page) 158 { 159 if (!page->settings().accessibilityEnabled()) 160 return 0; 161 162 // Cross-process accessibility is not yet implemented. 163 if (!page->focusController().focusedOrMainFrame()->isLocalFrame()) 164 return 0; 165 166 // get the focused node in the page 167 Document* focusedDocument = toLocalFrame(page->focusController().focusedOrMainFrame())->document(); 168 Node* focusedNode = focusedDocument->focusedElement(); 169 if (!focusedNode) 170 focusedNode = focusedDocument; 171 172 if (isHTMLAreaElement(*focusedNode)) 173 return focusedImageMapUIElement(toHTMLAreaElement(focusedNode)); 174 175 AXObject* obj = focusedNode->document().axObjectCache()->getOrCreate(focusedNode); 176 if (!obj) 177 return 0; 178 179 if (obj->shouldFocusActiveDescendant()) { 180 if (AXObject* descendant = obj->activeDescendant()) 181 obj = descendant; 182 } 183 184 // the HTML element, for example, is focusable but has an AX object that is ignored 185 if (obj->accessibilityIsIgnored()) 186 obj = obj->parentObjectUnignored(); 187 188 return obj; 189 } 190 191 AXObject* AXObjectCache::get(Widget* widget) 192 { 193 if (!widget) 194 return 0; 195 196 AXID axID = m_widgetObjectMapping.get(widget); 197 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); 198 if (!axID) 199 return 0; 200 201 return m_objects.get(axID); 202 } 203 204 AXObject* AXObjectCache::get(RenderObject* renderer) 205 { 206 if (!renderer) 207 return 0; 208 209 AXID axID = m_renderObjectMapping.get(renderer); 210 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); 211 if (!axID) 212 return 0; 213 214 return m_objects.get(axID); 215 } 216 217 AXObject* AXObjectCache::get(Node* node) 218 { 219 if (!node) 220 return 0; 221 222 AXID renderID = node->renderer() ? m_renderObjectMapping.get(node->renderer()) : 0; 223 ASSERT(!HashTraits<AXID>::isDeletedValue(renderID)); 224 225 AXID nodeID = m_nodeObjectMapping.get(node); 226 ASSERT(!HashTraits<AXID>::isDeletedValue(nodeID)); 227 228 if (node->renderer() && nodeID && !renderID) { 229 // This can happen if an AXNodeObject is created for a node that's not 230 // rendered, but later something changes and it gets a renderer (like if it's 231 // reparented). 232 remove(nodeID); 233 return 0; 234 } 235 236 if (renderID) 237 return m_objects.get(renderID); 238 239 if (!nodeID) 240 return 0; 241 242 return m_objects.get(nodeID); 243 } 244 245 AXObject* AXObjectCache::get(AbstractInlineTextBox* inlineTextBox) 246 { 247 if (!inlineTextBox) 248 return 0; 249 250 AXID axID = m_inlineTextBoxObjectMapping.get(inlineTextBox); 251 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); 252 if (!axID) 253 return 0; 254 255 return m_objects.get(axID); 256 } 257 258 // FIXME: This probably belongs on Node. 259 // FIXME: This should take a const char*, but one caller passes nullAtom. 260 bool nodeHasRole(Node* node, const String& role) 261 { 262 if (!node || !node->isElementNode()) 263 return false; 264 265 return equalIgnoringCase(toElement(node)->getAttribute(roleAttr), role); 266 } 267 268 static PassRefPtr<AXObject> createFromRenderer(RenderObject* renderer) 269 { 270 // FIXME: How could renderer->node() ever not be an Element? 271 Node* node = renderer->node(); 272 273 // If the node is aria role="list" or the aria role is empty and its a 274 // ul/ol/dl type (it shouldn't be a list if aria says otherwise). 275 if (node && ((nodeHasRole(node, "list") || nodeHasRole(node, "directory")) 276 || (nodeHasRole(node, nullAtom) && (isHTMLUListElement(*node) || isHTMLOListElement(*node) || isHTMLDListElement(*node))))) 277 return AXList::create(renderer); 278 279 // aria tables 280 if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid")) 281 return AXARIAGrid::create(renderer); 282 if (nodeHasRole(node, "row")) 283 return AXARIAGridRow::create(renderer); 284 if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader")) 285 return AXARIAGridCell::create(renderer); 286 287 // media controls 288 if (node && node->isMediaControlElement()) 289 return AccessibilityMediaControl::create(renderer); 290 291 if (isHTMLOptionElement(node)) 292 return AXListBoxOption::create(renderer); 293 294 if (renderer->isSVGRoot()) 295 return AXSVGRoot::create(renderer); 296 297 if (renderer->isBoxModelObject()) { 298 RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer); 299 if (cssBox->isListBox()) 300 return AXListBox::create(toRenderListBox(cssBox)); 301 if (cssBox->isMenuList()) 302 return AXMenuList::create(toRenderMenuList(cssBox)); 303 304 // standard tables 305 if (cssBox->isTable()) 306 return AXTable::create(toRenderTable(cssBox)); 307 if (cssBox->isTableRow()) 308 return AXTableRow::create(toRenderTableRow(cssBox)); 309 if (cssBox->isTableCell()) 310 return AXTableCell::create(toRenderTableCell(cssBox)); 311 312 // progress bar 313 if (cssBox->isProgress()) 314 return AXProgressIndicator::create(toRenderProgress(cssBox)); 315 316 // input type=range 317 if (cssBox->isSlider()) 318 return AXSlider::create(toRenderSlider(cssBox)); 319 } 320 321 return AXRenderObject::create(renderer); 322 } 323 324 static PassRefPtr<AXObject> createFromNode(Node* node) 325 { 326 return AXNodeObject::create(node); 327 } 328 329 static PassRefPtr<AXObject> createFromInlineTextBox(AbstractInlineTextBox* inlineTextBox) 330 { 331 return AXInlineTextBox::create(inlineTextBox); 332 } 333 334 AXObject* AXObjectCache::getOrCreate(Widget* widget) 335 { 336 if (!widget) 337 return 0; 338 339 if (AXObject* obj = get(widget)) 340 return obj; 341 342 RefPtr<AXObject> newObj = nullptr; 343 if (widget->isFrameView()) 344 newObj = AXScrollView::create(toScrollView(widget)); 345 else if (widget->isScrollbar()) 346 newObj = AXScrollbar::create(toScrollbar(widget)); 347 348 // Will crash later if we have two objects for the same widget. 349 ASSERT(!get(widget)); 350 351 // Catch the case if an (unsupported) widget type is used. Only FrameView and ScrollBar are supported now. 352 ASSERT(newObj); 353 if (!newObj) 354 return 0; 355 356 getAXID(newObj.get()); 357 358 m_widgetObjectMapping.set(widget, newObj->axObjectID()); 359 m_objects.set(newObj->axObjectID(), newObj); 360 newObj->init(); 361 attachWrapper(newObj.get()); 362 return newObj.get(); 363 } 364 365 AXObject* AXObjectCache::getOrCreate(Node* node) 366 { 367 if (!node) 368 return 0; 369 370 if (AXObject* obj = get(node)) 371 return obj; 372 373 if (node->renderer()) 374 return getOrCreate(node->renderer()); 375 376 if (!node->parentElement()) 377 return 0; 378 379 // It's only allowed to create an AXObject from a Node if it's in a canvas subtree. 380 // Or if it's a hidden element, but we still want to expose it because of other ARIA attributes. 381 bool inCanvasSubtree = node->parentElement()->isInCanvasSubtree(); 382 bool isHidden = !node->renderer() && isNodeAriaVisible(node); 383 if (!inCanvasSubtree && !isHidden) 384 return 0; 385 386 RefPtr<AXObject> newObj = createFromNode(node); 387 388 // Will crash later if we have two objects for the same node. 389 ASSERT(!get(node)); 390 391 getAXID(newObj.get()); 392 393 m_nodeObjectMapping.set(node, newObj->axObjectID()); 394 m_objects.set(newObj->axObjectID(), newObj); 395 newObj->init(); 396 attachWrapper(newObj.get()); 397 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); 398 399 return newObj.get(); 400 } 401 402 AXObject* AXObjectCache::getOrCreate(RenderObject* renderer) 403 { 404 if (!renderer) 405 return 0; 406 407 if (AXObject* obj = get(renderer)) 408 return obj; 409 410 RefPtr<AXObject> newObj = createFromRenderer(renderer); 411 412 // Will crash later if we have two objects for the same renderer. 413 ASSERT(!get(renderer)); 414 415 getAXID(newObj.get()); 416 417 m_renderObjectMapping.set(renderer, newObj->axObjectID()); 418 m_objects.set(newObj->axObjectID(), newObj); 419 newObj->init(); 420 attachWrapper(newObj.get()); 421 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); 422 423 return newObj.get(); 424 } 425 426 AXObject* AXObjectCache::getOrCreate(AbstractInlineTextBox* inlineTextBox) 427 { 428 if (!inlineTextBox) 429 return 0; 430 431 if (AXObject* obj = get(inlineTextBox)) 432 return obj; 433 434 RefPtr<AXObject> newObj = createFromInlineTextBox(inlineTextBox); 435 436 // Will crash later if we have two objects for the same inlineTextBox. 437 ASSERT(!get(inlineTextBox)); 438 439 getAXID(newObj.get()); 440 441 m_inlineTextBoxObjectMapping.set(inlineTextBox, newObj->axObjectID()); 442 m_objects.set(newObj->axObjectID(), newObj); 443 newObj->init(); 444 attachWrapper(newObj.get()); 445 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); 446 447 return newObj.get(); 448 } 449 450 AXObject* AXObjectCache::rootObject() 451 { 452 if (!accessibilityEnabled()) 453 return 0; 454 455 return getOrCreate(m_document.view()); 456 } 457 458 AXObject* AXObjectCache::getOrCreate(AccessibilityRole role) 459 { 460 RefPtr<AXObject> obj = nullptr; 461 462 // will be filled in... 463 switch (role) { 464 case ImageMapLinkRole: 465 obj = AXImageMapLink::create(); 466 break; 467 case ColumnRole: 468 obj = AXTableColumn::create(); 469 break; 470 case TableHeaderContainerRole: 471 obj = AXTableHeaderContainer::create(); 472 break; 473 case SliderThumbRole: 474 obj = AXSliderThumb::create(); 475 break; 476 case MenuListPopupRole: 477 obj = AXMenuListPopup::create(); 478 break; 479 case MenuListOptionRole: 480 obj = AXMenuListOption::create(); 481 break; 482 case SpinButtonRole: 483 obj = AXSpinButton::create(); 484 break; 485 case SpinButtonPartRole: 486 obj = AXSpinButtonPart::create(); 487 break; 488 default: 489 obj = nullptr; 490 } 491 492 if (obj) 493 getAXID(obj.get()); 494 else 495 return 0; 496 497 m_objects.set(obj->axObjectID(), obj); 498 obj->init(); 499 attachWrapper(obj.get()); 500 return obj.get(); 501 } 502 503 void AXObjectCache::remove(AXID axID) 504 { 505 if (!axID) 506 return; 507 508 // first fetch object to operate some cleanup functions on it 509 AXObject* obj = m_objects.get(axID); 510 if (!obj) 511 return; 512 513 detachWrapper(obj); 514 obj->detach(); 515 removeAXID(obj); 516 517 // finally remove the object 518 if (!m_objects.take(axID)) 519 return; 520 521 ASSERT(m_objects.size() >= m_idsInUse.size()); 522 } 523 524 void AXObjectCache::remove(RenderObject* renderer) 525 { 526 if (!renderer) 527 return; 528 529 AXID axID = m_renderObjectMapping.get(renderer); 530 remove(axID); 531 m_renderObjectMapping.remove(renderer); 532 } 533 534 void AXObjectCache::remove(Node* node) 535 { 536 if (!node) 537 return; 538 539 removeNodeForUse(node); 540 541 // This is all safe even if we didn't have a mapping. 542 AXID axID = m_nodeObjectMapping.get(node); 543 remove(axID); 544 m_nodeObjectMapping.remove(node); 545 546 if (node->renderer()) { 547 remove(node->renderer()); 548 return; 549 } 550 } 551 552 void AXObjectCache::remove(Widget* view) 553 { 554 if (!view) 555 return; 556 557 AXID axID = m_widgetObjectMapping.get(view); 558 remove(axID); 559 m_widgetObjectMapping.remove(view); 560 } 561 562 void AXObjectCache::remove(AbstractInlineTextBox* inlineTextBox) 563 { 564 if (!inlineTextBox) 565 return; 566 567 AXID axID = m_inlineTextBoxObjectMapping.get(inlineTextBox); 568 remove(axID); 569 m_inlineTextBoxObjectMapping.remove(inlineTextBox); 570 } 571 572 // FIXME: Oilpan: Use a weak hashmap for this instead. 573 void AXObjectCache::clearWeakMembers(Visitor* visitor) 574 { 575 Vector<Node*> deadNodes; 576 for (HashMap<Node*, AXID>::iterator it = m_nodeObjectMapping.begin(); it != m_nodeObjectMapping.end(); ++it) { 577 if (!visitor->isAlive(it->key)) 578 deadNodes.append(it->key); 579 } 580 for (unsigned i = 0; i < deadNodes.size(); ++i) 581 remove(deadNodes[i]); 582 } 583 584 AXID AXObjectCache::platformGenerateAXID() const 585 { 586 static AXID lastUsedID = 0; 587 588 // Generate a new ID. 589 AXID objID = lastUsedID; 590 do { 591 ++objID; 592 } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID)); 593 594 lastUsedID = objID; 595 596 return objID; 597 } 598 599 AXID AXObjectCache::getAXID(AXObject* obj) 600 { 601 // check for already-assigned ID 602 AXID objID = obj->axObjectID(); 603 if (objID) { 604 ASSERT(m_idsInUse.contains(objID)); 605 return objID; 606 } 607 608 objID = platformGenerateAXID(); 609 610 m_idsInUse.add(objID); 611 obj->setAXObjectID(objID); 612 613 return objID; 614 } 615 616 void AXObjectCache::removeAXID(AXObject* object) 617 { 618 if (!object) 619 return; 620 621 AXID objID = object->axObjectID(); 622 if (!objID) 623 return; 624 ASSERT(!HashTraits<AXID>::isDeletedValue(objID)); 625 ASSERT(m_idsInUse.contains(objID)); 626 object->setAXObjectID(0); 627 m_idsInUse.remove(objID); 628 } 629 630 void AXObjectCache::selectionChanged(Node* node) 631 { 632 // Find the nearest ancestor that already has an accessibility object, since we 633 // might be in the middle of a layout. 634 while (node) { 635 if (AXObject* obj = get(node)) { 636 obj->selectionChanged(); 637 return; 638 } 639 node = node->parentNode(); 640 } 641 } 642 643 void AXObjectCache::textChanged(Node* node) 644 { 645 textChanged(getOrCreate(node)); 646 } 647 648 void AXObjectCache::textChanged(RenderObject* renderer) 649 { 650 textChanged(getOrCreate(renderer)); 651 } 652 653 void AXObjectCache::textChanged(AXObject* obj) 654 { 655 if (!obj) 656 return; 657 658 bool parentAlreadyExists = obj->parentObjectIfExists(); 659 obj->textChanged(); 660 postNotification(obj, obj->document(), AXObjectCache::AXTextChanged, true); 661 if (parentAlreadyExists) 662 obj->notifyIfIgnoredValueChanged(); 663 } 664 665 void AXObjectCache::updateCacheAfterNodeIsAttached(Node* node) 666 { 667 // Calling get() will update the AX object if we had an AXNodeObject but now we need 668 // an AXRenderObject, because it was reparented to a location outside of a canvas. 669 get(node); 670 } 671 672 void AXObjectCache::childrenChanged(Node* node) 673 { 674 childrenChanged(get(node)); 675 } 676 677 void AXObjectCache::childrenChanged(RenderObject* renderer) 678 { 679 childrenChanged(get(renderer)); 680 } 681 682 void AXObjectCache::childrenChanged(AXObject* obj) 683 { 684 if (!obj) 685 return; 686 687 obj->childrenChanged(); 688 } 689 690 void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>*) 691 { 692 RefPtrWillBeRawPtr<Document> protectorForCacheOwner(m_document); 693 694 m_notificationPostTimer.stop(); 695 696 unsigned i = 0, count = m_notificationsToPost.size(); 697 for (i = 0; i < count; ++i) { 698 AXObject* obj = m_notificationsToPost[i].first.get(); 699 if (!obj->axObjectID()) 700 continue; 701 702 if (!obj->axObjectCache()) 703 continue; 704 705 #if ENABLE(ASSERT) 706 // Make sure none of the render views are in the process of being layed out. 707 // Notifications should only be sent after the renderer has finished 708 if (obj->isAXRenderObject()) { 709 AXRenderObject* renderObj = toAXRenderObject(obj); 710 RenderObject* renderer = renderObj->renderer(); 711 if (renderer && renderer->view()) 712 ASSERT(!renderer->view()->layoutState()); 713 } 714 #endif 715 716 AXNotification notification = m_notificationsToPost[i].second; 717 postPlatformNotification(obj, notification); 718 719 if (notification == AXChildrenChanged && obj->parentObjectIfExists() && obj->lastKnownIsIgnoredValue() != obj->accessibilityIsIgnored()) 720 childrenChanged(obj->parentObject()); 721 } 722 723 m_notificationsToPost.clear(); 724 } 725 726 void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, bool postToElement, PostType postType) 727 { 728 if (!renderer) 729 return; 730 731 m_computedObjectAttributeCache->clear(); 732 733 // Get an accessibility object that already exists. One should not be created here 734 // because a render update may be in progress and creating an AX object can re-trigger a layout 735 RefPtr<AXObject> object = get(renderer); 736 while (!object && renderer) { 737 renderer = renderer->parent(); 738 object = get(renderer); 739 } 740 741 if (!renderer) 742 return; 743 744 postNotification(object.get(), &renderer->document(), notification, postToElement, postType); 745 } 746 747 void AXObjectCache::postNotification(Node* node, AXNotification notification, bool postToElement, PostType postType) 748 { 749 if (!node) 750 return; 751 752 m_computedObjectAttributeCache->clear(); 753 754 // Get an accessibility object that already exists. One should not be created here 755 // because a render update may be in progress and creating an AX object can re-trigger a layout 756 RefPtr<AXObject> object = get(node); 757 while (!object && node) { 758 node = node->parentNode(); 759 object = get(node); 760 } 761 762 if (!node) 763 return; 764 765 postNotification(object.get(), &node->document(), notification, postToElement, postType); 766 } 767 768 void AXObjectCache::postNotification(AXObject* object, Document* document, AXNotification notification, bool postToElement, PostType postType) 769 { 770 m_computedObjectAttributeCache->clear(); 771 772 if (object && !postToElement) 773 object = object->observableObject(); 774 775 if (!object && document) 776 object = get(document->renderView()); 777 778 if (!object) 779 return; 780 781 if (postType == PostAsynchronously) { 782 m_notificationsToPost.append(std::make_pair(object, notification)); 783 if (!m_notificationPostTimer.isActive()) 784 m_notificationPostTimer.startOneShot(0, FROM_HERE); 785 } else { 786 postPlatformNotification(object, notification); 787 } 788 } 789 790 void AXObjectCache::checkedStateChanged(Node* node) 791 { 792 postNotification(node, AXObjectCache::AXCheckedStateChanged, true); 793 } 794 795 void AXObjectCache::selectedChildrenChanged(Node* node) 796 { 797 // postToElement is false so that you can pass in any child of an element and it will go up the parent tree 798 // to find the container which should send out the notification. 799 postNotification(node, AXSelectedChildrenChanged, false); 800 } 801 802 void AXObjectCache::selectedChildrenChanged(RenderObject* renderer) 803 { 804 // postToElement is false so that you can pass in any child of an element and it will go up the parent tree 805 // to find the container which should send out the notification. 806 postNotification(renderer, AXSelectedChildrenChanged, false); 807 } 808 809 void AXObjectCache::handleScrollbarUpdate(ScrollView* view) 810 { 811 if (!view) 812 return; 813 814 // We don't want to create a scroll view from this method, only update an existing one. 815 if (AXObject* scrollViewObject = get(view)) { 816 m_computedObjectAttributeCache->clear(); 817 scrollViewObject->updateChildrenIfNecessary(); 818 } 819 } 820 821 void AXObjectCache::handleLayoutComplete(RenderObject* renderer) 822 { 823 if (!renderer) 824 return; 825 826 m_computedObjectAttributeCache->clear(); 827 828 // Create the AXObject if it didn't yet exist - that's always safe at the end of a layout, and it 829 // allows an AX notification to be sent when a page has its first layout, rather than when the 830 // document first loads. 831 if (AXObject* obj = getOrCreate(renderer)) 832 postNotification(obj, obj->document(), AXLayoutComplete, true); 833 } 834 835 void AXObjectCache::handleAriaExpandedChange(Node* node) 836 { 837 if (AXObject* obj = getOrCreate(node)) 838 obj->handleAriaExpandedChanged(); 839 } 840 841 void AXObjectCache::handleActiveDescendantChanged(Node* node) 842 { 843 if (AXObject* obj = getOrCreate(node)) 844 obj->handleActiveDescendantChanged(); 845 } 846 847 void AXObjectCache::handleAriaRoleChanged(Node* node) 848 { 849 if (AXObject* obj = getOrCreate(node)) { 850 obj->updateAccessibilityRole(); 851 m_computedObjectAttributeCache->clear(); 852 obj->notifyIfIgnoredValueChanged(); 853 } 854 } 855 856 void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Element* element) 857 { 858 if (attrName == roleAttr) 859 handleAriaRoleChanged(element); 860 else if (attrName == altAttr || attrName == titleAttr) 861 textChanged(element); 862 else if (attrName == forAttr && isHTMLLabelElement(*element)) 863 labelChanged(element); 864 865 if (!attrName.localName().startsWith("aria-")) 866 return; 867 868 if (attrName == aria_activedescendantAttr) 869 handleActiveDescendantChanged(element); 870 else if (attrName == aria_valuenowAttr || attrName == aria_valuetextAttr) 871 postNotification(element, AXObjectCache::AXValueChanged, true); 872 else if (attrName == aria_labelAttr || attrName == aria_labeledbyAttr || attrName == aria_labelledbyAttr) 873 textChanged(element); 874 else if (attrName == aria_checkedAttr) 875 checkedStateChanged(element); 876 else if (attrName == aria_selectedAttr) 877 selectedChildrenChanged(element); 878 else if (attrName == aria_expandedAttr) 879 handleAriaExpandedChange(element); 880 else if (attrName == aria_hiddenAttr) 881 childrenChanged(element->parentNode()); 882 else if (attrName == aria_invalidAttr) 883 postNotification(element, AXObjectCache::AXInvalidStatusChanged, true); 884 else 885 postNotification(element, AXObjectCache::AXAriaAttributeChanged, true); 886 } 887 888 void AXObjectCache::labelChanged(Element* element) 889 { 890 textChanged(toHTMLLabelElement(element)->control()); 891 } 892 893 void AXObjectCache::recomputeIsIgnored(RenderObject* renderer) 894 { 895 if (AXObject* obj = get(renderer)) 896 obj->notifyIfIgnoredValueChanged(); 897 } 898 899 void AXObjectCache::inlineTextBoxesUpdated(RenderObject* renderer) 900 { 901 if (!inlineTextBoxAccessibilityEnabled()) 902 return; 903 904 // Only update if the accessibility object already exists and it's 905 // not already marked as dirty. 906 if (AXObject* obj = get(renderer)) { 907 if (!obj->needsToUpdateChildren()) { 908 obj->setNeedsToUpdateChildren(); 909 postNotification(renderer, AXChildrenChanged, true); 910 } 911 } 912 } 913 914 Settings* AXObjectCache::settings() 915 { 916 return m_document.settings(); 917 } 918 919 bool AXObjectCache::accessibilityEnabled() 920 { 921 Settings* settings = this->settings(); 922 if (!settings) 923 return false; 924 return settings->accessibilityEnabled(); 925 } 926 927 bool AXObjectCache::inlineTextBoxAccessibilityEnabled() 928 { 929 Settings* settings = this->settings(); 930 if (!settings) 931 return false; 932 return settings->inlineTextBoxAccessibilityEnabled(); 933 } 934 935 const Element* AXObjectCache::rootAXEditableElement(const Node* node) 936 { 937 const Element* result = node->rootEditableElement(); 938 const Element* element = node->isElementNode() ? toElement(node) : node->parentElement(); 939 940 for (; element; element = element->parentElement()) { 941 if (nodeIsTextControl(element)) 942 result = element; 943 } 944 945 return result; 946 } 947 948 bool AXObjectCache::nodeIsTextControl(const Node* node) 949 { 950 if (!node) 951 return false; 952 953 const AXObject* axObject = getOrCreate(const_cast<Node*>(node)); 954 return axObject && axObject->isTextControl(); 955 } 956 957 bool isNodeAriaVisible(Node* node) 958 { 959 if (!node) 960 return false; 961 962 if (!node->isElementNode()) 963 return false; 964 965 return equalIgnoringCase(toElement(node)->getAttribute(aria_hiddenAttr), "false"); 966 } 967 968 void AXObjectCache::detachWrapper(AXObject* obj) 969 { 970 // In Chromium, AXObjects are not wrapped. 971 } 972 973 void AXObjectCache::attachWrapper(AXObject*) 974 { 975 // In Chromium, AXObjects are not wrapped. 976 } 977 978 void AXObjectCache::postPlatformNotification(AXObject* obj, AXNotification notification) 979 { 980 if (obj && obj->isAXScrollbar() && notification == AXValueChanged) { 981 // Send document value changed on scrollbar value changed notification. 982 Scrollbar* scrollBar = toAXScrollbar(obj)->scrollbar(); 983 if (!scrollBar || !scrollBar->parent() || !scrollBar->parent()->isFrameView()) 984 return; 985 Document* document = toFrameView(scrollBar->parent())->frame().document(); 986 if (document != document->topDocument()) 987 return; 988 obj = get(document->renderView()); 989 } 990 991 if (!obj || !obj->document() || !obj->documentFrameView() || !obj->documentFrameView()->frame().page()) 992 return; 993 994 ChromeClient& client = obj->document()->axObjectCacheOwner().page()->chrome().client(); 995 996 if (notification == AXActiveDescendantChanged 997 && obj->document()->focusedElement() 998 && obj->node() == obj->document()->focusedElement()) { 999 // Calling handleFocusedUIElementChanged will focus the new active 1000 // descendant and send the AXFocusedUIElementChanged notification. 1001 handleFocusedUIElementChanged(0, obj->document()->focusedElement()); 1002 } 1003 1004 client.postAccessibilityNotification(obj, notification); 1005 } 1006 1007 void AXObjectCache::handleFocusedUIElementChanged(Node*, Node* newFocusedNode) 1008 { 1009 if (!newFocusedNode) 1010 return; 1011 1012 Page* page = newFocusedNode->document().page(); 1013 if (!page) 1014 return; 1015 1016 AXObject* focusedObject = focusedUIElementForPage(page); 1017 if (!focusedObject) 1018 return; 1019 1020 postPlatformNotification(focusedObject, AXFocusedUIElementChanged); 1021 } 1022 1023 void AXObjectCache::handleScrolledToAnchor(const Node* anchorNode) 1024 { 1025 // The anchor node may not be accessible. Post the notification for the 1026 // first accessible object. 1027 postPlatformNotification(AXObject::firstAccessibleObjectFromNode(anchorNode), AXScrolledToAnchor); 1028 } 1029 1030 void AXObjectCache::handleScrollPositionChanged(ScrollView* scrollView) 1031 { 1032 postPlatformNotification(getOrCreate(scrollView), AXScrollPositionChanged); 1033 } 1034 1035 void AXObjectCache::handleScrollPositionChanged(RenderObject* renderObject) 1036 { 1037 postPlatformNotification(getOrCreate(renderObject), AXScrollPositionChanged); 1038 } 1039 1040 void AXObjectCache::setCanvasObjectBounds(Element* element, const LayoutRect& rect) 1041 { 1042 AXObject* obj = getOrCreate(element); 1043 if (!obj) 1044 return; 1045 1046 obj->setElementRect(rect); 1047 } 1048 1049 } // namespace blink 1050