AccessibleBase.cpp   [plain text]


/*
 * Copyright (C) 2008-2010, 2013, 2015 Apple Inc. All Rights Reserved.
 * Copyright (C) 2012 Serotek Corporation. 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 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 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. 
 */

#include "WebKitDLL.h"
#include "AccessibleBase.h"

#include "AccessibleImage.h"
#include "AccessibleTextImpl.h"
#include "WebView.h"
#include <WebCore/AccessibilityListBox.h>
#include <WebCore/AccessibilityMenuListPopup.h>
#include <WebCore/AccessibilityObject.h>
#include <WebCore/AXObjectCache.h>
#include <WebCore/BString.h>
#include <WebCore/Element.h>
#include <WebCore/EventHandler.h>
#include <WebCore/FrameView.h>
#include <WebCore/HostWindow.h>
#include <WebCore/HTMLNames.h>
#include <WebCore/HTMLFrameElementBase.h>
#include <WebCore/HTMLInputElement.h>
#include <WebCore/IntRect.h>
#include <WebCore/NotImplemented.h>
#include <WebCore/PlatformEvent.h>
#include <WebCore/RenderFrame.h>
#include <WebCore/RenderObject.h>
#include <WebCore/RenderView.h>
#include <comutil.h>
#include <oleacc.h>
#include <wtf/RefPtr.h>
#include <wtf/text/StringBuilder.h>

using namespace WebCore;

AccessibleBase::AccessibleBase(AccessibilityObject* obj, HWND window)
    : AccessibilityObjectWrapper(obj)
    , m_window(window)
{
    ASSERT_ARG(obj, obj);
    m_object->setWrapper(this);
    ++gClassCount;
    gClassNameCount().add("AccessibleBase");
}

AccessibleBase::~AccessibleBase()
{
    --gClassCount;
    gClassNameCount().remove("AccessibleBase");
}

AccessibleBase* AccessibleBase::createInstance(AccessibilityObject* obj, HWND window)
{
    ASSERT_ARG(obj, obj);

    if (obj->isImage())
        return new AccessibleImage(obj, window);
    else if (obj->isStaticText() || obj->isTextControl() || (obj->node() && obj->node()->isTextNode()))
        return new AccessibleText(obj, window);

    return new AccessibleBase(obj, window);
}

HRESULT AccessibleBase::QueryService(_In_ REFGUID guidService, _In_ REFIID riid, _COM_Outptr_ void **ppvObject)
{
    if (!IsEqualGUID(guidService, SID_AccessibleComparable)
        && !IsEqualGUID(guidService, IID_IAccessible2_2)
        && !IsEqualGUID(guidService, IID_IAccessible2)
        && !IsEqualGUID(guidService, IID_IAccessibleApplication)
        && !IsEqualGUID(guidService, IID_IAccessible)
        && !IsEqualGUID(guidService, IID_IAccessibleText)
        && !IsEqualGUID(guidService, IID_IAccessibleText2)
        && !IsEqualGUID(guidService, IID_IAccessibleEditableText)) {
        *ppvObject = nullptr;
        return E_INVALIDARG;
    }
    return QueryInterface(riid, ppvObject);
}

// IUnknown
HRESULT AccessibleBase::QueryInterface(_In_ REFIID riid, _COM_Outptr_ void** ppvObject)
{
    if (!ppvObject)
        return E_POINTER;
    if (IsEqualGUID(riid, __uuidof(IAccessible)))
        *ppvObject = static_cast<IAccessible*>(this);
    else if (IsEqualGUID(riid, __uuidof(IDispatch)))
        *ppvObject = static_cast<IAccessible*>(this);
    else if (IsEqualGUID(riid, __uuidof(IUnknown)))
        *ppvObject = static_cast<IAccessible*>(this);
    else if (IsEqualGUID(riid, __uuidof(IAccessible2_2)))
        *ppvObject = static_cast<IAccessible2_2*>(this);
    else if (IsEqualGUID(riid, __uuidof(IAccessible2)))
        *ppvObject = static_cast<IAccessible2*>(this);
    else if (IsEqualGUID(riid, __uuidof(IAccessibleComparable)))
        *ppvObject = static_cast<IAccessibleComparable*>(this);
    else if (IsEqualGUID(riid, __uuidof(IServiceProvider)))
        *ppvObject = static_cast<IServiceProvider*>(this);
    else if (IsEqualGUID(riid, __uuidof(AccessibleBase)))
        *ppvObject = static_cast<AccessibleBase*>(this);
    else {
        *ppvObject = nullptr;
        return E_NOINTERFACE;
    }
    AddRef();
    return S_OK;
}

