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#include "AXObjectCache.h" 31 32#include "AccessibilityARIAGrid.h" 33#include "AccessibilityARIAGridCell.h" 34#include "AccessibilityARIAGridRow.h" 35#include "AccessibilityImageMapLink.h" 36#include "AccessibilityList.h" 37#include "AccessibilityListBox.h" 38#include "AccessibilityListBoxOption.h" 39#include "AccessibilityMediaControls.h" 40#include "AccessibilityMenuList.h" 41#include "AccessibilityMenuListOption.h" 42#include "AccessibilityMenuListPopup.h" 43#include "AccessibilityProgressIndicator.h" 44#include "AccessibilityRenderObject.h" 45#include "AccessibilityScrollView.h" 46#include "AccessibilityScrollbar.h" 47#include "AccessibilitySlider.h" 48#include "AccessibilityTable.h" 49#include "AccessibilityTableCell.h" 50#include "AccessibilityTableColumn.h" 51#include "AccessibilityTableHeaderContainer.h" 52#include "AccessibilityTableRow.h" 53#include "Document.h" 54#include "FocusController.h" 55#include "Frame.h" 56#include "HTMLAreaElement.h" 57#include "HTMLImageElement.h" 58#include "HTMLNames.h" 59#if ENABLE(VIDEO) 60#include "MediaControlElements.h" 61#endif 62#include "InputElement.h" 63#include "Page.h" 64#include "RenderListBox.h" 65#include "RenderMenuList.h" 66#include "RenderProgress.h" 67#include "RenderSlider.h" 68#include "RenderTable.h" 69#include "RenderTableCell.h" 70#include "RenderTableRow.h" 71#include "RenderView.h" 72#include "ScrollView.h" 73 74#include <wtf/PassRefPtr.h> 75 76namespace WebCore { 77 78using namespace HTMLNames; 79 80bool AXObjectCache::gAccessibilityEnabled = false; 81bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false; 82 83AXObjectCache::AXObjectCache(const Document* doc) 84 : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired) 85{ 86 m_document = const_cast<Document*>(doc); 87} 88 89AXObjectCache::~AXObjectCache() 90{ 91 HashMap<AXID, RefPtr<AccessibilityObject> >::iterator end = m_objects.end(); 92 for (HashMap<AXID, RefPtr<AccessibilityObject> >::iterator it = m_objects.begin(); it != end; ++it) { 93 AccessibilityObject* obj = (*it).second.get(); 94 detachWrapper(obj); 95 obj->detach(); 96 removeAXID(obj); 97 } 98} 99 100AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement) 101{ 102 // Find the corresponding accessibility object for the HTMLAreaElement. This should be 103 // in the list of children for its corresponding image. 104 if (!areaElement) 105 return 0; 106 107 HTMLImageElement* imageElement = areaElement->imageElement(); 108 if (!imageElement) 109 return 0; 110 111 AccessibilityObject* axRenderImage = areaElement->document()->axObjectCache()->getOrCreate(imageElement->renderer()); 112 if (!axRenderImage) 113 return 0; 114 115 AccessibilityObject::AccessibilityChildrenVector imageChildren = axRenderImage->children(); 116 unsigned count = imageChildren.size(); 117 for (unsigned k = 0; k < count; ++k) { 118 AccessibilityObject* child = imageChildren[k].get(); 119 if (!child->isImageMapLink()) 120 continue; 121 122 if (static_cast<AccessibilityImageMapLink*>(child)->areaElement() == areaElement) 123 return child; 124 } 125 126 return 0; 127} 128 129AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page) 130{ 131 // get the focused node in the page 132 Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document(); 133 Node* focusedNode = focusedDocument->focusedNode(); 134 if (!focusedNode) 135 focusedNode = focusedDocument; 136 137 if (focusedNode->hasTagName(areaTag)) 138 return focusedImageMapUIElement(static_cast<HTMLAreaElement*>(focusedNode)); 139 140 RenderObject* focusedNodeRenderer = focusedNode->renderer(); 141 if (!focusedNodeRenderer) 142 return 0; 143 144 AccessibilityObject* obj = focusedNodeRenderer->document()->axObjectCache()->getOrCreate(focusedNodeRenderer); 145 146 if (obj->shouldFocusActiveDescendant()) { 147 if (AccessibilityObject* descendant = obj->activeDescendant()) 148 obj = descendant; 149 } 150 151 // the HTML element, for example, is focusable but has an AX object that is ignored 152 if (obj->accessibilityIsIgnored()) 153 obj = obj->parentObjectUnignored(); 154 155 return obj; 156} 157 158AccessibilityObject* AXObjectCache::get(Widget* widget) 159{ 160 if (!widget) 161 return 0; 162 163 AXID axID = m_widgetObjectMapping.get(widget); 164 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); 165 if (!axID) 166 return 0; 167 168 return m_objects.get(axID).get(); 169} 170 171AccessibilityObject* AXObjectCache::get(RenderObject* renderer) 172{ 173 if (!renderer) 174 return 0; 175 176 AXID axID = m_renderObjectMapping.get(renderer); 177 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); 178 if (!axID) 179 return 0; 180 181 return m_objects.get(axID).get(); 182} 183 184// FIXME: This probably belongs on Node. 185// FIXME: This should take a const char*, but one caller passes nullAtom. 186bool nodeHasRole(Node* node, const String& role) 187{ 188 if (!node || !node->isElementNode()) 189 return false; 190 191 return equalIgnoringCase(static_cast<Element*>(node)->getAttribute(roleAttr), role); 192} 193 194static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer) 195{ 196 // FIXME: How could renderer->node() ever not be an Element? 197 Node* node = renderer->node(); 198 199 // If the node is aria role="list" or the aria role is empty and its a 200 // ul/ol/dl type (it shouldn't be a list if aria says otherwise). 201 if (node && ((nodeHasRole(node, "list") || nodeHasRole(node, "directory")) 202 || (nodeHasRole(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag))))) 203 return AccessibilityList::create(renderer); 204 205 // aria tables 206 if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid")) 207 return AccessibilityARIAGrid::create(renderer); 208 if (nodeHasRole(node, "row")) 209 return AccessibilityARIAGridRow::create(renderer); 210 if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader")) 211 return AccessibilityARIAGridCell::create(renderer); 212 213#if ENABLE(VIDEO) 214 // media controls 215 if (node && node->isMediaControlElement()) 216 return AccessibilityMediaControl::create(renderer); 217#endif 218 219 if (renderer->isBoxModelObject()) { 220 RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer); 221 if (cssBox->isListBox()) 222 return AccessibilityListBox::create(toRenderListBox(cssBox)); 223 if (cssBox->isMenuList()) 224 return AccessibilityMenuList::create(toRenderMenuList(cssBox)); 225 226 // standard tables 227 if (cssBox->isTable()) 228 return AccessibilityTable::create(toRenderTable(cssBox)); 229 if (cssBox->isTableRow()) 230 return AccessibilityTableRow::create(toRenderTableRow(cssBox)); 231 if (cssBox->isTableCell()) 232 return AccessibilityTableCell::create(toRenderTableCell(cssBox)); 233 234#if ENABLE(PROGRESS_TAG) 235 // progress bar 236 if (cssBox->isProgress()) 237 return AccessibilityProgressIndicator::create(toRenderProgress(cssBox)); 238#endif 239 240 // input type=range 241 if (cssBox->isSlider()) 242 return AccessibilitySlider::create(toRenderSlider(cssBox)); 243 } 244 245 return AccessibilityRenderObject::create(renderer); 246} 247 248AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget) 249{ 250 if (!widget) 251 return 0; 252 253 if (AccessibilityObject* obj = get(widget)) 254 return obj; 255 256 RefPtr<AccessibilityObject> newObj = 0; 257 if (widget->isFrameView()) 258 newObj = AccessibilityScrollView::create(static_cast<ScrollView*>(widget)); 259 else if (widget->isScrollbar()) 260 newObj = AccessibilityScrollbar::create(static_cast<Scrollbar*>(widget)); 261 262 getAXID(newObj.get()); 263 264 m_widgetObjectMapping.set(widget, newObj->axObjectID()); 265 m_objects.set(newObj->axObjectID(), newObj); 266 attachWrapper(newObj.get()); 267 return newObj.get(); 268} 269 270AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer) 271{ 272 if (!renderer) 273 return 0; 274 275 if (AccessibilityObject* obj = get(renderer)) 276 return obj; 277 278 RefPtr<AccessibilityObject> newObj = createFromRenderer(renderer); 279 280 getAXID(newObj.get()); 281 282 m_renderObjectMapping.set(renderer, newObj->axObjectID()); 283 m_objects.set(newObj->axObjectID(), newObj); 284 attachWrapper(newObj.get()); 285 return newObj.get(); 286} 287 288AccessibilityObject* AXObjectCache::rootObject() 289{ 290 return getOrCreate(m_document->view()); 291} 292 293AccessibilityObject* AXObjectCache::rootObjectForFrame(Frame* frame) 294{ 295 if (!frame) 296 return 0; 297 return getOrCreate(frame->view()); 298} 299 300AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role) 301{ 302 RefPtr<AccessibilityObject> obj = 0; 303 304 // will be filled in... 305 switch (role) { 306 case ListBoxOptionRole: 307 obj = AccessibilityListBoxOption::create(); 308 break; 309 case ImageMapLinkRole: 310 obj = AccessibilityImageMapLink::create(); 311 break; 312 case ColumnRole: 313 obj = AccessibilityTableColumn::create(); 314 break; 315 case TableHeaderContainerRole: 316 obj = AccessibilityTableHeaderContainer::create(); 317 break; 318 case SliderThumbRole: 319 obj = AccessibilitySliderThumb::create(); 320 break; 321 case MenuListPopupRole: 322 obj = AccessibilityMenuListPopup::create(); 323 break; 324 case MenuListOptionRole: 325 obj = AccessibilityMenuListOption::create(); 326 break; 327 default: 328 obj = 0; 329 } 330 331 if (obj) 332 getAXID(obj.get()); 333 else 334 return 0; 335 336 m_objects.set(obj->axObjectID(), obj); 337 attachWrapper(obj.get()); 338 return obj.get(); 339} 340 341void AXObjectCache::remove(AXID axID) 342{ 343 if (!axID) 344 return; 345 346 // first fetch object to operate some cleanup functions on it 347 AccessibilityObject* obj = m_objects.get(axID).get(); 348 if (!obj) 349 return; 350 351 detachWrapper(obj); 352 obj->detach(); 353 removeAXID(obj); 354 355 // finally remove the object 356 if (!m_objects.take(axID)) 357 return; 358 359 ASSERT(m_objects.size() >= m_idsInUse.size()); 360} 361 362void AXObjectCache::remove(RenderObject* renderer) 363{ 364 if (!renderer) 365 return; 366 367 AXID axID = m_renderObjectMapping.get(renderer); 368 remove(axID); 369 m_renderObjectMapping.remove(renderer); 370} 371 372void AXObjectCache::remove(Widget* view) 373{ 374 if (!view) 375 return; 376 377 AXID axID = m_widgetObjectMapping.get(view); 378 remove(axID); 379 m_widgetObjectMapping.remove(view); 380} 381 382 383#if !PLATFORM(WIN) || OS(WINCE) 384AXID AXObjectCache::platformGenerateAXID() const 385{ 386 static AXID lastUsedID = 0; 387 388 // Generate a new ID. 389 AXID objID = lastUsedID; 390 do { 391 ++objID; 392 } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID)); 393 394 lastUsedID = objID; 395 396 return objID; 397} 398#endif 399 400AXID AXObjectCache::getAXID(AccessibilityObject* obj) 401{ 402 // check for already-assigned ID 403 AXID objID = obj->axObjectID(); 404 if (objID) { 405 ASSERT(m_idsInUse.contains(objID)); 406 return objID; 407 } 408 409 objID = platformGenerateAXID(); 410 411 m_idsInUse.add(objID); 412 obj->setAXObjectID(objID); 413 414 return objID; 415} 416 417void AXObjectCache::removeAXID(AccessibilityObject* object) 418{ 419 if (!object) 420 return; 421 422 AXID objID = object->axObjectID(); 423 if (!objID) 424 return; 425 ASSERT(!HashTraits<AXID>::isDeletedValue(objID)); 426 ASSERT(m_idsInUse.contains(objID)); 427 object->setAXObjectID(0); 428 m_idsInUse.remove(objID); 429} 430 431#if HAVE(ACCESSIBILITY) 432void AXObjectCache::contentChanged(RenderObject* renderer) 433{ 434 AccessibilityObject* object = getOrCreate(renderer); 435 if (object) 436 object->contentChanged(); 437} 438#endif 439 440void AXObjectCache::childrenChanged(RenderObject* renderer) 441{ 442 if (!renderer) 443 return; 444 445 AXID axID = m_renderObjectMapping.get(renderer); 446 if (!axID) 447 return; 448 449 AccessibilityObject* obj = m_objects.get(axID).get(); 450 if (obj) 451 obj->childrenChanged(); 452} 453 454void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>*) 455{ 456 m_notificationPostTimer.stop(); 457 458 unsigned i = 0, count = m_notificationsToPost.size(); 459 for (i = 0; i < count; ++i) { 460 AccessibilityObject* obj = m_notificationsToPost[i].first.get(); 461#ifndef NDEBUG 462 // Make sure none of the render views are in the process of being layed out. 463 // Notifications should only be sent after the renderer has finished 464 if (obj->isAccessibilityRenderObject()) { 465 AccessibilityRenderObject* renderObj = static_cast<AccessibilityRenderObject*>(obj); 466 RenderObject* renderer = renderObj->renderer(); 467 if (renderer && renderer->view()) 468 ASSERT(!renderer->view()->layoutState()); 469 } 470#endif 471 472 postPlatformNotification(obj, m_notificationsToPost[i].second); 473 } 474 475 m_notificationsToPost.clear(); 476} 477 478#if HAVE(ACCESSIBILITY) 479void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, bool postToElement, PostType postType) 480{ 481 // Notifications for text input objects are sent to that object. 482 // All others are sent to the top WebArea. 483 if (!renderer) 484 return; 485 486 // Get an accessibility object that already exists. One should not be created here 487 // because a render update may be in progress and creating an AX object can re-trigger a layout 488 RefPtr<AccessibilityObject> object = get(renderer); 489 while (!object && renderer) { 490 renderer = renderer->parent(); 491 object = get(renderer); 492 } 493 494 if (!renderer) 495 return; 496 497 postNotification(object.get(), renderer->document(), notification, postToElement, postType); 498} 499 500void AXObjectCache::postNotification(AccessibilityObject* object, Document* document, AXNotification notification, bool postToElement, PostType postType) 501{ 502 if (object && !postToElement) 503 object = object->observableObject(); 504 505 if (!object && document) 506 object = get(document->renderer()); 507 508 if (!object) 509 return; 510 511 if (postType == PostAsynchronously) { 512 m_notificationsToPost.append(make_pair(object, notification)); 513 if (!m_notificationPostTimer.isActive()) 514 m_notificationPostTimer.startOneShot(0); 515 } else 516 postPlatformNotification(object, notification); 517} 518 519void AXObjectCache::selectedChildrenChanged(RenderObject* renderer) 520{ 521 // postToElement is false so that you can pass in any child of an element and it will go up the parent tree 522 // to find the container which should send out the notification. 523 postNotification(renderer, AXSelectedChildrenChanged, false); 524} 525 526void AXObjectCache::nodeTextChangeNotification(RenderObject* renderer, AXTextChange textChange, unsigned offset, unsigned count) 527{ 528 if (!renderer) 529 return; 530 531 // Delegate on the right platform 532 AccessibilityObject* obj = getOrCreate(renderer); 533 nodeTextChangePlatformNotification(obj, textChange, offset, count); 534} 535#endif 536 537#if HAVE(ACCESSIBILITY) 538 539void AXObjectCache::handleScrollbarUpdate(ScrollView* view) 540{ 541 if (!view) 542 return; 543 544 // We don't want to create a scroll view from this method, only update an existing one. 545 AccessibilityObject* scrollViewObject = get(view); 546 if (scrollViewObject) 547 scrollViewObject->updateChildrenIfNecessary(); 548} 549 550void AXObjectCache::handleAriaExpandedChange(RenderObject *renderer) 551{ 552 if (!renderer) 553 return; 554 AccessibilityObject* obj = getOrCreate(renderer); 555 if (obj) 556 obj->handleAriaExpandedChanged(); 557} 558 559void AXObjectCache::handleActiveDescendantChanged(RenderObject* renderer) 560{ 561 if (!renderer) 562 return; 563 AccessibilityObject* obj = getOrCreate(renderer); 564 if (obj) 565 obj->handleActiveDescendantChanged(); 566} 567 568void AXObjectCache::handleAriaRoleChanged(RenderObject* renderer) 569{ 570 if (!renderer) 571 return; 572 AccessibilityObject* obj = getOrCreate(renderer); 573 if (obj && obj->isAccessibilityRenderObject()) 574 static_cast<AccessibilityRenderObject*>(obj)->updateAccessibilityRole(); 575} 576#endif 577 578VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData) 579{ 580 if (!isNodeInUse(textMarkerData.node)) 581 return VisiblePosition(); 582 583 // FIXME: Accessability should make it clear these are DOM-compliant offsets or store Position objects. 584 VisiblePosition visiblePos = VisiblePosition(Position(textMarkerData.node, textMarkerData.offset), textMarkerData.affinity); 585 Position deepPos = visiblePos.deepEquivalent(); 586 if (deepPos.isNull()) 587 return VisiblePosition(); 588 589 RenderObject* renderer = deepPos.deprecatedNode()->renderer(); 590 if (!renderer) 591 return VisiblePosition(); 592 593 AXObjectCache* cache = renderer->document()->axObjectCache(); 594 if (!cache->isIDinUse(textMarkerData.axID)) 595 return VisiblePosition(); 596 597 if (deepPos.deprecatedNode() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset) 598 return VisiblePosition(); 599 600 return visiblePos; 601} 602 603void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos) 604{ 605 // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence. 606 // This also allows callers to check for failure by looking at textMarkerData upon return. 607 memset(&textMarkerData, 0, sizeof(TextMarkerData)); 608 609 if (visiblePos.isNull()) 610 return; 611 612 Position deepPos = visiblePos.deepEquivalent(); 613 Node* domNode = deepPos.deprecatedNode(); 614 ASSERT(domNode); 615 if (!domNode) 616 return; 617 618 if (domNode->isHTMLElement()) { 619 InputElement* inputElement = domNode->toInputElement(); 620 if (inputElement && inputElement->isPasswordField()) 621 return; 622 } 623 624 // locate the renderer, which must exist for a visible dom node 625 RenderObject* renderer = domNode->renderer(); 626 ASSERT(renderer); 627 628 // find or create an accessibility object for this renderer 629 AXObjectCache* cache = renderer->document()->axObjectCache(); 630 RefPtr<AccessibilityObject> obj = cache->getOrCreate(renderer); 631 632 textMarkerData.axID = obj.get()->axObjectID(); 633 textMarkerData.node = domNode; 634 textMarkerData.offset = deepPos.deprecatedEditingOffset(); 635 textMarkerData.affinity = visiblePos.affinity(); 636 637 cache->setNodeInUse(domNode); 638} 639 640} // namespace WebCore 641