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