diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/vr/ipc/VRManagerChild.cpp | 628 |
1 files changed, 628 insertions, 0 deletions
diff --git a/gfx/vr/ipc/VRManagerChild.cpp b/gfx/vr/ipc/VRManagerChild.cpp new file mode 100644 index 0000000000..b02f94ba8c --- /dev/null +++ b/gfx/vr/ipc/VRManagerChild.cpp @@ -0,0 +1,628 @@ +/* -*- 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 "VRManagerChild.h" + +#include "VRLayerChild.h" +#include "VRManagerParent.h" +#include "VRThread.h" +#include "VRDisplayClient.h" +#include "nsGlobalWindowInner.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/layers/CompositorThread.h" // for CompositorThread +#include "mozilla/dom/Navigator.h" +#include "mozilla/dom/VREventObserver.h" +#include "mozilla/dom/WebXRBinding.h" +#include "mozilla/dom/WindowBinding.h" // for FrameRequestCallback +#include "mozilla/dom/XRSystem.h" +#include "mozilla/dom/XRFrame.h" +#include "mozilla/dom/ContentChild.h" +#include "nsContentUtils.h" +#include "mozilla/dom/GamepadManager.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/SyncObject.h" +#include "mozilla/layers/TextureForwarder.h" + +using namespace mozilla::dom; + +namespace { +const nsTArray<RefPtr<mozilla::gfx::VRManagerEventObserver>>::index_type + kNoIndex = nsTArray<RefPtr<mozilla::gfx::VRManagerEventObserver>>::NoIndex; +} // namespace + +namespace mozilla { +namespace gfx { + +static StaticRefPtr<VRManagerChild> sVRManagerChildSingleton; +static StaticRefPtr<VRManagerParent> sVRManagerParentSingleton; + +static TimeStamp sMostRecentFrameEnd; +static TimeDuration sAverageFrameInterval; + +void ReleaseVRManagerParentSingleton() { sVRManagerParentSingleton = nullptr; } + +VRManagerChild::VRManagerChild() + : mRuntimeCapabilities(VRDisplayCapabilityFlags::Cap_None), + mFrameRequestCallbackCounter(0), + mWaitingForEnumeration(false), + mBackend(layers::LayersBackend::LAYERS_NONE) { + MOZ_ASSERT(NS_IsMainThread()); + + mStartTimeStamp = TimeStamp::Now(); +} + +VRManagerChild::~VRManagerChild() { MOZ_ASSERT(NS_IsMainThread()); } + +/*static*/ +void VRManagerChild::IdentifyTextureHost( + const TextureFactoryIdentifier& aIdentifier) { + if (sVRManagerChildSingleton) { + sVRManagerChildSingleton->mBackend = aIdentifier.mParentBackend; + } +} + +layers::LayersBackend VRManagerChild::GetBackendType() const { + return mBackend; +} + +/*static*/ +VRManagerChild* VRManagerChild::Get() { + MOZ_ASSERT(sVRManagerChildSingleton); + return sVRManagerChildSingleton; +} + +/* static */ +bool VRManagerChild::IsCreated() { return !!sVRManagerChildSingleton; } + +/* static */ +bool VRManagerChild::IsPresenting() { + if (!VRManagerChild::IsCreated()) { + return false; + } + + nsTArray<RefPtr<VRDisplayClient>> displays; + sVRManagerChildSingleton->GetVRDisplays(displays); + + bool result = false; + for (auto& display : displays) { + result |= display->IsPresenting(); + } + return result; +} + +TimeStamp VRManagerChild::GetIdleDeadlineHint(TimeStamp aDefault) { + MOZ_ASSERT(NS_IsMainThread()); + if (!VRManagerChild::IsCreated() || sMostRecentFrameEnd.IsNull()) { + return aDefault; + } + + TimeStamp idleEnd = sMostRecentFrameEnd + sAverageFrameInterval; + return idleEnd < aDefault ? idleEnd : aDefault; +} + +/* static */ +bool VRManagerChild::InitForContent(Endpoint<PVRManagerChild>&& aEndpoint) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<VRManagerChild> child(new VRManagerChild()); + if (!aEndpoint.Bind(child)) { + return false; + } + sVRManagerChildSingleton = child; + return true; +} + +/*static*/ +void VRManagerChild::InitSameProcess() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sVRManagerChildSingleton); + + sVRManagerChildSingleton = new VRManagerChild(); + sVRManagerParentSingleton = VRManagerParent::CreateSameProcess(); + sVRManagerChildSingleton->Open(sVRManagerParentSingleton, CompositorThread(), + mozilla::ipc::ChildSide); +} + +/* static */ +void VRManagerChild::InitWithGPUProcess(Endpoint<PVRManagerChild>&& aEndpoint) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sVRManagerChildSingleton); + + sVRManagerChildSingleton = new VRManagerChild(); + if (!aEndpoint.Bind(sVRManagerChildSingleton)) { + MOZ_CRASH("Couldn't Open() Compositor channel."); + } +} + +/*static*/ +void VRManagerChild::ShutDown() { + MOZ_ASSERT(NS_IsMainThread()); + if (!sVRManagerChildSingleton) { + return; + } + sVRManagerChildSingleton->Close(); + sVRManagerChildSingleton = nullptr; +} + +void VRManagerChild::ActorDestroy(ActorDestroyReason aReason) { + if (sVRManagerChildSingleton == this) { + sVRManagerChildSingleton = nullptr; + } +} + +PVRLayerChild* VRManagerChild::AllocPVRLayerChild(const uint32_t& aDisplayID, + const uint32_t& aGroup) { + return VRLayerChild::CreateIPDLActor(); +} + +bool VRManagerChild::DeallocPVRLayerChild(PVRLayerChild* actor) { + return VRLayerChild::DestroyIPDLActor(actor); +} + +void VRManagerChild::UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo) { + nsTArray<uint32_t> disconnectedDisplays; + nsTArray<uint32_t> connectedDisplays; + + const nsTArray<RefPtr<VRDisplayClient>> prevDisplays(mDisplays.Clone()); + + // Check if any displays have been disconnected + for (auto& display : prevDisplays) { + bool found = false; + if (aDisplayInfo.GetDisplayID() != 0) { + if (display->GetDisplayInfo().GetDisplayID() == + aDisplayInfo.GetDisplayID()) { + found = true; + break; + } + } + if (!found) { + // In order to make the current VRDisplay can continue to apply for the + // newest VRDisplayInfo, we need to exit presentionation before + // disconnecting. + if (display->IsPresentationGenerationCurrent()) { + NotifyPresentationGenerationChangedInternal( + display->GetDisplayInfo().GetDisplayID()); + + RefPtr<VRManagerChild> vm = VRManagerChild::Get(); + vm->FireDOMVRDisplayPresentChangeEvent( + display->GetDisplayInfo().GetDisplayID()); + } + display->NotifyDisconnected(); + disconnectedDisplays.AppendElement( + display->GetDisplayInfo().GetDisplayID()); + } + } + + // mDisplays could be a hashed container for more scalability, but not worth + // it now as we expect < 10 entries. + nsTArray<RefPtr<VRDisplayClient>> displays; + if (aDisplayInfo.GetDisplayID() != 0) { + bool isNewDisplay = true; + for (auto& display : prevDisplays) { + const VRDisplayInfo& prevInfo = display->GetDisplayInfo(); + if (prevInfo.GetDisplayID() == aDisplayInfo.GetDisplayID()) { + if (aDisplayInfo.GetIsConnected() && !prevInfo.GetIsConnected()) { + connectedDisplays.AppendElement(aDisplayInfo.GetDisplayID()); + } + if (!aDisplayInfo.GetIsConnected() && prevInfo.GetIsConnected()) { + disconnectedDisplays.AppendElement(aDisplayInfo.GetDisplayID()); + } + // MOZ_KnownLive because 'prevDisplays' is guaranteed to keep it alive. + // + // This can go away once + // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed. + MOZ_KnownLive(display)->UpdateDisplayInfo(aDisplayInfo); + displays.AppendElement(display); + isNewDisplay = false; + break; + } + } + if (isNewDisplay) { + displays.AppendElement(new VRDisplayClient(aDisplayInfo)); + connectedDisplays.AppendElement(aDisplayInfo.GetDisplayID()); + } + } + + mDisplays = std::move(displays); + + // We wish to fire the events only after mDisplays is updated + for (uint32_t displayID : disconnectedDisplays) { + FireDOMVRDisplayDisconnectEvent(displayID); + } + + for (uint32_t displayID : connectedDisplays) { + FireDOMVRDisplayConnectEvent(displayID); + } +} + +bool VRManagerChild::RuntimeSupportsVR() const { + return bool(mRuntimeCapabilities & VRDisplayCapabilityFlags::Cap_ImmersiveVR); +} +bool VRManagerChild::RuntimeSupportsAR() const { + return bool(mRuntimeCapabilities & VRDisplayCapabilityFlags::Cap_ImmersiveAR); +} +bool VRManagerChild::RuntimeSupportsInline() const { + return bool(mRuntimeCapabilities & VRDisplayCapabilityFlags::Cap_Inline); +} + +mozilla::ipc::IPCResult VRManagerChild::RecvUpdateRuntimeCapabilities( + const VRDisplayCapabilityFlags& aCapabilities) { + mRuntimeCapabilities = aCapabilities; + nsContentUtils::AddScriptRunner(NewRunnableMethod<>( + "gfx::VRManagerChild::NotifyRuntimeCapabilitiesUpdatedInternal", this, + &VRManagerChild::NotifyRuntimeCapabilitiesUpdatedInternal)); + return IPC_OK(); +} + +void VRManagerChild::NotifyRuntimeCapabilitiesUpdatedInternal() { + const nsTArray<RefPtr<VRManagerEventObserver>> listeners = mListeners.Clone(); + for (auto& listener : listeners) { + listener->NotifyDetectRuntimesCompleted(); + } +} + +mozilla::ipc::IPCResult VRManagerChild::RecvUpdateDisplayInfo( + const VRDisplayInfo& aDisplayInfo) { + UpdateDisplayInfo(aDisplayInfo); + for (auto& windowId : mNavigatorCallbacks) { + /** We must call NotifyVRDisplaysUpdated for every + * window's Navigator in mNavigatorCallbacks to ensure that + * the promise returned by Navigator.GetVRDevices + * can resolve. This must happen even if no changes + * to VRDisplays have been detected here. + */ + nsGlobalWindowInner* window = + nsGlobalWindowInner::GetInnerWindowWithId(windowId); + if (!window) { + continue; + } + dom::Navigator* nav = window->Navigator(); + if (!nav) { + continue; + } + nav->NotifyVRDisplaysUpdated(); + } + mNavigatorCallbacks.Clear(); + if (mWaitingForEnumeration) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<>( + "gfx::VRManagerChild::NotifyEnumerationCompletedInternal", this, + &VRManagerChild::NotifyEnumerationCompletedInternal)); + mWaitingForEnumeration = false; + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerChild::RecvNotifyPuppetCommandBufferCompleted( + bool aSuccess) { + RefPtr<dom::Promise> promise = mRunPuppetPromise; + mRunPuppetPromise = nullptr; + if (aSuccess) { + promise->MaybeResolve(JS::UndefinedHandleValue); + } else { + promise->MaybeRejectWithUndefined(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerChild::RecvNotifyPuppetResetComplete() { + nsTArray<RefPtr<dom::Promise>> promises; + promises.AppendElements(mResetPuppetPromises); + mResetPuppetPromises.Clear(); + for (const auto& promise : promises) { + promise->MaybeResolve(JS::UndefinedHandleValue); + } + return IPC_OK(); +} + +void VRManagerChild::RunPuppet(const nsTArray<uint64_t>& aBuffer, + dom::Promise* aPromise, ErrorResult& aRv) { + if (mRunPuppetPromise) { + // We only allow one puppet script to run simultaneously. + // The prior promise must be resolved before running a new + // script. + aRv.Throw(NS_ERROR_INVALID_ARG); + return; + } + if (!SendRunPuppet(aBuffer)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + mRunPuppetPromise = aPromise; +} + +void VRManagerChild::ResetPuppet(dom::Promise* aPromise, ErrorResult& aRv) { + if (!SendResetPuppet()) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + mResetPuppetPromises.AppendElement(aPromise); +} + +void VRManagerChild::GetVRDisplays( + nsTArray<RefPtr<VRDisplayClient>>& aDisplays) { + aDisplays = mDisplays.Clone(); +} + +bool VRManagerChild::RefreshVRDisplaysWithCallback(uint64_t aWindowId) { + bool success = SendRefreshDisplays(); + if (success) { + mNavigatorCallbacks.AppendElement(aWindowId); + } + return success; +} + +bool VRManagerChild::EnumerateVRDisplays() { + bool success = SendRefreshDisplays(); + if (success) { + mWaitingForEnumeration = true; + } + return success; +} + +void VRManagerChild::DetectRuntimes() { Unused << SendDetectRuntimes(); } + +PVRLayerChild* VRManagerChild::CreateVRLayer(uint32_t aDisplayID, + uint32_t aGroup) { + PVRLayerChild* vrLayerChild = AllocPVRLayerChild(aDisplayID, aGroup); + return SendPVRLayerConstructor(vrLayerChild, aDisplayID, aGroup); +} + +void VRManagerChild::XRFrameRequest::Call( + const DOMHighResTimeStamp& aTimeStamp) { + if (mCallback) { + RefPtr<mozilla::dom::FrameRequestCallback> callback = mCallback; + callback->Call(aTimeStamp); + } else { + RefPtr<mozilla::dom::XRFrameRequestCallback> callback = mXRCallback; + RefPtr<mozilla::dom::XRFrame> frame = mXRFrame; + callback->Call(aTimeStamp, *frame); + } +} + +nsresult VRManagerChild::ScheduleFrameRequestCallback( + mozilla::dom::FrameRequestCallback& aCallback, int32_t* aHandle) { + if (mFrameRequestCallbackCounter == INT32_MAX) { + // Can't increment without overflowing; bail out + return NS_ERROR_NOT_AVAILABLE; + } + int32_t newHandle = ++mFrameRequestCallbackCounter; + + mFrameRequestCallbacks.AppendElement(XRFrameRequest(aCallback, newHandle)); + + *aHandle = newHandle; + return NS_OK; +} + +void VRManagerChild::CancelFrameRequestCallback(int32_t aHandle) { + // mFrameRequestCallbacks is stored sorted by handle + mFrameRequestCallbacks.RemoveElementSorted(aHandle); +} + +void VRManagerChild::RunFrameRequestCallbacks() { + AUTO_PROFILER_TRACING_MARKER("VR", "RunFrameRequestCallbacks", GRAPHICS); + + TimeStamp nowTime = TimeStamp::Now(); + mozilla::TimeDuration duration = nowTime - mStartTimeStamp; + DOMHighResTimeStamp timeStamp = duration.ToMilliseconds(); + + if (!sMostRecentFrameEnd.IsNull()) { + TimeDuration frameInterval = nowTime - sMostRecentFrameEnd; + if (sAverageFrameInterval.IsZero()) { + sAverageFrameInterval = frameInterval; + } else { + // Calculate the average interval between frame end and next frame start. + // Apply some smoothing to make it more stable. + const double smooth = 0.9; + sAverageFrameInterval = sAverageFrameInterval.MultDouble(smooth) + + frameInterval.MultDouble(1.0 - smooth); + } + } + + nsTArray<XRFrameRequest> callbacks; + callbacks.AppendElements(mFrameRequestCallbacks); + mFrameRequestCallbacks.Clear(); + for (auto& callback : callbacks) { + // The FrameRequest copied into the on-stack array holds a strong ref to its + // mCallback and there's nothing that can drop that ref until we return. + MOZ_KnownLive(callback.mCallback)->Call(timeStamp); + } + + if (IsPresenting()) { + sMostRecentFrameEnd = TimeStamp::Now(); + } +} + +void VRManagerChild::NotifyPresentationGenerationChanged(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::NotifyPresentationGenerationChangedInternal", this, + &VRManagerChild::NotifyPresentationGenerationChangedInternal, + aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayMountedEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayMountedEventInternal", this, + &VRManagerChild::FireDOMVRDisplayMountedEventInternal, aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayUnmountedEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayUnmountedEventInternal", this, + &VRManagerChild::FireDOMVRDisplayUnmountedEventInternal, aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayConnectEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayConnectEventInternal", this, + &VRManagerChild::FireDOMVRDisplayConnectEventInternal, aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayDisconnectEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayDisconnectEventInternal", this, + &VRManagerChild::FireDOMVRDisplayDisconnectEventInternal, aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayPresentChangeEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal", this, + &VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal, aDisplayID)); + + if (!IsPresenting()) { + sMostRecentFrameEnd = TimeStamp(); + sAverageFrameInterval = 0; + } +} + +void VRManagerChild::FireDOMVRDisplayMountedEventInternal(uint32_t aDisplayID) { + // Iterate over a copy of mListeners, as dispatched events may modify it. + for (auto& listener : mListeners.Clone()) { + listener->NotifyVRDisplayMounted(aDisplayID); + } +} + +void VRManagerChild::FireDOMVRDisplayUnmountedEventInternal( + uint32_t aDisplayID) { + // Iterate over a copy of mListeners, as dispatched events may modify it. + for (auto& listener : mListeners.Clone()) { + listener->NotifyVRDisplayUnmounted(aDisplayID); + } +} + +void VRManagerChild::FireDOMVRDisplayConnectEventInternal(uint32_t aDisplayID) { + // Iterate over a copy of mListeners, as dispatched events may modify it. + for (auto& listener : mListeners.Clone()) { + listener->NotifyVRDisplayConnect(aDisplayID); + } +} + +void VRManagerChild::FireDOMVRDisplayDisconnectEventInternal( + uint32_t aDisplayID) { + // Iterate over a copy of mListeners, as dispatched events may modify it. + for (auto& listener : mListeners.Clone()) { + listener->NotifyVRDisplayDisconnect(aDisplayID); + } +} + +void VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal( + uint32_t aDisplayID) { + // Iterate over a copy of mListeners, as dispatched events may modify it. + for (auto& listener : mListeners.Clone()) { + // MOZ_KnownLive because 'listeners' is guaranteed to keep it alive. + // + // This can go away once + // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed. + MOZ_KnownLive(listener)->NotifyVRDisplayPresentChange(aDisplayID); + } +} + +void VRManagerChild::FireDOMVRDisplayConnectEventsForLoadInternal( + uint32_t aDisplayID, VRManagerEventObserver* aObserver) { + aObserver->NotifyVRDisplayConnect(aDisplayID); +} + +void VRManagerChild::NotifyPresentationGenerationChangedInternal( + uint32_t aDisplayID) { + for (auto& listener : mListeners.Clone()) { + listener->NotifyPresentationGenerationChanged(aDisplayID); + } +} + +void VRManagerChild::NotifyEnumerationCompletedInternal() { + for (auto& listener : mListeners.Clone()) { + listener->NotifyEnumerationCompleted(); + } +} + +void VRManagerChild::FireDOMVRDisplayConnectEventsForLoad( + VRManagerEventObserver* aObserver) { + // We need to fire the VRDisplayConnect event when a page is loaded + // for each VR Display that has already been enumerated + for (const auto& display : mDisplays.Clone()) { + const VRDisplayInfo& info = display->GetDisplayInfo(); + if (info.GetIsConnected()) { + nsContentUtils::AddScriptRunner(NewRunnableMethod< + uint32_t, RefPtr<VRManagerEventObserver>>( + "gfx::VRManagerChild::FireDOMVRDisplayConnectEventsForLoadInternal", + this, &VRManagerChild::FireDOMVRDisplayConnectEventsForLoadInternal, + info.GetDisplayID(), aObserver)); + } + } +} + +void VRManagerChild::AddListener(VRManagerEventObserver* aObserver) { + MOZ_ASSERT(aObserver); + + if (mListeners.IndexOf(aObserver) != kNoIndex) { + return; // already exists + } + + mListeners.AppendElement(aObserver); + if (mListeners.Length() == 1) { + Unused << SendSetHaveEventListener(true); + } +} + +void VRManagerChild::RemoveListener(VRManagerEventObserver* aObserver) { + MOZ_ASSERT(aObserver); + + mListeners.RemoveElement(aObserver); + if (mListeners.IsEmpty()) { + Unused << SendSetHaveEventListener(false); + } +} + +void VRManagerChild::StartActivity() { Unused << SendStartActivity(); } + +void VRManagerChild::StopActivity() { + for (auto& listener : mListeners) { + if (!listener->GetStopActivityStatus()) { + // We are still showing VR in the active window. + return; + } + } + + Unused << SendStopActivity(); +} + +void VRManagerChild::HandleFatalError(const char* aMsg) { + dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aMsg, OtherPid()); +} + +void VRManagerChild::AddPromise(const uint32_t& aID, dom::Promise* aPromise) { + MOZ_ASSERT(!mGamepadPromiseList.Contains(aID)); + mGamepadPromiseList.InsertOrUpdate(aID, RefPtr{aPromise}); +} + +gfx::VRAPIMode VRManagerChild::GetVRAPIMode(uint32_t aDisplayID) const { + for (auto& display : mDisplays) { + if (display->GetDisplayInfo().GetDisplayID() == aDisplayID) { + return display->GetXRAPIMode(); + } + } + return VRAPIMode::WebXR; +} + +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(); +} + +} // namespace gfx +} // namespace mozilla |