Bug 1299937 - Part 5: Add gamepad extension API tests; r=Lenzak,qdot
authorDaosheng Mu <daoshengmu@gmail.com>
Thu, 02 Feb 2017 15:00:51 +0800
changeset 349325 3c1984bdf4047df79fde8e84264f17de80351f20
parent 349324 3381a8485d054b31ada9f67fd880267db1d6b0ef
child 349326 5d8e162ef5f73106037a6ad5c34770f26ab86447
push id31550
push usercbook@mozilla.com
push dateFri, 24 Mar 2017 13:22:27 +0000
treeherdermozilla-central@473e0b201761 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersLenzak, qdot
bugs1299937
milestone55.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 1299937 - Part 5: Add gamepad extension API tests; r=Lenzak,qdot MozReview-Commit-ID: ICeDyGUn4XH
dom/gamepad/GamepadManager.cpp
dom/gamepad/GamepadManager.h
dom/gamepad/GamepadPlatformService.cpp
dom/gamepad/GamepadPlatformService.h
dom/gamepad/GamepadServiceTest.cpp
dom/gamepad/GamepadServiceTest.h
dom/gamepad/android/AndroidGamepad.cpp
dom/gamepad/cocoa/CocoaGamepad.cpp
dom/gamepad/ipc/GamepadEventChannelChild.cpp
dom/gamepad/ipc/GamepadEventChannelChild.h
dom/gamepad/ipc/GamepadEventChannelParent.cpp
dom/gamepad/ipc/GamepadEventChannelParent.h
dom/gamepad/ipc/GamepadTestChannelChild.h
dom/gamepad/ipc/GamepadTestChannelParent.cpp
dom/gamepad/ipc/PGamepadEventChannel.ipdl
dom/gamepad/linux/LinuxGamepad.cpp
dom/gamepad/windows/WindowsGamepad.cpp
dom/tests/mochitest/gamepad/mochitest.ini
dom/tests/mochitest/gamepad/test_check_timestamp.html
dom/tests/mochitest/gamepad/test_gamepad.html
dom/tests/mochitest/gamepad/test_gamepad_connect_events.html
dom/tests/mochitest/gamepad/test_gamepad_extensions.html
dom/tests/mochitest/gamepad/test_gamepad_frame_state_sync.html
dom/tests/mochitest/gamepad/test_gamepad_hidden_frame.html
dom/tests/mochitest/gamepad/test_navigator_gamepads.html
dom/webidl/GamepadServiceTest.webidl
--- a/dom/gamepad/GamepadManager.cpp
+++ b/dom/gamepad/GamepadManager.cpp
@@ -681,17 +681,22 @@ GamepadManager::VibrateHaptic(uint32_t a
 
   if (aControllerIdx >= VR_GAMEPAD_IDX_OFFSET) {
     uint32_t index = aControllerIdx - VR_GAMEPAD_IDX_OFFSET;
     mVRChannelChild->AddPromise(mPromiseID, promise);
     mVRChannelChild->SendVibrateHaptic(index, aHapticIndex,
                                        aIntensity, aDuration,
                                        mPromiseID);
   } else {
-    // TODO: Bug 680289, implement for standard gamepads
+    for (const auto& channelChild: mChannelChildren) {
+      channelChild->AddPromise(mPromiseID, promise);
+      channelChild->SendVibrateHaptic(aControllerIdx, aHapticIndex,
+                                      aIntensity, aDuration,
+                                      mPromiseID);
+    }
   }
 
   ++mPromiseID;
   return promise.forget();
 }
 
 //Override nsIIPCBackgroundChildCreateCallback
 void
--- a/dom/gamepad/GamepadManager.h
+++ b/dom/gamepad/GamepadManager.h
@@ -82,17 +82,17 @@ class GamepadManager final : public nsIO
   already_AddRefed<Gamepad> GetGamepad(uint32_t aIndex) const;
 
   // Receive GamepadChangeEvent messages from parent process to fire DOM events
   void Update(const GamepadChangeEvent& aGamepadEvent);
 
   // Trigger vibrate haptic event to gamepad channels.
   already_AddRefed<Promise> VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                                           double aIntensity, double aDuration,
-                                          nsIGlobalObject* aGlobal, ErrorResult& aRv);
+                                          nsIGlobalObject* aGlobal);
 
  protected:
   GamepadManager();
   ~GamepadManager() {};
 
   // Fire a gamepadconnected or gamepaddisconnected event for the gamepad
   // at |aIndex| to all windows that are listening and have received
   // gamepad input.
--- a/dom/gamepad/GamepadPlatformService.cpp
+++ b/dom/gamepad/GamepadPlatformService.cpp
@@ -81,27 +81,29 @@ GamepadPlatformService::NotifyGamepadCha
   for(uint32_t i = 0; i < mChannelParents.Length(); ++i) {
     mChannelParents[i]->DispatchUpdateEvent(e);
   }
 }
 
 uint32_t
 GamepadPlatformService::AddGamepad(const char* aID,
                                    GamepadMappingType aMapping,
-                                   uint32_t aNumButtons, uint32_t aNumAxes)
+                                   GamepadHand aHand,
+                                   uint32_t aNumButtons, uint32_t aNumAxes,
+                                   uint32_t aHaptics)
 {
   // This method is called by monitor thread populated in
   // platform-dependent backends
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(!NS_IsMainThread());
 
   uint32_t index = ++mGamepadIndex;
   GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), index,
