Bug 1323521: Prevent Windows touchscreen support from instantiating a11y; r=jimm a=lizzard
authorAaron Klotz <aklotz@mozilla.com>
Wed, 14 Dec 2016 12:20:48 -0700
changeset 353223 3e93be52e058798d292e1c94997690b5af34651d
parent 353222 5ae4b9494db7c437b5c3a114431aab12b93b8d03
child 353224 4ed8e6215ade1186f734327dd484ac73103d5279
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimm, lizzard
bugs1323521
milestone52.0a2
Bug 1323521: Prevent Windows touchscreen support from instantiating a11y; r=jimm a=lizzard MozReview-Commit-ID: 3xV0b97lEre
widget/windows/nsWindow.cpp
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -324,16 +324,128 @@ private:
   static Maybe<TimeStamp> sBackwardsSkewStamp;
   static DWORD sLastPostTime;
   HWND mWnd;
 };
 
 Maybe<TimeStamp> CurrentWindowsTimeGetter::sBackwardsSkewStamp;
 DWORD CurrentWindowsTimeGetter::sLastPostTime = 0;
 
+#if defined(ACCESSIBILITY)
+/**
+ * Windows touchscreen code works by setting a global WH_GETMESSAGE hook and
+ * injecting tiptsf.dll. The touchscreen process then posts registered messages
+ * to our main thread. The tiptsf hook picks up those registered messages and
+ * uses them as commands, some of which call into UIA, which then calls into
+ * MSAA, which then sends WM_GETOBJECT to us.
+ *
+ * We can get ahead of this by installing our own thread-local WH_GETMESSAGE
+ * hook. Since thread-local hooks are called ahead of global hooks, we will
+ * see these registered messages before tiptsf does. At this point we can then
+ * raise a flag that blocks a11y before invoking CallNextHookEx which will then
+ * invoke the global tiptsf hook. Then when we see WM_GETOBJECT, we check the
+ * flag by calling TIPMessageHandler::IsA11yBlocked().
+ */
+class TIPMessageHandler
+{
+public:
+  ~TIPMessageHandler()
+  {
+    if (mHook) {
+      ::UnhookWindowsHookEx(mHook);
+    }
+  }
+
+  static void Initialize()
+  {
+    MOZ_ASSERT(!sInstance);
+    sInstance = new TIPMessageHandler();
+    ClearOnShutdown(&sInstance);
+  }
+
+  static bool IsA11yBlocked()
+  {
+    MOZ_ASSERT(sInstance);
+    if (!sInstance) {
+      return false;
+    }
+
+    return sInstance->mA11yBlockCount > 0;
+  }
+
+private:
+  TIPMessageHandler()
+    : mHook(nullptr)
+    , mA11yBlockCount(0)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // Registered messages used by tiptsf
+    mMessages[0] = ::RegisterWindowMessage(L"ImmersiveFocusNotification");
+    mMessages[1] = ::RegisterWindowMessage(L"TipCloseMenus");
+    mMessages[2] = ::RegisterWindowMessage(L"TabletInputPanelOpening");
+    mMessages[3] = ::RegisterWindowMessage(L"IHM Pen or Touch Event noticed");
+    mMessages[4] = ::RegisterWindowMessage(L"ProgrammabilityCaretVisibility");
+    mMessages[5] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPHidden");
+    mMessages[6] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPInfo");
+
+    mHook = ::SetWindowsHookEx(WH_GETMESSAGE, &TIPHook, nullptr,
+                               ::GetCurrentThreadId());
+    MOZ_ASSERT(mHook);
+  }
+
+  class MOZ_RAII A11yInstantiationBlocker
+  {
+  public:
+    A11yInstantiationBlocker()
+    {
+      MOZ_ASSERT(TIPMessageHandler::sInstance);
+      ++TIPMessageHandler::sInstance->mA11yBlockCount;
+    }
+
+    ~A11yInstantiationBlocker()
+    {
+      MOZ_ASSERT(TIPMessageHandler::sInstance &&
+                 TIPMessageHandler::sInstance->mA11yBlockCount > 0);
+      --TIPMessageHandler::sInstance->mA11yBlockCount;
+    }
+  };
+
+  friend class A11yInstantiationBlocker;
+
+  static LRESULT CALLBACK TIPHook(int aCode, WPARAM aWParam, LPARAM aLParam)
+  {
+    if (aCode < 0 || !sInstance) {
+      return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
+    }
+
+    MSG* msg = reinterpret_cast<MSG*>(aLParam);
+    UINT& msgCode = msg->message;
+
+    for (uint32_t i = 0; i < ArrayLength(sInstance->mMessages); ++i) {
+      if (msgCode == sInstance->mMessages[i]) {
+        A11yInstantiationBlocker block;
+        return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
+      }
+    }
+
+    return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
+  }
+
+  static StaticAutoPtr<TIPMessageHandler> sInstance;
+
+  HHOOK     mHook;
+  UINT      mMessages[7];
+  uint32_t  mA11yBlockCount;
+};
+
+StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance;
+
+#endif // defined(ACCESSIBILITY)
+
 } // namespace mozilla
 
 /**************************************************************
  *
  * SECTION: globals variables
  *
  **************************************************************/
 
