Bug 1299937 - Part 5: Add gamepad extension API tests; r?qdot, lenzak800 draft
authorDaosheng Mu <daoshengmu@gmail.com>
Thu, 02 Feb 2017 15:00:51 +0800
changeset 503540 140fac3a2a46c3d0017e9212841f5e0c75342a52
parent 503539 e9807ded0514f343429d42ef76b66c4ad51ae6c2
child 503541 a35c33f9c9ff1265c546f10895f7acc043d95ad9
child 504195 835501b1be6753001c8f86cfc02816a0e8d5c71c
push id50611
push userbmo:dmu@mozilla.com
push dateThu, 23 Mar 2017 09:11:47 +0000
reviewersqdot, lenzak800
bugs1299937
milestone55.0a1
Bug 1299937 - Part 5: Add gamepad extension API tests; r?qdot, lenzak800 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