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
This commit is contained in:
Kearwood "Kip" Gilbert 2020-01-03 22:47:26 +00:00
parent eb1ba6eb92
commit b3787eec77
21 changed files with 606 additions and 183 deletions

View File

@ -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],
],
});

View File

@ -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],
],
});
});

View File

@ -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],
],
});
});

View File

@ -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);

View File

@ -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<XRPermissionRequest> 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 {

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -0,0 +1 @@
pref(dom.vr.always_support_vr,true) load enumerate_vr_on_dying_window.html

View File

@ -0,0 +1,14 @@
<html>
<head>
<script>
window.onload = function(){
var frame = document.getElementById('test_iframe');
var win = frame.contentWindow;
frame.remove();
win.onvrdisplayactivate = function () {}
};
</script></head>
<body>
<iframe id="test_iframe"></iframe>
</body>
</html>

View File

@ -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")

View File

@ -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<VRDisplayInfo> 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();
}

View File

@ -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<bool()>& 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<CancelableRunnable> 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

View File

@ -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);

View File

@ -35,7 +35,7 @@ static StaticRefPtr<VRManagerParent> 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<VRDisplayInfo>& aDisplayUpdates) {
void VRManagerChild::UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo) {
nsTArray<uint32_t> disconnectedDisplays;
nsTArray<uint32_t> 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<RefPtr<VRDisplayClient>> 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<RefPtr<VRManagerEventObserver>> listeners;
listeners = mListeners;
for (auto& listener : listeners) {
listener->NotifyDetectRuntimesCompleted();
}
}
mozilla::ipc::IPCResult VRManagerChild::RecvUpdateDisplayInfo(
nsTArray<VRDisplayInfo>&& 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) {

View File

@ -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<RefPtr<VRDisplayClient>>& 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<VRDisplayInfo>& 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<VRDisplayInfo>&& 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<RefPtr<VRDisplayClient>> mDisplays;
VRDisplayCapabilityFlags mRuntimeCapabilities;
bool mDisplaysInitialized;
nsTArray<uint64_t> mNavigatorCallbacks;

View File

@ -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();
}

View File

@ -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);

View File

@ -64,6 +64,12 @@ struct ParamTraits<mozilla::gfx::VRSubmitFrameResultInfo> {
}
};
template <>
struct ParamTraits<mozilla::gfx::VRDisplayCapabilityFlags>
: public BitFlagsEnumSerializer<
mozilla::gfx::VRDisplayCapabilityFlags,
mozilla::gfx::VRDisplayCapabilityFlags::Cap_All> {};
} // namespace IPC
#endif // mozilla_gfx_vr_VRMessageUtils_h

View File

@ -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

View File

@ -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