Bug 1221730 - Remove Observers in Windows gamepad backend. r=qdot
authorChih-Yi Leu <cleu@mozilla.com>
Tue, 28 Jun 2016 00:25:00 +0200
changeset 302839 fe54272cd8ff1fbc4afee38b38ca164db02e0172
parent 302838 2bae08c081185081c42cea4bac6822d109c0b55e
child 302840 342068569153f7399dbf141c6c2b36bce71888fb
push id30376
push usercbook@mozilla.com
push dateTue, 28 Jun 2016 14:09:36 +0000
treeherdermozilla-central@e45890951ce7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersqdot
bugs1221730
milestone50.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 1221730 - Remove Observers in Windows gamepad backend. r=qdot
dom/gamepad/windows/WindowsGamepad.cpp
--- a/dom/gamepad/windows/WindowsGamepad.cpp
+++ b/dom/gamepad/windows/WindowsGamepad.cpp
@@ -11,26 +11,27 @@
 #define UNICODE
 #endif
 #include <windows.h>
 #include <hidsdi.h>
 #include <stdio.h>
 #include <xinput.h>
 
 #include "nsIComponentManager.h"
-#include "nsIObserver.h"
-#include "nsIObserverService.h"
 #include "nsITimer.h"
 #include "nsTArray.h"
