bug 604039 - Add DOM Gamepad APIs. r=smaug
authorTed Mielczarek <ted.mielczarek@gmail.com>
Wed, 03 Aug 2011 14:12:08 -0400
changeset 125675 370e227ed45259ccb851bbce041027097a1bac13
parent 125674 343ccbfa1f9aba392665d4055d904990679a0fb8
child 125676 6702404b73e24fb405df41187751ae155d7ea25e
push idunknown
push userunknown
push dateunknown
reviewerssmaug
bugs604039
milestone22.0a1
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
  */