-                 aMapping, GamepadHand::_empty, GamepadServiceType::Standard,
-                 aNumButtons, aNumAxes, 0);
+                 aMapping, aHand, GamepadServiceType::Standard,
+                 aNumButtons, aNumAxes, aHaptics);
 
   NotifyGamepadChange<GamepadAdded>(a);
   return index;
 }
 
 void
 GamepadPlatformService::RemoveGamepad(uint32_t aIndex)
 {
@@ -147,16 +149,29 @@ GamepadPlatformService::NewAxisMoveEvent
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(!NS_IsMainThread());
   GamepadAxisInformation a(aIndex, GamepadServiceType::Standard,
                            aAxis, aValue);
   NotifyGamepadChange<GamepadAxisInformation>(a);
 }
 
 void
+GamepadPlatformService::NewPoseEvent(uint32_t aIndex,
+                                     const GamepadPoseState& aPose)
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+  GamepadPoseInformation a(aIndex, GamepadServiceType::Standard,
+                           aPose);
+  NotifyGamepadChange<GamepadPoseInformation>(a);
+}
+
+void
 GamepadPlatformService::ResetGamepadIndexes()
 {
   // This method is called by monitor thread populated in
   // platform-dependent backends
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(!NS_IsMainThread());
   mGamepadIndex = 0;
 }
--- a/dom/gamepad/GamepadPlatformService.h
+++ b/dom/gamepad/GamepadPlatformService.h
@@ -33,33 +33,37 @@ class GamepadPlatformService final
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadPlatformService)
  public:
   //Get the singleton service
   static already_AddRefed<GamepadPlatformService> GetParentService();
 
   // Add a gamepad to the list of known gamepads, and return its index.
   uint32_t AddGamepad(const char* aID, GamepadMappingType aMapping,
-                      uint32_t aNumButtons, uint32_t aNumAxes);
+                      GamepadHand aHand, uint32_t aNumButtons,
+                      uint32_t aNumAxes, uint32_t aNumHaptics);
   // Remove the gamepad at |aIndex| from the list of known gamepads.
   void RemoveGamepad(uint32_t aIndex);
 
   // Update the state of |aButton| for the gamepad at |aIndex| for all
   // windows that are listening and visible, and fire one of
   // a gamepadbutton{up,down} event at them as well.
   // aPressed is used for digital buttons, aValue is for analog buttons.
   void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed,
                       double aValue);
   // When only a digital button is available the value will be synthesized.
   void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
 
   // Update the state of |aAxis| for the gamepad at |aIndex| for all
   // windows that are listening and visible, and fire a gamepadaxismove
   // event at them as well.
   void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue);
+  // Update the state of |aState| for the gamepad at |aIndex| for all
+  // windows that are listening and visible.
+  void NewPoseEvent(uint32_t aIndex, const GamepadPoseState& aState);
 
   // When shutting down the platform communications for gamepad, also reset the
   // indexes.
   void ResetGamepadIndexes();
 
   //Add IPDL parent instance
   void AddChannelParent(GamepadEventChannelParent* aParent);
 
--- a/dom/gamepad/GamepadServiceTest.cpp
+++ b/dom/gamepad/GamepadServiceTest.cpp
@@ -108,28 +108,30 @@ GamepadServiceTest::DestroyPBackgroundAc
     // operations.
     mPendingOperations.Clear();
   }
 }
 
 already_AddRefed<Promise>
 GamepadServiceTest::AddGamepad(const nsAString& aID,
                                GamepadMappingType aMapping,
+                               GamepadHand aHand,
                                uint32_t aNumButtons,
                                uint32_t aNumAxes,
+                               uint32_t aNumHaptics,
                                ErrorResult& aRv)
 {
   if (mShuttingDown) {
     return nullptr;
   }
 
   GamepadAdded a(nsString(aID), 0,
-                 aMapping, GamepadHand::_empty,
+                 aMapping, aHand,
                  GamepadServiceType::Standard,
-                 aNumButtons, aNumAxes, 0);
+                 aNumButtons, aNumAxes, aNumHaptics);
   GamepadChangeEvent e(a);
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
 
   RefPtr<Promise> p = Promise::Create(go, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
@@ -226,16 +228,97 @@ GamepadServiceTest::NewAxisMoveEvent(uin
     mChild->SendGamepadTestEvent(id, e);
   } else {
     PendingOperation op(id, e);
     mPendingOperations.AppendElement(op);
   }
 }
 
 void