ULONG AccessibleBase::Release()
{
    ASSERT(m_refCount > 0);
    if (--m_refCount)
        return m_refCount;
    delete this;
    return 0;
}

// IAccessible2_2
HRESULT AccessibleBase::get_attribute(_In_ BSTR key, _Out_ VARIANT* value)
{
    if (!value)
        return E_POINTER;

    AtomString keyAtomic(key, ::SysStringLen(key));

    accessibilityAttributeValue(keyAtomic, value);

    return S_OK;
}

HRESULT AccessibleBase::get_accessibleWithCaret(_COM_Outptr_opt_ IUnknown** accessible, _Out_ long* caretOffset)
{
    if (!accessible || !caretOffset)
        return E_POINTER;
    *accessible = nullptr;
    *caretOffset = 0;
    notImplemented();
    return E_NOTIMPL;
}

HRESULT AccessibleBase::get_relationTargetsOfType(_In_ BSTR type, long maxTargets, __deref_out_ecount_full_opt(*nTargets) IUnknown*** targets, _Out_ long* nTargets)
{
    notImplemented();
    return E_NOTIMPL;
}

// IAccessible2
HRESULT AccessibleBase::get_nRelations(_Out_ long* nRelations)
{
    if (!nRelations)
        return E_POINTER;

    if (!m_object)
        return E_FAIL;
    notImplemented();
    *nRelations = 0;
    return S_OK;
}

HRESULT AccessibleBase::get_relation(long relationIndex, _COM_Outptr_opt_ IAccessibleRelation** relation)
{
    if (!relation)
        return E_POINTER;

    *relation = nullptr;

    notImplemented();
    return E_NOTIMPL;
}

HRESULT AccessibleBase::get_relations(long maxRelations, __out_ecount_part(maxRelations, *nRelations)  IAccessibleRelation** relations, _Out_ long* nRelations)
{
    if (!relations || !nRelations)
        return E_POINTER;

    *relations = nullptr;

    notImplemented();
    return E_NOTIMPL;
}

HRESULT AccessibleBase::role(_Out_ long* role)
{
    if (!role)
        return E_POINTER;
    *role = 0;
    if (!m_object)
        return E_FAIL;

    *role = wrapper(m_object)->role();
    return S_OK;
}

HRESULT AccessibleBase::scrollTo(IA2ScrollType scrollType)
{
    if (!m_object)
        return E_FAIL;
    return S_FALSE;
}

HRESULT AccessibleBase::scrollToPoint(IA2CoordinateType coordinateType, long x, long y)
{
    if (!m_object)
        return E_FAIL;
    return S_FALSE;
}

HRESULT AccessibleBase::get_groupPosition(_Out_ long* groupLevel, _Out_ long* similarItemsInGroup, _Out_ long* positionInGroup)
{
    notImplemented();
    return E_NOTIMPL;
}

HRESULT AccessibleBase::get_states(_Out_ AccessibleStates* states)
{
    if (!states)
        return E_POINTER;

    if (!m_object)
        return E_FAIL;

    *states = 0;
    notImplemented();
    return S_OK;
}

HRESULT AccessibleBase::get_extendedRole(__deref_opt_out BSTR* extendedRole)
{
    if (!extendedRole)
        return E_POINTER;
    *extendedRole = nullptr;
    if (!m_object)
        return E_FAIL;

    notImplemented();
    return S_FALSE;
}

HRESULT AccessibleBase::get_localizedExtendedRole(__deref_opt_out BSTR* localizedExtendedRole)
{
    if (!localizedExtendedRole)
        return E_POINTER;
    *localizedExtendedRole = nullptr;
    if (!m_object)
        return E_FAIL;

    notImplemented();
    return S_FALSE;
}

HRESULT AccessibleBase::get_nExtendedStates(_Out_ long* nExtendedStates)
{
    if (!nExtendedStates)
        return E_POINTER;

    if (!m_object)
        return E_FAIL;

    notImplemented();
    *nExtendedStates = 0;
    return S_OK;
}

HRESULT AccessibleBase::get_extendedStates(long maxExtendedStates, __deref_out_ecount_part_opt(maxExtendedStates, *nExtendedStates) BSTR** extendedStates, _Out_ long* nExtendedStates)
{
    notImplemented();
    return E_NOTIMPL;
}

