Bug 1221730 - Move gamepad API to PBackground. r=qdot, r=baku
authorChih-Yi Leu <cleu@mozilla.com>
Tue, 28 Jun 2016 00:25:00 +0200
changeset 302840 342068569153f7399dbf141c6c2b36bce71888fb
parent 302839 fe54272cd8ff1fbc4afee38b38ca164db02e0172
child 302841 050df9c81c1e80c03e75905716ec95343d83bf0f
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, baku
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 - Move gamepad API to PBackground. r=qdot, r=baku
dom/base/nsGlobalWindow.cpp
dom/gamepad/GamepadFunctions.cpp
dom/gamepad/GamepadFunctions.h
dom/gamepad/GamepadManager.cpp
dom/gamepad/GamepadManager.h
dom/gamepad/GamepadMonitoring.cpp
dom/gamepad/GamepadPlatformService.cpp
dom/gamepad/GamepadPlatformService.h
dom/gamepad/GamepadServiceTest.cpp
dom/gamepad/GamepadServiceTest.h
dom/gamepad/cocoa/CocoaGamepad.cpp
dom/gamepad/ipc/GamepadEventChannelChild.cpp
dom/gamepad/ipc/GamepadEventChannelChild.h
dom/gamepad/ipc/GamepadEventChannelParent.cpp
dom/gamepad/ipc/GamepadEventChannelParent.h
dom/gamepad/ipc/PGamepadEventChannel.ipdl
dom/gamepad/linux/LinuxGamepad.cpp
dom/gamepad/moz.build
dom/gamepad/windows/WindowsGamepad.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
ipc/glue/BackgroundChildImpl.cpp
ipc/glue/BackgroundChildImpl.h
ipc/glue/BackgroundParentImpl.cpp
ipc/glue/BackgroundParentImpl.h
ipc/glue/PBackground.ipdl
widget/android/nsAppShell.cpp
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -184,17 +184,17 @@
 #include "prprf.h"
 
 #include "mozilla/dom/IDBFactory.h"
 #include "mozilla/dom/MessageChannel.h"
 #include "mozilla/dom/Promise.h"
 
 #ifdef MOZ_GAMEPAD
 #include "mozilla/dom/Gamepad.h"
-#include "mozilla/dom/GamepadService.h"
+#include "mozilla/dom/GamepadManager.h"
 #endif
 
 #include "mozilla/dom/VRDevice.h"
 
 #include "nsRefreshDriver.h"
 #include "Layers.h"
 
 #include "mozilla/AddonPathService.h"