+GamepadServiceTest::NewPoseMove(uint32_t aIndex,
+                                const Nullable<Float32Array>& aOrient,
+                                const Nullable<Float32Array>& aPos,
+                                const Nullable<Float32Array>& aAngVelocity,
+                                const Nullable<Float32Array>& aAngAcceleration,
+                                const Nullable<Float32Array>& aLinVelocity,
+                                const Nullable<Float32Array>& aLinAcceleration)
+{
+  if (mShuttingDown) {
+    return;
+  }
+
+  GamepadPoseState poseState;
+  poseState.flags = GamepadCapabilityFlags::Cap_Orientation |
+                    GamepadCapabilityFlags::Cap_Position |
+                    GamepadCapabilityFlags::Cap_AngularAcceleration |
+                    GamepadCapabilityFlags::Cap_LinearAcceleration;
+  if (!aOrient.IsNull()) {
+    const Float32Array& value = aOrient.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 4);
+    poseState.orientation[0] = value.Data()[0];
+    poseState.orientation[1] = value.Data()[1];
+    poseState.orientation[2] = value.Data()[2];
+    poseState.orientation[3] = value.Data()[3];
+  }
+  if (!aPos.IsNull()) {
+    const Float32Array& value = aPos.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.position[0] = value.Data()[0];
+    poseState.position[1] = value.Data()[1];
+    poseState.position[2] = value.Data()[2];
+  }
+  if (!aAngVelocity.IsNull()) {
+    const Float32Array& value = aAngVelocity.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.angularVelocity[0] = value.Data()[0];
+    poseState.angularVelocity[1] = value.Data()[1];
+    poseState.angularVelocity[2] = value.Data()[2];
+  }
+  if (!aAngAcceleration.IsNull()) {
+    const Float32Array& value = aAngAcceleration.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.angularAcceleration[0] = value.Data()[0];
+    poseState.angularAcceleration[1] = value.Data()[1];
+    poseState.angularAcceleration[2] = value.Data()[2];
+  }
+  if (!aLinVelocity.IsNull()) {
+    const Float32Array& value = aLinVelocity.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.linearVelocity[0] = value.Data()[0];
+    poseState.linearVelocity[1] = value.Data()[1];
+    poseState.linearVelocity[2] = value.Data()[2];
+  }
+  if (!aLinAcceleration.IsNull()) {
+    const Float32Array& value = aLinAcceleration.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.linearAcceleration[0] = value.Data()[0];
+    poseState.linearAcceleration[1] = value.Data()[1];
+    poseState.linearAcceleration[2] = value.Data()[2];
+  }
+
+  GamepadPoseInformation a(aIndex, GamepadServiceType::Standard,
+                           poseState);
+  GamepadChangeEvent e(a);
+
+  uint32_t id = ++mEventNumber;
+  if (mChild) {
+    mChild->SendGamepadTestEvent(id, e);
+  } else {
+    PendingOperation op(id, e);
+    mPendingOperations.AppendElement(op);
+  }
+}
+
+void
 GamepadServiceTest::FlushPendingOperations()
 {
   for (uint32_t i=0; i < mPendingOperations.Length(); ++i) {
     PendingOperation op = mPendingOperations[i];
     if (op.mPromise) {
       mChild->AddPromise(op.mID, op.mPromise);
     }
     mChild->SendGamepadTestEvent(op.mID, op.mEvent);
--- a/dom/gamepad/GamepadServiceTest.h
+++ b/dom/gamepad/GamepadServiceTest.h
@@ -26,26 +26,38 @@ class GamepadServiceTest final : public 
 public:
   NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(GamepadServiceTest,
                                            DOMEventTargetHelper)
 
   GamepadMappingType NoMapping() const { return GamepadMappingType::_empty; }
   GamepadMappingType StandardMapping() const { return GamepadMappingType::Standard; }
+  GamepadHand NoHand() const { return GamepadHand::_empty; }
+  GamepadHand LeftHand() const { return GamepadHand::Left; }
+  GamepadHand RightHand() const { return GamepadHand::Right; }
 
   already_AddRefed<Promise> AddGamepad(const nsAString& aID,
                                        GamepadMappingType aMapping,
+                                       GamepadHand aHand,
                                        uint32_t aNumButtons,
                                        uint32_t aNumAxes,
+                                       uint32_t aNumHaptics,
                                        ErrorResult& aRv);
   void RemoveGamepad(uint32_t aIndex);
   void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
   void NewButtonValueEvent(uint32_t aIndex, uint32_t aButton, bool aPressed, double aValue);
   void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue);
+  void NewPoseMove(uint32_t aIndex,
+                   const Nullable<Float32Array>& aOrient,
+                   const Nullable<Float32Array>& aPos,
+                   const Nullable<Float32Array>& aAngVelocity,
+                   const Nullable<Float32Array>& aAngAcceleration,
+                   const Nullable<Float32Array>& aLinVelocity,
+                   const Nullable<Float32Array>& aLinAcceleration);
   void Shutdown();
 
   static already_AddRefed<GamepadServiceTest> CreateTestService(nsPIDOMWindowInner* aWindow);
   nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
   JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
 
 private:
 
--- a/dom/gamepad/android/AndroidGamepad.cpp
+++ b/dom/gamepad/android/AndroidGamepad.cpp
@@ -24,17 +24,18 @@ public:
         GamepadPlatformService::GetParentService();
     if (!service) {
       return;
     }
 
     if (aAdded) {
       const int svc_id = service->AddGamepad(
           "android", GamepadMappingType::Standard,
-          kStandardGamepadButtons, kStandardGamepadAxes);
+          GamepadHand::_empty, kStandardGamepadButtons,
+          kStandardGamepadAxes, 0); // TODO: Bug 680289, implement gamepad haptics for Android
       java::AndroidGamepadManager::OnGamepadAdded(aID, svc_id);
 
     } else {
       service->RemoveGamepad(aID);
     }
   }
 
   static void
