diff -r 000000000000 -r 4f2f89ce4247 WebCore/accessibility/AXObjectCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebCore/accessibility/AXObjectCache.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,543 @@ +/* + * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AXObjectCache.h" + +#include "AccessibilityARIAGrid.h" +#include "AccessibilityARIAGridCell.h" +#include "AccessibilityARIAGridRow.h" +#include "AccessibilityImageMapLink.h" +#include "AccessibilityList.h" +#include "AccessibilityListBox.h" +#include "AccessibilityListBoxOption.h" +#include "AccessibilityMediaControls.h" +#include "AccessibilityMenuList.h" +#include "AccessibilityMenuListOption.h" +#include "AccessibilityMenuListPopup.h" +#include "AccessibilityProgressIndicator.h" +#include "AccessibilityRenderObject.h" +#include "AccessibilityScrollbar.h" +#include "AccessibilitySlider.h" +#include "AccessibilityTable.h" +#include "AccessibilityTableCell.h" +#include "AccessibilityTableColumn.h" +#include "AccessibilityTableHeaderContainer.h" +#include "AccessibilityTableRow.h" +#include "FocusController.h" +#include "Frame.h" +#include "HTMLAreaElement.h" +#include "HTMLImageElement.h" +#include "HTMLNames.h" +#if ENABLE(VIDEO) +#include "MediaControlElements.h" +#endif +#include "InputElement.h" +#include "Page.h" +#include "RenderObject.h" +#include "RenderProgress.h" +#include "RenderView.h" + +#include + +namespace WebCore { + +using namespace HTMLNames; + +bool AXObjectCache::gAccessibilityEnabled = false; +bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false; + +AXObjectCache::AXObjectCache() + : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired) +{ +} + +AXObjectCache::~AXObjectCache() +{ + HashMap >::iterator end = m_objects.end(); + for (HashMap >::iterator it = m_objects.begin(); it != end; ++it) { + AccessibilityObject* obj = (*it).second.get(); + detachWrapper(obj); + obj->detach(); + removeAXID(obj); + } +} + +AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement) +{ + // Find the corresponding accessibility object for the HTMLAreaElement. This should be + // in the list of children for its corresponding image. + if (!areaElement) + return 0; + + HTMLImageElement* imageElement = areaElement->imageElement(); + if (!imageElement) + return 0; + + AccessibilityObject* axRenderImage = areaElement->document()->axObjectCache()->getOrCreate(imageElement->renderer()); + if (!axRenderImage) + return 0; + + AccessibilityObject::AccessibilityChildrenVector imageChildren = axRenderImage->children(); + unsigned count = imageChildren.size(); + for (unsigned k = 0; k < count; ++k) { + AccessibilityObject* child = imageChildren[k].get(); + if (!child->isImageMapLink()) + continue; + + if (static_cast(child)->areaElement() == areaElement) + return child; + } + + return 0; +} + +AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page) +{ + // get the focused node in the page + Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document(); + Node* focusedNode = focusedDocument->focusedNode(); + if (!focusedNode) + focusedNode = focusedDocument; + + if (focusedNode->hasTagName(areaTag)) + return focusedImageMapUIElement(static_cast(focusedNode)); + + RenderObject* focusedNodeRenderer = focusedNode->renderer(); + if (!focusedNodeRenderer) + return 0; + + AccessibilityObject* obj = focusedNodeRenderer->document()->axObjectCache()->getOrCreate(focusedNodeRenderer); + + if (obj->shouldFocusActiveDescendant()) { + if (AccessibilityObject* descendant = obj->activeDescendant()) + obj = descendant; + } + + // the HTML element, for example, is focusable but has an AX object that is ignored + if (obj->accessibilityIsIgnored()) + obj = obj->parentObjectUnignored(); + + return obj; +} + +AccessibilityObject* AXObjectCache::get(RenderObject* renderer) +{ + if (!renderer) + return 0; + + AccessibilityObject* obj = 0; + AXID axID = m_renderObjectMapping.get(renderer); + ASSERT(!HashTraits::isDeletedValue(axID)); + + if (axID) + obj = m_objects.get(axID).get(); + + return obj; +} + +bool AXObjectCache::nodeHasRole(Node* node, const AtomicString& role) +{ + if (!node || !node->isElementNode()) + return false; + + return equalIgnoringCase(static_cast(node)->getAttribute(roleAttr), role); +} + +AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer) +{ + if (!renderer) + return 0; + + AccessibilityObject* obj = get(renderer); + + if (!obj) { + Node* node = renderer->node(); + RefPtr newObj = 0; + if (renderer->isListBox()) + newObj = AccessibilityListBox::create(renderer); + else if (renderer->isMenuList()) + newObj = AccessibilityMenuList::create(renderer); + + // 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). + else if (node && ((nodeHasRole(node, "list") || nodeHasRole(node, "directory")) + || (nodeHasRole(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag))))) + newObj = AccessibilityList::create(renderer); + + // aria tables + else if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid")) + newObj = AccessibilityARIAGrid::create(renderer); + else if (nodeHasRole(node, "row")) + newObj = AccessibilityARIAGridRow::create(renderer); + else if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader")) + newObj = AccessibilityARIAGridCell::create(renderer); + + // standard tables + else if (renderer->isTable()) + newObj = AccessibilityTable::create(renderer); + else if (renderer->isTableRow()) + newObj = AccessibilityTableRow::create(renderer); + else if (renderer->isTableCell()) + newObj = AccessibilityTableCell::create(renderer); + +#if ENABLE(VIDEO) + // media controls + else if (renderer->node() && renderer->node()->isMediaControlElement()) + newObj = AccessibilityMediaControl::create(renderer); +#endif + +#if ENABLE(PROGRESS_TAG) + // progress bar + else if (renderer->isProgress()) + newObj = AccessibilityProgressIndicator::create(toRenderProgress(renderer)); +#endif + + // input type=range + else if (renderer->isSlider()) + newObj = AccessibilitySlider::create(renderer); + + else + newObj = AccessibilityRenderObject::create(renderer); + + obj = newObj.get(); + + getAXID(obj); + + m_renderObjectMapping.set(renderer, obj->axObjectID()); + m_objects.set(obj->axObjectID(), obj); + attachWrapper(obj); + } + + return obj; +} + +AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role) +{ + RefPtr obj = 0; + + // will be filled in... + switch (role) { + case ListBoxOptionRole: + obj = AccessibilityListBoxOption::create(); + break; + case ImageMapLinkRole: + obj = AccessibilityImageMapLink::create(); + break; + case ColumnRole: + obj = AccessibilityTableColumn::create(); + break; + case TableHeaderContainerRole: + obj = AccessibilityTableHeaderContainer::create(); + break; + case SliderThumbRole: + obj = AccessibilitySliderThumb::create(); + break; + case MenuListPopupRole: + obj = AccessibilityMenuListPopup::create(); + break; + case MenuListOptionRole: + obj = AccessibilityMenuListOption::create(); + break; + case ScrollBarRole: + obj = AccessibilityScrollbar::create(); + break; + default: + obj = 0; + } + + if (obj) + getAXID(obj.get()); + else + return 0; + + m_objects.set(obj->axObjectID(), obj); + attachWrapper(obj.get()); + return obj.get(); +} + +void AXObjectCache::remove(AXID axID) +{ + if (!axID) + return; + + // first fetch object to operate some cleanup functions on it + AccessibilityObject* obj = m_objects.get(axID).get(); + if (!obj) + return; + + detachWrapper(obj); + obj->detach(); + removeAXID(obj); + + // finally remove the object + if (!m_objects.take(axID)) + return; + + ASSERT(m_objects.size() >= m_idsInUse.size()); +} + +void AXObjectCache::remove(RenderObject* renderer) +{ + if (!renderer) + return; + + AXID axID = m_renderObjectMapping.get(renderer); + remove(axID); + m_renderObjectMapping.remove(renderer); +} + +#if !PLATFORM(WIN) +AXID AXObjectCache::platformGenerateAXID() const +{ + static AXID lastUsedID = 0; + + // Generate a new ID. + AXID objID = lastUsedID; + do { + ++objID; + } while (!objID || HashTraits::isDeletedValue(objID) || m_idsInUse.contains(objID)); + + lastUsedID = objID; + + return objID; +} +#endif + +AXID AXObjectCache::getAXID(AccessibilityObject* obj) +{ + // check for already-assigned ID + AXID objID = obj->axObjectID(); + if (objID) { + ASSERT(m_idsInUse.contains(objID)); + return objID; + } + + objID = platformGenerateAXID(); + + m_idsInUse.add(objID); + obj->setAXObjectID(objID); + + return objID; +} + +void AXObjectCache::removeAXID(AccessibilityObject* object) +{ + if (!object) + return; + + AXID objID = object->axObjectID(); + if (!objID) + return; + ASSERT(!HashTraits::isDeletedValue(objID)); + ASSERT(m_idsInUse.contains(objID)); + object->setAXObjectID(0); + m_idsInUse.remove(objID); +} + +#if HAVE(ACCESSIBILITY) +void AXObjectCache::contentChanged(RenderObject* renderer) +{ + AccessibilityObject* object = getOrCreate(renderer); + if (object) + object->contentChanged(); +} +#endif + +void AXObjectCache::childrenChanged(RenderObject* renderer) +{ + if (!renderer) + return; + + AXID axID = m_renderObjectMapping.get(renderer); + if (!axID) + return; + + AccessibilityObject* obj = m_objects.get(axID).get(); + if (obj) + obj->childrenChanged(); +} + +void AXObjectCache::notificationPostTimerFired(Timer*) +{ + m_notificationPostTimer.stop(); + + unsigned i = 0, count = m_notificationsToPost.size(); + for (i = 0; i < count; ++i) { + AccessibilityObject* obj = m_notificationsToPost[i].first.get(); +#ifndef NDEBUG + // Make sure none of the render views are in the process of being layed out. + // Notifications should only be sent after the renderer has finished + if (obj->isAccessibilityRenderObject()) { + AccessibilityRenderObject* renderObj = static_cast(obj); + RenderObject* renderer = renderObj->renderer(); + if (renderer && renderer->view()) + ASSERT(!renderer->view()->layoutState()); + } +#endif + + postPlatformNotification(obj, m_notificationsToPost[i].second); + } + + m_notificationsToPost.clear(); +} + +#if HAVE(ACCESSIBILITY) +void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, bool postToElement, PostType postType) +{ + // Notifications for text input objects are sent to that object. + // All others are sent to the top WebArea. + if (!renderer) + return; + + // Get an accessibility object that already exists. One should not be created here + // because a render update may be in progress and creating an AX object can re-trigger a layout + RefPtr object = get(renderer); + while (!object && renderer) { + renderer = renderer->parent(); + object = get(renderer); + } + + if (!renderer) + return; + + postNotification(object.get(), renderer->document(), notification, postToElement, postType); +} + +void AXObjectCache::postNotification(AccessibilityObject* object, Document* document, AXNotification notification, bool postToElement, PostType postType) +{ + if (object && !postToElement) + object = object->observableObject(); + + if (!object && document) + object = get(document->renderer()); + + if (!object) + return; + + if (postType == PostAsynchronously) { + m_notificationsToPost.append(make_pair(object, notification)); + if (!m_notificationPostTimer.isActive()) + m_notificationPostTimer.startOneShot(0); + } else + postPlatformNotification(object, notification); +} + +void AXObjectCache::selectedChildrenChanged(RenderObject* renderer) +{ + // postToElement is false so that you can pass in any child of an element and it will go up the parent tree + // to find the container which should send out the notification. + postNotification(renderer, AXSelectedChildrenChanged, false); +} +#endif + +#if HAVE(ACCESSIBILITY) +void AXObjectCache::handleAriaExpandedChange(RenderObject *renderer) +{ + if (!renderer) + return; + AccessibilityObject* obj = getOrCreate(renderer); + if (obj) + obj->handleAriaExpandedChanged(); +} + +void AXObjectCache::handleActiveDescendantChanged(RenderObject* renderer) +{ + if (!renderer) + return; + AccessibilityObject* obj = getOrCreate(renderer); + if (obj) + obj->handleActiveDescendantChanged(); +} + +void AXObjectCache::handleAriaRoleChanged(RenderObject* renderer) +{ + if (!renderer) + return; + AccessibilityObject* obj = getOrCreate(renderer); + if (obj && obj->isAccessibilityRenderObject()) + static_cast(obj)->updateAccessibilityRole(); +} +#endif + +VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData) +{ + VisiblePosition visiblePos = VisiblePosition(textMarkerData.node, textMarkerData.offset, textMarkerData.affinity); + Position deepPos = visiblePos.deepEquivalent(); + if (deepPos.isNull()) + return VisiblePosition(); + + RenderObject* renderer = deepPos.node()->renderer(); + if (!renderer) + return VisiblePosition(); + + AXObjectCache* cache = renderer->document()->axObjectCache(); + if (!cache->isIDinUse(textMarkerData.axID)) + return VisiblePosition(); + + if (deepPos.node() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset) + return VisiblePosition(); + + return visiblePos; +} + +void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos) +{ + // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence. + // This also allows callers to check for failure by looking at textMarkerData upon return. + memset(&textMarkerData, 0, sizeof(TextMarkerData)); + + if (visiblePos.isNull()) + return; + + Position deepPos = visiblePos.deepEquivalent(); + Node* domNode = deepPos.node(); + ASSERT(domNode); + if (!domNode) + return; + + if (domNode->isHTMLElement()) { + InputElement* inputElement = toInputElement(static_cast(domNode)); + if (inputElement && inputElement->isPasswordField()) + return; + } + + // locate the renderer, which must exist for a visible dom node + RenderObject* renderer = domNode->renderer(); + ASSERT(renderer); + + // find or create an accessibility object for this renderer + AXObjectCache* cache = renderer->document()->axObjectCache(); + RefPtr obj = cache->getOrCreate(renderer); + + textMarkerData.axID = obj.get()->axObjectID(); + textMarkerData.node = domNode; + textMarkerData.offset = deepPos.deprecatedEditingOffset(); + textMarkerData.affinity = visiblePos.affinity(); +} + +} // namespace WebCore