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 "AccessibilityMenuListPopup.h" 42 #include "AccessibilityMenuListOption.h" 43 #include "AccessibilityRenderObject.h" 44 #include "AccessibilityScrollbar.h" 45 #include "AccessibilitySlider.h" 46 #include "AccessibilityTable.h" 47 #include "AccessibilityTableCell.h" 48 #include "AccessibilityTableColumn.h" 49 #include "AccessibilityTableHeaderContainer.h" 50 #include "AccessibilityTableRow.h" 51 #include "FocusController.h" 52 #include "Frame.h" 53 #include "HTMLAreaElement.h" 54 #include "HTMLImageElement.h" 55 #include "HTMLNames.h" 56 #if ENABLE(VIDEO) 57 #include "MediaControlElements.h" 58 #endif 59 #include "InputElement.h" 60 #include "Page.h" 61 #include "RenderObject.h" 62 #include "RenderView.h" 63 64 #include <wtf/PassRefPtr.h> 65 66 namespace WebCore { 67 68 using namespace HTMLNames; 69 70 bool AXObjectCache::gAccessibilityEnabled = false; 71 bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false; 72 73 AXObjectCache::AXObjectCache() 74 : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired) 75 { 76 } 77 78 AXObjectCache::~AXObjectCache() 79 { 80 HashMap<AXID, RefPtr<AccessibilityObject> >::iterator end = m_objects.end(); 81 for (HashMap<AXID, RefPtr<AccessibilityObject> >::iterator it = m_objects.begin(); it != end; ++it) { 82 AccessibilityObject* obj = (*it).second.get(); 83 detachWrapper(obj); 84 obj->detach(); 85 removeAXID(obj); 86 } 87 } 88 89 AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement) 90 { 91 // Find the corresponding accessibility object for the HTMLAreaElement. This should be 92 // in the list of children for its corresponding image. 93 if (!areaElement) 94 return 0; 95 96 HTMLImageElement* imageElement = areaElement->imageElement(); 97 if (!imageElement) 98 return 0; 99 100 AccessibilityObject* axRenderImage = areaElement->document()->axObjectCache()->getOrCreate(imageElement->renderer()); 101 if (!axRenderImage) 102 return 0; 103 104 AccessibilityObject::AccessibilityChildrenVector imageChildren = axRenderImage->children(); 105 unsigned count = imageChildren.size(); 106 for (unsigned k = 0; k < count; ++k) { 107 AccessibilityObject* child = imageChildren[k].get(); 108 if (!child->isImageMapLink()) 109 continue; 110 111 if (static_cast<AccessibilityImageMapLink*>(child)->areaElement() == areaElement) 112 return child; 113 } 114 115 return 0; 116 } 117 118 AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page) 119 { 120 // get the focused node in the page 121 Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document(); 122 Node* focusedNode = focusedDocument->focusedNode(); 123 if (!focusedNode) 124 focusedNode = focusedDocument; 125 126 if (focusedNode->hasTagName(areaTag)) 127 return focusedImageMapUIElement(static_cast<HTMLAreaElement*>(focusedNode)); 128 129 RenderObject* focusedNodeRenderer = focusedNode->renderer(); 130 if (!focusedNodeRenderer) 131 return 0; 132 133 AccessibilityObject* obj = focusedNodeRenderer->document()->axObjectCache()->getOrCreate(focusedNodeRenderer); 134 135 if (obj->shouldFocusActiveDescendant()) { 136 if (AccessibilityObject* descendant = obj->activeDescendant()) 137 obj = descendant; 138 } 139 140 // the HTML element, for example, is focusable but has an AX object that is ignored 141 if (obj->accessibilityIsIgnored()) 142 obj = obj->parentObjectUnignored(); 143 144 return obj; 145 } 146 147 AccessibilityObject* AXObjectCache::get(RenderObject* renderer) 148 { 149 if (!renderer) 150 return 0; 151 152 AccessibilityObject* obj = 0; 153 AXID axID = m_renderObjectMapping.get(renderer); 154 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); 155 156 if (axID) 157 obj = m_objects.get(axID).get(); 158 159 return obj; 160 } 161 162 bool AXObjectCache::nodeIsAriaType(Node* node, String role) 163 { 164 if (!node || !node->isElementNode()) 165 return false; 166 167 return equalIgnoringCase(static_cast<Element*>(node)->getAttribute(roleAttr), role); 168 } 169 170 AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer) 171 { 172 if (!renderer) 173 return 0; 174 175 AccessibilityObject* obj = get(renderer); 176 177 if (!obj) { 178 Node* node = renderer->node(); 179 RefPtr<AccessibilityObject> newObj = 0; 180 if (renderer->isListBox()) 181 newObj = AccessibilityListBox::create(renderer); 182 else if (renderer->isMenuList()) 183 newObj = AccessibilityMenuList::create(renderer); 184 185 // If the node is aria role="list" or the aria role is empty and its a ul/ol/dl type (it shouldn't be a list if aria says otherwise). 186 else if (node && ((nodeIsAriaType(node, "list") || nodeIsAriaType(node, "directory")) 187 || (nodeIsAriaType(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag))))) 188 newObj = AccessibilityList::create(renderer); 189 190 // aria tables 191 else if (nodeIsAriaType(node, "grid") || nodeIsAriaType(node, "treegrid")) 192 newObj = AccessibilityARIAGrid::create(renderer); 193 else if (nodeIsAriaType(node, "row")) 194 newObj = AccessibilityARIAGridRow::create(renderer); 195 else if (nodeIsAriaType(node, "gridcell") || nodeIsAriaType(node, "columnheader") || nodeIsAriaType(node, "rowheader")) 196 newObj = AccessibilityARIAGridCell::create(renderer); 197 198 // standard tables 199 else if (renderer->isTable()) 200 newObj = AccessibilityTable::create(renderer); 201 else if (renderer->isTableRow()) 202 newObj = AccessibilityTableRow::create(renderer); 203 else if (renderer->isTableCell()) 204 newObj = AccessibilityTableCell::create(renderer); 205 206 #if ENABLE(VIDEO) 207 // media controls 208 else if (renderer->node() && renderer->node()->isMediaControlElement()) 209 newObj = AccessibilityMediaControl::create(renderer); 210 #endif 211 212 // input type=range 213 else if (renderer->isSlider()) 214 newObj = AccessibilitySlider::create(renderer); 215 216 else 217 newObj = AccessibilityRenderObject::create(renderer); 218 219 obj = newObj.get(); 220 221 getAXID(obj); 222 223 m_renderObjectMapping.set(renderer, obj->axObjectID()); 224 m_objects.set(obj->axObjectID(), obj); 225 attachWrapper(obj); 226 } 227 228 return obj; 229 } 230 231 AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role) 232 { 233 RefPtr<AccessibilityObject> obj = 0; 234 235 // will be filled in... 236 switch (role) { 237 case ListBoxOptionRole: 238 obj = AccessibilityListBoxOption::create(); 239 break; 240 case ImageMapLinkRole: 241 obj = AccessibilityImageMapLink::create(); 242 break; 243 case ColumnRole: 244 obj = AccessibilityTableColumn::create(); 245 break; 246 case TableHeaderContainerRole: 247 obj = AccessibilityTableHeaderContainer::create(); 248 break; 249 case SliderThumbRole: 250 obj = AccessibilitySliderThumb::create(); 251 break; 252 case MenuListPopupRole: 253 obj = AccessibilityMenuListPopup::create(); 254 break; 255 case MenuListOptionRole: 256 obj = AccessibilityMenuListOption::create(); 257 break; 258 case ScrollBarRole: 259 obj = AccessibilityScrollbar::create(); 260 break; 261 default: 262 obj = 0; 263 } 264 265 if (obj) 266 getAXID(obj.get()); 267 else 268 return 0; 269 270 m_objects.set(obj->axObjectID(), obj); 271 attachWrapper(obj.get()); 272 return obj.get(); 273 } 274 275 void AXObjectCache::remove(AXID axID) 276 { 277 if (!axID) 278 return; 279 280 // first fetch object to operate some cleanup functions on it 281 AccessibilityObject* obj = m_objects.get(axID).get(); 282 if (!obj) 283 return; 284 285 detachWrapper(obj); 286 obj->detach(); 287 removeAXID(obj); 288 289 // finally remove the object 290 if (!m_objects.take(axID)) 291 return; 292 293 ASSERT(m_objects.size() >= m_idsInUse.size()); 294 } 295 296 void AXObjectCache::remove(RenderObject* renderer) 297 { 298 if (!renderer) 299 return; 300 301 AXID axID = m_renderObjectMapping.get(renderer); 302 remove(axID); 303 m_renderObjectMapping.remove(renderer); 304 } 305 306 #if !PLATFORM(WIN) 307 AXID AXObjectCache::platformGenerateAXID() const 308 { 309 static AXID lastUsedID = 0; 310 311 // Generate a new ID. 312 AXID objID = lastUsedID; 313 do { 314 ++objID; 315 } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID)); 316 317 lastUsedID = objID; 318 319 return objID; 320 } 321 #endif 322 323 AXID AXObjectCache::getAXID(AccessibilityObject* obj) 324 { 325 // check for already-assigned ID 326 AXID objID = obj->axObjectID(); 327 if (objID) { 328 ASSERT(m_idsInUse.contains(objID)); 329 return objID; 330 } 331 332 objID = platformGenerateAXID(); 333 334 m_idsInUse.add(objID); 335 obj->setAXObjectID(objID); 336 337 return objID; 338 } 339 340 void AXObjectCache::removeAXID(AccessibilityObject* object) 341 { 342 if (!object) 343 return; 344 345 AXID objID = object->axObjectID(); 346 if (!objID) 347 return; 348 ASSERT(!HashTraits<AXID>::isDeletedValue(objID)); 349 ASSERT(m_idsInUse.contains(objID)); 350 object->setAXObjectID(0); 351 m_idsInUse.remove(objID); 352 } 353 354 #if HAVE(ACCESSIBILITY) 355 void AXObjectCache::contentChanged(RenderObject* renderer) 356 { 357 AccessibilityObject* object = getOrCreate(renderer); 358 if (object) 359 object->contentChanged(); 360 } 361 #endif 362 363 void AXObjectCache::childrenChanged(RenderObject* renderer) 364 { 365 if (!renderer) 366 return; 367 368 AXID axID = m_renderObjectMapping.get(renderer); 369 if (!axID) 370 return; 371 372 AccessibilityObject* obj = m_objects.get(axID).get(); 373 if (obj) 374 obj->childrenChanged(); 375 } 376 377 void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>*) 378 { 379 m_notificationPostTimer.stop(); 380 381 unsigned i = 0, count = m_notificationsToPost.size(); 382 for (i = 0; i < count; ++i) { 383 AccessibilityObject* obj = m_notificationsToPost[i].first.get(); 384 #ifndef NDEBUG 385 // Make sure none of the render views are in the process of being layed out. 386 // Notifications should only be sent after the renderer has finished 387 if (obj->isAccessibilityRenderObject()) { 388 AccessibilityRenderObject* renderObj = static_cast<AccessibilityRenderObject*>(obj); 389 RenderObject* renderer = renderObj->renderer(); 390 if (renderer && renderer->view()) 391 ASSERT(!renderer->view()->layoutState()); 392 } 393 #endif 394 395 postPlatformNotification(obj, m_notificationsToPost[i].second); 396 } 397 398 m_notificationsToPost.clear(); 399 } 400 401 #if HAVE(ACCESSIBILITY) 402 void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, bool postToElement, PostType postType) 403 { 404 // Notifications for text input objects are sent to that object. 405 // All others are sent to the top WebArea. 406 if (!renderer) 407 return; 408 409 // Get an accessibility object that already exists. One should not be created here 410 // because a render update may be in progress and creating an AX object can re-trigger a layout 411 RefPtr<AccessibilityObject> object = get(renderer); 412 while (!object && renderer) { 413 renderer = renderer->parent(); 414 object = get(renderer); 415 } 416 417 if (!renderer) 418 return; 419 420 postNotification(object.get(), renderer->document(), notification, postToElement, postType); 421 } 422 423 void AXObjectCache::postNotification(AccessibilityObject* object, Document* document, AXNotification notification, bool postToElement, PostType postType) 424 { 425 if (object && !postToElement) 426 object = object->observableObject(); 427 428 if (!object && document) 429 object = get(document->renderer()); 430 431 if (!object) 432 return; 433 434 if (postType == PostAsynchronously) { 435 m_notificationsToPost.append(make_pair(object, notification)); 436 if (!m_notificationPostTimer.isActive()) 437 m_notificationPostTimer.startOneShot(0); 438 } else 439 postPlatformNotification(object, notification); 440 } 441 442 void AXObjectCache::selectedChildrenChanged(RenderObject* renderer) 443 { 444 postNotification(renderer, AXSelectedChildrenChanged, true); 445 } 446 #endif 447 448 #if HAVE(ACCESSIBILITY) 449 void AXObjectCache::handleActiveDescendantChanged(RenderObject* renderer) 450 { 451 if (!renderer) 452 return; 453 AccessibilityObject* obj = getOrCreate(renderer); 454 if (obj) 455 obj->handleActiveDescendantChanged(); 456 } 457 458 void AXObjectCache::handleAriaRoleChanged(RenderObject* renderer) 459 { 460 if (!renderer) 461 return; 462 AccessibilityObject* obj = getOrCreate(renderer); 463 if (obj && obj->isAccessibilityRenderObject()) 464 static_cast<AccessibilityRenderObject*>(obj)->updateAccessibilityRole(); 465 } 466 #endif 467 468 VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData) 469 { 470 VisiblePosition visiblePos = VisiblePosition(textMarkerData.node, textMarkerData.offset, textMarkerData.affinity); 471 Position deepPos = visiblePos.deepEquivalent(); 472 if (deepPos.isNull()) 473 return VisiblePosition(); 474 475 RenderObject* renderer = deepPos.node()->renderer(); 476 if (!renderer) 477 return VisiblePosition(); 478 479 AXObjectCache* cache = renderer->document()->axObjectCache(); 480 if (!cache->isIDinUse(textMarkerData.axID)) 481 return VisiblePosition(); 482 483 if (deepPos.node() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset) 484 return VisiblePosition(); 485 486 return visiblePos; 487 } 488 489 void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos) 490 { 491 // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence. 492 // This also allows callers to check for failure by looking at textMarkerData upon return. 493 memset(&textMarkerData, 0, sizeof(TextMarkerData)); 494 495 if (visiblePos.isNull()) 496 return; 497 498 Position deepPos = visiblePos.deepEquivalent(); 499 Node* domNode = deepPos.node(); 500 ASSERT(domNode); 501 if (!domNode) 502 return; 503 504 if (domNode->isHTMLElement()) { 505 InputElement* inputElement = toInputElement(static_cast<Element*>(domNode)); 506 if (inputElement && inputElement->isPasswordField()) 507 return; 508 } 509 510 // locate the renderer, which must exist for a visible dom node 511 RenderObject* renderer = domNode->renderer(); 512 ASSERT(renderer); 513 514 // find or create an accessibility object for this renderer 515 AXObjectCache* cache = renderer->document()->axObjectCache(); 516 RefPtr<AccessibilityObject> obj = cache->getOrCreate(renderer); 517 518 textMarkerData.axID = obj.get()->axObjectID(); 519 textMarkerData.node = domNode; 520 textMarkerData.offset = deepPos.deprecatedEditingOffset(); 521 textMarkerData.affinity = visiblePos.affinity(); 522 } 523 524 } // namespace WebCore 525