mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-18 14:56:07 +00:00
![Tim Huang](/assets/img/avatar_default.png)
To collect the gamepad information on the user device, we introduce a chrome only function called RequestAllGamepads to Navigator. The function will return a promise which resolves when we get all gamepads info from the parent process. Note that we cannot rely on The existing navigator.getGamepads() function because it cannot allow us collecting gamepads info without the user interacts with the gamepads. Differential Revision: https://phabricator.services.mozilla.com/D207979
697 lines
21 KiB
C++
697 lines
21 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "mozilla/dom/GamepadManager.h"
|
|
|
|
#include "mozilla/dom/Gamepad.h"
|
|
#include "mozilla/dom/GamepadAxisMoveEvent.h"
|
|
#include "mozilla/dom/GamepadButtonEvent.h"
|
|
#include "mozilla/dom/GamepadEvent.h"
|
|
#include "mozilla/dom/GamepadEventChannelChild.h"
|
|
#include "mozilla/dom/GamepadMonitoring.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
|
|
#include "mozilla/ipc/BackgroundChild.h"
|
|
#include "mozilla/ipc/PBackgroundChild.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/StaticPrefs_dom.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
|
|
#include "nsContentUtils.h"
|
|
#include "nsGlobalWindowInner.h"
|
|
#include "nsIObserver.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "VRManagerChild.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/Unused.h"
|
|
|
|
#include <cstddef>
|
|
|
|
using namespace mozilla::ipc;
|
|
|
|
namespace mozilla::dom {
|
|
|
|
namespace {
|
|
|
|
const nsTArray<RefPtr<nsGlobalWindowInner>>::index_type NoIndex =
|
|
nsTArray<RefPtr<nsGlobalWindowInner>>::NoIndex;
|
|
|
|
bool sShutdown = false;
|
|
|
|
StaticRefPtr<GamepadManager> gGamepadManagerSingleton;
|
|
|
|
// A threshold value of axis move to determine the first
|
|
// intent.
|
|
const float AXIS_FIRST_INTENT_THRESHOLD_VALUE = 0.1f;
|
|
|
|
} // namespace
|
|
|
|
NS_IMPL_ISUPPORTS(GamepadManager, nsIObserver)
|
|
|
|
GamepadManager::GamepadManager()
|
|
: mEnabled(false),
|
|
mNonstandardEventsEnabled(false),
|
|
mShuttingDown(false),
|
|
mPromiseID(0) {}
|
|
|
|
nsresult GamepadManager::Init() {
|
|
mEnabled = StaticPrefs::dom_gamepad_enabled();
|
|
mNonstandardEventsEnabled =
|
|
StaticPrefs::dom_gamepad_non_standard_events_enabled();
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
mozilla::services::GetObserverService();
|
|
|
|
if (NS_WARN_IF(!observerService)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult rv;
|
|
rv = observerService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
|
|
false);
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
GamepadManager::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData) {
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
mozilla::services::GetObserverService();
|
|
if (observerService) {
|
|
observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
|
|
}
|
|
BeginShutdown();
|
|
return NS_OK;
|
|
}
|
|
|
|
void GamepadManager::StopMonitoring() {
|
|
if (mChannelChild) {
|
|
PGamepadEventChannelChild::Send__delete__(mChannelChild);
|
|
mChannelChild = nullptr;
|
|
}
|
|
if (gfx::VRManagerChild::IsCreated()) {
|
|
gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
|
|
vm->SendControllerListenerRemoved();
|
|
}
|
|
mGamepads.Clear();
|
|
}
|
|
|
|
void GamepadManager::BeginShutdown() {
|
|
mShuttingDown = true;
|
|
StopMonitoring();
|
|
// Don't let windows call back to unregister during shutdown
|
|
for (uint32_t i = 0; i < mListeners.Length(); i++) {
|
|
mListeners[i]->SetHasGamepadEventListener(false);
|
|
}
|
|
mListeners.Clear();
|
|
sShutdown = true;
|
|
}
|
|
|
|
void GamepadManager::AddListener(nsGlobalWindowInner* aWindow) {
|
|
MOZ_ASSERT(aWindow);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// IPDL child has not been created
|
|
if (!mChannelChild) {
|
|
PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread();
|
|
if (NS_WARN_IF(!actor)) {
|
|
// We are probably shutting down.
|
|
return;
|
|
}
|
|
|
|
RefPtr<GamepadEventChannelChild> child(GamepadEventChannelChild::Create());
|
|
if (!actor->SendPGamepadEventChannelConstructor(child.get())) {
|
|
// We are probably shutting down.
|
|
return;
|
|
}
|
|
|
|
mChannelChild = child;
|
|
|
|
if (gfx::VRManagerChild::IsCreated()) {
|
|
// Construct VRManagerChannel and ask adding the connected
|
|
// VR controllers to GamepadManager
|
|
gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
|
|
vm->SendControllerListenerAdded();
|
|
}
|
|
}
|
|
|
|
if (!mEnabled || mShuttingDown ||
|
|
aWindow->ShouldResistFingerprinting(RFPTarget::Gamepad)) {
|
|
return;
|
|
}
|
|
|
|
if (mListeners.IndexOf(aWindow) != NoIndex) {
|
|
return; // already exists
|
|
}
|
|
|
|
mListeners.AppendElement(aWindow);
|
|
}
|
|
|
|
void GamepadManager::RemoveListener(nsGlobalWindowInner* aWindow) {
|
|
MOZ_ASSERT(aWindow);
|
|
|
|
if (mShuttingDown) {
|
|
// Doesn't matter at this point. It's possible we're being called
|
|
// as a result of our own destructor here, so just bail out.
|
|
return;
|
|
}
|
|
|
|
if (mListeners.IndexOf(aWindow) == NoIndex) {
|
|
return; // doesn't exist
|
|
}
|
|
|
|
for (const auto& key : mGamepads.Keys()) {
|
|
aWindow->RemoveGamepad(key);
|
|
}
|
|
|
|
mListeners.RemoveElement(aWindow);
|
|
|
|
if (mListeners.IsEmpty()) {
|
|
StopMonitoring();
|
|
}
|
|
}
|
|
|
|
already_AddRefed<Gamepad> GamepadManager::GetGamepad(
|
|
GamepadHandle aHandle) const {
|
|
RefPtr<Gamepad> gamepad;
|
|
if (mGamepads.Get(aHandle, getter_AddRefs(gamepad))) {
|
|
return gamepad.forget();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void GamepadManager::AddGamepad(GamepadHandle aHandle, const nsAString& aId,
|
|
GamepadMappingType aMapping, GamepadHand aHand,
|
|
uint32_t aDisplayID, uint32_t aNumButtons,
|
|
uint32_t aNumAxes, uint32_t aNumHaptics,
|
|
uint32_t aNumLightIndicator,
|
|
uint32_t aNumTouchEvents) {
|
|
// TODO: bug 852258: get initial button/axis state
|
|
RefPtr<Gamepad> newGamepad =
|
|
new Gamepad(nullptr, aId,
|
|
0, // index is set by global window
|
|
aHandle, aMapping, aHand, aDisplayID, aNumButtons, aNumAxes,
|
|
aNumHaptics, aNumLightIndicator, aNumTouchEvents);
|
|
|
|
// We store the gamepad related to its index given by the parent process,
|
|
// and no duplicate index is allowed.
|
|
MOZ_ASSERT(!mGamepads.Contains(aHandle));
|
|
mGamepads.InsertOrUpdate(aHandle, std::move(newGamepad));
|
|
NewConnectionEvent(aHandle, true);
|
|
}
|
|
|
|
void GamepadManager::RemoveGamepad(GamepadHandle aHandle) {
|
|
RefPtr<Gamepad> gamepad = GetGamepad(aHandle);
|
|
if (!gamepad) {
|
|
NS_WARNING("Trying to delete gamepad with invalid index");
|
|
return;
|
|
}
|
|
gamepad->SetConnected(false);
|
|
NewConnectionEvent(aHandle, false);
|
|
mGamepads.Remove(aHandle);
|
|
}
|
|
|
|
void GamepadManager::FireButtonEvent(EventTarget* aTarget, Gamepad* aGamepad,
|
|
uint32_t aButton, double aValue) {
|
|
nsString name =
|
|
aValue == 1.0L ? u"gamepadbuttondown"_ns : u"gamepadbuttonup"_ns;
|
|
GamepadButtonEventInit init;
|
|
init.mBubbles = false;
|
|
init.mCancelable = false;
|
|
init.mGamepad = aGamepad;
|
|
init.mButton = aButton;
|
|
RefPtr<GamepadButtonEvent> event =
|
|
GamepadButtonEvent::Constructor(aTarget, name, init);
|
|
|
|
event->SetTrusted(true);
|
|
|
|
aTarget->DispatchEvent(*event);
|
|
}
|
|
|
|
void GamepadManager::FireAxisMoveEvent(EventTarget* aTarget, Gamepad* aGamepad,
|
|
uint32_t aAxis, double aValue) {
|
|
GamepadAxisMoveEventInit init;
|
|
init.mBubbles = false;
|
|
init.mCancelable = false;
|
|
init.mGamepad = aGamepad;
|
|
init.mAxis = aAxis;
|
|
init.mValue = aValue;
|
|
RefPtr<GamepadAxisMoveEvent> event =
|
|
GamepadAxisMoveEvent::Constructor(aTarget, u"gamepadaxismove"_ns, init);
|
|
|
|
event->SetTrusted(true);
|
|
|
|
aTarget->DispatchEvent(*event);
|
|
}
|
|
|
|
void GamepadManager::NewConnectionEvent(GamepadHandle aHandle,
|
|
bool aConnected) {
|
|
if (mShuttingDown) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<Gamepad> gamepad = GetGamepad(aHandle);
|
|
if (!gamepad) {
|
|
return;
|
|
}
|
|
|
|
// Hold on to listeners in a separate array because firing events
|
|
// can mutate the mListeners array.
|
|
nsTArray<RefPtr<nsGlobalWindowInner>> listeners(mListeners.Clone());
|
|
|
|
if (aConnected) {
|
|
for (uint32_t i = 0; i < listeners.Length(); i++) {
|
|
#ifdef NIGHTLY_BUILD
|
|
// Don't fire a gamepadconnected event unless it's a secure context
|
|
if (!listeners[i]->IsSecureContext()) {
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
// Do not fire gamepadconnected and gamepaddisconnected events when
|
|
// privacy.resistFingerprinting is true.
|
|
if (listeners[i]->ShouldResistFingerprinting(RFPTarget::Gamepad)) {
|
|
continue;
|
|
}
|
|
|
|
// Only send events to non-background windows
|
|
if (!listeners[i]->IsCurrentInnerWindow() ||
|
|
listeners[i]->GetOuterWindow()->IsBackground()) {
|
|
continue;
|
|
}
|
|
|
|
// We don't fire a connected event here unless the window
|
|
// has seen input from at least one device.
|
|
if (!listeners[i]->HasSeenGamepadInput()) {
|
|
continue;
|
|
}
|
|
|
|
SetWindowHasSeenGamepad(listeners[i], aHandle);
|
|
|
|
RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aHandle);
|
|
if (listenerGamepad) {
|
|
// Fire event
|
|
FireConnectionEvent(listeners[i], listenerGamepad, aConnected);
|
|
}
|
|
}
|
|
} else {
|
|
// For disconnection events, fire one at every window that has received
|
|
// data from this gamepad.
|
|
for (uint32_t i = 0; i < listeners.Length(); i++) {
|
|
// Even background windows get these events, so we don't have to
|
|
// deal with the hassle of syncing the state of removed gamepads.
|
|
|
|
// Do not fire gamepadconnected and gamepaddisconnected events when
|
|
// privacy.resistFingerprinting is true.
|
|
if (listeners[i]->ShouldResistFingerprinting(RFPTarget::Gamepad)) {
|
|
continue;
|
|
}
|
|
|
|
if (WindowHasSeenGamepad(listeners[i], aHandle)) {
|
|
RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aHandle);
|
|
if (listenerGamepad) {
|
|
listenerGamepad->SetConnected(false);
|
|
// Fire event
|
|
FireConnectionEvent(listeners[i], listenerGamepad, false);
|
|
listeners[i]->RemoveGamepad(aHandle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GamepadManager::FireConnectionEvent(EventTarget* aTarget,
|
|
Gamepad* aGamepad, bool aConnected) {
|
|
nsString name =
|
|
aConnected ? u"gamepadconnected"_ns : u"gamepaddisconnected"_ns;
|
|
GamepadEventInit init;
|
|
init.mBubbles = false;
|
|
init.mCancelable = false;
|
|
init.mGamepad = aGamepad;
|
|
RefPtr<GamepadEvent> event = GamepadEvent::Constructor(aTarget, name, init);
|
|
|
|
event->SetTrusted(true);
|
|
|
|
aTarget->DispatchEvent(*event);
|
|
}
|
|
|
|
void GamepadManager::SyncGamepadState(GamepadHandle aHandle,
|
|
nsGlobalWindowInner* aWindow,
|
|
Gamepad* aGamepad) {
|
|
if (mShuttingDown || !mEnabled ||
|
|
aWindow->ShouldResistFingerprinting(RFPTarget::Gamepad)) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<Gamepad> gamepad = GetGamepad(aHandle);
|
|
if (!gamepad) {
|
|
return;
|
|
}
|
|
|
|
aGamepad->SyncState(gamepad);
|
|
}
|
|
|
|
// static
|
|
bool GamepadManager::IsServiceRunning() { return !!gGamepadManagerSingleton; }
|
|
|
|
// static
|
|
already_AddRefed<GamepadManager> GamepadManager::GetService() {
|
|
if (sShutdown) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!gGamepadManagerSingleton) {
|
|
RefPtr<GamepadManager> manager = new GamepadManager();
|
|
nsresult rv = manager->Init();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
gGamepadManagerSingleton = manager;
|
|
ClearOnShutdown(&gGamepadManagerSingleton);
|
|
}
|
|
|
|
RefPtr<GamepadManager> service(gGamepadManagerSingleton);
|
|
return service.forget();
|
|
}
|
|
|
|
bool GamepadManager::AxisMoveIsFirstIntent(nsGlobalWindowInner* aWindow,
|
|
GamepadHandle aHandle,
|
|
const GamepadChangeEvent& aEvent) {
|
|
const GamepadChangeEventBody& body = aEvent.body();
|
|
if (!WindowHasSeenGamepad(aWindow, aHandle) &&
|
|
body.type() == GamepadChangeEventBody::TGamepadAxisInformation) {
|
|
// Some controllers would send small axis values even they are just idle.
|
|
// To avoid controllers be activated without its first intent.
|
|
const GamepadAxisInformation& a = body.get_GamepadAxisInformation();
|
|
if (abs(a.value()) < AXIS_FIRST_INTENT_THRESHOLD_VALUE) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool GamepadManager::MaybeWindowHasSeenGamepad(nsGlobalWindowInner* aWindow,
|
|
GamepadHandle aHandle) {
|
|
if (!WindowHasSeenGamepad(aWindow, aHandle)) {
|
|
// This window hasn't seen this gamepad before, so
|
|
// send a connection event first.
|
|
SetWindowHasSeenGamepad(aWindow, aHandle);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool GamepadManager::WindowHasSeenGamepad(nsGlobalWindowInner* aWindow,
|
|
GamepadHandle aHandle) const {
|
|
RefPtr<Gamepad> gamepad = aWindow->GetGamepad(aHandle);
|
|
return gamepad != nullptr;
|
|
}
|
|
|
|
void GamepadManager::SetWindowHasSeenGamepad(nsGlobalWindowInner* aWindow,
|
|
GamepadHandle aHandle,
|
|
bool aHasSeen) {
|
|
MOZ_ASSERT(aWindow);
|
|
|
|
if (mListeners.IndexOf(aWindow) == NoIndex) {
|
|
// This window isn't even listening for gamepad events.
|
|
return;
|
|
}
|
|
|
|
if (aHasSeen) {
|
|
aWindow->SetHasSeenGamepadInput(true);
|
|
nsCOMPtr<nsISupports> window = ToSupports(aWindow);
|
|
RefPtr<Gamepad> gamepad = GetGamepad(aHandle);
|
|
if (!gamepad) {
|
|
return;
|
|
}
|
|
RefPtr<Gamepad> clonedGamepad = gamepad->Clone(window);
|
|
aWindow->AddGamepad(aHandle, clonedGamepad);
|
|
} else {
|
|
aWindow->RemoveGamepad(aHandle);
|
|
}
|
|
}
|
|
|
|
void GamepadManager::Update(const GamepadChangeEvent& aEvent) {
|
|
if (!mEnabled || mShuttingDown) {
|
|
return;
|
|
}
|
|
|
|
const GamepadHandle handle = aEvent.handle();
|
|
|
|
GamepadChangeEventBody body = aEvent.body();
|
|
|
|
if (body.type() == GamepadChangeEventBody::TGamepadAdded) {
|
|
const GamepadAdded& a = body.get_GamepadAdded();
|
|
AddGamepad(handle, a.id(), static_cast<GamepadMappingType>(a.mapping()),
|
|
static_cast<GamepadHand>(a.hand()), a.display_id(),
|
|
a.num_buttons(), a.num_axes(), a.num_haptics(), a.num_lights(),
|
|
a.num_touches());
|
|
return;
|
|
}
|
|
if (body.type() == GamepadChangeEventBody::TGamepadRemoved) {
|
|
RemoveGamepad(handle);
|
|
return;
|
|
}
|
|
|
|
if (!SetGamepadByEvent(aEvent)) {
|
|
return;
|
|
}
|
|
|
|
// Hold on to listeners in a separate array because firing events
|
|
// can mutate the mListeners array.
|
|
nsTArray<RefPtr<nsGlobalWindowInner>> listeners(mListeners.Clone());
|
|
|
|
for (uint32_t i = 0; i < listeners.Length(); i++) {
|
|
// Only send events to non-background windows
|
|
if (!listeners[i]->IsCurrentInnerWindow() ||
|
|
listeners[i]->GetOuterWindow()->IsBackground() ||
|
|
listeners[i]->ShouldResistFingerprinting(RFPTarget::Gamepad)) {
|
|
continue;
|
|
}
|
|
|
|
SetGamepadByEvent(aEvent, listeners[i]);
|
|
MaybeConvertToNonstandardGamepadEvent(aEvent, listeners[i]);
|
|
}
|
|
}
|
|
|
|
void GamepadManager::MaybeConvertToNonstandardGamepadEvent(
|
|
const GamepadChangeEvent& aEvent, nsGlobalWindowInner* aWindow) {
|
|
MOZ_ASSERT(aWindow);
|
|
|
|
if (!mNonstandardEventsEnabled) {
|
|
return;
|
|
}
|
|
|
|
GamepadHandle handle = aEvent.handle();
|
|
|
|
RefPtr<Gamepad> gamepad = aWindow->GetGamepad(handle);
|
|
const GamepadChangeEventBody& body = aEvent.body();
|
|
|
|
if (gamepad) {
|
|
switch (body.type()) {
|
|
case GamepadChangeEventBody::TGamepadButtonInformation: {
|
|
const GamepadButtonInformation& a = body.get_GamepadButtonInformation();
|
|
FireButtonEvent(aWindow, gamepad, a.button(), a.value());
|
|
break;
|
|
}
|
|
case GamepadChangeEventBody::TGamepadAxisInformation: {
|
|
const GamepadAxisInformation& a = body.get_GamepadAxisInformation();
|
|
FireAxisMoveEvent(aWindow, gamepad, a.axis(), a.value());
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool GamepadManager::SetGamepadByEvent(const GamepadChangeEvent& aEvent,
|
|
nsGlobalWindowInner* aWindow) {
|
|
bool ret = false;
|
|
bool firstTime = false;
|
|
|
|
GamepadHandle handle = aEvent.handle();
|
|
|
|
if (aWindow) {
|
|
if (!AxisMoveIsFirstIntent(aWindow, handle, aEvent)) {
|
|
return false;
|
|
}
|
|
firstTime = !MaybeWindowHasSeenGamepad(aWindow, handle);
|
|
}
|
|
|
|
RefPtr<Gamepad> gamepad =
|
|
aWindow ? aWindow->GetGamepad(handle) : GetGamepad(handle);
|
|
const GamepadChangeEventBody& body = aEvent.body();
|
|
|
|
if (gamepad) {
|
|
switch (body.type()) {
|
|
case GamepadChangeEventBody::TGamepadButtonInformation: {
|
|
const GamepadButtonInformation& a = body.get_GamepadButtonInformation();
|
|
gamepad->SetButton(a.button(), a.pressed(), a.touched(), a.value());
|
|
break;
|
|
}
|
|
case GamepadChangeEventBody::TGamepadAxisInformation: {
|
|
const GamepadAxisInformation& a = body.get_GamepadAxisInformation();
|
|
gamepad->SetAxis(a.axis(), a.value());
|
|
break;
|
|
}
|
|
case GamepadChangeEventBody::TGamepadPoseInformation: {
|
|
const GamepadPoseInformation& a = body.get_GamepadPoseInformation();
|
|
gamepad->SetPose(a.pose_state());
|
|
break;
|
|
}
|
|
case GamepadChangeEventBody::TGamepadLightIndicatorTypeInformation: {
|
|
const GamepadLightIndicatorTypeInformation& a =
|
|
body.get_GamepadLightIndicatorTypeInformation();
|
|
gamepad->SetLightIndicatorType(a.light(), a.type());
|
|
break;
|
|
}
|
|
case GamepadChangeEventBody::TGamepadTouchInformation: {
|
|
// Avoid GamepadTouch's touchId be accessed in cross-origin tracking.
|
|
for (uint32_t i = 0; i < mListeners.Length(); i++) {
|
|
RefPtr<Gamepad> listenerGamepad = mListeners[i]->GetGamepad(handle);
|
|
if (listenerGamepad && mListeners[i]->IsCurrentInnerWindow() &&
|
|
!mListeners[i]->GetOuterWindow()->IsBackground()) {
|
|
const GamepadTouchInformation& a =
|
|
body.get_GamepadTouchInformation();
|
|
listenerGamepad->SetTouchEvent(a.index(), a.touch_state());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case GamepadChangeEventBody::TGamepadHandInformation: {
|
|
const GamepadHandInformation& a = body.get_GamepadHandInformation();
|
|
gamepad->SetHand(a.hand());
|
|
break;
|
|
}
|
|
default:
|
|
MOZ_ASSERT(false);
|
|
break;
|
|
}
|
|
ret = true;
|
|
}
|
|
|
|
if (aWindow && firstTime) {
|
|
FireConnectionEvent(aWindow, gamepad, true);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
already_AddRefed<Promise> GamepadManager::VibrateHaptic(
|
|
GamepadHandle aHandle, uint32_t aHapticIndex, double aIntensity,
|
|
double aDuration, nsIGlobalObject* aGlobal, ErrorResult& aRv) {
|
|
RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return nullptr;
|
|
}
|
|
if (StaticPrefs::dom_gamepad_haptic_feedback_enabled()) {
|
|
if (aHandle.GetKind() == GamepadHandleKind::VR) {
|
|
if (gfx::VRManagerChild::IsCreated()) {
|
|
gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
|
|
vm->AddPromise(mPromiseID, promise);
|
|
vm->SendVibrateHaptic(aHandle, aHapticIndex, aIntensity, aDuration,
|
|
mPromiseID);
|
|
}
|
|
} else {
|
|
if (mChannelChild) {
|
|
mChannelChild->AddPromise(mPromiseID, promise);
|
|
mChannelChild->SendVibrateHaptic(aHandle, aHapticIndex, aIntensity,
|
|
aDuration, mPromiseID);
|
|
}
|
|
}
|
|
}
|
|
|
|
++mPromiseID;
|
|
return promise.forget();
|
|
}
|
|
|
|
void GamepadManager::StopHaptics() {
|
|
if (!StaticPrefs::dom_gamepad_haptic_feedback_enabled()) {
|
|
return;
|
|
}
|
|
|
|
for (const auto& entry : mGamepads) {
|
|
const GamepadHandle handle = entry.GetWeak()->GetHandle();
|
|
if (handle.GetKind() == GamepadHandleKind::VR) {
|
|
if (gfx::VRManagerChild::IsCreated()) {
|
|
gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
|
|
vm->SendStopVibrateHaptic(handle);
|
|
}
|
|
} else {
|
|
if (mChannelChild) {
|
|
mChannelChild->SendStopVibrateHaptic(handle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
already_AddRefed<Promise> GamepadManager::SetLightIndicatorColor(
|
|
GamepadHandle aHandle, uint32_t aLightColorIndex, uint8_t aRed,
|
|
uint8_t aGreen, uint8_t aBlue, nsIGlobalObject* aGlobal, ErrorResult& aRv) {
|
|
RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return nullptr;
|
|
}
|
|
if (StaticPrefs::dom_gamepad_extensions_lightindicator()) {
|
|
MOZ_RELEASE_ASSERT(aHandle.GetKind() != GamepadHandleKind::VR,
|
|
"We don't support light indicator in VR.");
|
|
|
|
if (mChannelChild) {
|
|
mChannelChild->AddPromise(mPromiseID, promise);
|
|
mChannelChild->SendLightIndicatorColor(aHandle, aLightColorIndex, aRed,
|
|
aGreen, aBlue, mPromiseID);
|
|
}
|
|
}
|
|
|
|
++mPromiseID;
|
|
return promise.forget();
|
|
}
|
|
|
|
already_AddRefed<Promise> GamepadManager::RequestAllGamepads(
|
|
nsIGlobalObject* aGlobal, ErrorResult& aRv) {
|
|
RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!mChannelChild) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return nullptr;
|
|
}
|
|
|
|
mChannelChild->SendRequestAllGamepads(
|
|
[promise](const nsTArray<GamepadAdded>& aAddedGamepads) {
|
|
nsTArray<RefPtr<Gamepad>> gamepads;
|
|
|
|
for (const auto& addedGamepad : aAddedGamepads) {
|
|
RefPtr<Gamepad> gamepad = new Gamepad(
|
|
nullptr, addedGamepad.id(), 0, GamepadHandle(),
|
|
addedGamepad.mapping(), addedGamepad.hand(),
|
|
addedGamepad.display_id(), addedGamepad.num_buttons(),
|
|
addedGamepad.num_axes(), addedGamepad.num_haptics(),
|
|
addedGamepad.num_lights(), addedGamepad.num_touches());
|
|
gamepads.AppendElement(gamepad);
|
|
}
|
|
promise->MaybeResolve(gamepads);
|
|
},
|
|
[promise](mozilla::ipc::ResponseRejectReason) {
|
|
promise->MaybeReject(NS_ERROR_UNEXPECTED);
|
|
});
|
|
|
|
return promise.forget();
|
|
}
|
|
} // namespace mozilla::dom
|