Bug 1419886: Part 2 - Add UIA detection to a11y and centralize a11y instantiator telemetry under a11y::SetInstantiator function; r=Jamie
authorAaron Klotz <aklotz@mozilla.com>
Mon, 04 Dec 2017 17:56:31 -0700
changeset 395459 da4106e06989ab988a2ed112920aaad5aec0e670
parent 395458 3606176ddf2fc9de85e6932af7380cb2142349cc
child 395460 affa7f97ff916198343f2ead9a5c3362efd70c20
push id33042
push userbtara@mozilla.com
push dateThu, 07 Dec 2017 13:50:03 +0000
treeherdermozilla-central@e30c06a1074c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersJamie
bugs1419886
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1419886: Part 2 - Add UIA detection to a11y and centralize a11y instantiator telemetry under a11y::SetInstantiator function; r=Jamie MozReview-Commit-ID: 11QN7amImK9
accessible/base/Platform.h
accessible/windows/msaa/Compatibility.h
accessible/windows/msaa/CompatibilityUIA.cpp
accessible/windows/msaa/LazyInstantiator.cpp
accessible/windows/msaa/LazyInstantiator.h
accessible/windows/msaa/NtUndoc.h
accessible/windows/msaa/Platform.cpp
accessible/windows/msaa/moz.build
toolkit/xre/nsAppRunner.cpp
--- a/accessible/base/Platform.h
+++ b/accessible/base/Platform.h
@@ -47,18 +47,18 @@ bool ShouldA11yBeEnabled();
 /*
  * Do we have AccessibleHandler.dll registered.
  */
 bool IsHandlerRegistered();
 
 /*
  * Name of platform service that instantiated accessibility
  */
-void SetInstantiator(const nsAString& aInstantiator);
-bool GetInstantiator(nsAString& aInstantiator);
+void SetInstantiator(const uint32_t aInstantiatorPid);
+bool GetInstantiator(nsIFile** aOutInstantiator);
 #endif
 
 /**
  * Called to initialize platform specific accessibility support.
  * Note this is called after internal accessibility support is initialized.
  */
 void PlatformInit();
 
--- a/accessible/windows/msaa/Compatibility.h
+++ b/accessible/windows/msaa/Compatibility.h
@@ -2,16 +2,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef COMPATIBILITY_MANAGER_H
 #define COMPATIBILITY_MANAGER_H
 
+#include "mozilla/Maybe.h"
 #include "nsString.h"
 #include <stdint.h>
 
 namespace mozilla {
 namespace a11y {
 
 /**
  * Used to get compatibility modes. Note, modes are computed at accessibility
@@ -52,16 +53,20 @@ public:
    */
   static void GetHumanReadableConsumersStr(nsAString &aResult);
 
   /**
    * Initialize compatibility mode information.
    */
   static void Init();
 
+  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
    */
   static bool HasKnownNonUiaConsumer();
 
 private:
   Compatibility();
   Compatibility(const Compatibility&);
@@ -85,14 +90,15 @@ private:
     YOUDAO = 1 << 9,
     UNKNOWN = 1 << 10,
     UIAUTOMATION = 1 << 11
   };
   #define CONSUMERS_ENUM_LEN 12
 
 private:
   static uint32_t sConsumers;
+  static Maybe<DWORD> sUiaRemotePid;
 };
 
 } // a11y namespace
 } // mozilla namespace
 
 #endif