@@ -9914,34 +9914,34 @@ void nsGlobalWindow::MaybeUpdateTouchSta
 
 void
 nsGlobalWindow::EnableGamepadUpdates()
 {
   MOZ_ASSERT(IsInnerWindow());
 
   if (mHasGamepad) {
 #ifdef MOZ_GAMEPAD
-    RefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
-    if (gamepadsvc) {
-      gamepadsvc->AddListener(this);
+    RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+    if (gamepadManager) {
+      gamepadManager->AddListener(this);
     }
 #endif
   }
 }
 
 void
 nsGlobalWindow::DisableGamepadUpdates()
 {
   MOZ_ASSERT(IsInnerWindow());
 
   if (mHasGamepad) {
 #ifdef MOZ_GAMEPAD
-    RefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
-    if (gamepadsvc) {
-      gamepadsvc->RemoveListener(this);
+    RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+    if (gamepadManager) {
+      gamepadManager->RemoveListener(this);
     }
 #endif
   }
 }
 
 void
 nsGlobalWindow::SetChromeEventHandler(EventTarget* aChromeEventHandler)
 {
@@ -13398,19 +13398,19 @@ nsGlobalWindow::HasSeenGamepadInput()
   return mHasSeenGamepadInput;
 }
 
 void
 nsGlobalWindow::SyncGamepadState()
 {
   MOZ_ASSERT(IsInnerWindow());
   if (mHasSeenGamepadInput) {
-    RefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
+    RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
     for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) {
-      gamepadsvc->SyncGamepadState(iter.Key(), iter.UserData());
+      gamepadManager->SyncGamepadState(iter.Key(), iter.UserData());
     }
   }
 }
 #endif // MOZ_GAMEPAD
 
 bool
 nsGlobalWindow::UpdateVRDevices(nsTArray<RefPtr<mozilla::dom::VRDevice>>& aDevices)
 {
deleted file mode 100644
--- a/dom/gamepad/GamepadFunctions.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/* -*- 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 "mozilla/dom/GamepadFunctions.h"
-#include "mozilla/dom/GamepadService.h"
-#include "nsHashKeys.h"
-#include "mozilla/dom/ContentParent.h"
-#include "mozilla/unused.h"
-
-namespace mozilla {
-namespace dom {
-namespace GamepadFunctions {
-
-namespace {
-// Increments as gamepads are added
-uint32_t gGamepadIndex = 0;
-}
-
-template<class T>
-void
-NotifyGamepadChange(const T& aInfo)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  GamepadChangeEvent e(aInfo);
-  nsTArray<ContentParent*> t;
-  ContentParent::GetAll(t);
-  for(uint32_t i = 0; i < t.Length(); ++i) {
-    Unused << t[i]->SendGamepadUpdate(e);
-  }
-  // If we have a GamepadService in the main process, send directly to it.
-  if (GamepadService::IsServiceRunning()) {
-    RefPtr<GamepadService> svc = GamepadService::GetService();
-    svc->Update(e);
-  }
-}
-
-uint32_t
-AddGamepad(const char* aID,
-           GamepadMappingType aMapping,
-           uint32_t aNumButtons, uint32_t aNumAxes)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-
-  int index = gGamepadIndex;
-  gGamepadIndex++;
-  GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), index,
-                 (uint32_t)aMapping, aNumButtons, aNumAxes);
-  NotifyGamepadChange<GamepadAdded>(a);
-  return index;
-}
-
-void
-RemoveGamepad(uint32_t aIndex)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  GamepadRemoved a(aIndex);
-  NotifyGamepadChange<GamepadRemoved>(a);
-}
-
-void
-NewButtonEvent(uint32_t aIndex, uint32_t aButton,
-               bool aPressed, double aValue)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  GamepadButtonInformation a(aIndex, aButton, aPressed, aValue);
-  NotifyGamepadChange<GamepadButtonInformation>(a);
-}
-
-void
-NewButtonEvent(uint32_t aIndex, uint32_t aButton,
-               bool aPressed)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  // When only a digital button is available the value will be synthesized.
-  NewButtonEvent(aIndex, aButton, aPressed, aPressed ? 1.0L : 0.0L);
-}
-
-void
-NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis,
-                 double aValue)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  GamepadAxisInformation a(aIndex, aAxis, aValue);
-  NotifyGamepadChange<GamepadAxisInformation>(a);
-}
-
-void
-ResetGamepadIndexes()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  gGamepadIndex = 0;
-}
-
-} // namespace GamepadFunctions
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/gamepad/GamepadFunctions.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/* -*- 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_dom_GamepadFunctions_h_
-#define mozilla_dom_GamepadFunctions_h_
-
-#include "mozilla/dom/GamepadBinding.h"
-
-namespace mozilla {
-namespace dom {
-namespace GamepadFunctions {
-
-// Functions for building and transmitting IPDL messages through the HAL
-// sandbox. Used by platform specific Gamepad implementations
-
-// Add a gamepad to the list of known gamepads, and return its index.
-uint32_t AddGamepad(const char* aID, GamepadMappingType aMapping,
-                    uint32_t aNumButtons, uint32_t aNumAxes);
-// Remove the gamepad at |aIndex| from the list of known gamepads.
-void RemoveGamepad(uint32_t aIndex);
-
-// Update the state of |aButton| for the gamepad at |aIndex| for all
-// windows that are listening and visible, and fire one of
-// a gamepadbutton{up,down} event at them as well.
-// aPressed is used for digital buttons, aValue is for analog buttons.
-void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed,
-                    double aValue);
-// When only a digital button is available the value will be synthesized.
-void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
-
-// Update the state of |aAxis| for the gamepad at |aIndex| for all
-// windows that are listening and visible, and fire a gamepadaxismove
-// event at them as well.
-void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue);
-
-// When shutting down the platform communications for gamepad, also reset the
-// indexes.
-void ResetGamepadIndexes();
-
-} // namespace GamepadFunctions
-} // namespace dom
-} // namespace mozilla
-
-#endif
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/GamepadManager.cpp
@@ -0,0 +1,596 @@
+/* -*- 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 "mozilla/dom/GamepadManager.h"
+
+#include "mozilla/dom/Gamepad.h"
+#include "mozilla/dom/GamepadAxisMoveEvent.h"
+#include "mozilla/dom/GamepadButtonEvent.h"
+#include "mozilla/dom/GamepadEvent.h"
+#include "mozilla/dom/GamepadEventChannelChild.h"
+#include "mozilla/dom/GamepadMonitoring.h"
+
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPtr.h"
+
+#include "nsAutoPtr.h"
+#include "nsGlobalWindow.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMWindow.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+#include "mozilla/unused.h"
+
+#include <cstddef>
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+const char* kGamepadEnabledPref = "dom.gamepad.enabled";
+const char* kGamepadEventsEnabledPref =
+  "dom.gamepad.non_standard_events.enabled";
+
+const nsTArray<RefPtr<nsGlobalWindow>>::index_type NoIndex =
+  nsTArray<RefPtr<nsGlobalWindow>>::NoIndex;
+
+bool sShutdown = false;
+
+StaticRefPtr<GamepadManager> gGamepadManagerSingleton;
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(GamepadManager, nsIObserver)
+
+GamepadManager::GamepadManager()
+  : mEnabled(false),
+    mNonstandardEventsEnabled(false),
+    mShuttingDown(false),
+    mChild(nullptr)
+{}
+
+nsresult
+GamepadManager::Init()
+{
+  mEnabled = IsAPIEnabled();
+  mNonstandardEventsEnabled =
+    Preferences::GetBool(kGamepadEventsEnabledPref, false);
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+
+  if (NS_WARN_IF(!observerService)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv;
+  rv = observerService->AddObserver(this,
+                                    NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
+                                    false);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GamepadManager::Observe(nsISupports* aSubject,
+                        const char* aTopic,
+                        const char16_t* aData)
+{
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (observerService) {
+    observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+  }
+  BeginShutdown();
+  return NS_OK;
+}
+
+void
+GamepadManager::StopMonitoring()
+{
+  if(mChild) {
+    mChild->SendGamepadListenerRemoved();
+    mChild = nullptr;
+  }
+  mGamepads.Clear();
+}
+
+void
+GamepadManager::BeginShutdown()
+{
+  mShuttingDown = true;
+  StopMonitoring();
+  // Don't let windows call back to unregister during shutdown
+  for (uint32_t i = 0; i < mListeners.Length(); i++) {
+    mListeners[i]->SetHasGamepadEventListener(false);
+  }
+  mListeners.Clear();
+  sShutdown = true;
+}
+
+void
+GamepadManager::AddListener(nsGlobalWindow* aWindow)
+{
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aWindow->IsInnerWindow());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mEnabled || mShuttingDown) {
+    return;
+  }
+
+  if (mListeners.IndexOf(aWindow) != NoIndex) {
+    return; // already exists
+  }
+
+  mListeners.AppendElement(aWindow);
+
+  // IPDL child has been created
+  if (mChild) {
+    return;
+  }
+  PBackgroundChild *actor = BackgroundChild::GetForCurrentThread();
+  //Try to get the PBackground Child actor
+  if (actor) {
+    ActorCreated(actor);
+  } else {
+    Unused << BackgroundChild::GetOrCreateForCurrentThread(this);
+  }
+}
+
+void
+GamepadManager::RemoveListener(nsGlobalWindow* aWindow)
+{
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aWindow->IsInnerWindow());
+
+  if (mShuttingDown) {
+    // Doesn't matter at this point. It's possible we're being called
+    // as a result of our own destructor here, so just bail out.
+    return;
+  }
+
+  if (mListeners.IndexOf(aWindow) == NoIndex) {
+    return; // doesn't exist
+  }
+
+  mListeners.RemoveElement(aWindow);
+
+  if (mListeners.IsEmpty()) {
+    StopMonitoring();
+  }
+}
+
+already_AddRefed<Gamepad>
+GamepadManager::GetGamepad(uint32_t aIndex) const
+{
+  RefPtr<Gamepad> gamepad;
+  if (mGamepads.Get(aIndex, getter_AddRefs(gamepad))) {
+    return gamepad.forget();
+  }
+
+  return nullptr;
+}
+
+void
+GamepadManager::AddGamepad(uint32_t aIndex,
+                           const nsAString& aId,
+                           GamepadMappingType aMapping,
+                           uint32_t aNumButtons,
+                           uint32_t aNumAxes)
+{
+  //TODO: bug 852258: get initial button/axis state
+  RefPtr<Gamepad> gamepad =
+    new Gamepad(nullptr,
+                aId,
+                0, // index is set by global window
+                aMapping,
+                aNumButtons,
+                aNumAxes);
+
+  // We store the gamepad related to its index given by the parent process,
+  // and no duplicate index is allowed.
+  MOZ_ASSERT(!mGamepads.Get(aIndex, nullptr));
+  mGamepads.Put(aIndex, gamepad);
+  NewConnectionEvent(aIndex, true);
+}
+
+void
+GamepadManager::RemoveGamepad(uint32_t aIndex)
+{
+  RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+  if (!gamepad) {
+    NS_WARNING("Trying to delete gamepad with invalid index");
+    return;
+  }
+  gamepad->SetConnected(false);
+  NewConnectionEvent(aIndex, false);
+  mGamepads.Remove(aIndex);
+}
+
+void
+GamepadManager::NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed,
+                               double aValue)
+{
+  if (mShuttingDown) {
+    return;
+  }
+
+  RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+  if (!gamepad) {
+    return;
+  }
+
+  gamepad->SetButton(aButton, aPressed, aValue);
+
+  // Hold on to listeners in a separate array because firing events
+  // can mutate the mListeners array.
+  nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners);
+  MOZ_ASSERT(!listeners.IsEmpty());
+
+  for (uint32_t i = 0; i < listeners.Length(); i++) {
+
+    MOZ_ASSERT(listeners[i]->IsInnerWindow());
+
+    // Only send events to non-background windows
+    if (!listeners[i]->AsInner()->IsCurrentInnerWindow() ||
+        listeners[i]->GetOuterWindow()->IsBackground()) {
+      continue;
+    }
+
+    bool firstTime = MaybeWindowHasSeenGamepad(listeners[i], aIndex);
+
+    RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex);
+    if (listenerGamepad) {
+      listenerGamepad->SetButton(aButton, aPressed, aValue);
+      if (firstTime) {
+        FireConnectionEvent(listeners[i], listenerGamepad, true);
+      }
+      if (mNonstandardEventsEnabled) {
+        // Fire event
+        FireButtonEvent(listeners[i], listenerGamepad, aButton, aValue);
+      }
+    }
+  }
+}
+
+void
+GamepadManager::FireButtonEvent(EventTarget* aTarget,
+                                Gamepad* aGamepad,
+                                uint32_t aButton,
+                                double aValue)
+{
+  nsString name = aValue == 1.0L ? NS_LITERAL_STRING("gamepadbuttondown") :
+                                   NS_LITERAL_STRING("gamepadbuttonup");
+  GamepadButtonEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mGamepad = aGamepad;
+  init.mButton = aButton;
+  RefPtr<GamepadButtonEvent> event =
+    GamepadButtonEvent::Constructor(aTarget, name, init);
+
+  event->SetTrusted(true);
+
+  bool defaultActionEnabled = true;
+  aTarget->DispatchEvent(event, &defaultActionEnabled);
+}
+
+void
+GamepadManager::NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue)
+{
+  if (mShuttingDown) {
+    return;
+  }
+
+  RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+  if (!gamepad) {
+    return;
+  }
+  gamepad->SetAxis(aAxis, aValue);
+
+  // Hold on to listeners in a separate array because firing events
+  // can mutate the mListeners array.
+  nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners);
+  MOZ_ASSERT(!listeners.IsEmpty());
+
+  for (uint32_t i = 0; i < listeners.Length(); i++) {
+
+    MOZ_ASSERT(listeners[i]->IsInnerWindow());
+
+    // Only send events to non-background windows
+    if (!listeners[i]->AsInner()->IsCurrentInnerWindow() ||
+        listeners[i]->GetOuterWindow()->IsBackground()) {
+      continue;
+    }
+
+    bool firstTime = MaybeWindowHasSeenGamepad(listeners[i], aIndex);
+
+    RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex);
+    if (listenerGamepad) {
+      listenerGamepad->SetAxis(aAxis, aValue);
+      if (firstTime) {
+        FireConnectionEvent(listeners[i], listenerGamepad, true);
+      }
+      if (mNonstandardEventsEnabled) {
+        // Fire event
+        FireAxisMoveEvent(listeners[i], listenerGamepad, aAxis, aValue);
+      }
+    }
+  }
+}
+
+void
+GamepadManager::FireAxisMoveEvent(EventTarget* aTarget,
+                                  Gamepad* aGamepad,
+                                  uint32_t aAxis,
+                                  double aValue)
+{
+  GamepadAxisMoveEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mGamepad = aGamepad;
+  init.mAxis = aAxis;
+  init.mValue = aValue;
+  RefPtr<GamepadAxisMoveEvent> event =
+    GamepadAxisMoveEvent::Constructor(aTarget,
+                                      NS_LITERAL_STRING("gamepadaxismove"),
+                                      init);
+
+  event->SetTrusted(true);
+
+  bool defaultActionEnabled = true;
+  aTarget->DispatchEvent(event, &defaultActionEnabled);
+}
+
+void
+GamepadManager::NewConnectionEvent(uint32_t aIndex, bool aConnected)
+{
+  if (mShuttingDown) {
+    return;
+  }
+
+  RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+  if (!gamepad) {
+    return;
+  }
+
+  // Hold on to listeners in a separate array because firing events
+  // can mutate the mListeners array.
+  nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners);
+  MOZ_ASSERT(!listeners.IsEmpty());
+
+  if (aConnected) {
+    for (uint32_t i = 0; i < listeners.Length(); i++) {
+
+      MOZ_ASSERT(listeners[i]->IsInnerWindow());
+
+      // Only send events to non-background windows
+      if (!listeners[i]->AsInner()->IsCurrentInnerWindow() ||
+          listeners[i]->GetOuterWindow()->IsBackground()) {
+        continue;
+      }
+
+      // We don't fire a connected event here unless the window
+      // has seen input from at least one device.
+      if (!listeners[i]->HasSeenGamepadInput()) {
+        continue;
+      }
+
+      SetWindowHasSeenGamepad(listeners[i], aIndex);
+
+      RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex);
+      if (listenerGamepad) {
+        // Fire event
+        FireConnectionEvent(listeners[i], listenerGamepad, aConnected);
+      }
+    }
+  } else {
+    // For disconnection events, fire one at every window that has received
+    // data from this gamepad.
+    for (uint32_t i = 0; i < listeners.Length(); i++) {
+
+      // Even background windows get these events, so we don't have to
+      // deal with the hassle of syncing the state of removed gamepads.
+
+      if (WindowHasSeenGamepad(listeners[i], aIndex)) {
+        RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex);
+        if (listenerGamepad) {
+          listenerGamepad->SetConnected(false);
+          // Fire event
+          FireConnectionEvent(listeners[i], listenerGamepad, false);
+          listeners[i]->RemoveGamepad(aIndex);
+        }
+      }
+    }
+  }
+}
+
+void
+GamepadManager::FireConnectionEvent(EventTarget* aTarget,
+                                    Gamepad* aGamepad,
+                                    bool aConnected)
+{
+  nsString name = aConnected ? NS_LITERAL_STRING("gamepadconnected") :
+                               NS_LITERAL_STRING("gamepaddisconnected");
+  GamepadEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mGamepad = aGamepad;
+  RefPtr<GamepadEvent> event =
+    GamepadEvent::Constructor(aTarget, name, init);
+
+  event->SetTrusted(true);
+
+  bool defaultActionEnabled = true;
+  aTarget->DispatchEvent(event, &defaultActionEnabled);
+}
+
+void
+GamepadManager::SyncGamepadState(uint32_t aIndex, Gamepad* aGamepad)
+{
+  if (mShuttingDown || !mEnabled) {
+    return;
+  }
+
+  RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+  if (!gamepad) {
+    return;
+  }
+
+  aGamepad->SyncState(gamepad);
+}
+
+// static
+bool
+GamepadManager::IsServiceRunning()
+{
+  return !!gGamepadManagerSingleton;
+}
+
+// static
+already_AddRefed<GamepadManager>
+GamepadManager::GetService()
+{
+  if (sShutdown) {
+    return nullptr;
+  }
+
+  if (!gGamepadManagerSingleton) {
+    RefPtr<GamepadManager> manager = new GamepadManager();
+    nsresult rv = manager->Init();
+    if(NS_WARN_IF(NS_FAILED(rv))) {
+      return nullptr;
+    }
+    gGamepadManagerSingleton = manager;
+    ClearOnShutdown(&gGamepadManagerSingleton);
+  }
+
+  RefPtr<GamepadManager> service(gGamepadManagerSingleton);
+  return service.forget();
+}
+
+// static
+bool
+GamepadManager::IsAPIEnabled() {
+  return Preferences::GetBool(kGamepadEnabledPref, false);
+}
+
+bool
+GamepadManager::MaybeWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex)
+{
+  if (!WindowHasSeenGamepad(aWindow, aIndex)) {
+    // This window hasn't seen this gamepad before, so
+    // send a connection event first.
+    SetWindowHasSeenGamepad(aWindow, aIndex);
+    return true;
+  }
+  return false;
+}
+
+bool
+GamepadManager::WindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex) const
+{
+  RefPtr<Gamepad> gamepad = aWindow->GetGamepad(aIndex);
+  return gamepad != nullptr;
+}
+
+void
+GamepadManager::SetWindowHasSeenGamepad(nsGlobalWindow* aWindow,
+                                        uint32_t aIndex,
+                                        bool aHasSeen)
+{
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aWindow->IsInnerWindow());
+
+  if (mListeners.IndexOf(aWindow) == NoIndex) {
+    // This window isn't even listening for gamepad events.
+    return;
+  }
+
+  if (aHasSeen) {
+    aWindow->SetHasSeenGamepadInput(true);
+    nsCOMPtr<nsISupports> window = ToSupports(aWindow);
+    RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+    if (!gamepad) {
+      return;
+    }
+    RefPtr<Gamepad> clonedGamepad = gamepad->Clone(window);
+    aWindow->AddGamepad(aIndex, clonedGamepad);
+  } else {
+    aWindow->RemoveGamepad(aIndex);
+  }
+}
+
+void
+GamepadManager::Update(const GamepadChangeEvent& aEvent)
+{
+  if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) {
+    const GamepadAdded& a = aEvent.get_GamepadAdded();
+    AddGamepad(a.index(), a.id(),
+               static_cast<GamepadMappingType>(a.mapping()),
+               a.num_buttons(), a.num_axes());
+    return;
+  }
+  if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) {
+    const GamepadRemoved& a = aEvent.get_GamepadRemoved();
+    RemoveGamepad(a.index());
+    return;
+  }
+  if (aEvent.type() == GamepadChangeEvent::TGamepadButtonInformation) {
+    const GamepadButtonInformation& a = aEvent.get_GamepadButtonInformation();
+    NewButtonEvent(a.index(), a.button(), a.pressed(), a.value());
+    return;
+  }
+  if (aEvent.type() == GamepadChangeEvent::TGamepadAxisInformation) {
+    const GamepadAxisInformation& a = aEvent.get_GamepadAxisInformation();
+    NewAxisMoveEvent(a.index(), a.axis(), a.value());
+    return;
+  }
+
+  MOZ_CRASH("We shouldn't be here!");
+
+}
+
+//Override nsIIPCBackgroundChildCreateCallback
+void
+GamepadManager::ActorCreated(PBackgroundChild *aActor)
+{
+  MOZ_ASSERT(aActor);
+  GamepadEventChannelChild *child = new GamepadEventChannelChild();
+  PGamepadEventChannelChild *initedChild =
+    aActor->SendPGamepadEventChannelConstructor(child);
+  if (NS_WARN_IF(!initedChild)) {
+    ActorFailed();
+    return;
+  }
+  MOZ_ASSERT(initedChild == child);
+  mChild = child;
+  mChild->SendGamepadListenerAdded();
+}
+
+//Override nsIIPCBackgroundChildCreateCallback
+void
+GamepadManager::ActorFailed()
+{
+  MOZ_CRASH("Gamepad IPC actor create failed!");
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/GamepadManager.h
@@ -0,0 +1,142 @@
+/* -*- 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_dom_GamepadManager_h_
+#define mozilla_dom_GamepadManager_h_
+
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "nsIObserver.h"
+// Needed for GamepadMappingType
+#include "mozilla/dom/GamepadBinding.h"
+
+class nsGlobalWindow;
+
+namespace mozilla {
+namespace dom {
+
+class EventTarget;
+class Gamepad;
+class GamepadChangeEvent;
+class GamepadEventChannelChild;
+
+class GamepadManager final : public nsIObserver,
+                             public nsIIPCBackgroundChildCreateCallback
+{
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
+
+  // Returns true if we actually have a service up and running
+  static bool IsServiceRunning();
+  // Get the singleton service
+  static already_AddRefed<GamepadManager> GetService();
+  // Return true if the API is preffed on.
+  static bool IsAPIEnabled();
+
+  void BeginShutdown();
+  void StopMonitoring();
+
+  // Indicate that |aWindow| wants to receive gamepad events.
+  void AddListener(nsGlobalWindow* aWindow);
+  // Indicate that |aWindow| should no longer receive gamepad events.
+  void RemoveListener(nsGlobalWindow* aWindow);
+
+  // Add a gamepad to the list of known gamepads.
+  void AddGamepad(uint32_t aIndex, const nsAString& aID, GamepadMappingType aMapping,
+                  uint32_t aNumButtons, uint32_t aNumAxes);
+
+  // Remove the gamepad at |aIndex| from the list of known gamepads.
+  void RemoveGamepad(uint32_t aIndex);
+
+  // Update the state of |aButton| for the gamepad at |aIndex| for all
+  // windows that are listening and visible, and fire one of
+  // a gamepadbutton{up,down} event at them as well.
+  // aPressed is used for digital buttons, aValue is for analog buttons.
+  void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed,
+                      double aValue);
+
+  // Update the state of |aAxis| for the gamepad at |aIndex| for all
+  // windows that are listening and visible, and fire a gamepadaxismove
+  // event at them as well.
+  void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue);
+
+  // Synchronize the state of |aGamepad| to match the gamepad stored at |aIndex|
+  void SyncGamepadState(uint32_t aIndex, Gamepad* aGamepad);
+
+  // Returns gamepad object if index exists, null otherwise
+  already_AddRefed<Gamepad> GetGamepad(uint32_t aIndex) const;
+
+  // Receive GamepadChangeEvent messages from parent process to fire DOM events
+  void Update(const GamepadChangeEvent& aGamepadEvent);
+
+ protected:
+  GamepadManager();
+  ~GamepadManager() {};
+
+  // Fire a gamepadconnected or gamepaddisconnected event for the gamepad
+  // at |aIndex| to all windows that are listening and have received
+  // gamepad input.
+  void NewConnectionEvent(uint32_t aIndex, bool aConnected);
+
+  // Fire a gamepadaxismove event to the window at |aTarget| for |aGamepad|.
+  void FireAxisMoveEvent(EventTarget* aTarget,
+                         Gamepad* aGamepad,
+                         uint32_t axis,
+                         double value);
+
+  // Fire one of gamepadbutton{up,down} event at the window at |aTarget| for
+  // |aGamepad|.
+  void FireButtonEvent(EventTarget* aTarget,
+                       Gamepad* aGamepad,
+                       uint32_t aButton,
+                       double aValue);
+
+  // Fire one of gamepad{connected,disconnected} event at the window at
+  // |aTarget| for |aGamepad|.
+  void FireConnectionEvent(EventTarget* aTarget,
+                           Gamepad* aGamepad,
+                           bool aConnected);
+
+  // true if this feature is enabled in preferences
+  bool mEnabled;
+  // true if non-standard events are enabled in preferences
+  bool mNonstandardEventsEnabled;
+  // true when shutdown has begun
+  bool mShuttingDown;
+
+  // Gamepad IPDL child
+  // This pointer is only used by this singleton instance and
+  // will be destroyed during the IPDL shutdown chain, so we
+  // don't need to refcount it here
+  GamepadEventChannelChild MOZ_NON_OWNING_REF *mChild;
+
+ private:
+
+  nsresult Init();
+
+  bool MaybeWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex);
+  // Returns true if we have already sent data from this gamepad
+  // to this window. This should only return true if the user
+  // explicitly interacted with a gamepad while this window
+  // was focused, by pressing buttons or similar actions.
+  bool WindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex) const;
+  // Indicate that a window has recieved data from a gamepad.
+  void SetWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex,
+                               bool aHasSeen = true);
+
+  // Gamepads connected to the system. Copies of these are handed out
+  // to each window.
+  nsRefPtrHashtable<nsUint32HashKey, Gamepad> mGamepads;
+  // Inner windows that are listening for gamepad events.
+  // has been sent to that window.
+  nsTArray<RefPtr<nsGlobalWindow>> mListeners;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_GamepadManager_h_
--- a/dom/gamepad/GamepadMonitoring.cpp
+++ b/dom/gamepad/GamepadMonitoring.cpp
@@ -1,33 +1,32 @@
 /* -*- 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 "mozilla/dom/GamepadMonitoring.h"
-#include "mozilla/dom/GamepadFunctions.h"
-#include "mozilla/dom/PContentParent.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace dom {
 
-using namespace GamepadFunctions;
-
 void
 MaybeStopGamepadMonitoring()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  nsTArray<ContentParent*> t;
-  ContentParent::GetAll(t);
-  for(uint32_t i = 0; i < t.Length(); ++i) {
-    if (t[i]->HasGamepadListener()) {
-      return;
-    }
+  AssertIsOnBackgroundThread();
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
+  if(service->HasGamepadListeners()) {
+    return;
   }
   StopGamepadMonitoring();
-  ResetGamepadIndexes();
+  service->ResetGamepadIndexes();
+  service->MaybeShutdown();
 }
 
 } // namespace dom
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/GamepadPlatformService.cpp
@@ -0,0 +1,220 @@
+/* -*- 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 "mozilla/dom/GamepadPlatformService.h"
+
+#include "mozilla/dom/GamepadEventChannelParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/unused.h"
+
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsIThread.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+// This is the singleton instance of GamepadPlatformService, can be called
+// by both background and monitor thread.
+StaticAutoPtr<GamepadPlatformService> gGamepadPlatformServiceSingleton;
+
+} //namepsace
+
+GamepadPlatformService::GamepadPlatformService()
+  : mGamepadIndex(0),
+    mMutex("mozilla::dom::GamepadPlatformService")
+{}
+
+GamepadPlatformService::~GamepadPlatformService()
+{
+  Cleanup();
+}
+
+// static
+GamepadPlatformService*
+GamepadPlatformService::GetParentService()
+{
+  //GamepadPlatformService can only be accessed in parent process
+  MOZ_ASSERT(XRE_IsParentProcess());
+  if(!gGamepadPlatformServiceSingleton) {
+    gGamepadPlatformServiceSingleton = new GamepadPlatformService();
+  }
+  return gGamepadPlatformServiceSingleton;
+}
+
+template<class T>
+void
+GamepadPlatformService::NotifyGamepadChange(const T& aInfo)
+{
+  // This method is called by monitor populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  GamepadChangeEvent e(aInfo);
+
+  // mChannelParents may be accessed by background thread in the
+  // same time, we use mutex to prevent possible race condtion
+  MutexAutoLock autoLock(mMutex);
+  for(uint32_t i = 0; i < mChannelParents.Length(); ++i) {
+    mChannelParents[i]->DispatchUpdateEvent(e);
+  }
+}
+
+uint32_t
+GamepadPlatformService::AddGamepad(const char* aID,
+                                   GamepadMappingType aMapping,
+                                   uint32_t aNumButtons, uint32_t aNumAxes)
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  uint32_t index = ++mGamepadIndex;
+  GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), index,
+                 (uint32_t)aMapping, aNumButtons, aNumAxes);
+  NotifyGamepadChange<GamepadAdded>(a);
+  return index;
+}
+
+void
+GamepadPlatformService::RemoveGamepad(uint32_t aIndex)
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+  GamepadRemoved a(aIndex);
+  NotifyGamepadChange<GamepadRemoved>(a);
+}
+
+void
+GamepadPlatformService::NewButtonEvent(uint32_t aIndex, uint32_t aButton,
+                                       bool aPressed, double aValue)
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+  GamepadButtonInformation a(aIndex, aButton, aPressed, aValue);
+  NotifyGamepadChange<GamepadButtonInformation>(a);
+}
+
+void
+GamepadPlatformService::NewButtonEvent(uint32_t aIndex, uint32_t aButton,
+                                       bool aPressed)
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+  // When only a digital button is available the value will be synthesized.
+  NewButtonEvent(aIndex, aButton, aPressed, aPressed ? 1.0L : 0.0L);
+}
+
+void
+GamepadPlatformService::NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis,
+                                         double aValue)
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+  GamepadAxisInformation a(aIndex, aAxis, aValue);
+  NotifyGamepadChange<GamepadAxisInformation>(a);
+}
+
+void
+GamepadPlatformService::ResetGamepadIndexes()
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+  mGamepadIndex = 0;
+}
+
+void
+GamepadPlatformService::AddChannelParent(GamepadEventChannelParent* aParent)
+{
+  // mChannelParents can only be modified once GamepadEventChannelParent
+  // is created or removed in Background thread
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParent);
+  MOZ_ASSERT(!mChannelParents.Contains(aParent));
+
+  // We use mutex here to prevent race condition with monitor thread
+  MutexAutoLock autoLock(mMutex);
+  mChannelParents.AppendElement(aParent);
+}
+
+void
+GamepadPlatformService::RemoveChannelParent(GamepadEventChannelParent* aParent)
+{
+  // mChannelParents can only be modified once GamepadEventChannelParent
+  // is created or removed in Background thread
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParent);
+  MOZ_ASSERT(mChannelParents.Contains(aParent));
+
+  // We use mutex here to prevent race condition with monitor thread
+  MutexAutoLock autoLock(mMutex);
+  mChannelParents.RemoveElement(aParent);
+}
+
+bool
+GamepadPlatformService::HasGamepadListeners()
+{
+  // mChannelParents may be accessed by background thread in the
+  // same time, we use mutex to prevent possible race condtion
+  AssertIsOnBackgroundThread();
+
+  // We use mutex here to prevent race condition with monitor thread
+  MutexAutoLock autoLock(mMutex);
+  for (uint32_t i = 0; i < mChannelParents.Length(); i++) {
+    if(mChannelParents[i]->HasGamepadListener()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void
+GamepadPlatformService::MaybeShutdown()
+{
+  // This method is invoked in MaybeStopGamepadMonitoring when
+  // an IPDL channel is going to be destroyed
+  AssertIsOnBackgroundThread();
+
+  bool isChannelParentEmpty;
+  {
+    MutexAutoLock autoLock(mMutex);
+    isChannelParentEmpty = mChannelParents.IsEmpty();
+  }
+  if(isChannelParentEmpty) {
+    gGamepadPlatformServiceSingleton = nullptr;
+  }
+}
+
+void
+GamepadPlatformService::Cleanup()
+{
+  // This method is called when GamepadPlatformService is
+  // successfully distructed in background thread
+  AssertIsOnBackgroundThread();
+
+  MutexAutoLock autoLock(mMutex);
+  mChannelParents.Clear();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/GamepadPlatformService.h
@@ -0,0 +1,94 @@
+/* -*- 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_dom_GamepadPlatformService_h_
+#define mozilla_dom_GamepadPlatformService_h_
+
+#include "mozilla/dom/GamepadBinding.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class GamepadEventChannelParent;
+
+// Platform Service for building and transmitting IPDL messages
+// through the HAL sandbox. Used by platform specific
+// Gamepad implementations
+//
+// This class can be accessed by the following 2 threads :
+// 1. Background thread:
+//    This thread takes charge of IPDL communications
+//    between here and DOM side
+//
+// 2. Monitor Thread:
+//    This thread is populated in platform-dependent backends, which
+//    is in charge of processing gamepad hardware events from OS
+class GamepadPlatformService final
+{
+ public:
+  ~GamepadPlatformService();
+  //Get the singleton service
+  static GamepadPlatformService* GetParentService();
+
+  // Add a gamepad to the list of known gamepads, and return its index.
+  uint32_t AddGamepad(const char* aID, GamepadMappingType aMapping,
+                      uint32_t aNumButtons, uint32_t aNumAxes);
+  // Remove the gamepad at |aIndex| from the list of known gamepads.
+  void RemoveGamepad(uint32_t aIndex);
+
+  // Update the state of |aButton| for the gamepad at |aIndex| for all
+  // windows that are listening and visible, and fire one of
+  // a gamepadbutton{up,down} event at them as well.
+  // aPressed is used for digital buttons, aValue is for analog buttons.
+  void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed,
+                      double aValue);
+  // When only a digital button is available the value will be synthesized.
+  void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
+
+  // Update the state of |aAxis| for the gamepad at |aIndex| for all
+  // windows that are listening and visible, and fire a gamepadaxismove
+  // event at them as well.
+  void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue);
+
+  // When shutting down the platform communications for gamepad, also reset the
+  // indexes.
+  void ResetGamepadIndexes();
+
+  //Add IPDL parent instance
+  void AddChannelParent(GamepadEventChannelParent* aParent);
+
+  //Remove IPDL parent instance
+  void RemoveChannelParent(GamepadEventChannelParent* aParent);
+
+  bool HasGamepadListeners();
+
+  void MaybeShutdown();
+
+ private:
+  GamepadPlatformService();
+  template<class T> void NotifyGamepadChange(const T& aInfo);
+  void Cleanup();
+
+  // mGamepadIndex can only be accessed by monitor thread
+  uint32_t mGamepadIndex;
+
+  // mChannelParents stores all the GamepadEventChannelParent instances
+  // which may be accessed by both background thread and monitor thread
+  // simultaneously, so we have a mutex to prevent race condition
+  nsTArray<RefPtr<GamepadEventChannelParent>> mChannelParents;
+
+  // This mutex protects mChannelParents from race condition
+  // between background and monitor thread
+  Mutex mMutex;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
--- a/dom/gamepad/GamepadServiceTest.cpp
+++ b/dom/gamepad/GamepadServiceTest.cpp
@@ -1,17 +1,17 @@
 /* -*- 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 "GamepadServiceTest.h"
-#include "mozilla/dom/GamepadService.h"
-#include "mozilla/dom/GamepadFunctions.h"
+#include "mozilla/dom/GamepadManager.h"
+#include "mozilla/dom/GamepadPlatformService.h"
 
 using namespace mozilla::dom;
 
 /*
  * Implementation of the test service. This is just to provide a simple binding
  * of the GamepadService to JavaScript via XPCOM so that we can write Mochitests
  * that add and remove fake gamepads, avoiding the platform-specific backends.
  */
@@ -26,61 +26,71 @@ GamepadServiceTest::CreateService()
   if (sSingleton == nullptr) {
     sSingleton = new GamepadServiceTest();
   }
   RefPtr<GamepadServiceTest> service = sSingleton;
   return service.forget();
 }
 
 GamepadServiceTest::GamepadServiceTest() :
-  mService(GamepadService::GetService())
+  mService(GamepadManager::GetService())
 {
 }
 
 GamepadServiceTest::~GamepadServiceTest()
 {
 }
 
 NS_IMETHODIMP
 GamepadServiceTest::AddGamepad(const char* aID,
                                uint32_t aMapping,
                                uint32_t aNumButtons,
                                uint32_t aNumAxes,
                                uint32_t* aGamepadIndex)
 {
-  *aGamepadIndex = GamepadFunctions::AddGamepad(aID,
-                                                static_cast<GamepadMappingType>(aMapping),
-                                                aNumButtons,
-                                                aNumAxes);
+  GamepadService* service = GamepadService::GetService();
+  MOZ_ASSERT(service);
+  *aGamepadIndex = service->AddGamepad(aID,
+                                       static_cast<GamepadMappingType>(aMapping),
+                                       aNumButtons,
+                                       aNumAxes);
   return NS_OK;
 }
 
 NS_IMETHODIMP GamepadServiceTest::RemoveGamepad(uint32_t aIndex)
 {
-  GamepadFunctions::RemoveGamepad(aIndex);
+  GamepadService* service = GamepadService::GetService();
+  MOZ_ASSERT(service);
+  service->RemoveGamepad(aIndex);
   return NS_OK;
 }
 
 NS_IMETHODIMP GamepadServiceTest::NewButtonEvent(uint32_t aIndex,
                                                  uint32_t aButton,
                                                  bool aPressed)
 {
-  GamepadFunctions::NewButtonEvent(aIndex, aButton, aPressed);
+  GamepadService* service = GamepadService::GetService();
+  MOZ_ASSERT(service);
+  service->NewButtonEvent(aIndex, aButton, aPressed);
   return NS_OK;
 }
 
 NS_IMETHODIMP GamepadServiceTest::NewButtonValueEvent(uint32_t aIndex,
                                                       uint32_t aButton,
                                                       bool aPressed,
                                                       double aValue)
 {
-  GamepadFunctions::NewButtonEvent(aIndex, aButton, aPressed, aValue);
+  GamepadService* service = GamepadService::GetService();
+  MOZ_ASSERT(service);
+  service->NewButtonEvent(aIndex, aButton, aPressed, aValue);
   return NS_OK;
 }
 
 NS_IMETHODIMP GamepadServiceTest::NewAxisMoveEvent(uint32_t aIndex,
                                                    uint32_t aAxis,
                                                    double aValue)
 {
-  GamepadFunctions::NewAxisMoveEvent(aIndex, aAxis, aValue);
+  GamepadService* service = GamepadService::GetService();
+  MOZ_ASSERT(service);
+  service->NewAxisMoveEvent(aIndex, aAxis, aValue);
   return NS_OK;
 }
 
--- a/dom/gamepad/GamepadServiceTest.h
+++ b/dom/gamepad/GamepadServiceTest.h
@@ -7,34 +7,34 @@
 #ifndef mozilla_dom_GamepadServiceTest_h_
 #define mozilla_dom_GamepadServiceTest_h_
 
 #include "nsIGamepadServiceTest.h"
 
 namespace mozilla {
 namespace dom {
 
-class GamepadService;
+class GamepadManager;
 
 // Service for testing purposes
 class GamepadServiceTest : public nsIGamepadServiceTest
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIGAMEPADSERVICETEST
 
   GamepadServiceTest();
 
   static already_AddRefed<GamepadServiceTest> CreateService();
 
 private:
   static GamepadServiceTest* sSingleton;
   // Hold a reference to the gamepad service so we don't have to worry about
   // execution order in tests.
-  RefPtr<GamepadService> mService;
+  RefPtr<GamepadManager> mService;
   virtual ~GamepadServiceTest();
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #define NS_GAMEPAD_TEST_CID \
 { 0xfb1fcb57, 0xebab, 0x4cf4, \
--- a/dom/gamepad/cocoa/CocoaGamepad.cpp
+++ b/dom/gamepad/cocoa/CocoaGamepad.cpp
@@ -2,30 +2,31 @@
 /* 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/. */
 
 // mostly derived from the Allegro source code at:
 // http://alleg.svn.sourceforge.net/viewvc/alleg/allegro/branches/4.9/src/macosx/hidjoy.m?revision=13760&view=markup
 
-#include "mozilla/dom/GamepadFunctions.h"
+#include "mozilla/dom/GamepadPlatformService.h"
 #include "mozilla/ArrayUtils.h"
+#include "nsThreadUtils.h"
 #include <CoreFoundation/CoreFoundation.h>
 #include <IOKit/hid/IOHIDBase.h>
 #include <IOKit/hid/IOHIDKeys.h>
 #include <IOKit/hid/IOHIDManager.h>
 
 #include <stdio.h>
 #include <vector>
 
 namespace {
 
 using namespace mozilla;
-using namespace mozilla::dom::GamepadFunctions;
+using namespace mozilla::dom;
 using std::vector;
 
 struct Button {
   int id;
   bool analog;
   IOHIDElementRef element;
   CFIndex min;
   CFIndex max;
@@ -195,32 +196,57 @@ void Gamepad::init(IOHIDDeviceRef device
   }
 }
 
 class DarwinGamepadService {
  private:
   IOHIDManagerRef mManager;
   vector<Gamepad> mGamepads;
 
+  //Workaround to support running in background thread
+  CFRunLoopRef mMonitorRunLoop;
+  nsCOMPtr<nsIThread> mMonitorThread;
+
   static void DeviceAddedCallback(void* data, IOReturn result,
                                   void* sender, IOHIDDeviceRef device);
   static void DeviceRemovedCallback(void* data, IOReturn result,
                                     void* sender, IOHIDDeviceRef device);
   static void InputValueChangedCallback(void* data, IOReturn result,
                                         void* sender, IOHIDValueRef newValue);
 
   void DeviceAdded(IOHIDDeviceRef device);
   void DeviceRemoved(IOHIDDeviceRef device);
   void InputValueChanged(IOHIDValueRef value);
+  void StartupInternal();
 
  public:
   DarwinGamepadService();
   ~DarwinGamepadService();
   void Startup();
   void Shutdown();
+  friend class DarwinGamepadServiceStartupRunnable;
+};
+
+class DarwinGamepadServiceStartupRunnable final : public Runnable
+{
+ private:
+  ~DarwinGamepadServiceStartupRunnable() {}
+  // This Runnable schedules startup of DarwinGamepadService
+  // in a new thread, pointer to DarwinGamepadService is only
+  // used by this Runnable within its thread.
+  DarwinGamepadService MOZ_NON_OWNING_REF *mService;
+ public:
+  explicit DarwinGamepadServiceStartupRunnable(DarwinGamepadService *service)
+             : mService(service) {}
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(mService);
+    mService->StartupInternal();
+    return NS_OK;
+  }
 };
 
 void
 DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device)
 {
   size_t slot = size_t(-1);
   for (size_t i = 0; i < mGamepads.size(); i++) {
     if (mGamepads[i] == device)
@@ -245,28 +271,35 @@ DarwinGamepadService::DeviceAdded(IOHIDD
   int vendorId, productId;
   CFNumberGetValue(vendorIdRef, kCFNumberIntType, &vendorId);
   CFNumberGetValue(productIdRef, kCFNumberIntType, &productId);
   char product_name[128];
   CFStringGetCString(productRef, product_name,
                      sizeof(product_name), kCFStringEncodingASCII);
   char buffer[256];
   sprintf(buffer, "%x-%x-%s", vendorId, productId, product_name);
-  mGamepads[slot].mSuperIndex = AddGamepad(buffer,
-                                           mozilla::dom::GamepadMappingType::_empty,
-                                           (int)mGamepads[slot].numButtons(),
-                                           (int)mGamepads[slot].numAxes());
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
+  uint32_t index = service->AddGamepad(buffer,
+                                       mozilla::dom::GamepadMappingType::_empty,
+                                       (int)mGamepads[slot].numButtons(),
+                                       (int)mGamepads[slot].numAxes());
+  mGamepads[slot].mSuperIndex = index;
 }
 
 void
 DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device)
 {
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
   for (size_t i = 0; i < mGamepads.size(); i++) {
     if (mGamepads[i] == device) {
-      RemoveGamepad(mGamepads[i].mSuperIndex);
+      service->RemoveGamepad(mGamepads[i].mSuperIndex);
       mGamepads[i].clear();
       return;
     }
   }
 }
 
 /*
  * Given a value from a d-pad (POV hat in USB HID terminology),
@@ -313,49 +346,54 @@ DarwinGamepadService::InputValueChanged(
   uint32_t value_length = IOHIDValueGetLength(value);
   if (value_length > 4) {
     // Workaround for bizarre issue with PS3 controllers that try to return
     // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
     return;
   }
   IOHIDElementRef element = IOHIDValueGetElement(value);
   IOHIDDeviceRef device = IOHIDElementGetDevice(element);
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
   for (unsigned i = 0; i < mGamepads.size(); i++) {
     Gamepad &gamepad = mGamepads[i];
     if (gamepad == device) {
       if (gamepad.isDpad(element)) {
         const dpad_buttons& oldState = gamepad.getDpadState();
         dpad_buttons newState = { false, false, false, false };
         UnpackDpad(IOHIDValueGetIntegerValue(value),
                    IOHIDElementGetLogicalMin(element),
                    IOHIDElementGetLogicalMax(element),
                    newState);
         const int numButtons = gamepad.numButtons();
         for (unsigned b = 0; b < ArrayLength(newState); b++) {
           if (newState[b] != oldState[b]) {
-            NewButtonEvent(gamepad.mSuperIndex, numButtons - 4 + b, newState[b]);
+            service->NewButtonEvent(gamepad.mSuperIndex,
+                                    numButtons - 4 + b,
+                                    newState[b]);
           }
         }
         gamepad.setDpadState(newState);
       } else if (const Axis* axis = gamepad.lookupAxis(element)) {
         double d = IOHIDValueGetIntegerValue(value);
         double v = 2.0f * (d - axis->min) /
           (double)(axis->max - axis->min) - 1.0f;
-        NewAxisMoveEvent(gamepad.mSuperIndex, axis->id, v);
+        service->NewAxisMoveEvent(gamepad.mSuperIndex, axis->id, v);
       } else if (const Button* button = gamepad.lookupButton(element)) {
         int iv = IOHIDValueGetIntegerValue(value);
         bool pressed = iv != 0;
         double v = 0;
         if (button->analog) {
           double dv = iv;
           v = (dv - button->min) / (double)(button->max - button->min);
         } else {
           v = pressed ? 1.0 : 0.0;
         }
-        NewButtonEvent(gamepad.mSuperIndex, button->id, pressed, v);
+        service->NewButtonEvent(gamepad.mSuperIndex, button->id, pressed, v);
       }
       return;
     }
   }
 }
 
 void
 DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result,
@@ -417,17 +455,18 @@ MatchingDictionary(UInt32 inUsagePage, U
 DarwinGamepadService::DarwinGamepadService() : mManager(nullptr) {}
 
 DarwinGamepadService::~DarwinGamepadService()
 {
   if (mManager != nullptr)
     CFRelease(mManager);
 }
 
-void DarwinGamepadService::Startup()
+void
+DarwinGamepadService::StartupInternal()
 {
   if (mManager != nullptr)
     return;
 
   IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault,
                                                kIOHIDOptionsTypeNone);
 
   CFMutableDictionaryRef criteria_arr[2];
@@ -474,26 +513,44 @@ void DarwinGamepadService::Startup()
                                   kCFRunLoopDefaultMode);
   IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone);
   if (rv != kIOReturnSuccess) {
     CFRelease(manager);
     return;
   }
 
   mManager = manager;
+
+  // We held the handle of the CFRunLoop to make sure we
+  // can shut it down explicitly by CFRunLoopStop in another
+  // thread.
+  mMonitorRunLoop = CFRunLoopGetCurrent();
+
+  // CFRunLoopRun() is a blocking message loop when it's called in
+  // non-main thread so this thread cannot receive any other runnables
+  // and nsITimer timeout events after it's called.
+  CFRunLoopRun();
+}
+
+void DarwinGamepadService::Startup()
+{
+  Unused << NS_NewThread(getter_AddRefs(mMonitorThread),
+                         new DarwinGamepadServiceStartupRunnable(this));
 }
 
 void DarwinGamepadService::Shutdown()
 {
   IOHIDManagerRef manager = (IOHIDManagerRef)mManager;
+  CFRunLoopStop(mMonitorRunLoop);
   if (manager) {
     IOHIDManagerClose(manager, 0);
     CFRelease(manager);
     mManager = nullptr;
   }
+  mMonitorThread->Shutdown();
 }
 
 } // namespace
 
 namespace mozilla {
 namespace dom {
 
 DarwinGamepadService* gService = nullptr;
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelChild.cpp
@@ -0,0 +1,42 @@
+/* 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 "GamepadEventChannelChild.h"
+#include "mozilla/dom/GamepadManager.h"
+
+namespace mozilla {
+namespace dom{
+
+namespace {
+
+class GamepadUpdateRunnable final : public Runnable
+{
+ public:
+  explicit GamepadUpdateRunnable(const GamepadChangeEvent& aGamepadEvent)
+             : mEvent(aGamepadEvent) {}
+  NS_IMETHOD Run() override
+  {
+    RefPtr<GamepadManager> svc(GamepadManager::GetService());
+    if (svc) {
+      svc->Update(mEvent);
+    }
+    return NS_OK;
+  }
+ protected:
+  GamepadChangeEvent mEvent;
+};
+
+} // namespace
+
+bool
+GamepadEventChannelChild::RecvGamepadUpdate(
+                                       const GamepadChangeEvent& aGamepadEvent)
+{
+  nsresult rv;
+  rv = NS_DispatchToMainThread(new GamepadUpdateRunnable(aGamepadEvent));
+  NS_WARN_IF(NS_FAILED(rv));
+  return true;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelChild.h
@@ -0,0 +1,24 @@
+/* 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 "mozilla/dom/PGamepadEventChannelChild.h"
+
+#ifndef mozilla_dom_GamepadEventChannelChild_h_
+#define mozilla_dom_GamepadEventChannelChild_h_
+
+namespace mozilla{
+namespace dom{
+
+class GamepadEventChannelChild final : public PGamepadEventChannelChild
+{
+ public:
+  GamepadEventChannelChild() {}
+  ~GamepadEventChannelChild() {}
+  virtual bool
+  RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override;
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.cpp
@@ -0,0 +1,101 @@
+/* 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 "GamepadEventChannelParent.h"
+#include "GamepadPlatformService.h"
+#include "mozilla/dom/GamepadMonitoring.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace mozilla::ipc;
+
+namespace {
+
+class SendGamepadUpdateRunnable final : public Runnable
+{
+ private:
+  ~SendGamepadUpdateRunnable() {}
+  RefPtr<GamepadEventChannelParent> mParent;
+  GamepadChangeEvent mEvent;
+ public:
+  SendGamepadUpdateRunnable(GamepadEventChannelParent* aParent,
+                            GamepadChangeEvent aEvent)
+    : mEvent(aEvent)
+  {
+    MOZ_ASSERT(aParent);
+    mParent = aParent;
+  }
+  NS_IMETHOD Run() override
+  {
+    AssertIsOnBackgroundThread();
+    if(mParent->HasGamepadListener()) {
+      Unused << mParent->SendGamepadUpdate(mEvent);
+    }
+    return NS_OK;
+  }
+};
+
+} // namespace
+
+GamepadEventChannelParent::GamepadEventChannelParent()
+  : mHasGamepadListener(false)
+{
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
+  service->AddChannelParent(this);
+  mBackgroundThread = NS_GetCurrentThread();
+}
+
+bool
+GamepadEventChannelParent::RecvGamepadListenerAdded()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mHasGamepadListener);
+  mHasGamepadListener = true;
+  StartGamepadMonitoring();
+  return true;
+}
+
+bool
+GamepadEventChannelParent::RecvGamepadListenerRemoved()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mHasGamepadListener);
+  mHasGamepadListener = false;
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
+  service->RemoveChannelParent(this);
+  Unused << Send__delete__(this);
+  return true;
+}
+
+void
+GamepadEventChannelParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnBackgroundThread();
+
+  // It may be called because IPDL child side crashed, we'll
+  // not receive RecvGamepadListenerRemoved in that case
+  if (mHasGamepadListener) {
+    mHasGamepadListener = false;
+    GamepadPlatformService* service =
+      GamepadPlatformService::GetParentService();
+    MOZ_ASSERT(service);
+    service->RemoveChannelParent(this);
+  }
+  MaybeStopGamepadMonitoring();
+}
+
+void
+GamepadEventChannelParent::DispatchUpdateEvent(const GamepadChangeEvent& aEvent)
+{
+  mBackgroundThread->Dispatch(new SendGamepadUpdateRunnable(this, aEvent),
+                              NS_DISPATCH_NORMAL);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.h
@@ -0,0 +1,31 @@
+/* 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 "mozilla/dom/PGamepadEventChannelParent.h"
+
+#ifndef mozilla_dom_GamepadEventChannelParent_h_
+#define mozilla_dom_GamepadEventChannelParent_h_
+
+namespace mozilla{
+namespace dom{
+
+class GamepadEventChannelParent final : public PGamepadEventChannelParent
+{
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadEventChannelParent)
+  GamepadEventChannelParent();
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+  virtual bool RecvGamepadListenerAdded() override;
+  virtual bool RecvGamepadListenerRemoved() override;
+  void DispatchUpdateEvent(const GamepadChangeEvent& aEvent);
+  bool HasGamepadListener() const { return mHasGamepadListener; }
+ private:
+  ~GamepadEventChannelParent() {}
+  bool mHasGamepadListener;
+  nsCOMPtr<nsIThread> mBackgroundThread;
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/PGamepadEventChannel.ipdl
@@ -0,0 +1,52 @@
+/* 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 protocol PBackground;
+
+namespace mozilla {
+namespace dom {
+
+struct GamepadAdded {
+  nsString id;
+  uint32_t index;
+  uint32_t mapping;
+  uint32_t num_buttons;
+  uint32_t num_axes;
+};
+
+struct GamepadRemoved {
+  uint32_t index;
+};
+
+struct GamepadAxisInformation {
+  uint32_t index;
+  uint32_t axis;
+  double value;
+};
+
+struct GamepadButtonInformation {
+  uint32_t index;
+  uint32_t button;
+  bool pressed;
+  double value;
+};
+
+union GamepadChangeEvent {
+  GamepadAdded;
+  GamepadRemoved;
+  GamepadAxisInformation;
+  GamepadButtonInformation;
+};
+
+async protocol PGamepadEventChannel {
+  manager PBackground;
+  parent:
+    async GamepadListenerAdded();
+    async GamepadListenerRemoved();
+  child:
+    async __delete__();
+    async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
+};
+
+}
+}
--- a/dom/gamepad/linux/LinuxGamepad.cpp
+++ b/dom/gamepad/linux/LinuxGamepad.cpp
@@ -14,22 +14,22 @@
 
 #include <glib.h>
 #include <linux/joystick.h>
 #include <stdio.h>
 #include <stdint.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
 #include "nscore.h"
-#include "mozilla/dom/GamepadFunctions.h"
+#include "mozilla/dom/GamepadPlatformService.h"
 #include "udev.h"
 
 namespace {
 
-using namespace mozilla::dom::GamepadFunctions;
+using namespace mozilla::dom;
 using mozilla::udev_lib;
 using mozilla::udev_device;
 using mozilla::udev_list_entry;
 using mozilla::udev_enumerate;
 using mozilla::udev_monitor;
 
 static const float kMaxAxisValue = 32767.0;
 static const char kJoystickPath[] = "/dev/input/js";
@@ -129,25 +129,28 @@ LinuxGamepadService::AddDevice(struct ud
   }
   snprintf(gamepad.idstring, sizeof(gamepad.idstring),
            "%s-%s-%s",
            vendor_id ? vendor_id : "unknown",
            model_id ? model_id : "unknown",
            name);
 
   char numAxes = 0, numButtons = 0;
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
   ioctl(fd, JSIOCGAXES, &numAxes);
   gamepad.numAxes = numAxes;
   ioctl(fd, JSIOCGBUTTONS, &numButtons);
   gamepad.numButtons = numButtons;
 
-  gamepad.index = AddGamepad(gamepad.idstring,
-                             mozilla::dom::GamepadMappingType::_empty,
-                             gamepad.numButtons,
-                             gamepad.numAxes);
+  gamepad.index = service->AddGamepad(gamepad.idstring,
+                                      mozilla::dom::GamepadMappingType::_empty,
+                                      gamepad.numButtons,
+                                      gamepad.numAxes);
 
   gamepad.source_id =
     g_io_add_watch(channel,
                    GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
                    OnGamepadData,
                    GINT_TO_POINTER(gamepad.index));
   g_io_channel_unref(channel);
 
@@ -156,21 +159,23 @@ LinuxGamepadService::AddDevice(struct ud
 
 void
 LinuxGamepadService::RemoveDevice(struct udev_device* dev)
 {
   const char* devpath = mUdev.udev_device_get_devnode(dev);
   if (!devpath) {
     return;
   }
-
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
     if (strcmp(mGamepads[i].devpath, devpath) == 0) {
       g_source_remove(mGamepads[i].source_id);
-      RemoveGamepad(mGamepads[i].index);
+      service->RemoveGamepad(mGamepads[i].index);
       mGamepads.RemoveElementAt(i);
       break;
     }
   }
 }
 
 void
 LinuxGamepadService::ScanForDevices()
@@ -291,16 +296,19 @@ LinuxGamepadService::ReadUdevChange()
 
 // static
 gboolean
 LinuxGamepadService::OnGamepadData(GIOChannel* source,
                                    GIOCondition condition,
                                    gpointer data)
 {
   int index = GPOINTER_TO_INT(data);
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
   //TODO: remove gamepad?
   if (condition & G_IO_ERR || condition & G_IO_HUP)
     return FALSE;
 
   while (true) {
     struct js_event event;
     gsize count;
     GError* err = nullptr;
@@ -315,21 +323,21 @@ LinuxGamepadService::OnGamepadData(GIOCh
 
     //TODO: store device state?
     if (event.type & JS_EVENT_INIT) {
       continue;
     }
 
     switch (event.type) {
     case JS_EVENT_BUTTON:
-      NewButtonEvent(index, event.number, !!event.value);
+      service->NewButtonEvent(index, event.number, !!event.value);
       break;
     case JS_EVENT_AXIS:
-      NewAxisMoveEvent(index, event.number,
-                       ((float)event.value) / kMaxAxisValue);
+      service->NewAxisMoveEvent(index, event.number,
+                                ((float)event.value) / kMaxAxisValue);
       break;
     }
   }
 
   return TRUE;
 }
 
 // static
--- a/dom/gamepad/moz.build
+++ b/dom/gamepad/moz.build
@@ -2,29 +2,33 @@
 # vim: set filetype=python:
 # 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/.
 
 EXPORTS.mozilla.dom += [
     'Gamepad.h',
     'GamepadButton.h',
-    'GamepadFunctions.h',
+    'GamepadManager.h',
     'GamepadMonitoring.h',
-    'GamepadService.h',
-    'GamepadServiceTest.h'
+    'GamepadPlatformService.h',
+    'GamepadServiceTest.h',
+    'ipc/GamepadEventChannelChild.h',
+    'ipc/GamepadEventChannelParent.h'
     ]
 
 UNIFIED_SOURCES = [
     'Gamepad.cpp',
     'GamepadButton.cpp',
-    'GamepadFunctions.cpp',
+    'GamepadManager.cpp',
     'GamepadMonitoring.cpp',
-    'GamepadService.cpp',
-    'GamepadServiceTest.cpp'
+    'GamepadPlatformService.cpp',
+    'GamepadServiceTest.cpp',
+    'ipc/GamepadEventChannelChild.cpp',
+    'ipc/GamepadEventChannelParent.cpp'
     ]
 
 if CONFIG['MOZ_GAMEPAD_BACKEND'] == 'stub':
     UNIFIED_SOURCES += [
         'fallback/FallbackGamepad.cpp'
     ]
 elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'cocoa':
     UNIFIED_SOURCES += [
@@ -38,16 +42,24 @@ elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'l
     UNIFIED_SOURCES += [
         'linux/LinuxGamepad.cpp'
     ]
 elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'android':
     UNIFIED_SOURCES += [
         'android/AndroidGamepad.cpp'
     ]
 
+LOCAL_INCLUDES += [
+    'ipc',
+]
+
+IPDL_SOURCES += [
+    'ipc/PGamepadEventChannel.ipdl'
+]
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base',
 ]
 
 CFLAGS += CONFIG['GLIB_CFLAGS']
--- a/dom/gamepad/windows/WindowsGamepad.cpp
+++ b/dom/gamepad/windows/WindowsGamepad.cpp
@@ -14,26 +14,27 @@
 #include <hidsdi.h>
 #include <stdio.h>
 #include <xinput.h>
 
 #include "nsIComponentManager.h"
 #include "nsITimer.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
+
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/Services.h"
+
 #include "mozilla/ipc/BackgroundParent.h"
-#include "mozilla/dom/GamepadFunctions.h"
-#include "mozilla/Services.h"
+#include "mozilla/dom/GamepadPlatformService.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;
 
 // USB HID usage tables
@@ -437,27 +438,30 @@ WindowsGamepadService::ScanForXInputDevi
     }
     found = true;
     // See if this device is already present in our list.
     if (HaveXInputGamepad(i)) {
       continue;
     }
 
     // Not already present, add it.
+    GamepadPlatformService* service =
+      GamepadPlatformService::GetParentService();
+    MOZ_ASSERT(service);
     Gamepad gamepad = {};
     gamepad.type = kXInputGamepad;
     gamepad.present = true;
     gamepad.state = state;
     gamepad.userIndex = i;
     gamepad.numButtons = kStandardGamepadButtons;
     gamepad.numAxes = kStandardGamepadAxes;
-    gamepad.id = AddGamepad("xinput",
-                            GamepadMappingType::Standard,
-                            kStandardGamepadButtons,
-                            kStandardGamepadAxes);
+    gamepad.id = service->AddGamepad("xinput",
+                                     GamepadMappingType::Standard,
+                                     kStandardGamepadButtons,
+                                     kStandardGamepadAxes);
     mGamepads.AppendElement(gamepad);
   }
 
   return found;
 }
 
 void
 WindowsGamepadService::ScanForDevices()
@@ -477,19 +481,22 @@ WindowsGamepadService::ScanForDevices()
                                          kXInputPollInterval,
                                          nsITimer::TYPE_ONE_SHOT);
     } else {
       mIsXInputMonitoring = false;
     }
   }
 
   // Look for devices that are no longer present and remove them.
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
   for (int i = mGamepads.Length() - 1; i >= 0; i--) {
     if (!mGamepads[i].present) {
-      RemoveGamepad(mGamepads[i].id);
+      service->RemoveGamepad(mGamepads[i].id);
       mGamepads.RemoveElementAt(i);
     }
   }
 }
 
 void
 WindowsGamepadService::PollXInput()
 {
@@ -504,60 +511,63 @@ WindowsGamepadService::PollXInput()
         && state.dwPacketNumber != mGamepads[i].state.dwPacketNumber) {
         CheckXInputChanges(mGamepads[i], state);
     }
   }
 }
 
 void WindowsGamepadService::CheckXInputChanges(Gamepad& gamepad,
                                                XINPUT_STATE& state) {
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
   // Handle digital buttons first
   for (size_t b = 0; b < kNumMappings; b++) {
     if (state.Gamepad.wButtons & kXIButtonMap[b].button &&
         !(gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button)) {
       // Button pressed
-      NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, true);
+      service->NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, true);
     } else if (!(state.Gamepad.wButtons & kXIButtonMap[b].button) &&
                gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button) {
       // Button released
-      NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, false);
+      service->NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, false);
     }
   }
 
   // Then triggers
   if (state.Gamepad.bLeftTrigger != gamepad.state.Gamepad.bLeftTrigger) {
     bool pressed =
       state.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
-    NewButtonEvent(gamepad.id, kButtonLeftTrigger,
-                   pressed, state.Gamepad.bLeftTrigger / 255.0);
+    service->NewButtonEvent(gamepad.id, kButtonLeftTrigger,
+                            pressed, state.Gamepad.bLeftTrigger / 255.0);
   }
   if (state.Gamepad.bRightTrigger != gamepad.state.Gamepad.bRightTrigger) {
     bool pressed =
       state.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
-    NewButtonEvent(gamepad.id, kButtonRightTrigger,
-                   pressed, state.Gamepad.bRightTrigger / 255.0);
+    service->NewButtonEvent(gamepad.id, kButtonRightTrigger,
+                            pressed, state.Gamepad.bRightTrigger / 255.0);
   }
 
   // Finally deal with analog sticks
   // TODO: bug 1001955 - Support deadzones.
   if (state.Gamepad.sThumbLX != gamepad.state.Gamepad.sThumbLX) {
-    NewAxisMoveEvent(gamepad.id, kLeftStickXAxis,
-                     state.Gamepad.sThumbLX / 32767.0);
+    service->NewAxisMoveEvent(gamepad.id, kLeftStickXAxis,
+                              state.Gamepad.sThumbLX / 32767.0);
   }
   if (state.Gamepad.sThumbLY != gamepad.state.Gamepad.sThumbLY) {
-    NewAxisMoveEvent(gamepad.id, kLeftStickYAxis,
-                     -1.0 * state.Gamepad.sThumbLY / 32767.0);
+    service->NewAxisMoveEvent(gamepad.id, kLeftStickYAxis,
+                              -1.0 * state.Gamepad.sThumbLY / 32767.0);
   }
   if (state.Gamepad.sThumbRX != gamepad.state.Gamepad.sThumbRX) {
-    NewAxisMoveEvent(gamepad.id, kRightStickXAxis,
-                     state.Gamepad.sThumbRX / 32767.0);
+    service->NewAxisMoveEvent(gamepad.id, kRightStickXAxis,
+                              state.Gamepad.sThumbRX / 32767.0);
   }
   if (state.Gamepad.sThumbRY != gamepad.state.Gamepad.sThumbRY) {
-    NewAxisMoveEvent(gamepad.id, kRightStickYAxis,
-                     -1.0 * state.Gamepad.sThumbRY / 32767.0);
+    service->NewAxisMoveEvent(gamepad.id, kRightStickYAxis,
+                              -1.0 * state.Gamepad.sThumbRY / 32767.0);
   }
   gamepad.state = state;
 }
 
 // Used to sort a list of axes by HID usage.
 class HidValueComparator {
 public:
   bool Equals(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const
@@ -708,20 +718,23 @@ WindowsGamepadService::GetRawGamepad(HAN
       break;
     }
     gamepad.axes[i].caps = axes[i];
   }
   gamepad.type = kRawInputGamepad;
   gamepad.handle = handle;
   gamepad.present = true;
 
-  gamepad.id = GamepadFunctions::AddGamepad(gamepad_id,
-                                            GamepadMappingType::_empty,
-                                            gamepad.numButtons,
-                                            gamepad.numAxes);
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
+  gamepad.id = service->AddGamepad(gamepad_id,
+                                   GamepadMappingType::_empty,
+                                   gamepad.numButtons,
+                                   gamepad.numAxes);
   mGamepads.AppendElement(gamepad);
   return true;
 }
 
 bool
 WindowsGamepadService::HandleRawInput(HRAWINPUT handle)
 {
   if (!mHID) {
@@ -755,16 +768,19 @@ WindowsGamepadService::HandleRawInput(HR
   nsTArray<uint8_t> parsedbytes;
   if (!GetPreparsedData(raw->header.hDevice, parsedbytes)) {
     return false;
   }
   PHIDP_PREPARSED_DATA parsed =
     reinterpret_cast<PHIDP_PREPARSED_DATA>(parsedbytes.Elements());
 
   // Get all the pressed buttons.
+  GamepadPlatformService* service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
   nsTArray<USAGE> usages(gamepad->numButtons);
   usages.SetLength(gamepad->numButtons);
   ULONG usageLength = gamepad->numButtons;
   if (mHID.mHidP_GetUsages(HidP_Input, kButtonUsagePage, 0, usages.Elements(),
                      &usageLength, parsed, (PCHAR)raw->data.hid.bRawData,
                      raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
     return false;
   }
@@ -780,17 +796,17 @@ WindowsGamepadService::HandleRawInput(HR
     ULONG value;
     if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->dpadCaps.UsagePage, 0, gamepad->dpadCaps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) {
       UnpackDpad(static_cast<LONG>(value), gamepad, buttons);
     }
   }
 
   for (unsigned i = 0; i < gamepad->numButtons; i++) {
     if (gamepad->buttons[i] != buttons[i]) {
-      NewButtonEvent(gamepad->id, i, buttons[i]);
+      service->NewButtonEvent(gamepad->id, i, buttons[i]);
       gamepad->buttons[i] = buttons[i];
     }
   }
 
   // Get all axis values.
   for (unsigned i = 0; i < gamepad->numAxes; i++) {
     double new_value;
     if (gamepad->axes[i].caps.LogicalMin < 0) {
@@ -813,17 +829,17 @@ LONG value;
                              raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
         continue;
       }
 
       new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
                             gamepad->axes[i].caps.LogicalMax);
     }
     if (gamepad->axes[i].value != new_value) {
-      NewAxisMoveEvent(gamepad->id, i, new_value);
+      service->NewAxisMoveEvent(gamepad->id, i, new_value);
       gamepad->axes[i].value = new_value;
     }
   }
 
   return true;
 }
 
 void
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -168,20 +168,16 @@
 #include "nsIAccessibilityService.h"
 #endif
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 #include "NuwaChild.h"
 
-#ifdef MOZ_GAMEPAD
-#include "mozilla/dom/GamepadService.h"
-#endif
-
 #ifndef MOZ_SIMPLEPUSH
 #include "mozilla/dom/PushNotifier.h"
 #endif
 
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/cellbroadcast/CellBroadcastIPCService.h"
 #include "mozilla/dom/icc/IccChild.h"
 #include "mozilla/dom/mobileconnection/MobileConnectionChild.h"
@@ -3225,28 +3221,16 @@ ContentChild::RecvPWebBrowserPersistDocu
 bool
 ContentChild::DeallocPWebBrowserPersistDocumentChild(PWebBrowserPersistDocumentChild* aActor)
 {
   delete aActor;
   return true;
 }
 
 bool
-ContentChild::RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent)
-{
-#ifdef MOZ_GAMEPAD
-  RefPtr<GamepadService> svc(GamepadService::GetService());
-  if (svc) {
-    svc->Update(aGamepadEvent);
-  }
-#endif
-  return true;
-}
-
-bool
 ContentChild::RecvSetAudioSessionData(const nsID& aId,
                                       const nsString& aDisplayName,
                                       const nsString& aIconPath)
 {
 #if defined(XP_WIN)
     if (NS_FAILED(mozilla::widget::RecvAudioSessionData(aId, aDisplayName,
                                                         aIconPath))) {
       return true;
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -614,18 +614,16 @@ public:
 
   virtual PContentPermissionRequestChild*
   AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests,
                                       const IPC::Principal& aPrincipal,
                                       const TabId& aTabId) override;
   virtual bool
   DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor) override;
 
-  virtual bool RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override;
-
   // Windows specific - set up audio session
   virtual bool
   RecvSetAudioSessionData(const nsID& aId,
                           const nsString& aDisplayName,
                           const nsString& aIconPath) override;
 
 private:
   static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -256,20 +256,16 @@ using namespace mozilla::system;
 #include "nsIBrowserSearchService.h"
 #endif
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
 #include "nsIProfiler.h"
 #include "nsIProfileSaveEvent.h"
 #endif
 
-#ifdef MOZ_GAMEPAD
-#include "mozilla/dom/GamepadMonitoring.h"
-#endif
-
 #ifndef MOZ_SIMPLEPUSH
 #include "mozilla/dom/PushNotifier.h"
 #endif
 
 #ifdef XP_WIN
 #include "mozilla/widget/AudioSession.h"
 #endif
 
@@ -2303,17 +2299,16 @@ ContentParent::InitializeMembers()
   mMetamorphosed = false;
   mSendPermissionUpdates = false;
   mCalledClose = false;
   mCalledKillHard = false;
   mCreatedPairedMinidumps = false;
   mShutdownPending = false;
   mIPCOpen = true;
   mHangMonitorActor = nullptr;
-  mHasGamepadListener = false;
 }
 
 bool
 ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */)
 {
   PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
 
   std::vector<std::string> extraArgs;
@@ -5623,44 +5618,16 @@ ContentParent::GetBrowserConfiguration(c
     swr->GetRegistrations(aConfig.serviceWorkerRegistrations());
     return true;
   }
 
   return ContentChild::GetSingleton()->SendGetBrowserConfiguration(aURI, &aConfig);
 }
 
 bool
-ContentParent::RecvGamepadListenerAdded()
-{
-#ifdef MOZ_GAMEPAD
-  if (mHasGamepadListener) {
-    NS_WARNING("Gamepad listener already started, cannot start again!");
-    return false;
-  }
-  mHasGamepadListener = true;
-  StartGamepadMonitoring();
-#endif
-  return true;
-}
-
-bool
-ContentParent::RecvGamepadListenerRemoved()
-{
-#ifdef MOZ_GAMEPAD
-  if (!mHasGamepadListener) {
-    NS_WARNING("Gamepad listener already stopped, cannot stop again!");
-    return false;
-  }
-  mHasGamepadListener = false;
-  MaybeStopGamepadMonitoring();
-#endif
-  return true;
-}
-
-bool
 ContentParent::RecvProfile(const nsCString& aProfile)
 {
 #ifdef MOZ_ENABLE_PROFILER_SPS
   if (NS_WARN_IF(!mGatherer)) {
     return true;
   }
   mProfile = aProfile;
   mGatherer->GatheredOOPProfile();
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -541,18 +541,16 @@ public:
                                        const IPC::Principal& aPrincipal,
                                        const TabId& aTabId) override;
 
   virtual bool
   DeallocPContentPermissionRequestParent(PContentPermissionRequestParent* actor) override;
 
   virtual bool HandleWindowsMessages(const Message& aMsg) const override;
 
-  bool HasGamepadListener() const { return mHasGamepadListener; }
-
   void SetNuwaParent(NuwaParent* aNuwaParent) { mNuwaParent = aNuwaParent; }
 
   void ForkNewProcess(bool aBlocking);
 
   virtual bool RecvCreateWindow(PBrowserParent* aThisTabParent,
                                 PBrowserParent* aOpener,
                                 layout::PRenderFrameParent* aRenderFrame,
                                 const uint32_t& aChromeFlags,
@@ -1140,20 +1138,16 @@ private:
 
 
   virtual bool RecvUpdateDropEffect(const uint32_t& aDragAction,
                                     const uint32_t& aDropEffect) override;
 
   virtual bool RecvGetBrowserConfiguration(const nsCString& aURI,
                                            BrowserConfiguration* aConfig) override;
 
-  virtual bool RecvGamepadListenerAdded() override;
-
-  virtual bool RecvGamepadListenerRemoved() override;
-
   virtual bool RecvProfile(const nsCString& aProfile) override;
 
   virtual bool RecvGetGraphicsDeviceInitData(DeviceInitData* aOut) override;
 
   void StartProfiler(nsIProfilerStartParams* aParams);
 
   virtual bool RecvGetDeviceStorageLocation(const nsString& aType,
                                             nsString* aPath) override;
@@ -1220,17 +1214,16 @@ private:
 
   // True only the if process is already a browser or app or has
   // been transformed into one.
   bool mMetamorphosed;
 
   bool mSendPermissionUpdates;
   bool mIsForBrowser;
   bool mIsNuwaProcess;
-  bool mHasGamepadListener;
 
   // These variables track whether we've called Close() and KillHard() on our
   // channel.
   bool mCalledClose;
   bool mCalledKillHard;
   bool mCreatedPairedMinidumps;
   bool mShutdownPending;
   bool mIPCOpen;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -334,47 +334,17 @@ struct DomainPolicyClone
 {
     bool        active;
     URIParams[] blacklist;
     URIParams[] whitelist;
     URIParams[] superBlacklist;
     URIParams[] superWhitelist;
 };
 
-struct GamepadAdded {
-    nsString id;
-    uint32_t index;
-    uint32_t mapping;
-    uint32_t num_buttons;
-    uint32_t num_axes;
-};
 
-struct GamepadRemoved {
-    uint32_t index;
-};
-
-struct GamepadAxisInformation {
-    uint32_t index;
-    uint32_t axis;
-    double value;
-};
-
-struct GamepadButtonInformation {
-    uint32_t index;
-    uint32_t button;
-    bool pressed;
-    double value;
-};
-
-union GamepadChangeEvent {
-    GamepadAdded;
-    GamepadRemoved;
-    GamepadAxisInformation;
-    GamepadButtonInformation;
-};
 
 struct FrameScriptInfo
 {
     nsString url;
     bool runInGlobalScope;
 };
 
 struct AndroidSystemInfo
@@ -632,21 +602,16 @@ child:
 
     /**
      * Requests a full native update of a native plugin child window. This is
      * a Windows specific call.
      */
     async UpdateWindow(uintptr_t aChildId);
 
     /**
-     * Send gamepad status update to child.
-     */
-    async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
-
-    /**
      * Notify the child that presentation receiver has been launched with the
      * correspondent iframe.
      */
     async NotifyPresentationReceiverLaunched(PBrowser aIframe, nsString aSessionId);
 
     /**
      * Notify the child that the info about a presentation receiver needs to be
      * cleaned up.
@@ -1122,26 +1087,16 @@ parent:
                                     TabId tabId);
 
     /**
      * Send ServiceWorkerRegistrationData to child process.
      */
     sync GetBrowserConfiguration(nsCString aUri)
         returns (BrowserConfiguration aConfig);
 
-    /*
-     * Tells the parent to start the gamepad listening service if it hasn't already.
-     */
-    async GamepadListenerAdded();
-
-    /**
-     * Tells the parent to stop the gamepad listening service if it hasn't already.
-     */
-    async GamepadListenerRemoved();
-
     async Profile(nsCString aProfile);
 
     /**
      * Request graphics initialization information from the parent.
      */
     sync GetGraphicsDeviceInitData()
         returns (DeviceInitData aData);
 
--- a/ipc/glue/BackgroundChildImpl.cpp
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -19,16 +19,17 @@
 #include "mozilla/dom/PFileSystemRequestChild.h"
 #include "mozilla/dom/FileSystemTaskBase.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/cache/ActorUtils.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsChild.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/dom/quota/PQuotaChild.h"
+#include "mozilla/dom/GamepadEventChannelChild.h"
 #include "mozilla/dom/MessagePortChild.h"
 #include "mozilla/dom/NuwaChild.h"
 #include "mozilla/ipc/PBackgroundTestChild.h"
 #include "mozilla/ipc/PSendStreamChild.h"
 #include "mozilla/layout/VsyncChild.h"
 #include "mozilla/net/PUDPSocketChild.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
 #include "nsID.h"
@@ -478,16 +479,32 @@ BackgroundChildImpl::DeallocPFileSystemR
 {
   // The reference is increased in FileSystemTaskBase::Start of
   // FileSystemTaskBase.cpp. We should decrease it after IPC.
   RefPtr<dom::FileSystemTaskChildBase> child =
     dont_AddRef(static_cast<dom::FileSystemTaskChildBase*>(aActor));
   return true;
 }
 
+// Gamepad API Background IPC
+dom::PGamepadEventChannelChild*
+BackgroundChildImpl::AllocPGamepadEventChannelChild()
+{
+  MOZ_CRASH("PGamepadEventChannelChild actor should be manually constructed!");
+  return nullptr;
+}
+
+bool
+BackgroundChildImpl::DeallocPGamepadEventChannelChild(PGamepadEventChannelChild* aActor)
+{
+  MOZ_ASSERT(aActor);
+  delete static_cast<dom::GamepadEventChannelChild*>(aActor);
+  return true;
+}
+
 } // namespace ipc
 } // namespace mozilla
 
 bool
 TestChild::Recv__delete__(const nsCString& aTestArg)
 {
   MOZ_RELEASE_ASSERT(aTestArg == mTestArg,
                      "BackgroundTest message was corrupted!");
--- a/ipc/glue/BackgroundChildImpl.h
+++ b/ipc/glue/BackgroundChildImpl.h
@@ -167,16 +167,22 @@ protected:
   DeallocPQuotaChild(PQuotaChild* aActor) override;
 
   virtual PFileSystemRequestChild*
   AllocPFileSystemRequestChild(const FileSystemParams&) override;
 
   virtual bool
   DeallocPFileSystemRequestChild(PFileSystemRequestChild*) override;
 
+  // Gamepad API Background IPC
+  virtual PGamepadEventChannelChild*
+  AllocPGamepadEventChannelChild() override;
+
+  virtual bool
+  DeallocPGamepadEventChannelChild(PGamepadEventChannelChild* aActor) override;
 };
 
 class BackgroundChildImpl::ThreadLocal final
 {
   friend class nsAutoPtr<ThreadLocal>;
 
 public:
   nsAutoPtr<mozilla::dom::indexedDB::ThreadLocal> mIndexedDBThreadLocal;
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -13,18 +13,20 @@
 #endif
 #include "mozilla/media/MediaParent.h"
 #include "mozilla/AppProcessChecker.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DOMTypes.h"
 #include "mozilla/dom/FileSystemBase.h"
 #include "mozilla/dom/FileSystemRequestParent.h"
+#include "mozilla/dom/GamepadEventChannelParent.h"
 #include "mozilla/dom/NuwaParent.h"
 #include "mozilla/dom/PBlobParent.h"
+#include "mozilla/dom/PGamepadEventChannelParent.h"
 #include "mozilla/dom/MessagePortParent.h"
 #include "mozilla/dom/ServiceWorkerRegistrar.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/cache/ActorUtils.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/ipc/BlobParent.h"
 #include "mozilla/dom/quota/ActorsParent.h"
 #include "mozilla/ipc/BackgroundParent.h"
@@ -911,16 +913,34 @@ BackgroundParentImpl::DeallocPFileSystem
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
 
   RefPtr<FileSystemRequestParent> parent =
     dont_AddRef(static_cast<FileSystemRequestParent*>(aDoomed));
   return true;
 }
 
+// Gamepad API Background IPC
+dom::PGamepadEventChannelParent*
+BackgroundParentImpl::AllocPGamepadEventChannelParent()
+{
+  RefPtr<dom::GamepadEventChannelParent> parent =
+    new dom::GamepadEventChannelParent();
+
+  return parent.forget().take();
+}
+
+bool
+BackgroundParentImpl::DeallocPGamepadEventChannelParent(dom::PGamepadEventChannelParent *aActor)
+{
+  MOZ_ASSERT(aActor);
+  RefPtr<dom::GamepadEventChannelParent> parent =
+    dont_AddRef(static_cast<dom::GamepadEventChannelParent*>(aActor));
+  return true;
+}
 } // namespace ipc
 } // namespace mozilla
 
 void
 TestParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   mozilla::ipc::AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -198,14 +198,20 @@ protected:
 
   virtual bool
   RecvPFileSystemRequestConstructor(PFileSystemRequestParent* aActor,
                                     const FileSystemParams& aParams) override;
 
   virtual bool
   DeallocPFileSystemRequestParent(PFileSystemRequestParent*) override;
 
