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