HRESULT AccessibleBase::get_localizedExtendedStates(long maxLocalizedExtendedStates, __deref_out_ecount_part_opt(maxLocalizedExtendedStates, *nLocalizedExtendedStates) BSTR** localizedExtendedStates, _Out_ long* nLocalizedExtendedStates)
{
    notImplemented();
    return E_NOTIMPL;
}

HRESULT AccessibleBase::get_uniqueID(_Out_ long* uniqueID)
{
    if (!uniqueID)
        return E_POINTER;

    if (!m_object)
        return E_FAIL;

    *uniqueID = static_cast<long>(m_object->axObjectID());
    return S_OK;
}

HRESULT AccessibleBase::get_windowHandle(_Out_ HWND* windowHandle)
{
    *windowHandle = m_window;
    return S_OK;
}

HRESULT AccessibleBase::get_indexInParent(_Out_ long* indexInParent)
{
    return E_NOTIMPL;
}

HRESULT AccessibleBase::get_locale(_Out_ IA2Locale* locale)
{
    if (!locale)
        return E_POINTER;

    if (!m_object)
        return E_FAIL;

    locale->language = BString(m_object->language().createCFString().get()).release();

    return S_OK;
}

HRESULT AccessibleBase::get_attributes(__deref_opt_out BSTR* attributes)
{
    if (!attributes)
        return E_POINTER;
    *attributes = nullptr;
    if (!m_object)
        return E_FAIL;

    notImplemented();
    return S_FALSE;
}

// IAccessible
HRESULT AccessibleBase::get_accParent(_COM_Outptr_opt_ IDispatch** parent)
{
    if (!parent)
        return E_POINTER;

    *parent = nullptr;

    if (!m_object)
        return E_FAIL;

    AccessibilityObject* parentObject = m_object->parentObjectUnignored();
    if (parentObject) {
        *parent = wrapper(parentObject);
        (*parent)->AddRef();
        return S_OK;
    }

    if (!m_window)
        return E_FAIL;

    return WebView::AccessibleObjectFromWindow(m_window,
        OBJID_WINDOW, __uuidof(IAccessible), reinterpret_cast<void**>(parent));
}

HRESULT AccessibleBase::get_accChildCount(_Out_ long* count)
{
    if (!m_object)
        return E_FAIL;
    if (!count)
        return E_POINTER;
    *count = static_cast<long>(m_object->children().size());
    return S_OK;
}

HRESULT AccessibleBase::get_accChild(VARIANT vChild, _COM_Outptr_opt_ IDispatch** ppChild)
{
    if (!ppChild)
        return E_POINTER;

    *ppChild = nullptr;

    AccessibilityObject* childObj;

    HRESULT hr = getAccessibilityObjectForChild(vChild, childObj);
    if (FAILED(hr))
        return hr;

    *ppChild = static_cast<IDispatch*>(wrapper(childObj));
    (*ppChild)->AddRef();
    return S_OK;
}

HRESULT AccessibleBase::get_accName(VARIANT vChild, __deref_opt_out BSTR* name)
{
    if (!name)
        return E_POINTER;

    *name = nullptr;

    AccessibilityObject* childObj;
    HRESULT hr = getAccessibilityObjectForChild(vChild, childObj);

    if (FAILED(hr))
        return hr;

    if (*name = BString(wrapper(childObj)->name()).release())
        return S_OK;
    return S_FALSE;
}

HRESULT AccessibleBase::get_accValue(VARIANT vChild, __deref_opt_out BSTR* value)
{
    if (!value)
        return E_POINTER;

    *value = nullptr;

    AccessibilityObject* childObj;
    HRESULT hr = getAccessibilityObjectForChild(vChild, childObj);

    if (FAILED(hr))
        return hr;

    if (*value = BString(wrapper(childObj)->value()).release())
        return S_OK;
    return S_FALSE;
}

HRESULT AccessibleBase::get_accDescription(VARIANT vChild, __deref_opt_out BSTR* description)
{
    if (!description)
        return E_POINTER;

    *description = nullptr;

    AccessibilityObject* childObj;
    HRESULT hr = getAccessibilityObjectForChild(vChild, childObj);

    if (FAILED(hr))
        return hr;

    if (*description = BString(childObj->descriptionForMSAA()).release())
        return S_OK;

    return S_FALSE;
}

HRESULT AccessibleBase::get_accRole(VARIANT vChild, _Out_ VARIANT* pvRole)
{
    if (!pvRole)
        return E_POINTER;

    ::VariantInit(pvRole);

    AccessibilityObject* childObj;
    HRESULT hr = getAccessibilityObjectForChild(vChild, childObj);

    if (FAILED(hr))
        return hr;

    String roleString = childObj->stringRoleForMSAA();
    if (!roleString.isEmpty()) {
        V_VT(pvRole) = VT_BSTR;
        V_BSTR(pvRole) = BString(roleString).release();
        return S_OK;
    }

    pvRole->vt = VT_I4;
    pvRole->lVal = wrapper(childObj)->role();
    return S_OK;
}

