1372 lines
38 KiB
C++
1372 lines
38 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 et sw=2 tw=80: */
|
|
/* 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 "EnumVariant.h"
|
|
#include "ia2AccessibleApplication.h"
|
|
#include "ia2AccessibleHypertext.h"
|
|
#include "ia2AccessibleImage.h"
|
|
#include "ia2AccessibleTable.h"
|
|
#include "ia2AccessibleTableCell.h"
|
|
#include "LocalAccessible-inl.h"
|
|
#include "mozilla/a11y/AccessibleWrap.h"
|
|
#include "mozilla/a11y/Compatibility.h"
|
|
#include "mozilla/a11y/DocAccessibleParent.h"
|
|
#include "MsaaAccessible.h"
|
|
#include "MsaaDocAccessible.h"
|
|
#include "MsaaRootAccessible.h"
|
|
#include "MsaaXULMenuAccessible.h"
|
|
#include "nsEventMap.h"
|
|
#include "nsViewManager.h"
|
|
#include "nsWinUtils.h"
|
|
#include "Relation.h"
|
|
#include "sdnAccessible.h"
|
|
#include "HyperTextAccessible-inl.h"
|
|
#include "ServiceProvider.h"
|
|
#include "ARIAMap.h"
|
|
#include "mozilla/PresShell.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::a11y;
|
|
|
|
static const VARIANT kVarChildIdSelf = {{{VT_I4}}};
|
|
|
|
// Used internally to safely get an MsaaAccessible from a COM pointer provided
|
|
// to us by a client.
|
|
static const GUID IID_MsaaAccessible = {
|
|
/* a94aded3-1a9c-4afc-a32c-d6b5c010046b */
|
|
0xa94aded3,
|
|
0x1a9c,
|
|
0x4afc,
|
|
{0xa3, 0x2c, 0xd6, 0xb5, 0xc0, 0x10, 0x04, 0x6b}};
|
|
|
|
MOZ_RUNINIT MsaaIdGenerator MsaaAccessible::sIDGen;
|
|
ITypeInfo* MsaaAccessible::gTypeInfo = nullptr;
|
|
|
|
/* static */
|
|
MsaaAccessible* MsaaAccessible::Create(Accessible* aAcc) {
|
|
// This should only ever be called in the parent process.
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
// The order of some of these is important! For example, when isRoot is true,
|
|
// IsDoc will also be true, so we must check IsRoot first. IsTable/Cell and
|
|
// IsHyperText are a similar case.
|
|
if (aAcc->IsRoot()) {
|
|
MOZ_ASSERT(aAcc->IsLocal());
|
|
return new MsaaRootAccessible(aAcc);
|
|
}
|
|
if (aAcc->IsDoc()) {
|
|
return new MsaaDocAccessible(aAcc);
|
|
}
|
|
if (aAcc->IsTable()) {
|
|
return new ia2AccessibleTable(aAcc);
|
|
}
|
|
if (aAcc->IsTableCell()) {
|
|
return new ia2AccessibleTableCell(aAcc);
|
|
}
|
|
if (aAcc->IsApplication()) {
|
|
MOZ_ASSERT(aAcc->IsLocal());
|
|
return new ia2AccessibleApplication(aAcc);
|
|
}
|
|
if (aAcc->IsImage()) {
|
|
return new ia2AccessibleImage(aAcc);
|
|
}
|
|
if (LocalAccessible* localAcc = aAcc->AsLocal()) {
|
|
if (localAcc->GetContent() &&
|
|
localAcc->GetContent()->IsXULElement(nsGkAtoms::menuitem)) {
|
|
return new MsaaXULMenuitemAccessible(aAcc);
|
|
}
|
|
}
|
|
if (aAcc->IsHyperText()) {
|
|
return new ia2AccessibleHypertext(aAcc);
|
|
}
|
|
return new MsaaAccessible(aAcc);
|
|
}
|
|
|
|
MsaaAccessible::MsaaAccessible(Accessible* aAcc) : mAcc(aAcc), mID(kNoID) {}
|
|
|
|
MsaaAccessible::~MsaaAccessible() {
|
|
MOZ_ASSERT(!mAcc, "MsaaShutdown wasn't called!");
|
|
if (mID != kNoID) {
|
|
sIDGen.ReleaseID(WrapNotNull(this));
|
|
}
|
|
}
|
|
|
|
void MsaaAccessible::MsaaShutdown() {
|
|
// Accessibles can be shut down twice in some cases. If that happens,
|
|
// MsaaShutdown will also be called twice because AccessibleWrap holds
|
|
// the reference until its destructor is called; see the comments in
|
|
// AccessibleWrap::Shutdown.
|
|
if (!mAcc) {
|
|
return;
|
|
}
|
|
|
|
if (mID != kNoID) {
|
|
auto doc = MsaaDocAccessible::GetFromOwned(mAcc);
|
|
MOZ_ASSERT(doc);
|
|
doc->RemoveID(mID);
|
|
}
|
|
|
|
mAcc = nullptr;
|
|
}
|
|
|
|
int32_t MsaaAccessible::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;
|
|
}
|
|
|
|
auto doc = MsaaDocAccessible::GetFromOwned(aAccessible);
|
|
if (!doc) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t* id = &MsaaAccessible::GetFrom(aAccessible)->mID;
|
|
if (*id != kNoID) return *id;
|
|
|
|
*id = sIDGen.GetID();
|
|
doc->AddID(*id, aAccessible);
|
|
|
|
return *id;
|
|
}
|
|
|
|
HWND MsaaAccessible::GetHWNDFor(Accessible* aAccessible) {
|
|
if (!aAccessible) {
|
|
return nullptr;
|
|
}
|
|
|
|
LocalAccessible* localAcc = aAccessible->AsLocal();
|
|
if (!localAcc) {
|
|
RemoteAccessible* proxy = aAccessible->AsRemote();
|
|
if (!proxy) {
|
|
return nullptr;
|
|
}
|
|
|
|
// If window emulation is enabled, retrieve the emulated window from the
|
|
// containing document document proxy.
|
|
if (nsWinUtils::IsWindowEmulationStarted()) {
|
|
DocAccessibleParent* doc = proxy->Document();
|
|
HWND hWnd = doc->GetEmulatedWindowHandle();
|
|
if (hWnd) {
|
|
return hWnd;
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
LocalAccessible* outerDoc = proxy->OuterDocOfRemoteBrowser();
|
|
if (!outerDoc) {
|
|
// In some cases, the outer document accessible may be unattached from its
|
|
// document at this point, if it is scheduled for removal. Do not assert
|
|
// in such case. An example: putting aria-hidden="true" on HTML:iframe
|
|
// element will destroy iframe's document asynchroniously, but
|
|
// the document may be a target of selection events until then, and thus
|
|
// it may attempt to deliever these events to MSAA clients.
|
|
return nullptr;
|
|
}
|
|
|
|
return GetHWNDFor(outerDoc);
|
|
}
|
|
|
|
DocAccessible* document = localAcc->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 = localAcc->GetFrame();
|
|
if (frame) {
|
|
nsIWidget* widget = frame->GetNearestWidget();
|
|
if (widget && widget->IsVisible()) {
|
|
if (nsViewManager* vm = document->PresShellPtr()->GetViewManager()) {
|
|
nsCOMPtr<nsIWidget> rootWidget = vm->GetRootWidget();
|
|
// 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());
|
|
}
|
|
|
|
void MsaaAccessible::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");
|
|
|
|
if (aEventType == 0 || aEventType >= std::size(gWinEventMap)) {
|
|
MOZ_ASSERT_UNREACHABLE("invalid event type");
|
|
return;
|
|
}
|
|
|
|
uint32_t winEvent = gWinEventMap[aEventType];
|
|
if (!winEvent) return;
|
|
|
|
int32_t childID = MsaaAccessible::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);
|
|
}
|
|
|
|
AccessibleWrap* MsaaAccessible::LocalAcc() {
|
|
if (!mAcc || mAcc->IsRemote()) {
|
|
return nullptr;
|
|
}
|
|
auto acc = static_cast<AccessibleWrap*>(mAcc);
|
|
MOZ_ASSERT(!acc || !acc->IsDefunct(),
|
|
"mAcc defunct but MsaaShutdown wasn't called");
|
|
return acc;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
MsaaAccessible::ResolveChild(const VARIANT& aVarChild,
|
|
IAccessible** aOutInterface) {
|
|
MOZ_ASSERT(aOutInterface);
|
|
*aOutInterface = nullptr;
|
|
|
|
if (aVarChild.vt != VT_I4) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (!mAcc) {
|
|
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;
|
|
}
|
|
|
|
static Accessible* GetAccessibleInSubtree(DocAccessible* aDoc, uint32_t aID) {
|
|
Accessible* child = MsaaDocAccessible::GetFrom(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 Accessible* GetAccessibleInSubtree(DocAccessibleParent* aDoc,
|
|
uint32_t aID) {
|
|
Accessible* child = MsaaDocAccessible::GetFrom(aDoc)->GetAccessibleByID(aID);
|
|
if (child) {
|
|
return child;
|
|
}
|
|
|
|
size_t childDocCount = aDoc->ChildDocCount();
|
|
for (size_t i = 0; i < childDocCount; i++) {
|
|
child = GetAccessibleInSubtree(aDoc->ChildDocAt(i), aID);
|
|
if (child) {
|
|
return child;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static bool IsInclusiveDescendantOf(DocAccessible* aAncestor,
|
|
DocAccessible* aDescendant) {
|
|
for (DocAccessible* doc = aDescendant; doc; doc = doc->ParentDocument()) {
|
|
if (doc == aAncestor) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
already_AddRefed<IAccessible> MsaaAccessible::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 (!mAcc) {
|
|
*aIsDefunct = true;
|
|
return nullptr;
|
|
}
|
|
|
|
if (varChild.lVal == CHILDID_SELF) {
|
|
result = this;
|
|
return result.forget();
|
|
}
|
|
|
|
if (varChild.ulVal != GetExistingID() && nsAccUtils::MustPrune(mAcc)) {
|
|
// This accessible should have no subtree in platform, return null for its
|
|
// children.
|
|
return nullptr;
|
|
}
|
|
|
|
if (varChild.lVal > 0) {
|
|
// Gecko child indices are 0-based in contrast to indices used in MSAA.
|
|
Accessible* xpAcc = mAcc->ChildAt(varChild.lVal - 1);
|
|
if (!xpAcc) {
|
|
return nullptr;
|
|
}
|
|
MOZ_ASSERT(xpAcc->IsRemote() || !xpAcc->AsLocal()->IsDefunct(),
|
|
"Shouldn't get a defunct child");
|
|
result = MsaaAccessible::GetFrom(xpAcc);
|
|
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.
|
|
Accessible* doc = nullptr;
|
|
Accessible* child = nullptr;
|
|
auto id = static_cast<uint32_t>(varChild.lVal);
|
|
if (LocalAccessible* localAcc = mAcc->AsLocal()) {
|
|
DocAccessible* localDoc = localAcc->Document();
|
|
doc = localDoc;
|
|
child = GetAccessibleInSubtree(localDoc, id);
|
|
if (!child) {
|
|
// Search remote documents which are descendants of this local document.
|
|
const auto remoteDocs = DocManager::TopLevelRemoteDocs();
|
|
if (!remoteDocs) {
|
|
return nullptr;
|
|
}
|
|
for (DocAccessibleParent* remoteDoc : *remoteDocs) {
|
|
LocalAccessible* outerDoc = remoteDoc->OuterDocOfRemoteBrowser();
|
|
if (!outerDoc ||
|
|
!IsInclusiveDescendantOf(localDoc, outerDoc->Document())) {
|
|
continue;
|
|
}
|
|
child = GetAccessibleInSubtree(remoteDoc, id);
|
|
if (child) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
DocAccessibleParent* remoteDoc = mAcc->AsRemote()->Document();
|
|
doc = remoteDoc;
|
|
child = GetAccessibleInSubtree(remoteDoc, id);
|
|
}
|
|
if (!child) {
|
|
return nullptr;
|
|
}
|
|
|
|
MOZ_ASSERT(child->IsRemote() || !child->AsLocal()->IsDefunct(),
|
|
"Shouldn't get a defunct child");
|
|
// If this method is being called on the document we searched, we can just
|
|
// return child.
|
|
if (mAcc == doc) {
|
|
result = MsaaAccessible::GetFrom(child);
|
|
return result.forget();
|
|
}
|
|
|
|
// Otherwise, this method was called on a descendant, so we searched an
|
|
// ancestor. We must check whether child is really a descendant. This is used
|
|
// for ARIA documents and popups.
|
|
Accessible* parent = child;
|
|
while (parent && parent != doc) {
|
|
if (parent == mAcc) {
|
|
result = MsaaAccessible::GetFrom(child);
|
|
return result.forget();
|
|
}
|
|
|
|
parent = parent->Parent();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
IDispatch* MsaaAccessible::NativeAccessible(Accessible* aAccessible) {
|
|
if (!aAccessible) {
|
|
NS_WARNING("Not passing in an aAccessible");
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<IDispatch> disp;
|
|
disp = MsaaAccessible::GetFrom(aAccessible);
|
|
IDispatch* rawDisp;
|
|
disp.forget(&rawDisp);
|
|
return rawDisp;
|
|
}
|
|
|
|
ITypeInfo* MsaaAccessible::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 */
|
|
MsaaAccessible* MsaaAccessible::GetFrom(Accessible* aAcc) {
|
|
if (!aAcc) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (RemoteAccessible* remoteAcc = aAcc->AsRemote()) {
|
|
return reinterpret_cast<MsaaAccessible*>(remoteAcc->GetWrapper());
|
|
}
|
|
return static_cast<AccessibleWrap*>(aAcc)->GetMsaa();
|
|
}
|
|
|
|
/* static */
|
|
Accessible* MsaaAccessible::GetAccessibleFrom(IUnknown* aUnknown) {
|
|
RefPtr<MsaaAccessible> msaa;
|
|
aUnknown->QueryInterface(IID_MsaaAccessible, getter_AddRefs(msaa));
|
|
if (!msaa) {
|
|
return nullptr;
|
|
}
|
|
return msaa->Acc();
|
|
}
|
|
|
|
// IUnknown methods
|
|
STDMETHODIMP
|
|
MsaaAccessible::QueryInterface(REFIID iid, void** ppv) {
|
|
if (!ppv) return E_INVALIDARG;
|
|
|
|
*ppv = nullptr;
|
|
|
|
if (IID_IClientSecurity == iid) {
|
|
// Some code might QI(IID_IClientSecurity) to detect whether or not we are
|
|
// a proxy. Right now that can potentially happen off the main thread, so we
|
|
// look for this condition immediately so that we don't trigger other code
|
|
// that might not be thread-safe.
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
if (NS_WARN_IF(!NS_IsMainThread())) {
|
|
// Bug 1896816: JAWS sometimes traverses into Gecko UI from a file dialog
|
|
// thread. It shouldn't do that, but let's fail gracefully instead of
|
|
// crashing.
|
|
return RPC_E_WRONG_THREAD;
|
|
}
|
|
|
|
// These interfaces are always available. We can support querying to them
|
|
// even if the Accessible is dead.
|
|
if (IID_IUnknown == iid) {
|
|
*ppv = static_cast<IAccessible*>(this);
|
|
} else if (IID_MsaaAccessible == iid) {
|
|
*ppv = static_cast<MsaaAccessible*>(this);
|
|
} else if (IID_IDispatch == iid || IID_IAccessible == iid) {
|
|
*ppv = static_cast<IAccessible*>(this);
|
|
} else if (IID_IServiceProvider == iid) {
|
|
*ppv = new ServiceProvider(this);
|
|
} else {
|
|
HRESULT hr = ia2Accessible::QueryInterface(iid, ppv);
|
|
if (SUCCEEDED(hr)) {
|
|
return hr;
|
|
}
|
|
if (Compatibility::IsUiaEnabled()) {
|
|
hr = uiaRawElmProvider::QueryInterface(iid, ppv);
|
|
if (SUCCEEDED(hr)) {
|
|
return hr;
|
|
}
|
|
}
|
|
}
|
|
if (*ppv) {
|
|
(reinterpret_cast<IUnknown*>(*ppv))->AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
// For interfaces below this point, we have to query the Accessible to
|
|
// determine if they are available.
|
|
if (!mAcc) {
|
|
// mscom::Interceptor (and maybe other callers) expects either S_OK or
|
|
// E_NOINTERFACE, so don't return CO_E_OBJNOTCONNECTED like we normally
|
|
// would for a dead object.
|
|
return E_NOINTERFACE;
|
|
}
|
|
AccessibleWrap* localAcc = LocalAcc();
|
|
if (IID_IEnumVARIANT == iid) {
|
|
// We don't support this interface for leaf elements.
|
|
if (!mAcc->HasChildren() || nsAccUtils::MustPrune(mAcc)) {
|
|
return E_NOINTERFACE;
|
|
}
|
|
*ppv = static_cast<IEnumVARIANT*>(new ChildrenEnumVariant(this));
|
|
} else if (IID_ISimpleDOMNode == iid) {
|
|
if (mAcc->IsDoc() || (localAcc && !localAcc->HasOwnContent())) {
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
*ppv = static_cast<ISimpleDOMNode*>(new sdnAccessible(WrapNotNull(this)));
|
|
}
|
|
|
|
if (!*ppv && localAcc) {
|
|
HRESULT hr = ia2AccessibleComponent::QueryInterface(iid, ppv);
|
|
if (SUCCEEDED(hr)) return hr;
|
|
}
|
|
|
|
if (!*ppv) {
|
|
HRESULT hr = ia2AccessibleHyperlink::QueryInterface(iid, ppv);
|
|
if (SUCCEEDED(hr)) return hr;
|
|
}
|
|
|
|
if (!*ppv) {
|
|
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;
|
|
}
|
|
|
|
// IAccessible methods
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accParent(IDispatch __RPC_FAR* __RPC_FAR* ppdispParent) {
|
|
if (!ppdispParent) return E_INVALIDARG;
|
|
|
|
*ppdispParent = nullptr;
|
|
|
|
if (!mAcc) {
|
|
return CO_E_OBJNOTCONNECTED;
|
|
}
|
|
|
|
Accessible* xpParentAcc = mAcc->Parent();
|
|
if (!xpParentAcc) return S_FALSE;
|
|
|
|
*ppdispParent = NativeAccessible(xpParentAcc);
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accChildCount(long __RPC_FAR* pcountChildren) {
|
|
if (!pcountChildren) return E_INVALIDARG;
|
|
|
|
*pcountChildren = 0;
|
|
|
|
if (!mAcc) return CO_E_OBJNOTCONNECTED;
|
|
|
|
if ((Compatibility::A11ySuppressionReasons() &
|
|
SuppressionReasons::Clipboard) &&
|
|
mAcc->IsDoc()) {
|
|
// Bug 1798098: Windows Suggested Actions (introduced in Windows 11 22H2)
|
|
// might walk the entire a11y tree using UIA whenever anything is copied to
|
|
// the clipboard. This causes an unacceptable hang. We prevent this tree
|
|
// walk by returning a 0 child count for documents, from which Windows might
|
|
// walk.
|
|
return S_OK;
|
|
}
|
|
|
|
if (nsAccUtils::MustPrune(mAcc)) {
|
|
return S_OK;
|
|
}
|
|
|
|
*pcountChildren = mAcc->ChildCount();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accChild(
|
|
/* [in] */ VARIANT varChild,
|
|
/* [retval][out] */ IDispatch __RPC_FAR* __RPC_FAR* ppdispChild) {
|
|
if (!ppdispChild) return E_INVALIDARG;
|
|
|
|
*ppdispChild = nullptr;
|
|
if (!mAcc) 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;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accName(
|
|
/* [optional][in] */ VARIANT varChild,
|
|
/* [retval][out] */ BSTR __RPC_FAR* pszName) {
|
|
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;
|
|
Acc()->Name(name);
|
|
|
|
if (name.IsVoid()) return S_FALSE;
|
|
|
|
*pszName = ::SysAllocStringLen(name.get(), name.Length());
|
|
if (!*pszName) return E_OUTOFMEMORY;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accValue(
|
|
/* [optional][in] */ VARIANT varChild,
|
|
/* [retval][out] */ BSTR __RPC_FAR* pszValue) {
|
|
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;
|
|
Acc()->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;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accDescription(VARIANT varChild,
|
|
BSTR __RPC_FAR* pszDescription) {
|
|
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;
|
|
Acc()->Description(description);
|
|
|
|
*pszDescription =
|
|
::SysAllocStringLen(description.get(), description.Length());
|
|
return *pszDescription ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accRole(
|
|
/* [optional][in] */ VARIANT varChild,
|
|
/* [retval][out] */ VARIANT __RPC_FAR* pvarRole) {
|
|
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
|
|
if (mAcc->IsLocal()) {
|
|
NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(mAcc->AsLocal()),
|
|
"Does not support Text when it should");
|
|
}
|
|
#endif
|
|
geckoRole = mAcc->Role();
|
|
|
|
uint32_t msaaRole = 0;
|
|
|
|
#define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
|
|
_msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \
|
|
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 = mAcc->Parent();
|
|
if (xpParent && xpParent->Role() == roles::TREE_TABLE)
|
|
msaaRole = ROLE_SYSTEM_OUTLINEITEM;
|
|
}
|
|
|
|
pvarRole->vt = VT_I4;
|
|
pvarRole->lVal = msaaRole;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accState(
|
|
/* [optional][in] */ VARIANT varChild,
|
|
/* [retval][out] */ VARIANT __RPC_FAR* pvarState) {
|
|
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 = Acc()->State();
|
|
|
|
uint32_t msaaState = 0;
|
|
nsAccUtils::To32States(state, &msaaState, nullptr);
|
|
pvarState->lVal = msaaState;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accHelp(
|
|
/* [optional][in] */ VARIANT varChild,
|
|
/* [retval][out] */ BSTR __RPC_FAR* pszHelp) {
|
|
if (!pszHelp) return E_INVALIDARG;
|
|
|
|
*pszHelp = nullptr;
|
|
return S_FALSE;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accHelpTopic(
|
|
/* [out] */ BSTR __RPC_FAR* pszHelpFile,
|
|
/* [optional][in] */ VARIANT varChild,
|
|
/* [retval][out] */ long __RPC_FAR* pidTopic) {
|
|
if (!pszHelpFile || !pidTopic) return E_INVALIDARG;
|
|
|
|
*pszHelpFile = nullptr;
|
|
*pidTopic = 0;
|
|
return S_FALSE;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accKeyboardShortcut(
|
|
/* [optional][in] */ VARIANT varChild,
|
|
/* [retval][out] */ BSTR __RPC_FAR* pszKeyboardShortcut) {
|
|
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);
|
|
}
|
|
|
|
nsAutoString shortcut;
|
|
|
|
if (!mAcc->GetStringARIAAttr(nsGkAtoms::aria_keyshortcuts, shortcut)) {
|
|
KeyBinding keyBinding = mAcc->AccessKey();
|
|
if (keyBinding.IsEmpty()) {
|
|
if (LocalAccessible* localAcc = mAcc->AsLocal()) {
|
|
keyBinding = localAcc->KeyboardShortcut();
|
|
}
|
|
}
|
|
|
|
keyBinding.ToString(shortcut);
|
|
}
|
|
|
|
*pszKeyboardShortcut = ::SysAllocStringLen(shortcut.get(), shortcut.Length());
|
|
return *pszKeyboardShortcut ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accFocus(
|
|
/* [retval][out] */ VARIANT __RPC_FAR* pvarChild) {
|
|
if (!pvarChild) return E_INVALIDARG;
|
|
|
|
VariantInit(pvarChild);
|
|
|
|
// clang-format off
|
|
// 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.
|
|
// clang-format on
|
|
if (!mAcc) {
|
|
return CO_E_OBJNOTCONNECTED;
|
|
}
|
|
// Return the current IAccessible child that has focus
|
|
Accessible* focusedAccessible = mAcc->FocusedChild();
|
|
if (focusedAccessible == mAcc) {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* This helper class implements IEnumVARIANT for a nsTArray containing
|
|
* accessible objects.
|
|
*/
|
|
class AccessibleEnumerator final : public IEnumVARIANT {
|
|
public:
|
|
explicit AccessibleEnumerator(const nsTArray<Accessible*>& aArray)
|
|
: mArray(aArray.Clone()), mCurIndex(0) {}
|
|
AccessibleEnumerator(const AccessibleEnumerator& toCopy)
|
|
: mArray(toCopy.mArray.Clone()), 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) {
|
|
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;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
AccessibleEnumerator::Next(unsigned long celt, VARIANT FAR* rgvar,
|
|
unsigned long FAR* pceltFetched) {
|
|
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 = MsaaAccessible::NativeAccessible(mArray[mCurIndex]);
|
|
}
|
|
|
|
if (pceltFetched) *pceltFetched = celt;
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
AccessibleEnumerator::Clone(IEnumVARIANT FAR* FAR* ppenum) {
|
|
*ppenum = new AccessibleEnumerator(*this);
|
|
NS_ADDREF(*ppenum);
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
AccessibleEnumerator::Skip(unsigned long celt) {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
MsaaAccessible::get_accSelection(VARIANT __RPC_FAR* pvarChildren) {
|
|
if (!pvarChildren) return E_INVALIDARG;
|
|
|
|
VariantInit(pvarChildren);
|
|
pvarChildren->vt = VT_EMPTY;
|
|
|
|
if (!mAcc) {
|
|
return CO_E_OBJNOTCONNECTED;
|
|
}
|
|
Accessible* acc = Acc();
|
|
|
|
if (!acc->IsSelect()) {
|
|
return S_OK;
|
|
}
|
|
|
|
AutoTArray<Accessible*, 10> selectedItems;
|
|
acc->SelectedItems(&selectedItems);
|
|
uint32_t count = selectedItems.Length();
|
|
if (count == 1) {
|
|
pvarChildren->vt = VT_DISPATCH;
|
|
pvarChildren->pdispVal = NativeAccessible(selectedItems[0]);
|
|
} else if (count > 1) {
|
|
RefPtr<AccessibleEnumerator> pEnum =
|
|
new AccessibleEnumerator(selectedItems);
|
|
pvarChildren->vt =
|
|
VT_UNKNOWN; // this must be VT_UNKNOWN for an IEnumVARIANT
|
|
NS_ADDREF(pvarChildren->punkVal = pEnum);
|
|
}
|
|
// If count == 0, vt is already VT_EMPTY, so there's nothing else to do.
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::get_accDefaultAction(
|
|
/* [optional][in] */ VARIANT varChild,
|
|
/* [retval][out] */ BSTR __RPC_FAR* pszDefaultAction) {
|
|
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;
|
|
mAcc->ActionNameAt(0, defaultAction);
|
|
|
|
*pszDefaultAction =
|
|
::SysAllocStringLen(defaultAction.get(), defaultAction.Length());
|
|
return *pszDefaultAction ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::accSelect(
|
|
/* [in] */ long flagsSelect,
|
|
/* [optional][in] */ VARIANT varChild) {
|
|
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) {
|
|
mAcc->TakeFocus();
|
|
return S_OK;
|
|
}
|
|
|
|
if (flagsSelect & SELFLAG_TAKESELECTION) {
|
|
mAcc->TakeSelection();
|
|
return S_OK;
|
|
}
|
|
|
|
if (flagsSelect & SELFLAG_ADDSELECTION) {
|
|
mAcc->SetSelected(true);
|
|
return S_OK;
|
|
}
|
|
|
|
if (flagsSelect & SELFLAG_REMOVESELECTION) {
|
|
mAcc->SetSelected(false);
|
|
return S_OK;
|
|
}
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::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) {
|
|
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);
|
|
}
|
|
|
|
LayoutDeviceIntRect rect = Acc()->Bounds();
|
|
*pxLeft = rect.X();
|
|
*pyTop = rect.Y();
|
|
*pcxWidth = rect.Width();
|
|
*pcyHeight = rect.Height();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::accNavigate(
|
|
/* [in] */ long navDir,
|
|
/* [optional][in] */ VARIANT varStart,
|
|
/* [retval][out] */ VARIANT __RPC_FAR* pvarEndUpAt) {
|
|
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 (!nsAccUtils::MustPrune(mAcc)) {
|
|
navAccessible = mAcc->FirstChild();
|
|
}
|
|
break;
|
|
case NAVDIR_LASTCHILD:
|
|
if (!nsAccUtils::MustPrune(mAcc)) {
|
|
navAccessible = mAcc->LastChild();
|
|
}
|
|
break;
|
|
case NAVDIR_NEXT:
|
|
navAccessible = mAcc->NextSibling();
|
|
break;
|
|
case NAVDIR_PREVIOUS:
|
|
navAccessible = mAcc->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 = mAcc->RelationByType(*xpRelation);
|
|
navAccessible = rel.Next();
|
|
}
|
|
|
|
if (!navAccessible) return E_FAIL;
|
|
|
|
pvarEndUpAt->pdispVal = NativeAccessible(navAccessible);
|
|
pvarEndUpAt->vt = VT_DISPATCH;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::accHitTest(
|
|
/* [in] */ long xLeft,
|
|
/* [in] */ long yTop,
|
|
/* [retval][out] */ VARIANT __RPC_FAR* pvarChild) {
|
|
if (!pvarChild) return E_INVALIDARG;
|
|
|
|
VariantInit(pvarChild);
|
|
|
|
if (!mAcc) {
|
|
return CO_E_OBJNOTCONNECTED;
|
|
}
|
|
|
|
// The MSAA documentation says accHitTest should return a child. However,
|
|
// clients call AccessibleObjectFromPoint, which ends up walking the
|
|
// descendants calling accHitTest on each one. Since clients want the
|
|
// deepest descendant anyway, it's faster and probably more accurate to
|
|
// just do this ourselves.
|
|
Accessible* accessible = mAcc->ChildAtPoint(
|
|
xLeft, yTop, Accessible::EWhichChildAtPoint::DeepestChild);
|
|
|
|
// if we got a child
|
|
if (accessible) {
|
|
if (accessible != mAcc && accessible->IsTextLeaf()) {
|
|
Accessible* parent = accessible->Parent();
|
|
if (parent != mAcc && parent->Role() == roles::LINK) {
|
|
// Bug 1843832: The UI Automation -> IAccessible2 proxy barfs if we
|
|
// return the text leaf child of a link when hit testing an ancestor of
|
|
// the link. Therefore, we return the link instead. MSAA clients which
|
|
// call AccessibleObjectFromPoint will still get to the text leaf, since
|
|
// AccessibleObjectFromPoint keeps calling accHitTest until it can't
|
|
// descend any further. We should remove this tragic hack once we have
|
|
// a native UIA implementation.
|
|
accessible = parent;
|
|
}
|
|
}
|
|
if (accessible == mAcc) {
|
|
pvarChild->vt = VT_I4;
|
|
pvarChild->lVal = CHILDID_SELF;
|
|
} else {
|
|
pvarChild->vt = VT_DISPATCH;
|
|
pvarChild->pdispVal = NativeAccessible(accessible);
|
|
}
|
|
} else {
|
|
// no child at that point
|
|
pvarChild->vt = VT_EMPTY;
|
|
return S_FALSE;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::accDoDefaultAction(
|
|
/* [optional][in] */ VARIANT varChild) {
|
|
RefPtr<IAccessible> accessible;
|
|
HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
if (accessible) {
|
|
return accessible->accDoDefaultAction(kVarChildIdSelf);
|
|
}
|
|
|
|
return mAcc->DoAction(0) ? S_OK : E_INVALIDARG;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::put_accName(
|
|
/* [optional][in] */ VARIANT varChild,
|
|
/* [in] */ BSTR szName) {
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::put_accValue(
|
|
/* [optional][in] */ VARIANT varChild,
|
|
/* [in] */ BSTR szValue) {
|
|
RefPtr<IAccessible> accessible;
|
|
HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
if (accessible) {
|
|
return accessible->put_accValue(kVarChildIdSelf, szValue);
|
|
}
|
|
|
|
HyperTextAccessibleBase* ht = mAcc->AsHyperTextBase();
|
|
if (!ht) {
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
uint32_t length = ::SysStringLen(szValue);
|
|
nsAutoString text(szValue, length);
|
|
ht->ReplaceText(text);
|
|
return S_OK;
|
|
}
|
|
|
|
// IDispatch methods
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::GetTypeInfoCount(UINT* pctinfo) {
|
|
if (!pctinfo) return E_INVALIDARG;
|
|
|
|
*pctinfo = 1;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
MsaaAccessible::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
|
|
MsaaAccessible::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
|
|
MsaaAccessible::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);
|
|
}
|