+  // Gamepad API Background IPC
+  virtual PGamepadEventChannelParent*
+  AllocPGamepadEventChannelParent() override;
+
+  virtual bool
+  DeallocPGamepadEventChannelParent(PGamepadEventChannelParent *aActor) override;
 };
 
 } // namespace ipc
 } // namespace mozilla
 
 #endif // mozilla_ipc_backgroundparentimpl_h__
--- a/ipc/glue/PBackground.ipdl
+++ b/ipc/glue/PBackground.ipdl
@@ -8,16 +8,17 @@ include protocol PBackgroundIndexedDBUti
 include protocol PBackgroundTest;
 include protocol PBlob;
 include protocol PBroadcastChannel;
 include protocol PCache;
 include protocol PCacheStorage;
 include protocol PCacheStreamControl;
 include protocol PFileDescriptorSet;
 include protocol PFileSystemRequest;
+include protocol PGamepadEventChannel;
 include protocol PMessagePort;
 include protocol PCameras;
 include protocol PNuwa;
 include protocol PQuota;
 include protocol PSendStream;
 include protocol PServiceWorkerManager;
 include protocol PUDPSocket;
 include protocol PVsync;
@@ -49,16 +50,17 @@ sync protocol PBackground
   manages PBackgroundTest;
   manages PBlob;
   manages PBroadcastChannel;
   manages PCache;
   manages PCacheStorage;
   manages PCacheStreamControl;
   manages PFileDescriptorSet;
   manages PFileSystemRequest;
