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