long AccessibleBase::state() const
{
    long state = 0;
    if (m_object->isLinked())
        state |= STATE_SYSTEM_LINKED;

    if (m_object->isHovered())
        state |= STATE_SYSTEM_HOTTRACKED;

    if (!m_object->isEnabled())
        state |= STATE_SYSTEM_UNAVAILABLE;

    if (!m_object->canSetValueAttribute())
        state |= STATE_SYSTEM_READONLY;

    if (m_object->isOffScreen())
        state |= STATE_SYSTEM_OFFSCREEN;

    if (m_object->isPasswordField())
        state |= STATE_SYSTEM_PROTECTED;

    if (m_object->isIndeterminate())
        state |= STATE_SYSTEM_INDETERMINATE;

    if (m_object->isChecked())
        state |= STATE_SYSTEM_CHECKED;

    if (m_object->isPressed())
        state |= STATE_SYSTEM_PRESSED;

    if (m_object->isFocused())
        state |= STATE_SYSTEM_FOCUSED;

    if (m_object->isVisited())
        state |= STATE_SYSTEM_TRAVERSED;

    if (m_object->canSetFocusAttribute())
        state |= STATE_SYSTEM_FOCUSABLE;

    if (m_object->isSelected())
        state |= STATE_SYSTEM_SELECTED;

    if (m_object->canSetSelectedAttribute())
        state |= STATE_SYSTEM_SELECTABLE;

    if (m_object->isMultiSelectable())
        state |= STATE_SYSTEM_EXTSELECTABLE | STATE_SYSTEM_MULTISELECTABLE;

    if (!m_object->isVisible())
        state |= STATE_SYSTEM_INVISIBLE;

    if (m_object->isCollapsed())
        state |= STATE_SYSTEM_COLLAPSED;

    if (m_object->roleValue() == AccessibilityRole::PopUpButton) {
        state |= STATE_SYSTEM_HASPOPUP;

        if (!m_object->isCollapsed())
            state |= STATE_SYSTEM_EXPANDED;
    }

    return state;
}

HRESULT AccessibleBase::get_accState(VARIANT vChild, _Out_ VARIANT* pvState)
{
    if (!pvState)
        return E_POINTER;

    ::VariantInit(pvState);

    AccessibilityObject* childObj;
    HRESULT hr = getAccessibilityObjectForChild(vChild, childObj);

    if (FAILED(hr))
        return hr;

    pvState->vt = VT_I4;
    pvState->lVal = wrapper(childObj)->state();

    return S_OK;
}

HRESULT AccessibleBase::get_accHelp(VARIANT vChild, __deref_opt_out BSTR* helpText)
{
    if (!helpText)
        return E_POINTER;

    *helpText = nullptr;

    AccessibilityObject* childObj;
    HRESULT hr = getAccessibilityObjectForChild(vChild, childObj);

    if (FAILED(hr))
        return hr;

    if (*helpText = BString(childObj->helpText()).release())
        return S_OK;
    return S_FALSE;
}

HRESULT AccessibleBase::get_accKeyboardShortcut(VARIANT vChild, __deref_opt_out BSTR* shortcut)
{
    if (!shortcut)
        return E_POINTER;

    *shortcut = nullptr;

    AccessibilityObject* childObj;
    HRESULT hr = getAccessibilityObjectForChild(vChild, childObj);

    if (FAILED(hr))
        return hr;

    String accessKey = childObj->accessKey();
    if (accessKey.isNull())
        return S_FALSE;

    static String accessKeyModifiers;
    if (accessKeyModifiers.isNull()) {
        StringBuilder accessKeyModifiersBuilder;
        auto modifiers = EventHandler::accessKeyModifiers();
        // Follow the same order as Mozilla MSAA implementation:
        // Ctrl+Alt+Shift+Meta+key. MSDN states that keyboard shortcut strings
        // should not be localized and defines the separator as "+".
        if (modifiers.contains(PlatformEvent::Modifier::ControlKey))
            accessKeyModifiersBuilder.appendLiteral("Ctrl+");
        if (modifiers.contains(PlatformEvent::Modifier::AltKey))
            accessKeyModifiersBuilder.appendLiteral("Alt+");
        if (modifiers.contains(PlatformEvent::Modifier::ShiftKey))
            accessKeyModifiersBuilder.appendLiteral("Shift+");
        if (modifiers.contains(PlatformEvent::Modifier::MetaKey))
            accessKeyModifiersBuilder.appendLiteral("Win+");
        accessKeyModifiers = accessKeyModifiersBuilder.toString();
    }
    *shortcut = BString(String(accessKeyModifiers + accessKey)).release();
    return S_OK;
}

