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 66namespace WebCore { 67 68using namespace HTMLNames; 69 70bool AXObjectCache::gAccessibilityEnabled = false; 71bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false; 72 73AXObjectCache::AXObjectCache() 74 : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired) 75{ 76} 77 78AXObjectCache::~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 89AccessibilityObject* 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 118AccessibilityObject* 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 147AccessibilityObject* 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 162bool 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 170AccessibilityObject* 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 231AccessibilityObject* 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 275void 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 296void 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) 307AXID 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 323AXID 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 340void 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) 355void AXObjectCache::contentChanged(RenderObject* renderer) 356{ 357 AccessibilityObject* object = getOrCreate(renderer); 358 if (object) 359 object->contentChanged(); 360} 361#endif 362 363void 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 377void 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) 402void 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 423void 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 442void AXObjectCache::selectedChildrenChanged(RenderObject* renderer) 443{ 444 postNotification(renderer, AXSelectedChildrenChanged, true); 445} 446#endif 447 448#if HAVE(ACCESSIBILITY) 449void AXObjectCache::handleActiveDescendantChanged(RenderObject* renderer) 450{ 451 if (!renderer) 452 return; 453 AccessibilityObject* obj = getOrCreate(renderer); 454 if (obj) 455 obj->handleActiveDescendantChanged(); 456} 457 458void 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 468VisiblePosition 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 489void 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