1. Introduction
In order to allow Web Platform Tests for WebXR there are some basic functions which are common across all tests, such as adding a fake test device and specifying poses. Below is an API which attempts to capture the necessary functions, based off what was defined in the spec. Different browser vendors can implement this API in whatever way is most compatible with their browser. For example, some browsers may back the interface with a WebDriver API while others may use HTTP or IPC mechanisms to communicate with an out of process fake backend.
These initialization object and control interfaces do not represent a complete set of WebXR functionality, and are expected to be expanded on as the WebXR spec grows.
2. Conformance
Interfaces and functionality exposed by this specification SHOULD NOT be exposed to typical browsing experiences, and instead SHOULD only be used when running Web Platform Tests.
3. Simulated devices
This API gives tests the ability to spin up a simulated XR device which is an XR device which from the point of view of the WebXR API behaves like a normal XR device. These simulated XR devices can be controlled by the associated FakeXRDevice
object.
Every simulated XR device may have an native bounds geometry which is an array of DOMPointReadOnly
s, used to initialize the native bounds geometry of any XRBoundedReferenceSpace
s created for the device. If null
, the device is treated as if it is not currently tracking a bounded reference space.
Every simulated XR device may have a floor origin which is an XRRigidTransform
used to note the position of the physical floor. If null
, the device is treated as if it is unable to identify the physical floor.
Every simulated XR device may have an viewer origin which is an XRRigidTransform
used to set the position and orientation of the viewer. If null
, the device is treated as if it has lost tracking.
Every simulated XR device has an emulated position boolean which is a boolean used to set the emulatedPosition
of any XRPose
s produced involving the viewer. This is initially false
.
Every simulated XR device has an visibility state which is an XRVisibilityState
used to set the visibilityState
of any XRSession
s associated with the simulated XR device . This is initially "visible"
. When it is changed, the associated changes must be reflected on the XRSession
, including triggering onvisibilitychange
events if necessary.
Every view for a simulated XR device has an associated device resolution, which is an instance of FakeXRDeviceResolution
. This resolution must be used when constructing XRViewport
values for the view, based on the canvas size.
Every view for a simulated XR device may have an associated field of view, which is an instance of FakeXRFieldOfViewInit
used to calculate projection matrices using depth values. If the field of view is set, projection matrix values are calculated using the field of view and depthNear
and depthFar
values.
The WebXR API never exposes native origins directly, instead exposing transforms between them, so we need to specify a base reference space for FakeXRRigidTransformInit
s so that we can have consistent numerical values across implementations. When used as an origin, FakeXRRigidTransformInit
s are in the base reference space where the viewer's native origin is identity at initialization, unless otherwise specified. In this space, the "local"
reference space has a native origin of identity. This is an arbitrary choice: changing this reference space doesn’t affect the data returned by the WebXR API, but we must make such a choice so that the tests produce the same results across different UAs. When used as an origin it is logically a transform from the origin’s space to the underlying base reference space described above.
4. Initialization
4.1. navigator.xr.test
partial interface XR { [SameObject ]readonly attribute XRTest test ; };
The test
attribute’s getter MUST return the XRTest
object that is associated with it. This object MAY be lazily created.
4.2. XRTest
The XRTest
object is the entry point for all testing.
interface {
XRTest Promise <FakeXRDevice >simulateDeviceConnection (FakeXRDeviceInit );
init void simulateUserActivation (Function );
f Promise <void >disconnectAllDevices (); };
simulateDeviceConnection(init)
method creates a new simulated XR device.
When this method is invoked, the user agent MUST run the following steps:
-
Let promise be a new Promise.
-
Run the following steps in parallel:
-
Let device be a new simulated XR device.
-
For each view in init’s
views
:-
Let v be the result of running parse a view on view.
-
If running parse a view threw an error, reject promise with this error and abort these steps.
-
Append v to device’s list of views.
-
-
If init’s
supportedFeatures
is set, set device’s list of features it is capable of supporting to init’ssupportedFeatures
. -
If init’s
boundsCoordinates
is set, perform the following steps:-
If init’s
boundsCoordinates
has less than 3 elements, reject promise withTypeError
and abort these steps. -
Set device’s native bounds geometry to init’s
boundsCoordinates
.
-
-
If init’s
floorOrigin
is set, set device’s floor origin to init’sfloorOrigin
. -
If init’s
viewerOrigin
is set, set device’s viewer origin to init’sviewerOrigin
. -
Let supportedModes be an empty list of
XRSessionMode
s. -
Modify supportedModes as follows:
- If init’s
supportedModes
is present: -
-
Append the contents of init’s
supportedModes
to supportedModes. -
If supportedModes is empty, append
"inline"
to it.
-
- Else
-
-
Append
"inline"
to supportedModes. -
If init’s
supportsImmersive
istrue
, append"immersive-vr"
to supportedModes.
-
- If init’s
-
Set device’s list of supported modes to supportedModes.
-
Register device based on the following:
-
If supportedModes contains
"immersive-vr"
or"immersive-ar"
, append device to thexr
's list of immersive XR devices. -
If supportedModes contains
"inline"
, set the inline XR device to device.
-
-
Let d be a new
FakeXRDevice
object with device as device. -
Resolve promise with d.
-
-
Return promise.
When simulateUserActivation(f)
is called, invoke f
as if it was triggered by user activation.
When disconnectAllDevices()
is called, remove all simulated XR devices from the context object's XR
object’s list of immersive XR devices as if they were disconnected. If the inline XR device is a simulated XR device, reset it to the default inline XR device.
4.3. FakeXRDeviceInit
dictionary {
FakeXRDeviceInit required boolean ;
supportsImmersive sequence <XRSessionMode >;
supportedModes required sequence <FakeXRViewInit >;
views sequence <any >;
supportedFeatures sequence <FakeXRBoundsPoint >;
boundsCoordinates FakeXRRigidTransformInit ;
floorOrigin FakeXRRigidTransformInit ; // Hit test extensions:
viewerOrigin FakeXRWorldInit ; };
world dictionary {
FakeXRViewInit required XREye ;
eye required sequence <float >;
projectionMatrix required FakeXRDeviceResolution ;
resolution required FakeXRRigidTransformInit ;
viewOffset FakeXRFieldOfViewInit ; };
fieldOfView dictionary {
FakeXRFieldOfViewInit required float ;
upDegrees required float ;
downDegrees required float ;
leftDegrees required float ; };
rightDegrees dictionary {
FakeXRDeviceResolution required long ;
width required long ; };
height dictionary {
FakeXRBoundsPoint double ;
x double ; };
z dictionary {
FakeXRRigidTransformInit required sequence <float >;
position required sequence <float >; };
orientation
The supportsImmersive
is deprecated in favor of supportedModes
and will be removed in future revisions of the specification.
FakeXRRigidTransformInit
init, perform the following steps:
-
Let p be init’s
position
. -
If p does not have three elements, throw a
TypeError
. -
Let o be init’s
orientation
. -
If o does not have four elements, throw a
TypeError
. -
Let position be a
DOMPointInit
withx
,y
andz
equal to the three elements of p in order, andw
equal to1
. -
Let orientation be a
DOMPointInit
withx
,y
,z
, andw
equal to the four elements of o in order. -
Construct an XRRigidTransform
transform withposition
position andorientation
orientation. -
Return transform.
FakeXRViewInit
init, perform the following steps:
-
Let view be a new view.
-
If init’s
projectionMatrix
does not have 16 elements, throw aTypeError
. -
Set view’s projection matrix to init’s
projectionMatrix
. -
Set view’s view offset to the result of running parse a rigid transform init’s
viewOffset
. -
Set view’s device resolution to init’s
resolution
. -
If init’s
fieldOfView
is set, perform the following steps:-
Set view’s field of view to init’s
fieldOfView
. -
Set view’s projection matrix to the projection matrix corresponding to this field of view, and depth values equal to
depthNear
anddepthFar
of anyXRSession
associated with the device. If there currently is none, use the default values ofnear=0.1, far=1000.0
.
-
-
Set view’s projection matrix to init’s
projectionMatrix
. -
Return view.
5. Mocking
5.1. FakeXRDevice
interface {
FakeXRDevice void setViews (sequence <FakeXRViewInit >);
views Promise <void >disconnect ();void setViewerOrigin (FakeXRRigidTransformInit ,
origin optional boolean =
emulatedPosition false );void clearViewerOrigin ();void setFloorOrigin (FakeXRRigidTransformInit );
origin void clearFloorOrigin ();void setBoundsGeometry (sequence <FakeXRBoundsPoint >);
boundsCoordinates void simulateResetPose ();void simulateVisibilityChange (XRVisibilityState );
state FakeXRInputController (
simulateInputSourceConnection FakeXRInputSourceInit ); // Hit test extensions:
init void (
setWorld FakeXRWorldInit );
world void (); };
clearWorld
Each FakeXRDevice
object has an associated device, which is a simulated XR device that it is able to control.
Operations on the FakeXRDevice
's device typically take place on the next animation frame, i.e. they are not immediately observable until a future requestAnimationFrame()
callback.
To determine when this frame is, for a given operation, choose a frame based on the following:
- If such an operation is triggered within an XR animation frame:
- Choose the next XR animation frame, whenever it may occur
- If such an operation is triggered outside of an XR animation frame:
-
Choose a frame based on the following:
- If there are no callbacks in the list of animation frame callbacks:
- Choose the next XR animation frame, whenever it may occur
- Otherwise:
- Choose the next-to-next XR animation frame, whenever it may occur
NOTE: The reason we defer an extra frame when there are pending animation frame callbacks is to avoid having to deal with potential race conditions when the device is ready to trigger an animation frame callback, but has not yet. In practice, this means that tests should be written so that they wait until they have performed all such operations before calling the next requestAnimationFrame()
setViews(views)
method performs the following steps:
-
On the next animation frame, run the following steps:
-
Let l be an empty list.
-
For each view in views:
-
Let v be the result of running parse a view on view.
-
Append v to l.
-
-
Set device's list of views to l.
-
When disconnect()
method is called, perform the following steps:
-
Remove device from the context object's
XR
object’s list of immersive XR devices as if it were disconnected. -
If the inline XR device is equal to the
FakeXRDevice
, reset it to the default inline XR device.
setViewerOrigin(origin, emulatedPosition)
performs the following steps:
-
Let o be the result of running parse a rigid transform on origin.
-
On the next animation frame, perform the following steps:
-
Set device's viewer origin to o.
-
Set device's emulated position boolean to emulatedPosition.
-
The clearViewerOrigin()
method will, on the next animation frame, set device's viewer origin to null
.
The simulateVisibilityChange(state)
method will, as soon as possible, set device's visibility state to state.
setFloorOrigin(origin)
performs the following steps:
-
Let o be the result of running parse a rigid transform on origin.
-
On the next animation frame, set device's floor origin to o.
The clearFloorOrigin()
method will, on the next animation frame, set device's floor origin to null
.
setBoundsGeometry(boundsCoordinates)
performs the following steps:
-
If boundsCoordinates has fewer than 3 elements, throw a
TypeError
. -
On the next animation frame, set device's native bounds geometry to boundsCoordinates.
The simulateResetPose()
method will, as soon as possible, behave as if the device's viewer's native origin had a discontinuity, triggering appropriate reset
events.
5.2. FakeXRInputController
dictionary {
FakeXRInputSourceInit required XRHandedness ;
handedness required XRTargetRayMode ;
targetRayMode required FakeXRRigidTransformInit ;
pointerOrigin required sequence <DOMString >;
profiles boolean =
selectionStarted false ;boolean =
selectionClicked false ;sequence <FakeXRButtonStateInit >;
supportedButtons FakeXRRigidTransformInit ; };
gripOrigin interface {
FakeXRInputController void (
setHandedness XRHandedness );
handedness void (
setTargetRayMode XRTargetRayMode );
targetRayMode void (
setProfiles sequence <DOMString >);
profiles void (
setGripOrigin FakeXRRigidTransformInit ,
gripOrigin optional boolean =
emulatedPosition false );void ();
clearGripOrigin void (
setPointerOrigin FakeXRRigidTransformInit ,
pointerOrigin optional boolean =
emulatedPosition false );void ();
disconnect void ();
reconnect void ();
startSelection void ();
endSelection void ();
simulateSelect void (
setSupportedButtons sequence <FakeXRButtonStateInit >);
supportedButtons void (
updateButtonState FakeXRButtonStateInit ); };
buttonState enum {
FakeXRButtonType ,
"grip" ,
"touchpad" ,
"thumbstick" ,
"optional-button" };
"optional-thumbstick" dictionary {
FakeXRButtonStateInit required FakeXRButtonType ;
buttonType required boolean ;
pressed required boolean ;
touched required float ;
pressedValue float = 0.0;
xValue float = 0.0; };
yValue
6. Hit test extensions
The hit test extensions for test API SHOULD be implemented by all user agents that implement WebXR Hit Test Module.
dictionary {
FakeXRWorldInit required sequence <FakeXRRegionInit >; };
hitTestRegions
FakeXRWorldInit
dictionary describes the state of the world that will be used when computing hit test results on a FakeXRDevice
.
hitTestRegions
contains a collection of FakeXRRegionInit
s that are used to describe specific regions of the fake world. The order of the regions does not matter.
dictionary {
FakeXRRegionInit required sequence <FakeXRTriangleInit >;
faces required FakeXRRegionType ; };
type
FakeXRRegionInit
dictionary describes the contents of a specific region of the world.
faces
contains a collection of FakeXRTriangleInit
s that enumerate all the faces contained by the region. The order of the faces does not matter.
type
contains a type of the region that will be used during computation of hit test results.
dictionary {
FakeXRTriangleInit required sequence <DOMPointInit >; // size = 3 };
vertices
FakeXRTriangleInit
dictionary describes a single face of a region.
vertices
contains a collection of DOMPointInit
s that comprise the face. The face will be considered as solid when computing hit test results and as such, the winding order of the vertices does not matter.
enum {
FakeXRRegionType ,
"point" ,
"plane" };
"mesh"
FakeXRRegionType
enum is used to describe a type of the world region.