+  manages PGamepadEventChannel;
   manages PMessagePort;
   manages PCameras;
   manages PNuwa;
   manages PQuota;
   manages PSendStream;
   manages PServiceWorkerManager;
   manages PUDPSocket;
   manages PVsync;
@@ -98,16 +100,18 @@ parent:
   async PAsmJSCacheEntry(OpenMode openMode,
                          WriteParams write,
                          PrincipalInfo principalInfo);
 
   async PQuota();
 
   async PFileSystemRequest(FileSystemParams params);
 
+  async PGamepadEventChannel();
+
 child:
   async PCache();
   async PCacheStreamControl();
 
 both:
   async PBlob(BlobConstructorParams params);
 
   async PFileDescriptorSet(FileDescriptor fd);
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -39,17 +39,17 @@
 #include "AndroidBridgeUtilities.h"
 #include "GeneratedJNINatives.h"
 #include <android/log.h>
 #include <pthread.h>
 #include <wchar.h>
 
 #include "mozilla/dom/ScreenOrientation.h"
 #ifdef MOZ_GAMEPAD
-#include "mozilla/dom/GamepadFunctions.h"
+#include "mozilla/dom/GamepadPlatformService.h"
 #include "mozilla/dom/Gamepad.h"
 #endif
 
 #include "GeckoProfiler.h"
 #ifdef MOZ_ANDROID_HISTORY
 #include "nsNetUtil.h"
 #include "nsIURI.h"
 #include "IHistory.h"
