/*
* 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.
*
* 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 "config.h"
#include "WebKitDLL.h"
#include "AccessibleBase.h"
#include "AccessibleImage.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/PlatformKeyboardEvent.h>
#include <WebCore/RenderFrame.h>
#include <WebCore/RenderObject.h>
#include <WebCore/RenderView.h>
#include <oleacc.h>
#include <wtf/RefPtr.h>
using namespace WebCore;
AccessibleBase::AccessibleBase(AccessibilityObject* obj)
: AccessibilityObjectWrapper(obj)
, m_refCount(0)
{
ASSERT_ARG(obj, obj);
m_object->setWrapper(this);
++gClassCount;
gClassNameCount.add("AccessibleBase");
}
AccessibleBase::~AccessibleBase()
{
--gClassCount;
gClassNameCount.remove("AccessibleBase");
}
AccessibleBase* AccessibleBase::createInstance(AccessibilityObject* obj)
{
ASSERT_ARG(obj, obj);
if (obj->isImage())
return new AccessibleImage(obj);
return new AccessibleBase(obj);
}
HRESULT AccessibleBase::QueryService(REFGUID guidService, REFIID riid, void **ppvObject)
{
if (!IsEqualGUID(guidService, SID_AccessibleComparable)) {
*ppvObject = 0;
return E_INVALIDARG;
}
return QueryInterface(riid, ppvObject);
}
// IUnknown
HRESULT STDMETHODCALLTYPE AccessibleBase::QueryInterface(REFIID riid, void** ppvObject)
{
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(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 = 0;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
ULONG STDMETHODCALLTYPE AccessibleBase::Release(void)
{
ASSERT(m_refCount > 0);
if (--m_refCount)
return m_refCount;
delete this;
return 0;
}
// IAccessible
HRESULT STDMETHODCALLTYPE AccessibleBase::get_accParent(IDispatch** parent)
{
*parent = 0;
if (!m_object)
return E_FAIL;
AccessibilityObject* parentObject = m_object->parentObjectUnignored();
if (parentObject) {
*parent = wrapper(parentObject);
(*parent)->AddRef();
return S_OK;
}
if (!m_object->topDocumentFrameView())
return E_FAIL;
return WebView::AccessibleObjectFromWindow(m_object->topDocumentFrameView()->hostWindow()->platformPageClient(),
OBJID_WINDOW, __uuidof(IAccessible), reinterpret_cast<void**>(parent));
}
HRESULT STDMETHODCALLTYPE AccessibleBase::get_accChildCount(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 STDMETHODCALLTYPE AccessibleBase::get_accChild(VARIANT vChild, IDispatch** ppChild)
{
if (!ppChild)
return E_POINTER;
*ppChild = 0;
AccessibilityObject* childObj;
HRESULT hr = getAccessibilityObjectForChild(vChild, childObj);
if (FAILED(hr))
return hr;
*ppChild = static_cast<IDispatch*>(wrapper(childObj));
(*ppChild)->AddRef();
return S_OK;
}
HRESULT STDMETHODCALLTYPE AccessibleBase::get_accName(VARIANT vChild, BSTR* name)
{
if (!name)
return E_POINTER;
*name = 0;
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 STDMETHODCALLTYPE AccessibleBase::get_accValue(VARIANT vChild, BSTR* value)
{
if (!value)
return E_POINTER;
*value = 0;
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 STDMETHODCALLTYPE AccessibleBase::get_accDescription(VARIANT vChild, BSTR* description)
{
if (!description)
return E_POINTER;
*description = 0;
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 STDMETHODCALLTYPE AccessibleBase::get_accRole(VARIANT vChild, 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;
}
HRESULT STDMETHODCALLTYPE AccessibleBase::get_accState(VARIANT vChild, 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 = 0;
if (childObj->isLinked())
pvState->lVal |= STATE_SYSTEM_LINKED;
if (childObj->isHovered())
pvState->lVal |= STATE_SYSTEM_HOTTRACKED;
if (!childObj->isEnabled())
pvState->lVal |= STATE_SYSTEM_UNAVAILABLE;
if (childObj->isReadOnly())
pvState->lVal |= STATE_SYSTEM_READONLY;
if (childObj->isOffScreen())
pvState->lVal |= STATE_SYSTEM_OFFSCREEN;
if (childObj->isPasswordField())
pvState->lVal |= STATE_SYSTEM_PROTECTED;
if (childObj->isIndeterminate())
pvState->lVal |= STATE_SYSTEM_INDETERMINATE;
if (childObj->isChecked())
pvState->lVal |= STATE_SYSTEM_CHECKED;
if (childObj->isPressed())
pvState->lVal |= STATE_SYSTEM_PRESSED;
if (childObj->isFocused())
pvState->lVal |= STATE_SYSTEM_FOCUSED;
if (childObj->isVisited())
pvState->lVal |= STATE_SYSTEM_TRAVERSED;
if (childObj->canSetFocusAttribute())
pvState->lVal |= STATE_SYSTEM_FOCUSABLE;
if (childObj->isSelected())
pvState->lVal |= STATE_SYSTEM_SELECTED;
if (childObj->canSetSelectedAttribute())
pvState->lVal |= STATE_SYSTEM_SELECTABLE;
if (childObj->isMultiSelectable())
pvState->lVal |= STATE_SYSTEM_EXTSELECTABLE | STATE_SYSTEM_MULTISELECTABLE;
if (!childObj->isVisible())
pvState->lVal |= STATE_SYSTEM_INVISIBLE;
if (childObj->isCollapsed())
pvState->lVal |= STATE_SYSTEM_COLLAPSED;
if (childObj->roleValue() == PopUpButtonRole) {
pvState->lVal |= STATE_SYSTEM_HASPOPUP;
if (!childObj->isCollapsed())
pvState->lVal |= STATE_SYSTEM_EXPANDED;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE AccessibleBase::get_accHelp(VARIANT vChild, BSTR* helpText)
{
if (!helpText)
return E_POINTER;
*helpText = 0;
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 STDMETHODCALLTYPE AccessibleBase::get_accKeyboardShortcut(VARIANT vChild, BSTR* shortcut)
{
if (!shortcut)
return E_POINTER;
*shortcut = 0;
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()) {
unsigned 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 & PlatformKeyboardEvent::CtrlKey)
accessKeyModifiers += "Ctrl+";
if (modifiers & PlatformKeyboardEvent::AltKey)
accessKeyModifiers += "Alt+";
if (modifiers & PlatformKeyboardEvent::ShiftKey)
accessKeyModifiers += "Shift+";
if (modifiers & PlatformKeyboardEvent::MetaKey)
accessKeyModifiers += "Win+";
}
*shortcut = BString(accessKeyModifiers + accessKey).release();
return S_OK;
}
HRESULT STDMETHODCALLTYPE 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 (parentObject->isListBox()) {
Vector<RefPtr<AccessibilityObject> > selectedChildren(1);
selectedChildren[0] = childObject;
static_cast<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 STDMETHODCALLTYPE AccessibleBase::get_accSelection(VARIANT*)
{
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE AccessibleBase::get_accFocus(VARIANT* pvFocusedChild)
{
if (!pvFocusedChild)
return E_POINTER;
::VariantInit(pvFocusedChild);
if (!m_object)
return E_FAIL;
AccessibilityObject* focusedObj = m_object->focusedUIElement();
if (!focusedObj)
return S_FALSE;
if (focusedObj == m_object) {
V_VT(pvFocusedChild) = VT_I4;
V_I4(pvFocusedChild) = CHILDID_SELF;
return S_OK;
}
V_VT(pvFocusedChild) = VT_DISPATCH;
V_DISPATCH(pvFocusedChild) = wrapper(focusedObj);
V_DISPATCH(pvFocusedChild)->AddRef();
return S_OK;
}
HRESULT STDMETHODCALLTYPE AccessibleBase::get_accDefaultAction(VARIANT vChild, BSTR* action)
{
if (!action)
return E_POINTER;
*action = 0;
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 STDMETHODCALLTYPE AccessibleBase::accLocation(long* left, long* top, long* width, 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(childObj->elementRect()));
*left = screenRect.x();
*top = screenRect.y();
*width = screenRect.width();
*height = screenRect.height();
return S_OK;
}
HRESULT STDMETHODCALLTYPE AccessibleBase::accNavigate(long direction, VARIANT vFromChild, VARIANT* pvNavigatedTo)
{
if (!pvNavigatedTo)
return E_POINTER;
::VariantInit(pvNavigatedTo);
AccessibilityObject* childObj = 0;
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 STDMETHODCALLTYPE AccessibleBase::accHitTest(long x, long y, 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));
AccessibilityObject* childObj = m_object->accessibilityHitTest(point);
if (!childObj) {
// 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;
childObj = m_object;
}
if (childObj == 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(childObj));
V_DISPATCH(pvChildAtPoint)->AddRef();
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE 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 WebCore::ButtonRole:
return ROLE_SYSTEM_PUSHBUTTON;
case WebCore::RadioButtonRole:
return ROLE_SYSTEM_RADIOBUTTON;
case WebCore::CheckBoxRole:
return ROLE_SYSTEM_CHECKBUTTON;
case WebCore::SliderRole:
return ROLE_SYSTEM_SLIDER;
case WebCore::TabGroupRole:
return ROLE_SYSTEM_PAGETABLIST;
case WebCore::TextFieldRole:
case WebCore::TextAreaRole:
case WebCore::EditableTextRole:
return ROLE_SYSTEM_TEXT;
case WebCore::ListMarkerRole:
case WebCore::StaticTextRole:
return ROLE_SYSTEM_STATICTEXT;
case WebCore::OutlineRole:
return ROLE_SYSTEM_OUTLINE;
case WebCore::ColumnRole:
return ROLE_SYSTEM_COLUMN;
case WebCore::RowRole:
return ROLE_SYSTEM_ROW;
case WebCore::GroupRole:
return ROLE_SYSTEM_GROUPING;
case WebCore::ListRole:
case WebCore::ListBoxRole:
case WebCore::MenuListPopupRole:
return ROLE_SYSTEM_LIST;
case WebCore::TableRole:
return ROLE_SYSTEM_TABLE;
case WebCore::LinkRole:
case WebCore::WebCoreLinkRole:
return ROLE_SYSTEM_LINK;
case WebCore::ImageMapRole:
case WebCore::ImageRole:
return ROLE_SYSTEM_GRAPHIC;
case WebCore::MenuListOptionRole:
case WebCore::ListItemRole:
case WebCore::ListBoxOptionRole:
return ROLE_SYSTEM_LISTITEM;
case WebCore::PopUpButtonRole:
return ROLE_SYSTEM_COMBOBOX;
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;
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)
{
AccessibleBase* result = static_cast<AccessibleBase*>(obj->wrapper());
if (!result)
result = createInstance(obj);
return result;
}
HRESULT AccessibleBase::isSameObject(IAccessibleComparable* other, BOOL* result)
{
COMPtr<AccessibleBase> otherAccessibleBase(Query, other);
*result = (otherAccessibleBase == this || otherAccessibleBase->m_object == m_object);
return S_OK;
}