HRESULT AccessibleBase::accSelect(long selectionFlags, VARIANT vChild)
{
    // According to MSDN, these combinations are invalid.
    if (((selectionFlags & (SELFLAG_ADDSELECTION | SELFLAG_REMOVESELECTION)) == (SELFLAG_ADDSELECTION | SELFLAG_REMOVESELECTION))
        || ((selectionFlags & (SELFLAG_ADDSELECTION | SELFLAG_TAKESELECTION)) == (SELFLAG_ADDSELECTION | SELFLAG_TAKESELECTION))
        || ((selectionFlags & (SELFLAG_REMOVESELECTION | SELFLAG_TAKESELECTION)) == (SELFLAG_REMOVESELECTION | SELFLAG_TAKESELECTION))
        || ((selectionFlags & (SELFLAG_EXTENDSELECTION | SELFLAG_TAKESELECTION)) == (SELFLAG_EXTENDSELECTION | SELFLAG_TAKESELECTION)))
        return E_INVALIDARG;

    AccessibilityObject* childObject;
    HRESULT hr = getAccessibilityObjectForChild(vChild, childObject);

    if (FAILED(hr))
        return hr;

    if (selectionFlags & SELFLAG_TAKEFOCUS)
        childObject->setFocused(true);

    AccessibilityObject* parentObject = childObject->parentObject();
    if (!parentObject)
        return E_INVALIDARG;

    if (selectionFlags & SELFLAG_TAKESELECTION) {
        if (is<AccessibilityListBox>(*parentObject)) {
            Vector<RefPtr<AccessibilityObject> > selectedChildren(1);
            selectedChildren[0] = childObject;
            downcast<AccessibilityListBox>(*parentObject).setSelectedChildren(selectedChildren);
        } else { // any element may be selectable by virtue of it having the aria-selected property
            ASSERT(!parentObject->isMultiSelectable());
            childObject->setSelected(true);
        }
    }

    // MSDN says that ADD, REMOVE, and EXTENDSELECTION with no other flags are invalid for
    // single-select.
    const long allSELFLAGs = SELFLAG_TAKEFOCUS | SELFLAG_TAKESELECTION | SELFLAG_EXTENDSELECTION | SELFLAG_ADDSELECTION | SELFLAG_REMOVESELECTION;
    if (!parentObject->isMultiSelectable()
        && (((selectionFlags & allSELFLAGs) == SELFLAG_ADDSELECTION)
        || ((selectionFlags & allSELFLAGs) == SELFLAG_REMOVESELECTION)
        || ((selectionFlags & allSELFLAGs) == SELFLAG_EXTENDSELECTION)))
        return E_INVALIDARG;

    if (selectionFlags & SELFLAG_ADDSELECTION)
        childObject->setSelected(true);

    if (selectionFlags & SELFLAG_REMOVESELECTION)
        childObject->setSelected(false);

    // FIXME: Should implement SELFLAG_EXTENDSELECTION. For now, we just return
    // S_OK, matching Firefox.

    return S_OK;
}

HRESULT AccessibleBase::get_accSelection(_Out_ VARIANT*)
{
    return E_NOTIMPL;
}

HRESULT AccessibleBase::get_accFocus(_Out_ VARIANT* pvFocusedChild)
{
    if (!pvFocusedChild)
        return E_POINTER;

    ::VariantInit(pvFocusedChild);

    if (!m_object)
        return E_FAIL;

    auto focusedObject = downcast<AccessibilityObject>(m_object->focusedUIElement());
    if (!focusedObject)
        return S_FALSE;

    if (focusedObject == m_object) {
        V_VT(pvFocusedChild) = VT_I4;
        V_I4(pvFocusedChild) = CHILDID_SELF;
        return S_OK;
    }

    V_VT(pvFocusedChild) = VT_DISPATCH;
    V_DISPATCH(pvFocusedChild) = wrapper(focusedObject);
    V_DISPATCH(pvFocusedChild)->AddRef();
    return S_OK;
}

