bug 604039 - Add DOM Gamepad APIs. r=smaug
authorTed Mielczarek <ted.mielczarek@gmail.com>
Wed, 03 Aug 2011 14:12:08 -0400
changeset 136073 370e227ed45259ccb851bbce041027097a1bac13
parent 136072 343ccbfa1f9aba392665d4055d904990679a0fb8
child 136074 6702404b73e24fb405df41187751ae155d7ea25e
push id2452
push userlsblakk@mozilla.com
push dateMon, 13 May 2013 16:59:38 +0000
treeherdermozilla-beta@d4b152d29d8d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs604039
milestone22.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 604039 - Add DOM Gamepad APIs. r=smaug
browser/installer/package-manifest.in
configure.in
content/base/src/nsGkAtomList.h
content/events/public/nsEventNameList.h
content/events/src/Makefile.in
content/events/src/nsDOMGamepad.cpp
content/events/src/nsDOMGamepad.h
content/events/src/nsEventListenerManager.cpp
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsPIDOMWindow.h
dom/interfaces/events/moz.build
dom/interfaces/events/nsIDOMGamepadAxisMoveEvent.idl
dom/interfaces/events/nsIDOMGamepadButtonEvent.idl
dom/interfaces/events/nsIDOMGamepadEvent.idl
dom/interfaces/gamepad/Makefile.in
dom/interfaces/gamepad/moz.build
dom/interfaces/gamepad/nsIDOMGamepad.idl
dom/interfaces/gamepad/nsIGamepadServiceTest.idl
dom/moz.build
dom/system/GamepadService.cpp
dom/system/GamepadService.h
dom/system/Makefile.in
dom/tests/mochitest/gamepad/Makefile.in
dom/tests/mochitest/gamepad/gamepad_frame.html
dom/tests/mochitest/gamepad/gamepad_frame_state.html
dom/tests/mochitest/gamepad/mock_gamepad.js
dom/tests/mochitest/gamepad/moz.build
dom/tests/mochitest/gamepad/test_gamepad.html
dom/tests/mochitest/gamepad/test_gamepad_basic.html
dom/tests/mochitest/gamepad/test_gamepad_frame_state_sync.html
dom/tests/mochitest/gamepad/test_gamepad_hidden_frame.html
dom/tests/mochitest/general/test_interfaces.html
dom/tests/mochitest/moz.build
hal/Hal.cpp
hal/Hal.h
hal/Makefile.in
hal/fallback/FallbackGamepad.cpp
hal/sandbox/SandboxHal.cpp
js/xpconnect/src/event_impl_gen.conf.in
layout/build/nsLayoutModule.cpp
widget/nsGUIEvent.h
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -209,16 +209,19 @@
 @BINPATH@/components/dom_sidebar.xpt
 @BINPATH@/components/dom_mobilemessage.xpt
 @BINPATH@/components/dom_storage.xpt
 @BINPATH@/components/dom_stylesheets.xpt
 @BINPATH@/components/dom_traversal.xpt
 @BINPATH@/components/dom_xbl.xpt
 @BINPATH@/components/dom_xpath.xpt
 @BINPATH@/components/dom_xul.xpt
+#ifdef MOZ_GAMEPAD
+@BINPATH@/components/dom_gamepad.xpt
+#endif
 @BINPATH@/components/downloads.xpt
 @BINPATH@/components/editor.xpt
 @BINPATH@/components/embed_base.xpt
 @BINPATH@/components/extensions.xpt
 @BINPATH@/components/exthandler.xpt
 @BINPATH@/components/exthelper.xpt
 @BINPATH@/components/fastfind.xpt
 @BINPATH@/components/feeds.xpt
--- a/configure.in
+++ b/configure.in
@@ -5923,16 +5923,47 @@ if test -n "$MOZ_ANGLE_RENDERER" -a -z "
   if test -z "$MOZ_D3DCOMPILER_CAB"; then
     AC_MSG_ERROR([Couldn't find the DirectX redistributable files. Either reinstall the DirectX SDK (making sure the "DirectX Redistributable Files" option is selected), or reconfigure with --disable-webgl.])
   fi
 
   MOZ_D3DCOMPILER_DLL=D3DCompiler_$MOZ_D3DX9_VERSION.dll
 fi
 
 dnl ========================================================
+dnl Gamepad support
+dnl ========================================================
+MOZ_GAMEPAD=
+MOZ_GAMEPAD_BACKEND=stub
+
+# Gamepad DOM is built on supported platforms by default.
+case "$OS_TARGET" in
+     Darwin|WINNT|Linux)
+       MOZ_GAMEPAD=1
+       ;;
+     *)
+       ;;
+esac
+
+MOZ_ARG_DISABLE_BOOL(gamepad,
+[  --disable-gamepad   Disable gamepad support],
+    MOZ_GAMEPAD=,
+    MOZ_GAMEPAD=1)
+
+if test "$MOZ_GAMEPAD"; then
+    case "$OS_TARGET" in
+    *)
+        ;;
+   esac
+
+  AC_DEFINE(MOZ_GAMEPAD)
+fi
+AC_SUBST(MOZ_GAMEPAD)
+AC_SUBST(MOZ_GAMEPAD_BACKEND)
+
+dnl ========================================================
 dnl = Breakpad crash reporting (on by default on supported platforms)
 dnl ========================================================
 
 case $target in
 i?86-*-mingw*|x86_64-*-mingw*)
   MOZ_CRASHREPORTER=1
   ;;
 i?86-apple-darwin*|x86_64-apple-darwin*)
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1890,16 +1890,23 @@ GK_ATOM(seeking, "seeking")
 GK_ATOM(seeked, "seeked")
 GK_ATOM(timeupdate, "timeupdate")
 GK_ATOM(ended, "ended")
 GK_ATOM(canplay, "canplay")
 GK_ATOM(canplaythrough, "canplaythrough")
 GK_ATOM(ratechange, "ratechange")
 GK_ATOM(durationchange, "durationchange")
 GK_ATOM(volumechange, "volumechange")
+#ifdef MOZ_GAMEPAD
+GK_ATOM(ongamepadbuttondown, "ongamepadbuttondown")
+GK_ATOM(ongamepadbuttonup, "ongamepadbuttonup")
+GK_ATOM(ongamepadaxismove, "ongamepadaxismove")
+GK_ATOM(ongamepadconnected, "ongamepadconnected")
+GK_ATOM(ongamepaddisconnected, "ongamepaddisconnected")
+#endif
 
 // Content property names
 GK_ATOM(animationsProperty, "AnimationsProperty")        // FrameAnimations*
 GK_ATOM(animationsOfBeforeProperty, "AnimationsOfBeforeProperty") // FrameAnimations*
 GK_ATOM(animationsOfAfterProperty, "AnimationsOfAfterProperty") // FrameAnimations*
 GK_ATOM(transitionsProperty, "TransitionsProperty")        // FrameTransitions*
 GK_ATOM(transitionsOfBeforeProperty, "TransitionsOfBeforeProperty") // FrameTransitions*
 GK_ATOM(transitionsOfAfterProperty, "TransitionsOfAfterProperty") // FrameTransitions*
--- a/content/events/public/nsEventNameList.h
+++ b/content/events/public/nsEventNameList.h
@@ -749,16 +749,39 @@ NON_IDL_EVENT(MozAfterPaint,
               EventNameType_None,
               NS_EVENT)
 
 NON_IDL_EVENT(MozScrolledAreaChanged,
               NS_SCROLLEDAREACHANGED,
               EventNameType_None,
               NS_SCROLLAREA_EVENT)
 