--- a/dom/gamepad/cocoa/CocoaGamepad.cpp
+++ b/dom/gamepad/cocoa/CocoaGamepad.cpp
@@ -279,18 +279,20 @@ DarwinGamepadService::DeviceAdded(IOHIDD
   CFNumberGetValue(productIdRef, kCFNumberIntType, &productId);
   char product_name[128];
   CFStringGetCString(productRef, product_name,
                      sizeof(product_name), kCFStringEncodingASCII);
   char buffer[256];
   sprintf(buffer, "%x-%x-%s", vendorId, productId, product_name);
   uint32_t index = service->AddGamepad(buffer,
                                        mozilla::dom::GamepadMappingType::_empty,
+                                       mozilla::dom::GamepadHand::_empty,
                                        (int)mGamepads[slot].numButtons(),
-                                       (int)mGamepads[slot].numAxes());
+                                       (int)mGamepads[slot].numAxes(),
+                                       0); // TODO: Bug 680289, implement gamepad haptics for cocoa
   mGamepads[slot].mSuperIndex = index;
 }
 
 void
 DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device)
 {
   RefPtr<GamepadPlatformService> service =
     GamepadPlatformService::GetParentService();
--- a/dom/gamepad/ipc/GamepadEventChannelChild.cpp
+++ b/dom/gamepad/ipc/GamepadEventChannelChild.cpp
@@ -33,10 +33,30 @@ GamepadEventChannelChild::RecvGamepadUpd
                                        const GamepadChangeEvent& aGamepadEvent)
 {
   DebugOnly<nsresult> rv =
     NS_DispatchToMainThread(new GamepadUpdateRunnable(aGamepadEvent));
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
   return IPC_OK();
 }
 
+void
+GamepadEventChannelChild::AddPromise(const uint32_t& aID, dom::Promise* aPromise)
+{
+  MOZ_ASSERT(!mPromiseList.Get(aID, nullptr));
+  mPromiseList.Put(aID, aPromise);
+}
+
+mozilla::ipc::IPCResult
+GamepadEventChannelChild::RecvReplyGamepadVibrateHaptic(const uint32_t& aPromiseID)
+{
+  RefPtr<dom::Promise> p;
+  if (!mPromiseList.Get(aPromiseID, getter_AddRefs(p))) {
+    MOZ_CRASH("We should always have a promise.");
+  }
+
+  p->MaybeResolve(true);
+  mPromiseList.Remove(aPromiseID);
+  return IPC_OK();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/gamepad/ipc/GamepadEventChannelChild.h
+++ b/dom/gamepad/ipc/GamepadEventChannelChild.h
@@ -6,19 +6,25 @@
 #ifndef mozilla_dom_GamepadEventChannelChild_h_
 #define mozilla_dom_GamepadEventChannelChild_h_
 
 namespace mozilla{
 namespace dom{
 
 class GamepadEventChannelChild final : public PGamepadEventChannelChild
 {
- public:
+public:
   GamepadEventChannelChild() {}
   ~GamepadEventChannelChild() {}
   virtual mozilla::ipc::IPCResult
   RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override;
+  virtual mozilla::ipc::IPCResult
+  RecvReplyGamepadVibrateHaptic(const uint32_t& aPromiseID) override;
+  void AddPromise(const uint32_t& aID, dom::Promise* aPromise);
+
+private:
+  nsRefPtrHashtable<nsUint32HashKey, dom::Promise> mPromiseList;
 };
 
 }// namespace dom
 }// namespace mozilla
 
 #endif
--- a/dom/gamepad/ipc/GamepadEventChannelParent.cpp
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.cpp
@@ -68,16 +68,32 @@ GamepadEventChannelParent::RecvGamepadLi
   RefPtr<GamepadPlatformService> service =
     GamepadPlatformService::GetParentService();
   MOZ_ASSERT(service);
   service->RemoveChannelParent(this);
   Unused << Send__delete__(this);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+GamepadEventChannelParent::RecvVibrateHaptic(const uint32_t& aControllerIdx,
+                                   const uint32_t& aHapticIndex,
+                                   const double& aIntensity,
+                                   const double& aDuration,
+                                   const uint32_t& aPromiseID)
+{
+  // TODO: Bug 680289, implement for standard gamepads
+
+  if (SendReplyGamepadVibrateHaptic(aPromiseID)) {
+    return IPC_OK();
+  }
+
+  return IPC_FAIL(this, "SendReplyGamepadVibrateHaptic fail.");
+}
+
 void
 GamepadEventChannelParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnBackgroundThread();
 
   // It may be called because IPDL child side crashed, we'll
   // not receive RecvGamepadListenerRemoved in that case
   if (mHasGamepadListener) {
--- a/dom/gamepad/ipc/GamepadEventChannelParent.h
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.h
@@ -12,16 +12,21 @@ namespace dom{
 class GamepadEventChannelParent final : public PGamepadEventChannelParent
 {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadEventChannelParent)
   GamepadEventChannelParent();
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
   virtual mozilla::ipc::IPCResult RecvGamepadListenerAdded() override;
   virtual mozilla::ipc::IPCResult RecvGamepadListenerRemoved() override;
+  virtual mozilla::ipc::IPCResult RecvVibrateHaptic(const uint32_t& aControllerIdx,
+                                                    const uint32_t& aHapticIndex,
+                                                    const double& aIntensity,
+                                                    const double& aDuration,
+                                                    const uint32_t& aPromiseID) override;
   void DispatchUpdateEvent(const GamepadChangeEvent& aEvent);
   bool HasGamepadListener() const { return mHasGamepadListener; }
  private:
   ~GamepadEventChannelParent() {}
   bool mHasGamepadListener;
   nsCOMPtr<nsIThread> mBackgroundThread;
 };
 
--- a/dom/gamepad/ipc/GamepadTestChannelChild.h
+++ b/dom/gamepad/ipc/GamepadTestChannelChild.h
@@ -15,15 +15,16 @@ class GamepadTestChannelChild final : pu
 {
  public:
   GamepadTestChannelChild() {}
   ~GamepadTestChannelChild() {}
   void AddPromise(const uint32_t& aID, Promise* aPromise);
  private:
   virtual mozilla::ipc::IPCResult RecvReplyGamepadIndex(const uint32_t& aID,
                                                         const uint32_t& aIndex) override;
-  nsRefPtrHashtable<nsUint32HashKey, Promise> mPromiseList;
+
+  nsRefPtrHashtable<nsUint32HashKey, dom::Promise> mPromiseList;
 };
 
 }// namespace dom
 }// namespace mozilla
 
 #endif