@@ -64,16 +64,17 @@
 
 #ifdef DEBUG_ANDROID_EVENTS
 #define EVLOG(args...)  ALOG(args)
 #else
 #define EVLOG(args...) do { } while (0)
 #endif
 
 using namespace mozilla;
+typedef mozilla::dom::GamepadPlatformService GamepadPlatformService;
 
 nsIGeolocationUpdate *gLocationCallback = nullptr;
 nsAutoPtr<mozilla::AndroidGeckoEvent> gLastSizeChange;
 
 nsAppShell* nsAppShell::sAppShell;
 StaticAutoPtr<Mutex> nsAppShell::sAppShellLock;
 
 NS_IMPL_ISUPPORTS_INHERITED(nsAppShell, nsBaseAppShell, nsIObserver)
@@ -928,46 +929,52 @@ nsAppShell::LegacyGeckoEvent::Run()
         } else {
             Telemetry::Accumulate(NS_ConvertUTF16toUTF8(curEvent->Characters()).get(),
                                   curEvent->Count());
         }
         break;
 
     case AndroidGeckoEvent::GAMEPAD_ADDREMOVE: {
 #ifdef MOZ_GAMEPAD
+            GamepadPlatformService* service;
+            service = GamepadPlatformService::GetParentService();
+            MOZ_ASSERT(service);
             if (curEvent->Action() == AndroidGeckoEvent::ACTION_GAMEPAD_ADDED) {
-            int svc_id = dom::GamepadFunctions::AddGamepad("android",
-                                                           dom::GamepadMappingType::Standard,
-                                                           dom::kStandardGamepadButtons,
-                                                           dom::kStandardGamepadAxes);
-                widget::GeckoAppShell::GamepadAdded(curEvent->ID(),
-                                                    svc_id);
+              int svc_id = service->AddGamepad("android",
+                                               dom::GamepadMappingType::Standard,
+                                               dom::kStandardGamepadButtons,
+                                               dom::kStandardGamepadAxes);
+              widget::GeckoAppShell::GamepadAdded(curEvent->ID(),
+                                                  svc_id);
             } else if (curEvent->Action() == AndroidGeckoEvent::ACTION_GAMEPAD_REMOVED) {
-            dom::GamepadFunctions::RemoveGamepad(curEvent->ID());
-        }
+              service->RemoveGamepad(curEvent->ID());
+            }
 #endif
         break;
     }
 
     case AndroidGeckoEvent::GAMEPAD_DATA: {
 #ifdef MOZ_GAMEPAD
             int id = curEvent->ID();
+            GamepadPlatformService* service;
+            service = GamepadPlatformService::GetParentService();
+            MOZ_ASSERT(service);
             if (curEvent->Action() == AndroidGeckoEvent::ACTION_GAMEPAD_BUTTON) {
-            dom::GamepadFunctions::NewButtonEvent(id, curEvent->GamepadButton(),
-                                     curEvent->GamepadButtonPressed(),
-                                     curEvent->GamepadButtonValue());
+              service->NewButtonEvent(id, curEvent->GamepadButton(),
+                                      curEvent->GamepadButtonPressed(),
+                                      curEvent->GamepadButtonValue());
             } else if (curEvent->Action() == AndroidGeckoEvent::ACTION_GAMEPAD_AXES) {
                 int valid = curEvent->Flags();
                 const nsTArray<float>& values = curEvent->GamepadValues();
                 for (unsigned i = 0; i < values.Length(); i++) {
                     if (valid & (1<<i)) {
-                    dom::GamepadFunctions::NewAxisMoveEvent(id, i, values[i]);
+                      service->NewAxisMoveEvent(id, i, values[i]);
+                    }
                 }
             }
-        }
 #endif
         break;
     }
     case AndroidGeckoEvent::NOOP:
         break;
 
     default:
         nsWindow::OnGlobalAndroidEvent(curEvent.get());