Bug 1299937 - Part 6: Handle Stop vibrating when the window defoucses; r?qdot draft
authorDaosheng Mu <daoshengmu@gmail.com>
Tue, 07 Mar 2017 10:17:57 +0800
changeset 504195 835501b1be6753001c8f86cfc02816a0e8d5c71c
parent 503540 140fac3a2a46c3d0017e9212841f5e0c75342a52
child 550613 10247a7079b014dd51b8ac45adc48e4bf179c4bf
push id50759
push userbmo:dmu@mozilla.com
push dateFri, 24 Mar 2017 03:20:26 +0000
reviewersqdot
bugs1299937
milestone55.0a1
Bug 1299937 - Part 6: Handle Stop vibrating when the window defoucses; r?qdot MozReview-Commit-ID: Kvd40jnSPvK
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/gamepad/GamepadManager.cpp
dom/gamepad/GamepadManager.h
dom/gamepad/ipc/GamepadEventChannelParent.cpp
dom/gamepad/ipc/GamepadEventChannelParent.h
dom/gamepad/ipc/PGamepadEventChannel.ipdl
dom/tests/mochitest/gamepad/test_gamepad_extensions.html
gfx/vr/VRManager.cpp
gfx/vr/VRManager.h
gfx/vr/gfxVROpenVR.cpp
gfx/vr/gfxVROpenVR.h
gfx/vr/ipc/PVRManager.ipdl
gfx/vr/ipc/VRManagerChild.cpp
gfx/vr/ipc/VRManagerParent.cpp
gfx/vr/ipc/VRManagerParent.h
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -10417,31 +10417,32 @@ nsGlobalWindow::IsTopLevelWindowActive()
 
 void nsGlobalWindow::SetIsBackground(bool aIsBackground)
 {
   MOZ_ASSERT(IsOuterWindow());
 
   bool resetTimers = (!aIsBackground && AsOuter()->IsBackground());
   nsPIDOMWindow::SetIsBackground(aIsBackground);
 
+  nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
+
   if (aIsBackground) {
     MOZ_ASSERT(!resetTimers);
-    return;
-  }
-
-  nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
-  if (!inner) {
-    return;
-  }
-
-  if (resetTimers) {
-    inner->mTimeoutManager->ResetTimersForThrottleReduction();
-  }
-
-  inner->SyncGamepadState();
+    // Notify gamepadManager we are at the background window,
+    // we need to stop vibrate.
+    if (inner) {
+      inner->StopGamepadHaptics();
+    }
+    return;
+  } else if (inner) {
+    if (resetTimers) {
+      inner->mTimeoutManager->ResetTimersForThrottleReduction();
+    }
+    inner->SyncGamepadState();
+  }
 }
 
 void nsGlobalWindow::MaybeUpdateTouchState()
 {
   FORWARD_TO_INNER_VOID(MaybeUpdateTouchState, ());
 
   if (mMayHaveTouchEventListener) {
     nsCOMPtr<nsIObserverService> observerService =
@@ -13510,16 +13511,26 @@ nsGlobalWindow::SyncGamepadState()
   if (mHasSeenGamepadInput) {
     RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
     for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) {
       gamepadManager->SyncGamepadState(iter.Key(), iter.UserData());
     }
   }
 }
 
+void
+nsGlobalWindow::StopGamepadHaptics()
+{
+  MOZ_ASSERT(IsInnerWindow());
+  if (mHasSeenGamepadInput) {
+    RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+    gamepadManager->StopHaptics();
+  }
+}
+
 bool
 nsGlobalWindow::UpdateVRDisplays(nsTArray<RefPtr<mozilla::dom::VRDisplay>>& aDevices)
 {
   FORWARD_TO_INNER(UpdateVRDisplays, (aDevices), false);
 
   VRDisplay::UpdateVRDisplays(mVRDisplays, AsInner());
   aDevices = mVRDisplays;
   return true;
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -744,16 +744,17 @@ public:
   // Inner windows only.
   void AddGamepad(uint32_t aIndex, mozilla::dom::Gamepad* aGamepad);
   void RemoveGamepad(uint32_t aIndex);
   void GetGamepads(nsTArray<RefPtr<mozilla::dom::Gamepad> >& aGamepads);
   already_AddRefed<mozilla::dom::Gamepad> GetGamepad(uint32_t aIndex);
   void SetHasSeenGamepadInput(bool aHasSeen);
   bool HasSeenGamepadInput();
   void SyncGamepadState();
+  void StopGamepadHaptics();
 
   // Inner windows only.
   // Enable/disable updates for gamepad input.
   void EnableGamepadUpdates();
   void DisableGamepadUpdates();
 
   // Inner windows only.
   // Enable/disable updates for VR
--- a/dom/gamepad/GamepadManager.cpp
+++ b/dom/gamepad/GamepadManager.cpp
@@ -693,16 +693,32 @@ GamepadManager::VibrateHaptic(uint32_t a
                                       mPromiseID);
     }
   }
 
   ++mPromiseID;
   return promise.forget();
 }
 
