855 lines
26 KiB
C++
855 lines
26 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=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 "LazyInstantiator.h"
|
|
|
|
#include "MainThreadUtils.h"
|
|
#include "mozilla/a11y/LocalAccessible.h"
|
|
#include "mozilla/a11y/Compatibility.h"
|
|
#include "mozilla/a11y/Platform.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/mscom/ProcessRuntime.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "mozilla/WinHeaderOnlyUtils.h"
|
|
#include "MsaaRootAccessible.h"
|
|
#include "nsAccessibilityService.h"
|
|
#include "nsWindowsHelpers.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsIFile.h"
|
|
#include "nsXPCOM.h"
|
|
#include "WinUtils.h"
|
|
#include "prenv.h"
|
|
|
|
#include <oaidl.h>
|
|
|
|
#if !defined(STATE_SYSTEM_NORMAL)
|
|
# define STATE_SYSTEM_NORMAL (0)
|
|
#endif // !defined(STATE_SYSTEM_NORMAL)
|
|
|
|
#define DLL_BLOCKLIST_ENTRY(name, ...) {L##name, __VA_ARGS__},
|
|
#define DLL_BLOCKLIST_STRING_TYPE const wchar_t*
|
|
#include "mozilla/WindowsDllBlocklistA11yDefs.h"
|
|
|
|
namespace mozilla {
|
|
namespace a11y {
|
|
|
|
static const wchar_t kLazyInstantiatorProp[] =
|
|
L"mozilla::a11y::LazyInstantiator";
|
|
|
|
Maybe<bool> LazyInstantiator::sShouldBlockUia;
|
|
|
|
template <class T>
|
|
already_AddRefed<T> LazyInstantiator::GetRoot(HWND aHwnd) {
|
|
RefPtr<T> result;
|
|
// At this time we only want to check whether the acc service is running. We
|
|
// don't actually want to create the acc service yet.
|
|
if (!GetAccService()) {
|
|
// There must only be one LazyInstantiator per HWND.
|
|
// To track this, we set the kLazyInstantiatorProp on the HWND with a
|
|
// pointer to an existing instance. We only create a new LazyInstatiator if
|
|
// that prop has not already been set.
|
|
LazyInstantiator* existingInstantiator =
|
|
reinterpret_cast<LazyInstantiator*>(
|
|
::GetProp(aHwnd, kLazyInstantiatorProp));
|
|
|
|
if (existingInstantiator) {
|
|
// Temporarily disable blind aggregation until we know that we have been
|
|
// marshaled. See EnableBlindAggregation for more information.
|
|
existingInstantiator->mAllowBlindAggregation = false;
|
|
result = existingInstantiator;
|
|
return result.forget();
|
|
}
|
|
|
|
// a11y is not running yet, there are no existing LazyInstantiators for this
|
|
// HWND, so create a new one and return it as a surrogate for the root
|
|
// accessible.
|
|
result = new LazyInstantiator(aHwnd);
|
|
return result.forget();
|
|
}
|
|
|
|
// a11y is running, so we just resolve the real root accessible.
|
|
a11y::LocalAccessible* rootAcc =
|
|
widget::WinUtils::GetRootAccessibleForHWND(aHwnd);
|
|
if (!rootAcc) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!rootAcc->IsRoot()) {
|
|
// rootAcc might represent a popup as opposed to a true root accessible.
|
|
// In that case we just use the regular LocalAccessible::GetNativeInterface.
|
|
result = MsaaAccessible::GetFrom(rootAcc);
|
|
return result.forget();
|
|
}
|
|
|
|
auto msaaRoot =
|
|
static_cast<MsaaRootAccessible*>(MsaaAccessible::GetFrom(rootAcc));
|
|
// Subtle: msaaRoot might still be wrapped by a LazyInstantiator, but we
|
|
// don't need LazyInstantiator's capabilities anymore (since a11y is already
|
|
// running). We can bypass LazyInstantiator by retrieving the internal
|
|
// unknown (which is not wrapped by the LazyInstantiator) and then querying
|
|
// that for the interface we want.
|
|
RefPtr<IUnknown> punk(msaaRoot->GetInternalUnknown());
|
|
|
|
MOZ_ASSERT(punk);
|
|
if (!punk) {
|
|
return nullptr;
|
|
}
|
|
|
|
punk->QueryInterface(__uuidof(T), getter_AddRefs(result));
|
|
return result.forget();
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) {
|
|
return GetRoot<IAccessible>(aHwnd);
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<IRawElementProviderSimple> LazyInstantiator::GetRootUia(
|
|
HWND aHwnd) {
|
|
if (!Compatibility::IsUiaEnabled()) {
|
|
return nullptr;
|
|
}
|
|
return GetRoot<IRawElementProviderSimple>(aHwnd);
|
|
}
|
|
|
|
/**
|
|
* When marshaling an interface, COM makes a whole bunch of QueryInterface
|
|
* calls to determine what kind of marshaling the interface supports. We need
|
|
* to handle those queries without instantiating a11y, so we temporarily
|
|
* disable passing through of QueryInterface calls to a11y. Once we know that
|
|
* COM is finished marshaling, we call EnableBlindAggregation to re-enable
|
|
* QueryInterface passthrough.
|
|
*/
|
|
/* static */
|
|
void LazyInstantiator::EnableBlindAggregation(HWND aHwnd) {
|
|
if (GetAccService()) {
|
|
// The accessibility service is already running. That means that
|
|
// LazyInstantiator::GetRootAccessible returned the real MsaaRootAccessible,
|
|
// rather than returning a LazyInstantiator with blind aggregation disabled.
|
|
// Thus, we have nothing to do here.
|
|
return;
|
|
}
|
|
|
|
LazyInstantiator* existingInstantiator = reinterpret_cast<LazyInstantiator*>(
|
|
::GetProp(aHwnd, kLazyInstantiatorProp));
|
|
|
|
if (!existingInstantiator) {
|
|
return;
|
|
}
|
|
|
|
existingInstantiator->mAllowBlindAggregation = true;
|
|
}
|
|
|
|
LazyInstantiator::LazyInstantiator(HWND aHwnd)
|
|
: mHwnd(aHwnd),
|
|
mAllowBlindAggregation(false),
|
|
mWeakMsaaRoot(nullptr),
|
|
mWeakAccessible(nullptr),
|
|
mWeakDispatch(nullptr),
|
|
mWeakUia(nullptr) {
|
|
MOZ_ASSERT(aHwnd);
|
|
// Assign ourselves as the designated LazyInstantiator for aHwnd
|
|
DebugOnly<BOOL> setPropOk =
|
|
::SetProp(aHwnd, kLazyInstantiatorProp, reinterpret_cast<HANDLE>(this));
|
|
MOZ_ASSERT(setPropOk);
|
|
}
|
|
|
|
LazyInstantiator::~LazyInstantiator() {
|
|
if (mRealRootUnk) {
|
|
// Disconnect ourselves from the root accessible.
|
|
RefPtr<IUnknown> dummy(mWeakMsaaRoot->Aggregate(nullptr));
|
|
}
|
|
|
|
ClearProp();
|
|
}
|
|
|
|
void LazyInstantiator::ClearProp() {
|
|
// Remove ourselves as the designated LazyInstantiator for mHwnd
|
|
DebugOnly<HANDLE> removedProp = ::RemoveProp(mHwnd, kLazyInstantiatorProp);
|
|
MOZ_ASSERT(!removedProp ||
|
|
reinterpret_cast<LazyInstantiator*>(removedProp.value) == this);
|
|
}
|
|
|
|
/**
|
|
* Get the process id of a remote (out-of-process) MSAA/IA2 client.
|
|
*/
|
|
DWORD LazyInstantiator::GetRemoteMsaaClientPid() {
|
|
nsAutoHandle callingThread(
|
|
::OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE,
|
|
mscom::ProcessRuntime::GetClientThreadId()));
|
|
if (!callingThread) {
|
|
return 0;
|
|
}
|
|
return ::GetProcessIdOfThread(callingThread);
|
|
}
|
|
|
|
/**
|
|
* This is the blocklist for known "bad" remote clients that instantiate a11y.
|
|
*/
|
|
static const char* gBlockedRemoteClients[] = {
|
|
"tbnotifier.exe", // Ask.com Toolbar, bug 1453876
|
|
"flow.exe", // Conexant Flow causes performance issues, bug 1569712
|
|
"rtop_bg.exe", // ByteFence Anti-Malware, bug 1713383
|
|
"osk.exe", // Windows On-Screen Keyboard, bug 1424505
|
|
"corplink-uc.exe", // Feilian CorpLink, bug 1951571
|
|
};
|
|
|
|
/**
|
|
* Check for the presence of any known "bad" injected DLLs that may be trying
|
|
* to instantiate a11y.
|
|
*
|
|
* @return true to block a11y instantiation, otherwise false to continue
|
|
*/
|
|
bool LazyInstantiator::IsBlockedInjection() {
|
|
// Check debugging options see if we should disable the blocklist.
|
|
if (PR_GetEnv("MOZ_DISABLE_ACCESSIBLE_BLOCKLIST")) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t index = 0, len = std::size(gBlockedInprocDlls); index < len;
|
|
++index) {
|
|
const DllBlockInfo& blockedDll = gBlockedInprocDlls[index];
|
|
HMODULE module = ::GetModuleHandleW(blockedDll.mName);
|
|
if (!module) {
|
|
// This dll isn't loaded.
|
|
continue;
|
|
}
|
|
|
|
LauncherResult<ModuleVersion> version = GetModuleVersion(module);
|
|
return version.isOk() && blockedDll.IsVersionBlocked(version.unwrap());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Given a remote client's process ID, determine whether we should proceed with
|
|
* a11y instantiation. This is where telemetry should be gathered and any
|
|
* potential blocking of unwanted a11y clients should occur.
|
|
*
|
|
* @return true if we should instantiate a11y
|
|
*/
|
|
bool LazyInstantiator::ShouldInstantiate(const DWORD aClientPid) {
|
|
a11y::SetInstantiator(aClientPid);
|
|
|
|
nsCOMPtr<nsIFile> clientExe;
|
|
if (!a11y::GetInstantiator(getter_AddRefs(clientExe))) {
|
|
return true;
|
|
}
|
|
|
|
nsresult rv;
|
|
if (!PR_GetEnv("MOZ_DISABLE_ACCESSIBLE_BLOCKLIST")) {
|
|
// Debugging option is not present, so check blocklist.
|
|
nsAutoString leafName;
|
|
rv = clientExe->GetLeafName(leafName);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
for (size_t i = 0, len = std::size(gBlockedRemoteClients); i < len; ++i) {
|
|
if (leafName.EqualsIgnoreCase(gBlockedRemoteClients[i])) {
|
|
// If client exe is in our blocklist, do not instantiate.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Determine whether we should proceed with a11y instantiation, considering the
|
|
* various different types of clients.
|
|
*/
|
|
bool LazyInstantiator::ShouldInstantiate() {
|
|
if (Compatibility::IsA11ySuppressed()) {
|
|
return false;
|
|
}
|
|
if (DWORD pid = GetRemoteMsaaClientPid()) {
|
|
return ShouldInstantiate(pid);
|
|
}
|
|
if (Compatibility::HasKnownNonUiaConsumer()) {
|
|
// We detected a known in-process client.
|
|
return true;
|
|
}
|
|
// UIA client detection can be expensive, so we cache the result. See the
|
|
// header comment for ResetUiaDetectionCache() for details.
|
|
if (sShouldBlockUia.isNothing()) {
|
|
// Unlike MSAA, we can't tell which specific UIA client is querying us right
|
|
// now. We can only determine which clients have tried querying us.
|
|
// Therefore, we must check all of them.
|
|
AutoTArray<DWORD, 1> uiaPids;
|
|
Compatibility::GetUiaClientPids(uiaPids);
|
|
if (uiaPids.IsEmpty()) {
|
|
// No UIA clients, so don't block UIA. However, we might block for
|
|
// non-UIA clients below.
|
|
sShouldBlockUia = Some(false);
|
|
} else {
|
|
for (const DWORD pid : uiaPids) {
|
|
if (ShouldInstantiate(pid)) {
|
|
sShouldBlockUia = Some(false);
|
|
return true;
|
|
}
|
|
}
|
|
// We didn't return in the loop above, so there are only blocked UIA
|
|
// clients.
|
|
sShouldBlockUia = Some(true);
|
|
}
|
|
}
|
|
if (*sShouldBlockUia) {
|
|
return false;
|
|
}
|
|
if (IsBlockedInjection()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
MsaaRootAccessible* LazyInstantiator::ResolveMsaaRoot() {
|
|
LocalAccessible* acc = widget::WinUtils::GetRootAccessibleForHWND(mHwnd);
|
|
if (!acc || !acc->IsRoot()) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<IAccessible> ia;
|
|
acc->GetNativeInterface(getter_AddRefs(ia));
|
|
return static_cast<MsaaRootAccessible*>(ia.get());
|
|
}
|
|
|
|
/**
|
|
* With COM aggregation, the aggregated inner object usually delegates its
|
|
* reference counting to the outer object. In other words, we would expect
|
|
* mRealRootUnk to delegate its AddRef() and Release() to this LazyInstantiator.
|
|
*
|
|
* This scheme will not work in our case because the RootAccessibleWrap is
|
|
* cycle-collected!
|
|
*
|
|
* Instead, once a LazyInstantiator aggregates a RootAccessibleWrap, we transfer
|
|
* our strong references into mRealRootUnk. Any future calls to AddRef or
|
|
* Release now operate on mRealRootUnk instead of our intrinsic reference
|
|
* count. This is a bit strange, but it is the only way for these objects to
|
|
* share their reference count in a way that is safe for cycle collection.
|
|
*
|
|
* How do we know when it is safe to destroy ourselves? In
|
|
* LazyInstantiator::Release, we examine the result of mRealRootUnk->Release().
|
|
* If mRealRootUnk's resulting refcount is 1, then we know that the only
|
|
* remaining reference to mRealRootUnk is the mRealRootUnk reference itself (and
|
|
* thus nobody else holds references to either this or mRealRootUnk). Therefore
|
|
* we may now delete ourselves.
|
|
*/
|
|
void LazyInstantiator::TransplantRefCnt() {
|
|
MOZ_ASSERT(mRefCnt > 0);
|
|
MOZ_ASSERT(mRealRootUnk);
|
|
|
|
while (mRefCnt > 0) {
|
|
mRealRootUnk.get()->AddRef();
|
|
--mRefCnt;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::MaybeResolveRoot() {
|
|
if (!GetAccService() && !ShouldInstantiate()) {
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (!mWeakAccessible) {
|
|
mWeakMsaaRoot = ResolveMsaaRoot();
|
|
if (!mWeakMsaaRoot) {
|
|
return E_POINTER;
|
|
}
|
|
|
|
// Wrap ourselves around the root accessible wrap
|
|
mRealRootUnk = mWeakMsaaRoot->Aggregate(static_cast<IAccessible*>(this));
|
|
if (!mRealRootUnk) {
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Move our strong references into the root accessible (see the comments
|
|
// above TransplantRefCnt for explanation).
|
|
TransplantRefCnt();
|
|
|
|
// Now obtain mWeakAccessible which we use to forward our incoming calls
|
|
// to the real accessible.
|
|
HRESULT hr =
|
|
mRealRootUnk->QueryInterface(IID_IAccessible, (void**)&mWeakAccessible);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
// mWeakAccessible is weak, so don't hold a strong ref
|
|
mWeakAccessible->Release();
|
|
|
|
// Now that a11y is running, we don't need to remain registered with our
|
|
// HWND anymore.
|
|
ClearProp();
|
|
}
|
|
|
|
// If the UIA pref is changed during the session, this method might be first
|
|
// called with UIA disabled and then called again later with UIA enabled.
|
|
// Thus, we handle mWeakUia separately from mWeakAccessible.
|
|
if (!mWeakUia && Compatibility::IsUiaEnabled()) {
|
|
MOZ_ASSERT(mWeakAccessible);
|
|
HRESULT hr = mRealRootUnk->QueryInterface(IID_IRawElementProviderSimple,
|
|
(void**)&mWeakUia);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
mWeakUia->Release();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
#define RESOLVE_ROOT \
|
|
{ \
|
|
HRESULT hr = MaybeResolveRoot(); \
|
|
if (FAILED(hr)) { \
|
|
return hr; \
|
|
} \
|
|
}
|
|
|
|
#define RESOLVE_ROOT_UIA_RETURN_IF_FAIL \
|
|
RESOLVE_ROOT \
|
|
if (!mWeakUia) { \
|
|
/* UIA was previously enabled, allowing QueryInterface to a UIA interface. \
|
|
* It was subsequently disabled before we could resolve the root. \
|
|
*/ \
|
|
return E_FAIL; \
|
|
}
|
|
|
|
IMPL_IUNKNOWN_QUERY_HEAD(LazyInstantiator)
|
|
if (NS_WARN_IF(!NS_IsMainThread())) {
|
|
// Bug 1814780, bug 1949617: The COM marshaler sometimes calls QueryInterface
|
|
// on the wrong thread.
|
|
return RPC_E_WRONG_THREAD;
|
|
}
|
|
IMPL_IUNKNOWN_QUERY_IFACE_AMBIGIOUS(IUnknown, IAccessible)
|
|
IMPL_IUNKNOWN_QUERY_IFACE(IAccessible)
|
|
IMPL_IUNKNOWN_QUERY_IFACE(IDispatch)
|
|
IMPL_IUNKNOWN_QUERY_IFACE(IServiceProvider)
|
|
if (Compatibility::IsUiaEnabled()) {
|
|
IMPL_IUNKNOWN_QUERY_IFACE(IRawElementProviderSimple)
|
|
}
|
|
// See EnableBlindAggregation for comments.
|
|
if (!mAllowBlindAggregation) {
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
if (aIID == IID_IAccIdentity) {
|
|
return E_NOINTERFACE;
|
|
}
|
|
// If the client queries for an interface that LazyInstantiator does not
|
|
// intrinsically support, then we must resolve the root accessible and pass
|
|
// on the QueryInterface call to mRealRootUnk.
|
|
RESOLVE_ROOT
|
|
IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mRealRootUnk)
|
|
|
|
ULONG
|
|
LazyInstantiator::AddRef() {
|
|
// Always delegate refcounting to mRealRootUnk when it exists
|
|
if (mRealRootUnk) {
|
|
return mRealRootUnk.get()->AddRef();
|
|
}
|
|
|
|
return ++mRefCnt;
|
|
}
|
|
|
|
ULONG
|
|
LazyInstantiator::Release() {
|
|
ULONG result;
|
|
|
|
// Always delegate refcounting to mRealRootUnk when it exists
|
|
if (mRealRootUnk) {
|
|
result = mRealRootUnk.get()->Release();
|
|
if (result == 1) {
|
|
// mRealRootUnk is the only strong reference left, so nothing else holds
|
|
// a strong reference to us. Drop result to zero so that we destroy
|
|
// ourselves (See the comments above LazyInstantiator::TransplantRefCnt
|
|
// for more info).
|
|
--result;
|
|
}
|
|
} else {
|
|
result = --mRefCnt;
|
|
}
|
|
|
|
if (!result) {
|
|
delete this;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Create a standard IDispatch implementation. mStdDispatch will translate any
|
|
* IDispatch::Invoke calls into real IAccessible calls.
|
|
*/
|
|
HRESULT
|
|
LazyInstantiator::ResolveDispatch() {
|
|
if (mWeakDispatch) {
|
|
return S_OK;
|
|
}
|
|
|
|
// Extract IAccessible's type info
|
|
RefPtr<ITypeInfo> accTypeInfo = MsaaAccessible::GetTI(LOCALE_USER_DEFAULT);
|
|
if (!accTypeInfo) {
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// Now create the standard IDispatch for IAccessible
|
|
HRESULT hr = ::CreateStdDispatch(static_cast<IAccessible*>(this),
|
|
static_cast<IAccessible*>(this), accTypeInfo,
|
|
getter_AddRefs(mStdDispatch));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
hr = mStdDispatch->QueryInterface(IID_IDispatch, (void**)&mWeakDispatch);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// WEAK reference
|
|
mWeakDispatch->Release();
|
|
return S_OK;
|
|
}
|
|
|
|
#define RESOLVE_IDISPATCH \
|
|
{ \
|
|
HRESULT hr = ResolveDispatch(); \
|
|
if (FAILED(hr)) { \
|
|
return hr; \
|
|
} \
|
|
}
|
|
|
|
/**
|
|
* The remaining methods implement IDispatch, IAccessible, and IServiceProvider,
|
|
* lazily resolving the real a11y objects and passing the call through.
|
|
*/
|
|
|
|
HRESULT
|
|
LazyInstantiator::GetTypeInfoCount(UINT* pctinfo) {
|
|
RESOLVE_IDISPATCH;
|
|
return mWeakDispatch->GetTypeInfoCount(pctinfo);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) {
|
|
RESOLVE_IDISPATCH;
|
|
return mWeakDispatch->GetTypeInfo(iTInfo, lcid, ppTInfo);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames,
|
|
LCID lcid, DISPID* rgDispId) {
|
|
RESOLVE_IDISPATCH;
|
|
return mWeakDispatch->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
|
|
WORD wFlags, DISPPARAMS* pDispParams,
|
|
VARIANT* pVarResult, EXCEPINFO* pExcepInfo,
|
|
UINT* puArgErr) {
|
|
RESOLVE_IDISPATCH;
|
|
return mWeakDispatch->Invoke(dispIdMember, riid, lcid, wFlags, pDispParams,
|
|
pVarResult, pExcepInfo, puArgErr);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accParent(IDispatch** ppdispParent) {
|
|
if (!mWeakAccessible) {
|
|
// If we'd resolve the root right now this would be the codepath we'd end
|
|
// up in anyway. So we might as well return it here.
|
|
return ::CreateStdAccessibleObject(mHwnd, OBJID_WINDOW, IID_IAccessible,
|
|
(void**)ppdispParent);
|
|
}
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->get_accParent(ppdispParent);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accChildCount(long* pcountChildren) {
|
|
if (!pcountChildren) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->get_accChildCount(pcountChildren);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accChild(VARIANT varChild, IDispatch** ppdispChild) {
|
|
if (!ppdispChild) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (V_VT(&varChild) == VT_I4 && V_I4(&varChild) == CHILDID_SELF) {
|
|
RefPtr<IDispatch> disp(this);
|
|
disp.forget(ppdispChild);
|
|
return S_OK;
|
|
}
|
|
|
|
if (NS_WARN_IF(!NS_IsMainThread())) {
|
|
// Bug 1965216: The COM runtime occasionally calls this method on the wrong
|
|
// thread, violating COM rules. We can't reproduce this and don't understand
|
|
// what causes it.
|
|
return RPC_E_WRONG_THREAD;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->get_accChild(varChild, ppdispChild);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accName(VARIANT varChild, BSTR* pszName) {
|
|
if (!pszName) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->get_accName(varChild, pszName);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accValue(VARIANT varChild, BSTR* pszValue) {
|
|
if (!pszValue) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->get_accValue(varChild, pszValue);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accDescription(VARIANT varChild, BSTR* pszDescription) {
|
|
if (!pszDescription) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->get_accDescription(varChild, pszDescription);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accRole(VARIANT varChild, VARIANT* pvarRole) {
|
|
if (!pvarRole) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (V_VT(&varChild) == VT_I4 && V_I4(&varChild) == CHILDID_SELF) {
|
|
V_VT(pvarRole) = VT_I4;
|
|
V_I4(pvarRole) = ROLE_SYSTEM_APPLICATION;
|
|
return S_OK;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->get_accRole(varChild, pvarRole);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accState(VARIANT varChild, VARIANT* pvarState) {
|
|
if (!pvarState) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->get_accState(varChild, pvarState);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accHelp(VARIANT varChild, BSTR* pszHelp) {
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild,
|
|
long* pidTopic) {
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accKeyboardShortcut(VARIANT varChild,
|
|
BSTR* pszKeyboardShortcut) {
|
|
if (!pszKeyboardShortcut) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->get_accKeyboardShortcut(varChild,
|
|
pszKeyboardShortcut);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accFocus(VARIANT* pvarChild) {
|
|
if (!pvarChild) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->get_accFocus(pvarChild);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accSelection(VARIANT* pvarChildren) {
|
|
if (!pvarChildren) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->get_accSelection(pvarChildren);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::get_accDefaultAction(VARIANT varChild,
|
|
BSTR* pszDefaultAction) {
|
|
if (!pszDefaultAction) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->get_accDefaultAction(varChild, pszDefaultAction);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::accSelect(long flagsSelect, VARIANT varChild) {
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->accSelect(flagsSelect, varChild);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::accLocation(long* pxLeft, long* pyTop, long* pcxWidth,
|
|
long* pcyHeight, VARIANT varChild) {
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight,
|
|
varChild);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::accNavigate(long navDir, VARIANT varStart,
|
|
VARIANT* pvarEndUpAt) {
|
|
if (!pvarEndUpAt) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->accNavigate(navDir, varStart, pvarEndUpAt);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::accHitTest(long xLeft, long yTop, VARIANT* pvarChild) {
|
|
if (!pvarChild) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->accHitTest(xLeft, yTop, pvarChild);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::accDoDefaultAction(VARIANT varChild) {
|
|
RESOLVE_ROOT;
|
|
return mWeakAccessible->accDoDefaultAction(varChild);
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::put_accName(VARIANT varChild, BSTR szName) {
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT
|
|
LazyInstantiator::put_accValue(VARIANT varChild, BSTR szValue) {
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static const GUID kUnsupportedServices[] = {
|
|
// clang-format off
|
|
// Unknown, queried by Windows on devices with touch screens or similar devices
|
|
// connected.
|
|
{0x33f139ee, 0xe509, 0x47f7, {0xbf, 0x39, 0x83, 0x76, 0x44, 0xf7, 0x45, 0x76}},
|
|
// Unknown, queried by Windows
|
|
{0xFDA075CF, 0x7C8B, 0x498C, { 0xB5, 0x14, 0xA9, 0xCB, 0x52, 0x1B, 0xBF, 0xB4 }},
|
|
// Unknown, queried by Windows
|
|
{0x8EDAA462, 0x21F4, 0x4C87, { 0xA0, 0x12, 0xB3, 0xCD, 0xA3, 0xAB, 0x01, 0xFC }},
|
|
// Unknown, queried by Windows
|
|
{0xacd46652, 0x829d, 0x41cb, { 0xa5, 0xfc, 0x17, 0xac, 0xf4, 0x36, 0x61, 0xac }},
|
|
// SID_IsUIAutomationObject (undocumented), queried by Windows
|
|
{0xb96fdb85, 0x7204, 0x4724, { 0x84, 0x2b, 0xc7, 0x05, 0x9d, 0xed, 0xb9, 0xd0 }},
|
|
// IIS_IsOleaccProxy (undocumented), queried by Windows
|
|
{0x902697FA, 0x80E4, 0x4560, {0x80, 0x2A, 0xA1, 0x3F, 0x22, 0xA6, 0x47, 0x09}},
|
|
// IID_IHTMLElement, queried by JAWS
|
|
{0x3050F1FF, 0x98B5, 0x11CF, {0xBB, 0x82, 0x00, 0xAA, 0x00, 0xBD, 0xCE, 0x0B}}
|
|
// clang-format on
|
|
};
|
|
|
|
HRESULT
|
|
LazyInstantiator::QueryService(REFGUID aServiceId, REFIID aServiceIid,
|
|
void** aOutInterface) {
|
|
if (!aOutInterface) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
for (const GUID& unsupportedService : kUnsupportedServices) {
|
|
if (aServiceId == unsupportedService) {
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
*aOutInterface = nullptr;
|
|
|
|
RESOLVE_ROOT;
|
|
|
|
RefPtr<IServiceProvider> servProv;
|
|
HRESULT hr = mRealRootUnk->QueryInterface(IID_IServiceProvider,
|
|
getter_AddRefs(servProv));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
return servProv->QueryService(aServiceId, aServiceIid, aOutInterface);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
LazyInstantiator::get_ProviderOptions(
|
|
__RPC__out enum ProviderOptions* aOptions) {
|
|
// This method is called before a UIA connection is fully established and thus
|
|
// before we can detect the client. We must not call
|
|
// RESOLVE_ROOT_UIA_RETURN_IF_FAIL here because this might turn out to be a
|
|
// client we want to block.
|
|
if (!aOptions) {
|
|
return E_INVALIDARG;
|
|
}
|
|
*aOptions = uiaRawElmProvider::kProviderOptions;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
LazyInstantiator::GetPatternProvider(
|
|
PATTERNID aPatternId, __RPC__deref_out_opt IUnknown** aPatternProvider) {
|
|
RESOLVE_ROOT_UIA_RETURN_IF_FAIL;
|
|
return mWeakUia->GetPatternProvider(aPatternId, aPatternProvider);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
LazyInstantiator::GetPropertyValue(PROPERTYID aPropertyId,
|
|
__RPC__out VARIANT* aPropertyValue) {
|
|
RESOLVE_ROOT_UIA_RETURN_IF_FAIL;
|
|
return mWeakUia->GetPropertyValue(aPropertyId, aPropertyValue);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
LazyInstantiator::get_HostRawElementProvider(
|
|
__RPC__deref_out_opt IRawElementProviderSimple** aRawElmProvider) {
|
|
// This method is called before a UIA connection is fully established and thus
|
|
// before we can detect the client. We must not call
|
|
// RESOLVE_ROOT_UIA_RETURN_IF_FAIL here because this might turn out to be a
|
|
// client we want to block.
|
|
if (!aRawElmProvider) {
|
|
return E_INVALIDARG;
|
|
}
|
|
*aRawElmProvider = nullptr;
|
|
return UiaHostProviderFromHwnd(mHwnd, aRawElmProvider);
|
|
}
|
|
|
|
} // namespace a11y
|
|
} // namespace mozilla
|