--- a/dom/gamepad/ipc/GamepadTestChannelParent.cpp
+++ b/dom/gamepad/ipc/GamepadTestChannelParent.cpp
@@ -19,18 +19,20 @@ GamepadTestChannelParent::RecvGamepadTes
     GamepadPlatformService::GetParentService();
   MOZ_ASSERT(service);
   if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) {
     const GamepadAdded& a = aEvent.get_GamepadAdded();
     nsCString gamepadID;
     LossyCopyUTF16toASCII(a.id(), gamepadID);
     uint32_t index = service->AddGamepad(gamepadID.get(),
                                          static_cast<GamepadMappingType>(a.mapping()),
+                                         a.hand(),
                                          a.num_buttons(),
-                                         a.num_axes());
+                                         a.num_axes(),
+                                         a.num_haptics());
     if (!mShuttingdown) {
       Unused << SendReplyGamepadIndex(aID, index);
     }
     return IPC_OK();
   }
   if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) {
     const GamepadRemoved& a = aEvent.get_GamepadRemoved();
     service->RemoveGamepad(a.index());
@@ -41,16 +43,21 @@ GamepadTestChannelParent::RecvGamepadTes
     service->NewButtonEvent(a.index(), a.button(), a.pressed(), a.value());
     return IPC_OK();
   }
   if (aEvent.type() == GamepadChangeEvent::TGamepadAxisInformation) {
     const GamepadAxisInformation& a = aEvent.get_GamepadAxisInformation();
     service->NewAxisMoveEvent(a.index(), a.axis(), a.value());
     return IPC_OK();
   }
+  if (aEvent.type() == GamepadChangeEvent::TGamepadPoseInformation) {
+    const GamepadPoseInformation& a = aEvent.get_GamepadPoseInformation();
+    service->NewPoseEvent(a.index(), a.pose_state());
+    return IPC_OK();
+  }
 
   NS_WARNING("Unknown event type.");
   return IPC_FAIL_NO_REASON(this);
 }
 
 mozilla::ipc::IPCResult
 GamepadTestChannelParent::RecvShutdownChannel()
 {
--- a/dom/gamepad/ipc/PGamepadEventChannel.ipdl
+++ b/dom/gamepad/ipc/PGamepadEventChannel.ipdl
@@ -7,15 +7,18 @@ include GamepadEventTypes;
 namespace mozilla {
 namespace dom {
 
 async protocol PGamepadEventChannel {
   manager PBackground;
   parent:
     async GamepadListenerAdded();
     async GamepadListenerRemoved();
+    async VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
+                        double aIntensity, double aDuration, uint32_t aPromiseID);
   child:
     async __delete__();
     async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
+    async ReplyGamepadVibrateHaptic(uint32_t aPromiseID);
 };
 
 }
 }