+void
+GamepadManager::StopHaptics()
+{
+  for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) {
+    const uint32_t gamepadIndex = iter.UserData()->HashKey();
+    if (gamepadIndex >= VR_GAMEPAD_IDX_OFFSET) {
+      const uint32_t index = gamepadIndex - VR_GAMEPAD_IDX_OFFSET;
+      mVRChannelChild->SendStopVibrateHaptic(index);
+    } else {
+      for (auto& channelChild : mChannelChildren) {
+        channelChild->SendStopVibrateHaptic(gamepadIndex);
+      }
+    }
+  }
+}
+
 //Override nsIIPCBackgroundChildCreateCallback
 void
 GamepadManager::ActorCreated(PBackgroundChild *aActor)
 {
   MOZ_ASSERT(aActor);
   GamepadEventChannelChild *child = new GamepadEventChannelChild();
   PGamepadEventChannelChild *initedChild =
     aActor->SendPGamepadEventChannelConstructor(child);
--- a/dom/gamepad/GamepadManager.h
+++ b/dom/gamepad/GamepadManager.h
@@ -82,17 +82,19 @@ 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);
+                                          nsIGlobalObject* aGlobal, ErrorResult& aRv);
+  // Send stop haptic events to gamepad channels.
+  void StopHaptics();
 
  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/ipc/GamepadEventChannelParent.cpp
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.cpp
@@ -84,16 +84,23 @@ GamepadEventChannelParent::RecvVibrateHa
 
   if (SendReplyGamepadVibrateHaptic(aPromiseID)) {
     return IPC_OK();
   }
 
   return IPC_FAIL(this, "SendReplyGamepadVibrateHaptic fail.");
 }
 
+mozilla::ipc::IPCResult
+GamepadEventChannelParent::RecvStopVibrateHaptic(const uint32_t& aGamepadIndex)
+{
+  // TODO: Bug 680289, implement for standard gamepads
+  return IPC_OK();
+}
+
 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
@@ -17,16 +17,18 @@ class GamepadEventChannelParent final : 
   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;
+  virtual mozilla::ipc::IPCResult RecvStopVibrateHaptic(
+                                    const uint32_t& aGamepadIndex) override;
   void DispatchUpdateEvent(const GamepadChangeEvent& aEvent);
   bool HasGamepadListener() const { return mHasGamepadListener; }
  private:
   ~GamepadEventChannelParent() {}
   bool mHasGamepadListener;
   nsCOMPtr<nsIThread> mBackgroundThread;
 };
 
--- a/dom/gamepad/ipc/PGamepadEventChannel.ipdl
+++ b/dom/gamepad/ipc/PGamepadEventChannel.ipdl
@@ -9,16 +9,18 @@ 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);
+    async StopVibrateHaptic(uint32_t aGamepadIndex);
+
   child:
     async __delete__();
     async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
     async ReplyGamepadVibrateHaptic(uint32_t aPromiseID);
 };
 
 }
 }
--- a/dom/tests/mochitest/gamepad/test_gamepad_extensions.html
+++ b/dom/tests/mochitest/gamepad/test_gamepad_extensions.html
@@ -32,18 +32,16 @@ window.addEventListener("gamepadbuttondo
   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,
@@ -104,22 +102,33 @@ function posecheck() {
      "correct gamepadPose angularAcceleration");
   is(checkValueInFloat32Array(pose.linearVelocity, poseLinVel), true,
      "correct gamepadPose linearVelocity");
   is(checkValueInFloat32Array(pose.linearAcceleration, poseLinAcc), true,
      "correct gamepadPose linearAcceleration");
   pressButton();
 }
 
+function setFrameVisible(f, visible) {
+  var Ci = SpecialPowers.Ci;
+  var docshell = SpecialPowers.wrap(f.contentWindow).QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell);
+  docshell.isActive = visible;
+}
+
 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();
   });
+  // When page is background, we should stop our haptics and still
+  // can get the promise.
+  var f1 = document.getElementById('f1');
+  setFrameVisible(f1, false);
 }
 
 </script>