HRESULT AccessibleBase::get_accDefaultAction(VARIANT vChild, __deref_opt_out BSTR* action)
{
    if (!action)
        return E_POINTER;

    *action = nullptr;

    AccessibilityObject* childObj;
    HRESULT hr = getAccessibilityObjectForChild(vChild, childObj);

    if (FAILED(hr))
        return hr;

    if (*action = BString(childObj->actionVerb()).release())
        return S_OK;
    return S_FALSE;
}

HRESULT AccessibleBase::accLocation(_Out_ long* left, _Out_ long* top, _Out_ long* width, _Out_ long* height, VARIANT vChild)
{
    if (!left || !top || !width || !height)
        return E_POINTER;

    *left = *top = *width = *height = 0;

    AccessibilityObject* childObj;
    HRESULT hr = getAccessibilityObjectForChild(vChild, childObj);

    if (FAILED(hr))
        return hr;

    if (!childObj->documentFrameView())
        return E_FAIL;

    IntRect screenRect(childObj->documentFrameView()->contentsToScreen(snappedIntRect(childObj->elementRect())));
    *left = screenRect.x();
    *top = screenRect.y();
    *width = screenRect.width();
    *height = screenRect.height();
    return S_OK;
}

HRESULT AccessibleBase::accNavigate(long direction, VARIANT vFromChild, _Out_ VARIANT* pvNavigatedTo)
{
    if (!pvNavigatedTo)
        return E_POINTER;

    ::VariantInit(pvNavigatedTo);

    AccessibilityObject* childObj = nullptr;

    switch (direction) {
        case NAVDIR_DOWN:
        case NAVDIR_UP:
        case NAVDIR_LEFT:
        case NAVDIR_RIGHT:
            // These directions are not implemented, matching Mozilla and IE.
            return E_NOTIMPL;
        case NAVDIR_LASTCHILD:
        case NAVDIR_FIRSTCHILD:
            // MSDN states that navigating to first/last child can only be from self.
            if (vFromChild.lVal != CHILDID_SELF)
                return E_INVALIDARG;

            if (!m_object)
                return E_FAIL;

            if (direction == NAVDIR_FIRSTCHILD)
                childObj = m_object->firstChild();
            else
                childObj = m_object->lastChild();
            break;
        case NAVDIR_NEXT:
        case NAVDIR_PREVIOUS: {
            // Navigating to next and previous is allowed from self or any of our children.
            HRESULT hr = getAccessibilityObjectForChild(vFromChild, childObj);
            if (FAILED(hr))
                return hr;

            if (direction == NAVDIR_NEXT)
                childObj = childObj->nextSibling();
            else
                childObj = childObj->previousSibling();
            break;
        }
        default:
            ASSERT_NOT_REACHED();
            return E_INVALIDARG;
    }

    if (!childObj)
        return S_FALSE;

    V_VT(pvNavigatedTo) = VT_DISPATCH;
    V_DISPATCH(pvNavigatedTo) = wrapper(childObj);
    V_DISPATCH(pvNavigatedTo)->AddRef();
    return S_OK;
}

HRESULT AccessibleBase::accHitTest(long x, long y, _Out_ VARIANT* pvChildAtPoint)
{
    if (!pvChildAtPoint)
        return E_POINTER;

    ::VariantInit(pvChildAtPoint);

    if (!m_object || !m_object->documentFrameView())
        return E_FAIL;

    IntPoint point = m_object->documentFrameView()->screenToContents(IntPoint(x, y));
    auto childObject = downcast<AccessibilityObject>(m_object->accessibilityHitTest(point));

    if (!childObject) {
        // If we did not hit any child objects, test whether the point hit us, and
        // report that.
        if (!m_object->boundingBoxRect().contains(point))
            return S_FALSE;
        childObject = m_object;
    }

    if (childObject == m_object) {
        V_VT(pvChildAtPoint) = VT_I4;
        V_I4(pvChildAtPoint) = CHILDID_SELF;
    } else {
        V_VT(pvChildAtPoint) = VT_DISPATCH;
        V_DISPATCH(pvChildAtPoint) = static_cast<IDispatch*>(wrapper(childObject));
        V_DISPATCH(pvChildAtPoint)->AddRef();
    }
    return S_OK;
}

HRESULT AccessibleBase::accDoDefaultAction(VARIANT vChild)
{
    AccessibilityObject* childObj;
    HRESULT hr = getAccessibilityObjectForChild(vChild, childObj);

    if (FAILED(hr))
        return hr;

    if (!childObj->performDefaultAction())
        return S_FALSE;

    return S_OK;
}

// AccessibleBase
String AccessibleBase::name() const
{
    return m_object->nameForMSAA();
}

