From b3787eec77e9292e578b89fa6837e65ccfa87900 Mon Sep 17 00:00:00 2001 From: "Kearwood \"Kip\" Gilbert" Date: Fri, 3 Jan 2020 22:47:26 +0000 Subject: [PATCH] Bug 1603825 - Suppress the VR permission UI when no VR runtimes are detected r=daoshengmu,bzbarsky This patch suppresses VR device access permission prompts for users that do not have any VR runtimes installed. We could not depend on the existing VR device enumeration functions to suppress the permission prompts, as the act of enumerating VR devices will result in some hardware physically powering on and software starting up (and staying running) in the background. This patch includes logic to spawn the VR process with an additional flag indicating that it should attempt only to detect the runtimes, without proceeding to enumerate and activate hardware and software. VRManager now includes an enum to more clearly organize it's state machine model, which now must ensure that the runtime detection happens on-demand when the VR session support capabilities are first determined. There is a new pref to disable the suppression of permission prompts for use within permission UI tests on machines without VR runtimes. Renamed some variables and added comments to make code in nsGlobalWindowInner and Navigator clearer and better represent the updated logic -- to allow the separate detection of VR runtimes and VR session activation. Both the runtime detection and VR session activity uses VREventObserver to send events to nsGlobalWindowInner. Differential Revision: https://phabricator.services.mozilla.com/D57568 --HG-- extra : moz-landing-system : lando --- .../browser_temporary_permissions_expiry.js | 1 + .../popupNotifications/browser_displayURI.js | 1 + .../browser_privatebrowsing_rememberprompt.js | 5 +- dom/base/Navigator.cpp | 6 +- dom/base/nsGlobalWindowInner.cpp | 131 ++++- dom/base/nsGlobalWindowInner.h | 12 +- dom/vr/VREventObserver.cpp | 9 +- dom/vr/VREventObserver.h | 1 + dom/vr/test/crashtests/crashtests.list | 1 + .../enumerate_vr_on_dying_window.html | 14 + dom/vr/test/mochitest/mochitest.ini | 1 - gfx/vr/VRManager.cpp | 448 +++++++++++++----- gfx/vr/VRManager.h | 28 +- gfx/vr/ipc/PVRManager.ipdl | 9 +- gfx/vr/ipc/VRManagerChild.cpp | 57 ++- gfx/vr/ipc/VRManagerChild.h | 12 +- gfx/vr/ipc/VRManagerParent.cpp | 25 +- gfx/vr/ipc/VRManagerParent.h | 1 + gfx/vr/ipc/VRMessageUtils.h | 6 + modules/libpref/init/StaticPrefList.yaml | 20 + testing/crashtest/crashtests.list | 1 + 21 files changed, 606 insertions(+), 183 deletions(-) create mode 100644 dom/vr/test/crashtests/crashtests.list create mode 100644 dom/vr/test/crashtests/enumerate_vr_on_dying_window.html diff --git a/browser/base/content/test/permissions/browser_temporary_permissions_expiry.js b/browser/base/content/test/permissions/browser_temporary_permissions_expiry.js index 0fc34e0d153b..a7e333c1b96b 100644 --- a/browser/base/content/test/permissions/browser_temporary_permissions_expiry.js +++ b/browser/base/content/test/permissions/browser_temporary_permissions_expiry.js @@ -27,6 +27,7 @@ add_task(async function testTempPermissionRequestAfterExpiry() { set: [ ["privacy.temporary_permission_expire_time_ms", EXPIRE_TIME_MS], ["media.navigator.permission.fake", true], + ["dom.vr.always_support_vr", true], ], }); diff --git a/browser/base/content/test/popupNotifications/browser_displayURI.js b/browser/base/content/test/popupNotifications/browser_displayURI.js index 43748ec225b7..4383322b5710 100644 --- a/browser/base/content/test/popupNotifications/browser_displayURI.js +++ b/browser/base/content/test/popupNotifications/browser_displayURI.js @@ -89,6 +89,7 @@ add_task(async function setup() { set: [ ["media.navigator.permission.fake", true], ["media.navigator.permission.force", true], + ["dom.vr.always_support_vr", true], ], }); }); diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_rememberprompt.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_rememberprompt.js index 1d6ad207c230..2bc311bed18d 100644 --- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_rememberprompt.js +++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_rememberprompt.js @@ -9,7 +9,10 @@ // non-Fission e10s tests to try to avoid them. add_task(async function setup() { await SpecialPowers.pushPrefEnv({ - set: [["dom.ipc.keepProcessesAlive.webIsolated.perOrigin", 1]], + set: [ + ["dom.ipc.keepProcessesAlive.webIsolated.perOrigin", 1], + ["dom.vr.always_support_vr", true], + ], }); }); diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index 842ffbc5bf39..170334cbf770 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -1519,7 +1519,7 @@ void Navigator::FinishGetVRDisplays(bool isWebVRSupportedInwindow, Promise* p) { // response, it's possible that the Window can be torn down before this // call. In that case, the Window's cyclic references to VR objects are // also torn down and should not be recreated via - // NotifyVREventListenerAdded. + // NotifyHasXRSession. nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow); if (win->IsDying()) { // The Window has been torn down, so there is no further work that can @@ -1563,7 +1563,7 @@ void Navigator::GetActiveVRDisplays( * GetActiveVRDisplays should only enumerate displays that * are already active without causing any other hardware to be * activated. - * We must not call nsGlobalWindow::NotifyVREventListenerAdded here, + * We must not call nsGlobalWindow::NotifyHasXRSession here, * as that would cause enumeration and activation of other VR hardware. * Activating VR hardware is intrusive to the end user, as it may * involve physically powering on devices that the user did not @@ -1608,7 +1608,7 @@ void Navigator::NotifyActiveVRDisplaysChanged() { VRServiceTest* Navigator::RequestVRServiceTest() { // Ensure that the Mock VR devices are not released prematurely nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow); - win->NotifyVREventListenerAdded(); + win->NotifyHasXRSession(); if (!mVRServiceTest) { mVRServiceTest = VRServiceTest::CreateTestService(mWindow); diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp index ab60b4d4df4a..e5dbe8975bed 100644 --- a/dom/base/nsGlobalWindowInner.cpp +++ b/dom/base/nsGlobalWindowInner.cpp @@ -839,8 +839,9 @@ nsGlobalWindowInner::nsGlobalWindowInner(nsGlobalWindowOuter* aOuterWindow, mFocusByKeyOccurred(false), mDidFireDocElemInserted(false), mHasGamepad(false), - mHasVREvents(false), + mHasXRSession(false), mHasVRDisplayActivateEvents(false), + mXRRuntimeDetectionInFlight(false), mXRPermissionRequestInFlight(false), mXRPermissionGranted(false), mWasCurrentInnerWindow(false), @@ -1142,8 +1143,9 @@ void nsGlobalWindowInner::FreeInnerObjects() { mHasGamepad = false; mGamepads.Clear(); DisableVRUpdates(); - mHasVREvents = false; + mHasXRSession = false; mHasVRDisplayActivateEvents = false; + mXRRuntimeDetectionInFlight = false; mXRPermissionRequestInFlight = false; mXRPermissionGranted = false; mVRDisplays.Clear(); @@ -3985,12 +3987,23 @@ void nsGlobalWindowInner::DisableGamepadUpdates() { } void nsGlobalWindowInner::EnableVRUpdates() { - if (mHasVREvents && !mVREventObserver) { - MOZ_ASSERT(!IsDying()); + // We need to create a VREventObserver before we can either detect XR runtimes + // or start an XR session + if (!mVREventObserver && (mHasXRSession || mXRRuntimeDetectionInFlight)) { + // Assert that we are not creating the observer while IsDying() as + // that would result in a leak. VREventObserver holds a RefPtr to + // this nsGlobalWindowInner and would prevent it from being deallocated. + MOZ_ASSERT(!IsDying(), + "Creating a VREventObserver for an nsGlobalWindow that is " + "dying would cause it to leak."); mVREventObserver = new VREventObserver(this); + } + // If the content has an XR session, then we need to tell + // VREventObserver that there is VR activity. + if (mHasXRSession) { nsPIDOMWindowOuter* outer = GetOuterWindow(); if (outer && !outer->IsBackground()) { - mVREventObserver->StartActivity(); + StartVRActivity(); } } } @@ -4009,13 +4022,36 @@ void nsGlobalWindowInner::ResetVRTelemetry(bool aUpdate) { } void nsGlobalWindowInner::StartVRActivity() { - if (mVREventObserver) { + /** + * If the content has an XR session, tell + * the VREventObserver that the window is accessing + * VR devices. + * + * It's possible to have a VREventObserver without + * and XR session, if we are using it to get updates + * about XR runtime enumeration. In this case, + * we would not tell the VREventObserver that + * we are accessing VR devices. + */ + if (mVREventObserver && mHasXRSession) { mVREventObserver->StartActivity(); } } void nsGlobalWindowInner::StopVRActivity() { - if (mVREventObserver) { + /** + * If the content has an XR session, tell + * the VReventObserver that the window is no longer + * accessing VR devices. This does not stop the + * XR session itself, which may be resumed with + * EnableVRUpdates. + * It's possible to have a VREventObserver without + * and XR session, if we are using it to get updates + * about XR runtime enumeration. In this case, + * we would not tell the VREventObserver that + * we ending an activity that accesses VR devices. + */ + if (mVREventObserver && mHasXRSession) { mVREventObserver->StopActivity(); } } @@ -5994,28 +6030,70 @@ void nsGlobalWindowInner::SetHasGamepadEventListener( } } -void nsGlobalWindowInner::RequestXRPermission() { - if (mXRPermissionGranted) { - // Don't prompt redundantly once permission to - // access XR devices has been granted. - OnXRPermissionRequestAllow(); +void nsGlobalWindowInner::NotifyDetectXRRuntimesCompleted() { + if (!mXRRuntimeDetectionInFlight) { return; } + mXRRuntimeDetectionInFlight = false; if (mXRPermissionRequestInFlight) { - // Don't allow multiple simultaneous permissions requests; return; } + gfx::VRManagerChild* vm = gfx::VRManagerChild::Get(); + bool supported = vm->RuntimeSupportsVR(); + if (!supported) { + // A VR runtime was not installed; we can suppress + // the permission prompt + OnXRPermissionRequestCancel(); + return; + } + // A VR runtime was found. Display a permission prompt before + // allowing it to be accessed. + // Connect to the VRManager in order to receive the runtime + // detection results. mXRPermissionRequestInFlight = true; RefPtr request = new XRPermissionRequest(this, WindowID()); Unused << NS_WARN_IF(NS_FAILED(request->Start())); } +void nsGlobalWindowInner::RequestXRPermission() { + if (IsDying()) { + // Do not proceed if the window is dying, as that will result + // in leaks of objects that get re-allocated after FreeInnerObjects + // has been called, including mVREventObserver. + return; + } + if (mXRPermissionGranted) { + // Don't prompt redundantly once permission to + // access XR devices has been granted. + OnXRPermissionRequestAllow(); + return; + } + if (mXRRuntimeDetectionInFlight || mXRPermissionRequestInFlight) { + // Don't allow multiple simultaneous permissions requests; + return; + } + // Before displaying a permission prompt, detect + // if there is any VR runtime installed. + gfx::VRManagerChild* vm = gfx::VRManagerChild::Get(); + mXRRuntimeDetectionInFlight = true; + EnableVRUpdates(); + vm->DetectRuntimes(); +} + void nsGlobalWindowInner::OnXRPermissionRequestAllow() { mXRPermissionRequestInFlight = false; + if (IsDying()) { + // The window may have started dying while the permission request + // is in flight. + // Do not proceed if the window is dying, as that will result + // in leaks of objects that get re-allocated after FreeInnerObjects + // has been called, including mNavigator. + return; + } mXRPermissionGranted = true; - NotifyVREventListenerAdded(); + NotifyHasXRSession(); dom::Navigator* nav = Navigator(); MOZ_ASSERT(nav != nullptr); @@ -6024,6 +6102,14 @@ void nsGlobalWindowInner::OnXRPermissionRequestAllow() { void nsGlobalWindowInner::OnXRPermissionRequestCancel() { mXRPermissionRequestInFlight = false; + if (IsDying()) { + // The window may have started dying while the permission request + // is in flight. + // Do not proceed if the window is dying, as that will result + // in leaks of objects that get re-allocated after FreeInnerObjects + // has been called, including mNavigator. + return; + } dom::Navigator* nav = Navigator(); MOZ_ASSERT(nav != nullptr); nav->OnXRPermissionRequestCancel(); @@ -6084,15 +6170,22 @@ void nsGlobalWindowInner::EventListenerRemoved(nsAtom* aType) { } } -void nsGlobalWindowInner::NotifyVREventListenerAdded() { - mHasVREvents = true; +void nsGlobalWindowInner::NotifyHasXRSession() { + if (IsDying()) { + // Do not proceed if the window is dying, as that will result + // in leaks of objects that get re-allocated after FreeInnerObjects + // has been called, including mVREventObserver. + return; + } + mHasXRSession = true; EnableVRUpdates(); } bool nsGlobalWindowInner::HasUsedVR() const { - // Returns true only if any WebVR API call or related event - // has been used - return mHasVREvents; + // Returns true only if content has enumerated and activated + // XR devices. Detection of XR runtimes without activation + // will not cause true to be returned. + return mHasXRSession; } bool nsGlobalWindowInner::IsVRContentDetected() const { diff --git a/dom/base/nsGlobalWindowInner.h b/dom/base/nsGlobalWindowInner.h index 84fbb6114752..61d603ba9307 100644 --- a/dom/base/nsGlobalWindowInner.h +++ b/dom/base/nsGlobalWindowInner.h @@ -368,7 +368,7 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget, // Inner windows only. virtual void SetHasGamepadEventListener(bool aHasGamepad = true) override; - void NotifyVREventListenerAdded(); + void NotifyHasXRSession(); bool HasUsedVR() const; bool IsVRContentDetected() const; bool IsVRContentPresenting() const; @@ -521,6 +521,7 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget, // Inner windows only. // Called to inform that the set of active VR displays has changed. void NotifyActiveVRDisplaysChanged(); + void NotifyDetectXRRuntimesCompleted(); void NotifyPresentationGenerationChanged(uint32_t aDisplayID); void DispatchVRDisplayActivate(uint32_t aDisplayID, @@ -1273,12 +1274,17 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget, // Indicates whether this window wants gamepad input events bool mHasGamepad : 1; - // Indicates whether this window wants VR events - bool mHasVREvents : 1; + // Indicates whether this window has content that has an XR session + // An XR session results in enumeration and activation of XR devices. + bool mHasXRSession : 1; // Indicates whether this window wants VRDisplayActivate events bool mHasVRDisplayActivateEvents : 1; + // Indicates that a request for XR runtime detection has been + // requested, but has not yet been resolved + bool mXRRuntimeDetectionInFlight : 1; + // Indicates that an XR permission request has been requested // but has not yet been resolved. bool mXRPermissionRequestInFlight : 1; diff --git a/dom/vr/VREventObserver.cpp b/dom/vr/VREventObserver.cpp index 97fcbdff3a04..2af16c5844ce 100644 --- a/dom/vr/VREventObserver.cpp +++ b/dom/vr/VREventObserver.cpp @@ -157,12 +157,19 @@ void VREventObserver::NotifyVRDisplayPresentChange(uint32_t aDisplayID) { void VREventObserver::NotifyPresentationGenerationChanged(uint32_t aDisplayID) { if (mWindow && mWindow->IsCurrentInnerWindow()) { - mWindow->NotifyPresentationGenerationChanged(aDisplayID); MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + mWindow->NotifyPresentationGenerationChanged(aDisplayID); } } void VREventObserver::NotifyEnumerationCompleted() {} +void VREventObserver::NotifyDetectRuntimesCompleted() { + if (mWindow && mWindow->IsCurrentInnerWindow()) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + mWindow->NotifyDetectXRRuntimesCompleted(); + } +} + } // namespace dom } // namespace mozilla diff --git a/dom/vr/VREventObserver.h b/dom/vr/VREventObserver.h index 3285fc27f75f..f2567f9ab7b9 100644 --- a/dom/vr/VREventObserver.h +++ b/dom/vr/VREventObserver.h @@ -31,6 +31,7 @@ class VREventObserver final : public gfx::VRManagerEventObserver { void NotifyVRDisplayPresentChange(uint32_t aDisplayID) override; void NotifyPresentationGenerationChanged(uint32_t aDisplayID) override; void NotifyEnumerationCompleted() override; + void NotifyDetectRuntimesCompleted() override; void DisconnectFromOwner(); void UpdateSpentTimeIn2DTelemetry(bool aUpdate); diff --git a/dom/vr/test/crashtests/crashtests.list b/dom/vr/test/crashtests/crashtests.list new file mode 100644 index 000000000000..1cc06896f80b --- /dev/null +++ b/dom/vr/test/crashtests/crashtests.list @@ -0,0 +1 @@ +pref(dom.vr.always_support_vr,true) load enumerate_vr_on_dying_window.html diff --git a/dom/vr/test/crashtests/enumerate_vr_on_dying_window.html b/dom/vr/test/crashtests/enumerate_vr_on_dying_window.html new file mode 100644 index 000000000000..95975258bc5e --- /dev/null +++ b/dom/vr/test/crashtests/enumerate_vr_on_dying_window.html @@ -0,0 +1,14 @@ + + + + + + + diff --git a/dom/vr/test/mochitest/mochitest.ini b/dom/vr/test/mochitest/mochitest.ini index b09669a6037c..87a4174a21c5 100644 --- a/dom/vr/test/mochitest/mochitest.ini +++ b/dom/vr/test/mochitest/mochitest.ini @@ -5,7 +5,6 @@ support-files = requestPresent.js runVRTest.js WebVRHelpers.js - [test_vrController_displayId.html] # Enable Linux after Bug 1310655 # TIMED_OUT for Android. # skip-if = (os != "win" && release_or_beta) || (os == "android") diff --git a/gfx/vr/VRManager.cpp b/gfx/vr/VRManager.cpp index fd518f3d2009..948f03459fda 100644 --- a/gfx/vr/VRManager.cpp +++ b/gfx/vr/VRManager.cpp @@ -107,6 +107,10 @@ void VRManager::ManagerInit() { VRManager::VRManager() : mState(VRManagerState::Disabled), mAccumulator100ms(0.0f), + mRuntimeDetectionRequested(false), + mRuntimeDetectionCompleted(false), + mEnumerationRequested(false), + mEnumerationCompleted(false), mVRDisplaysRequested(false), mVRDisplaysRequestedNonFocus(false), mVRControllersRequested(false), @@ -116,7 +120,7 @@ VRManager::VRManager() mCurrentSubmitTask(nullptr), mLastSubmittedFrameId(0), mLastStartedFrame(0), - mEnumerationCompleted(false), + mRuntimeSupportFlags(VRDisplayCapabilityFlags::Cap_None), mAppPaused(false), mShmem(nullptr), mHapticPulseRemaining{}, @@ -172,6 +176,15 @@ void VRManager::OpenShmem() { } else { mShmem->ClearShMem(); } + + // Reset local information for new connection + mDisplayInfo.Clear(); + mLastUpdateDisplayInfo.Clear(); + mFrameStarted = false; + mBrowserState.Clear(); + mLastSensorState.Clear(); + mEnumerationCompleted = false; + mDisplayInfo.mGroupMask = kVRGroupContent; } void VRManager::CloseShmem() { @@ -206,7 +219,10 @@ void VRManager::AddLayer(VRLayerParent* aLayer) { } // Ensure that the content process receives the change immediately - RefreshVRDisplays(); + if (mState != VRManagerState::Enumeration && + mState != VRManagerState::RuntimeDetection) { + DispatchVRDisplayInfoUpdate(); + } } void VRManager::RemoveLayer(VRLayerParent* aLayer) { @@ -220,7 +236,10 @@ void VRManager::RemoveLayer(VRLayerParent* aLayer) { } // Ensure that the content process receives the change immediately - RefreshVRDisplays(); + if (mState != VRManagerState::Enumeration && + mState != VRManagerState::RuntimeDetection) { + DispatchVRDisplayInfoUpdate(); + } } void VRManager::AddVRManagerParent(VRManagerParent* aVRManagerParent) { @@ -422,9 +441,7 @@ void VRManager::Run100msTasks() { mServiceHost->Refresh(); CheckForPuppetCompletion(); #endif - RefreshVRDisplays(); - CheckForInactiveTimeout(); - CheckForShutdown(); + ProcessManagerState(); } void VRManager::ProcessTelemetryEvent() { @@ -461,7 +478,9 @@ void VRManager::ProcessTelemetryEvent() { void VRManager::CheckForInactiveTimeout() { // Shut down the VR devices when not in use if (mVRDisplaysRequested || mVRDisplaysRequestedNonFocus || - mVRControllersRequested) { + mVRControllersRequested || mEnumerationRequested || + mRuntimeDetectionRequested || mState == VRManagerState::Enumeration || + mState == VRManagerState::RuntimeDetection) { // We are using a VR device, keep it alive mLastActiveTime = TimeStamp::Now(); } else if (mLastActiveTime.IsNull()) { @@ -482,8 +501,7 @@ void VRManager::CheckForInactiveTimeout() { void VRManager::CheckForShutdown() { // Check for remote end shutdown - if (mState != VRManagerState::Disabled && mState != VRManagerState::Idle && - mDisplayInfo.mDisplayState.shutdown) { + if (mDisplayInfo.mDisplayState.shutdown) { Shutdown(); } } @@ -542,146 +560,312 @@ void VRManager::StartFrame() { DispatchVRDisplayInfoUpdate(); } -void VRManager::EnumerateVRDisplays() { +void VRManager::DetectRuntimes() { + if (mState == VRManagerState::RuntimeDetection) { + // Runtime detection has already been started. + // This additional request will also receive the + // result from the first request. + return; + } + + // Detect XR runtimes to determine if they are + // capable of supporting VR or AR sessions, while + // avoiding activating any XR devices or persistent + // background software. + if (mRuntimeDetectionCompleted) { + // We have already detected runtimes, so we can + // immediately respond with the same results. + // This will require the user to restart the browser + // after installing or removing an XR device + // runtime. + DispatchRuntimeCapabilitiesUpdate(); + return; + } + mRuntimeDetectionRequested = true; + ProcessManagerState(); +} + +void VRManager::EnumerateDevices() { + if (mState == VRManagerState::Enumeration) { + // Enumeration has already been started. + // This additional request will also receive the + // result from the first request. + return; + } + // Activate XR runtimes and enumerate XR devices. + mEnumerationRequested = true; + ProcessManagerState(); +} + +void VRManager::ProcessManagerState() { + switch (mState) { + case VRManagerState::Disabled: + ProcessManagerState_Disabled(); + break; + case VRManagerState::Idle: + ProcessManagerState_Idle(); + break; + case VRManagerState::RuntimeDetection: + ProcessManagerState_DetectRuntimes(); + break; + case VRManagerState::Enumeration: + ProcessManagerState_Enumeration(); + break; + case VRManagerState::Active: + ProcessManagerState_Active(); + break; + case VRManagerState::Stopping: + ProcessManagerState_Stopping(); + break; + } + CheckForInactiveTimeout(); + CheckForShutdown(); +} + +void VRManager::ProcessManagerState_Disabled() { + MOZ_ASSERT(mState == VRManagerState::Disabled); + if (!StaticPrefs::dom_vr_enabled()) { return; } - if (mState == VRManagerState::Disabled) { + if (mRuntimeDetectionRequested || mEnumerationRequested || + mVRDisplaysRequested) { StartTasks(); mState = VRManagerState::Idle; } - - if (mState == VRManagerState::Idle) { - /** - * Throttle the rate of enumeration to the interval set in - * VRDisplayEnumerateInterval - */ - if (!mLastDisplayEnumerationTime.IsNull()) { - TimeDuration duration = TimeStamp::Now() - mLastDisplayEnumerationTime; - if (duration.ToMilliseconds() < - StaticPrefs::dom_vr_display_enumerate_interval()) { - return; - } - } - - if (!mEarliestRestartTime.IsNull() && - mEarliestRestartTime > TimeStamp::Now()) { - // When the VR Service shuts down it informs us of how long we - // must wait until we can re-start it. - // We must wait until mEarliestRestartTime before attempting - // to enumerate again. - return; - } - - /** - * If we get this far, don't try again until - * the VRDisplayEnumerateInterval elapses - */ - mLastDisplayEnumerationTime = TimeStamp::Now(); - - OpenShmem(); - - /** - * We must start the VR Service thread - * and VR Process before enumeration. - * We don't want to start this until we will - * actualy enumerate, to avoid continuously - * re-launching the thread/process when - * no hardware is found or a VR software update - * is in progress - */ -#if !defined(MOZ_WIDGET_ANDROID) - mServiceHost->StartService(); -#endif - if (mShmem) { - mDisplayInfo.Clear(); - mLastUpdateDisplayInfo.Clear(); - mFrameStarted = false; - mBrowserState.Clear(); - mLastSensorState.Clear(); - mEnumerationCompleted = false; - mDisplayInfo.mGroupMask = kVRGroupContent; - // We must block until enumeration has completed in order - // to signal that the WebVR promise should be resolved at the - // right time. -#if defined(MOZ_WIDGET_ANDROID) - // In Android, we need to make sure calling - // GeckoVRManager::SetExternalContext() from an external VR service - // before doing enumeration. - if (!mShmem->GetExternalShmem()) { - mShmem->CreateShMemForAndroid(); - } - if (mShmem->GetExternalShmem()) { - mState = VRManagerState::Enumeration; - } -#else - mState = VRManagerState::Enumeration; -#endif // MOZ_WIDGET_ANDROID - } - } // if (mState == VRManagerState::Idle) - - if (mState == VRManagerState::Enumeration) { - MOZ_ASSERT(mShmem != nullptr); - - PullState(); - if (mEnumerationCompleted) { - if (mDisplayInfo.mDisplayState.isConnected) { - mDisplayInfo.mDisplayID = VRManager::AllocateDisplayID(); - mState = VRManagerState::Active; - } else { - Shutdown(); - } - } - } // if (mState == VRManagerState::Enumeration) } -void VRManager::RefreshVRDisplays(bool aMustDispatch) { - uint32_t previousDisplayID = mDisplayInfo.GetDisplayID(); - +void VRManager::ProcessManagerState_Stopping() { + MOZ_ASSERT(mState == VRManagerState::Stopping); + PullState(); /** - * If we aren't viewing WebVR content, don't enumerate - * new hardware, as it will cause some devices to power on - * or interrupt other VR activities. + * In the case of Desktop, the VRService shuts itself down. + * Before it's finished stopping, it sets a flag in the ShMem + * to let VRManager know that it's done. VRManager watches for + * this flag and transitions out of the VRManagerState::Stopping + * state to VRManagerState::Idle. */ - if (mVRDisplaysRequested || aMustDispatch) { - EnumerateVRDisplays(); - } - if (mState == VRManagerState::Enumeration) { - // If we are enumerating VR Displays, do not dispatch - // updates until the enumeration has completed. +#if defined(MOZ_WIDGET_ANDROID) + // On Android, the VR service never actually shuts + // down or requests VRManager to stop. + Shutdown(); +#endif // defined(MOZ_WIDGET_ANDROID) +} + +void VRManager::ProcessManagerState_Idle_StartEnumeration() { + MOZ_ASSERT(mState == VRManagerState::Idle); + + if (!mEarliestRestartTime.IsNull() && + mEarliestRestartTime > TimeStamp::Now()) { + // When the VR Service shuts down it informs us of how long we + // must wait until we can re-start it. + // We must wait until mEarliestRestartTime before attempting + // to enumerate again. return; } - bool changed = false; - if (previousDisplayID != mDisplayInfo.GetDisplayID()) { - changed = true; + /** + * Throttle the rate of enumeration to the interval set in + * VRDisplayEnumerateInterval + */ + if (!mLastDisplayEnumerationTime.IsNull()) { + TimeDuration duration = TimeStamp::Now() - mLastDisplayEnumerationTime; + if (duration.ToMilliseconds() < + StaticPrefs::dom_vr_display_enumerate_interval()) { + return; + } } - if (mState == VRManagerState::Active && - mDisplayInfo != mLastUpdateDisplayInfo) { - // This display's info has changed - changed = true; + /** + * If we get this far, don't try again until + * the VRDisplayEnumerateInterval elapses + */ + mLastDisplayEnumerationTime = TimeStamp::Now(); + + OpenShmem(); + + mEnumerationRequested = false; + // We must block until enumeration has completed in order + // to signal that the WebVR promise should be resolved at the + // right time. +#if defined(MOZ_WIDGET_ANDROID) + // In Android, we need to make sure calling + // GeckoVRManager::SetExternalContext() from an external VR service + // before doing enumeration. + if (!mShmem->GetExternalShmem()) { + mShmem->CreateShMemForAndroid(); + } + if (mShmem->GetExternalShmem()) { + mState = VRManagerState::Enumeration; + } else { + // Not connected to shmem, so no devices to enumerate. + mDisplayInfo.Clear(); + DispatchVRDisplayInfoUpdate(); + } +#else + + PushState(); + + /** + * We must start the VR Service thread + * and VR Process before enumeration. + * We don't want to start this until we will + * actualy enumerate, to avoid continuously + * re-launching the thread/process when + * no hardware is found or a VR software update + * is in progress + */ + mServiceHost->StartService(); + mState = VRManagerState::Enumeration; +#endif // MOZ_WIDGET_ANDROID +} + +void VRManager::ProcessManagerState_Idle_StartRuntimeDetection() { + MOZ_ASSERT(mState == VRManagerState::Idle); + + OpenShmem(); + mBrowserState.detectRuntimesOnly = true; + mRuntimeDetectionRequested = false; + + // We must block until enumeration has completed in order + // to signal that the WebVR promise should be resolved at the + // right time. +#if defined(MOZ_WIDGET_ANDROID) + // In Android, we need to make sure calling + // GeckoVRManager::SetExternalContext() from an external VR service + // before doing enumeration. + if (!mShmem->GetExternalShmem()) { + mShmem->CreateShMemForAndroid(); + } + if (mShmem->GetExternalShmem()) { + mState = VRManagerState::RuntimeDetection; + } else { + // Not connected to shmem, so no runtimes to detect. + mRuntimeSupportFlags = VRDisplayCapabilityFlags::Cap_None; + mRuntimeDetectionCompleted = true; + DispatchRuntimeCapabilitiesUpdate(); + } +#else + + PushState(); + + /** + * We must start the VR Service thread + * and VR Process before enumeration. + * We don't want to start this until we will + * actualy enumerate, to avoid continuously + * re-launching the thread/process when + * no hardware is found or a VR software update + * is in progress + */ + mServiceHost->StartService(); + mState = VRManagerState::RuntimeDetection; +#endif // MOZ_WIDGET_ANDROID +} + +void VRManager::ProcessManagerState_Idle() { + MOZ_ASSERT(mState == VRManagerState::Idle); + + if (!mRuntimeDetectionCompleted) { + // Check if we should start detecting runtimes + // We must alwasy detect runtimes before doing anything + // else with the VR process. + // This will happen only once per browser startup. + if (mRuntimeDetectionRequested || mEnumerationRequested) { + ProcessManagerState_Idle_StartRuntimeDetection(); + } + return; } - if (changed || aMustDispatch) { + // Check if we should start activating enumerating XR hardware + if (mRuntimeDetectionCompleted && + (mVRDisplaysRequested || mEnumerationRequested)) { + ProcessManagerState_Idle_StartEnumeration(); + } +} + +void VRManager::ProcessManagerState_DetectRuntimes() { + MOZ_ASSERT(mState == VRManagerState::RuntimeDetection); + MOZ_ASSERT(mShmem != nullptr); + + PullState(); + if (mEnumerationCompleted) { + /** + * When mBrowserState.detectRuntimesOnly is set, the + * VRService and VR process will shut themselves down + * automatically after detecting runtimes. + * mEnumerationCompleted is also used in this case, + * but to mean "enumeration of runtimes" not + * "enumeration of VR devices". + * + * We set mState to `VRManagerState::Stopping` + * to make sure that we don't try to do anything + * else with the active VRService until it has stopped. + * We must start another one when an XR session will be + * requested. + * + * This logic is optimized for the WebXR design, but still + * works for WebVR so it can continue to function until + * deprecated and removed. + */ + mState = VRManagerState::Stopping; + mRuntimeSupportFlags = mDisplayInfo.mDisplayState.capabilityFlags & + (VRDisplayCapabilityFlags::Cap_ImmersiveVR | + VRDisplayCapabilityFlags::Cap_ImmersiveAR); + mRuntimeDetectionCompleted = true; + DispatchRuntimeCapabilitiesUpdate(); + } +} + +void VRManager::ProcessManagerState_Enumeration() { + MOZ_ASSERT(mState == VRManagerState::Enumeration); + MOZ_ASSERT(mShmem != nullptr); + + PullState(); + if (mEnumerationCompleted) { + if (mDisplayInfo.mDisplayState.isConnected) { + mDisplayInfo.mDisplayID = VRManager::AllocateDisplayID(); + mState = VRManagerState::Active; + } else { + mDisplayInfo.Clear(); + mState = VRManagerState::Stopping; + } + DispatchVRDisplayInfoUpdate(); + } +} + +void VRManager::ProcessManagerState_Active() { + MOZ_ASSERT(mState == VRManagerState::Active); + + if (mDisplayInfo != mLastUpdateDisplayInfo) { + // While the display is active, send continuous updates DispatchVRDisplayInfoUpdate(); } } void VRManager::DispatchVRDisplayInfoUpdate() { - // This could be simplified further by only supporting one display - nsTArray displayUpdates; - if (mState == VRManagerState::Active) { - MOZ_ASSERT(mDisplayInfo.mDisplayID != 0); - displayUpdates.AppendElement(mDisplayInfo); - } for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) { - Unused << iter.Get()->GetKey()->SendUpdateDisplayInfo(displayUpdates); + Unused << iter.Get()->GetKey()->SendUpdateDisplayInfo(mDisplayInfo); } mLastUpdateDisplayInfo = mDisplayInfo; } +void VRManager::DispatchRuntimeCapabilitiesUpdate() { + VRDisplayCapabilityFlags flags = mRuntimeSupportFlags; + if (StaticPrefs::dom_vr_always_support_vr()) { + flags |= VRDisplayCapabilityFlags::Cap_ImmersiveVR; + } + + if (StaticPrefs::dom_vr_always_support_ar()) { + flags |= VRDisplayCapabilityFlags::Cap_ImmersiveAR; + } + + for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) { + Unused << iter.Get()->GetKey()->SendUpdateRuntimeCapabilities(flags); + } +} + void VRManager::StopAllHaptics() { for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) { ClearHapticSlot(i); @@ -898,11 +1082,31 @@ void VRManager::Shutdown() { StopPresentation(); CancelCurrentSubmitTask(); ShutdownSubmitThread(); + mDisplayInfo.Clear(); mEnumerationCompleted = false; + if (mState == VRManagerState::RuntimeDetection) { + /** + * We have failed to detect runtimes before shutting down. + * Ensure that promises are resolved + * + * This call to DispatchRuntimeCapabilitiesUpdate will only + * happen when we have failed to detect runtimes. In that case, + * mRuntimeSupportFlags will be 0 and send the correct message + * to the content process. + * + * When we are successful, we store the result in mRuntimeSupportFlags + * and never try again unless the browser is restarted. mRuntimeSupportFlags + * is never reset back to 0 in that case but we will never re-enter the + * VRManagerState::RuntimeDetection state and hit this code path again. + */ + DispatchRuntimeCapabilitiesUpdate(); + } + if (mState == VRManagerState::Enumeration) { - // Ensure that enumeration promises are resolved + // We have failed to enumerate VR devices before shutting down. + // Ensure that promises are resolved DispatchVRDisplayInfoUpdate(); } diff --git a/gfx/vr/VRManager.h b/gfx/vr/VRManager.h index 74f5ff32ca61..b88ce9476a70 100644 --- a/gfx/vr/VRManager.h +++ b/gfx/vr/VRManager.h @@ -26,8 +26,11 @@ class VRShMem; enum class VRManagerState : uint32_t { Disabled, // All VRManager activity is stopped Idle, // No VR hardware has been activated, but background tasks are running - Enumeration, // Waiting for enumeration and startup of VR hardware - Active // VR hardware is active + RuntimeDetection, // Waiting for detection of runtimes without starting up VR + // hardware + Enumeration, // Waiting for enumeration and startup of VR hardware + Active, // VR hardware is active + Stopping, // Waiting for the VRService to stop }; class VRManager : nsIObserver { @@ -43,7 +46,8 @@ class VRManager : nsIObserver { void NotifyVsync(const TimeStamp& aVsyncTimestamp); - void RefreshVRDisplays(bool aMustDispatch = false); + void DetectRuntimes(); + void EnumerateDevices(); void StopAllHaptics(); void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex, @@ -81,14 +85,22 @@ class VRManager : nsIObserver { void Run10msTasks(); void Run100msTasks(); uint32_t GetOptimalTaskInterval(); + void ProcessManagerState(); + void ProcessManagerState_Disabled(); + void ProcessManagerState_Idle(); + void ProcessManagerState_Idle_StartRuntimeDetection(); + void ProcessManagerState_Idle_StartEnumeration(); + void ProcessManagerState_DetectRuntimes(); + void ProcessManagerState_Enumeration(); + void ProcessManagerState_Active(); + void ProcessManagerState_Stopping(); void PullState(const std::function& aWaitCondition = nullptr); void PushState(const bool aNotifyCond = false); void ProcessTelemetryEvent(); static uint32_t AllocateDisplayID(); - void DispatchVRDisplayInfoUpdate(); + void DispatchRuntimeCapabilitiesUpdate(); void UpdateRequestedDevices(); - void EnumerateVRDisplays(); void CheckForInactiveTimeout(); #if !defined(MOZ_WIDGET_ANDROID) void CheckForPuppetCompletion(); @@ -131,6 +143,10 @@ class VRManager : nsIObserver { TimeStamp mVRNavigationTransitionEnd; TimeStamp mLastFrameStart[kVRMaxLatencyFrames]; double mAccumulator100ms; + bool mRuntimeDetectionRequested; + bool mRuntimeDetectionCompleted; + bool mEnumerationRequested; + bool mEnumerationCompleted; bool mVRDisplaysRequested; bool mVRDisplaysRequestedNonFocus; bool mVRControllersRequested; @@ -141,7 +157,7 @@ class VRManager : nsIObserver { RefPtr mCurrentSubmitTask; uint64_t mLastSubmittedFrameId; uint64_t mLastStartedFrame; - bool mEnumerationCompleted; + VRDisplayCapabilityFlags mRuntimeSupportFlags; bool mAppPaused; // Note: mShmem doesn't support RefPtr; thus, do not share this private diff --git a/gfx/vr/ipc/PVRManager.ipdl b/gfx/vr/ipc/PVRManager.ipdl index 8b27e570a050..8ff75c9bce95 100644 --- a/gfx/vr/ipc/PVRManager.ipdl +++ b/gfx/vr/ipc/PVRManager.ipdl @@ -18,6 +18,7 @@ using struct mozilla::gfx::VRSensorUpdate from "gfxVR.h"; using struct mozilla::gfx::VRHMDSensorState from "gfxVR.h"; using struct mozilla::gfx::VRControllerInfo from "gfxVR.h"; using struct mozilla::gfx::VRSubmitFrameResultInfo from "gfxVR.h"; +using mozilla::gfx::VRDisplayCapabilityFlags from "moz_external_vr.h"; using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h"; using mozilla::layers::TextureFlags from "mozilla/layers/CompositorTypes.h"; @@ -37,6 +38,10 @@ sync protocol PVRManager parent: async PVRLayer(uint32_t aDisplayID, uint32_t aGroup); + // Detect runtime capabilities. This will return the presense of VR and/or AR + // runtime software, without enumerating or activating any hardware devices. + async DetectRuntimes(); + // (Re)Enumerate VR Displays. An updated list of VR displays will be returned // asynchronously to children via UpdateDisplayInfo. async RefreshDisplays(); @@ -62,7 +67,9 @@ child: // be sent to all children when the parent receives RefreshDisplays, even // if no changes have been detected. This ensures that Promises exposed // through DOM calls are always resolved. - async UpdateDisplayInfo(VRDisplayInfo[] aDisplayUpdates); + async UpdateDisplayInfo(VRDisplayInfo aDisplayInfo); + + async UpdateRuntimeCapabilities(VRDisplayCapabilityFlags aCapabilities); async ReplyGamepadVibrateHaptic(uint32_t aPromiseID); async NotifyPuppetCommandBufferCompleted(bool aSuccess); diff --git a/gfx/vr/ipc/VRManagerChild.cpp b/gfx/vr/ipc/VRManagerChild.cpp index f2207a5a2352..c9762ddb356b 100644 --- a/gfx/vr/ipc/VRManagerChild.cpp +++ b/gfx/vr/ipc/VRManagerChild.cpp @@ -35,7 +35,7 @@ static StaticRefPtr sVRManagerParentSingleton; void ReleaseVRManagerParentSingleton() { sVRManagerParentSingleton = nullptr; } VRManagerChild::VRManagerChild() - : mDisplaysInitialized(false), + : mRuntimeCapabilities(VRDisplayCapabilityFlags::Cap_None), mMessageLoop(MessageLoop::current()), mFrameRequestCallbackCounter(0), mWaitingForEnumeration(false), @@ -165,8 +165,7 @@ bool VRManagerChild::DeallocPVRLayerChild(PVRLayerChild* actor) { return VRLayerChild::DestroyIPDLActor(actor); } -void VRManagerChild::UpdateDisplayInfo( - nsTArray& aDisplayUpdates) { +void VRManagerChild::UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo) { nsTArray disconnectedDisplays; nsTArray connectedDisplays; @@ -176,9 +175,9 @@ void VRManagerChild::UpdateDisplayInfo( // Check if any displays have been disconnected for (auto& display : prevDisplays) { bool found = false; - for (auto& displayUpdate : aDisplayUpdates) { + if (aDisplayInfo.GetDisplayID() != 0) { if (display->GetDisplayInfo().GetDisplayID() == - displayUpdate.GetDisplayID()) { + aDisplayInfo.GetDisplayID()) { found = true; break; } @@ -204,26 +203,26 @@ void VRManagerChild::UpdateDisplayInfo( // mDisplays could be a hashed container for more scalability, but not worth // it now as we expect < 10 entries. nsTArray> displays; - for (VRDisplayInfo& displayUpdate : aDisplayUpdates) { + if (aDisplayInfo.GetDisplayID() != 0) { bool isNewDisplay = true; for (auto& display : prevDisplays) { const VRDisplayInfo& prevInfo = display->GetDisplayInfo(); - if (prevInfo.GetDisplayID() == displayUpdate.GetDisplayID()) { - if (displayUpdate.GetIsConnected() && !prevInfo.GetIsConnected()) { - connectedDisplays.AppendElement(displayUpdate.GetDisplayID()); + if (prevInfo.GetDisplayID() == aDisplayInfo.GetDisplayID()) { + if (aDisplayInfo.GetIsConnected() && !prevInfo.GetIsConnected()) { + connectedDisplays.AppendElement(aDisplayInfo.GetDisplayID()); } - if (!displayUpdate.GetIsConnected() && prevInfo.GetIsConnected()) { - disconnectedDisplays.AppendElement(displayUpdate.GetDisplayID()); + if (!aDisplayInfo.GetIsConnected() && prevInfo.GetIsConnected()) { + disconnectedDisplays.AppendElement(aDisplayInfo.GetDisplayID()); } - display->UpdateDisplayInfo(displayUpdate); + display->UpdateDisplayInfo(aDisplayInfo); displays.AppendElement(display); isNewDisplay = false; break; } } if (isNewDisplay) { - displays.AppendElement(new VRDisplayClient(displayUpdate)); - connectedDisplays.AppendElement(displayUpdate.GetDisplayID()); + displays.AppendElement(new VRDisplayClient(aDisplayInfo)); + connectedDisplays.AppendElement(aDisplayInfo.GetDisplayID()); } } @@ -237,13 +236,35 @@ void VRManagerChild::UpdateDisplayInfo( for (uint32_t displayID : connectedDisplays) { FireDOMVRDisplayConnectEvent(displayID); } +} - mDisplaysInitialized = true; +bool VRManagerChild::RuntimeSupportsVR() const { + return bool(mRuntimeCapabilities & VRDisplayCapabilityFlags::Cap_ImmersiveVR); +} +bool VRManagerChild::RuntimeSupportsAR() const { + return bool(mRuntimeCapabilities & VRDisplayCapabilityFlags::Cap_ImmersiveAR); +} + +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() { + nsTArray> listeners; + listeners = mListeners; + for (auto& listener : listeners) { + listener->NotifyDetectRuntimesCompleted(); + } } mozilla::ipc::IPCResult VRManagerChild::RecvUpdateDisplayInfo( - nsTArray&& aDisplayUpdates) { - UpdateDisplayInfo(aDisplayUpdates); + const VRDisplayInfo& aDisplayInfo) { + UpdateDisplayInfo(aDisplayInfo); for (auto& windowId : mNavigatorCallbacks) { /** We must call NotifyVRDisplaysUpdated for every * window's Navigator in mNavigatorCallbacks to ensure that @@ -339,6 +360,8 @@ bool VRManagerChild::EnumerateVRDisplays() { return success; } +void VRManagerChild::DetectRuntimes() { Unused << SendDetectRuntimes(); } + PVRLayerChild* VRManagerChild::CreateVRLayer(uint32_t aDisplayID, nsIEventTarget* aTarget, uint32_t aGroup) { diff --git a/gfx/vr/ipc/VRManagerChild.h b/gfx/vr/ipc/VRManagerChild.h index ab0d41a88fef..4c13f2cb6307 100644 --- a/gfx/vr/ipc/VRManagerChild.h +++ b/gfx/vr/ipc/VRManagerChild.h @@ -41,6 +41,7 @@ class VRManagerEventObserver { virtual void NotifyPresentationGenerationChanged(uint32_t aDisplayID) = 0; virtual bool GetStopActivityStatus() const = 0; virtual void NotifyEnumerationCompleted() = 0; + virtual void NotifyDetectRuntimesCompleted() = 0; protected: virtual ~VRManagerEventObserver() = default; @@ -60,10 +61,13 @@ class VRManagerChild : public PVRManagerChild { void RemoveListener(VRManagerEventObserver* aObserver); void StartActivity(); void StopActivity(); + bool RuntimeSupportsVR() const; + bool RuntimeSupportsAR() const; void GetVRDisplays(nsTArray>& aDisplays); bool RefreshVRDisplaysWithCallback(uint64_t aWindowId); bool EnumerateVRDisplays(); + void DetectRuntimes(); void AddPromise(const uint32_t& aID, dom::Promise* aPromise); static void InitSameProcess(); @@ -91,7 +95,7 @@ class VRManagerChild : public PVRManagerChild { void NotifyPresentationGenerationChanged(uint32_t aDisplayID); MOZ_CAN_RUN_SCRIPT - void UpdateDisplayInfo(nsTArray& aDisplayUpdates); + void UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo); void FireDOMVRDisplayMountedEvent(uint32_t aDisplayID); void FireDOMVRDisplayUnmountedEvent(uint32_t aDisplayID); void FireDOMVRDisplayConnectEvent(uint32_t aDisplayID); @@ -119,7 +123,9 @@ class VRManagerChild : public PVRManagerChild { // MOZ_CAN_RUN_SCRIPT. MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvUpdateDisplayInfo( - nsTArray&& aDisplayUpdates); + const VRDisplayInfo& aDisplayInfo); + mozilla::ipc::IPCResult RecvUpdateRuntimeCapabilities( + const VRDisplayCapabilityFlags& aCapabilities); mozilla::ipc::IPCResult RecvReplyGamepadVibrateHaptic( const uint32_t& aPromiseID); @@ -140,8 +146,10 @@ class VRManagerChild : public PVRManagerChild { uint32_t aDisplayID, VRManagerEventObserver* aObserver); void NotifyPresentationGenerationChangedInternal(uint32_t aDisplayID); void NotifyEnumerationCompletedInternal(); + void NotifyRuntimeCapabilitiesUpdatedInternal(); nsTArray> mDisplays; + VRDisplayCapabilityFlags mRuntimeCapabilities; bool mDisplaysInitialized; nsTArray mNavigatorCallbacks; diff --git a/gfx/vr/ipc/VRManagerParent.cpp b/gfx/vr/ipc/VRManagerParent.cpp index fdb7a905a279..aaae15c3c4ba 100644 --- a/gfx/vr/ipc/VRManagerParent.cpp +++ b/gfx/vr/ipc/VRManagerParent.cpp @@ -24,7 +24,7 @@ VRManagerParent::VRManagerParent(ProcessId aChildProcessId, : mHaveEventListener(false), mHaveControllerListener(false), mIsContentChild(aIsContentChild), - mVRActiveStatus(true) { + mVRActiveStatus(false) { MOZ_COUNT_CTOR(VRManagerParent); MOZ_ASSERT(NS_IsMainThread()); @@ -140,13 +140,24 @@ void VRManagerParent::OnChannelConnected(int32_t aPid) { mCompositorThreadHolder = CompositorThreadHolder::GetSingleton(); } -mozilla::ipc::IPCResult VRManagerParent::RecvRefreshDisplays() { - // This is called to refresh the VR Displays for Navigator.GetVRDevices(). - // We must pass "true" to VRManager::RefreshVRDisplays() - // to ensure that the promise returned by Navigator.GetVRDevices - // can resolve even if there are no changes to the VR Displays. +mozilla::ipc::IPCResult VRManagerParent::RecvDetectRuntimes() { + // Detect runtime capabilities. This will return the presense of VR and/or AR + // runtime software, without enumerating or activating any hardware devices. + // UpdateDisplayInfo will be sent to VRManagerChild with the results of the + // detection. VRManager* vm = VRManager::Get(); - vm->RefreshVRDisplays(true); + vm->DetectRuntimes(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvRefreshDisplays() { + // This is called to activate the VR runtimes, detecting the + // presence and capabilities of XR hardware. + // UpdateDisplayInfo will be sent to VRManagerChild with the results of the + // enumerated hardware. + VRManager* vm = VRManager::Get(); + vm->EnumerateDevices(); return IPC_OK(); } diff --git a/gfx/vr/ipc/VRManagerParent.h b/gfx/vr/ipc/VRManagerParent.h index c0d31b81a206..b7fc058919db 100644 --- a/gfx/vr/ipc/VRManagerParent.h +++ b/gfx/vr/ipc/VRManagerParent.h @@ -49,6 +49,7 @@ class VRManagerParent final : public PVRManagerParent { virtual void ActorDestroy(ActorDestroyReason why) override; void OnChannelConnected(int32_t pid) override; + mozilla::ipc::IPCResult RecvDetectRuntimes(); mozilla::ipc::IPCResult RecvRefreshDisplays(); mozilla::ipc::IPCResult RecvSetGroupMask(const uint32_t& aDisplayID, const uint32_t& aGroupMask); diff --git a/gfx/vr/ipc/VRMessageUtils.h b/gfx/vr/ipc/VRMessageUtils.h index a193b3f44ba1..0625568da883 100644 --- a/gfx/vr/ipc/VRMessageUtils.h +++ b/gfx/vr/ipc/VRMessageUtils.h @@ -64,6 +64,12 @@ struct ParamTraits { } }; +template <> +struct ParamTraits + : public BitFlagsEnumSerializer< + mozilla::gfx::VRDisplayCapabilityFlags, + mozilla::gfx::VRDisplayCapabilityFlags::Cap_All> {}; + } // namespace IPC #endif // mozilla_gfx_vr_VRMessageUtils_h diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 7175f33f3ab1..65ffa599273b 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -2451,6 +2451,26 @@ #endif mirror: always +# Should VR sessions always be reported as supported, without first +# checking for VR runtimes? This will prevent permission prompts +# from being suppressed on machines without VR runtimes and cause +# navigatior.xr.isSessionSupported to always report that immersive-vr +# is supported. +- name: dom.vr.always_support_vr + type: RelaxedAtomicBool + value: false + mirror: always + +# Should AR sessions always be reported as supported, without first +# checking for AR runtimes? This will prevent permission prompts +# from being suppressed on machines without AR runtimes and cause +# navigatior.xr.isSessionSupported to always report that immersive-ar +# is supported. +- name: dom.vr.always_support_ar + type: RelaxedAtomicBool + value: false + mirror: always + # It is often desirable to automatically start vr presentation when # a user puts on the VR headset. This is done by emitting the # Window.vrdisplayactivate event when the headset's sensors detect it diff --git a/testing/crashtest/crashtests.list b/testing/crashtest/crashtests.list index 5869350bf4e6..66a3c86e23cc 100644 --- a/testing/crashtest/crashtests.list +++ b/testing/crashtest/crashtests.list @@ -28,6 +28,7 @@ include ../../dom/plugins/test/crashtests/crashtests.list include ../../dom/security/test/crashtests/crashtests.list include ../../dom/smil/crashtests/crashtests.list include ../../dom/svg/crashtests/crashtests.list +include ../../dom/vr/test/crashtests/crashtests.list include ../../dom/workers/test/crashtests/crashtests.list include ../../dom/xhr/tests/crashtests/crashtests.list include ../../dom/xml/crashtests/crashtests.list