--- 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;