From 1cfd3a3bc76a8d09cb4266576f30802753a80c83 Mon Sep 17 00:00:00 2001 From: Iulian Moraru Date: Thu, 13 Jul 2023 07:35:44 +0300 Subject: [PATCH] Backed out changeset 0724fde82b2f (bug 1838123) for causing build bustages. CLOSED TREE --- accessible/windows/msaa/Compatibility.h | 7 +- accessible/windows/msaa/CompatibilityUIA.cpp | 318 +++++++++++++++++++++++---- accessible/windows/msaa/LazyInstantiator.cpp | 93 +++----- accessible/windows/msaa/LazyInstantiator.h | 17 +- widget/windows/nsAppShell.cpp | 117 ++++++++++ widget/windows/nsAppShell.h | 1 + widget/windows/nsWindow.cpp | 4 - 7 files changed, 433 insertions(+), 124 deletions(-) diff --git a/accessible/windows/msaa/Compatibility.h b/accessible/windows/msaa/Compatibility.h index d1e1b0e9acc0..e5c03bfea17a 100644 --- a/accessible/windows/msaa/Compatibility.h +++ b/accessible/windows/msaa/Compatibility.h @@ -8,7 +8,7 @@ #define COMPATIBILITY_MANAGER_H #include -#include "nsTArray.h" +#include "mozilla/Maybe.h" #include "nsString.h" #include @@ -60,7 +60,9 @@ class Compatibility { */ static void Init(); - static void GetUiaClientPids(nsTArray& aPids); + static Maybe OnUIAMessage(WPARAM aWParam, LPARAM aLParam); + + static Maybe 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 sUiaRemotePid; }; } // namespace a11y diff --git a/accessible/windows/msaa/CompatibilityUIA.cpp b/accessible/windows/msaa/CompatibilityUIA.cpp index b59293164a7d..83e1c9fdda81 100644 --- a/accessible/windows/msaa/CompatibilityUIA.cpp +++ b/accessible/windows/msaa/CompatibilityUIA.cpp @@ -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(aBuf); } +}; + +typedef UniquePtr ObjDirInfoPtr; + +// ComparatorFnT returns true to continue searching, or else false to indicate +// search completion. +template +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( + 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( + 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& aPids) { - if (!::GetModuleHandleW(L"uiautomationcore.dll")) { - // UIAutomationCore isn't loaded, so there is no UIA client. - return; - } +Maybe Compatibility::sUiaRemotePid; + +Maybe Compatibility::OnUIAMessage(WPARAM aWParam, LPARAM aLParam) { + auto clearUiaRemotePid = MakeScopeExit([]() { sUiaRemotePid = Nothing(); }); + Telemetry::AutoTimer 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 handleInfoBuf; + UniquePtr handleInfoBuf; ULONG handleInfoBufLen = sizeof(SYSTEM_HANDLE_INFORMATION_EX) + 1024 * sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX); @@ -40,9 +211,9 @@ void Compatibility::GetUiaClientPids(nsTArray& aPids) { while (true) { // These allocations can be hundreds of megabytes on some computers, so // we should use fallible new here. - handleInfoBuf = MakeUniqueFallible(handleInfoBufLen); + handleInfoBuf = MakeUniqueFallible(handleInfoBufLen); if (!handleInfoBuf) { - return; + return Nothing(); } ntStatus = ::NtQuerySystemInformation( @@ -53,56 +224,121 @@ void Compatibility::GetUiaClientPids(nsTArray& aPids) { } if (!NT_SUCCESS(ntStatus)) { - return; + return Nothing(); } break; } const DWORD ourPid = ::GetCurrentProcessId(); + Maybe kernelObject; + static Maybe sectionObjTypeIndex; + nsTHashSet nonSectionObjTypes; + nsTHashMap objMap; + auto handleInfo = reinterpret_cast(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(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) { - continue; - } - auto objNameBuf = MakeUnique(objNameBufLen); - ntStatus = - ::NtQueryObject(handle, (OBJECT_INFORMATION_CLASS)ObjectNameInformation, - objNameBuf.get(), objNameBufLen, &objNameBufLen); - if (!NT_SUCCESS(ntStatus)) { + // 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(curHandle.mObjectTypeIndex))) { + // Otherwise we check whether or not the object type is definitely _not_ + // a Section... 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(objTypeBufLen); + ntStatus = + ::NtQueryObject(handle, ObjectTypeInformation, objTypeBuf.get(), + objTypeBufLen, &objTypeBufLen); + if (!NT_SUCCESS(ntStatus)) { + continue; + } + + auto objType = + reinterpret_cast(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(curHandle.mObjectTypeIndex)); + continue; + } + + sectionObjTypeIndex = Some(curHandle.mObjectTypeIndex); } - auto objNameInfo = - reinterpret_cast(objNameBuf.get()); - if (!objNameInfo->mName.Length) { - continue; + + // 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); } - nsDependentString objName(objNameInfo->mName.Buffer, - objNameInfo->mName.Length / sizeof(wchar_t)); - - // 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 instantiator; + if (a11y::GetInstantiator(getter_AddRefs(instantiator)) && + ShouldBlockUIAClient(instantiator)) { + return Some(false); + } + + return Some(true); } } // namespace a11y diff --git a/accessible/windows/msaa/LazyInstantiator.cpp b/accessible/windows/msaa/LazyInstantiator.cpp index 66e7af387eab..6da9463f4dc3 100644 --- a/accessible/windows/msaa/LazyInstantiator.cpp +++ b/accessible/windows/msaa/LazyInstantiator.cpp @@ -39,8 +39,6 @@ namespace a11y { static const wchar_t kLazyInstantiatorProp[] = L"mozilla::a11y::LazyInstantiator"; -Maybe LazyInstantiator::sShouldBlockUia; - /* static */ already_AddRefed LazyInstantiator::GetRootAccessible(HWND aHwnd) { RefPtr 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 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 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; diff --git a/accessible/windows/msaa/LazyInstantiator.h b/accessible/windows/msaa/LazyInstantiator.h index 00fa4ba6eddc..e632a247457b 100644 --- a/accessible/windows/msaa/LazyInstantiator.h +++ b/accessible/windows/msaa/LazyInstantiator.h @@ -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 sShouldBlockUia; }; } // namespace a11y diff --git a/widget/windows/nsAppShell.cpp b/widget/windows/nsAppShell.cpp index 34e3771a314d..873178bcde8a 100644 --- a/widget/windows/nsAppShell.cpp +++ b/widget/windows/nsAppShell.cpp @@ -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(aLParam); + if (gUiaMsg && cwp->message == gUiaMsg) { + if (gUiaAttempts < kMaxUiaAttempts) { + ++gUiaAttempts; + + Maybe 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 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 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 diff --git a/widget/windows/nsAppShell.h b/widget/windows/nsAppShell.h index 239aedd01a32..e9bc87a3666b 100644 --- a/widget/windows/nsAppShell.h +++ b/widget/windows/nsAppShell.h @@ -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; diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp index 04c0c940c784..a5f527316832 100644 --- a/widget/windows/nsWindow.cpp +++ b/widget/windows/nsWindow.cpp @@ -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; -- 2.11.4.GIT