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