| /* |
| * 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"_s); |
| } |
| |
| AccessibleBase::~AccessibleBase() |
| { |
| --gClassCount; |
| gClassNameCount().remove("AccessibleBase"_s); |
| } |
| |
| 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 keyAtom(key, ::SysStringLen(key)); |
| |
| accessibilityAttributeValue(keyAtom, 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->objectID().toUInt64()); |
| 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; |
| |
| #if USE(CF) |
| locale->language = BString(m_object->language().createCFString().get()).release(); |
| #else |
| locale->language = BString(m_object->language()).release(); |
| #endif |
| |
| 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 = static_cast<AccessibilityObject*>(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.append("Ctrl+"); |
| if (modifiers.contains(PlatformEvent::Modifier::AltKey)) |
| accessKeyModifiersBuilder.append("Alt+"); |
| if (modifiers.contains(PlatformEvent::Modifier::ShiftKey)) |
| accessKeyModifiersBuilder.append("Shift+"); |
| if (modifiers.contains(PlatformEvent::Modifier::MetaKey)) |
| accessKeyModifiersBuilder.append("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<AXCoreObject> > 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->localizedActionVerb()).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()->objectForID(makeObjectIdentifier<AXIDType>(-vChild.lVal)); |
| } else { |
| size_t childIndex = static_cast<size_t>(vChild.lVal - 1); |
| |
| if (childIndex >= m_object->children().size()) |
| return E_FAIL; |
| childObj = static_cast<AccessibilityObject*>(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; |
| } |