new file mode 100644
--- /dev/null
+++ b/accessible/windows/msaa/CompatibilityUIA.cpp
@@ -0,0 +1,293 @@
+/* -*- 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 "Compatibility.h"
+
+#include "mozilla/Telemetry.h"
+
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsUnicharUtils.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)
+
+static bool
+GetLocalObjectHandle(DWORD aSrcPid, HANDLE aSrcHandle, nsAutoHandle& aProcess,
+                     nsAutoHandle& aLocal)
+{
+  aLocal.reset();
+
+  if (!aProcess) {
+    HANDLE rawProcess = ::OpenProcess(PROCESS_DUP_HANDLE, FALSE, aSrcPid);
+    if (!rawProcess) {
+      LOG_ERROR(OpenProcess);
+      return false;
+    }
+
+    aProcess.own(rawProcess);
+  }
+
+  HANDLE rawDuped;
+  if (!::DuplicateHandle(aProcess.get(), aSrcHandle, ::GetCurrentProcess(),
+                         &rawDuped, GENERIC_READ, FALSE, 0)) {
+    LOG_ERROR(DuplicateHandle);
+    return false;
+  }
+
+  aLocal.own(rawDuped);
+
+  return true;
+}
+
+namespace mozilla {
+namespace a11y {
+
+Maybe<DWORD> Compatibility::sUiaRemotePid;
+
+Maybe<bool>
+Compatibility::OnUIAMessage(WPARAM aWParam, LPARAM aLParam)
+{
+  Maybe<DWORD>& remotePid = sUiaRemotePid;
+  auto clearUiaRemotePid = MakeScopeExit([&remotePid]() {
+    remotePid = Nothing();
+  });
+
+  static auto pNtQuerySystemInformation =
+    reinterpret_cast<decltype(&::NtQuerySystemInformation)>(
+      ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"),
+                       "NtQuerySystemInformation"));
+
+  static auto pNtQueryObject =
+    reinterpret_cast<decltype(&::NtQueryObject)>(
+      ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtQueryObject"));
+
+  // UIA creates a section containing the substring "HOOK_SHMEM_"
+  NS_NAMED_LITERAL_STRING(kStrHookShmem, "HOOK_SHMEM_");
+
+  // 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("_%08x_%08x_%08x", ::GetCurrentThreadId(),
+                                    aLParam, aWParam);
+
+  NTSTATUS ntStatus;
+
+  // First we must query for a list of all the open handles in the system.
+  UniquePtr<char[]> handleInfoBuf;
+  ULONG handleInfoBufLen = sizeof(SYSTEM_HANDLE_INFORMATION_EX) +
+                           1024 * sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX);
+
+  // We must query for handle information in a loop, since we are effectively
+  // asking the kernel to take a snapshot of all the handles on the system;
+  // the size of the required buffer may fluctuate between successive calls.
+  while (true) {
+    handleInfoBuf = MakeUnique<char[]>(handleInfoBufLen);
+
+    ntStatus = pNtQuerySystemInformation(
+                 (SYSTEM_INFORMATION_CLASS) SystemExtendedHandleInformation,
+                 handleInfoBuf.get(), handleInfoBufLen, &handleInfoBufLen);
+    if (ntStatus == STATUS_INFO_LENGTH_MISMATCH) {
+      continue;
+    }
+
+    if (!NT_SUCCESS(ntStatus)) {
+      return Nothing();
+    }
+
+    break;
+  }
+
+  // Now we iterate through the system handle list, searching for a section
+  // handle whose name matches the section name used by UIA.
+
+  static Maybe<USHORT> sSectionObjTypeIndex;
+
+  const DWORD ourPid = ::GetCurrentProcessId();
+
+  Maybe<PVOID> kernelObject;
+
+  ULONG lastPid = 0;
+  nsAutoHandle process;
+
+  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 (lastPid && lastPid == curHandle.mPid && !process) {
+      // During the previous iteration, we could not obtain a handle for this
+      // pid. Skip any remaining handles belonging to that pid.
+      continue;
+    }
+
+    // As a perf optimization, we reuse the same process handle as long as we're
+    // still looking at the same pid. Once the pid changes, we need to reset
+    // process so that we open a new handle to the newly-seen process.
+    if (lastPid != curHandle.mPid) {
+      process.reset();
+    }
+
+    nsAutoHandle handle;
+
+    if (kernelObject.isSome() && kernelObject.value() == curHandle.mObject) {
+      // If we know the value of the underlying kernel object, we can immediately
+      // check for equality by comparing against curHandle.mObject
+      remotePid = Some(static_cast<DWORD>(curHandle.mPid));
+      break;
+    } else if (sSectionObjTypeIndex.isSome()) {
+      // Otherwise, if we know which object type value corresponds to a Section
+      // object, we can use that to eliminate any handles that are not sections.
+      if (curHandle.mObjectTypeIndex != sSectionObjTypeIndex.value()) {
+        // Not a section handle
+        continue;
+      }
+    } else {
+      // Otherwise we need to query the handle to determine its type. Note that
+      // each handle in the system list is relative to its owning process, so
+      // we cannot do anything with it until we duplicate the handle into our
+      // own process.
+
+      lastPid = curHandle.mPid;
+
+      if (!GetLocalObjectHandle((DWORD) curHandle.mPid,
+                                (HANDLE) curHandle.mHandle,
+                                process, handle)) {
+        // We don't have access to do this, assume this handle isn't relevant
+        continue;
+      }
+
+      // Now we have our own handle to the object, lets find out what type of
+      // object this handle represents. Any handle whose type is not "Section"
+      // is of no interest to us.
+      ULONG objTypeBufLen;
+      ntStatus = pNtQueryObject(handle, ObjectTypeInformation, nullptr,
+                                0, &objTypeBufLen);
+      if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) {
+        continue;
+      }
+
+      auto objTypeBuf = MakeUnique<char[]>(objTypeBufLen);
+      ntStatus = pNtQueryObject(handle, ObjectTypeInformation, objTypeBuf.get(),
+                                objTypeBufLen, &objTypeBufLen);
+      if (!NT_SUCCESS(ntStatus)) {
+        // We don't have access to do this, assume this handle isn't relevant
+        continue;
+      }
+
+      auto objType =
+        reinterpret_cast<PUBLIC_OBJECT_TYPE_INFORMATION*>(objTypeBuf.get());
+
+      nsDependentString objTypeName(objType->TypeName.Buffer,
+                                    objType->TypeName.Length / sizeof(wchar_t));
+      if (!objTypeName.Equals(NS_LITERAL_STRING("Section"))) {
+        // Not a section, so we don't care about this handle anymore.
+        continue;
+      }
+
+      // We have a section, save this handle's type code so that we can go
+      // faster in future iterations.
+      sSectionObjTypeIndex = Some(curHandle.mObjectTypeIndex);
+    }
+
+    // If we reached this point without needing to query the handle, then we
+    // need to open it here so that we can query its name.
+    lastPid = curHandle.mPid;
+
+    if ((!process || !handle) &&
+        !GetLocalObjectHandle((DWORD) curHandle.mPid, (HANDLE) curHandle.mHandle,
+                              process, handle)) {
+      // We don't have access to do this, assume this handle isn't relevant
+      continue;
+    }
+
+    // At this point, |handle| is a valid section handle. Let's try to find
+    // out the name of its underlying object.
+    ULONG objNameBufLen;
+    ntStatus = pNtQueryObject(handle,
+                              (OBJECT_INFORMATION_CLASS)ObjectNameInformation,
+                              nullptr, 0, &objNameBufLen);
+    if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) {
+      continue;
+    }
+
+    auto objNameBuf = MakeUnique<char[]>(objNameBufLen);
+    ntStatus = pNtQueryObject(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) {
+      // This section is unnamed. We don't care about those.
+      continue;
+    }
+
+    nsDependentString objName(objNameInfo->mName.Buffer,
+                              objNameInfo->mName.Length / sizeof(wchar_t));
+
+    // Check to see if the section's name matches our expected name.
+    if (!FindInReadable(kStrHookShmem, objName) ||
+        !StringEndsWith(objName, partialSectionSuffix)) {
+      // The names don't match, continue searching.
+      continue;
+    }
+
+    // At this point we have a section handle whose name matches the one that
+    // we're looking for.
+
+    if (curHandle.mPid == ourPid) {
+      // Our own process also has a handle to the section of interest. While we
+      // don't want our own pid, this *does* give us an opportunity to speed up
+      // future iterations by examining each handle for its kernel object (which
+      // is the same for all processes) instead of searching by name.
+      kernelObject = Some(curHandle.mObject);
+      continue;
+    }
+
+    // Bingo! We want this pid!
+    remotePid = Some(static_cast<DWORD>(curHandle.mPid));
+
+    break;
+  }
+
+  if (!remotePid) {
+    return Nothing();
+  }
+
+  a11y::SetInstantiator(remotePid.value());
+
+  /* This is where we could block UIA stuff
+  nsCOMPtr<nsIFile> instantiator;
+  if (a11y::GetInstantiator(getter_AddRefs(instantiator)) &&
+      ShouldBlockUIAClient(instantiator)) {
+    return Some(false);
+  }
+  */
+
+  return Some(true);
+}
+
+} // namespace a11y
+} // namespace mozilla
--- a/accessible/windows/msaa/LazyInstantiator.cpp
+++ b/accessible/windows/msaa/LazyInstantiator.cpp
@@ -18,50 +18,22 @@
 #include "nsWindowsHelpers.h"
 #include "nsCOMPtr.h"
 #include "nsExceptionHandler.h"
 #include "nsIFile.h"
 #include "nsXPCOM.h"
 #include "RootAccessibleWrap.h"
 #include "WinUtils.h"
 
