зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset 0724fde82b2f (bug 1838123) for causing build bustages. CLOSED TREE
This commit is contained in:
Родитель
b9171ec2f1
Коммит
1cfd3a3bc7
|
@ -8,7 +8,7 @@
|
|||
#define COMPATIBILITY_MANAGER_H
|
||||
|
||||
#include <windows.h>
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "nsString.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
@ -60,7 +60,9 @@ class Compatibility {
|
|||
*/
|
||||
static void Init();
|
||||
|
||||
static void GetUiaClientPids(nsTArray<DWORD>& aPids);
|
||||
static Maybe<bool> OnUIAMessage(WPARAM aWParam, LPARAM aLParam);
|
||||
|
||||
static Maybe<DWORD> GetUiaRemotePid() { return sUiaRemotePid; }
|
||||
|
||||
/**
|
||||
* return true if a known, non-UIA a11y consumer is present
|
||||
|
@ -110,6 +112,7 @@ class Compatibility {
|
|||
|
||||
private:
|
||||
static uint32_t sConsumers;
|
||||
static Maybe<DWORD> sUiaRemotePid;
|
||||
};
|
||||
|
||||
} // namespace a11y
|
||||
|
|
|
@ -7,30 +7,201 @@
|
|||
#include "Compatibility.h"
|
||||
|
||||
#include "mozilla/a11y/Platform.h"
|
||||
#include "mozilla/ScopeExit.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
#include "mozilla/WindowsVersion.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsdefs.h"
|
||||
#include "nspr/prenv.h"
|
||||
|
||||
#include "nsIFile.h"
|
||||
#include "nsTHashMap.h"
|
||||
#include "nsTHashSet.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTHashtable.h"
|
||||
#include "nsUnicharUtils.h"
|
||||
#include "nsWindowsHelpers.h"
|
||||
#include "nsWinUtils.h"
|
||||
|
||||
#include "NtUndoc.h"
|
||||
|
||||
#if defined(UIA_LOGGING)
|
||||
|
||||
# define LOG_ERROR(FuncName) \
|
||||
{ \
|
||||
DWORD err = ::GetLastError(); \
|
||||
nsPrintfCString msg(#FuncName " failed with code %u\n", err); \
|
||||
::OutputDebugStringA(msg.get()); \
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
# define LOG_ERROR(FuncName)
|
||||
|
||||
#endif // defined(UIA_LOGGING)
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
struct ByteArrayDeleter {
|
||||
void operator()(void* aBuf) { delete[] reinterpret_cast<char*>(aBuf); }
|
||||
};
|
||||
|
||||
typedef UniquePtr<OBJECT_DIRECTORY_INFORMATION, ByteArrayDeleter> ObjDirInfoPtr;
|
||||
|
||||
// ComparatorFnT returns true to continue searching, or else false to indicate
|
||||
// search completion.
|
||||
template <typename ComparatorFnT>
|
||||
static bool FindNamedObject(const ComparatorFnT& aComparator) {
|
||||
// We want to enumerate every named kernel object in our session. We do this
|
||||
// by opening a directory object using a path constructed using the session
|
||||
// id under which our process resides.
|
||||
DWORD sessionId;
|
||||
if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &sessionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoString path;
|
||||
path.AppendPrintf("\\Sessions\\%lu\\BaseNamedObjects", sessionId);
|
||||
|
||||
UNICODE_STRING baseNamedObjectsName;
|
||||
::RtlInitUnicodeString(&baseNamedObjectsName, path.get());
|
||||
|
||||
OBJECT_ATTRIBUTES attributes;
|
||||
InitializeObjectAttributes(&attributes, &baseNamedObjectsName, 0, nullptr,
|
||||
nullptr);
|
||||
|
||||
HANDLE rawBaseNamedObjects;
|
||||
NTSTATUS ntStatus = ::NtOpenDirectoryObject(
|
||||
&rawBaseNamedObjects, DIRECTORY_QUERY | DIRECTORY_TRAVERSE, &attributes);
|
||||
if (!NT_SUCCESS(ntStatus)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoHandle baseNamedObjects(rawBaseNamedObjects);
|
||||
|
||||
ULONG context = 0, returnedLen;
|
||||
|
||||
ULONG objDirInfoBufLen = 1024 * sizeof(OBJECT_DIRECTORY_INFORMATION);
|
||||
ObjDirInfoPtr objDirInfo(reinterpret_cast<OBJECT_DIRECTORY_INFORMATION*>(
|
||||
new char[objDirInfoBufLen]));
|
||||
|
||||
// Now query that directory object for every named object that it contains.
|
||||
|
||||
BOOL firstCall = TRUE;
|
||||
|
||||
do {
|
||||
ntStatus = ::NtQueryDirectoryObject(baseNamedObjects, objDirInfo.get(),
|
||||
objDirInfoBufLen, FALSE, firstCall,
|
||||
&context, &returnedLen);
|
||||
#if defined(HAVE_64BIT_BUILD)
|
||||
if (!NT_SUCCESS(ntStatus)) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (ntStatus == STATUS_BUFFER_TOO_SMALL) {
|
||||
// This case only occurs on 32-bit builds running atop WOW64.
|
||||
// (See https://bugzilla.mozilla.org/show_bug.cgi?id=1423999#c3)
|
||||
objDirInfo.reset(reinterpret_cast<OBJECT_DIRECTORY_INFORMATION*>(
|
||||
new char[returnedLen]));
|
||||
objDirInfoBufLen = returnedLen;
|
||||
continue;
|
||||
} else if (!NT_SUCCESS(ntStatus)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// NtQueryDirectoryObject gave us an array of OBJECT_DIRECTORY_INFORMATION
|
||||
// structures whose final entry is zeroed out.
|
||||
OBJECT_DIRECTORY_INFORMATION* curDir = objDirInfo.get();
|
||||
while (curDir->mName.Length && curDir->mTypeName.Length) {
|
||||
// We use nsDependentSubstring here because UNICODE_STRINGs are not
|
||||
// guaranteed to be null-terminated.
|
||||
nsDependentSubstring objName(curDir->mName.Buffer,
|
||||
curDir->mName.Length / sizeof(wchar_t));
|
||||
nsDependentSubstring typeName(curDir->mTypeName.Buffer,
|
||||
curDir->mTypeName.Length / sizeof(wchar_t));
|
||||
|
||||
if (!aComparator(objName, typeName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
++curDir;
|
||||
}
|
||||
|
||||
firstCall = FALSE;
|
||||
} while (ntStatus == STATUS_MORE_ENTRIES);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char* gBlockedUiaClients[] = {"osk.exe"};
|
||||
|
||||
static bool ShouldBlockUIAClient(nsIFile* aClientExe) {
|
||||
if (PR_GetEnv("MOZ_DISABLE_ACCESSIBLE_BLOCKLIST")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoString leafName;
|
||||
nsresult rv = aClientExe->GetLeafName(leafName);
|
||||
if (NS_FAILED(rv)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t index = 0, len = ArrayLength(gBlockedUiaClients); index < len;
|
||||
++index) {
|
||||
if (leafName.EqualsIgnoreCase(gBlockedUiaClients[index])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
|
||||
void Compatibility::GetUiaClientPids(nsTArray<DWORD>& aPids) {
|
||||
if (!::GetModuleHandleW(L"uiautomationcore.dll")) {
|
||||
// UIAutomationCore isn't loaded, so there is no UIA client.
|
||||
return;
|
||||
}
|
||||
Maybe<DWORD> Compatibility::sUiaRemotePid;
|
||||
|
||||
Maybe<bool> Compatibility::OnUIAMessage(WPARAM aWParam, LPARAM aLParam) {
|
||||
auto clearUiaRemotePid = MakeScopeExit([]() { sUiaRemotePid = Nothing(); });
|
||||
|
||||
Telemetry::AutoTimer<Telemetry::A11Y_UIA_DETECTION_TIMING_MS> timer;
|
||||
|
||||
// UIA creates a section containing the substring "HOOK_SHMEM_"
|
||||
constexpr auto kStrHookShmem = u"HOOK_SHMEM_"_ns;
|
||||
|
||||
// The section name always ends with this suffix, which is derived from the
|
||||
// current thread id and the UIA message's WPARAM and LPARAM.
|
||||
nsAutoString partialSectionSuffix;
|
||||
partialSectionSuffix.AppendPrintf("_%08lx_%08" PRIxLPTR "_%08zx",
|
||||
::GetCurrentThreadId(), aLParam, aWParam);
|
||||
|
||||
// Find any named Section that matches the naming convention of the UIA shared
|
||||
// memory.
|
||||
nsAutoHandle section;
|
||||
auto comparator = [&](const nsDependentSubstring& aName,
|
||||
const nsDependentSubstring& aType) -> bool {
|
||||
if (aType.Equals(u"Section"_ns) && FindInReadable(kStrHookShmem, aName) &&
|
||||
StringEndsWith(aName, partialSectionSuffix)) {
|
||||
section.own(::OpenFileMapping(GENERIC_READ, FALSE,
|
||||
PromiseFlatString(aName).get()));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!FindNamedObject(comparator) || !section) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
NTSTATUS ntStatus;
|
||||
|
||||
// First we must query for a list of all the open handles in the system.
|
||||
UniquePtr<std::byte[]> handleInfoBuf;
|
||||
UniquePtr<char[]> handleInfoBuf;
|
||||
ULONG handleInfoBufLen = sizeof(SYSTEM_HANDLE_INFORMATION_EX) +
|
||||
1024 * sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX);
|
||||
|
||||
|
@ -40,9 +211,9 @@ void Compatibility::GetUiaClientPids(nsTArray<DWORD>& aPids) {
|
|||
while (true) {
|
||||
// These allocations can be hundreds of megabytes on some computers, so
|
||||
// we should use fallible new here.
|
||||
handleInfoBuf = MakeUniqueFallible<std::byte[]>(handleInfoBufLen);
|
||||
handleInfoBuf = MakeUniqueFallible<char[]>(handleInfoBufLen);
|
||||
if (!handleInfoBuf) {
|
||||
return;
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
ntStatus = ::NtQuerySystemInformation(
|
||||
|
@ -53,56 +224,121 @@ void Compatibility::GetUiaClientPids(nsTArray<DWORD>& aPids) {
|
|||
}
|
||||
|
||||
if (!NT_SUCCESS(ntStatus)) {
|
||||
return;
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const DWORD ourPid = ::GetCurrentProcessId();
|
||||
Maybe<PVOID> kernelObject;
|
||||
static Maybe<USHORT> sectionObjTypeIndex;
|
||||
nsTHashSet<uint32_t> nonSectionObjTypes;
|
||||
nsTHashMap<nsVoidPtrHashKey, DWORD> objMap;
|
||||
|
||||
auto handleInfo =
|
||||
reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>(handleInfoBuf.get());
|
||||
|
||||
for (ULONG index = 0; index < handleInfo->mHandleCount; ++index) {
|
||||
SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX& curHandle = handleInfo->mHandles[index];
|
||||
if (curHandle.mPid != ourPid) {
|
||||
// We're only interested in handles in our own process.
|
||||
continue;
|
||||
}
|
||||
|
||||
HANDLE handle = reinterpret_cast<HANDLE>(curHandle.mHandle);
|
||||
|
||||
// Get the name of the handle.
|
||||
ULONG objNameBufLen;
|
||||
ntStatus =
|
||||
::NtQueryObject(handle, (OBJECT_INFORMATION_CLASS)ObjectNameInformation,
|
||||
nullptr, 0, &objNameBufLen);
|
||||
if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) {
|
||||
// The mapping of the curHandle.mObjectTypeIndex field depends on the
|
||||
// underlying OS kernel. As we scan through the handle list, we record the
|
||||
// type indices such that we may use those values to skip over handles that
|
||||
// refer to non-section objects.
|
||||
if (sectionObjTypeIndex) {
|
||||
// If we know the type index for Sections, that's the fastest check...
|
||||
if (sectionObjTypeIndex.value() != curHandle.mObjectTypeIndex) {
|
||||
// Not a section
|
||||
continue;
|
||||
}
|
||||
} else if (nonSectionObjTypes.Contains(
|
||||
static_cast<uint32_t>(curHandle.mObjectTypeIndex))) {
|
||||
// Otherwise we check whether or not the object type is definitely _not_
|
||||
// a Section...
|
||||
continue;
|
||||
}
|
||||
auto objNameBuf = MakeUnique<std::byte[]>(objNameBufLen);
|
||||
ntStatus =
|
||||
::NtQueryObject(handle, (OBJECT_INFORMATION_CLASS)ObjectNameInformation,
|
||||
objNameBuf.get(), objNameBufLen, &objNameBufLen);
|
||||
if (!NT_SUCCESS(ntStatus)) {
|
||||
continue;
|
||||
}
|
||||
auto objNameInfo =
|
||||
reinterpret_cast<OBJECT_NAME_INFORMATION*>(objNameBuf.get());
|
||||
if (!objNameInfo->mName.Length) {
|
||||
continue;
|
||||
}
|
||||
nsDependentString objName(objNameInfo->mName.Buffer,
|
||||
objNameInfo->mName.Length / sizeof(wchar_t));
|
||||
} else if (ourPid == curHandle.mPid) {
|
||||
// Otherwise we need to issue some system calls to find out the object
|
||||
// type corresponding to the current handle's type index.
|
||||
ULONG objTypeBufLen;
|
||||
ntStatus = ::NtQueryObject(handle, ObjectTypeInformation, nullptr, 0,
|
||||
&objTypeBufLen);
|
||||
if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// UIA creates a named pipe between the client and server processes. Find
|
||||
// our handle to that pipe (if any).
|
||||
if (StringBeginsWith(objName, u"\\Device\\NamedPipe\\UIA_PIPE_"_ns)) {
|
||||
// Get the process id of the remote end. Counter-intuitively, for this
|
||||
// pipe, we're the client and the remote process is the server.
|
||||
ULONG pid = 0;
|
||||
::GetNamedPipeServerProcessId(handle, &pid);
|
||||
aPids.AppendElement(pid);
|
||||
auto objTypeBuf = MakeUnique<char[]>(objTypeBufLen);
|
||||
ntStatus =
|
||||
::NtQueryObject(handle, ObjectTypeInformation, objTypeBuf.get(),
|
||||
objTypeBufLen, &objTypeBufLen);
|
||||
if (!NT_SUCCESS(ntStatus)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto objType =
|
||||
reinterpret_cast<PUBLIC_OBJECT_TYPE_INFORMATION*>(objTypeBuf.get());
|
||||
|
||||
// Now we check whether the object's type name matches "Section"
|
||||
nsDependentSubstring objTypeName(
|
||||
objType->TypeName.Buffer, objType->TypeName.Length / sizeof(wchar_t));
|
||||
if (!objTypeName.Equals(u"Section"_ns)) {
|
||||
nonSectionObjTypes.Insert(
|
||||
static_cast<uint32_t>(curHandle.mObjectTypeIndex));
|
||||
continue;
|
||||
}
|
||||
|
||||
sectionObjTypeIndex = Some(curHandle.mObjectTypeIndex);
|
||||
}
|
||||
|
||||
// At this point we know that curHandle references a Section object.
|
||||
// Now we can do some actual tests on it.
|
||||
|
||||
if (ourPid != curHandle.mPid) {
|
||||
if (kernelObject && kernelObject.value() == curHandle.mObject) {
|
||||
// The kernel objects match -- we have found the remote pid!
|
||||
sUiaRemotePid = Some(curHandle.mPid);
|
||||
break;
|
||||
}
|
||||
|
||||
// An object that is not ours. Since we do not yet know which kernel
|
||||
// object we're interested in, we'll save the current object for later.
|
||||
objMap.InsertOrUpdate(curHandle.mObject, curHandle.mPid);
|
||||
} else if (handle == section.get()) {
|
||||
// This is the file mapping that we opened above. We save this mObject
|
||||
// in order to compare to Section objects opened by other processes.
|
||||
kernelObject = Some(curHandle.mObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (!kernelObject) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
if (!sUiaRemotePid) {
|
||||
// We found kernelObject *after* we saw the remote process's copy. Now we
|
||||
// must look it up in objMap.
|
||||
DWORD pid;
|
||||
if (objMap.Get(kernelObject.value(), &pid)) {
|
||||
sUiaRemotePid = Some(pid);
|
||||
}
|
||||
}
|
||||
|
||||
if (!sUiaRemotePid) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
a11y::SetInstantiator(sUiaRemotePid.value());
|
||||
|
||||
// Block if necessary
|
||||
nsCOMPtr<nsIFile> instantiator;
|
||||
if (a11y::GetInstantiator(getter_AddRefs(instantiator)) &&
|
||||
ShouldBlockUIAClient(instantiator)) {
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
return Some(true);
|
||||
}
|
||||
|
||||
} // namespace a11y
|
||||
|
|
|
@ -39,8 +39,6 @@ namespace a11y {
|
|||
static const wchar_t kLazyInstantiatorProp[] =
|
||||
L"mozilla::a11y::LazyInstantiator";
|
||||
|
||||
Maybe<bool> LazyInstantiator::sShouldBlockUia;
|
||||
|
||||
/* static */
|
||||
already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) {
|
||||
RefPtr<IAccessible> result;
|
||||
|
@ -152,15 +150,16 @@ void LazyInstantiator::ClearProp() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the process id of a remote (out-of-process) MSAA/IA2 client.
|
||||
* Given the remote client's thread ID, resolve its process ID.
|
||||
*/
|
||||
DWORD LazyInstantiator::GetRemoteMsaaClientPid() {
|
||||
DWORD
|
||||
LazyInstantiator::GetClientPid(const DWORD aClientTid) {
|
||||
nsAutoHandle callingThread(
|
||||
::OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE,
|
||||
mscom::ProcessRuntime::GetClientThreadId()));
|
||||
::OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, aClientTid));
|
||||
if (!callingThread) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ::GetProcessIdOfThread(callingThread);
|
||||
}
|
||||
|
||||
|
@ -171,7 +170,6 @@ 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
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -186,6 +184,11 @@ bool LazyInstantiator::IsBlockedInjection() {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (Compatibility::HasKnownNonUiaConsumer()) {
|
||||
// If we already see a known AT, don't block a11y instantiation
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t index = 0, len = ArrayLength(gBlockedInprocDlls); index < len;
|
||||
++index) {
|
||||
const DllBlockInfo& blockedDll = gBlockedInprocDlls[index];
|
||||
|
@ -203,14 +206,30 @@ bool LazyInstantiator::IsBlockedInjection() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Given a remote client's process ID, determine whether we should proceed with
|
||||
* Given a remote client's thread 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);
|
||||
bool LazyInstantiator::ShouldInstantiate(const DWORD aClientTid) {
|
||||
if (Compatibility::IsA11ySuppressedForClipboardCopy()) {
|
||||
// Bug 1774285: Windows Suggested Actions (introduced in Windows 11 22H2)
|
||||
// walks the entire a11y tree using UIA whenever anything is copied to the
|
||||
// clipboard. This causes an unacceptable hang, particularly when the cache
|
||||
// is disabled. Don't allow a11y to be instantiated by this.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!aClientTid) {
|
||||
// aClientTid == 0 implies that this is either an in-process call, or else
|
||||
// we failed to retrieve information about the remote caller.
|
||||
// We should always default to instantiating a11y in this case, provided
|
||||
// that we don't see any known bad injected DLLs.
|
||||
return !IsBlockedInjection();
|
||||
}
|
||||
|
||||
a11y::SetInstantiator(GetClientPid(aClientTid));
|
||||
|
||||
nsCOMPtr<nsIFile> clientExe;
|
||||
if (!a11y::GetInstantiator(getter_AddRefs(clientExe))) {
|
||||
|
@ -236,57 +255,6 @@ bool LazyInstantiator::ShouldInstantiate(const DWORD aClientPid) {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether we should proceed with a11y instantiation, considering the
|
||||
* various different types of clients.
|
||||
*/
|
||||
bool LazyInstantiator::ShouldInstantiate() {
|
||||
if (Compatibility::IsA11ySuppressedForClipboardCopy()) {
|
||||
// Bug 1774285: Windows Suggested Actions (introduced in Windows 11 22H2)
|
||||
// walks the entire a11y tree using UIA whenever anything is copied to the
|
||||
// clipboard. This causes an unacceptable hang, particularly when the cache
|
||||
// is disabled. Don't allow a11y to be instantiated by this.
|
||||
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)) {
|
||||
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()) {
|
||||
|
@ -346,7 +314,8 @@ LazyInstantiator::MaybeResolveRoot() {
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
if (GetAccService() || ShouldInstantiate()) {
|
||||
if (GetAccService() ||
|
||||
ShouldInstantiate(mscom::ProcessRuntime::GetClientThreadId())) {
|
||||
mWeakMsaaRoot = ResolveMsaaRoot();
|
||||
if (!mWeakMsaaRoot) {
|
||||
return E_POINTER;
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#define mozilla_a11y_LazyInstantiator_h
|
||||
|
||||
#include "IUnknownImpl.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsString.h"
|
||||
|
||||
|
@ -83,25 +82,14 @@ class LazyInstantiator final : public IAccessible, public IServiceProvider {
|
|||
STDMETHODIMP QueryService(REFGUID aServiceId, REFIID aServiceIid,
|
||||
void** aOutInterface) override;
|
||||
|
||||
/**
|
||||
* We cache the result of UIA detection because it could be expensive if a
|
||||
* client repeatedly queries us. This function is called to reset that cache
|
||||
* when one of our windows comes to the foreground. If there is a new UIA
|
||||
* client that isn't blocked, instantiation will subsequently be allowed. The
|
||||
* hope is that a user will probably need to switch apps in order to start a
|
||||
* new client.
|
||||
*/
|
||||
static void ResetUiaDetectionCache() { sShouldBlockUia = Nothing(); }
|
||||
|
||||
private:
|
||||
explicit LazyInstantiator(HWND aHwnd);
|
||||
~LazyInstantiator();
|
||||
|
||||
bool IsBlockedInjection();
|
||||
bool ShouldInstantiate(const DWORD aClientPid);
|
||||
bool ShouldInstantiate();
|
||||
bool ShouldInstantiate(const DWORD aClientTid);
|
||||
|
||||
DWORD GetRemoteMsaaClientPid();
|
||||
DWORD GetClientPid(const DWORD aClientTid);
|
||||
|
||||
/**
|
||||
* @return S_OK if we have a valid mRealRoot to invoke methods on
|
||||
|
@ -133,7 +121,6 @@ class LazyInstantiator final : public IAccessible, public IServiceProvider {
|
|||
MsaaRootAccessible* mWeakMsaaRoot;
|
||||
IAccessible* mWeakAccessible;
|
||||
IDispatch* mWeakDispatch;
|
||||
static Maybe<bool> sShouldBlockUia;
|
||||
};
|
||||
|
||||
} // namespace a11y
|
||||
|
|
|
@ -390,6 +390,79 @@ nsAppShell::~nsAppShell() {
|
|||
sOutstandingNativeEventCallbacks = 0;
|
||||
}
|
||||
|
||||
#if defined(ACCESSIBILITY)
|
||||
|
||||
static ULONG gUiaMsg;
|
||||
static HHOOK gUiaHook;
|
||||
static uint32_t gUiaAttempts;
|
||||
static const uint32_t kMaxUiaAttempts = 5;
|
||||
|
||||
static void InitUIADetection();
|
||||
|
||||
static LRESULT CALLBACK UiaHookProc(int aCode, WPARAM aWParam, LPARAM aLParam) {
|
||||
if (aCode < 0) {
|
||||
return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
|
||||
}
|
||||
|
||||
auto cwp = reinterpret_cast<CWPSTRUCT*>(aLParam);
|
||||
if (gUiaMsg && cwp->message == gUiaMsg) {
|
||||
if (gUiaAttempts < kMaxUiaAttempts) {
|
||||
++gUiaAttempts;
|
||||
|
||||
Maybe<bool> shouldCallNextHook =
|
||||
a11y::Compatibility::OnUIAMessage(cwp->wParam, cwp->lParam);
|
||||
if (shouldCallNextHook.isSome()) {
|
||||
// We've got an instantiator.
|
||||
if (!shouldCallNextHook.value()) {
|
||||
// We're blocking this instantiation. We need to keep this hook set
|
||||
// so that we can catch any future instantiation attempts.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We're allowing the instantiator to proceed, so this hook is no longer
|
||||
// needed.
|
||||
if (::UnhookWindowsHookEx(gUiaHook)) {
|
||||
gUiaHook = nullptr;
|
||||
}
|
||||
} else {
|
||||
// Our hook might be firing after UIA; let's try reinstalling ourselves.
|
||||
InitUIADetection();
|
||||
}
|
||||
} else {
|
||||
// We've maxed out our attempts. Let's unhook.
|
||||
if (::UnhookWindowsHookEx(gUiaHook)) {
|
||||
gUiaHook = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
|
||||
}
|
||||
|
||||
static void InitUIADetection() {
|
||||
if (gUiaHook) {
|
||||
// In this case we want to re-hook so that the hook is always called ahead
|
||||
// of UIA's hook.
|
||||
if (::UnhookWindowsHookEx(gUiaHook)) {
|
||||
gUiaHook = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gUiaMsg) {
|
||||
// This is the message that UIA sends to trigger a command. UIA's
|
||||
// CallWndProc looks for this message and then handles the request.
|
||||
// Our hook gets in front of UIA's hook and examines the message first.
|
||||
gUiaMsg = ::RegisterWindowMessageW(L"HOOKUTIL_MSG");
|
||||
}
|
||||
|
||||
if (!gUiaHook) {
|
||||
gUiaHook = ::SetWindowsHookEx(WH_CALLWNDPROC, &UiaHookProc, nullptr,
|
||||
::GetCurrentThreadId());
|
||||
}
|
||||
}
|
||||
|
||||
#endif // defined(ACCESSIBILITY)
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAppShell::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
const char16_t* aData) {
|
||||
|
@ -397,6 +470,25 @@ nsAppShell::Observe(nsISupports* aSubject, const char* aTopic,
|
|||
nsCOMPtr<nsIObserverService> obsServ(
|
||||
mozilla::services::GetObserverService());
|
||||
|
||||
#if defined(ACCESSIBILITY)
|
||||
if (!strcmp(aTopic, "dll-loaded-main-thread")) {
|
||||
if (a11y::PlatformDisabledState() != a11y::ePlatformIsDisabled &&
|
||||
!gUiaHook) {
|
||||
nsDependentString dllName(aData);
|
||||
|
||||
if (StringEndsWith(dllName, u"uiautomationcore.dll"_ns,
|
||||
nsCaseInsensitiveStringComparator)) {
|
||||
InitUIADetection();
|
||||
|
||||
// Now that we've handled the observer notification, we can remove it
|
||||
obsServ->RemoveObserver(this, "dll-loaded-main-thread");
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
#endif // defined(ACCESSIBILITY)
|
||||
|
||||
if (!strcmp(aTopic, "sessionstore-restoring-on-startup")) {
|
||||
nsWindow::SetIsRestoringSession(true);
|
||||
// Now that we've handled the observer notification, we can remove it
|
||||
|
@ -488,6 +580,14 @@ nsresult nsAppShell::Init() {
|
|||
|
||||
obsServ->AddObserver(this, "sessionstore-restoring-on-startup", false);
|
||||
obsServ->AddObserver(this, "sessionstore-windows-restored", false);
|
||||
|
||||
#if defined(ACCESSIBILITY)
|
||||
if (::GetModuleHandleW(L"uiautomationcore.dll")) {
|
||||
InitUIADetection();
|
||||
} else {
|
||||
obsServ->AddObserver(this, "dll-loaded-main-thread", false);
|
||||
}
|
||||
#endif // defined(ACCESSIBILITY)
|
||||
}
|
||||
|
||||
if (!WinUtils::GetTimezoneName(mTimezoneName)) {
|
||||
|
@ -530,6 +630,23 @@ nsAppShell::Run(void) {
|
|||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAppShell::Exit(void) {
|
||||
#if defined(ACCESSIBILITY)
|
||||
if (XRE_IsParentProcess()) {
|
||||
nsCOMPtr<nsIObserverService> obsServ(
|
||||
mozilla::services::GetObserverService());
|
||||
obsServ->RemoveObserver(this, "dll-loaded-main-thread");
|
||||
|
||||
if (gUiaHook && ::UnhookWindowsHookEx(gUiaHook)) {
|
||||
gUiaHook = nullptr;
|
||||
}
|
||||
}
|
||||
#endif // defined(ACCESSIBILITY)
|
||||
|
||||
return nsBaseAppShell::Exit();
|
||||
}
|
||||
|
||||
void nsAppShell::DoProcessMoreGeckoEvents() {
|
||||
// Called by nsBaseAppShell's NativeEventCallback() after it has finished
|
||||
// processing pending gecko events and there are still gecko events pending
|
||||
|
|
|
@ -39,6 +39,7 @@ class nsAppShell : public nsBaseAppShell {
|
|||
|
||||
protected:
|
||||
NS_IMETHOD Run() override;
|
||||
NS_IMETHOD Exit() override;
|
||||
NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
|
||||
const char16_t* aData) override;
|
||||
|
||||
|
|
|
@ -6132,10 +6132,6 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam,
|
|||
DispatchInputEvent(&event);
|
||||
if (sSwitchKeyboardLayout && mLastKeyboardLayout)
|
||||
ActivateKeyboardLayout(mLastKeyboardLayout, 0);
|
||||
|
||||
#ifdef ACCESSIBILITY
|
||||
a11y::LazyInstantiator::ResetUiaDetectionCache();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
|
Загрузка…
Ссылка в новой задаче