+#ifdef MOZ_GAMEPAD
+NON_IDL_EVENT(gamepadbuttondown,
+              NS_GAMEPAD_BUTTONDOWN,
+              EventNameType_None,
+              NS_EVENT_NULL)
+NON_IDL_EVENT(gamepadbuttonup,
+              NS_GAMEPAD_BUTTONUP,
+              EventNameType_None,
+              NS_EVENT_NULL)
+NON_IDL_EVENT(gamepadaxismove,
+              NS_GAMEPAD_AXISMOVE,
+              EventNameType_None,
+              NS_EVENT_NULL)
+NON_IDL_EVENT(gamepadconnected,
+              NS_GAMEPAD_CONNECTED,
+              EventNameType_None,
+              NS_EVENT_NULL)
+NON_IDL_EVENT(gamepaddisconnected,
+              NS_GAMEPAD_DISCONNECTED,
+              EventNameType_None,
+              NS_EVENT_NULL)
+#endif
+
 // Simple gesture events
 NON_IDL_EVENT(MozSwipeGesture,
               NS_SIMPLE_GESTURE_SWIPE,
               EventNameType_None,
               NS_SIMPLE_GESTURE_EVENT)
 NON_IDL_EVENT(MozMagnifyGestureStart,
               NS_SIMPLE_GESTURE_MAGNIFY_START,
               EventNameType_None,
--- a/content/events/src/Makefile.in
+++ b/content/events/src/Makefile.in
@@ -16,16 +16,17 @@ ifndef _MSC_VER
 FAIL_ON_WARNINGS = 1
 endif # !_MSC_VER
 
 EXPORTS		= \
 		nsEventStateManager.h \
 		nsEventListenerManager.h \
 		nsDOMEventTargetHelper.h \
 		nsDOMEvent.h \
+		nsDOMGamepad.h \
 		nsDOMTouchEvent.h \
 		nsDOMUIEvent.h \
 		$(NULL)
 
 CPPSRCS		= \
 		nsEventListenerManager.cpp \
 		nsEventStateManager.cpp \
 		nsDOMEvent.cpp \
@@ -59,16 +60,22 @@ CPPSRCS		= \
 		nsDOMAnimationEvent.cpp \
 		nsDOMTouchEvent.cpp \
 		nsDOMCompositionEvent.cpp \
 		nsDOMClipboardEvent.cpp \
 		DOMWheelEvent.cpp \
 		TextComposition.cpp \
 		$(NULL)
 
+ifdef MOZ_GAMEPAD
+CPPSRCS += \
+  nsDOMGamepad.cpp \
+  $(NULL)
+endif
+
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES	+= \
new file mode 100644
--- /dev/null
+++ b/content/events/src/nsDOMGamepad.cpp
@@ -0,0 +1,171 @@
+/* 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 "nsDOMGamepad.h"
+#include "nsDOMClassInfoID.h"
+#include "nsIClassInfo.h"
+#include "nsIXPCScriptable.h"
+#include "nsTArray.h"
+#include "nsContentUtils.h"
+#include "nsVariant.h"
+
+DOMCI_DATA(Gamepad, nsDOMGamepad)
+
+NS_IMPL_ADDREF(nsDOMGamepad)
+NS_IMPL_RELEASE(nsDOMGamepad)
+
+NS_INTERFACE_MAP_BEGIN(nsDOMGamepad)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMGamepad)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Gamepad)
+NS_INTERFACE_MAP_END
+
+nsDOMGamepad::nsDOMGamepad(const nsAString& aID, uint32_t aIndex,
+                           uint32_t aNumButtons, uint32_t aNumAxes)
+  : mID(aID),
+    mIndex(aIndex),
+    mConnected(true)
+{
+  mButtons.InsertElementsAt(0, aNumButtons, 0);
+  mAxes.InsertElementsAt(0, aNumAxes, 0.0f);
+}
+
+/* readonly attribute DOMString id; */
+NS_IMETHODIMP
+nsDOMGamepad::GetId(nsAString& aID)
+{
+  aID = mID;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMGamepad::GetIndex(uint32_t* aIndex)
+{
+  *aIndex = mIndex;
+  return NS_OK;
+}
+
+void
+nsDOMGamepad::SetIndex(uint32_t aIndex)
+{
+  mIndex = aIndex;
+}
+
+void
+nsDOMGamepad::SetConnected(bool aConnected)
+{
+  mConnected = aConnected;
+}
+
+void
+nsDOMGamepad::SetButton(uint32_t aButton, double aValue)
+{
+  MOZ_ASSERT(aButton < mButtons.Length());
+  mButtons[aButton] = aValue;
+}
+
+void
+nsDOMGamepad::SetAxis(uint32_t aAxis, double aValue)
+{
+  MOZ_ASSERT(aAxis < mAxes.Length());
+  mAxes[aAxis] = aValue;
+}
+
+/* readonly attribute boolean connected; */
+NS_IMETHODIMP
+nsDOMGamepad::GetConnected(bool* aConnected)
+{
+  *aConnected = mConnected;
+  return NS_OK;
+}
+
+/* readonly attribute nsIVariant buttons; */
+NS_IMETHODIMP
+nsDOMGamepad::GetButtons(nsIVariant** aButtons)
+{
+  nsRefPtr<nsVariant> out = new nsVariant();
+  NS_ENSURE_STATE(out);
+
+  if (mButtons.Length() == 0) {
+    nsresult rv = out->SetAsEmptyArray();
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    // Note: The resulting nsIVariant dupes both the array and its elements.
+    double* array = reinterpret_cast<double*>
+                      (NS_Alloc(mButtons.Length() * sizeof(double)));
+    NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
+
+    for (uint32_t i = 0; i < mButtons.Length(); ++i) {
+      array[i] = mButtons[i];
+    }
+
+    nsresult rv = out->SetAsArray(nsIDataType::VTYPE_DOUBLE,
+                                  nullptr,
+                                  mButtons.Length(),
+                                  reinterpret_cast<void*>(array));
+    NS_Free(array);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  *aButtons = out.forget().get();
+  return NS_OK;
+}
+
+/* readonly attribute nsIVariant axes; */
+NS_IMETHODIMP
+nsDOMGamepad::GetAxes(nsIVariant** aAxes)
+{
+  nsRefPtr<nsVariant> out = new nsVariant();
+  NS_ENSURE_STATE(out);
+
+  if (mAxes.Length() == 0) {
+    nsresult rv = out->SetAsEmptyArray();
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    // Note: The resulting nsIVariant dupes both the array and its elements.
+    double* array = reinterpret_cast<double*>
+                              (NS_Alloc(mAxes.Length() * sizeof(double)));
+    NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
+
+    for (uint32_t i = 0; i < mAxes.Length(); ++i) {
+      array[i] = mAxes[i];
+    }
+
+    nsresult rv = out->SetAsArray(nsIDataType::VTYPE_DOUBLE,
+                                  nullptr,
+                                  mAxes.Length(),
+                                  reinterpret_cast<void*>(array));
+    NS_Free(array);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  *aAxes = out.forget().get();
+  return NS_OK;
+}
+
+void
+nsDOMGamepad::SyncState(nsDOMGamepad* aOther)
+{
+  if (mButtons.Length() != aOther->mButtons.Length() ||
+      mAxes.Length() != aOther->mAxes.Length()) {
+    return;
+  }
+
+  mConnected = aOther->mConnected;
+  for (uint32_t i = 0; i < mButtons.Length(); ++i) {
+    mButtons[i] = aOther->mButtons[i];
+  }
+  for (uint32_t i = 0; i < mAxes.Length(); ++i) {
+    mAxes[i] = aOther->mAxes[i];
+  }
+}
+
+already_AddRefed<nsDOMGamepad>
+nsDOMGamepad::Clone()
+{
+  nsRefPtr<nsDOMGamepad> out =
+    new nsDOMGamepad(mID, mIndex, mButtons.Length(), mAxes.Length());
+  out->SyncState(this);
+  return out.forget();
+}
new file mode 100644
--- /dev/null
+++ b/content/events/src/nsDOMGamepad.h
@@ -0,0 +1,49 @@
+/* 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 nsDomGamepad_h
+#define nsDomGamepad_h
+
+#include "mozilla/StandardInteger.h"
+#include "nsIDOMGamepad.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+class nsDOMGamepad : public nsIDOMGamepad
+{
+public:
+  nsDOMGamepad(const nsAString& aID, uint32_t aIndex,
+               uint32_t aNumButtons, uint32_t aNumAxes);
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMGAMEPAD
+
+  nsDOMGamepad();
+  void SetConnected(bool aConnected);
+  void SetButton(uint32_t aButton, double aValue);
+  void SetAxis(uint32_t aAxis, double aValue);
+  void SetIndex(uint32_t aIndex);
+
+  // Make the state of this gamepad equivalent to other.
+  void SyncState(nsDOMGamepad* other);
+
+  // Return a new nsDOMGamepad containing the same data as this object.
+  already_AddRefed<nsDOMGamepad> Clone();
+
+private:
+  virtual ~nsDOMGamepad() {}
+
+protected:
+  nsString mID;
+  uint32_t mIndex;
+
+  // true if this gamepad is currently connected.
+  bool mConnected;
+
+  // Current state of buttons, axes.
+  nsTArray<double> mButtons;
+  nsTArray<double> mAxes;
+};
+
+#endif // nsDomGamepad_h
--- a/content/events/src/nsEventListenerManager.cpp
+++ b/content/events/src/nsEventListenerManager.cpp
@@ -334,16 +334,24 @@ nsEventListenerManager::AddEventListener
 #ifdef DEBUG
       nsCOMPtr<nsIDocument> d = do_QueryInterface(window->GetExtantDocument());
       NS_WARN_IF_FALSE(!nsContentUtils::IsChromeDoc(d),
                        "Please do not use mouseenter/leave events in chrome. "
                        "They are slower than mouseover/out!");
 #endif
       window->SetHasMouseEnterLeaveEventListeners();
     }
+#ifdef MOZ_GAMEPAD
+  } else if (aType >= NS_GAMEPAD_START &&
+             aType <= NS_GAMEPAD_END) {
+    nsPIDOMWindow* window = GetInnerWindowForTarget();
+    if (window) {
+      window->SetHasGamepadEventListener();
+    }
+#endif
   }
 }
 
 bool
 nsEventListenerManager::IsDeviceType(uint32_t aType)
 {
   switch (aType) {
     case NS_DEVICE_ORIENTATION:
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -225,16 +225,19 @@
 #include "nsIDOMCRMFObject.h"
 #include "nsIDOMCryptoLegacy.h"
 #else
 #include "nsIDOMCrypto.h"
 #endif
 #include "nsIControllers.h"
 #include "nsISelection.h"
 #include "nsIBoxObject.h"
+#ifdef MOZ_GAMEPAD
+#include "nsIDOMGamepad.h"
+#endif
 #ifdef MOZ_XUL
 #include "nsITreeSelection.h"
 #include "nsITreeContentView.h"
 #include "nsITreeView.h"
 #include "nsIXULTemplateBuilder.h"
 #include "nsTreeColumns.h"
 #endif
 #include "nsIDOMXPathExpression.h"
@@ -1052,16 +1055,21 @@ static nsDOMClassInfoData sClassInfoData
 
   NS_DEFINE_CLASSINFO_DATA(Touch, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(TouchList, nsDOMTouchListSH,
                            ARRAY_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(TouchEvent, nsEventSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
+#ifdef MOZ_GAMEPAD
+  NS_DEFINE_CLASSINFO_DATA(Gamepad, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+#endif
+
   NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframeRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframesRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(CSSPageRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
@@ -2657,16 +2665,22 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN_MAYBE_DISABLE(TouchEvent, nsIDOMTouchEvent,
                                         !nsDOMTouchEvent::PrefEnabled())
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMTouchEvent)
     DOM_CLASSINFO_UI_EVENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
+#ifdef MOZ_GAMEPAD
+  DOM_CLASSINFO_MAP_BEGIN(Gamepad, nsIDOMGamepad)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMGamepad)
+  DOM_CLASSINFO_MAP_END
+#endif
+
   DOM_CLASSINFO_MAP_BEGIN(MozCSSKeyframeRule, nsIDOMMozCSSKeyframeRule)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozCSSKeyframeRule)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(MozCSSKeyframesRule, nsIDOMMozCSSKeyframesRule)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozCSSKeyframesRule)
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -258,16 +258,20 @@ DOMCI_CLASS(IDBKeyRange)
 DOMCI_CLASS(IDBIndex)
 DOMCI_CLASS(IDBVersionChangeEvent)
 DOMCI_CLASS(IDBOpenDBRequest)
 
 DOMCI_CLASS(Touch)
 DOMCI_CLASS(TouchList)
 DOMCI_CLASS(TouchEvent)
 
+#ifdef MOZ_GAMEPAD
+DOMCI_CLASS(Gamepad)
+#endif
+
 DOMCI_CLASS(MozCSSKeyframeRule)
 DOMCI_CLASS(MozCSSKeyframesRule)
 
 DOMCI_CLASS(CSSPageRule)
 
 DOMCI_CLASS(MediaQueryList)
 
 #ifdef MOZ_B2G_RIL
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -206,16 +206,20 @@
 #include "prlog.h"
 #include "prenv.h"
 
 #include "mozilla/dom/indexedDB/IDBFactory.h"
 #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
 
 #include "mozilla/dom/StructuredCloneTags.h"
 
+#ifdef MOZ_GAMEPAD
+#include "mozilla/dom/GamepadService.h"
+#endif
+
 #include "nsRefreshDriver.h"
 #include "mozAutoDocUpdate.h"
 
 #include "mozilla/Telemetry.h"
 #include "nsLocation.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsDOMEventTargetHelper.h"
 #include "nsIAppsService.h"
@@ -916,16 +920,20 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
     mShowAccelerators(false),
     mShowFocusRings(false),
 #else
     mShowAccelerators(true),
     mShowFocusRings(true),
 #endif
     mShowFocusRingForContent(false),
     mFocusByKeyOccurred(false),
+    mHasGamepad(false),
+#ifdef MOZ_GAMEPAD
+    mHasSeenGamepadInput(false),
+#endif
     mNotifiedIDDestroyed(false),
     mAllowScriptsToClose(false),
     mTimeoutInsertionPoint(nullptr),
     mTimeoutPublicIdCounter(1),
     mTimeoutFiringDepth(0),
     mJSObject(nullptr),
     mTimeoutsSuspendDepth(0),
     mFocusMethod(0),
@@ -936,16 +944,20 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
     mCleanedUp(false),
     mCallCleanUpAfterModalDialogCloses(false),
     mDialogAbuseCount(0),
     mStopAbuseDialogs(false),
     mDialogsPermanentlyDisabled(false)
 {
   nsLayoutStatics::AddRef();
 
+#ifdef MOZ_GAMEPAD
+  mGamepads.Init();
+#endif
+
   // Initialize the PRCList (this).
   PR_INIT_CLIST(this);
 
   if (aOuterWindow) {
     // |this| is an inner window, add this inner window to the outer
     // window list of inners.
     PR_INSERT_AFTER(this, aOuterWindow);
 
@@ -1283,16 +1295,19 @@ nsGlobalWindow::CleanUp(bool aIgnoreModa
   mParentTarget = nullptr;
 
   nsGlobalWindow *inner = GetCurrentInnerWindowInternal();
 
   if (inner) {
     inner->CleanUp(aIgnoreModalDialog);
   }
 
+  DisableGamepadUpdates();
+  mHasGamepad = false;
+
   if (mCleanMessageManager) {
     NS_ABORT_IF_FALSE(mIsChrome, "only chrome should have msg manager cleaned");
     nsGlobalChromeWindow *asChrome = static_cast<nsGlobalChromeWindow*>(this);
     if (asChrome->mMessageManager) {
       static_cast<nsFrameMessageManager*>(
         asChrome->mMessageManager.get())->Disconnect();
     }
   }
@@ -8015,16 +8030,24 @@ nsGlobalWindow::SetActive(bool aActive)
 
 void nsGlobalWindow::SetIsBackground(bool aIsBackground)
 {
   bool resetTimers = (!aIsBackground && IsBackground());
   nsPIDOMWindow::SetIsBackground(aIsBackground);
   if (resetTimers) {
     ResetTimersForNonBackgroundWindow();
   }
+#ifdef MOZ_GAMEPAD
+  if (!aIsBackground) {
+    nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
+    if (inner) {
+      inner->SyncGamepadState();
+    }
+  }
+#endif
 }
 
 void nsGlobalWindow::MaybeUpdateTouchState()
 {
   FORWARD_TO_INNER_VOID(MaybeUpdateTouchState, ());
 
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
 
@@ -8059,16 +8082,44 @@ void nsGlobalWindow::UpdateTouchState()
   if (mMayHaveTouchEventListener) {
     mainWidget->RegisterTouchWindow();
   } else {
     mainWidget->UnregisterTouchWindow();
   }
 }
 
 void
+nsGlobalWindow::EnableGamepadUpdates()
+{
+  FORWARD_TO_INNER_VOID(EnableGamepadUpdates, ());
+  if (mHasGamepad) {
+#ifdef MOZ_GAMEPAD
+    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
+    if (gamepadsvc) {
+      gamepadsvc->AddListener(this);
+    }
+#endif
+  }
+}
+
+void
+nsGlobalWindow::DisableGamepadUpdates()
+{
+  FORWARD_TO_INNER_VOID(DisableGamepadUpdates, ());
+  if (mHasGamepad) {
+#ifdef MOZ_GAMEPAD
+    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
+    if (gamepadsvc) {
+      gamepadsvc->RemoveListener(this);
+    }
+#endif
+  }
+}
+
+void
 nsGlobalWindow::SetChromeEventHandler(nsIDOMEventTarget* aChromeEventHandler)
 {
   SetChromeEventHandlerInternal(aChromeEventHandler);
   if (IsOuterWindow()) {
     // update the chrome event handler on all our inner windows
     for (nsGlobalWindow *inner = (nsGlobalWindow *)PR_LIST_HEAD(this);
          inner != this;
          inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) {
@@ -10759,16 +10810,17 @@ nsGlobalWindow::SuspendTimeouts(uint32_t
   mTimeoutsSuspendDepth += aIncrease;
 
   if (!suspended) {
     nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
     if (ac) {
       for (uint32_t i = 0; i < mEnabledSensors.Length(); i++)
         ac->RemoveWindowListener(mEnabledSensors[i], this);
     }
+    DisableGamepadUpdates();
 
     // Suspend all of the workers for this window.
     nsIScriptContext *scx = GetContextInternal();
     AutoPushJSContext cx(scx ? scx->GetNativeContext() : nullptr);
     mozilla::dom::workers::SuspendWorkersForWindow(cx, this);
 
     TimeStamp now = TimeStamp::Now();
     for (nsTimeout *t = mTimeouts.getFirst(); t; t = t->getNext()) {
@@ -10839,16 +10891,17 @@ nsGlobalWindow::ResumeTimeouts(bool aTha
   nsresult rv;
 
   if (shouldResume) {
     nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
     if (ac) {
       for (uint32_t i = 0; i < mEnabledSensors.Length(); i++)
         ac->AddWindowListener(mEnabledSensors[i], this);
     }
+    EnableGamepadUpdates();
 
     // Resume all of the workers for this window.
     nsIScriptContext *scx = GetContextInternal();
     AutoPushJSContext cx(scx ? scx->GetNativeContext() : nullptr);
     mozilla::dom::workers::ResumeWorkersForWindow(cx, this);
 
     // Restore all of the timeouts, using the stored time remaining
     // (stored in timeout->mTimeRemaining).
@@ -10990,16 +11043,26 @@ nsGlobalWindow::DisableDeviceSensor(uint
 
   nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
   if (ac) {
     ac->RemoveWindowListener(aType, this);
   }
 }
 
 void
+nsGlobalWindow::SetHasGamepadEventListener(bool aHasGamepad/* = true*/)
+{
+  FORWARD_TO_INNER_VOID(SetHasGamepadEventListener, (aHasGamepad));
+  mHasGamepad = aHasGamepad;
+  if (aHasGamepad) {
+    EnableGamepadUpdates();
+  }
+}
+
+void
 nsGlobalWindow::EnableTimeChangeNotifications()
 {
   nsSystemTimeChangeObserver::AddWindowListener(this);
 }
 
 void
 nsGlobalWindow::DisableTimeChangeNotifications()
 {
@@ -11053,16 +11116,76 @@ nsGlobalWindow::SizeOfIncludingThis(nsWi
       mNavigator->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf) : 0;
 
   aWindowSizes->mDOMEventTargets +=
     mEventTargetObjects.SizeOfExcludingThis(
       SizeOfEventTargetObjectsEntryExcludingThisFun,
       aWindowSizes->mMallocSizeOf);
 }
 
+
+#ifdef MOZ_GAMEPAD
+void
+nsGlobalWindow::AddGamepad(PRUint32 aIndex, nsDOMGamepad* aGamepad)
+{
+  FORWARD_TO_INNER_VOID(AddGamepad, (aIndex, aGamepad));
+  mGamepads.Put(aIndex, aGamepad);
+}
+
+void
+nsGlobalWindow::RemoveGamepad(PRUint32 aIndex)
+{
+  FORWARD_TO_INNER_VOID(RemoveGamepad, (aIndex));
+  mGamepads.Remove(aIndex);
+}
+
+already_AddRefed<nsDOMGamepad>
+nsGlobalWindow::GetGamepad(PRUint32 aIndex)
+{
+  FORWARD_TO_INNER(GetGamepad, (aIndex), nullptr);
+  nsRefPtr<nsDOMGamepad> gamepad;
+  if (mGamepads.Get(aIndex, getter_AddRefs(gamepad))) {
+    return gamepad.forget();
+  }
+
+  return nullptr;
+}
+
+void
+nsGlobalWindow::SetHasSeenGamepadInput(bool aHasSeen)
+{
+  FORWARD_TO_INNER_VOID(SetHasSeenGamepadInput, (aHasSeen));
+  mHasSeenGamepadInput = aHasSeen;
+}
+
+bool
+nsGlobalWindow::HasSeenGamepadInput()
+{
+  FORWARD_TO_INNER(HasSeenGamepadInput, (), false);
+  return mHasSeenGamepadInput;
+}
+
+// static
+PLDHashOperator
+nsGlobalWindow::EnumGamepadsForSync(const PRUint32& aKey, nsDOMGamepad* aData, void* userArg)
+{
+  nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
+  gamepadsvc->SyncGamepadState(aKey, aData);
+  return PL_DHASH_NEXT;
+}
+
+void
+nsGlobalWindow::SyncGamepadState()
+{
+  FORWARD_TO_INNER_VOID(SyncGamepadState, ());
+  if (mHasSeenGamepadInput) {
+    mGamepads.EnumerateRead(EnumGamepadsForSync, NULL);
+  }
+}
+#endif
 // nsGlobalChromeWindow implementation
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGlobalChromeWindow,
                                                   nsGlobalWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserDOMWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -57,16 +57,19 @@
 #include "nsFrameMessageManager.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/TimeStamp.h"
 #include "nsIDOMTouchEvent.h"
 #include "nsIInlineEventHandlers.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsIIdleObserver.h"
 #include "nsIDOMWakeLock.h"
+#ifdef MOZ_GAMEPAD
+#include "nsDOMGamepad.h"
+#endif
 
 #include "mozilla/dom/EventTarget.h"
 
 // JS includes
 #include "jsapi.h"
 
 #ifdef MOZ_B2G
 #include "nsIDOMWindowB2G.h"
@@ -373,16 +376,18 @@ public:
   virtual NS_HIDDEN_(nsresult) ForceClose();
 
   virtual NS_HIDDEN_(void) MaybeUpdateTouchState();
   virtual NS_HIDDEN_(void) UpdateTouchState();
   virtual NS_HIDDEN_(bool) DispatchCustomEvent(const char *aEventName);
   virtual NS_HIDDEN_(void) RefreshCompartmentPrincipal();
   virtual NS_HIDDEN_(nsresult) SetFullScreenInternal(bool aIsFullScreen, bool aRequireTrust);
 
+  virtual NS_HIDDEN_(void) SetHasGamepadEventListener(bool aHasGamepad = true);
+
   // nsIDOMStorageIndexedDB
   NS_DECL_NSIDOMSTORAGEINDEXEDDB
 
   // nsIInterfaceRequestor
   NS_DECL_NSIINTERFACEREQUESTOR
 
   // WebIDL interface.
   uint32_t GetLength();
@@ -604,16 +609,33 @@ public:
   bool ContainsIdleObserver(nsIIdleObserver* aIdleObserver, uint32_t timeInS);
   void HandleIdleObserverCallback();
 
   void AllowScriptsToClose()
   {
     mAllowScriptsToClose = true;
   }
 
+#ifdef MOZ_GAMEPAD
+  void AddGamepad(PRUint32 aIndex, nsDOMGamepad* aGamepad);
+  void RemoveGamepad(PRUint32 aIndex);
+  already_AddRefed<nsDOMGamepad> GetGamepad(PRUint32 aIndex);
+  void SetHasSeenGamepadInput(bool aHasSeen);
+  bool HasSeenGamepadInput();
+  void SyncGamepadState();
+  static PLDHashOperator EnumGamepadsForSync(const PRUint32& aKey,
+                                             nsDOMGamepad* aData,
+                                             void* userArg);
+#endif
+
+  // Enable/disable updates for gamepad input.
+  void EnableGamepadUpdates();
+  void DisableGamepadUpdates();
+
+
 #define EVENT(name_, id_, type_, struct_)                                     \
   mozilla::dom::EventHandlerNonNull* GetOn##name_()                           \
   {                                                                           \
     nsEventListenerManager *elm = GetListenerManager(false);                  \
     return elm ? elm->GetEventHandler(nsGkAtoms::on##name_) : nullptr;        \
   }                                                                           \
   void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler,               \
                     mozilla::ErrorResult& error)                              \
@@ -1039,16 +1061,23 @@ protected:
   // when true, show focus rings for the current focused content only.
   // This will be reset when another element is focused
   bool                   mShowFocusRingForContent : 1;
 
   // true if tab navigation has occurred for this window. Focus rings
   // should be displayed.
   bool                   mFocusByKeyOccurred : 1;
 
+  // Indicates whether this window wants gamepad input events
+  bool                   mHasGamepad : 1;
+#ifdef MOZ_GAMEPAD
+  nsRefPtrHashtable<nsUint32HashKey, nsDOMGamepad> mGamepads;
+  bool mHasSeenGamepadInput;
+#endif
+
   // whether we've sent the destroy notification for our window id
   bool                   mNotifiedIDDestroyed : 1;
   // whether scripts may close the window,
   // even if "dom.allow_scripts_to_close_windows" is false.
   bool                   mAllowScriptsToClose : 1;
 
   nsCOMPtr<nsIScriptContext>    mContext;
   nsWeakPtr                     mOpener;
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -52,18 +52,18 @@ class nsPIWindowRoot;
 
 namespace mozilla {
 namespace dom {
 class AudioContext;
 }
 }
 
 #define NS_PIDOMWINDOW_IID \
-{ 0xf5af1c3c, 0xebad, 0x4d00, \
-  { 0xa2, 0xa4, 0x12, 0x2e, 0x27, 0x16, 0x59, 0x01 } }
+{0xf30405c2, 0x5da8, 0x4339, \
+  {0x87, 0xe2, 0xfa, 0xb2, 0x51, 0x26, 0x8a, 0xe8}}
 
 class nsPIDOMWindow : public nsIDOMWindowInternal
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIDOMWINDOW_IID)
 
   virtual nsPIDOMWindow* GetPrivateRoot() = 0;
 
@@ -595,16 +595,21 @@ public:
   /**
    * Tell the window that it should stop to listen to the network event of the
    * given aType.
    */
   virtual void DisableNetworkEvent(uint32_t aType) = 0;
 #endif // MOZ_B2G
 
   /**
+   * Tell this window that there is an observer for gamepad input
+   */
+  virtual void SetHasGamepadEventListener(bool aHasGamepad = true) = 0;
+
+  /**
    * Set a arguments for this window. This will be set on the window
    * right away (if there's an existing document) and it will also be
    * installed on the window when the next document is loaded. Each
    * language impl is responsible for converting to an array of args
    * as appropriate for that language.
    */
   virtual nsresult SetArguments(nsIArray *aArguments, nsIPrincipal *aOrigin) = 0;
 
--- a/dom/interfaces/events/moz.build
+++ b/dom/interfaces/events/moz.build
@@ -42,16 +42,19 @@ XPIDL_SOURCES += [
     'nsIDOMScrollAreaEvent.idl',
     'nsIDOMSimpleGestureEvent.idl',
     'nsIDOMSmartCardEvent.idl',
     'nsIDOMTouchEvent.idl',
     'nsIDOMTransitionEvent.idl',
     'nsIDOMUIEvent.idl',
     'nsIDOMUserProximityEvent.idl',
     'nsIDOMWheelEvent.idl',
+    'nsIDOMGamepadButtonEvent.idl',
+    'nsIDOMGamepadAxisMoveEvent.idl',
+    'nsIDOMGamepadEvent.idl',
 ]
 
 XPIDL_MODULE = 'dom_events'
 
 XPIDL_FLAGS += [
     '-I$(topsrcdir)/dom/interfaces/base',
 ]
 
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/events/nsIDOMGamepadAxisMoveEvent.idl
@@ -0,0 +1,33 @@
+/* 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 "nsIDOMGamepadEvent.idl"
+
+[builtinclass, scriptable, uuid(bd09eef8-8e07-4baf-8d39-4f92003dbca8)]
+interface nsIDOMGamepadAxisMoveEvent : nsIDOMGamepadEvent
+{
+  /**
+   * Index in gamepad.axes of the axis that was moved.
+   */
+  readonly attribute unsigned long axis;
+
+  /**
+   * Position of the axis in the range -1.0...1.0.
+   */
+  readonly attribute double value;
+
+  [noscript]
+  void initGamepadAxisMoveEvent(in DOMString typeArg,
+                                in boolean canBubbleArg,
+                                in boolean cancelableArg,
+                                in nsIDOMGamepad gamepad,
+                                in unsigned long axis,
+                                in double value);
+};
+
+dictionary GamepadAxisMoveEventInit : GamepadEventInit
+{
+  unsigned long axis;
+  double value;
+};
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/events/nsIDOMGamepadButtonEvent.idl
@@ -0,0 +1,26 @@
+/* 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 "nsIDOMGamepadEvent.idl"
+
+[builtinclass, scriptable, uuid(d75d4d2b-e7b4-4b93-ac35-2e70b57d9b28)]
+interface nsIDOMGamepadButtonEvent : nsIDOMGamepadEvent
+{
+  /**
+   * Index in gamepad.buttons of the button that was pressed or released.
+   */
+  readonly attribute unsigned long button;
+
+  [noscript]
+  void initGamepadButtonEvent(in DOMString typeArg,
+                              in boolean canBubbleArg,
+                              in boolean cancelableArg,
+                              in nsIDOMGamepad gamepad,
+                              in unsigned long button);
+};
+
+dictionary GamepadButtonEventInit : GamepadEventInit
+{
+  unsigned long button;
+};
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/events/nsIDOMGamepadEvent.idl
@@ -0,0 +1,27 @@
+/* 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 "nsIDOMEvent.idl"
+
+interface nsIDOMGamepad;
+
+[builtinclass, scriptable, uuid(93b048d6-2aef-46a9-b0f4-06d7f00d8ef2)]
+interface nsIDOMGamepadEvent : nsIDOMEvent
+{
+  /**
+   * The device that generated this event.
+   */
+  readonly attribute nsIDOMGamepad gamepad;
+
+  [noscript]
+  void initGamepadEvent(in DOMString typeArg,
+                        in boolean canBubbleArg,
+                        in boolean cancelableArg,
+                        in nsIDOMGamepad gamepad);
+};
+
+dictionary GamepadEventInit : EventInit
+{
+  nsIDOMGamepad gamepad;
+};
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/gamepad/Makefile.in
@@ -0,0 +1,12 @@
+# 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/.
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/gamepad/moz.build
@@ -0,0 +1,11 @@
+# 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/.
+
+XPIDL_MODULE = 'dom_gamepad'
+
+XPIDL_SOURCES = [
+  'nsIDOMGamepad.idl',
+  'nsIGamepadServiceTest.idl',
+  ]
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/gamepad/nsIDOMGamepad.idl
@@ -0,0 +1,39 @@
+/* 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 "nsISupports.idl"
+
+interface nsIVariant;
+
+[builtinclass, scriptable, uuid(ff13acd9-11da-4817-8f2a-4a5700dfd13e)]
+interface nsIDOMGamepad : nsISupports
+{
+  /**
+   * An identifier, unique per type of device.
+   */
+  readonly attribute DOMString id;
+
+  /**
+   * The game port index for the device. Unique per device
+   * attached to this system.
+   */
+  readonly attribute unsigned long index;
+
+  /**
+   * true if this gamepad is currently connected to the system.
+   */
+  readonly attribute boolean connected;
+
+  /**
+   * The current state of all buttons on the device, an
+   * array of doubles.
+   */
+  readonly attribute nsIVariant buttons;
+
+  /**
+   * The current position of all axes on the device, an
+   * array of doubles.
+   */
+  readonly attribute nsIVariant axes;
+};
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/gamepad/nsIGamepadServiceTest.idl
@@ -0,0 +1,22 @@
+/* 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 "nsISupports.idl"
+
+interface nsIVariant;
+
+/*
+ * This interface is intended only for use in tests.
+ */
+[scriptable, uuid(7edf77a2-6b3e-4bbb-9100-4452d425feaa)]
+interface nsIGamepadServiceTest : nsISupports
+{
+  unsigned long addGamepad(in string id, in unsigned long numButtons,
+			   in unsigned long numAxes);
+  void removeGamepad(in unsigned long index);
+  void newButtonEvent(in unsigned long index, in unsigned long button,
+		      in boolean pressed);
+  void newAxisMoveEvent(in unsigned long index, in unsigned long axis,
+			in double value);
+};
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -24,16 +24,17 @@ interfaces = [
     'json',
     'offline',
     'geolocation',
     'notification',
     'permission',
     'svg',
     'smil',
     'apps',
+    'gamepad',
 ]
 
 PARALLEL_DIRS += ['interfaces/' + i for i in interfaces]
 
 PARALLEL_DIRS += [
     'apps',
     'base',
     'activities',
new file mode 100644
--- /dev/null
+++ b/dom/system/GamepadService.cpp
@@ -0,0 +1,530 @@
+/* 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/Hal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+
+#include "GamepadService.h"
+#include "nsAutoPtr.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMEventTarget.h"
+#include "nsDOMGamepad.h"
+#include "nsIDOMGamepadButtonEvent.h"
+#include "nsIDOMGamepadAxisMoveEvent.h"
+#include "nsIDOMGamepadEvent.h"
+#include "GeneratedEvents.h"
+#include "nsIDOMWindow.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+#include "nsITimer.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+
+#include <cstddef>
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+// Amount of time to wait before cleaning up gamepad resources
+// when no pages are listening for events.
+const int kCleanupDelayMS = 2000;
+const nsTArray<nsRefPtr<nsGlobalWindow> >::index_type NoIndex =
+    nsTArray<nsRefPtr<nsGlobalWindow> >::NoIndex;
+
+StaticRefPtr<GamepadService> gGamepadServiceSingleton;
+
+} // namespace
+
+bool GamepadService::sShutdown = false;
+
+NS_IMPL_ISUPPORTS0(GamepadService)
+
+GamepadService::GamepadService()
+  : mStarted(false),
+    mShuttingDown(false)
+{
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  observerService->AddObserver(this,
+                               NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
+                               false);
+}
+
+NS_IMETHODIMP
+GamepadService::Observe(nsISupports* aSubject,
+                        const char* aTopic,
+                        const PRUnichar* aData)
+{
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  nsCOMPtr<nsIObserver> observer = do_QueryInterface(this);
+  observerService->RemoveObserver(observer, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+
+  BeginShutdown();
+  return NS_OK;
+}
+
+void
+GamepadService::BeginShutdown()
+{
+  mShuttingDown = true;
+  if (mTimer) {
+    mTimer->Cancel();
+  }
+  mozilla::hal::StopMonitoringGamepadStatus();
+  mStarted = false;
+  // 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();
+  mGamepads.Clear();
+  sShutdown = true;
+}
+
+void
+GamepadService::AddListener(nsGlobalWindow* aWindow)
+{
+  if (mShuttingDown) {
+    return;
+  }
+
+  if (mListeners.IndexOf(aWindow) != NoIndex) {
+    return; // already exists
+  }
+
+  if (!mStarted) {
+    mozilla::hal::StartMonitoringGamepadStatus();
+    mStarted = true;
+  }
+
+  mListeners.AppendElement(aWindow);
+}
+
+void
+GamepadService::RemoveListener(nsGlobalWindow* aWindow)
+{
+  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.Length() == 0 && !mShuttingDown) {
+    StartCleanupTimer();
+  }
+}
+
+uint32_t
+GamepadService::AddGamepad(const char* aId,
+                           uint32_t aNumButtons,
+                           uint32_t aNumAxes)
+{
+  //TODO: bug 852258: get initial button/axis state
+  nsRefPtr<nsDOMGamepad> gamepad =
+    new nsDOMGamepad(NS_ConvertUTF8toUTF16(nsDependentCString(aId)),
+                     0,
+                     aNumButtons,
+                     aNumAxes);
+  int index = -1;
+  for (uint32_t i = 0; i < mGamepads.Length(); i++) {
+    if (!mGamepads[i]) {
+      mGamepads[i] = gamepad;
+      index = i;
+      break;
+    }
+  }
+  if (index == -1) {
+    mGamepads.AppendElement(gamepad);
+    index = mGamepads.Length() - 1;
+  }
+
+  gamepad->SetIndex(index);
+  NewConnectionEvent(index, true);
+
+  return index;
+}
+
+void
+GamepadService::RemoveGamepad(uint32_t aIndex)
+{
+  if (aIndex < mGamepads.Length()) {
+    mGamepads[aIndex]->SetConnected(false);
+    NewConnectionEvent(aIndex, false);
+    // If this is the last entry in the list, just remove it.
+    if (aIndex == mGamepads.Length() - 1) {
+      mGamepads.RemoveElementAt(aIndex);
+    } else {
+      // Otherwise just null it out and leave it, so the
+      // indices of the following entries remain valid.
+      mGamepads[aIndex] = nullptr;
+    }
+  }
+}
+
+void
+GamepadService::NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed)
+{
+  if (mShuttingDown || aIndex >= mGamepads.Length()) {
+    return;
+  }
+
+  double value = aPressed ? 1.0L : 0.0L;
+  mGamepads[aIndex]->SetButton(aButton, value);
+
+  // Hold on to listeners in a separate array because firing events
+  // can mutate the mListeners array.
+  nsTArray<nsRefPtr<nsGlobalWindow> > listeners(mListeners);
+
+  for (uint32_t i = listeners.Length(); i > 0 ; ) {
+    --i;
+
+    // Only send events to non-background windows
+    if (!listeners[i]->GetOuterWindow() ||
+        listeners[i]->GetOuterWindow()->IsBackground()) {
+      continue;
+    }
+
+    if (!WindowHasSeenGamepad(listeners[i], aIndex)) {
+      SetWindowHasSeenGamepad(listeners[i], aIndex);
+      // This window hasn't seen this gamepad before, so
+      // send a connection event first.
+      NewConnectionEvent(aIndex, true);
+    }
+
+    nsRefPtr<nsDOMGamepad> gamepad = listeners[i]->GetGamepad(aIndex);
+    if (gamepad) {
+      gamepad->SetButton(aButton, value);
+      // Fire event
+      FireButtonEvent(listeners[i], gamepad, aButton, value);
+    }
+  }
+}
+
+void
+GamepadService::FireButtonEvent(EventTarget* aTarget,
+                                nsDOMGamepad* aGamepad,
+                                uint32_t aButton,
+                                double aValue)
+{
+  nsCOMPtr<nsIDOMEvent> event;
+  bool defaultActionEnabled = true;
+  NS_NewDOMGamepadButtonEvent(getter_AddRefs(event), aTarget, nullptr, nullptr);
+  nsCOMPtr<nsIDOMGamepadButtonEvent> je = do_QueryInterface(event);
+  MOZ_ASSERT(je, "QI should not fail");
+
+
+  nsString name = aValue == 1.0L ? NS_LITERAL_STRING("gamepadbuttondown") :
+                                   NS_LITERAL_STRING("gamepadbuttonup");
+  je->InitGamepadButtonEvent(name, false, false, aGamepad, aButton);
+  je->SetTrusted(true);
+
+  aTarget->DispatchEvent(event, &defaultActionEnabled);
+}
+
+void
+GamepadService::NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue)
+{
+  if (mShuttingDown || aIndex >= mGamepads.Length()) {
+    return;
+  }
+  mGamepads[aIndex]->SetAxis(aAxis, aValue);
+
+  // Hold on to listeners in a separate array because firing events
+  // can mutate the mListeners array.
+  nsTArray<nsRefPtr<nsGlobalWindow> > listeners(mListeners);
+
+  for (uint32_t i = listeners.Length(); i > 0 ; ) {
+    --i;
+
+    // Only send events to non-background windows
+    if (!listeners[i]->GetOuterWindow() ||
+        listeners[i]->GetOuterWindow()->IsBackground()) {
+      continue;
+    }
+
+    if (!WindowHasSeenGamepad(listeners[i], aIndex)) {
+      SetWindowHasSeenGamepad(listeners[i], aIndex);
+      // This window hasn't seen this gamepad before, so
+      // send a connection event first.
+      NewConnectionEvent(aIndex, true);
+    }
+
+    nsRefPtr<nsDOMGamepad> gamepad = listeners[i]->GetGamepad(aIndex);
+    if (gamepad) {
+      gamepad->SetAxis(aAxis, aValue);
+      // Fire event
+      FireAxisMoveEvent(listeners[i], gamepad, aAxis, aValue);
+    }
+  }
+}
+
+void
+GamepadService::FireAxisMoveEvent(EventTarget* aTarget,
+                                  nsDOMGamepad* aGamepad,
+                                  uint32_t aAxis,
+                                  double aValue)
+{
+  nsCOMPtr<nsIDOMEvent> event;
+  bool defaultActionEnabled = true;
+  NS_NewDOMGamepadAxisMoveEvent(getter_AddRefs(event), aTarget, nullptr,
+                                nullptr);
+  nsCOMPtr<nsIDOMGamepadAxisMoveEvent> je = do_QueryInterface(event);
+  MOZ_ASSERT(je, "QI should not fail");
+
+  je->InitGamepadAxisMoveEvent(NS_LITERAL_STRING("gamepadaxismove"),
+                               false, false, aGamepad, aAxis, aValue);
+  je->SetTrusted(true);
+
+  aTarget->DispatchEvent(event, &defaultActionEnabled);
+}
+
+void
+GamepadService::NewConnectionEvent(uint32_t aIndex, bool aConnected)
+{
+  if (mShuttingDown || aIndex >= mGamepads.Length()) {
+    return;
+  }
+
+  // Hold on to listeners in a separate array because firing events
+  // can mutate the mListeners array.
+  nsTArray<nsRefPtr<nsGlobalWindow> > listeners(mListeners);
+
+  if (aConnected) {
+    for (uint32_t i = listeners.Length(); i > 0 ; ) {
+      --i;
+
+      // Only send events to non-background windows
+      if (!listeners[i]->GetOuterWindow() ||
+          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 (aConnected && !listeners[i]->HasSeenGamepadInput()) {
+        return;
+      }
+
+      SetWindowHasSeenGamepad(listeners[i], aIndex);
+
+      nsRefPtr<nsDOMGamepad> gamepad = listeners[i]->GetGamepad(aIndex);
+      if (gamepad) {
+        // Fire event
+        FireConnectionEvent(listeners[i], gamepad, aConnected);
+      }
+    }
+  } else {
+    // For disconnection events, fire one at every window that has received
+    // data from this gamepad.
+    for (uint32_t i = listeners.Length(); i > 0 ; ) {
+      --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)) {
+        nsRefPtr<nsDOMGamepad> gamepad = listeners[i]->GetGamepad(aIndex);
+        if (gamepad) {
+          gamepad->SetConnected(false);
+          // Fire event
+          FireConnectionEvent(listeners[i], gamepad, false);
+        }
+
+        if (gamepad) {
+          listeners[i]->RemoveGamepad(aIndex);
+        }
+      }
+    }
+  }
+}
+
+void
+GamepadService::FireConnectionEvent(EventTarget* aTarget,
+                                    nsDOMGamepad* aGamepad,
+                                    bool aConnected)
+{
+  nsCOMPtr<nsIDOMEvent> event;
+  bool defaultActionEnabled = true;
+  NS_NewDOMGamepadEvent(getter_AddRefs(event), aTarget, nullptr, nullptr);
+  nsCOMPtr<nsIDOMGamepadEvent> je = do_QueryInterface(event);
+  MOZ_ASSERT(je, "QI should not fail");
+
+  nsString name = aConnected ? NS_LITERAL_STRING("gamepadconnected") :
+                               NS_LITERAL_STRING("gamepaddisconnected");
+  je->InitGamepadEvent(name, false, false, aGamepad);
+  je->SetTrusted(true);
+
+  aTarget->DispatchEvent(event, &defaultActionEnabled);
+}
+
+void
+GamepadService::SyncGamepadState(uint32_t aIndex, nsDOMGamepad* aGamepad)
+{
+  if (mShuttingDown || aIndex > mGamepads.Length()) {
+    return;
+  }
+
+  aGamepad->SyncState(mGamepads[aIndex]);
+}
+
+// static
+already_AddRefed<GamepadService>
+GamepadService::GetService()
+{
+  if (sShutdown) {
+    return nullptr;
+  }
+
+  if (!gGamepadServiceSingleton) {
+    gGamepadServiceSingleton = new GamepadService();
+    ClearOnShutdown(&gGamepadServiceSingleton);
+  }
+  nsRefPtr<GamepadService> service(gGamepadServiceSingleton);
+  return service.forget();
+}
+
+bool
+GamepadService::WindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex)
+{
+  nsRefPtr<nsDOMGamepad> gamepad = aWindow->GetGamepad(aIndex);
+  return gamepad != nullptr;
+}
+
+void
+GamepadService::SetWindowHasSeenGamepad(nsGlobalWindow* aWindow,
+                                        uint32_t aIndex,
+                                        bool aHasSeen)
+{
+  if (mListeners.IndexOf(aWindow) == NoIndex) {
+    // This window isn't even listening for gamepad events.
+    return;
+  }
+
+  if (aHasSeen) {
+    aWindow->SetHasSeenGamepadInput(true);
+    nsRefPtr<nsDOMGamepad> gamepad = mGamepads[aIndex]->Clone();
+    aWindow->AddGamepad(aIndex, gamepad);
+  } else {
+    aWindow->RemoveGamepad(aIndex);
+  }
+}
+
+// static
+void
+GamepadService::TimeoutHandler(nsITimer* aTimer, void* aClosure)
+{
+  // the reason that we use self, instead of just using nsITimerCallback or nsIObserver
+  // is so that subclasses are free to use timers without worry about the base classes's
+  // usage.
+  GamepadService* self = reinterpret_cast<GamepadService*>(aClosure);
+  if (!self) {
+    NS_ERROR("no self");
+    return;
+  }
+
+  if (self->mShuttingDown) {
+    return;
+  }
+
+  if (self->mListeners.Length() == 0) {
+    mozilla::hal::StopMonitoringGamepadStatus();
+    self->mStarted = false;
+    if (!self->mGamepads.IsEmpty()) {
+      self->mGamepads.Clear();
+    }
+  }
+}
+
+void
+GamepadService::StartCleanupTimer()
+{
+  if (mTimer) {
+    mTimer->Cancel();
+  }
+
+  mTimer = do_CreateInstance("@mozilla.org/timer;1");
+  if (mTimer) {
+    mTimer->InitWithFuncCallback(TimeoutHandler,
+                                 this,
+                                 kCleanupDelayMS,
+                                 nsITimer::TYPE_ONE_SHOT);
+  }
+}
+
+/*
+ * 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.
+ */
+NS_IMPL_ISUPPORTS1(GamepadServiceTest, nsIGamepadServiceTest)
+
+GamepadServiceTest* GamepadServiceTest::sSingleton = nullptr;
+
+// static
+already_AddRefed<GamepadServiceTest>
+GamepadServiceTest::CreateService()
+{
+  if (sSingleton == nullptr) {
+    sSingleton = new GamepadServiceTest();
+  }
+  nsRefPtr<GamepadServiceTest> service = sSingleton;
+  return service.forget();
+}
+
+GamepadServiceTest::GamepadServiceTest()
+{
+  /* member initializers and constructor code */
+}
+
+/* uint32_t addGamepad (in string id, in uint32_t numButtons, in uint32_t numAxes); */
+NS_IMETHODIMP GamepadServiceTest::AddGamepad(const char* aID,
+                                             uint32_t aNumButtons,
+                                             uint32_t aNumAxes,
+                                             uint32_t* aRetval)
+{
+  *aRetval = gGamepadServiceSingleton->AddGamepad(aID,
+                                                  aNumButtons,
+                                                  aNumAxes);
+  return NS_OK;
+}
+
+/* void removeGamepad (in uint32_t index); */
+NS_IMETHODIMP GamepadServiceTest::RemoveGamepad(uint32_t aIndex)
+{
+  gGamepadServiceSingleton->RemoveGamepad(aIndex);
+  return NS_OK;
+}
+
+/* void newButtonEvent (in uint32_t index, in uint32_t button,
+   in boolean pressed); */
+NS_IMETHODIMP GamepadServiceTest::NewButtonEvent(uint32_t aIndex,
+                                                 uint32_t aButton,
+                                                 bool aPressed)
+{
+  gGamepadServiceSingleton->NewButtonEvent(aIndex, aButton, aPressed);
+  return NS_OK;
+}
+
+/* void newAxisMoveEvent (in uint32_t index, in uint32_t axis,
+   in double value); */
+NS_IMETHODIMP GamepadServiceTest::NewAxisMoveEvent(uint32_t aIndex,
+                                                   uint32_t aAxis,
+                                                   double aValue)
+{
+  gGamepadServiceSingleton->NewAxisMoveEvent(aIndex, aAxis, aValue);
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/system/GamepadService.h
@@ -0,0 +1,126 @@
+/* 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_GamepadService_h_
+#define mozilla_dom_GamepadService_h_
+
+#include "mozilla/StandardInteger.h"
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsDOMGamepad.h"
+#include "nsIGamepadServiceTest.h"
+#include "nsGlobalWindow.h"
+#include "nsIFocusManager.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+
+class nsIDOMDocument;
+
+namespace mozilla {
+namespace dom {
+
+class EventTarget;
+
+class GamepadService : public nsIObserver
+{
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  // Get the singleton service
+  static already_AddRefed<GamepadService> GetService();
+
+  void BeginShutdown();
+
+  // 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, and return its index.
+  uint32_t AddGamepad(const char* aID, uint32_t aNumButtons, uint32_t aNumAxes);
+  // Remove the gamepad at |aIndex| from the list of known gamepads.
+  void RemoveGamepad(uint32_t aIndex);
+
+  //TODO: the spec uses double values for buttons, to allow for analog
+  // buttons.
+  void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
+  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, nsDOMGamepad* aGamepad);
+
+ protected:
+  GamepadService();
+  virtual ~GamepadService() {};
+  void StartCleanupTimer();
+
+  void NewConnectionEvent(uint32_t aIndex, bool aConnected);
+  void FireAxisMoveEvent(EventTarget* aTarget,
+                         nsDOMGamepad* aGamepad,
+                         uint32_t axis,
+                         double value);
+  void FireButtonEvent(EventTarget* aTarget,
+                       nsDOMGamepad* aGamepad,
+                       uint32_t aButton,
+                       double aValue);
+  void FireConnectionEvent(EventTarget* aTarget,
+                           nsDOMGamepad* aGamepad,
+                           bool aConnected);
+
+  // true if the platform-specific backend has started work
+  bool mStarted;
+  // true when shutdown has begun
+  bool mShuttingDown;
+
+ private:
+  // 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);
+  // Indicate that a window has recieved data from a gamepad.
+  void SetWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex,
+                               bool aHasSeen = true);
+
+  static void TimeoutHandler(nsITimer* aTimer, void* aClosure);
+  static bool sShutdown;
+
+  // Gamepads connected to the system. Copies of these are handed out
+  // to each window.
+  nsTArray<nsRefPtr<nsDOMGamepad> > mGamepads;
+  // nsGlobalWindows that are listening for gamepad events.
+  // has been sent to that window.
+  nsTArray<nsRefPtr<nsGlobalWindow> > mListeners;
+  nsCOMPtr<nsITimer> mTimer;
+  nsCOMPtr<nsIFocusManager> mFocusManager;
+  nsCOMPtr<nsIObserver> mObserver;
+};
+
+// 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;
+  virtual ~GamepadServiceTest() {};
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#define NS_GAMEPAD_TEST_CID \
+{ 0xfb1fcb57, 0xebab, 0x4cf4, \
+{ 0x96, 0x3b, 0x1e, 0x4d, 0xb8, 0x52, 0x16, 0x96 } }
+#define NS_GAMEPAD_TEST_CONTRACTID "@mozilla.org/gamepad-test;1"
+
+#endif // mozilla_dom_GamepadService_h_
--- a/dom/system/Makefile.in
+++ b/dom/system/Makefile.in
@@ -15,16 +15,20 @@ FAIL_ON_WARNINGS := 1
 
 DEFINES += -DDLL_PREFIX=\"$(DLL_PREFIX)\" -DDLL_SUFFIX=\"$(DLL_SUFFIX)\"
 
 CPPSRCS     = \
   nsDeviceSensors.cpp \
   OSFileConstants.cpp \
   $(NULL)
 
+ifdef MOZ_GAMEPAD
+CPPSRCS += GamepadService.cpp
+endif
+
 # We fire the nsDOMDeviceAcceleration
 LOCAL_INCLUDES += \
   -I$(topsrcdir)/content/events/src \
   -I$(topsrcdir)/dom/base \
   -I$(topsrcdir)/dom/bindings \
   $(NULL)
 
 # On Systems that have build in geolocation providers,
@@ -50,23 +54,27 @@ EXPORTS_NAMESPACES = mozilla
 EXPORTS     = \
   nsDeviceSensors.h \
   $(NULL)
 
 EXPORTS_mozilla = \
   OSFileConstants.h \
   $(NULL)
 
-# We fire the nsDOMDeviceAcceleration
 LOCAL_INCLUDES += \
   -I$(topsrcdir)/content/events/src \
   -I$(topsrcdir)/js/xpconnect/loader \
   $(NULL)
+ifdef MOZ_GAMEPAD
+EXPORTS_NAMESPACES += mozilla/dom
+EXPORTS_mozilla/dom = \
+  GamepadService.h \
+  $(NULL)
+endif
 
 include $(topsrcdir)/config/config.mk
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
 LIBXUL_LIBRARY   = 1
 FORCE_STATIC_LIB = 1
 EXPORT_LIBRARY = 1
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
-
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/gamepad/Makefile.in
@@ -0,0 +1,23 @@
+# 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/.
+
+DEPTH		= ../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir	= dom/tests/mochitest/gamepad
+
+include $(DEPTH)/config/autoconf.mk
+
+MOCHITEST_FILES = \
+  test_gamepad_basic.html \
+  test_gamepad.html \
+  test_gamepad_frame_state_sync.html \
+  gamepad_frame_state.html \
+  test_gamepad_hidden_frame.html \
+  gamepad_frame.html \
+  mock_gamepad.js \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/gamepad/gamepad_frame.html
@@ -0,0 +1,16 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>frame</title>
+  <script type="text/javascript">
+    var buttonPresses = 0;
+    window.addEventListener("gamepadbuttondown", function() {
+      buttonPresses++;
+});
+  </script>
+</head>
+<body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/gamepad/gamepad_frame_state.html
@@ -0,0 +1,16 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>frame</title>
+  <script type="text/javascript">
+    var gamepad;
+    window.addEventListener("gamepadconnected", function(e) {
+      gamepad = e.gamepad;
+});
+  </script>
+</head>
+<body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/gamepad/mock_gamepad.js
@@ -0,0 +1,5 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+var GamepadService = (function() {
+  return SpecialPowers.Cc["@mozilla.org/gamepad-test;1"].getService(SpecialPowers.Ci.nsIGamepadServiceTest);
+})();
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/gamepad/moz.build
@@ -0,0 +1,4 @@
+# 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/.
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/gamepad/test_gamepad.html
@@ -0,0 +1,33 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test gamepad</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script type="text/javascript" src="mock_gamepad.js"></script>
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+window.addEventListener("gamepadconnected", connecthandler);
+// Add a gamepad
+var index = GamepadService.addGamepad("test gamepad", // id
+                                      4, // buttons
+                                      2);// axes
+// Press a button
+GamepadService.newButtonEvent(index, 0, true);
+function connecthandler(e) {
+ is(e.gamepad.id, "test gamepad", "correct gamepad name");
+  is(e.gamepad.buttons.length, 4, "correct number of buttons");
+  is(e.gamepad.axes.length, 2, "correct number of axes");
+  SimpleTest.executeSoon(function() {
+    GamepadService.removeGamepad(index);
+    SimpleTest.finish();
+  });
+}
+</script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/gamepad/test_gamepad_basic.html
@@ -0,0 +1,22 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test gamepad  </title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+// Not much we can test here, but this is enough to get the
+// gamepad service up and running, which should test that it
+// doesn't leak anything.
+window.addEventListener("gamepadconnected", function() {});
+SimpleTest.ok(true, "dummy check");
+
+</script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/gamepad/test_gamepad_frame_state_sync.html
@@ -0,0 +1,85 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test hidden frames</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script type="text/javascript" src="mock_gamepad.js"></script>
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+var index = GamepadService.addGamepad("test gamepad", // id
+                                      4, // buttons
+                                      2);// axes
+
+function setFrameVisible(f, visible) {
+  var Ci = SpecialPowers.wrap(Components.interfaces);
+  var docshell = SpecialPowers.wrap(f.contentWindow).QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell);
+  docshell.isActive = visible;
+}
+
+var frames_loaded = 0;
+var f1, f2;
+function frame_loaded() {
+  frames_loaded++;
+  if (frames_loaded == 2) {
+    f1 = document.getElementById('f1');
+    f2 = document.getElementById('f2');
+    // Now press the button, but don't release it.
+    GamepadService.newButtonEvent(index, 0, true);
+ }
+}
+
+window.addEventListener("gamepadbuttondown", function() {
+  // Wait to ensure that all frames received the button press as well.
+ SpecialPowers.executeSoon(tests[testNum++]);
+});
+
+var testNum = 0;
+var tests = [
+  check_button_pressed,
+  check_second_frame_no_button_press,
+];
+
+function check_button_pressed() {
+  // At this point the both frames should see the button as pressed.
+  is(f1.contentWindow.gamepad.buttons[0], 1, "frame 1 sees button pressed");
+  is(f2.contentWindow.gamepad.buttons[0], 1, "frame 2 sees button pressed");
+
+  // Now release the button, then hide the second frame.
+  GamepadService.newButtonEvent(index, 0, false);
+  setFrameVisible(f2, false);
+  SpecialPowers.executeSoon(function() {
+    // Now press the button, but don't release it.
+    GamepadService.newButtonEvent(index, 0, true);
+  });
+}
+
+function check_second_frame_no_button_press () {
+  /*
+   * At this point the first frame should see the button as pressed,
+   * but the second frame should not, since it's hidden.
+   */
+  is(f1.contentWindow.gamepad.buttons[0], 1, "frame 1 sees button pressed");
+  is(f2.contentWindow.gamepad.buttons[0], 0, "frame 2 should not see button pressed");
+
+  // Now unhide the second frame.
+  setFrameVisible(f2, true);
+  SpecialPowers.executeSoon(function() {
+    // Now that the frame is visible again, it should see the button
+    // that was pressed.
+    is(f2.contentWindow.gamepad.buttons[0], 1, "frame 2 sees button pressed");
+    // cleanup
+    GamepadService.removeGamepad(index);
+    SimpleTest.finish();
+  });
+}
+
+</script>
+<iframe id="f1" src="gamepad_frame_state.html" onload="frame_loaded()"></iframe>
+<iframe id="f2" src="gamepad_frame_state.html" onload="frame_loaded()"></iframe>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/gamepad/test_gamepad_hidden_frame.html
@@ -0,0 +1,71 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test hidden frames</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script type="text/javascript" src="mock_gamepad.js"></script>
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+var index = GamepadService.addGamepad("test gamepad", // id
+                                      4, // buttons
+                                      2);// axes
+
+function pressButton() {
+  GamepadService.newButtonEvent(index, 0, true);
+  GamepadService.newButtonEvent(index, 0, false);
+}
+
+function setFrameVisible(f, visible) {
+  var Ci = SpecialPowers.wrap(Components.interfaces);
+  var docshell = SpecialPowers.wrap(f.contentWindow).QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell);
+  docshell.isActive = visible;
+}
+
+var frames_loaded = 0;
+var f1, f2;
+function frame_loaded() {
+  frames_loaded++;
+ if (frames_loaded == 2) {
+    f1 = document.getElementById('f1');
+    f2 = document.getElementById('f2');
+    pressButton();
+  }
+}
+
+window.addEventListener("gamepadbuttondown", function() {
+  // Wait to ensure that all frames received the button press as well.
+ SpecialPowers.executeSoon(tests[testNum++]);
+});
+
+var testNum = 0;
+var tests = [
+  test1,
+  test2,
+];
+
+function test1() {
+  is(f1.contentWindow.buttonPresses, 1, "right number of button presses in frame 1");
+  is(f2.contentWindow.buttonPresses, 1, "right number of button presses in frame 2");
+
+  // Now hide the second frame and send another button press.
+  setFrameVisible(f2, false);
+  SpecialPowers.executeSoon(function() { pressButton(); });
+}
+
+function test2() {
+  is(f1.contentWindow.buttonPresses, 2, "right number of button presses in frame 1");
+  is(f2.contentWindow.buttonPresses, 1, "right number of button presses in frame 2");
+  GamepadService.removeGamepad(index);
+  SimpleTest.finish();
+}
+
+</script>
+<iframe id="f1" src="gamepad_frame.html" onload="frame_loaded()"></iframe>
+<iframe id="f2" src="gamepad_frame.html" onload="frame_loaded()"></iframe>
+</body>
+</html>
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -535,17 +535,21 @@ var interfaceNamesInGlobalScope =
     "RTCIceCandidate",
     "RTCPeerConnection",
     "LocalMediaStream",
     "CSSConditionRule",
     "CSSGroupingRule",
     "AsyncScrollEventDetail",
     "MozSmsSegmentInfo",
     "DOMCursor",