-#if defined(MOZ_TELEMETRY_REPORTING)
-#include "mozilla/Telemetry.h"
-#endif // defined(MOZ_TELEMETRY_REPORTING)
-
 #include <oaidl.h>
 
 #if !defined(STATE_SYSTEM_NORMAL)
 #define STATE_SYSTEM_NORMAL (0)
 #endif // !defined(STATE_SYSTEM_NORMAL)
 
-/**
- * Because our wrapped accessible is cycle-collected, we can't safely AddRef()
- * or Release() ourselves off the main thread. This template specialization
- * forces NewRunnableMethod() to use STAUniquePtr instead of RefPtr for managing
- * a runnable's lifetime. Once the runnable has completed, the STAUniquePtr will
- * post a runnable to the main thread to release ourselves from there.
- */
-template<>
-struct nsRunnableMethodReceiver<mozilla::a11y::LazyInstantiator, true>
-{
-  mozilla::mscom::STAUniquePtr<mozilla::a11y::LazyInstantiator> mObj;
-  explicit nsRunnableMethodReceiver(mozilla::a11y::LazyInstantiator* aObj)
-    : mObj(aObj)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    // STAUniquePtr does not implicitly AddRef(), so we must explicitly do so
-    // here.
-    aObj->AddRef();
-  }
-  ~nsRunnableMethodReceiver() { Revoke(); }
-  mozilla::a11y::LazyInstantiator* Get() const { return mObj.get(); }
-  void Revoke() { mObj = nullptr; }
-};
-
 namespace mozilla {
 namespace a11y {
 
 static const wchar_t kLazyInstantiatorProp[] = L"mozilla::a11y::LazyInstantiator";
 
 /* static */
 already_AddRefed<IAccessible>
 LazyInstantiator::GetRootAccessible(HWND aHwnd)
@@ -174,63 +146,28 @@ 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);
 }
 
 /**
- * Given the remote client's thread ID, resolve its execuatable image name.
+ * Given the remote client's thread ID, resolve its process ID.
  */
