Home | History | Annotate | Download | only in accessibility
      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