String AccessibleBase::value() const
{
    return m_object->stringValueForMSAA();
}

static long MSAARole(AccessibilityRole role)
{
    switch (role) {
    case AccessibilityRole::Button:
        return ROLE_SYSTEM_PUSHBUTTON;
    case AccessibilityRole::RadioButton:
        return ROLE_SYSTEM_RADIOBUTTON;
    case AccessibilityRole::CheckBox:
    case AccessibilityRole::ToggleButton:
    case AccessibilityRole::Switch:
        return ROLE_SYSTEM_CHECKBUTTON;
    case AccessibilityRole::Slider:
        return ROLE_SYSTEM_SLIDER;
    case AccessibilityRole::TabGroup:
    case AccessibilityRole::TabList:
        return ROLE_SYSTEM_PAGETABLIST;
    case AccessibilityRole::TextField:
    case AccessibilityRole::TextArea:
    case AccessibilityRole::EditableText:
        return ROLE_SYSTEM_TEXT;
    case AccessibilityRole::Heading:
    case AccessibilityRole::ListMarker:
    case AccessibilityRole::StaticText:
    case AccessibilityRole::Label:
        return ROLE_SYSTEM_STATICTEXT;
    case AccessibilityRole::Outline:
        return ROLE_SYSTEM_OUTLINE;
    case AccessibilityRole::Column:
        return ROLE_SYSTEM_COLUMN;
    case AccessibilityRole::Row:
        return ROLE_SYSTEM_ROW;
    case AccessibilityRole::ApplicationGroup:
    case AccessibilityRole::Group:
    case AccessibilityRole::RadioGroup:
        return ROLE_SYSTEM_GROUPING;
    case AccessibilityRole::DescriptionList:
    case AccessibilityRole::Directory:
    case AccessibilityRole::List:
    case AccessibilityRole::ListBox:
    case AccessibilityRole::MenuListPopup:
        return ROLE_SYSTEM_LIST;
    case AccessibilityRole::Grid:
    case AccessibilityRole::Table:
        return ROLE_SYSTEM_TABLE;
    case AccessibilityRole::ImageMapLink:
    case AccessibilityRole::Link:
    case AccessibilityRole::WebCoreLink:
        return ROLE_SYSTEM_LINK;
    case AccessibilityRole::Canvas:
    case AccessibilityRole::ImageMap:
    case AccessibilityRole::Image:
        return ROLE_SYSTEM_GRAPHIC;
    case AccessibilityRole::ListItem:
        return ROLE_SYSTEM_LISTITEM;
    case AccessibilityRole::ListBoxOption:
    case AccessibilityRole::MenuListOption:
        return ROLE_SYSTEM_STATICTEXT;
    case AccessibilityRole::ComboBox:
    case AccessibilityRole::PopUpButton:
        return ROLE_SYSTEM_COMBOBOX;
    case AccessibilityRole::Div:
    case AccessibilityRole::Footer:
    case AccessibilityRole::Form:
    case AccessibilityRole::Paragraph:
        return ROLE_SYSTEM_GROUPING;
    case AccessibilityRole::HorizontalRule:
    case AccessibilityRole::Splitter:
        return ROLE_SYSTEM_SEPARATOR;
    case AccessibilityRole::ApplicationAlert:
    case AccessibilityRole::ApplicationAlertDialog:
        return ROLE_SYSTEM_ALERT;
    case AccessibilityRole::DisclosureTriangle:
        return ROLE_SYSTEM_BUTTONDROPDOWN;
    case AccessibilityRole::Incrementor:
    case AccessibilityRole::SpinButton:
        return ROLE_SYSTEM_SPINBUTTON;
    case AccessibilityRole::SpinButtonPart:
        return ROLE_SYSTEM_PUSHBUTTON;
    case AccessibilityRole::Toolbar:
        return ROLE_SYSTEM_TOOLBAR;
    case AccessibilityRole::UserInterfaceTooltip:
        return ROLE_SYSTEM_TOOLTIP;
    case AccessibilityRole::Tree:
    case AccessibilityRole::TreeGrid:
        return ROLE_SYSTEM_OUTLINE;
    case AccessibilityRole::TreeItem:
        return ROLE_SYSTEM_OUTLINEITEM;
    case AccessibilityRole::TabPanel:
        return ROLE_SYSTEM_GROUPING;
    case AccessibilityRole::Tab:
        return ROLE_SYSTEM_PAGETAB;
    case AccessibilityRole::Application:
        return ROLE_SYSTEM_APPLICATION;
    case AccessibilityRole::ApplicationDialog:
        return ROLE_SYSTEM_DIALOG;
    case AccessibilityRole::ApplicationLog:
    case AccessibilityRole::ApplicationMarquee:
        return ROLE_SYSTEM_GROUPING;
    case AccessibilityRole::ApplicationStatus:
        return ROLE_SYSTEM_STATUSBAR;
    case AccessibilityRole::ApplicationTimer:
        return ROLE_SYSTEM_CLOCK;
    case AccessibilityRole::Cell:
        return ROLE_SYSTEM_CELL;
    case AccessibilityRole::ColumnHeader:
        return ROLE_SYSTEM_COLUMNHEADER;
    case AccessibilityRole::Definition:
    case AccessibilityRole::DescriptionListDetail:
    case AccessibilityRole::DescriptionListTerm:
    case AccessibilityRole::Document:
    case AccessibilityRole::DocumentArticle:
    case AccessibilityRole::DocumentNote:
        return ROLE_SYSTEM_GROUPING;
    case AccessibilityRole::DocumentMath:
    case AccessibilityRole::MathElement:
        return ROLE_SYSTEM_EQUATION;
    case AccessibilityRole::HelpTag:
        return ROLE_SYSTEM_HELPBALLOON;
    case AccessibilityRole::WebApplication:
    case AccessibilityRole::LandmarkBanner:
    case AccessibilityRole::LandmarkComplementary:
    case AccessibilityRole::LandmarkContentInfo:
    case AccessibilityRole::LandmarkMain:
    case AccessibilityRole::LandmarkNavigation:
    case AccessibilityRole::LandmarkRegion:
    case AccessibilityRole::LandmarkSearch:
    case AccessibilityRole::Legend:
        return ROLE_SYSTEM_GROUPING;
    case AccessibilityRole::Menu:
        return ROLE_SYSTEM_MENUPOPUP;
    case AccessibilityRole::MenuItem:
    case AccessibilityRole::MenuButton:
        return ROLE_SYSTEM_MENUITEM;
    case AccessibilityRole::MenuBar:
        return ROLE_SYSTEM_MENUBAR;
    case AccessibilityRole::ProgressIndicator:
        return ROLE_SYSTEM_PROGRESSBAR;
    case AccessibilityRole::RowHeader:
        return ROLE_SYSTEM_ROWHEADER;
    case AccessibilityRole::ScrollBar:
        return ROLE_SYSTEM_SCROLLBAR;
    case AccessibilityRole::SVGRoot:
        return ROLE_SYSTEM_GROUPING;
    case AccessibilityRole::TableHeaderContainer:
        return ROLE_SYSTEM_GROUPING;
    case AccessibilityRole::Window:
        return ROLE_SYSTEM_WINDOW;
    default:
        // This is the default role for MSAA.
        return ROLE_SYSTEM_CLIENT;
    }
}