-bool
-LazyInstantiator::GetClientExecutableName(const DWORD aClientTid,
-                                          nsIFile** aOutClientExe)
+DWORD
+LazyInstantiator::GetClientPid(const DWORD aClientTid)
 {
   nsAutoHandle callingThread(::OpenThread(THREAD_QUERY_LIMITED_INFORMATION,
                                           FALSE, aClientTid));
   if (!callingThread) {
-    return false;
-  }
-
-  DWORD callingPid = ::GetProcessIdOfThread(callingThread);
-
-  nsAutoHandle callingProcess(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
-                                            FALSE, callingPid));
-  if (!callingProcess) {
-    return false;
+    return 0;
   }
 
-  DWORD bufLen = MAX_PATH;
-  UniquePtr<wchar_t[]> buf;
-
-  while (true) {
-    buf = MakeUnique<wchar_t[]>(bufLen);
-    if (::QueryFullProcessImageName(callingProcess, 0, buf.get(), &bufLen)) {
-      break;
-    }
-
-    DWORD lastError = ::GetLastError();
-    MOZ_ASSERT(lastError == ERROR_INSUFFICIENT_BUFFER);
-    if (lastError != ERROR_INSUFFICIENT_BUFFER) {
-      return false;
-    }
-
-    bufLen *= 2;
-  }
-
-  nsCOMPtr<nsIFile> file;
-  nsresult rv = NS_NewLocalFile(nsDependentString(buf.get(), bufLen), false,
-                                getter_AddRefs(file));
-  if (NS_FAILED(rv)) {
-    return false;
-  }
-
-  file.forget(aOutClientExe);
-  return NS_SUCCEEDED(rv);
+  return ::GetProcessIdOfThread(callingThread);
 }
 
 /**
  * This is the blocklist for known "bad" DLLs that instantiate a11y.
  */
 static const wchar_t* gBlockedInprocDlls[] = {
   L"dtvhooks.dll",  // RealPlayer, bug 1418535
   L"dtvhooks64.dll" // RealPlayer, bug 1418535
@@ -285,160 +222,41 @@ LazyInstantiator::ShouldInstantiate(cons
   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 (!GetClientExecutableName(aClientTid, getter_AddRefs(clientExe))) {
-#if defined(MOZ_TELEMETRY_REPORTING)
-    AccumulateTelemetry(NS_LITERAL_STRING("(Failed to retrieve client image name)"));
-#endif // defined(MOZ_TELEMETRY_REPORTING)
-    // We should return true as a failsafe
+  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 = ArrayLength(gBlockedRemoteClients); i < len; ++i) {
         if (leafName.EqualsIgnoreCase(gBlockedRemoteClients[i])) {
           // If client exe is in our blocklist, do not instantiate.
           return false;
         }
       }
     }
   }
 
-  // Store full path to executable for support purposes.
-  nsAutoString filePath;
-  rv = clientExe->GetPath(filePath);
-  if (NS_SUCCEEDED(rv)) {
-    a11y::SetInstantiator(filePath);
-  }
-
-#if defined(MOZ_TELEMETRY_REPORTING)
-  if (!mTelemetryThread) {
-    // Call GatherTelemetry on a background thread because it does I/O on
-    // the executable file to retrieve version information.
-    nsCOMPtr<nsIRunnable> runnable(
-        NewRunnableMethod<nsCOMPtr<nsIFile>, RefPtr<AccumulateRunnable>>(
-                                             "LazyInstantiator::GatherTelemetry",
-                                             this,
-                                             &LazyInstantiator::GatherTelemetry,
-                                             clientExe,
-                                             new AccumulateRunnable(this)));
-    NS_NewThread(getter_AddRefs(mTelemetryThread), runnable);
-  }
-#endif // defined(MOZ_TELEMETRY_REPORTING)
-
   return true;
 }
 