+<iframe id="f1" src="gamepad_frame_state.html" onload="runGamepadTest(startTest)"></iframe>
 </body>
 </html>
 
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -418,10 +418,26 @@ VRManager::VibrateHaptic(uint32_t aContr
 
 {
   for (uint32_t i = 0; i < mManagers.Length(); ++i) {
     mManagers[i]->VibrateHaptic(aControllerIdx, aHapticIndex,
                                 aIntensity, aDuration, aPromiseID);
   }
 }
 
+void
+VRManager::StopVibrateHaptic(uint32_t aControllerIdx)
+{
+  for (const auto& manager: mManagers) {
+    manager->StopVibrateHaptic(aControllerIdx);
+  }
+}
+
+void
+VRManager::NotifyVibrateHapticCompleted(uint32_t aPromiseID)
+{
+  for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
+    Unused << iter.Get()->GetKey()->SendReplyGamepadVibrateHaptic(aPromiseID);
+  }
+}
+
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/vr/VRManager.h
+++ b/gfx/vr/VRManager.h
@@ -46,16 +46,18 @@ public:
   void SubmitFrame(VRLayerParent* aLayer, layers::PTextureParent* aTexture,
                    const gfx::Rect& aLeftEyeRect,
                    const gfx::Rect& aRightEyeRect);
   RefPtr<gfx::VRControllerHost> GetController(const uint32_t& aControllerID);
   void GetVRControllerInfo(nsTArray<VRControllerInfo>& aControllerInfo);
   void CreateVRTestSystem();
   void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                      double aIntensity, double aDuration, uint32_t aPromiseID);
+  void StopVibrateHaptic(uint32_t aControllerIdx);
+  void NotifyVibrateHapticCompleted(uint32_t aPromiseID);
 
 protected:
   VRManager();
   ~VRManager();
 
 private:
   RefPtr<layers::TextureHost> mLastFrame;
 
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -390,17 +390,17 @@ VRDisplayOpenVR::NotifyVSync()
   PollEvents();
 }
 
 VRControllerOpenVR::VRControllerOpenVR(dom::GamepadHand aHand, uint32_t aNumButtons,
                                        uint32_t aNumAxes)
   : VRControllerHost(VRDeviceType::OpenVR)
   , mTrigger(0)
   , mVibrateThread(nullptr)