--- a/dom/gamepad/linux/LinuxGamepad.cpp
+++ b/dom/gamepad/linux/LinuxGamepad.cpp
@@ -142,18 +142,20 @@ LinuxGamepadService::AddDevice(struct ud
   char numAxes = 0, numButtons = 0;
   ioctl(fd, JSIOCGAXES, &numAxes);
   gamepad.numAxes = numAxes;
   ioctl(fd, JSIOCGBUTTONS, &numButtons);
   gamepad.numButtons = numButtons;
 
   gamepad.index = service->AddGamepad(gamepad.idstring,
                                       mozilla::dom::GamepadMappingType::_empty,
+                                      mozilla::dom::GamepadHand::_empty,
                                       gamepad.numButtons,
-                                      gamepad.numAxes);
+                                      gamepad.numAxes,
+                                      0); // TODO: Bug 680289, implement gamepad haptics for Linux.
 
   gamepad.source_id =
     g_io_add_watch(channel,
                    GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
                    OnGamepadData,
                    GINT_TO_POINTER(gamepad.index));
   g_io_channel_unref(channel);
 
--- a/dom/gamepad/windows/WindowsGamepad.cpp
+++ b/dom/gamepad/windows/WindowsGamepad.cpp
@@ -498,18 +498,20 @@ WindowsGamepadService::ScanForXInputDevi
     Gamepad gamepad(kStandardGamepadAxes,
                     kStandardGamepadButtons,
                     true,
                     kXInputGamepad);
     gamepad.userIndex = i;
     gamepad.state = state;
     gamepad.id = service->AddGamepad("xinput",
                                      GamepadMappingType::Standard,
+                                     GamepadHand::_empty,
                                      kStandardGamepadButtons,
-                                     kStandardGamepadAxes);
+                                     kStandardGamepadAxes,
+                                     0); // TODO: Bug 680289, implement gamepad haptics for Windows.
     mGamepads.AppendElement(gamepad);
   }
 
   return found;
 }
 
 void
 WindowsGamepadService::ScanForDevices()