-#if defined(MOZ_TELEMETRY_REPORTING)
-/**
- * Appends version information in the format "|a.b.c.d".
- * If there is no version information, we append nothing.
- */
-void
-LazyInstantiator::AppendVersionInfo(nsIFile* aClientExe,
-                                    nsAString& aStrToAppend)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-
-  nsAutoString fullPath;
-  nsresult rv = aClientExe->GetPath(fullPath);
-  if (NS_FAILED(rv)) {
-    return;
-  }
-
-  DWORD verInfoSize = ::GetFileVersionInfoSize(fullPath.get(), nullptr);
-  if (!verInfoSize) {
-    return;
-  }
-
-  auto verInfoBuf = MakeUnique<BYTE[]>(verInfoSize);
-
-  if (!::GetFileVersionInfo(fullPath.get(), 0, verInfoSize, verInfoBuf.get())) {
-    return;
-  }
-
-  VS_FIXEDFILEINFO* fixedInfo = nullptr;
-  UINT fixedInfoLen = 0;
-
-  if (!::VerQueryValue(verInfoBuf.get(), L"\\", (LPVOID*) &fixedInfo,
-                       &fixedInfoLen)) {
-    return;
-  }
-
-  uint32_t major = HIWORD(fixedInfo->dwFileVersionMS);
-  uint32_t minor = LOWORD(fixedInfo->dwFileVersionMS);
-  uint32_t patch = HIWORD(fixedInfo->dwFileVersionLS);
-  uint32_t build = LOWORD(fixedInfo->dwFileVersionLS);
-
-  aStrToAppend.AppendLiteral(u"|");
-
-  NS_NAMED_LITERAL_STRING(dot, ".");
-
-  aStrToAppend.AppendInt(major);
-  aStrToAppend.Append(dot);
-  aStrToAppend.AppendInt(minor);
-  aStrToAppend.Append(dot);
-  aStrToAppend.AppendInt(patch);
-  aStrToAppend.Append(dot);
-  aStrToAppend.AppendInt(build);
-}
-
-void
-LazyInstantiator::GatherTelemetry(nsIFile* aClientExe,
-                                  AccumulateRunnable* aRunnable)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-
-  nsAutoString value;
-  nsresult rv = aClientExe->GetLeafName(value);
-  if (NS_SUCCEEDED(rv)) {
-    AppendVersionInfo(aClientExe, value);
-  }
-
-  aRunnable->SetData(value);
-
-  // Now that we've (possibly) obtained version info, send the resulting
-  // string back to the main thread to accumulate in telemetry.
-  NS_DispatchToMainThread(aRunnable);
-}
-
-void
-LazyInstantiator::AccumulateTelemetry(const nsString& aValue)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (!aValue.IsEmpty()) {
-#if defined(MOZ_TELEMETRY_REPORTING)
-    Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_INSTANTIATORS,
-                         aValue);
-#endif // defined(MOZ_TELEMETRY_REPORTING)
-    CrashReporter::
-      AnnotateCrashReport(NS_LITERAL_CSTRING("AccessibilityClient"),
-                          NS_ConvertUTF16toUTF8(aValue));
-  }
-
-  if (mTelemetryThread) {
-    mTelemetryThread->Shutdown();
-    mTelemetryThread = nullptr;
-  }
-}
-#endif // defined(MOZ_TELEMETRY_REPORTING)
-
 RootAccessibleWrap*
 LazyInstantiator::ResolveRootAccWrap()
 {
   Accessible* acc = widget::WinUtils::GetRootAccessibleForHWND(mHwnd);
   if (!acc || !acc->IsRoot()) {
     return nullptr;
   }
 
--- a/accessible/windows/msaa/LazyInstantiator.h
+++ b/accessible/windows/msaa/LazyInstantiator.h
@@ -6,19 +6,16 @@
 
 #ifndef mozilla_a11y_LazyInstantiator_h
 #define mozilla_a11y_LazyInstantiator_h
 
 #include "IUnknownImpl.h"
 #include "mozilla/mscom/Ptr.h"
 #include "mozilla/RefPtr.h"
 #include "nsString.h"
-#if defined(MOZ_TELEMETRY_REPORTING)
-#include "nsThreadUtils.h"
-#endif // defined(MOZ_TELEMETRY_REPORTING)
 
 #include <oleacc.h>
 
 class nsIFile;
 
 namespace mozilla {
 namespace a11y {
 
@@ -81,51 +78,17 @@ public:
 
 private:
   explicit LazyInstantiator(HWND aHwnd);
   ~LazyInstantiator();
 
   bool IsBlockedInjection();
   bool ShouldInstantiate(const DWORD aClientTid);
 
-  bool GetClientExecutableName(const DWORD aClientTid, nsIFile** aOutClientExe);
-#if defined(MOZ_TELEMETRY_REPORTING)
-  class AccumulateRunnable final : public Runnable
-  {
-  public:
-    explicit AccumulateRunnable(LazyInstantiator* aObj)
-      : Runnable("mozilla::a11y::LazyInstantiator::AccumulateRunnable")
-      , mObj(aObj)
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-      aObj->AddRef();
-    }
-
-    void SetData(const nsAString& aData)
-    {
-      mData = aData;
-    }
-
-    NS_IMETHOD Run() override
-    {
-      mObj->AccumulateTelemetry(mData);
-      return NS_OK;
-    }
-
-  private:
-    mscom::STAUniquePtr<LazyInstantiator> mObj;
-    nsString                              mData;
-  };
-
-  friend class AccumulateRunnable;
-
-  void AppendVersionInfo(nsIFile* aClientExe, nsAString& aStrToAppend);
-  void GatherTelemetry(nsIFile* aClientExe, AccumulateRunnable* aRunnable);
-  void AccumulateTelemetry(const nsString& aValue);
-#endif // defined(MOZ_TELEMETRY_REPORTING)
+  DWORD GetClientPid(const DWORD aClientTid);
 
   /**
    * @return S_OK if we have a valid mRealRoot to invoke methods on
    */
   HRESULT MaybeResolveRoot();
 
   /**
    * @return S_OK if we have a valid mWeakDispatch to invoke methods on
@@ -147,18 +110,15 @@ private:
    * are interfaces that come from objects that we aggregate. Aggregated object
    * interfaces share refcount methods with ours, so if we were to hold strong
    * references to them, we would be holding strong references to ourselves,
    * creating a cycle.
    */
   RootAccessibleWrap* mWeakRootAccWrap;
   IAccessible*        mWeakAccessible;
   IDispatch*          mWeakDispatch;
-#if defined(MOZ_TELEMETRY_REPORTING)
-  nsCOMPtr<nsIThread> mTelemetryThread;
-#endif // defined(MOZ_TELEMETRY_REPORTING)
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_LazyInstantiator_h
 
new file mode 100644
--- /dev/null
+++ b/accessible/windows/msaa/NtUndoc.h
@@ -0,0 +1,50 @@
+/* -*- 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/. */
+
+#ifndef mozilla_NtUndoc_h
+#define mozilla_NtUndoc_h
+
+#include <winternl.h>
+
+#ifndef STATUS_INFO_LENGTH_MISMATCH
+#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
+#endif
+
+enum UndocSystemInformationClass
+{
+  SystemExtendedHandleInformation = 64
+};
+
+struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
+{
+  PVOID       mObject;
+  ULONG_PTR   mPid;
+  ULONG_PTR   mHandle;
+  ACCESS_MASK mGrantedAccess;
+  USHORT      mCreatorBackTraceIndex;
+  USHORT      mObjectTypeIndex;
+  ULONG       mAttributes;
+  ULONG       mReserved;
+};
+
+struct SYSTEM_HANDLE_INFORMATION_EX
+{
+  ULONG_PTR                         mHandleCount;
+  ULONG_PTR                         mReserved;
+  SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX mHandles[1];
+};
+
+enum UndocObjectInformationClass
+{
+  ObjectNameInformation = 1
+};
+
+struct OBJECT_NAME_INFORMATION
+{
+  UNICODE_STRING  mName;
+};
+
+#endif // mozilla_NtUndoc_h
--- a/accessible/windows/msaa/Platform.cpp
+++ b/accessible/windows/msaa/Platform.cpp
@@ -19,25 +19,29 @@
 #include "mozilla/mscom/Registration.h"
 #include "mozilla/mscom/Utils.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/WindowsVersion.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "ProxyWrappers.h"
 
+#if defined(MOZ_TELEMETRY_REPORTING)
+#include "mozilla/Telemetry.h"
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+
 using namespace mozilla;
 using namespace mozilla::a11y;
 using namespace mozilla::mscom;
 
 static StaticAutoPtr<RegisteredProxy> gRegCustomProxy;
 static StaticAutoPtr<RegisteredProxy> gRegProxy;
 static StaticAutoPtr<RegisteredProxy> gRegAccTlb;
 static StaticAutoPtr<RegisteredProxy> gRegMiscTlb;
-static nsString* gInstantiator = nullptr;
+static StaticRefPtr<nsIFile> gInstantiator;
 
 void
 a11y::PlatformInit()
 {
   nsWinUtils::MaybeStartWindowEmulation();
   ia2AccessibleText::InitTextChangeData();
 
   mscom::InterceptorLog::Init();
@@ -63,17 +67,16 @@ a11y::PlatformShutdown()
 
   nsWinUtils::ShutdownWindowEmulation();
   gRegCustomProxy = nullptr;
   gRegProxy = nullptr;
   gRegAccTlb = nullptr;
   gRegMiscTlb = nullptr;
 
   if (gInstantiator) {
-    delete gInstantiator;
     gInstantiator = nullptr;
   }
 }
 
 void
 a11y::ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces)
 {
   AccessibleWrap* wrapper = nullptr;
@@ -234,28 +237,169 @@ a11y::IsHandlerRegistered()
     return false;
   }
 
   bool equal;
   rv = expectedHandler->Equals(actualHandler, &equal);
   return NS_SUCCEEDED(rv) && equal;
 }
 
