mirror of https://github.com/roytam1/UXP
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1685 lines
42 KiB
1685 lines
42 KiB
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
/* This Source Code Form is subject to the terms of the Mozilla Public |
|
* License, v. 2.0. If a copy of the MPL was not distributed with this |
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
|
|
#include "AccessibleWrap.h" |
|
#include "Accessible-inl.h" |
|
|
|
#include "Compatibility.h" |
|
#include "DocAccessible-inl.h" |
|
#include "mozilla/dom/TabChild.h" |
|
#include "mozilla/a11y/DocAccessibleChild.h" |
|
#include "mozilla/a11y/DocAccessibleParent.h" |
|
#include "EnumVariant.h" |
|
#include "nsAccUtils.h" |
|
#include "nsCoreUtils.h" |
|
#include "nsIAccessibleEvent.h" |
|
#include "nsWinUtils.h" |
|
#include "mozilla/a11y/ProxyAccessible.h" |
|
#include "ProxyWrappers.h" |
|
#include "ServiceProvider.h" |
|
#include "Relation.h" |
|
#include "Role.h" |
|
#include "RootAccessible.h" |
|
#include "sdnAccessible.h" |
|
#include "States.h" |
|
|
|
#ifdef A11Y_LOG |
|
#include "Logging.h" |
|
#endif |
|
|
|
#include "nsIMutableArray.h" |
|
#include "nsIFrame.h" |
|
#include "nsIScrollableFrame.h" |
|
#include "mozilla/dom/NodeInfo.h" |
|
#include "nsIServiceManager.h" |
|
#include "nsNameSpaceManager.h" |
|
#include "nsTextFormatter.h" |
|
#include "nsView.h" |
|
#include "nsViewManager.h" |
|
#include "nsEventMap.h" |
|
#include "nsArrayUtils.h" |
|
#include "mozilla/Preferences.h" |
|
#include "nsIXULRuntime.h" |
|
|
|
#include "oleacc.h" |
|
|
|
using namespace mozilla; |
|
using namespace mozilla::a11y; |
|
|
|
const uint32_t USE_ROLE_STRING = 0; |
|
|
|
/* For documentation of the accessibility architecture, |
|
* see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html |
|
*/ |
|
|
|
//#define DEBUG_LEAKS |
|
|
|
#ifdef DEBUG_LEAKS |
|
static gAccessibles = 0; |
|
#endif |
|
|
|
MsaaIdGenerator AccessibleWrap::sIDGen; |
|
|
|
static const VARIANT kVarChildIdSelf = {VT_I4}; |
|
|
|
static const int32_t kIEnumVariantDisconnected = -1; |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
// AccessibleWrap |
|
//////////////////////////////////////////////////////////////////////////////// |
|
AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) : |
|
Accessible(aContent, aDoc) |
|
, mID(kNoID) |
|
{ |
|
} |
|
|
|
AccessibleWrap::~AccessibleWrap() |
|
{ |
|
if (mID != kNoID) { |
|
sIDGen.ReleaseID(this); |
|
} |
|
} |
|
|
|
ITypeInfo* AccessibleWrap::gTypeInfo = nullptr; |
|
|
|
NS_IMPL_ISUPPORTS_INHERITED0(AccessibleWrap, Accessible) |
|
|
|
void |
|
AccessibleWrap::Shutdown() |
|
{ |
|
if (mID != kNoID) { |
|
auto doc = static_cast<DocAccessibleWrap*>(mDoc.get()); |
|
MOZ_ASSERT(doc); |
|
if (doc) { |
|
doc->RemoveID(mID); |
|
mID = kNoID; |
|
} |
|
} |
|
|
|
Accessible::Shutdown(); |
|
} |
|
|
|
//----------------------------------------------------- |
|
// IUnknown interface methods - see iunknown.h for documentation |
|
//----------------------------------------------------- |
|
|
|
// Microsoft COM QueryInterface |
|
STDMETHODIMP |
|
AccessibleWrap::QueryInterface(REFIID iid, void** ppv) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!ppv) |
|
return E_INVALIDARG; |
|
|
|
*ppv = nullptr; |
|
|
|
if (IID_IUnknown == iid) |
|
*ppv = static_cast<IAccessible*>(this); |
|
else if (IID_IDispatch == iid || IID_IAccessible == iid) |
|
*ppv = static_cast<IAccessible*>(this); |
|
else if (IID_IEnumVARIANT == iid && !IsProxy()) { |
|
// Don't support this interface for leaf elements. |
|
if (!HasChildren() || nsAccUtils::MustPrune(this)) |
|
return E_NOINTERFACE; |
|
|
|
*ppv = static_cast<IEnumVARIANT*>(new ChildrenEnumVariant(this)); |
|
} else if (IID_IServiceProvider == iid) |
|
*ppv = new ServiceProvider(this); |
|
else if (IID_ISimpleDOMNode == iid && !IsProxy()) { |
|
if (IsDefunct() || (!HasOwnContent() && !IsDoc())) |
|
return E_NOINTERFACE; |
|
|
|
*ppv = static_cast<ISimpleDOMNode*>(new sdnAccessible(GetNode())); |
|
} |
|
|
|
if (nullptr == *ppv) { |
|
HRESULT hr = ia2Accessible::QueryInterface(iid, ppv); |
|
if (SUCCEEDED(hr)) |
|
return hr; |
|
} |
|
|
|
if (nullptr == *ppv && !IsProxy()) { |
|
HRESULT hr = ia2AccessibleComponent::QueryInterface(iid, ppv); |
|
if (SUCCEEDED(hr)) |
|
return hr; |
|
} |
|
|
|
if (nullptr == *ppv) { |
|
HRESULT hr = ia2AccessibleHyperlink::QueryInterface(iid, ppv); |
|
if (SUCCEEDED(hr)) |
|
return hr; |
|
} |
|
|
|
if (nullptr == *ppv && !IsProxy()) { |
|
HRESULT hr = ia2AccessibleValue::QueryInterface(iid, ppv); |
|
if (SUCCEEDED(hr)) |
|
return hr; |
|
} |
|
|
|
if (nullptr == *ppv) |
|
return E_NOINTERFACE; |
|
|
|
(reinterpret_cast<IUnknown*>(*ppv))->AddRef(); |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
//----------------------------------------------------- |
|
// IAccessible methods |
|
//----------------------------------------------------- |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accParent( IDispatch __RPC_FAR *__RPC_FAR *ppdispParent) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!ppdispParent) |
|
return E_INVALIDARG; |
|
|
|
*ppdispParent = nullptr; |
|
|
|
if (IsDefunct()) |
|
return CO_E_OBJNOTCONNECTED; |
|
|
|
DocAccessible* doc = AsDoc(); |
|
if (doc) { |
|
// Return window system accessible object for root document and tab document |
|
// accessibles. |
|
if (!doc->ParentDocument() || |
|
(nsWinUtils::IsWindowEmulationStarted() && |
|
nsCoreUtils::IsTabDocument(doc->DocumentNode()))) { |
|
HWND hwnd = static_cast<HWND>(doc->GetNativeWindow()); |
|
if (hwnd && SUCCEEDED(::AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, |
|
IID_IAccessible, |
|
(void**)ppdispParent))) { |
|
return S_OK; |
|
} |
|
} |
|
} |
|
|
|
Accessible* xpParentAcc = Parent(); |
|
if (!xpParentAcc) |
|
return S_FALSE; |
|
|
|
*ppdispParent = NativeAccessible(xpParentAcc); |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accChildCount( long __RPC_FAR *pcountChildren) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pcountChildren) |
|
return E_INVALIDARG; |
|
|
|
*pcountChildren = 0; |
|
|
|
if (IsDefunct()) |
|
return CO_E_OBJNOTCONNECTED; |
|
|
|
if (nsAccUtils::MustPrune(this)) |
|
return S_OK; |
|
|
|
*pcountChildren = ChildCount(); |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accChild( |
|
/* [in] */ VARIANT varChild, |
|
/* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispChild) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!ppdispChild) |
|
return E_INVALIDARG; |
|
|
|
*ppdispChild = nullptr; |
|
if (IsDefunct()) |
|
return CO_E_OBJNOTCONNECTED; |
|
|
|
// IAccessible::accChild is used to return this accessible or child accessible |
|
// at the given index or to get an accessible by child ID in the case of |
|
// document accessible. |
|
// The getting an accessible by child ID is used by AccessibleObjectFromEvent() |
|
// called by AT when AT handles our MSAA event. |
|
bool isDefunct = false; |
|
RefPtr<IAccessible> child = GetIAccessibleFor(varChild, &isDefunct); |
|
if (!child) { |
|
return E_INVALIDARG; |
|
} |
|
|
|
if (isDefunct) { |
|
return CO_E_OBJNOTCONNECTED; |
|
} |
|
|
|
child.forget(ppdispChild); |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
/** |
|
* This function is a helper for implementing IAccessible methods that accept |
|
* a Child ID as a parameter. If the child ID is CHILDID_SELF, the function |
|
* returns S_OK but a null *aOutInterface. Otherwise, *aOutInterface points |
|
* to the resolved IAccessible. |
|
* |
|
* The CHILDID_SELF case is special because in that case we actually execute |
|
* the implementation of the IAccessible method, whereas in the non-self case, |
|
* we delegate the method call to that object for execution. |
|
* |
|
* A sample invocation of this would look like: |
|
* |
|
* RefPtr<IAccessible> accessible; |
|
* HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); |
|
* if (FAILED(hr)) { |
|
* return hr; |
|
* } |
|
* |
|
* if (accessible) { |
|
* return accessible->get_accFoo(kVarChildIdSelf, pszName); |
|
* } |
|
* |
|
* // Implementation for CHILDID_SELF case goes here |
|
*/ |
|
HRESULT |
|
AccessibleWrap::ResolveChild(const VARIANT& aVarChild, |
|
IAccessible** aOutInterface) |
|
{ |
|
MOZ_ASSERT(aOutInterface); |
|
*aOutInterface = nullptr; |
|
|
|
if (aVarChild.vt != VT_I4) { |
|
return E_INVALIDARG; |
|
} |
|
|
|
if (IsDefunct()) { |
|
return CO_E_OBJNOTCONNECTED; |
|
} |
|
|
|
if (aVarChild.lVal == CHILDID_SELF) { |
|
return S_OK; |
|
} |
|
|
|
bool isDefunct = false; |
|
RefPtr<IAccessible> accessible = GetIAccessibleFor(aVarChild, &isDefunct); |
|
if (!accessible) { |
|
return E_INVALIDARG; |
|
} |
|
|
|
if (isDefunct) { |
|
return CO_E_OBJNOTCONNECTED; |
|
} |
|
|
|
accessible.forget(aOutInterface); |
|
return S_OK; |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accName( |
|
/* [optional][in] */ VARIANT varChild, |
|
/* [retval][out] */ BSTR __RPC_FAR *pszName) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pszName || varChild.vt != VT_I4) |
|
return E_INVALIDARG; |
|
|
|
*pszName = nullptr; |
|
|
|
RefPtr<IAccessible> accessible; |
|
HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); |
|
if (FAILED(hr)) { |
|
return hr; |
|
} |
|
|
|
if (accessible) { |
|
return accessible->get_accName(kVarChildIdSelf, pszName); |
|
} |
|
|
|
nsAutoString name; |
|
Name(name); |
|
|
|
// The name was not provided, e.g. no alt attribute for an image. A screen |
|
// reader may choose to invent its own accessible name, e.g. from an image src |
|
// attribute. Refer to eNoNameOnPurpose return value. |
|
if (name.IsVoid()) |
|
return S_FALSE; |
|
|
|
*pszName = ::SysAllocStringLen(name.get(), name.Length()); |
|
if (!*pszName) |
|
return E_OUTOFMEMORY; |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accValue( |
|
/* [optional][in] */ VARIANT varChild, |
|
/* [retval][out] */ BSTR __RPC_FAR *pszValue) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pszValue) |
|
return E_INVALIDARG; |
|
|
|
*pszValue = nullptr; |
|
|
|
RefPtr<IAccessible> accessible; |
|
HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); |
|
if (FAILED(hr)) { |
|
return hr; |
|
} |
|
|
|
if (accessible) { |
|
return accessible->get_accValue(kVarChildIdSelf, pszValue); |
|
} |
|
|
|
nsAutoString value; |
|
Value(value); |
|
|
|
// See bug 438784: need to expose URL on doc's value attribute. For this, |
|
// reverting part of fix for bug 425693 to make this MSAA method behave |
|
// IAccessible2-style. |
|
if (value.IsEmpty()) |
|
return S_FALSE; |
|
|
|
*pszValue = ::SysAllocStringLen(value.get(), value.Length()); |
|
if (!*pszValue) |
|
return E_OUTOFMEMORY; |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accDescription(VARIANT varChild, |
|
BSTR __RPC_FAR *pszDescription) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pszDescription) |
|
return E_INVALIDARG; |
|
|
|
*pszDescription = nullptr; |
|
|
|
RefPtr<IAccessible> accessible; |
|
HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); |
|
if (FAILED(hr)) { |
|
return hr; |
|
} |
|
|
|
if (accessible) { |
|
return accessible->get_accDescription(kVarChildIdSelf, pszDescription); |
|
} |
|
|
|
nsAutoString description; |
|
Description(description); |
|
|
|
*pszDescription = ::SysAllocStringLen(description.get(), |
|
description.Length()); |
|
return *pszDescription ? S_OK : E_OUTOFMEMORY; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accRole( |
|
/* [optional][in] */ VARIANT varChild, |
|
/* [retval][out] */ VARIANT __RPC_FAR *pvarRole) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pvarRole) |
|
return E_INVALIDARG; |
|
|
|
VariantInit(pvarRole); |
|
|
|
RefPtr<IAccessible> accessible; |
|
HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); |
|
if (FAILED(hr)) { |
|
return hr; |
|
} |
|
|
|
if (accessible) { |
|
return accessible->get_accRole(kVarChildIdSelf, pvarRole); |
|
} |
|
|
|
a11y::role geckoRole; |
|
#ifdef DEBUG |
|
NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(this), |
|
"Does not support Text when it should"); |
|
#endif |
|
|
|
geckoRole = Role(); |
|
|
|
uint32_t msaaRole = 0; |
|
|
|
#define ROLE(_geckoRole, stringRole, atkRole, macRole, \ |
|
_msaaRole, ia2Role, nameRule) \ |
|
case roles::_geckoRole: \ |
|
msaaRole = _msaaRole; \ |
|
break; |
|
|
|
switch (geckoRole) { |
|
#include "RoleMap.h" |
|
default: |
|
MOZ_CRASH("Unknown role."); |
|
} |
|
|
|
#undef ROLE |
|
|
|
// Special case, if there is a ROLE_ROW inside of a ROLE_TREE_TABLE, then call the MSAA role |
|
// a ROLE_OUTLINEITEM for consistency and compatibility. |
|
// We need this because ARIA has a role of "row" for both grid and treegrid |
|
if (geckoRole == roles::ROW) { |
|
Accessible* xpParent = Parent(); |
|
if (xpParent && xpParent->Role() == roles::TREE_TABLE) |
|
msaaRole = ROLE_SYSTEM_OUTLINEITEM; |
|
} |
|
|
|
// -- Try enumerated role |
|
if (msaaRole != USE_ROLE_STRING) { |
|
pvarRole->vt = VT_I4; |
|
pvarRole->lVal = msaaRole; // Normal enumerated role |
|
return S_OK; |
|
} |
|
|
|
// -- Try BSTR role |
|
// Could not map to known enumerated MSAA role like ROLE_BUTTON |
|
// Use BSTR role to expose role attribute or tag name + namespace |
|
nsIContent *content = GetContent(); |
|
if (!content) |
|
return E_FAIL; |
|
|
|
if (content->IsElement()) { |
|
nsAutoString roleString; |
|
if (msaaRole != ROLE_SYSTEM_CLIENT && |
|
!content->GetAttr(kNameSpaceID_None, nsGkAtoms::role, roleString)) { |
|
nsIDocument * document = content->GetUncomposedDoc(); |
|
if (!document) |
|
return E_FAIL; |
|
|
|
dom::NodeInfo *nodeInfo = content->NodeInfo(); |
|
nodeInfo->GetName(roleString); |
|
|
|
// Only append name space if different from that of current document. |
|
if (!nodeInfo->NamespaceEquals(document->GetDefaultNamespaceID())) { |
|
nsAutoString nameSpaceURI; |
|
nodeInfo->GetNamespaceURI(nameSpaceURI); |
|
roleString += NS_LITERAL_STRING(", ") + nameSpaceURI; |
|
} |
|
} |
|
|
|
if (!roleString.IsEmpty()) { |
|
pvarRole->vt = VT_BSTR; |
|
pvarRole->bstrVal = ::SysAllocString(roleString.get()); |
|
return S_OK; |
|
} |
|
} |
|
|
|
return E_FAIL; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accState( |
|
/* [optional][in] */ VARIANT varChild, |
|
/* [retval][out] */ VARIANT __RPC_FAR *pvarState) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pvarState) |
|
return E_INVALIDARG; |
|
|
|
VariantInit(pvarState); |
|
pvarState->vt = VT_I4; |
|
pvarState->lVal = 0; |
|
|
|
RefPtr<IAccessible> accessible; |
|
HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); |
|
if (FAILED(hr)) { |
|
return hr; |
|
} |
|
|
|
if (accessible) { |
|
return accessible->get_accState(kVarChildIdSelf, pvarState); |
|
} |
|
|
|
// MSAA only has 31 states and the lowest 31 bits of our state bit mask |
|
// are the same states as MSAA. |
|
// Note: we map the following Gecko states to different MSAA states: |
|
// REQUIRED -> ALERT_LOW |
|
// ALERT -> ALERT_MEDIUM |
|
// INVALID -> ALERT_HIGH |
|
// CHECKABLE -> MARQUEED |
|
|
|
uint64_t state = State(); |
|
|
|
uint32_t msaaState = 0; |
|
nsAccUtils::To32States(state, &msaaState, nullptr); |
|
pvarState->lVal = msaaState; |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accHelp( |
|
/* [optional][in] */ VARIANT varChild, |
|
/* [retval][out] */ BSTR __RPC_FAR *pszHelp) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pszHelp) |
|
return E_INVALIDARG; |
|
|
|
*pszHelp = nullptr; |
|
return S_FALSE; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accHelpTopic( |
|
/* [out] */ BSTR __RPC_FAR *pszHelpFile, |
|
/* [optional][in] */ VARIANT varChild, |
|
/* [retval][out] */ long __RPC_FAR *pidTopic) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pszHelpFile || !pidTopic) |
|
return E_INVALIDARG; |
|
|
|
*pszHelpFile = nullptr; |
|
*pidTopic = 0; |
|
return S_FALSE; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accKeyboardShortcut( |
|
/* [optional][in] */ VARIANT varChild, |
|
/* [retval][out] */ BSTR __RPC_FAR *pszKeyboardShortcut) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pszKeyboardShortcut) |
|
return E_INVALIDARG; |
|
*pszKeyboardShortcut = nullptr; |
|
|
|
RefPtr<IAccessible> accessible; |
|
HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); |
|
if (FAILED(hr)) { |
|
return hr; |
|
} |
|
|
|
if (accessible) { |
|
return accessible->get_accKeyboardShortcut(kVarChildIdSelf, |
|
pszKeyboardShortcut); |
|
} |
|
|
|
KeyBinding keyBinding = AccessKey(); |
|
if (keyBinding.IsEmpty()) |
|
keyBinding = KeyboardShortcut(); |
|
|
|
nsAutoString shortcut; |
|
keyBinding.ToString(shortcut); |
|
|
|
*pszKeyboardShortcut = ::SysAllocStringLen(shortcut.get(), |
|
shortcut.Length()); |
|
return *pszKeyboardShortcut ? S_OK : E_OUTOFMEMORY; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accFocus( |
|
/* [retval][out] */ VARIANT __RPC_FAR *pvarChild) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pvarChild) |
|
return E_INVALIDARG; |
|
|
|
VariantInit(pvarChild); |
|
|
|
// VT_EMPTY: None. This object does not have the keyboard focus itself |
|
// and does not contain a child that has the keyboard focus. |
|
// VT_I4: lVal is CHILDID_SELF. The object itself has the keyboard focus. |
|
// VT_I4: lVal contains the child ID of the child element with the keyboard focus. |
|
// VT_DISPATCH: pdispVal member is the address of the IDispatch interface |
|
// for the child object with the keyboard focus. |
|
if (IsDefunct()) |
|
return CO_E_OBJNOTCONNECTED; |
|
|
|
// Return the current IAccessible child that has focus |
|
Accessible* focusedAccessible = FocusedChild(); |
|
|
|
if (focusedAccessible == this) { |
|
pvarChild->vt = VT_I4; |
|
pvarChild->lVal = CHILDID_SELF; |
|
} |
|
else if (focusedAccessible) { |
|
pvarChild->vt = VT_DISPATCH; |
|
pvarChild->pdispVal = NativeAccessible(focusedAccessible); |
|
} |
|
else { |
|
pvarChild->vt = VT_EMPTY; // No focus or focus is not a child |
|
} |
|
|
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
/** |
|
* This helper class implements IEnumVARIANT for a nsTArray containing |
|
* accessible objects. |
|
*/ |
|
class AccessibleEnumerator final : public IEnumVARIANT |
|
{ |
|
public: |
|
AccessibleEnumerator(const nsTArray<Accessible*>& aArray) : |
|
mArray(aArray), mCurIndex(0) { } |
|
AccessibleEnumerator(const AccessibleEnumerator& toCopy) : |
|
mArray(toCopy.mArray), mCurIndex(toCopy.mCurIndex) { } |
|
~AccessibleEnumerator() { } |
|
|
|
// IUnknown |
|
DECL_IUNKNOWN |
|
|
|
// IEnumVARIANT |
|
STDMETHODIMP Next(unsigned long celt, VARIANT FAR* rgvar, unsigned long FAR* pceltFetched); |
|
STDMETHODIMP Skip(unsigned long celt); |
|
STDMETHODIMP Reset() |
|
{ |
|
mCurIndex = 0; |
|
return S_OK; |
|
} |
|
STDMETHODIMP Clone(IEnumVARIANT FAR* FAR* ppenum); |
|
|
|
private: |
|
nsTArray<Accessible*> mArray; |
|
uint32_t mCurIndex; |
|
}; |
|
|
|
STDMETHODIMP |
|
AccessibleEnumerator::QueryInterface(REFIID iid, void ** ppvObject) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (iid == IID_IEnumVARIANT) { |
|
*ppvObject = static_cast<IEnumVARIANT*>(this); |
|
AddRef(); |
|
return S_OK; |
|
} |
|
if (iid == IID_IUnknown) { |
|
*ppvObject = static_cast<IUnknown*>(this); |
|
AddRef(); |
|
return S_OK; |
|
} |
|
|
|
*ppvObject = nullptr; |
|
return E_NOINTERFACE; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleEnumerator::Next(unsigned long celt, VARIANT FAR* rgvar, unsigned long FAR* pceltFetched) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
uint32_t length = mArray.Length(); |
|
HRESULT hr = S_OK; |
|
|
|
// Can't get more elements than there are... |
|
if (celt > length - mCurIndex) { |
|
hr = S_FALSE; |
|
celt = length - mCurIndex; |
|
} |
|
|
|
// Copy the elements of the array into rgvar. |
|
for (uint32_t i = 0; i < celt; ++i, ++mCurIndex) { |
|
rgvar[i].vt = VT_DISPATCH; |
|
rgvar[i].pdispVal = AccessibleWrap::NativeAccessible(mArray[mCurIndex]); |
|
} |
|
|
|
if (pceltFetched) |
|
*pceltFetched = celt; |
|
|
|
return hr; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleEnumerator::Clone(IEnumVARIANT FAR* FAR* ppenum) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
*ppenum = new AccessibleEnumerator(*this); |
|
if (!*ppenum) |
|
return E_OUTOFMEMORY; |
|
NS_ADDREF(*ppenum); |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleEnumerator::Skip(unsigned long celt) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
uint32_t length = mArray.Length(); |
|
// Check if we can skip the requested number of elements |
|
if (celt > length - mCurIndex) { |
|
mCurIndex = length; |
|
return S_FALSE; |
|
} |
|
mCurIndex += celt; |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
/** |
|
* This method is called when a client wants to know which children of a node |
|
* are selected. Note that this method can only find selected children for |
|
* accessible object which implement SelectAccessible. |
|
* |
|
* The VARIANT return value arguement is expected to either contain a single IAccessible |
|
* or an IEnumVARIANT of IAccessibles. We return the IEnumVARIANT regardless of the number |
|
* of children selected, unless there are none selected in which case we return an empty |
|
* VARIANT. |
|
* |
|
* We get the selected options from the select's accessible object and wrap |
|
* those in an AccessibleEnumerator which we then put in the return VARIANT. |
|
* |
|
* returns a VT_EMPTY VARIANT if: |
|
* - there are no selected children for this object |
|
* - the object is not the type that can have children selected |
|
*/ |
|
STDMETHODIMP |
|
AccessibleWrap::get_accSelection(VARIANT __RPC_FAR *pvarChildren) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pvarChildren) |
|
return E_INVALIDARG; |
|
|
|
VariantInit(pvarChildren); |
|
pvarChildren->vt = VT_EMPTY; |
|
|
|
if (IsDefunct()) |
|
return CO_E_OBJNOTCONNECTED; |
|
|
|
if (IsSelect()) { |
|
AutoTArray<Accessible*, 10> selectedItems; |
|
SelectedItems(&selectedItems); |
|
|
|
// 1) Create and initialize the enumeration |
|
RefPtr<AccessibleEnumerator> pEnum = new AccessibleEnumerator(selectedItems); |
|
pvarChildren->vt = VT_UNKNOWN; // this must be VT_UNKNOWN for an IEnumVARIANT |
|
NS_ADDREF(pvarChildren->punkVal = pEnum); |
|
} |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::get_accDefaultAction( |
|
/* [optional][in] */ VARIANT varChild, |
|
/* [retval][out] */ BSTR __RPC_FAR *pszDefaultAction) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pszDefaultAction) |
|
return E_INVALIDARG; |
|
|
|
*pszDefaultAction = nullptr; |
|
|
|
RefPtr<IAccessible> accessible; |
|
HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); |
|
if (FAILED(hr)) { |
|
return hr; |
|
} |
|
|
|
if (accessible) { |
|
return accessible->get_accDefaultAction(kVarChildIdSelf, pszDefaultAction); |
|
} |
|
|
|
nsAutoString defaultAction; |
|
ActionNameAt(0, defaultAction); |
|
|
|
*pszDefaultAction = ::SysAllocStringLen(defaultAction.get(), |
|
defaultAction.Length()); |
|
return *pszDefaultAction ? S_OK : E_OUTOFMEMORY; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::accSelect( |
|
/* [in] */ long flagsSelect, |
|
/* [optional][in] */ VARIANT varChild) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
RefPtr<IAccessible> accessible; |
|
HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); |
|
if (FAILED(hr)) { |
|
return hr; |
|
} |
|
|
|
if (accessible) { |
|
return accessible->accSelect(flagsSelect, kVarChildIdSelf); |
|
} |
|
|
|
if (flagsSelect & SELFLAG_TAKEFOCUS) { |
|
if (XRE_IsContentProcess()) { |
|
// In this case we might have been invoked while the IPC MessageChannel is |
|
// waiting on a sync reply. We cannot dispatch additional IPC while that |
|
// is happening, so we dispatch TakeFocus from the main thread to |
|
// guarantee that we are outside any IPC. |
|
nsCOMPtr<nsIRunnable> runnable = |
|
mozilla::NewRunnableMethod(this, &Accessible::TakeFocus); |
|
NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); |
|
return S_OK; |
|
} |
|
TakeFocus(); |
|
return S_OK; |
|
} |
|
|
|
if (flagsSelect & SELFLAG_TAKESELECTION) { |
|
TakeSelection(); |
|
return S_OK; |
|
} |
|
|
|
if (flagsSelect & SELFLAG_ADDSELECTION) { |
|
SetSelected(true); |
|
return S_OK; |
|
} |
|
|
|
if (flagsSelect & SELFLAG_REMOVESELECTION) { |
|
SetSelected(false); |
|
return S_OK; |
|
} |
|
|
|
return E_FAIL; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::accLocation( |
|
/* [out] */ long __RPC_FAR *pxLeft, |
|
/* [out] */ long __RPC_FAR *pyTop, |
|
/* [out] */ long __RPC_FAR *pcxWidth, |
|
/* [out] */ long __RPC_FAR *pcyHeight, |
|
/* [optional][in] */ VARIANT varChild) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pxLeft || !pyTop || !pcxWidth || !pcyHeight) |
|
return E_INVALIDARG; |
|
|
|
*pxLeft = 0; |
|
*pyTop = 0; |
|
*pcxWidth = 0; |
|
*pcyHeight = 0; |
|
|
|
RefPtr<IAccessible> accessible; |
|
HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); |
|
if (FAILED(hr)) { |
|
return hr; |
|
} |
|
|
|
if (accessible) { |
|
return accessible->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, |
|
kVarChildIdSelf); |
|
} |
|
|
|
nsIntRect rect = Bounds(); |
|
|
|
*pxLeft = rect.x; |
|
*pyTop = rect.y; |
|
*pcxWidth = rect.width; |
|
*pcyHeight = rect.height; |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::accNavigate( |
|
/* [in] */ long navDir, |
|
/* [optional][in] */ VARIANT varStart, |
|
/* [retval][out] */ VARIANT __RPC_FAR *pvarEndUpAt) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pvarEndUpAt) |
|
return E_INVALIDARG; |
|
|
|
VariantInit(pvarEndUpAt); |
|
|
|
RefPtr<IAccessible> accessible; |
|
HRESULT hr = ResolveChild(varStart, getter_AddRefs(accessible)); |
|
if (FAILED(hr)) { |
|
return hr; |
|
} |
|
|
|
if (accessible) { |
|
return accessible->accNavigate(navDir, kVarChildIdSelf, pvarEndUpAt); |
|
} |
|
|
|
Accessible* navAccessible = nullptr; |
|
Maybe<RelationType> xpRelation; |
|
|
|
#define RELATIONTYPE(geckoType, stringType, atkType, msaaType, ia2Type) \ |
|
case msaaType: \ |
|
xpRelation.emplace(RelationType::geckoType); \ |
|
break; |
|
|
|
switch(navDir) { |
|
case NAVDIR_FIRSTCHILD: |
|
if (IsProxy()) { |
|
if (!Proxy()->MustPruneChildren()) { |
|
navAccessible = WrapperFor(Proxy()->FirstChild()); |
|
} |
|
} else { |
|
if (!nsAccUtils::MustPrune(this)) |
|
navAccessible = FirstChild(); |
|
} |
|
break; |
|
case NAVDIR_LASTCHILD: |
|
if (IsProxy()) { |
|
if (!Proxy()->MustPruneChildren()) { |
|
navAccessible = WrapperFor(Proxy()->LastChild()); |
|
} |
|
} else { |
|
if (!nsAccUtils::MustPrune(this)) |
|
navAccessible = LastChild(); |
|
} |
|
break; |
|
case NAVDIR_NEXT: |
|
navAccessible = IsProxy() |
|
? WrapperFor(Proxy()->NextSibling()) |
|
: NextSibling(); |
|
break; |
|
case NAVDIR_PREVIOUS: |
|
navAccessible = IsProxy() |
|
? WrapperFor(Proxy()->PrevSibling()) |
|
: PrevSibling(); |
|
break; |
|
case NAVDIR_DOWN: |
|
case NAVDIR_LEFT: |
|
case NAVDIR_RIGHT: |
|
case NAVDIR_UP: |
|
return E_NOTIMPL; |
|
|
|
// MSAA relationship extensions to accNavigate |
|
#include "RelationTypeMap.h" |
|
|
|
default: |
|
return E_INVALIDARG; |
|
} |
|
|
|
#undef RELATIONTYPE |
|
|
|
pvarEndUpAt->vt = VT_EMPTY; |
|
|
|
if (xpRelation) { |
|
Relation rel = RelationByType(*xpRelation); |
|
navAccessible = rel.Next(); |
|
} |
|
|
|
if (!navAccessible) |
|
return E_FAIL; |
|
|
|
pvarEndUpAt->pdispVal = NativeAccessible(navAccessible); |
|
pvarEndUpAt->vt = VT_DISPATCH; |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::accHitTest( |
|
/* [in] */ long xLeft, |
|
/* [in] */ long yTop, |
|
/* [retval][out] */ VARIANT __RPC_FAR *pvarChild) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
if (!pvarChild) |
|
return E_INVALIDARG; |
|
|
|
VariantInit(pvarChild); |
|
|
|
if (IsDefunct()) |
|
return CO_E_OBJNOTCONNECTED; |
|
|
|
Accessible* accessible = ChildAtPoint(xLeft, yTop, eDirectChild); |
|
|
|
// if we got a child |
|
if (accessible) { |
|
// if the child is us |
|
if (accessible == this) { |
|
pvarChild->vt = VT_I4; |
|
pvarChild->lVal = CHILDID_SELF; |
|
} else { // its not create an Accessible for it. |
|
pvarChild->vt = VT_DISPATCH; |
|
pvarChild->pdispVal = NativeAccessible(accessible); |
|
} |
|
} else { |
|
// no child at that point |
|
pvarChild->vt = VT_EMPTY; |
|
return S_FALSE; |
|
} |
|
return S_OK; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::accDoDefaultAction( |
|
/* [optional][in] */ VARIANT varChild) |
|
{ |
|
A11Y_TRYBLOCK_BEGIN |
|
|
|
RefPtr<IAccessible> accessible; |
|
HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); |
|
if (FAILED(hr)) { |
|
return hr; |
|
} |
|
|
|
if (accessible) { |
|
return accessible->accDoDefaultAction(kVarChildIdSelf); |
|
} |
|
|
|
return DoAction(0) ? S_OK : E_INVALIDARG; |
|
|
|
A11Y_TRYBLOCK_END |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::put_accName( |
|
/* [optional][in] */ VARIANT varChild, |
|
/* [in] */ BSTR szName) |
|
{ |
|
return E_NOTIMPL; |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::put_accValue( |
|
/* [optional][in] */ VARIANT varChild, |
|
/* [in] */ BSTR szValue) |
|
{ |
|
return E_NOTIMPL; |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
// IDispatch |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::GetTypeInfoCount(UINT *pctinfo) |
|
{ |
|
if (!pctinfo) |
|
return E_INVALIDARG; |
|
|
|
*pctinfo = 1; |
|
return S_OK; |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) |
|
{ |
|
if (!ppTInfo) |
|
return E_INVALIDARG; |
|
|
|
*ppTInfo = nullptr; |
|
|
|
if (iTInfo != 0) |
|
return DISP_E_BADINDEX; |
|
|
|
ITypeInfo * typeInfo = GetTI(lcid); |
|
if (!typeInfo) |
|
return E_FAIL; |
|
|
|
typeInfo->AddRef(); |
|
*ppTInfo = typeInfo; |
|
|
|
return S_OK; |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, |
|
UINT cNames, LCID lcid, DISPID *rgDispId) |
|
{ |
|
ITypeInfo *typeInfo = GetTI(lcid); |
|
if (!typeInfo) |
|
return E_FAIL; |
|
|
|
HRESULT hr = DispGetIDsOfNames(typeInfo, rgszNames, cNames, rgDispId); |
|
return hr; |
|
} |
|
|
|
STDMETHODIMP |
|
AccessibleWrap::Invoke(DISPID dispIdMember, REFIID riid, |
|
LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, |
|
VARIANT *pVarResult, EXCEPINFO *pExcepInfo, |
|
UINT *puArgErr) |
|
{ |
|
ITypeInfo *typeInfo = GetTI(lcid); |
|
if (!typeInfo) |
|
return E_FAIL; |
|
|
|
return typeInfo->Invoke(static_cast<IAccessible*>(this), dispIdMember, |
|
wFlags, pDispParams, pVarResult, pExcepInfo, |
|
puArgErr); |
|
} |
|
|
|
void |
|
AccessibleWrap::GetNativeInterface(void** aOutAccessible) |
|
{ |
|
*aOutAccessible = static_cast<IAccessible*>(this); |
|
NS_ADDREF_THIS(); |
|
} |
|
|
|
void |
|
AccessibleWrap::SetID(uint32_t aID) |
|
{ |
|
MOZ_ASSERT(XRE_IsParentProcess() && IsProxy()); |
|
mID = aID; |
|
} |
|
|
|
void |
|
AccessibleWrap::FireWinEvent(Accessible* aTarget, uint32_t aEventType) |
|
{ |
|
MOZ_ASSERT(XRE_IsParentProcess()); |
|
static_assert(sizeof(gWinEventMap)/sizeof(gWinEventMap[0]) == nsIAccessibleEvent::EVENT_LAST_ENTRY, |
|
"MSAA event map skewed"); |
|
|
|
NS_ASSERTION(aEventType > 0 && aEventType < ArrayLength(gWinEventMap), "invalid event type"); |
|
|
|
uint32_t winEvent = gWinEventMap[aEventType]; |
|
if (!winEvent) |
|
return; |
|
|
|
int32_t childID = GetChildIDFor(aTarget); |
|
if (!childID) |
|
return; // Can't fire an event without a child ID |
|
|
|
HWND hwnd = GetHWNDFor(aTarget); |
|
if (!hwnd) { |
|
return; |
|
} |
|
|
|
// Fire MSAA event for client area window. |
|
::NotifyWinEvent(winEvent, hwnd, OBJID_CLIENT, childID); |
|
|
|
// JAWS announces collapsed combobox navigation based on focus events. |
|
if (aEventType == nsIAccessibleEvent::EVENT_SELECTION && |
|
Compatibility::IsJAWS()) { |
|
roles::Role role = aTarget->IsProxy() ? aTarget->Proxy()->Role() : |
|
aTarget->Role(); |
|
if (role == roles::COMBOBOX_OPTION) { |
|
::NotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, childID); |
|
} |
|
} |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
// Accessible |
|
|
|
nsresult |
|
AccessibleWrap::HandleAccEvent(AccEvent* aEvent) |
|
{ |
|
nsresult rv = Accessible::HandleAccEvent(aEvent); |
|
NS_ENSURE_SUCCESS(rv, rv); |
|
|
|
if (IPCAccessibilityActive()) { |
|
return NS_OK; |
|
} |
|
|
|
uint32_t eventType = aEvent->GetEventType(); |
|
|
|
// Means we're not active. |
|
NS_ENSURE_TRUE(!IsDefunct(), NS_ERROR_FAILURE); |
|
|
|
Accessible* accessible = aEvent->GetAccessible(); |
|
if (!accessible) |
|
return NS_OK; |
|
|
|
if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED || |
|
eventType == nsIAccessibleEvent::EVENT_FOCUS) { |
|
UpdateSystemCaretFor(accessible); |
|
} |
|
|
|
FireWinEvent(accessible, eventType); |
|
|
|
return NS_OK; |
|
} |
|
|
|
DocProxyAccessibleWrap* |
|
AccessibleWrap::DocProxyWrapper() const |
|
{ |
|
MOZ_ASSERT(IsProxy()); |
|
|
|
ProxyAccessible* proxy = Proxy(); |
|
if (!proxy) { |
|
return nullptr; |
|
} |
|
|
|
AccessibleWrap* acc = WrapperFor(proxy->Document()); |
|
MOZ_ASSERT(acc->IsDoc()); |
|
|
|
return static_cast<DocProxyAccessibleWrap*>(acc); |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
// AccessibleWrap |
|
|
|
//------- Helper methods --------- |
|
|
|
int32_t |
|
AccessibleWrap::GetChildIDFor(Accessible* aAccessible) |
|
{ |
|
// A child ID of the window is required, when we use NotifyWinEvent, |
|
// so that the 3rd party application can call back and get the IAccessible |
|
// the event occurred on. |
|
|
|
if (!aAccessible) { |
|
return 0; |
|
} |
|
|
|
// Chrome should use mID which has been generated by the content process. |
|
if (aAccessible->IsProxy()) { |
|
const uint32_t id = static_cast<AccessibleWrap*>(aAccessible)->mID; |
|
MOZ_ASSERT(id != kNoID); |
|
return id; |
|
} |
|
|
|
if (!aAccessible->Document()) |
|
return 0; |
|
|
|
uint32_t* id = & static_cast<AccessibleWrap*>(aAccessible)->mID; |
|
if (*id != kNoID) |
|
return *id; |
|
|
|
*id = sIDGen.GetID(); |
|
|
|
MOZ_ASSERT(!aAccessible->IsProxy()); |
|
DocAccessibleWrap* doc = |
|
static_cast<DocAccessibleWrap*>(aAccessible->Document()); |
|
doc->AddID(*id, static_cast<AccessibleWrap*>(aAccessible)); |
|
|
|
return *id; |
|
} |
|
|
|
HWND |
|
AccessibleWrap::GetHWNDFor(Accessible* aAccessible) |
|
{ |
|
if (!aAccessible) { |
|
return nullptr; |
|
} |
|
|
|
if (XRE_IsContentProcess()) { |
|
DocAccessible* doc = aAccessible->Document(); |
|
if (!doc) { |
|
return nullptr; |
|
} |
|
|
|
DocAccessibleChild* ipcDoc = doc->IPCDoc(); |
|
if (!ipcDoc) { |
|
return nullptr; |
|
} |
|
|
|
auto tab = static_cast<dom::TabChild*>(ipcDoc->Manager()); |
|
MOZ_ASSERT(tab); |
|
return reinterpret_cast<HWND>(tab->GetNativeWindowHandle()); |
|
} |
|
|
|
// Accessibles in child processes are said to have the HWND of the window |
|
// their tab is within. Popups are always in the parent process, and so |
|
// never proxied, which means this is basically correct. |
|
if (aAccessible->IsProxy()) { |
|
ProxyAccessible* proxy = aAccessible->Proxy(); |
|
if (!proxy) { |
|
return nullptr; |
|
} |
|
|
|
Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser(); |
|
NS_ASSERTION(outerDoc, "no outer doc for accessible remote tab!"); |
|
if (!outerDoc) { |
|
return nullptr; |
|
} |
|
|
|
return GetHWNDFor(outerDoc); |
|
} |
|
|
|
DocAccessible* document = aAccessible->Document(); |
|
if(!document) |
|
return nullptr; |
|
|
|
// Popup lives in own windows, use its HWND until the popup window is |
|
// hidden to make old JAWS versions work with collapsed comboboxes (see |
|
// discussion in bug 379678). |
|
nsIFrame* frame = aAccessible->GetFrame(); |
|
if (frame) { |
|
nsIWidget* widget = frame->GetNearestWidget(); |
|
if (widget && widget->IsVisible()) { |
|
nsIPresShell* shell = document->PresShell(); |
|
nsViewManager* vm = shell->GetViewManager(); |
|
if (vm) { |
|
nsCOMPtr<nsIWidget> rootWidget; |
|
vm->GetRootWidget(getter_AddRefs(rootWidget)); |
|
// Make sure the accessible belongs to popup. If not then use |
|
// document HWND (which might be different from root widget in the |
|
// case of window emulation). |
|
if (rootWidget != widget) |
|
return static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW)); |
|
} |
|
} |
|
} |
|
|
|
return static_cast<HWND>(document->GetNativeWindow()); |
|
} |
|
|
|
IDispatch* |
|
AccessibleWrap::NativeAccessible(Accessible* aAccessible) |
|
{ |
|
if (!aAccessible) { |
|
NS_WARNING("Not passing in an aAccessible"); |
|
return nullptr; |
|
} |
|
|
|
IAccessible* msaaAccessible = nullptr; |
|
aAccessible->GetNativeInterface(reinterpret_cast<void**>(&msaaAccessible)); |
|
return static_cast<IDispatch*>(msaaAccessible); |
|
} |
|
|
|
static Accessible* |
|
GetAccessibleInSubtree(DocAccessible* aDoc, uint32_t aID) |
|
{ |
|
Accessible* child = static_cast<DocAccessibleWrap*>(aDoc)->GetAccessibleByID(aID); |
|
if (child) |
|
return child; |
|
|
|
uint32_t childDocCount = aDoc->ChildDocumentCount(); |
|
for (uint32_t i = 0; i < childDocCount; i++) { |
|
child = GetAccessibleInSubtree(aDoc->GetChildDocumentAt(i), aID); |
|
if (child) |
|
return child; |
|
} |
|
|
|
return nullptr; |
|
} |
|
|
|
static already_AddRefed<IDispatch> |
|
GetProxiedAccessibleInSubtree(const DocAccessibleParent* aDoc, |
|
const VARIANT& aVarChild) |
|
{ |
|
auto wrapper = static_cast<DocProxyAccessibleWrap*>(WrapperFor(aDoc)); |
|
RefPtr<IAccessible> comProxy; |
|
int32_t wrapperChildId = AccessibleWrap::GetChildIDFor(wrapper); |
|
if (wrapperChildId == aVarChild.lVal) { |
|
wrapper->GetNativeInterface(getter_AddRefs(comProxy)); |
|
return comProxy.forget(); |
|
} |
|
|
|
MOZ_ASSERT(aDoc->IsTopLevel()); |
|
if (!aDoc->IsTopLevel()) { |
|
return nullptr; |
|
} |
|
|
|
wrapper->GetNativeInterface(getter_AddRefs(comProxy)); |
|
MOZ_ASSERT(comProxy); |
|
if (!comProxy) { |
|
return nullptr; |
|
} |
|
|
|
RefPtr<IDispatch> disp; |
|
if (FAILED(comProxy->get_accChild(aVarChild, getter_AddRefs(disp)))) { |
|
return nullptr; |
|
} |
|
|
|
return disp.forget(); |
|
} |
|
|
|
already_AddRefed<IAccessible> |
|
AccessibleWrap::GetIAccessibleFor(const VARIANT& aVarChild, bool* aIsDefunct) |
|
{ |
|
if (aVarChild.vt != VT_I4) |
|
return nullptr; |
|
|
|
VARIANT varChild = aVarChild; |
|
|
|
MOZ_ASSERT(aIsDefunct); |
|
*aIsDefunct = false; |
|
|
|
RefPtr<IAccessible> result; |
|
|
|
if (varChild.lVal == CHILDID_SELF) { |
|
*aIsDefunct = IsDefunct(); |
|
if (*aIsDefunct) { |
|
return nullptr; |
|
} |
|
GetNativeInterface(getter_AddRefs(result)); |
|
if (result) { |
|
return result.forget(); |
|
} |
|
// If we're not a proxy, there's nothing more we can do to attempt to |
|
// resolve the IAccessible, so we just fail. |
|
if (!IsProxy()) { |
|
return nullptr; |
|
} |
|
// Otherwise, since we're a proxy and we have a null native interface, this |
|
// indicates that we need to obtain a COM proxy. To do this, we'll replace |
|
// CHILDID_SELF with our real MSAA ID and continue the search from there. |
|
varChild.lVal = GetExistingID(); |
|
} |
|
|
|
if (IsProxy() ? Proxy()->MustPruneChildren() : nsAccUtils::MustPrune(this)) { |
|
return nullptr; |
|
} |
|
|
|
// If the MSAA ID is not a chrome id then we already know that we won't |
|
// find it here and should look remotely instead. |
|
if (XRE_IsParentProcess() && !sIDGen.IsChromeID(varChild.lVal)) { |
|
return GetRemoteIAccessibleFor(varChild); |
|
} |
|
MOZ_ASSERT(XRE_IsParentProcess() || |
|
sIDGen.IsIDForThisContentProcess(varChild.lVal)); |
|
|
|
if (varChild.lVal > 0) { |
|
// Gecko child indices are 0-based in contrast to indices used in MSAA. |
|
MOZ_ASSERT(!IsProxy()); |
|
Accessible* xpAcc = GetChildAt(varChild.lVal - 1); |
|
if (!xpAcc) { |
|
return nullptr; |
|
} |
|
*aIsDefunct = xpAcc->IsDefunct(); |
|
static_cast<AccessibleWrap*>(xpAcc)->GetNativeInterface(getter_AddRefs(result)); |
|
return result.forget(); |
|
} |
|
|
|
// If lVal negative then it is treated as child ID and we should look for |
|
// accessible through whole accessible subtree including subdocuments. |
|
// Otherwise we treat lVal as index in parent. |
|
// First handle the case that both this accessible and the id'd one are in |
|
// this process. |
|
if (!IsProxy()) { |
|
DocAccessible* document = Document(); |
|
Accessible* child = |
|
GetAccessibleInSubtree(document, static_cast<uint32_t>(varChild.lVal)); |
|
|
|
// If it is a document then just return an accessible. |
|
if (child && IsDoc()) { |
|
*aIsDefunct = child->IsDefunct(); |
|
static_cast<AccessibleWrap*>(child)->GetNativeInterface(getter_AddRefs(result)); |
|
return result.forget(); |
|
} |
|
|
|
// Otherwise check whether the accessible is a child (this path works for |
|
// ARIA documents and popups). |
|
Accessible* parent = child; |
|
while (parent && parent != document) { |
|
if (parent == this) { |
|
*aIsDefunct = child->IsDefunct(); |
|
static_cast<AccessibleWrap*>(child)->GetNativeInterface(getter_AddRefs(result)); |
|
return result.forget(); |
|
} |
|
|
|
parent = parent->Parent(); |
|
} |
|
} |
|
|
|
// Now see about the case that both this accessible and the target one are |
|
// proxied. |
|
if (IsProxy()) { |
|
DocAccessibleParent* proxyDoc = Proxy()->Document(); |
|
RefPtr<IDispatch> disp = GetProxiedAccessibleInSubtree(proxyDoc, varChild); |
|
if (!disp) { |
|
return nullptr; |
|
} |
|
|
|
MOZ_ASSERT(mscom::IsProxy(disp)); |
|
DebugOnly<HRESULT> hr = disp->QueryInterface(IID_IAccessible, |
|
getter_AddRefs(result)); |
|
MOZ_ASSERT(SUCCEEDED(hr)); |
|
return result.forget(); |
|
} |
|
|
|
return nullptr; |
|
} |
|
|
|
already_AddRefed<IAccessible> |
|
AccessibleWrap::GetRemoteIAccessibleFor(const VARIANT& aVarChild) |
|
{ |
|
DocAccessibleParent* proxyDoc = nullptr; |
|
DocAccessible* doc = Document(); |
|
const nsTArray<DocAccessibleParent*>* remoteDocs = |
|
DocManager::TopLevelRemoteDocs(); |
|
if (!remoteDocs) { |
|
return nullptr; |
|
} |
|
|
|
RefPtr<IAccessible> result; |
|
|
|
size_t docCount = remoteDocs->Length(); |
|
for (size_t i = 0; i < docCount; i++) { |
|
DocAccessibleParent* remoteDoc = remoteDocs->ElementAt(i); |
|
|
|
uint32_t remoteDocMsaaId = WrapperFor(remoteDoc)->GetExistingID(); |
|
if (!sIDGen.IsSameContentProcessFor(aVarChild.lVal, remoteDocMsaaId)) { |
|
continue; |
|
} |
|
|
|
Accessible* outerDoc = remoteDoc->OuterDocOfRemoteBrowser(); |
|
if (!outerDoc) { |
|
continue; |
|
} |
|
|
|
if (outerDoc->Document() != doc) { |
|
continue; |
|
} |
|
|
|
RefPtr<IDispatch> disp = |
|
GetProxiedAccessibleInSubtree(remoteDoc, aVarChild); |
|
if (!disp) { |
|
continue; |
|
} |
|
|
|
DebugOnly<HRESULT> hr = disp->QueryInterface(IID_IAccessible, |
|
getter_AddRefs(result)); |
|
MOZ_ASSERT(SUCCEEDED(hr)); |
|
return result.forget(); |
|
} |
|
|
|
return nullptr; |
|
} |
|
|
|
void |
|
AccessibleWrap::UpdateSystemCaretFor(Accessible* aAccessible) |
|
{ |
|
// Move the system caret so that Windows Tablet Edition and tradional ATs with |
|
// off-screen model can follow the caret |
|
::DestroyCaret(); |
|
|
|
HyperTextAccessible* text = aAccessible->AsHyperText(); |
|
if (!text) |
|
return; |
|
|
|
nsIWidget* widget = nullptr; |
|
LayoutDeviceIntRect caretRect = text->GetCaretRect(&widget); |
|
HWND caretWnd; |
|
if (caretRect.IsEmpty() || !(caretWnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW))) { |
|
return; |
|
} |
|
|
|
// Create invisible bitmap for caret, otherwise its appearance interferes |
|
// with Gecko caret |
|
HBITMAP caretBitMap = CreateBitmap(1, caretRect.height, 1, 1, nullptr); |
|
if (::CreateCaret(caretWnd, caretBitMap, 1, caretRect.height)) { // Also destroys the last caret |
|
::ShowCaret(caretWnd); |
|
RECT windowRect; |
|
::GetWindowRect(caretWnd, &windowRect); |
|
::SetCaretPos(caretRect.x - windowRect.left, caretRect.y - windowRect.top); |
|
::DeleteObject(caretBitMap); |
|
} |
|
} |
|
|
|
ITypeInfo* |
|
AccessibleWrap::GetTI(LCID lcid) |
|
{ |
|
if (gTypeInfo) |
|
return gTypeInfo; |
|
|
|
ITypeLib *typeLib = nullptr; |
|
HRESULT hr = LoadRegTypeLib(LIBID_Accessibility, 1, 0, lcid, &typeLib); |
|
if (FAILED(hr)) |
|
return nullptr; |
|
|
|
hr = typeLib->GetTypeInfoOfGuid(IID_IAccessible, &gTypeInfo); |
|
typeLib->Release(); |
|
|
|
if (FAILED(hr)) |
|
return nullptr; |
|
|
|
return gTypeInfo; |
|
} |
|
|
|
/* static */ |
|
uint32_t |
|
AccessibleWrap::GetContentProcessIdFor(dom::ContentParentId aIPCContentId) |
|
{ |
|
return sIDGen.GetContentProcessIDFor(aIPCContentId); |
|
} |
|
|
|
/* static */ |
|
void |
|
AccessibleWrap::ReleaseContentProcessIdFor(dom::ContentParentId aIPCContentId) |
|
{ |
|
sIDGen.ReleaseContentProcessIDFor(aIPCContentId); |
|
}
|
|
|