-    "BlobEvent"
+    "BlobEvent",
+    "Gamepad",
+    "GamepadEvent",
+    "GamepadButtonEvent",
+    "GamepadAxisMoveEvent"
   ]
 
 for (var i in SpecialPowers.Components.interfaces) {
   var s = i.toString();
   var name = null;
   if (s.indexOf("nsIDOM") == 0) {
     name = s.substring("nsIDOM".length);
   } else if (s.indexOf("nsI") == 0) {
--- a/dom/tests/mochitest/moz.build
+++ b/dom/tests/mochitest/moz.build
@@ -20,12 +20,15 @@ DIRS += [
     'sessionstorage',
     'storageevent',
     'pointerlock',
     'notification',
     'webapps',
     'webcomponents',
 ]
 
+if CONFIG['MOZ_GAMEPAD']:
+   DIRS += ['gamepad']
+
 #needs IPC support, also tests do not run successfully in Firefox for now
 #if CONFIG['MOZ_BUILD_APP'] != 'mobile':
 #    DIRS += ['notification']
 
--- a/hal/Hal.cpp
+++ b/hal/Hal.cpp
@@ -624,16 +624,26 @@ void PowerOff()
 
 void StartForceQuitWatchdog(ShutdownMode aMode, int32_t aTimeoutSecs)
 {
   AssertMainProcess();
   AssertMainThread();
   PROXY_IF_SANDBOXED(StartForceQuitWatchdog(aMode, aTimeoutSecs));
 }
 
+void StartMonitoringGamepadStatus()
+{
+  PROXY_IF_SANDBOXED(StartMonitoringGamepadStatus());
+}
+
+void StopMonitoringGamepadStatus()
+{
+  PROXY_IF_SANDBOXED(StopMonitoringGamepadStatus());
+}
+
 void
 RegisterWakeLockObserver(WakeLockObserver* aObserver)
 {
   AssertMainThread();
   sWakeLockObservers.AddObserver(aObserver);
 }
 
 void
--- a/hal/Hal.h
+++ b/hal/Hal.h
@@ -554,16 +554,26 @@ hal::FMRadioSettings GetFMBandSettings(h
  */
 void StartForceQuitWatchdog(hal::ShutdownMode aMode, int32_t aTimeoutSecs);
 
 /**
  * Perform Factory Reset to wipe out all user data.
  */
 void FactoryReset();
 
+/**
+ * Start monitoring the status of gamepads attached to the system.
+ */
+void StartMonitoringGamepadStatus();
+
+/**
+ * Stop monitoring the status of gamepads attached to the system.
+ */
+void StopMonitoringGamepadStatus();
+
 } // namespace MOZ_HAL_NAMESPACE
 } // namespace mozilla
 
 #ifdef MOZ_DEFINED_HAL_NAMESPACE
 # undef MOZ_DEFINED_HAL_NAMESPACE
 # undef MOZ_HAL_NAMESPACE
 #endif
 
--- a/hal/Makefile.in
+++ b/hal/Makefile.in
@@ -37,16 +37,20 @@ EXPORTS_mozilla = \
 
 CPPSRCS = \
   Hal.cpp \
   SandboxHal.cpp \
   WindowIdentifier.cpp \
   HalWakeLock.cpp \
   $(NULL)
 
+ifeq (stub,$(MOZ_GAMEPAD_BACKEND))
+CPPSRCS += FallbackGamepad.cpp
+endif
+
 ifeq (android,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += \
   AndroidHal.cpp \
   AndroidSensor.cpp \
   FallbackPower.cpp \
   FallbackAlarm.cpp \
   $(NULL)
 else ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
new file mode 100644
--- /dev/null
+++ b/hal/fallback/FallbackGamepad.cpp
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* 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 "Hal.h"
+
+namespace mozilla {
+namespace hal_impl {
+
+void StartMonitoringGamepadStatus()
+{}
+
+void StopMonitoringGamepadStatus()
+{}
+
+} // hal_impl
+} // namespace mozilla
--- a/hal/sandbox/SandboxHal.cpp
+++ b/hal/sandbox/SandboxHal.cpp
@@ -258,16 +258,23 @@ EnableSensorNotifications(SensorType aSe
   Hal()->SendEnableSensorNotifications(aSensor);
 }
 
 void
 DisableSensorNotifications(SensorType aSensor) {
   Hal()->SendDisableSensorNotifications(aSensor);
 }
 
+//TODO: bug 852944 - IPC implementations of these
+void StartMonitoringGamepadStatus()
+{}
+
+void StopMonitoringGamepadStatus()
+{}
+
 void
 EnableWakeLockNotifications()
 {
   Hal()->SendEnableWakeLockNotifications();
 }
 
 void
 DisableWakeLockNotifications()
--- a/js/xpconnect/src/event_impl_gen.conf.in
+++ b/js/xpconnect/src/event_impl_gen.conf.in
@@ -39,17 +39,22 @@ simple_events = [
     'MozVoicemailEvent',
     'USSDReceivedEvent',
 #endif
     'ElementReplaceEvent',
     'MozSmsEvent',
     'MozMmsEvent',
     'DeviceStorageChangeEvent',
     'PopupBlockedEvent',
-    'BlobEvent'
+    'BlobEvent',
+#ifdef MOZ_GAMEPAD
+    'GamepadEvent',
+    'GamepadButtonEvent',
+    'GamepadAxisMoveEvent',
+#endif
   ]
 
 """ include file names """
 special_includes = [
     'DictionaryHelpers.h',
     'nsContentUtils.h',
     'nsIDOMApplicationRegistry.h',
     'nsIDOMFile.h'
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -212,16 +212,19 @@ NS_NewXULContentBuilder(nsISupports* aOu
 nsresult
 NS_NewXULTreeBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult);
 #endif
 
 static void Shutdown();
 
 #include "nsGeolocation.h"
 #include "nsDeviceSensors.h"
+#ifdef MOZ_GAMEPAD
+#include "mozilla/dom/GamepadService.h"
+#endif
 #include "nsCSPService.h"
 #include "nsISmsService.h"
 #include "nsIMobileMessageService.h"
 #include "nsIMobileMessageDatabaseService.h"
 #include "mozilla/dom/mobilemessage/MobileMessageService.h"
 #include "mozilla/dom/mobilemessage/SmsServicesFactory.h"
 #include "nsIPowerManagerService.h"
 #include "nsIAlarmHalService.h"
@@ -311,16 +314,21 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIMobileMessageDatabaseService,
                                          SmsServicesFactory::CreateMobileMessageDatabaseService)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPowerManagerService,
                                          PowerManagerService::GetInstance)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIAlarmHalService,
                                          AlarmHalService::GetInstance)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITimeService,
                                          TimeService::GetInstance)
