Bug 1419886: Part 1 - Add hooks for UIA detection to nsAppShell; r=jimm
authorAaron Klotz <aklotz@mozilla.com>
Mon, 04 Dec 2017 17:53:13 -0700
changeset 395458 3606176ddf2fc9de85e6932af7380cb2142349cc
parent 395457 eae544da26b6ea225e3f402c1bd62085cf1c7ec1
child 395459 da4106e06989ab988a2ed112920aaad5aec0e670
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)
reviewersjimm
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 1 - Add hooks for UIA detection to nsAppShell; r=jimm MozReview-Commit-ID: 5p4kZDHcQGn
widget/windows/nsAppShell.cpp
widget/windows/nsAppShell.h
--- a/widget/windows/nsAppShell.cpp
+++ b/widget/windows/nsAppShell.cpp
@@ -22,16 +22,21 @@
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
 #include "GeckoProfiler.h"
 #include "nsComponentManagerUtils.h"
 #include "ScreenHelperWin.h"
 #include "HeadlessScreenHelper.h"
 #include "mozilla/widget/ScreenManager.h"
 
+#if defined(ACCESSIBILITY)
+#include "mozilla/a11y/Compatibility.h"
+#include "mozilla/a11y/Platform.h"
+#endif // defined(ACCESSIBILITY)
+
 // These are two messages that the code in winspool.drv on Windows 7 explicitly
 // waits for while it is pumping other Windows messages, during display of the
 // Printer Properties dialog.
 #define MOZ_WM_PRINTER_PROPERTIES_COMPLETION 0x5b7a
 #define MOZ_WM_PRINTER_PROPERTIES_FAILURE 0x5b7f
 
 using namespace mozilla;
 using namespace mozilla::widget;
@@ -156,16 +161,99 @@ nsAppShell::~nsAppShell()
   if (mEventWnd) {
     // DestroyWindow doesn't do anything when called from a non UI thread.
     // Since mEventWnd was created on the UI thread, it must be destroyed on
     // the UI thread.
     SendMessage(mEventWnd, WM_CLOSE, 0, 0);
   }
 }
 
+#if defined(ACCESSIBILITY)
+
+static ULONG gUiaMsg;
+static HHOOK gUiaHook;
+
+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) {
+    Maybe<bool> shouldCallNextHook =
+      a11y::Compatibility::OnUIAMessage(cwp->wParam, cwp->lParam);
+    if (shouldCallNextHook.isSome()) {
+      // We've got an instantiator, disconnect this hook
+      if (::UnhookWindowsHookEx(gUiaHook)) {
+        gUiaHook = nullptr;
+      }
+
+      if (!shouldCallNextHook.value()) {
+        return 0;
+      }
+    }
+  }
+
+  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());
+  }
+}
+
+NS_IMETHODIMP
+nsAppShell::Observe(nsISupports* aSubject, const char* aTopic,
+                    const char16_t* aData)
+{
+  if (XRE_IsParentProcess() && !strcmp(aTopic, "dll-loaded-main-thread")) {
+    if (a11y::PlatformDisabledState() != a11y::ePlatformIsDisabled && !gUiaHook) {
+      nsDependentString dllName(aData);
+
+      if (StringEndsWith(dllName, NS_LITERAL_STRING("uiautomationcore.dll"),
+                         nsCaseInsensitiveStringComparator())) {
+        InitUIADetection();
+
+        // Now that we've handled the observer notification, we can remove it
+        nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService());
+        obsServ->RemoveObserver(this, "dll-loaded-main-thread");
+      }
+    }
+
+    return NS_OK;
+  }
+
+  return nsBaseAppShell::Observe(aSubject, aTopic, aData);
+}
+
+#endif // defined(ACCESSIBILITY)
+
 nsresult
 nsAppShell::Init()
 {
   LSPAnnotate();
 
   mLastNativeEventScheduled = TimeStamp::NowLoRes();
 
   mozilla::ipc::windows::InitUIThread();
@@ -199,16 +287,25 @@ nsAppShell::Init()
   if (XRE_IsParentProcess()) {
     ScreenManager& screenManager = ScreenManager::GetSingleton();
     if (gfxPlatform::IsHeadless()) {
       screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
     } else {
       screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperWin>());
       ScreenHelperWin::RefreshScreens();
     }
+
+#if defined(ACCESSIBILITY)
+    if (::GetModuleHandleW(L"uiautomationcore.dll")) {
+      InitUIADetection();
+    } else {
+      nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService());
+      obsServ->AddObserver(this, "dll-loaded-main-thread", false);
+    }
+#endif // defined(ACCESSIBILITY)
   }
 
   return nsBaseAppShell::Init();
 }
 
 NS_IMETHODIMP
 nsAppShell::Run(void)
 {
@@ -230,16 +327,27 @@ nsAppShell::Run(void)
   RemoveScreenWakeLockListener();
 
   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
--- a/widget/windows/nsAppShell.h
+++ b/widget/windows/nsAppShell.h
@@ -36,16 +36,22 @@ public:
   static UINT GetTaskbarButtonCreatedMessage();
 
   NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal* thread,
                                    bool eventWasProcessed) final;
 
 protected:
   NS_IMETHOD Run();
   NS_IMETHOD Exit();
+
+#if defined(ACCESSIBILITY)
+  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+                     const char16_t* aData) override;
+#endif // defined(ACCESSIBILITY)
+
   virtual void ScheduleNativeEventCallback();
   virtual bool ProcessNextNativeEvent(bool mayWait);
   virtual ~nsAppShell();
 
   static LRESULT CALLBACK EventWindowProc(HWND, UINT, WPARAM, LPARAM);
 
 protected:
   HWND mEventWnd;