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