-  , mIsVibrating(false)
+  , mIsVibrateStopped(false)
 {
   MOZ_COUNT_CTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
   mControllerInfo.mControllerName.AssignLiteral("OpenVR Gamepad");
   mControllerInfo.mMappingType = GamepadMappingType::_empty;
   mControllerInfo.mHand = aHand;
   mControllerInfo.mNumButtons = aNumButtons;
   mControllerInfo.mNumAxes = aNumAxes;
   mControllerInfo.mNumHaptics = kNumOpenVRHaptcs;
@@ -445,18 +445,25 @@ VRControllerOpenVR::UpdateVibrateHaptic(
                                         uint32_t aHapticIndex,
                                         double aIntensity,
                                         double aDuration,
                                         uint64_t aVibrateIndex,
                                         uint32_t aPromiseID)
 {
   // UpdateVibrateHaptic() only can be called by mVibrateThread
   MOZ_ASSERT(mVibrateThread == NS_GetCurrentThread());
+
+  // It has been interrupted by loss focus.
+  if (mIsVibrateStopped) {
+    VibrateHapticComplete(aPromiseID);
+    return;
+  }
   // Avoid the previous vibrate event to override the new one.
   if (mVibrateIndex != aVibrateIndex) {
+    VibrateHapticComplete(aPromiseID);
     return;
   }
 
   double duration = (aIntensity == 0) ? 0 : aDuration;
   // We expect OpenVR to vibrate for 5 ms, but we found it only response the
   // commend ~ 3.9 ms. For duration time longer than 3.9 ms, we separate them
   // to a loop of 3.9 ms for make users feel that is a continuous events.
   uint32_t microSec = (duration < 3.9 ? duration : 3.9) * 1000 * aIntensity;
@@ -469,49 +476,62 @@ VRControllerOpenVR::UpdateVibrateHaptic(
   if (duration >= kVibrateRate) {
     MOZ_ASSERT(mVibrateThread);
 
     RefPtr<Runnable> runnable =
       NewRunnableMethod<vr::IVRSystem*, uint32_t, double, double, uint64_t, uint32_t>
         (this, &VRControllerOpenVR::UpdateVibrateHaptic, aVRSystem,
          aHapticIndex, aIntensity, duration - kVibrateRate, aVibrateIndex, aPromiseID);
     NS_DelayedDispatchToCurrentThread(runnable.forget(), kVibrateRate);
+  } else {
+    // The pulse has completed
+    VibrateHapticComplete(aPromiseID);
   }
 }
 
 void
+VRControllerOpenVR::VibrateHapticComplete(uint32_t aPromiseID)
+{
+  VRManager *vm = VRManager::Get();
+  MOZ_ASSERT(vm);
+
+  CompositorThreadHolder::Loop()->PostTask(NewRunnableMethod<uint32_t>
+    (vm, &VRManager::NotifyVibrateHapticCompleted, aPromiseID));
+}
+
+void
 VRControllerOpenVR::VibrateHaptic(vr::IVRSystem* aVRSystem,
                                   uint32_t aHapticIndex,
                                   double aIntensity,
                                   double aDuration,
                                   uint32_t aPromiseID)
 {
   // Spinning up the haptics thread at the first haptics call.
   if (!mVibrateThread) {
     nsresult rv = NS_NewThread(getter_AddRefs(mVibrateThread));
     MOZ_ASSERT(mVibrateThread);
 
     if (NS_FAILED(rv)) {
       MOZ_ASSERT(false, "Failed to create async thread.");
     }
   }
   ++mVibrateIndex;
+  mIsVibrateStopped = false;
 
-  mIsVibrating = true;
   RefPtr<Runnable> runnable =
       NewRunnableMethod<vr::IVRSystem*, uint32_t, double, double, uint64_t, uint32_t>
         (this, &VRControllerOpenVR::UpdateVibrateHaptic, aVRSystem,
          aHapticIndex, aIntensity, aDuration, mVibrateIndex, aPromiseID);
   mVibrateThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
 }
 
 void
 VRControllerOpenVR::StopVibrateHaptic()
 {
-  mIsVibrating = false;
+  mIsVibrateStopped = true;
 }
 
 VRSystemManagerOpenVR::VRSystemManagerOpenVR()
   : mVRSystem(nullptr)
   , mOpenVRInstalled(false)
 {
 }
 
--- a/gfx/vr/gfxVROpenVR.h
+++ b/gfx/vr/gfxVROpenVR.h
@@ -89,22 +89,23 @@ protected:
 
 private:
   void UpdateVibrateHaptic(vr::IVRSystem* aVRSystem,
                            uint32_t aHapticIndex,
                            double aIntensity,
                            double aDuration,
                            uint64_t aVibrateIndex,
                            uint32_t aPromiseID);
+  void VibrateHapticComplete(uint32_t aPromiseID);
 
   // The index of tracked devices from vr::IVRSystem.
   uint32_t mTrackedIndex;
   float mTrigger;
   nsCOMPtr<nsIThread> mVibrateThread;
-  bool mIsVibrating;
+  Atomic<bool> mIsVibrateStopped;
 };
 
 } // namespace impl
 
 class VRSystemManagerOpenVR : public VRSystemManager
 {
 public:
   static already_AddRefed<VRSystemManagerOpenVR> Create();
--- a/gfx/vr/ipc/PVRManager.ipdl
+++ b/gfx/vr/ipc/PVRManager.ipdl
@@ -55,16 +55,17 @@ parent:
 
   sync GetSensorState(uint32_t aDisplayID) returns(VRHMDSensorState aState);
   sync SetHaveEventListener(bool aHaveEventListener);
 
   async ControllerListenerAdded();
   async ControllerListenerRemoved();
   async VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                       double aIntensity, double aDuration, uint32_t aPromiseID);