-void
-a11y::SetInstantiator(const nsAString& aInstantiator)
+static bool
+GetInstantiatorExecutable(const DWORD aPid, nsIFile** aOutClientExe)
 {
-  if (!gInstantiator) {
-    gInstantiator = new nsString();
+  nsAutoHandle callingProcess(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
+                                            FALSE, aPid));
+  if (!callingProcess) {
+    return false;
+  }
+
+  DWORD bufLen = MAX_PATH;
+  UniquePtr<wchar_t[]> buf;
+
+  while (true) {
+    buf = MakeUnique<wchar_t[]>(bufLen);
+    if (::QueryFullProcessImageName(callingProcess, 0, buf.get(), &bufLen)) {
+      break;
+    }
+
+    DWORD lastError = ::GetLastError();
+    MOZ_ASSERT(lastError == ERROR_INSUFFICIENT_BUFFER);
+    if (lastError != ERROR_INSUFFICIENT_BUFFER) {
+      return false;
+    }
+
+    bufLen *= 2;
+  }
+
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = NS_NewLocalFile(nsDependentString(buf.get(), bufLen), false,
+                                getter_AddRefs(file));
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  file.forget(aOutClientExe);
+  return NS_SUCCEEDED(rv);
+}
+
+/**
+ * Appends version information in the format "|a.b.c.d".
+ * If there is no version information, we append nothing.
+ */
+static void
+AppendVersionInfo(nsIFile* aClientExe, nsAString& aStrToAppend)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  nsAutoString fullPath;
+  nsresult rv = aClientExe->GetPath(fullPath);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  DWORD verInfoSize = ::GetFileVersionInfoSize(fullPath.get(), nullptr);
+  if (!verInfoSize) {
+    return;
+  }
+
+  auto verInfoBuf = MakeUnique<BYTE[]>(verInfoSize);
+
+  if (!::GetFileVersionInfo(fullPath.get(), 0, verInfoSize, verInfoBuf.get())) {
+    return;
+  }
+
+  VS_FIXEDFILEINFO* fixedInfo = nullptr;
+  UINT fixedInfoLen = 0;
+
+  if (!::VerQueryValue(verInfoBuf.get(), L"\\", (LPVOID*) &fixedInfo,
+                       &fixedInfoLen)) {
+    return;
   }
 
