Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/gamepad/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 21 kB image not shown  

Quelle  GamepadManager.cpp   Sprache: 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

Messung V0.5
C=94 H=98 G=95

¤ Dauer der Verarbeitung: 0.6 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.