+#include "nsThreadUtils.h"
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/dom/GamepadFunctions.h"
 #include "mozilla/Services.h"
 
 namespace {
 
+using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::GamepadFunctions;
 using mozilla::ArrayLength;
 
 // USB HID usage tables, page 1 (Hat switch)
 const unsigned kUsageDpad = 0x39;
 // USB HID usage tables, page 1, 0x30 = X
 const unsigned kFirstAxis = 0x30;
@@ -90,17 +91,22 @@ const size_t kNumMappings = ArrayLength(
 
 enum GamepadType {
   kNoGamepad = 0,
   kRawInputGamepad,
   kXInputGamepad
 };
 
 class WindowsGamepadService;
-WindowsGamepadService* gService = nullptr;
+// This pointer holds a windows gamepad backend service,
+// it will be created and destroyed by background thread and
+// used by gMonitorThread
+WindowsGamepadService* MOZ_NON_OWNING_REF gService = nullptr;
+nsCOMPtr<nsIThread> gMonitorThread = nullptr;
+static bool sIsShutdown = false;
 
 struct Gamepad {
   GamepadType type;
 
   // Handle to raw input device
   HANDLE handle;
 
   // XInput Index of the user's controller. Passed to XInputGetState.
@@ -250,70 +256,16 @@ SupportedUsage(USHORT page, USHORT usage
   for (unsigned i = 0; i < ArrayLength(kUsagePages); i++) {
     if (page == kUsagePages[i].usagePage && usage == kUsagePages[i].usage) {
       return true;
     }
   }
   return false;
 }
 
-class Observer : public nsIObserver {
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-
-  Observer(WindowsGamepadService& svc) : mSvc(svc),
-                                         mObserving(true)
-  {
-    nsresult rv;
-    mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
-    nsCOMPtr<nsIObserverService> observerService =
-      mozilla::services::GetObserverService();
-    observerService->AddObserver(this,
-                                 NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
-                                 false);
-  }
-
-  void Stop()
-  {
-    if (mTimer) {
-      mTimer->Cancel();
-    }
-    if (mObserving) {
-      nsCOMPtr<nsIObserverService> observerService =
-        mozilla::services::GetObserverService();
-      observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
-      mObserving = false;
-    }
-  }
-
-  void SetDeviceChangeTimer()
-  {
-    // Set stable timer, since we will get multiple devices-changed
-    // notifications at once
-    if (mTimer) {
-      mTimer->Cancel();
-      mTimer->Init(this, kDevicesChangedStableDelay, nsITimer::TYPE_ONE_SHOT);
-    }
-  }
-
-private:
-  virtual ~Observer()
-  {
-    Stop();
-  }
-
-  // Gamepad service owns us, we just hold a reference back to it.
-  WindowsGamepadService& mSvc;
-  nsCOMPtr<nsITimer> mTimer;
-  bool mObserving;
-};
-
-NS_IMPL_ISUPPORTS(Observer, nsIObserver);
-
 class HIDLoader {
 public:
   HIDLoader() : mModule(LoadLibraryW(L"hid.dll")),
                 mHidD_GetProductString(nullptr),
                 mHidP_GetCaps(nullptr),
                 mHidP_GetButtonCaps(nullptr),
                 mHidP_GetValueCaps(nullptr),
                 mHidP_GetUsages(nullptr),
@@ -355,69 +307,65 @@ public:
   decltype(HidP_GetUsages) *mHidP_GetUsages;
   decltype(HidP_GetUsageValue) *mHidP_GetUsageValue;
   decltype(HidP_GetScaledUsageValue) *mHidP_GetScaledUsageValue;
 
 private:
   HMODULE mModule;
 };
 
-class WindowsGamepadService {
-public:
-  WindowsGamepadService();
+class WindowsGamepadService
+{
+ public:
+  WindowsGamepadService()
+  {
+    mXInputTimer = do_CreateInstance("@mozilla.org/timer;1");
+    mDeviceChangeTimer = do_CreateInstance("@mozilla.org/timer;1");
+  }
   virtual ~WindowsGamepadService()
   {
     Cleanup();
   }
 
-  enum DeviceChangeType {
-    DeviceChangeNotification,
-    DeviceChangeStable
-  };
-  void DevicesChanged(DeviceChangeType type);
+  void DevicesChanged(bool aIsStablizing);
   void Startup();
   void Shutdown();
   // Parse gamepad input from a WM_INPUT message.
   bool HandleRawInput(HRAWINPUT handle);
 
-private:
+  static void XInputMessageLoopOnceCallback(nsITimer *aTimer, void* aClosure);
+  static void DevicesChangeCallback(nsITimer *aTimer, void* aService);
+
+ private:
   void ScanForDevices();
   // Look for connected raw input devices.
   void ScanForRawInputDevices();
   // Look for connected XInput devices.
   bool ScanForXInputDevices();
   bool HaveXInputGamepad(int userIndex);
 
-  // Timer callback for XInput polling
-  static void XInputPollTimerCallback(nsITimer* aTimer, void* aClosure);
+  bool mIsXInputMonitoring;
   void PollXInput();
   void CheckXInputChanges(Gamepad& gamepad, XINPUT_STATE& state);
 
   // Get information about a raw input gamepad.
   bool GetRawGamepad(HANDLE handle);
   void Cleanup();
 
   // List of connected devices.
   nsTArray<Gamepad> mGamepads;
 
-  RefPtr<Observer> mObserver;
-  nsCOMPtr<nsITimer> mXInputPollTimer;
-
   HIDLoader mHID;
   XInputLoader mXInput;
+
+  nsCOMPtr<nsITimer> mXInputTimer;
+  nsCOMPtr<nsITimer> mDeviceChangeTimer;
 };
 
 
-WindowsGamepadService::WindowsGamepadService()
-{
-  nsresult rv;
-  mXInputPollTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
-  mObserver = new Observer(*this);
-}
-
 void
 WindowsGamepadService::ScanForRawInputDevices()
 {
   if (!mHID) {
     return;
   }
 
   UINT numDevices;
@@ -434,16 +382,40 @@ WindowsGamepadService::ScanForRawInputDe
 
   for (unsigned i = 0; i < devices.Length(); i++) {
     if (devices[i].dwType == RIM_TYPEHID) {
       GetRawGamepad(devices[i].hDevice);
     }
   }
 }
 
+// static
+void
+WindowsGamepadService::XInputMessageLoopOnceCallback(nsITimer *aTimer,
+                                                     void* aService)
+{
+  MOZ_ASSERT(aService);
+  WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
+  self->PollXInput();
+  if (self->mIsXInputMonitoring) {
+    aTimer->Cancel();
+    aTimer->InitWithFuncCallback(XInputMessageLoopOnceCallback, self,
+                                 kXInputPollInterval, nsITimer::TYPE_ONE_SHOT);
+  }
+}
+
+// static
+void
+WindowsGamepadService::DevicesChangeCallback(nsITimer *aTimer, void* aService)
+{
+  MOZ_ASSERT(aService);
+  WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
+  self->DevicesChanged(false);
+}
+
 bool
 WindowsGamepadService::HaveXInputGamepad(int userIndex)
 {
   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
     if (mGamepads[i].type == kXInputGamepad
         && mGamepads[i].userIndex == userIndex) {
       mGamepads[i].present = true;
       return true;
@@ -493,44 +465,36 @@ WindowsGamepadService::ScanForDevices()
   for (int i = mGamepads.Length() - 1; i >= 0; i--) {
     mGamepads[i].present = false;
   }
 
   if (mHID) {
     ScanForRawInputDevices();
   }
   if (mXInput) {
-    mXInputPollTimer->Cancel();
+    mXInputTimer->Cancel();
     if (ScanForXInputDevices()) {
-      mXInputPollTimer->InitWithFuncCallback(XInputPollTimerCallback,
-                                             this,
-                                             kXInputPollInterval,
-                                             nsITimer::TYPE_REPEATING_SLACK);
+      mIsXInputMonitoring = true;
+      mXInputTimer->InitWithFuncCallback(XInputMessageLoopOnceCallback, this,
+                                         kXInputPollInterval,
+                                         nsITimer::TYPE_ONE_SHOT);
+    } else {
+      mIsXInputMonitoring = false;
     }
   }
 
   // Look for devices that are no longer present and remove them.
   for (int i = mGamepads.Length() - 1; i >= 0; i--) {
     if (!mGamepads[i].present) {
       RemoveGamepad(mGamepads[i].id);
       mGamepads.RemoveElementAt(i);
     }
   }
 }
 
-// static
-void
-WindowsGamepadService::XInputPollTimerCallback(nsITimer* aTimer,
-                                               void* aClosure)
-{
-  WindowsGamepadService* self =
-    reinterpret_cast<WindowsGamepadService*>(aClosure);
-  self->PollXInput();
-}
-
 void
 WindowsGamepadService::PollXInput()
 {
   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
     if (mGamepads[i].type != kXInputGamepad) {
       continue;
     }
 
@@ -836,18 +800,17 @@ LONG value;
                                    &value, parsed,
                                    (PCHAR)raw->data.hid.bRawData,
                                    raw->data.hid.dwSizeHid)
           != HIDP_STATUS_SUCCESS) {
         continue;
       }
       new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
                             gamepad->axes[i].caps.LogicalMax);
-    }
-    else {
+    } else {
       ULONG value;
       if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->axes[i].caps.UsagePage, 0,
                              gamepad->axes[i].caps.Range.UsageMin, &value,
                              parsed, (PCHAR)raw->data.hid.bRawData,
                              raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
         continue;
       }
 
@@ -873,51 +836,39 @@ void
 WindowsGamepadService::Shutdown()
 {
   Cleanup();
 }
 
 void
 WindowsGamepadService::Cleanup()
 {
-  if (mXInputPollTimer) {
-    mXInputPollTimer->Cancel();
+  mIsXInputMonitoring = false;
+  if (mXInputTimer) {
+    mXInputTimer->Cancel();
+  }
+  if (mDeviceChangeTimer) {
+    mDeviceChangeTimer->Cancel();
   }
   mGamepads.Clear();
-  if (mObserver) {
-    mObserver->Stop();
-    mObserver = nullptr;
-  }
 }
 
 void
-WindowsGamepadService::DevicesChanged(DeviceChangeType type)
+WindowsGamepadService::DevicesChanged(bool aIsStablizing)
 {
-  if (type == DeviceChangeNotification) {
-    if (mObserver) {
-      mObserver->SetDeviceChangeTimer();
-    }
-  } else if (type == DeviceChangeStable) {
+  if (aIsStablizing) {
+    mDeviceChangeTimer->Cancel();
+    mDeviceChangeTimer->InitWithFuncCallback(DevicesChangeCallback, this,
+                                             kDevicesChangedStableDelay,
+                                             nsITimer::TYPE_ONE_SHOT);
+  } else {
     ScanForDevices();
   }
 }
 
-NS_IMETHODIMP
-Observer::Observe(nsISupports* aSubject,
-                  const char* aTopic,
-                  const char16_t* aData)
-{
-  if (strcmp(aTopic, "timer-callback") == 0) {
-    mSvc.DevicesChanged(WindowsGamepadService::DeviceChangeStable);
-  } else if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) {
-    Stop();
-  }
-  return NS_OK;
-}
-
 HWND sHWnd = nullptr;
 
 bool
 RegisterRawInput(HWND hwnd, bool enable)
 {
   nsTArray<RAWINPUTDEVICE> rid(ArrayLength(kUsagePages));
   rid.SetLength(ArrayLength(kUsagePages));
 
@@ -945,74 +896,141 @@ GamepadWindowProc(HWND hwnd, UINT msg, W
   const unsigned int DBT_DEVNODES_CHANGED     = 0x7;
 
   switch (msg) {
   case WM_DEVICECHANGE:
     if (wParam == DBT_DEVICEARRIVAL ||
         wParam == DBT_DEVICEREMOVECOMPLETE ||
         wParam == DBT_DEVNODES_CHANGED) {
       if (gService) {
-        gService->DevicesChanged(WindowsGamepadService::DeviceChangeNotification);
+        gService->DevicesChanged(true);
       }
     }
     break;
   case WM_INPUT:
     if (gService) {
       gService->HandleRawInput(reinterpret_cast<HRAWINPUT>(lParam));
     }
     break;
   }
   return DefWindowProc(hwnd, msg, wParam, lParam);
 }
 
+class WindowGamepadMessageLoopOnceRunnable final : public Runnable
+{
+public:
+  WindowGamepadMessageLoopOnceRunnable() {}
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
+    MSG msg;
+    while (PeekMessageW(&msg, sHWnd, 0, 0, PM_REMOVE) > 0) {
+      TranslateMessage(&msg);
+      DispatchMessage(&msg);
+    }
+    if (!sIsShutdown) {
+      NS_DispatchToCurrentThread(new WindowGamepadMessageLoopOnceRunnable());
+    }
+    return NS_OK;
+  }
+private:
+  ~WindowGamepadMessageLoopOnceRunnable() {}
+};
+
+class StartWindowsGamepadServiceRunnable final : public Runnable
+{
+public:
+  StartWindowsGamepadServiceRunnable() {}
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
+    gService = new WindowsGamepadService();
+    gService->Startup();
+
+    if (sHWnd == nullptr) {
+      WNDCLASSW wc;
+      HMODULE hSelf = GetModuleHandle(nullptr);
+
+      if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) {
+        ZeroMemory(&wc, sizeof(WNDCLASSW));
+        wc.hInstance = hSelf;
+        wc.lpfnWndProc = GamepadWindowProc;
+        wc.lpszClassName = L"MozillaGamepadClass";
+        RegisterClassW(&wc);
+      }
+
+      sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher",
+        0, 0, 0, 0, 0,
+        nullptr, nullptr, hSelf, nullptr);
+      RegisterRawInput(sHWnd, true);
+    }
+
+    // Explicitly start the message loop
+    NS_DispatchToCurrentThread(new WindowGamepadMessageLoopOnceRunnable());
+
+    return NS_OK;
+  }
+private:
+  ~StartWindowsGamepadServiceRunnable() {}
+};
+
+class StopWindowsGamepadServiceRunnable final : public Runnable
+{
+ public:
+  StopWindowsGamepadServiceRunnable() {}
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
+    if (sHWnd) {
+      RegisterRawInput(sHWnd, false);
+      DestroyWindow(sHWnd);
+      sHWnd = nullptr;
+    }
+
+    gService->Shutdown();
+    delete gService;
+    gService = nullptr;
+
+    return NS_OK;
+  }
+ private:
+  ~StopWindowsGamepadServiceRunnable() {}
+};
+
 } // namespace
 
 namespace mozilla {
 namespace dom {
 
-void StartGamepadMonitoring()
+using namespace mozilla::ipc;
+
+void
+StartGamepadMonitoring()
 {
-  if (gService) {
+  AssertIsOnBackgroundThread();
+
+  if (gMonitorThread || gService) {
     return;
   }
-
-  gService = new WindowsGamepadService();
-  gService->Startup();
-
-  if (sHWnd == nullptr) {
-    WNDCLASSW wc;
-    HMODULE hSelf = GetModuleHandle(nullptr);
-
-    if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) {
-      ZeroMemory(&wc, sizeof(WNDCLASSW));
-      wc.hInstance = hSelf;
-      wc.lpfnWndProc = GamepadWindowProc;
-      wc.lpszClassName = L"MozillaGamepadClass";
-      RegisterClassW(&wc);
-    }
-
-    sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher",
-                          0, 0, 0, 0, 0,
-                          nullptr, nullptr, hSelf, nullptr);
-    RegisterRawInput(sHWnd, true);
-  }
+  sIsShutdown = false;
+  NS_NewThread(getter_AddRefs(gMonitorThread));
+  gMonitorThread->Dispatch(new StartWindowsGamepadServiceRunnable(),
+                           NS_DISPATCH_NORMAL);
 }
 
-void StopGamepadMonitoring()
+void
+StopGamepadMonitoring()
 {
-  if (!gService) {
+  AssertIsOnBackgroundThread();
+
+  if (sIsShutdown) {
     return;
   }
-
-  if (sHWnd) {
-    RegisterRawInput(sHWnd, false);
-    DestroyWindow(sHWnd);
-    sHWnd = nullptr;
-  }
-
-  gService->Shutdown();
-  delete gService;
-  gService = nullptr;
+  sIsShutdown = true;
+  gMonitorThread->Dispatch(new StopWindowsGamepadServiceRunnable(), NS_DISPATCH_NORMAL);
+  gMonitorThread->Shutdown();
+  gMonitorThread = nullptr;
 }
 
 } // namespace dom
 } // namespace mozilla
-