-  gInstantiator->Assign(aInstantiator);
+  uint32_t major = HIWORD(fixedInfo->dwFileVersionMS);
+  uint32_t minor = LOWORD(fixedInfo->dwFileVersionMS);
+  uint32_t patch = HIWORD(fixedInfo->dwFileVersionLS);
+  uint32_t build = LOWORD(fixedInfo->dwFileVersionLS);
+
+  aStrToAppend.AppendLiteral(u"|");
+
+  NS_NAMED_LITERAL_STRING(dot, ".");
+
+  aStrToAppend.AppendInt(major);
+  aStrToAppend.Append(dot);
+  aStrToAppend.AppendInt(minor);
+  aStrToAppend.Append(dot);
+  aStrToAppend.AppendInt(patch);
+  aStrToAppend.Append(dot);
+  aStrToAppend.AppendInt(build);
+}
+
+static void
+AccumulateInstantiatorTelemetry(nsIFile* aClientExe, const nsAString& aValue)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!aValue.IsEmpty()) {
+#if defined(MOZ_TELEMETRY_REPORTING)
+    Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_INSTANTIATORS,
+                         aValue);
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+    CrashReporter::
+      AnnotateCrashReport(NS_LITERAL_CSTRING("AccessibilityClient"),
+                          NS_ConvertUTF16toUTF8(aValue));
+  }
+}
+
+static void
+GatherInstantiatorTelemetry(nsIFile* aClientExe)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  nsString value;
+  nsresult rv = aClientExe->GetLeafName(value);
+  if (NS_SUCCEEDED(rv)) {
+    AppendVersionInfo(aClientExe, value);
+  }
+
+  nsCOMPtr<nsIFile> ref(aClientExe);
+  nsCOMPtr<nsIRunnable> runnable(
+    NS_NewRunnableFunction("a11y::AccumulateInstantiatorTelemetry",
+                           [ref, value]() -> void {
+                             AccumulateInstantiatorTelemetry(ref, value);
+                           }));
+
+  // Now that we've (possibly) obtained version info, send the resulting
+  // string back to the main thread to accumulate in telemetry.
+  NS_DispatchToMainThread(runnable);
+}
+
+void
+a11y::SetInstantiator(const uint32_t aPid)
+{
+  nsCOMPtr<nsIFile> clientExe;
+  if (!GetInstantiatorExecutable(aPid, getter_AddRefs(clientExe))) {
+#if defined(MOZ_TELEMETRY_REPORTING)
+    AccumulateInstantiatorTelemetry(nullptr, NS_LITERAL_STRING("(Failed to retrieve client image name)"));
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+    return;
+  }
+
+  gInstantiator = clientExe;
+
+  nsCOMPtr<nsIRunnable> runnable(
+    NS_NewRunnableFunction("a11y::GatherInstantiatorTelemetry",
+                           [clientExe]() -> void {
+                             GatherInstantiatorTelemetry(clientExe);
+                           }));
+
+  nsCOMPtr<nsIThread> telemetryThread;
+  NS_NewThread(getter_AddRefs(telemetryThread), runnable);
 }
 
 bool