+#ifdef MOZ_GAMEPAD
+using mozilla::dom::GamepadServiceTest;
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(GamepadServiceTest,
+                                         GamepadServiceTest::CreateService)
+#endif
 
 #ifdef MOZ_WIDGET_GONK
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIGeolocationProvider,
                                          GonkGPSGeolocationProvider::GetSingleton)
 // Since the nsVolumeService constructor calls into nsIPowerManagerService,
 // we need it to be constructed sometime after nsIPowerManagerService.
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsVolumeService,
                                          nsVolumeService::GetSingleton)
@@ -834,16 +842,19 @@ NS_DEFINE_NAMED_CID(NS_POWERMANAGERSERVI
 NS_DEFINE_NAMED_CID(OSFILECONSTANTSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_ALARMHALSERVICE_CID);
 NS_DEFINE_NAMED_CID(TCPSOCKETCHILD_CID);
 NS_DEFINE_NAMED_CID(NS_TIMESERVICE_CID);
 #ifdef MOZ_WIDGET_GONK
 NS_DEFINE_NAMED_CID(GONK_GPS_GEOLOCATION_PROVIDER_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_MEDIAMANAGERSERVICE_CID);
+#ifdef MOZ_GAMEPAD
+NS_DEFINE_NAMED_CID(NS_GAMEPAD_TEST_CID);
+#endif
 
 static nsresult
 CreateWindowCommandTableConstructor(nsISupports *aOuter,
                                     REFNSIID aIID, void **aResult)
 {
   nsresult rv;
   nsCOMPtr<nsIControllerCommandTable> commandTable =
       do_CreateInstance(NS_CONTROLLERCOMMANDTABLE_CONTRACTID, &rv);
@@ -1113,16 +1124,19 @@ static const mozilla::Module::CIDEntry k
   { &kOSFILECONSTANTSSERVICE_CID, true, NULL, OSFileConstantsServiceConstructor },
   { &kNS_ALARMHALSERVICE_CID, false, NULL, nsIAlarmHalServiceConstructor },
   { &kTCPSOCKETCHILD_CID, false, NULL, TCPSocketChildConstructor },
   { &kNS_TIMESERVICE_CID, false, NULL, nsITimeServiceConstructor },
 #ifdef MOZ_WIDGET_GONK
   { &kGONK_GPS_GEOLOCATION_PROVIDER_CID, false, NULL, nsIGeolocationProviderConstructor },
 #endif
   { &kNS_MEDIAMANAGERSERVICE_CID, false, NULL, nsIMediaManagerServiceConstructor },
+#ifdef MOZ_GAMEPAD
+  { &kNS_GAMEPAD_TEST_CID, false, NULL, GamepadServiceTestConstructor },
+#endif
   { NULL }
 };
 
 static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
   XPCONNECT_CONTRACTS
   { "@mozilla.org/layout/xul-boxobject;1", &kNS_BOXOBJECT_CID },
 #ifdef MOZ_XUL
   { "@mozilla.org/layout/xul-boxobject-listbox;1", &kNS_LISTBOXOBJECT_CID },
@@ -1255,16 +1269,19 @@ static const mozilla::Module::ContractID
   { POWERMANAGERSERVICE_CONTRACTID, &kNS_POWERMANAGERSERVICE_CID },
   { OSFILECONSTANTSSERVICE_CONTRACTID, &kOSFILECONSTANTSSERVICE_CID },
   { ALARMHALSERVICE_CONTRACTID, &kNS_ALARMHALSERVICE_CID },
   { "@mozilla.org/tcp-socket-child;1", &kTCPSOCKETCHILD_CID },
   { TIMESERVICE_CONTRACTID, &kNS_TIMESERVICE_CID },
 #ifdef MOZ_WIDGET_GONK
   { GONK_GPS_GEOLOCATION_PROVIDER_CONTRACTID, &kGONK_GPS_GEOLOCATION_PROVIDER_CID },
 #endif
+#ifdef MOZ_GAMEPAD
+  { NS_GAMEPAD_TEST_CONTRACTID, &kNS_GAMEPAD_TEST_CID },
+#endif
   { MEDIAMANAGERSERVICE_CONTRACTID, &kNS_MEDIAMANAGERSERVICE_CID },
   { NULL }
 };
 
 static const mozilla::Module::CategoryEntry kLayoutCategories[] = {
   XPCONNECT_CATEGORIES
 #ifdef MOZ_MEDIA
   { JAVASCRIPT_GLOBAL_CONSTRUCTOR_CATEGORY, "Audio", NS_HTMLAUDIOELEMENT_CONTRACTID },
--- a/widget/nsGUIEvent.h
+++ b/widget/nsGUIEvent.h
@@ -447,16 +447,28 @@ enum nsEventStructType {
 //System time is changed
 #define NS_MOZ_TIME_CHANGE_EVENT     5500
 
 // Network packet events.
 #define NS_NETWORK_EVENT_START       5600
 #define NS_NETWORK_UPLOAD_EVENT      (NS_NETWORK_EVENT_START + 1)
 #define NS_NETWORK_DOWNLOAD_EVENT    (NS_NETWORK_EVENT_START + 2)
 
+#ifdef MOZ_GAMEPAD
+// Gamepad input events
+#define NS_GAMEPAD_START         6000
+#define NS_GAMEPAD_BUTTONDOWN    (NS_GAMEPAD_START)
+#define NS_GAMEPAD_BUTTONUP      (NS_GAMEPAD_START+1)
+#define NS_GAMEPAD_AXISMOVE      (NS_GAMEPAD_START+2)
+#define NS_GAMEPAD_CONNECTED     (NS_GAMEPAD_START+3)
+#define NS_GAMEPAD_DISCONNECTED  (NS_GAMEPAD_START+4)
+// Keep this defined to the same value as the event above
+#define NS_GAMEPAD_END           (NS_GAMEPAD_START+4)
+#endif
+
 /**
  * Return status for event processors, nsEventStatus, is defined in
  * nsEvent.h.
  */
 
 /**
  * different types of (top-level) window z-level positioning
  */