AXObjectCacheMac.mm   [plain text]


/*
 * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``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 COMPUTER, INC. OR
 * 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.
 */

#import "config.h"
#import "AXObjectCache.h"

#import "Document.h"
#import "FoundationExtras.h"
#import "RenderObject.h"
#import "WebCoreAXObject.h"
#import "WebCoreViewFactory.h"

// The simple Cocoa calls in this file don't throw exceptions.

namespace WebCore {

struct TextMarkerData  {
    AXID axID;
    Node* node;
    int offset;
    EAffinity affinity;
};

bool AXObjectCache::gAccessibilityEnabled = false;

AXObjectCache::~AXObjectCache()
{
    HashMap<RenderObject*, WebCoreAXObject*>::iterator end = m_objects.end();
    for (HashMap<RenderObject*, WebCoreAXObject*>::iterator it = m_objects.begin(); it != end; ++it) {
        WebCoreAXObject* obj = (*it).second;
        [obj detach];
        HardRelease(obj);
    }
}

WebCoreAXObject* AXObjectCache::get(RenderObject* renderer)
{
    WebCoreAXObject* obj = m_objects.get(renderer);
    if (obj)
        return obj;

    obj = [[WebCoreAXObject alloc] initWithRenderer:renderer];
    HardRetainWithNSRelease(obj);
    m_objects.set(renderer, obj);
    return obj;
}

void AXObjectCache::remove(RenderObject* renderer)
{
    HashMap<RenderObject*, WebCoreAXObject*>::iterator it = m_objects.find(renderer);
    if (it == m_objects.end())
        return;
    WebCoreAXObject* obj = (*it).second;
    [obj detach];
    HardRelease(obj);
    m_objects.remove(it);

    ASSERT(m_objects.size() >= m_idsInUse.size());
}

AXID AXObjectCache::getAXID(WebCoreAXObject* obj)
{
    // check for already-assigned ID
    AXID objID = [obj axObjectID];
    if (objID) {
        ASSERT(m_idsInUse.contains(objID));
        return objID;
    }

    // generate a new ID
    static AXID lastUsedID = 0;
    objID = lastUsedID;
    do
        ++objID;
    while (objID == 0 || objID == AXIDHashTraits::deletedValue() || m_idsInUse.contains(objID));
    m_idsInUse.add(objID);
    lastUsedID = objID;
    [obj setAXObjectID:objID];

    return objID;
}

void AXObjectCache::removeAXID(WebCoreAXObject* obj)
{
    AXID objID = [obj axObjectID];
    if (objID == 0)
        return;
    ASSERT(objID != AXIDHashTraits::deletedValue());
    ASSERT(m_idsInUse.contains(objID));
    [obj setAXObjectID:0];
    m_idsInUse.remove(objID);
}

WebCoreTextMarker* AXObjectCache::textMarkerForVisiblePosition(const VisiblePosition& visiblePos)
{
    Position deepPos = visiblePos.deepEquivalent();
    Node* domNode = deepPos.node();
    ASSERT(domNode);
    if (!domNode)
        return nil;
    
    // 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
    WebCoreAXObject* obj = get(renderer);
    
    // create a text marker, adding an ID for the WebCoreAXObject if needed
    TextMarkerData textMarkerData;
    textMarkerData.axID = getAXID(obj);
    textMarkerData.node = domNode;
    textMarkerData.offset = deepPos.offset();
    textMarkerData.affinity = visiblePos.affinity();
    return [[WebCoreViewFactory sharedFactory] textMarkerWithBytes:&textMarkerData length:sizeof(textMarkerData)]; 
}

VisiblePosition AXObjectCache::visiblePositionForTextMarker(WebCoreTextMarker* textMarker)
{
    TextMarkerData textMarkerData;
    
    if (![[WebCoreViewFactory sharedFactory] getBytes:&textMarkerData fromTextMarker:textMarker length:sizeof(textMarkerData)])
        return VisiblePosition();

    // return empty position if the text marker is no longer valid
    if (!m_idsInUse.contains(textMarkerData.axID))
        return VisiblePosition();

    // generate a VisiblePosition from the data we stored earlier
    VisiblePosition visiblePos = VisiblePosition(textMarkerData.node, textMarkerData.offset, textMarkerData.affinity);

    // make sure the node and offset still match (catches stale markers). affinity is not critical for this.
    Position deepPos = visiblePos.deepEquivalent();
    if (deepPos.node() != textMarkerData.node || deepPos.offset() != textMarkerData.offset)
        return VisiblePosition();
    
    return visiblePos;
}

void AXObjectCache::childrenChanged(RenderObject* renderer)
{
    WebCoreAXObject* obj = m_objects.get(renderer);
    if (obj)
        [obj childrenChanged];
}

void AXObjectCache::postNotification(RenderObject* renderer, const String& message)
{
    if (!renderer)
        return;
    
    // notifications for text input objects are sent to that object
    // all others are sent to the top WebArea
    WebCoreAXObject* obj = [get(renderer) observableObject];
    if (obj)
        NSAccessibilityPostNotification(obj, message);
    else
        NSAccessibilityPostNotification(get(renderer->document()->renderer()), message);
}

void AXObjectCache::postNotificationToElement(RenderObject* renderer, const String& message)
{
    // send the notification to the specified element itself, not one of its ancestors
    if (renderer)
        NSAccessibilityPostNotification(get(renderer), message);
}

void AXObjectCache::handleFocusedUIElementChanged()
{
    [[WebCoreViewFactory sharedFactory] accessibilityHandleFocusChanged];
}

}