-a11y::GetInstantiator(nsAString& aInstantiator)
+a11y::GetInstantiator(nsIFile** aOutInstantiator)
 {
   if (!gInstantiator) {
     return false;
   }
 
-  aInstantiator.Assign(*gInstantiator);
-  return true;
+  return NS_SUCCEEDED(gInstantiator->Clone(aOutInstantiator));
 }
--- a/accessible/windows/msaa/moz.build
+++ b/accessible/windows/msaa/moz.build
@@ -18,16 +18,17 @@ EXPORTS.mozilla.a11y += [
     'nsWinUtils.h',
 ]
 
 UNIFIED_SOURCES += [
     'AccessibleWrap.cpp',
     'ApplicationAccessibleWrap.cpp',
     'ARIAGridAccessibleWrap.cpp',
     'Compatibility.cpp',
+    'CompatibilityUIA.cpp',
     'DocAccessibleWrap.cpp',
     'EnumVariant.cpp',
     'GeckoCustom.cpp',
     'HTMLTableAccessibleWrap.cpp',
     'HTMLWin32ObjectAccessible.cpp',
     'HyperTextAccessibleWrap.cpp',
     'ImageAccessibleWrap.cpp',
     'IUnknownImpl.cpp',
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -1009,22 +1009,28 @@ nsXULAppInfo::GetAccessibleHandlerUsed(b
 NS_IMETHODIMP
 nsXULAppInfo::GetAccessibilityInstantiator(nsAString &aInstantiator)
 {
 #if defined(ACCESSIBILITY) && defined(XP_WIN)
   if (!GetAccService()) {
     aInstantiator = NS_LITERAL_STRING("");
     return NS_OK;
   }
-  nsAutoString oopClientInfo, ipClientInfo;
+  nsAutoString ipClientInfo;
   a11y::Compatibility::GetHumanReadableConsumersStr(ipClientInfo);
   aInstantiator.Append(ipClientInfo);
   aInstantiator.AppendLiteral("|");
-  a11y::GetInstantiator(oopClientInfo);
-  aInstantiator.Append(oopClientInfo);
+
+  nsCOMPtr<nsIFile> oopClientExe;
+  if (a11y::GetInstantiator(getter_AddRefs(oopClientExe))) {
+    nsAutoString oopClientInfo;
+    if (NS_SUCCEEDED(oopClientExe->GetPath(oopClientInfo))) {
+      aInstantiator.Append(oopClientInfo);
+    }
+  }
 #else
   aInstantiator = NS_LITERAL_STRING("");
 #endif
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXULAppInfo::GetShouldBlockIncompatJaws(bool* aResult)