long AccessibleBase::role() const
{
    return MSAARole(m_object->roleValueForMSAA());
}

HRESULT AccessibleBase::getAccessibilityObjectForChild(VARIANT vChild, AccessibilityObject*& childObj) const
{
    childObj = 0;

    if (!m_object)
        return E_FAIL;

    m_object->updateBackingStore();

    if (vChild.vt != VT_I4)
        return E_INVALIDARG;

    if (vChild.lVal == CHILDID_SELF)
        childObj = m_object;
    else if (vChild.lVal < 0) {
        // When broadcasting MSAA events, we negate the AXID and pass it as the
        // child ID.
        Document* document = m_object->document();
        if (!document)
            return E_FAIL;

        childObj = document->axObjectCache()->objectFromAXID(-vChild.lVal);
    } else {
        size_t childIndex = static_cast<size_t>(vChild.lVal - 1);

        if (childIndex >= m_object->children().size())
            return E_FAIL;
        childObj = m_object->children().at(childIndex).get();
    }

    if (!childObj)
        return E_FAIL;

    return S_OK;
}

AccessibleBase* AccessibleBase::wrapper(AccessibilityObject* obj) const
{
    AccessibleBase* result = static_cast<AccessibleBase*>(obj->wrapper());
    if (!result)
        result = createInstance(obj, m_window);
    return result;
}

HRESULT AccessibleBase::isSameObject(_In_opt_ IAccessibleComparable* other, _Out_ BOOL* result)
{
    if (!result || !other)
        return E_POINTER;

    COMPtr<AccessibleBase> otherAccessibleBase(Query, other);
    *result = (otherAccessibleBase == this || otherAccessibleBase->m_object == m_object);
    return S_OK;
}