GamepadManager.cpp (21857B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/GamepadManager.h" 8 9 #include "VRManagerChild.h" 10 #include "mozilla/ClearOnShutdown.h" 11 #include "mozilla/Preferences.h" 12 #include "mozilla/Services.h" 13 #include "mozilla/StaticPrefs_dom.h" 14 #include "mozilla/StaticPtr.h" 15 #include "mozilla/dom/Gamepad.h" 16 #include "mozilla/dom/GamepadAxisMoveEvent.h" 17 #include "mozilla/dom/GamepadButtonEvent.h" 18 #include "mozilla/dom/GamepadEvent.h" 19 #include "mozilla/dom/GamepadEventChannelChild.h" 20 #include "mozilla/dom/GamepadMonitoring.h" 21 #include "mozilla/dom/Promise.h" 22 #include "mozilla/ipc/BackgroundChild.h" 23 #include "mozilla/ipc/PBackgroundChild.h" 24 #include "nsContentUtils.h" 25 #include "nsGlobalWindowInner.h" 26 #include "nsIObserver.h" 27 #include "nsIObserverService.h" 28 #include "nsThreadUtils.h" 29 30 using namespace mozilla::ipc; 31 32 namespace mozilla::dom { 33 34 namespace { 35 36 const nsTArray<RefPtr<nsGlobalWindowInner>>::index_type NoIndex = 37 nsTArray<RefPtr<nsGlobalWindowInner>>::NoIndex; 38 39 bool sShutdown = false; 40 41 StaticRefPtr<GamepadManager> gGamepadManagerSingleton; 42 43 // A threshold value of axis move to determine the first 44 // intent. 45 const float AXIS_FIRST_INTENT_THRESHOLD_VALUE = 0.1f; 46 47 } // namespace 48 49 NS_IMPL_ISUPPORTS(GamepadManager, nsIObserver) 50 51 GamepadManager::GamepadManager() 52 : mEnabled(false), 53 mNonstandardEventsEnabled(false), 54 mShuttingDown(false), 55 mPromiseID(0) {} 56 57 nsresult GamepadManager::Init() { 58 mEnabled = StaticPrefs::dom_gamepad_enabled(); 59 mNonstandardEventsEnabled = 60 StaticPrefs::dom_gamepad_non_standard_events_enabled(); 61 nsCOMPtr<nsIObserverService> observerService = 62 mozilla::services::GetObserverService(); 63 64 if (NS_WARN_IF(!observerService)) { 65 return NS_ERROR_FAILURE; 66 } 67 68 nsresult rv; 69 rv = observerService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, 70 false); 71 72 if (NS_WARN_IF(NS_FAILED(rv))) { 73 return rv; 74 } 75 76 return NS_OK; 77 } 78 79 NS_IMETHODIMP 80 GamepadManager::Observe(nsISupports* aSubject, const char* aTopic, 81 const char16_t* aData) { 82 nsCOMPtr<nsIObserverService> observerService = 83 mozilla::services::GetObserverService(); 84 if (observerService) { 85 observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); 86 } 87 BeginShutdown(); 88 return NS_OK; 89 } 90 91 void GamepadManager::StopMonitoring() { 92 if (mChannelChild) { 93 PGamepadEventChannelChild::Send__delete__(mChannelChild); 94 mChannelChild = nullptr; 95 } 96 if (gfx::VRManagerChild::IsCreated()) { 97 gfx::VRManagerChild* vm = gfx::VRManagerChild::Get(); 98 vm->SendControllerListenerRemoved(); 99 } 100 mGamepads.Clear(); 101 } 102 103 void GamepadManager::BeginShutdown() { 104 mShuttingDown = true; 105 StopMonitoring(); 106 // Don't let windows call back to unregister during shutdown 107 for (uint32_t i = 0; i < mListeners.Length(); i++) { 108 mListeners[i]->SetHasGamepadEventListener(false); 109 } 110 mListeners.Clear(); 111 sShutdown = true; 112 } 113 114 void GamepadManager::AddListener(nsGlobalWindowInner* aWindow) { 115 MOZ_ASSERT(aWindow); 116 MOZ_ASSERT(NS_IsMainThread()); 117 118 // IPDL child has not been created 119 if (!mChannelChild) { 120 PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread(); 121 if (NS_WARN_IF(!actor)) { 122 // We are probably shutting down. 123 return; 124 } 125 126 RefPtr<GamepadEventChannelChild> child(GamepadEventChannelChild::Create()); 127 if (!actor->SendPGamepadEventChannelConstructor(child.get())) { 128 // We are probably shutting down. 129 return; 130 } 131 132 mChannelChild = child; 133 134 if (gfx::VRManagerChild::IsCreated()) { 135 // Construct VRManagerChannel and ask adding the connected 136 // VR controllers to GamepadManager 137 gfx::VRManagerChild* vm = gfx::VRManagerChild::Get(); 138 vm->SendControllerListenerAdded(); 139 } 140 } 141 142 if (!mEnabled || mShuttingDown || 143 aWindow->ShouldResistFingerprinting(RFPTarget::Gamepad)) { 144 return; 145 } 146 147 if (mListeners.IndexOf(aWindow) != NoIndex) { 148 return; // already exists 149 } 150 151 mListeners.AppendElement(aWindow); 152 } 153 154 void GamepadManager::RemoveListener(nsGlobalWindowInner* aWindow) { 155 MOZ_ASSERT(aWindow); 156 157 if (mShuttingDown) { 158 // Doesn't matter at this point. It's possible we're being called 159 // as a result of our own destructor here, so just bail out. 160 return; 161 } 162 163 if (mListeners.IndexOf(aWindow) == NoIndex) { 164 return; // doesn't exist 165 } 166 167 for (const auto& key : mGamepads.Keys()) { 168 aWindow->RemoveGamepad(key); 169 } 170 171 mListeners.RemoveElement(aWindow); 172 173 if (mListeners.IsEmpty()) { 174 StopMonitoring(); 175 } 176 } 177 178 already_AddRefed<Gamepad> GamepadManager::GetGamepad( 179 GamepadHandle aHandle) const { 180 RefPtr<Gamepad> gamepad; 181 if (mGamepads.Get(aHandle, getter_AddRefs(gamepad))) { 182 return gamepad.forget(); 183 } 184 185 return nullptr; 186 } 187 188 void GamepadManager::AddGamepad(GamepadHandle aHandle, const nsAString& aId, 189 GamepadMappingType aMapping, GamepadHand aHand, 190 uint32_t aNumButtons, uint32_t aNumAxes, 191 uint32_t aNumHaptics, 192 uint32_t aNumLightIndicator, 193 uint32_t aNumTouchEvents) { 194 // TODO: bug 852258: get initial button/axis state 195 RefPtr<Gamepad> newGamepad = 196 new Gamepad(nullptr, aId, 197 0, // index is set by global window 198 aHandle, aMapping, aHand, aNumButtons, aNumAxes, aNumHaptics, 199 aNumLightIndicator, aNumTouchEvents); 200 201 // We store the gamepad related to its index given by the parent process, 202 // and no duplicate index is allowed. 203 MOZ_ASSERT(!mGamepads.Contains(aHandle)); 204 mGamepads.InsertOrUpdate(aHandle, std::move(newGamepad)); 205 NewConnectionEvent(aHandle, true); 206 } 207 208 void GamepadManager::RemoveGamepad(GamepadHandle aHandle) { 209 RefPtr<Gamepad> gamepad = GetGamepad(aHandle); 210 if (!gamepad) { 211 NS_WARNING("Trying to delete gamepad with invalid index"); 212 return; 213 } 214 gamepad->SetConnected(false); 215 NewConnectionEvent(aHandle, false); 216 mGamepads.Remove(aHandle); 217 } 218 219 void GamepadManager::FireButtonEvent(EventTarget* aTarget, Gamepad* aGamepad, 220 uint32_t aButton, double aValue) { 221 nsString name = 222 aValue == 1.0L ? u"gamepadbuttondown"_ns : u"gamepadbuttonup"_ns; 223 GamepadButtonEventInit init; 224 init.mBubbles = false; 225 init.mCancelable = false; 226 init.mGamepad = aGamepad; 227 init.mButton = aButton; 228 RefPtr<GamepadButtonEvent> event = 229 GamepadButtonEvent::Constructor(aTarget, name, init); 230 231 event->SetTrusted(true); 232 233 aTarget->DispatchEvent(*event); 234 } 235 236 void GamepadManager::FireAxisMoveEvent(EventTarget* aTarget, Gamepad* aGamepad, 237 uint32_t aAxis, double aValue) { 238 GamepadAxisMoveEventInit init; 239 init.mBubbles = false; 240 init.mCancelable = false; 241 init.mGamepad = aGamepad; 242 init.mAxis = aAxis; 243 init.mValue = aValue; 244 RefPtr<GamepadAxisMoveEvent> event = 245 GamepadAxisMoveEvent::Constructor(aTarget, u"gamepadaxismove"_ns, init); 246 247 event->SetTrusted(true); 248 249 aTarget->DispatchEvent(*event); 250 } 251 252 void GamepadManager::NewConnectionEvent(GamepadHandle aHandle, 253 bool aConnected) { 254 if (mShuttingDown) { 255 return; 256 } 257 258 RefPtr<Gamepad> gamepad = GetGamepad(aHandle); 259 if (!gamepad) { 260 return; 261 } 262 263 // Hold on to listeners in a separate array because firing events 264 // can mutate the mListeners array. 265 nsTArray<RefPtr<nsGlobalWindowInner>> listeners(mListeners.Clone()); 266 267 if (aConnected) { 268 for (uint32_t i = 0; i < listeners.Length(); i++) { 269 #ifdef NIGHTLY_BUILD 270 // Don't fire a gamepadconnected event unless it's a secure context 271 if (!listeners[i]->IsSecureContext()) { 272 continue; 273 } 274 #endif 275 276 // Do not fire gamepadconnected and gamepaddisconnected events when 277 // privacy.resistFingerprinting is true. 278 if (listeners[i]->ShouldResistFingerprinting(RFPTarget::Gamepad)) { 279 continue; 280 } 281 282 // Only send events to non-background windows 283 if (!listeners[i]->IsCurrentInnerWindow() || 284 listeners[i]->GetOuterWindow()->IsBackground()) { 285 continue; 286 } 287 288 // We don't fire a connected event here unless the window 289 // has seen input from at least one device. 290 if (!listeners[i]->HasSeenGamepadInput()) { 291 continue; 292 } 293 294 SetWindowHasSeenGamepad(listeners[i], aHandle); 295 296 RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aHandle); 297 if (listenerGamepad) { 298 // Fire event 299 FireConnectionEvent(listeners[i], listenerGamepad, aConnected); 300 } 301 } 302 } else { 303 // For disconnection events, fire one at every window that has received 304 // data from this gamepad. 305 for (uint32_t i = 0; i < listeners.Length(); i++) { 306 // Even background windows get these events, so we don't have to 307 // deal with the hassle of syncing the state of removed gamepads. 308 309 // Do not fire gamepadconnected and gamepaddisconnected events when 310 // privacy.resistFingerprinting is true. 311 if (listeners[i]->ShouldResistFingerprinting(RFPTarget::Gamepad)) { 312 continue; 313 } 314 315 if (WindowHasSeenGamepad(listeners[i], aHandle)) { 316 RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aHandle); 317 if (listenerGamepad) { 318 listenerGamepad->SetConnected(false); 319 // Fire event 320 FireConnectionEvent(listeners[i], listenerGamepad, false); 321 listeners[i]->RemoveGamepad(aHandle); 322 } 323 } 324 } 325 } 326 } 327 328 void GamepadManager::FireConnectionEvent(EventTarget* aTarget, 329 Gamepad* aGamepad, bool aConnected) { 330 nsString name = 331 aConnected ? u"gamepadconnected"_ns : u"gamepaddisconnected"_ns; 332 GamepadEventInit init; 333 init.mBubbles = false; 334 init.mCancelable = false; 335 init.mGamepad = aGamepad; 336 RefPtr<GamepadEvent> event = GamepadEvent::Constructor(aTarget, name, init); 337 338 event->SetTrusted(true); 339 340 aTarget->DispatchEvent(*event); 341 } 342 343 void GamepadManager::SyncGamepadState(GamepadHandle aHandle, 344 nsGlobalWindowInner* aWindow, 345 Gamepad* aGamepad) { 346 if (mShuttingDown || !mEnabled || 347 aWindow->ShouldResistFingerprinting(RFPTarget::Gamepad)) { 348 return; 349 } 350 351 RefPtr<Gamepad> gamepad = GetGamepad(aHandle); 352 if (!gamepad) { 353 return; 354 } 355 356 aGamepad->SyncState(gamepad); 357 } 358 359 // static 360 bool GamepadManager::IsServiceRunning() { return !!gGamepadManagerSingleton; } 361 362 // static 363 already_AddRefed<GamepadManager> GamepadManager::GetService() { 364 if (sShutdown) { 365 return nullptr; 366 } 367 368 if (!gGamepadManagerSingleton) { 369 RefPtr<GamepadManager> manager = new GamepadManager(); 370 nsresult rv = manager->Init(); 371 if (NS_WARN_IF(NS_FAILED(rv))) { 372 return nullptr; 373 } 374 gGamepadManagerSingleton = manager; 375 ClearOnShutdown(&gGamepadManagerSingleton); 376 } 377 378 RefPtr<GamepadManager> service(gGamepadManagerSingleton); 379 return service.forget(); 380 } 381 382 bool GamepadManager::AxisMoveIsFirstIntent(nsGlobalWindowInner* aWindow, 383 GamepadHandle aHandle, 384 const GamepadChangeEvent& aEvent) { 385 const GamepadChangeEventBody& body = aEvent.body(); 386 if (!WindowHasSeenGamepad(aWindow, aHandle) && 387 body.type() == GamepadChangeEventBody::TGamepadAxisInformation) { 388 // Some controllers would send small axis values even they are just idle. 389 // To avoid controllers be activated without its first intent. 390 const GamepadAxisInformation& a = body.get_GamepadAxisInformation(); 391 if (abs(a.value()) < AXIS_FIRST_INTENT_THRESHOLD_VALUE) { 392 return false; 393 } 394 } 395 return true; 396 } 397 398 bool GamepadManager::MaybeWindowHasSeenGamepad(nsGlobalWindowInner* aWindow, 399 GamepadHandle aHandle) { 400 if (!WindowHasSeenGamepad(aWindow, aHandle)) { 401 // This window hasn't seen this gamepad before, so 402 // send a connection event first. 403 SetWindowHasSeenGamepad(aWindow, aHandle); 404 return false; 405 } 406 return true; 407 } 408 409 bool GamepadManager::WindowHasSeenGamepad(nsGlobalWindowInner* aWindow, 410 GamepadHandle aHandle) const { 411 RefPtr<Gamepad> gamepad = aWindow->GetGamepad(aHandle); 412 return gamepad != nullptr; 413 } 414 415 void GamepadManager::SetWindowHasSeenGamepad(nsGlobalWindowInner* aWindow, 416 GamepadHandle aHandle, 417 bool aHasSeen) { 418 MOZ_ASSERT(aWindow); 419 420 if (mListeners.IndexOf(aWindow) == NoIndex) { 421 // This window isn't even listening for gamepad events. 422 return; 423 } 424 425 if (aHasSeen) { 426 aWindow->SetHasSeenGamepadInput(true); 427 nsCOMPtr<nsISupports> window = ToSupports(aWindow); 428 RefPtr<Gamepad> gamepad = GetGamepad(aHandle); 429 if (!gamepad) { 430 return; 431 } 432 RefPtr<Gamepad> clonedGamepad = gamepad->Clone(window); 433 aWindow->AddGamepad(aHandle, clonedGamepad); 434 } else { 435 aWindow->RemoveGamepad(aHandle); 436 } 437 } 438 439 void GamepadManager::Update(const GamepadChangeEvent& aEvent) { 440 if (!mEnabled || mShuttingDown) { 441 return; 442 } 443 444 const GamepadHandle handle = aEvent.handle(); 445 446 GamepadChangeEventBody body = aEvent.body(); 447 448 if (body.type() == GamepadChangeEventBody::TGamepadAdded) { 449 const GamepadAdded& a = body.get_GamepadAdded(); 450 AddGamepad(handle, a.id(), static_cast<GamepadMappingType>(a.mapping()), 451 static_cast<GamepadHand>(a.hand()), a.num_buttons(), 452 a.num_axes(), a.num_haptics(), a.num_lights(), a.num_touches()); 453 return; 454 } 455 if (body.type() == GamepadChangeEventBody::TGamepadRemoved) { 456 RemoveGamepad(handle); 457 return; 458 } 459 460 if (!SetGamepadByEvent(aEvent)) { 461 return; 462 } 463 464 // Hold on to listeners in a separate array because firing events 465 // can mutate the mListeners array. 466 nsTArray<RefPtr<nsGlobalWindowInner>> listeners(mListeners.Clone()); 467 468 for (uint32_t i = 0; i < listeners.Length(); i++) { 469 // Only send events to non-background windows 470 if (!listeners[i]->IsCurrentInnerWindow() || 471 listeners[i]->GetOuterWindow()->IsBackground() || 472 listeners[i]->ShouldResistFingerprinting(RFPTarget::Gamepad)) { 473 continue; 474 } 475 476 SetGamepadByEvent(aEvent, listeners[i]); 477 MaybeConvertToNonstandardGamepadEvent(aEvent, listeners[i]); 478 } 479 } 480 481 void GamepadManager::MaybeConvertToNonstandardGamepadEvent( 482 const GamepadChangeEvent& aEvent, nsGlobalWindowInner* aWindow) { 483 MOZ_ASSERT(aWindow); 484 485 if (!mNonstandardEventsEnabled) { 486 return; 487 } 488 489 GamepadHandle handle = aEvent.handle(); 490 491 RefPtr<Gamepad> gamepad = aWindow->GetGamepad(handle); 492 const GamepadChangeEventBody& body = aEvent.body(); 493 494 if (gamepad) { 495 switch (body.type()) { 496 case GamepadChangeEventBody::TGamepadButtonInformation: { 497 const GamepadButtonInformation& a = body.get_GamepadButtonInformation(); 498 FireButtonEvent(aWindow, gamepad, a.button(), a.value()); 499 break; 500 } 501 case GamepadChangeEventBody::TGamepadAxisInformation: { 502 const GamepadAxisInformation& a = body.get_GamepadAxisInformation(); 503 FireAxisMoveEvent(aWindow, gamepad, a.axis(), a.value()); 504 break; 505 } 506 default: 507 break; 508 } 509 } 510 } 511 512 bool GamepadManager::SetGamepadByEvent(const GamepadChangeEvent& aEvent, 513 nsGlobalWindowInner* aWindow) { 514 bool ret = false; 515 bool firstTime = false; 516 517 GamepadHandle handle = aEvent.handle(); 518 519 if (aWindow) { 520 if (!AxisMoveIsFirstIntent(aWindow, handle, aEvent)) { 521 return false; 522 } 523 firstTime = !MaybeWindowHasSeenGamepad(aWindow, handle); 524 } 525 526 RefPtr<Gamepad> gamepad = 527 aWindow ? aWindow->GetGamepad(handle) : GetGamepad(handle); 528 const GamepadChangeEventBody& body = aEvent.body(); 529 530 if (gamepad) { 531 switch (body.type()) { 532 case GamepadChangeEventBody::TGamepadButtonInformation: { 533 const GamepadButtonInformation& a = body.get_GamepadButtonInformation(); 534 gamepad->SetButton(a.button(), a.pressed(), a.touched(), a.value()); 535 break; 536 } 537 case GamepadChangeEventBody::TGamepadAxisInformation: { 538 const GamepadAxisInformation& a = body.get_GamepadAxisInformation(); 539 gamepad->SetAxis(a.axis(), a.value()); 540 break; 541 } 542 case GamepadChangeEventBody::TGamepadPoseInformation: { 543 const GamepadPoseInformation& a = body.get_GamepadPoseInformation(); 544 gamepad->SetPose(a.pose_state()); 545 break; 546 } 547 case GamepadChangeEventBody::TGamepadLightIndicatorTypeInformation: { 548 const GamepadLightIndicatorTypeInformation& a = 549 body.get_GamepadLightIndicatorTypeInformation(); 550 gamepad->SetLightIndicatorType(a.light(), a.type()); 551 break; 552 } 553 case GamepadChangeEventBody::TGamepadTouchInformation: { 554 // Avoid GamepadTouch's touchId be accessed in cross-origin tracking. 555 for (uint32_t i = 0; i < mListeners.Length(); i++) { 556 RefPtr<Gamepad> listenerGamepad = mListeners[i]->GetGamepad(handle); 557 if (listenerGamepad && mListeners[i]->IsCurrentInnerWindow() && 558 !mListeners[i]->GetOuterWindow()->IsBackground()) { 559 const GamepadTouchInformation& a = 560 body.get_GamepadTouchInformation(); 561 listenerGamepad->SetTouchEvent(a.index(), a.touch_state()); 562 } 563 } 564 break; 565 } 566 case GamepadChangeEventBody::TGamepadHandInformation: { 567 const GamepadHandInformation& a = body.get_GamepadHandInformation(); 568 gamepad->SetHand(a.hand()); 569 break; 570 } 571 default: 572 MOZ_ASSERT(false); 573 break; 574 } 575 ret = true; 576 } 577 578 if (aWindow && firstTime) { 579 FireConnectionEvent(aWindow, gamepad, true); 580 } 581 582 return ret; 583 } 584 585 already_AddRefed<Promise> GamepadManager::VibrateHaptic( 586 GamepadHandle aHandle, uint32_t aHapticIndex, double aIntensity, 587 double aDuration, nsIGlobalObject* aGlobal, ErrorResult& aRv) { 588 RefPtr<Promise> promise = Promise::Create(aGlobal, aRv); 589 if (NS_WARN_IF(aRv.Failed())) { 590 aRv.Throw(NS_ERROR_FAILURE); 591 return nullptr; 592 } 593 if (StaticPrefs::dom_gamepad_haptic_feedback_enabled()) { 594 if (aHandle.GetKind() == GamepadHandleKind::VR) { 595 if (gfx::VRManagerChild::IsCreated()) { 596 gfx::VRManagerChild* vm = gfx::VRManagerChild::Get(); 597 vm->AddPromise(mPromiseID, promise); 598 vm->SendVibrateHaptic(aHandle, aHapticIndex, aIntensity, aDuration, 599 mPromiseID); 600 } 601 } else { 602 if (mChannelChild) { 603 mChannelChild->AddPromise(mPromiseID, promise); 604 mChannelChild->SendVibrateHaptic(aHandle, aHapticIndex, aIntensity, 605 aDuration, mPromiseID); 606 } 607 } 608 } 609 610 ++mPromiseID; 611 return promise.forget(); 612 } 613 614 void GamepadManager::StopHaptics() { 615 if (!StaticPrefs::dom_gamepad_haptic_feedback_enabled()) { 616 return; 617 } 618 619 for (const auto& entry : mGamepads) { 620 const GamepadHandle handle = entry.GetWeak()->GetHandle(); 621 if (handle.GetKind() == GamepadHandleKind::VR) { 622 if (gfx::VRManagerChild::IsCreated()) { 623 gfx::VRManagerChild* vm = gfx::VRManagerChild::Get(); 624 vm->SendStopVibrateHaptic(handle); 625 } 626 } else { 627 if (mChannelChild) { 628 mChannelChild->SendStopVibrateHaptic(handle); 629 } 630 } 631 } 632 } 633 634 already_AddRefed<Promise> GamepadManager::SetLightIndicatorColor( 635 GamepadHandle aHandle, uint32_t aLightColorIndex, uint8_t aRed, 636 uint8_t aGreen, uint8_t aBlue, nsIGlobalObject* aGlobal, ErrorResult& aRv) { 637 RefPtr<Promise> promise = Promise::Create(aGlobal, aRv); 638 if (NS_WARN_IF(aRv.Failed())) { 639 aRv.Throw(NS_ERROR_FAILURE); 640 return nullptr; 641 } 642 if (StaticPrefs::dom_gamepad_extensions_lightindicator()) { 643 MOZ_RELEASE_ASSERT(aHandle.GetKind() != GamepadHandleKind::VR, 644 "We don't support light indicator in VR."); 645 646 if (mChannelChild) { 647 mChannelChild->AddPromise(mPromiseID, promise); 648 mChannelChild->SendLightIndicatorColor(aHandle, aLightColorIndex, aRed, 649 aGreen, aBlue, mPromiseID); 650 } 651 } 652 653 ++mPromiseID; 654 return promise.forget(); 655 } 656 657 already_AddRefed<Promise> GamepadManager::RequestAllGamepads( 658 nsIGlobalObject* aGlobal, ErrorResult& aRv) { 659 RefPtr<Promise> promise = Promise::Create(aGlobal, aRv); 660 if (NS_WARN_IF(aRv.Failed())) { 661 return nullptr; 662 } 663 664 if (!mChannelChild) { 665 aRv.Throw(NS_ERROR_FAILURE); 666 return nullptr; 667 } 668 669 mChannelChild->SendRequestAllGamepads( 670 [promise](const nsTArray<GamepadAdded>& aAddedGamepads) { 671 nsTArray<RefPtr<Gamepad>> gamepads; 672 673 for (const auto& addedGamepad : aAddedGamepads) { 674 RefPtr<Gamepad> gamepad = 675 new Gamepad(nullptr, addedGamepad.id(), 0, GamepadHandle(), 676 addedGamepad.mapping(), addedGamepad.hand(), 677 addedGamepad.num_buttons(), addedGamepad.num_axes(), 678 addedGamepad.num_haptics(), addedGamepad.num_lights(), 679 addedGamepad.num_touches()); 680 gamepads.AppendElement(gamepad); 681 } 682 promise->MaybeResolve(gamepads); 683 }, 684 [promise](mozilla::ipc::ResponseRejectReason) { 685 promise->MaybeReject(NS_ERROR_UNEXPECTED); 686 }); 687 688 return promise.forget(); 689 } 690 } // namespace mozilla::dom