@@ -451,16 +563,19 @@ nsWindow::nsWindow()
   mTaskbarPreview = nullptr;
 
   // Global initialization
   if (!sInstanceCount) {
     // Global app registration id for Win7 and up. See
     // WinTaskbar.cpp for details.
     mozilla::widget::WinTaskbar::RegisterAppUserModelID();
     KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0));
+#if defined(ACCESSIBILITY)
+    mozilla::TIPMessageHandler::Initialize();
+#endif // defined(ACCESSIBILITY)
     IMEHandler::Initialize();
     if (SUCCEEDED(::OleInitialize(nullptr))) {
       sIsOleInitialized = TRUE;
     }
     NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
     MouseScrollHandler::Initialize();
     // Init titlebar button info for custom frames.
     nsUXThemeData::InitTitlebarInfo();
@@ -3274,19 +3389,19 @@ nsWindow::MakeFullScreen(bool aFullScree
     mWidgetListener->FullscreenChanged(aFullScreen);
   }
 
   // Send a eMouseEnterIntoWidget event since Windows has already sent
   // a WM_MOUSELEAVE that caused us to send a eMouseExitFromWidget event.
   if (aFullScreen && !sCurrentWindow) {
     sCurrentWindow = this;
     LPARAM pos = sCurrentWindow->lParamToClient(sMouseExitlParamScreen);
-    sCurrentWindow->DispatchMouseEvent(eMouseEnterIntoWidget,
-                                       sMouseExitwParam, pos, false,
-                                       WidgetMouseEvent::eLeftButton,
+    sCurrentWindow->DispatchMouseEvent(eMouseEnterIntoWidget,
+                                       sMouseExitwParam, pos, false,
+                                       WidgetMouseEvent::eLeftButton,
                                        MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
   }
 
   return NS_OK;
 }
 
 /**************************************************************
  *
@@ -5592,17 +5707,17 @@ nsWindow::ProcessMessage(UINT msg, WPARA
 
 #ifdef ACCESSIBILITY
     case WM_GETOBJECT:
     {
       *aRetValue = 0;
       // Do explicit casting to make it working on 64bit systems (see bug 649236
       // for details).
       int32_t objId = static_cast<DWORD>(lParam);
-      if (objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically
+      if (!TIPMessageHandler::IsA11yBlocked() && objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically
         a11y::Accessible* rootAccessible = GetAccessible(); // Held by a11y cache
         if (rootAccessible) {
           IAccessible *msaaAccessible = nullptr;
           rootAccessible->GetNativeInterface((void**)&msaaAccessible); // does an addref
           if (msaaAccessible) {
             *aRetValue = LresultFromObject(IID_IAccessible, wParam, msaaAccessible); // does an addref
             msaaAccessible->Release(); // release extra addref
             result = true;  // We handled the WM_GETOBJECT message