+  async StopVibrateHaptic(uint32_t aControllerIdx);
 
   async CreateVRTestSystem();
   async CreateVRServiceTestDisplay(nsCString aID, uint32_t aPromiseID);
   async CreateVRServiceTestController(nsCString aID, uint32_t aPromiseID);
   async SetDisplayInfoToMockDisplay(uint32_t aDeviceID, VRDisplayInfo aDisplayInfo);
   async SetSensorStateToMockDisplay(uint32_t aDeviceID, VRHMDSensorState aSensorState);
 
   async NewButtonEventToMockController(uint32_t aDeviceID, long aButton,
--- a/gfx/vr/ipc/VRManagerChild.cpp
+++ b/gfx/vr/ipc/VRManagerChild.cpp
@@ -692,16 +692,21 @@ VRManagerChild::AddPromise(const uint32_
 {
   MOZ_ASSERT(!mGamepadPromiseList.Get(aID, nullptr));
   mGamepadPromiseList.Put(aID, aPromise);
 }
 
 mozilla::ipc::IPCResult
 VRManagerChild::RecvReplyGamepadVibrateHaptic(const uint32_t& aPromiseID)
 {
+  // VRManagerChild could be at other processes, but GamepadManager
+  // only exists at the content process or the same process
+  // in non-e10s mode.
+  MOZ_ASSERT(XRE_IsContentProcess() || IsSameProcess());
+
   RefPtr<dom::Promise> p;
   if (!mGamepadPromiseList.Get(aPromiseID, getter_AddRefs(p))) {
     MOZ_CRASH("We should always have a promise.");
   }
 
   p->MaybeResolve(true);
   mGamepadPromiseList.Remove(aPromiseID);
   return IPC_OK();
--- a/gfx/vr/ipc/VRManagerParent.cpp
+++ b/gfx/vr/ipc/VRManagerParent.cpp
@@ -454,22 +454,43 @@ VRManagerParent::RecvVibrateHaptic(const
                                    const uint32_t& aPromiseID)
 {
   VRManager* vm = VRManager::Get();
   vm->VibrateHaptic(aControllerIdx, aHapticIndex, aIntensity,
                     aDuration, aPromiseID);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+VRManagerParent::RecvStopVibrateHaptic(const uint32_t& aControllerIdx)
+{
+  VRManager* vm = VRManager::Get();
+  vm->StopVibrateHaptic(aControllerIdx);
+  return IPC_OK();
+}
+
 bool
 VRManagerParent::SendGamepadUpdate(const GamepadChangeEvent& aGamepadEvent)
 {
   // GamepadManager only exists at the content process
   // or the same process in non-e10s mode.
   if (mIsContentChild || IsSameProcess()) {
     return PVRManagerParent::SendGamepadUpdate(aGamepadEvent);
   } else {
     return true;
   }
 }
 
+bool
+VRManagerParent::SendReplyGamepadVibrateHaptic(const uint32_t& aPromiseID)
+{
+  // GamepadManager only exists at the content process
+  // or the same process in non-e10s mode.
+  if (mHaveControllerListener &&
+      (mIsContentChild || IsSameProcess())) {
+    return PVRManagerParent::SendReplyGamepadVibrateHaptic(aPromiseID);
+  } else {
+    return true;
+  }
+}
+
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/vr/ipc/VRManagerParent.h
+++ b/gfx/vr/ipc/VRManagerParent.h
@@ -56,16 +56,17 @@ public:
 
   virtual bool IsSameProcess() const override;
   bool HaveEventListener();
   bool HaveControllerListener();
 
   virtual void NotifyNotUsed(PTextureParent* aTexture, uint64_t aTransactionId) override;
   virtual void SendAsyncMessage(const InfallibleTArray<AsyncParentMessageData>& aMessage) override;
   bool SendGamepadUpdate(const GamepadChangeEvent& aGamepadEvent);
+  bool SendReplyGamepadVibrateHaptic(const uint32_t& aPromiseID);
 
 protected:
   ~VRManagerParent();
 
   virtual PTextureParent* AllocPTextureParent(const SurfaceDescriptor& aSharedData,
                                               const LayersBackend& aLayersBackend,
                                               const TextureFlags& aFlags,
                                               const uint64_t& aSerial) override;
@@ -89,16 +90,17 @@ protected:
   virtual mozilla::ipc::IPCResult RecvGetDisplays(nsTArray<VRDisplayInfo> *aDisplays) override;
   virtual mozilla::ipc::IPCResult RecvResetSensor(const uint32_t& aDisplayID) override;
   virtual mozilla::ipc::IPCResult RecvGetSensorState(const uint32_t& aDisplayID, VRHMDSensorState* aState) override;
   virtual mozilla::ipc::IPCResult RecvSetHaveEventListener(const bool& aHaveEventListener) override;
   virtual mozilla::ipc::IPCResult RecvControllerListenerAdded() override;
   virtual mozilla::ipc::IPCResult RecvControllerListenerRemoved() 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;
+  virtual mozilla::ipc::IPCResult RecvStopVibrateHaptic(const uint32_t& aControllerIdx) override;
   
   virtual mozilla::ipc::IPCResult RecvCreateVRTestSystem() override;
   virtual mozilla::ipc::IPCResult RecvCreateVRServiceTestDisplay(const nsCString& aID, const uint32_t& aPromiseID) override;
   virtual mozilla::ipc::IPCResult RecvCreateVRServiceTestController(const nsCString& aID, const uint32_t& aPromiseID) override;
   virtual mozilla::ipc::IPCResult RecvSetDisplayInfoToMockDisplay(const uint32_t& aDeviceID,
                                                                   const VRDisplayInfo& aDisplayInfo) override;
   virtual mozilla::ipc::IPCResult RecvSetSensorStateToMockDisplay(const uint32_t& aDeviceID,
                                                                   const VRHMDSensorState& aSensorState) override;