@@ -781,18 +783,20 @@ WindowsGamepadService::GetRawGamepad(HAN
   gamepad.handle = handle;
 
   for (unsigned i = 0; i < gamepad.numAxes; i++) {
     gamepad.axes[i].caps = axes[i];
   }
 
   gamepad.id = service->AddGamepad(gamepad_id,
                                    GamepadMappingType::_empty,
+                                   GamepadHand::_empty,
                                    gamepad.numButtons,
-                                   gamepad.numAxes);
+                                   gamepad.numAxes,
+                                   0);
   mGamepads.AppendElement(gamepad);
   return true;
 }
 
 bool
 WindowsGamepadService::HandleRawInput(HRAWINPUT handle)
 {
   if (!mHID) {
--- a/dom/tests/mochitest/gamepad/mochitest.ini
+++ b/dom/tests/mochitest/gamepad/mochitest.ini
@@ -2,11 +2,12 @@
 support-files =
   gamepad_frame.html
   gamepad_frame_state.html
   mock_gamepad.js
 
 [test_check_timestamp.html]
 [test_gamepad.html]
 [test_gamepad_connect_events.html]
+[test_gamepad_extensions.html]
 [test_gamepad_frame_state_sync.html]
 [test_gamepad_hidden_frame.html]
 [test_navigator_gamepads.html]
--- a/dom/tests/mochitest/gamepad/test_check_timestamp.html
+++ b/dom/tests/mochitest/gamepad/test_check_timestamp.html
@@ -19,18 +19,20 @@ var firstPress = true;
 var testOver = false;
 
 SimpleTest.waitForExplicitFinish();
 runGamepadTest(checkTimestamp);
 
 function checkTimestamp(){
   GamepadService.addGamepad("test gamepad 1",
                             GamepadService.standardMapping,
+                            GamepadService.noHand,
                             4,
-                            2).then(function(i) {
+                            2,
+                            0).then(function(i) {
                               index = i;
                               // Press a button to make the gamepad visible
                               // to the page.
                               GamepadService.newButtonEvent(index, 0, true);
                               GamepadService.newButtonEvent(index, 0, true);
                               ok(true, "test");
                             });
 }
--- a/dom/tests/mochitest/gamepad/test_gamepad.html
+++ b/dom/tests/mochitest/gamepad/test_gamepad.html
@@ -28,18 +28,20 @@ window.addEventListener("gamepadbuttonup
 });
 
 runGamepadTest(startTest);
 
 function startTest() {
   // Add a gamepad
   GamepadService.addGamepad("test gamepad", // id
                      GamepadService.standardMapping,
+                     GamepadService.noHand,
                      4,
-                     2).then(function(i) {
+                     2,
+                     0).then(function(i) {
                        index = i;
                        // Simulate button events on the gamepad we added
                        GamepadService.newButtonEvent(index, 0, true);
                      });
 }
 
 function connecthandler(e) {
   ok(e.gamepad.timestamp <= performance.now(),
--- a/dom/tests/mochitest/gamepad/test_gamepad_connect_events.html
+++ b/dom/tests/mochitest/gamepad/test_gamepad_connect_events.html
@@ -26,18 +26,20 @@ function pressButton() {
 function startTests() {
   window.addEventListener("gamepadbuttondown", function() {
     // Wait to ensure that all frames received the button press as well.
     SpecialPowers.executeSoon(tests[testNum++]);
   });
 
   GamepadService.addGamepad("test gamepad", // id
                             GamepadService.standardMapping,
+                            GamepadService.noHand,
                             4, // buttons
-                            2).then(function(i) {
+                            2,
+                            0).then(function(i) {
                               gamepad_index = i;
                               gamepad_connected()
                             });
 }
 
 var f1, f2;
 function gamepad_connected() {
   f1 = document.getElementById('f1');
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/gamepad/test_gamepad_extensions.html
@@ -0,0 +1,125 @@
+<!-- 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();
+
+var tests = [
+  poseadd,
+  posecheck,
+  haptictest
+];
+var gamepad_index = 0;
+var testNum = 0;
+var poseOrient = new Float32Array([-0.203, -0.235, 0.740, -0.596]);
+var posePos = new Float32Array([-0.0233, -0.707, -0.763]);
+var poseAngVel = new Float32Array([-0.0008, 0.00147, 0.001]);
+var poseAngAcc = new Float32Array([-0.494, 0.476, -0.241]);
+var poseLinVel = new Float32Array([0.003,0.024,-0.068]);
+var poseLinAcc = new Float32Array([-1.211,21.427,-2.348]);
+
+window.addEventListener("gamepadconnected", connecthandler);
+window.addEventListener("gamepadbuttondown", function() {
+  // Wait to ensure that all frames received the button press as well.
+  SpecialPowers.executeSoon(tests[testNum++]);
+});
+
+function pressButton() {
+  GamepadService.newButtonEvent(gamepad_index, 0, true);
+  GamepadService.newButtonEvent(gamepad_index, 0, false);
+}
+
+runGamepadTest(startTest);
+
+function startTest() {
+  SpecialPowers.pushPrefEnv({ "set": [["dom.gamepad.extensions.enabled", true]] });
+  // Add a gamepad
+  GamepadService.addGamepad("test gamepad", // id
+                     GamepadService.standardMapping,
+                     GamepadService.leftHand,
+                     4,
+                     2,
+                     1).then(function(i) {
+                      gamepad_index = i;
+                       // Simulate button events on the gamepad we added
+                      pressButton();
+                     });
+}
+
+function connecthandler(e) {
+  ok(e.gamepad.timestamp <= performance.now(),
+     "gamepad.timestamp should less than or equal to performance.now()");
+  is(e.gamepad.index, 0, "correct gamepad index");
+  is(e.gamepad.id, "test gamepad", "correct gamepad name");
+  is(e.gamepad.mapping, "standard", "standard mapping");
+  is(e.gamepad.hand, "left", "left hand");
+  is(e.gamepad.buttons.length, 4, "correct number of buttons");
+  is(e.gamepad.axes.length, 2, "correct number of axes");
+  is(e.gamepad.hapticActuators.length, 1, "correct number of haptics");
+}
+
+function checkValueInFloat32Array(array1, array2) {
+  if (array1.length != array2.length) {
+    return false;
+  }
+  var index = 0;
+  while (index < array2.length) {
+    if (array1[index] != array2[index]) {
+      return false;
+    }
+    ++index;
+  }
+  return true;
+}
+
+function poseadd() {
+  GamepadService.newPoseMove(gamepad_index, poseOrient,
+                             posePos, poseAngVel, poseAngAcc,
+                             poseLinVel, poseLinAcc);
+  pressButton();
+}
+
+function posecheck() {
+  var gamepads = navigator.getGamepads();
+  var pose = gamepads[0].pose;
+  is(gamepads[0].pose.hasOrientation, true,
+     "correct gamepadPose hasOrientation");
+  is(gamepads[0].pose.hasPosition, true,
+     "correct gamepadPose hasPosition");
+  is(checkValueInFloat32Array(pose.orientation, poseOrient), true,
+     "correct gamepadPose orientation");
+  is(checkValueInFloat32Array(pose.position, posePos), true,
+     "correct gamepadPose position");
+  is(checkValueInFloat32Array(pose.angularVelocity, poseAngVel), true,
+     "correct gamepadPose angularVelocity");
+  is(checkValueInFloat32Array(pose.angularAcceleration, poseAngAcc), true,
+     "correct gamepadPose angularAcceleration");
+  is(checkValueInFloat32Array(pose.linearVelocity, poseLinVel), true,
+     "correct gamepadPose linearVelocity");
+  is(checkValueInFloat32Array(pose.linearAcceleration, poseLinAcc), true,
+     "correct gamepadPose linearAcceleration");
+  pressButton();
+}
+
+function haptictest() {
+  var gamepads = navigator.getGamepads();
+  var hapticActuators = gamepads[0].hapticActuators[0];
+  hapticActuators.pulse(1, 100).then(function(result) {
+    is(result, true, "gamepad hapticActuators test success.");
+    GamepadService.removeGamepad(gamepad_index);
+    SimpleTest.finish();
+  });
+}
+
+</script>
+</body>
+</html>
+
--- a/dom/tests/mochitest/gamepad/test_gamepad_frame_state_sync.html
+++ b/dom/tests/mochitest/gamepad/test_gamepad_frame_state_sync.html
@@ -21,18 +21,20 @@ function setFrameVisible(f, visible) {
 }
 
 var frames_loaded = 0;
 function startTest() {
   frames_loaded++;
   if (frames_loaded == 2) {
     GamepadService.addGamepad("test gamepad", // id
                               GamepadService.standardMapping,
+                              GamepadService.noHand,
                               4, // buttons
-                              2).then(function(i) {
+                              2,
+                              0).then(function(i) {
                                 index = i;
                                 gamepad_loaded();
                               });
   }
 }
 var f1, f2;
 function gamepad_loaded() {
   f1 = document.getElementById('f1');
--- a/dom/tests/mochitest/gamepad/test_gamepad_hidden_frame.html
+++ b/dom/tests/mochitest/gamepad/test_gamepad_hidden_frame.html
@@ -29,18 +29,20 @@ function setFrameVisible(f, visible) {
 }
 
 var frames_loaded = 0;
 function startTest() {
   frames_loaded++;
   if (frames_loaded == 2) {
     GamepadService.addGamepad("test gamepad", // id
                               GamepadService.standardMapping,
+                              GamepadService.noHand,
                               4, // buttons
-                              2).then(function(i) {
+                              2,
+                              0).then(function(i) {
                                 index = i;
                                 gamepad_loaded();
                               });
   }
 }
 var f1, f2;
 function gamepad_loaded() {
   f1 = document.getElementById('f1');
--- a/dom/tests/mochitest/gamepad/test_navigator_gamepads.html
+++ b/dom/tests/mochitest/gamepad/test_navigator_gamepads.html
@@ -37,18 +37,20 @@ window.addEventListener("gamepaddisconne
 runGamepadTest(startTest)
 
 function startTest() {
   // gamepads should be empty first
   is(navigator.getGamepads().length, 0, "should be zero gamepads exposed");
   // Add a gamepad
   GamepadService.addGamepad("test gamepad 1", // id
                      GamepadService.standardMapping,
+                     GamepadService.noHand,
                      4, // buttons
-                     2).then(function(index) {
+                     2,
+                     0).then(function(index) {
                        internal_index1 = index;
                        // Press a button to make the gamepad visible to the page.
                        GamepadService.newButtonEvent(internal_index1, 0, true);
                      });
 }
 
 var content_index1 = 0;
 var internal_index2;
@@ -60,18 +62,20 @@ function check_first_gamepad(e) {
   is(e.gamepad.id, "test gamepad 1", "correct gamepad name");
   var gamepads = navigator.getGamepads();
   is(gamepads.length, 1, "should have one gamepad exposed");
   is(gamepads[e.gamepad.index], e.gamepad, "right gamepad exposed at index");
   is(gamepads[content_index1], e.gamepad, "gamepad counter working correctly");
   // Add a second gamepad, should automatically show up.
   GamepadService.addGamepad("test gamepad 2", // id
                      GamepadService.standardMapping,
+                     GamepadService.noHand,
                      4, // buttons
-                     2).then(function(index) {
+                     2,
+                     0).then(function(index) {
                        internal_index2 = index;
                        GamepadService.newButtonEvent(internal_index2, 0, true);
                      });
   ok(true, "Done checking first gamepad");
 }
 
 function check_second_gamepad(e) {
   ok(true, "Checking second gamepad");
--- a/dom/webidl/GamepadServiceTest.webidl
+++ b/dom/webidl/GamepadServiceTest.webidl
@@ -2,30 +2,42 @@
  * 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/. */
 
 [Pref="dom.gamepad.test.enabled"]
 interface GamepadServiceTest
 {
   readonly attribute GamepadMappingType noMapping;
   readonly attribute GamepadMappingType standardMapping;
+  readonly attribute GamepadHand noHand;
+  readonly attribute GamepadHand leftHand;
+  readonly attribute GamepadHand rightHand;
 
   [Throws]
   Promise<unsigned long> addGamepad(DOMString id,
                                     GamepadMappingType mapping,
+                                    GamepadHand hand,
                                     unsigned long numButtons,
-                                    unsigned long numAxes);
+                                    unsigned long numAxes,
+                                    unsigned long numHaptics);
 
   void removeGamepad(unsigned long index);
 
   void newButtonEvent(unsigned long index,
                       unsigned long button,
                       boolean pressed);
 
   void newButtonValueEvent(unsigned long index,
                            unsigned long button,
                            boolean pressed,
                            double value);
 
   void newAxisMoveEvent(unsigned long index,
                         unsigned long axis,
                         double value);
+  void newPoseMove(unsigned long index,
+                   Float32Array? orient,
+                   Float32Array? pos,
+                   Float32Array? angVelocity,
+                   Float32Array? angAcceleration,
+                   Float32Array? linVelocity,
+                   Float32Array? linAcceleration);
 };
\ No newline at end of file