зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1838123: Update UI Automation client detection to work with modern versions of Windows. r=nlapre,handyman
The window message we previously hooked no longer gets sent and the associated shared memory no longer seems to be created either. Also, we don't seem to be notified about the load of UIAutomationCore.dll until after it has already instantiated a11y, which is obviously too late for us to hook anything. Instead, we block UIA instantiation via LazyInstantiator, just as we do for MSAA/IA2: 1. Refactor CompatibilityUIA so that rather than being called by a hook, it simply allows the caller to query the process ids of any UIA clients. 2. CompatibilityUIA now searches handles in our process for named pipes created by UIA for communication with the remote process and then uses GetNamedPipeServerProcessId to get the process id on the other end of each pipe. 3. Refactor LazyInstantiator so that it first tries to get the MSAA/IA2 client process id, then calls CompatibilityUIA to get any UIA client process ids. 4. LazyInstantiator now handles setting the instantiator and blocking of clients for UIA as well as MSAA/IA2 using the same code. 5. Because UIA client detection can be expensive if clients repeatedly query us, cache the result. Reset that cache only when one of our windows comes to the foreground. Differential Revision: https://phabricator.services.mozilla.com/D181958
This commit is contained in:
Родитель
b317dfc975
Коммит
b9171ec2f1
|
@ -8,7 +8,7 @@
|
|||
#define COMPATIBILITY_MANAGER_H
|
||||
|
||||
#include <windows.h>
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsString.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
@ -60,9 +60,7 @@ class Compatibility {
|
|||
*/
|
||||
static void Init();
|
||||
|
||||
static Maybe<bool> OnUIAMessage(WPARAM aWParam, LPARAM aLParam);
|
||||
|
||||
static Maybe<DWORD> GetUiaRemotePid() { return sUiaRemotePid; }
|
||||
static void GetUiaClientPids(nsTArray<DWORD>& aPids);
|
||||
|
||||
/**
|
||||
* return true if a known, non-UIA a11y consumer is present
|
||||
|
@ -112,7 +110,6 @@ class Compatibility {
|
|||
|
||||
private:
|
||||
static uint32_t sConsumers;
|
||||
static Maybe<DWORD> sUiaRemotePid;
|
||||
};
|
||||
|
||||
} // namespace a11y
|
||||
|
|
|
@ -7,201 +7,30 @@
|
|||
#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 {
|
||||
|
||||
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();
|
||||
void Compatibility::GetUiaClientPids(nsTArray<DWORD>& aPids) {
|
||||
if (!::GetModuleHandleW(L"uiautomationcore.dll")) {
|
||||
// UIAutomationCore isn't loaded, so there is no UIA client.
|
||||
return;
|
||||
}
|
||||
Telemetry::AutoTimer<Telemetry::A11Y_UIA_DETECTION_TIMING_MS> timer;
|
||||
|
||||
NTSTATUS ntStatus;
|
||||
|
||||
// First we must query for a list of all the open handles in the system.
|
||||
UniquePtr<char[]> handleInfoBuf;
|
||||
UniquePtr<std::byte[]> handleInfoBuf;
|
||||
ULONG handleInfoBufLen = sizeof(SYSTEM_HANDLE_INFORMATION_EX) +
|
||||
1024 * sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX);
|
||||
|
||||
|
@ -211,9 +40,9 @@ Maybe<bool> Compatibility::OnUIAMessage(WPARAM aWParam, LPARAM aLParam) {
|
|||
while (true) {
|
||||
// These allocations can be hundreds of megabytes on some computers, so
|
||||
// we should use fallible new here.
|
||||
handleInfoBuf = MakeUniqueFallible<char[]>(handleInfoBufLen);
|
||||
handleInfoBuf = MakeUniqueFallible<std::byte[]>(handleInfoBufLen);
|
||||
if (!handleInfoBuf) {
|
||||
return Nothing();
|
||||
return;
|
||||
}
|
||||
|
||||
ntStatus = ::NtQuerySystemInformation(
|
||||
|
@ -224,121 +53,56 @@ Maybe<bool> Compatibility::OnUIAMessage(WPARAM aWParam, LPARAM aLParam) {
|
|||
}
|
||||
|
||||
if (!NT_SUCCESS(ntStatus)) {
|
||||
return Nothing();
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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...
|
||||
// Get the name of the handle.
|
||||
ULONG objNameBufLen;
|
||||
ntStatus =
|
||||
::NtQueryObject(handle, (OBJECT_INFORMATION_CLASS)ObjectNameInformation,
|
||||
nullptr, 0, &objNameBufLen);
|
||||
if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) {
|
||||
continue;
|
||||
} 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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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));
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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,6 +39,8 @@ 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;
|
||||
|
@ -150,16 +152,15 @@ void LazyInstantiator::ClearProp() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Given the remote client's thread ID, resolve its process ID.
|
||||
* Get the process id of a remote (out-of-process) MSAA/IA2 client.
|
||||
*/
|
||||
DWORD
|
||||
LazyInstantiator::GetClientPid(const DWORD aClientTid) {
|
||||
DWORD LazyInstantiator::GetRemoteMsaaClientPid() {
|
||||
nsAutoHandle callingThread(
|
||||
::OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, aClientTid));
|
||||
::OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE,
|
||||
mscom::ProcessRuntime::GetClientThreadId()));
|
||||
if (!callingThread) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ::GetProcessIdOfThread(callingThread);
|
||||
}
|
||||
|
||||
|
@ -170,6 +171,7 @@ 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
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -184,11 +186,6 @@ 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];
|
||||
|
@ -206,30 +203,14 @@ bool LazyInstantiator::IsBlockedInjection() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Given a remote client's thread ID, determine whether we should proceed with
|
||||
* 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 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));
|
||||
bool LazyInstantiator::ShouldInstantiate(const DWORD aClientPid) {
|
||||
a11y::SetInstantiator(aClientPid);
|
||||
|
||||
nsCOMPtr<nsIFile> clientExe;
|
||||
if (!a11y::GetInstantiator(getter_AddRefs(clientExe))) {
|
||||
|
@ -255,6 +236,57 @@ bool LazyInstantiator::ShouldInstantiate(const DWORD aClientTid) {
|
|||
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()) {
|
||||
|
@ -314,8 +346,7 @@ LazyInstantiator::MaybeResolveRoot() {
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
if (GetAccService() ||
|
||||
ShouldInstantiate(mscom::ProcessRuntime::GetClientThreadId())) {
|
||||
if (GetAccService() || ShouldInstantiate()) {
|
||||
mWeakMsaaRoot = ResolveMsaaRoot();
|
||||
if (!mWeakMsaaRoot) {
|
||||
return E_POINTER;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define mozilla_a11y_LazyInstantiator_h
|
||||
|
||||
#include "IUnknownImpl.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsString.h"
|
||||
|
||||
|
@ -82,14 +83,25 @@ 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 aClientTid);
|
||||
bool ShouldInstantiate(const DWORD aClientPid);
|
||||
bool ShouldInstantiate();
|
||||
|
||||
DWORD GetClientPid(const DWORD aClientTid);
|
||||
DWORD GetRemoteMsaaClientPid();
|
||||
|
||||
/**
|
||||
* @return S_OK if we have a valid mRealRoot to invoke methods on
|
||||
|
@ -121,6 +133,7 @@ class LazyInstantiator final : public IAccessible, public IServiceProvider {
|
|||
MsaaRootAccessible* mWeakMsaaRoot;
|
||||
IAccessible* mWeakAccessible;
|
||||
IDispatch* mWeakDispatch;
|
||||
static Maybe<bool> sShouldBlockUia;
|
||||
};
|
||||
|
||||
} // namespace a11y
|
||||
|
|
|
@ -390,79 +390,6 @@ 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) {
|
||||
|
@ -470,25 +397,6 @@ 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
|
||||
|
@ -580,14 +488,6 @@ 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)) {
|
||||
|
@ -630,23 +530,6 @@ 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,7 +39,6 @@ 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,6 +6132,10 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam,
|
|||
DispatchInputEvent(&event);
|
||||
if (sSwitchKeyboardLayout && mLastKeyboardLayout)
|
||||
ActivateKeyboardLayout(mLastKeyboardLayout, 0);
|
||||
|
||||
#ifdef ACCESSIBILITY
|
||||
a11y::LazyInstantiator::ResetUiaDetectionCache();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
|
Загрузка…
Ссылка в новой задаче