├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .pr-preview.json ├── LICENSE.md ├── Makefile ├── README.md ├── explainer.md ├── favicon-32x32.png ├── favicon-96x96.png ├── favicon.ico ├── index.bs ├── package.json ├── sample_test.html └── w3c.json /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 🛎️ 11 | uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly. 12 | with: 13 | persist-credentials: false 14 | 15 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 16 | run: make 17 | - name: Prepare Deploy folder 18 | run: mkdir deploy && rsync -av --exclude=.git --exclude=.gitignore --exclude=deploy . deploy/ 19 | - name: Deploy 🚀 20 | uses: JamesIves/github-pages-deploy-action@4.0.0 21 | with: 22 | BRANCH: gh-pages # The branch the action should deploy to. 23 | FOLDER: deploy # The folder the action should deploy. 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | node_modules 4 | npm-debug*.log* 5 | -------------------------------------------------------------------------------- /.pr-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_file": "index.bs", 3 | "type": "bikeshed", 4 | "params": { 5 | "force": 1 6 | } 7 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All Reports in this Repository are licensed by Contributors 2 | under the 3 | [W3C Software and Document License](https://www.w3.org/copyright/software-license-2023/). 4 | 5 | Contributions to Specifications are made under the 6 | [W3C CLA](https://www.w3.org/community/about/agreements/cla/). 7 | 8 | Contributions to Test Suites are made under the 9 | [W3C 3-clause BSD License](https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html). 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LOCAL_BIKESHED := $(shell command -v bikeshed 2> /dev/null) 2 | 3 | .PHONY: all index.html 4 | 5 | all: index.html 6 | 7 | index.html: index.bs 8 | ifndef LOCAL_BIKESHED 9 | curl https://api.csswg.org/bikeshed/ -F file=@index.bs -F output=err 10 | curl https://api.csswg.org/bikeshed/ -F file=@index.bs -F force=1 > index.html | tee 11 | else 12 | bikeshed spec index.bs index.html 13 | endif 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebXR Testing API 2 | To ensure that WebXR Device API and related APIs work cross-platform, we will develop [Web Platform Tests](https://github.com/w3c/web-platform-tests). 3 | In order to enable these tests, we define a common API to simulate backend functionality. 4 | This repo contains the development and discussion for that API. 5 | 6 | [Explainer](explainer.md) 7 | 8 | [Tests](https://github.com/web-platform-tests/wpt/tree/master/webxr) 9 | -------------------------------------------------------------------------------- /explainer.md: -------------------------------------------------------------------------------- 1 | # WebXR Test API 2 | 3 | In order to allow javascript tests for WebXR there are some basic functions which are common across all tests, 4 | such as adding a fake test device and specifying poses. Below is a Javascript IDL which attempts to capture 5 | the necessary functions, based off what was defined in the spec. Different browser vendors can implement this 6 | Javascript IDL in whatever way is most compatible with their browser. For example, some browsers may back the 7 | interface with a WebDriver API while others may use HTTP or IPC mechanisms to communicate with an out of process 8 | fake backend. Because of this, any "synchronous" methods that update the state of a device or controller are not 9 | guaranteed to have that updated state respected until the next "requestAnimationFrame" returns. 10 | 11 | ```WebIDL 12 | partial interface XRSystem { 13 | [SameObject] readonly attribute XRTest test; 14 | }; 15 | 16 | interface XRTest { 17 | // Simulates connecting a device to the system. 18 | // Used to instantiate a fake device for use in tests. 19 | Promise simulateDeviceConnection(FakeXRDeviceInit init); 20 | 21 | // Simulates a user activation (aka user gesture) for the current scope. 22 | // The activation is only guaranteed to be valid in the provided function and only applies to WebXR 23 | // Device API methods. 24 | void simulateUserActivation(Function f); 25 | 26 | // Disconnect all fake devices 27 | Promise disconnectAllDevices(); 28 | }; 29 | ``` 30 | 31 | The promise returned from simulateDeviceConnection resolves with a FakeXRDevice, which can be used 32 | to control the fake XRDevice that has been created in the background. The fake device may be used in a session returned by 33 | navigator.xr.requestSession(), depending on how many devices have been created and how the browser decides to hand 34 | them out. 35 | 36 | ```WebIDL 37 | dictionary FakeXRDeviceInit { 38 | // Deprecated - use `supportedModes` instead. 39 | required boolean supportsImmersive; 40 | // Sequence of modes that should be supported by this device. 41 | sequence supportedModes; 42 | required sequence views; 43 | sequence secondaryViews; 44 | 45 | // https://immersive-web.github.io/webxr/#feature-name 46 | // The list of feature names that this device supports. 47 | // Any requests for features not in this list should be rejected, with the exception of those 48 | // that are guaranteed regardless of device availability (e.g. 'viewer'). 49 | // If not specified/empty, the device supports no features. 50 | // NOTE: This is meant to emulate hardware support, not whether a feature is 51 | // currently available (e.g. bounds not being tracked per below) 52 | sequence supportedFeatures; 53 | 54 | // The bounds coordinates. If empty, no bounded reference space is currently tracked. 55 | // If not, must have at least three elements. 56 | sequence boundsCoordinates; 57 | 58 | // A transform used to identify the physical position of the user's floor. 59 | // If not set, indicates that the device cannot identify the physical floor. 60 | FakeXRRigidTransformInit floorOrigin; 61 | 62 | // native origin of the viewer 63 | // If not set, the device is currently assumed to not be tracking, and xrFrame.getViewerPose should 64 | // not return a pose. 65 | // 66 | // This sets the viewer origin *shortly after* initialization; since the viewer origin at initialization 67 | // is used to provide a reference origin for all matrices. 68 | FakeXRRigidTransformInit viewerOrigin; 69 | }; 70 | 71 | interface FakeXRDevice { 72 | // Sets the values to be used for subsequent 73 | // requestAnimationFrame() callbacks. 74 | void setViews(sequence primaryViews, sequence secondaryViews); 75 | 76 | // behaves as if device was disconnected 77 | Promise disconnect(); 78 | 79 | // Sets the origin of the viewer 80 | void setViewerOrigin(FakeXRRigidTransformInit origin, optional boolean emulatedPosition = false); 81 | 82 | // If an origin is not specified, then the device is assumed to not be tracking, emulatedPosition should 83 | // be assumed for cases where the UA must always provide a pose. 84 | void clearViewerOrigin(); 85 | 86 | // Simulates devices focusing and blurring sessions. 87 | void simulateVisibilityChange(XRVisibilityState state); 88 | 89 | void setBoundsGeometry(sequence boundsCoordinates); 90 | // Sets the native origin of the physical floor 91 | void setFloorOrigin(FakeXRRigidTransformInit floorOrigin); 92 | 93 | // Indicates that the device can no longer identify the location of the physical floor. 94 | void clearFloorOrigin(); 95 | 96 | // Used to simulate a major change in tracking and that a reset pose event should be fired 97 | // https://immersive-web.github.io/webxr/#event-types 98 | void simulateResetPose(); 99 | 100 | // Used to connect and send input events 101 | FakeXRInputController simulateInputSourceConnection(FakeXRInputSourceInit init); 102 | }; 103 | 104 | // https://immersive-web.github.io/webxr/#xrview 105 | dictionary FakeXRViewInit { 106 | required XREye eye; 107 | // https://immersive-web.github.io/webxr/#view-projection-matrix 108 | required sequence projectionMatrix; 109 | // https://immersive-web.github.io/webxr/#dom-xrwebgllayer-getviewport 110 | required FakeXRDeviceResolution resolution; 111 | // https://immersive-web.github.io/webxr/#view-offset 112 | // This is the origin of the view in the viewer space. In other words, this is 113 | // a transform from the view space to the viewer space. 114 | required FakeXRRigidTransformInit viewOffset; 115 | // This is an optional means of specifying a decomposed form of the projection 116 | // matrix. If specified, the projectionMatrix should be ignored. 117 | // Any test that wishes to test clip planes or similar features that would require 118 | // decomposing/recomposing the projectionMatrix should use this instead of 119 | // the projection matrix. 120 | FakeXRFieldOfViewInit fieldOfView; 121 | }; 122 | 123 | // A set of 4 angles which describe the view from a center point, units are degrees. 124 | dictionary FakeXRFieldOfViewInit { 125 | required float upDegrees; 126 | required float downDegrees; 127 | required float leftDegrees; 128 | required float rightDegrees; 129 | }; 130 | 131 | // This represents the native resolution of the device, but may not reflect the viewport exposed to the page. 132 | // https://immersive-web.github.io/webxr/#xrviewport 133 | dictionary FakeXRDeviceResolution { 134 | required long width; 135 | required long height; 136 | }; 137 | 138 | dictionary FakeXRBoundsPoint { 139 | double x; double z; 140 | }; 141 | 142 | 143 | // https://immersive-web.github.io/webxr/#xrrigidtransform 144 | dictionary FakeXRRigidTransformInit { 145 | // must have three elements 146 | required sequence position; 147 | // must have four elements 148 | required sequence orientation; 149 | }; 150 | ``` 151 | 152 | 153 | The WebXR API never exposes native origins directly, instead exposing transforms between them, so we need to specify a base reference space for XRRigidTransformInit so that we can have consistent numerical values across implementations. When used as an origin, XRRigidTransformInits 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. 154 | 155 | For many UAs input is sent on a per-frame basis, therefore input events are not guaranteed to fire and the FakeXRInputController 156 | is not guaranteed to be present in session.inputSources until after one animation frame. 157 | 158 | ``` WebIDL 159 | dictionary FakeXRInputSourceInit { 160 | required XRHandedness handedness; 161 | required XRTargetRayMode targetRayMode; 162 | required FakeXRRigidTransformInit pointerOrigin; 163 | required sequence profiles; 164 | // was the primary action pressed when this was connected? 165 | boolean selectionStarted = false; 166 | // should this input source send a select immediately upon connection? 167 | boolean selectionClicked = false; 168 | // Initial button state for any buttons beyond the primary that are supported. 169 | // If empty, only the primary button is supported. 170 | // Note that if any FakeXRButtonType is repeated the behavior is undefined. 171 | sequence supportedButtons; 172 | // If not set the controller is assumed to not be tracked. 173 | FakeXRRigidTransformInit gripOrigin; 174 | }; 175 | 176 | interface FakeXRInputController { 177 | 178 | // Indicates that the handedness of the device has changed. 179 | void setHandedness(XRHandedness handedness); 180 | 181 | // Indicates that the target ray mode of the device has changed. 182 | void setTargetRayMode(XRTargetRayMode targetRayMode); 183 | 184 | // Indicates that the list of profiles representing the device has changed. 185 | void setProfiles(sequence profiles); 186 | 187 | // Sets or clears the position of the controller. If not set, the controller is assumed to 188 | // not be tracked. 189 | void setGripOrigin(FakeXRRigidTransformInit gripOrigin, optional boolean emulatedPosition = false); 190 | void clearGripOrigin(); 191 | 192 | // Sets the pointer origin for the controller. 193 | void setPointerOrigin(FakeXRRigidTransformInit pointerOrigin, optional boolean emulatedPosition = false); 194 | 195 | // Temporarily disconnect the input device 196 | void disconnect(); 197 | 198 | // Reconnect a disconnected input device 199 | void reconnect(); 200 | 201 | // Start a selection for the current frame with the primary input 202 | // If a gamepad is supported, should update the state of the primary button accordingly. 203 | void startSelection(); 204 | 205 | // End selection for the current frame with the primary input 206 | // If a gamepad is supported, should update the state of the primary button accordingly. 207 | void endSelection(); 208 | 209 | // Simulates a start/endSelection for the current frame with the primary input 210 | // If a gamepad is supported, should update the state of the primary button accordingly. 211 | void simulateSelect(); 212 | 213 | // Updates the set of supported buttons, including any initial state. 214 | // Note that this method should not be generally used to update the state of the 215 | // buttons, as the UA may treat this as re-creating the Gamepad. 216 | // Note that if any FakeXRButtonType is repeated the behavior is undefined. 217 | void setSupportedButtons(sequence supportedButtons); 218 | 219 | // Used to update the state of a button currently supported by the input source 220 | // Will not add support for that button if it is not currently supported. 221 | void updateButtonState(FakeXRButtonStateInit buttonState); 222 | }; 223 | 224 | // Bcause the primary button is always guaranteed to be present, and other buttons 225 | // should fulfill the role of validating any state from FakeXRButtonStateInit 226 | // the primary button is not present in this enum. 227 | enum FakeXRButtonType { 228 | "grip", 229 | "touchpad", 230 | "thumbstick", 231 | // Represents a button whose position is not specified by the xr-standard mapping. 232 | // Should appear at one past the last reserved button index. 233 | "optional-button", 234 | // Represents a thumbstick whose position is not specified by the xr-standard mapping. 235 | // Should appear at two past the last reserved button index. 236 | "optional-thumbstick" 237 | }; 238 | 239 | // Used to update the state of optionally supported buttons. 240 | dictionary FakeXRButtonStateInit { 241 | required FakeXRButtonType buttonType; 242 | required boolean pressed; 243 | required boolean touched; 244 | required float pressedValue; 245 | // x and y value are ignored if the FakeXRButtonType is not touchpad, thumbstick, or optional-thumbstick 246 | float xValue = 0.0; 247 | float yValue = 0.0; 248 | }; 249 | ``` 250 | 251 | These initialization object and control interfaces do not represent a complete set of WebXR functionality, 252 | and are expected to be expanded on as the WebXR spec grows. 253 | 254 | ## Hit Test Extension 255 | 256 | In order to create deterministic and cross-browser WPT tests for the proposed WebXR [hit testing API](https://github.com/immersive-web/hit-test/), the WPT tests need to have a way to mock the data that is supposed to be returned from the API under test. This can be achieved by leveraging the test API extensions for hit test, described below. 257 | 258 | ```webidl 259 | partial interface FakeXRDevice { 260 | // Sets new world state on the device. 261 | void setWorld(FakeXRWorldInit world); 262 | // Clears the entire knowledge of the world on the device. 263 | void clearWorld(); 264 | }; 265 | 266 | partial dictionary FakeXRDeviceInit { 267 | // Initial state of the world known to the device. 268 | FakeXRWorldInit worldInit; 269 | }; 270 | 271 | dictionary FakeXRWorldInit { 272 | // World consists of a collection of hit testing regions. 273 | // The regions are listed in no particular order. 274 | required sequence hitTestRegions; 275 | }; 276 | 277 | dictionary FakeXRRegionInit { 278 | // Collection of faces that comprise this region. 279 | required sequence faces; 280 | // Type of the region. This will be considered when computing hit test results 281 | // for the purpose of filtering out the ones that the applicaton is not interested in. 282 | // More details can be found in Hit Testing Explainer, Limiting results to specific entities section. 283 | required FakeXRRegionType type; 284 | }; 285 | 286 | dictionary FakeXRTriangleInit { 287 | // Sequence of vertices that comprise this triangle. 288 | // The triangle is considered to be a solid surface for the purposes of hit test computations. 289 | required sequence vertices; // size = 3 290 | }; 291 | 292 | enum FakeXRRegionType { 293 | "point", 294 | "plane", 295 | "mesh" 296 | }; 297 | 298 | ``` 299 | 300 | ## DOM Overlay Extension 301 | 302 | In order to create deterministic and cross-browser WPT tests for the proposed WebXR [DOM Overlay API](https://immersive-web.github.io/dom-overlays/), the WPT tests need to have a way to supply data for API interactions. This can be achieved by leveraging the test API extensions for DOM Overlay support, described below. 303 | 304 | ```webidl 305 | partial interface FakeXRInputController { 306 | // Sets the position within the DOM Overlay in DOM coordinates for the next controller action. 307 | void setOverlayPointerPosition(float x, float y); 308 | }; 309 | ``` 310 | -------------------------------------------------------------------------------- /favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immersive-web/webxr-test-api/a5866cc875d93e93ee5856f7c4bf9f45ac01462b/favicon-32x32.png -------------------------------------------------------------------------------- /favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immersive-web/webxr-test-api/a5866cc875d93e93ee5856f7c4bf9f45ac01462b/favicon-96x96.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immersive-web/webxr-test-api/a5866cc875d93e93ee5856f7c4bf9f45ac01462b/favicon.ico -------------------------------------------------------------------------------- /index.bs: -------------------------------------------------------------------------------- 1 | 21 | 27 | 28 |
 29 | spec: WebXR Device API - Level 1; urlPrefix: https://www.w3.org/TR/webxr/#
 30 |     type:event; text:reset; url: eventdef-xrreferencespace-reset
 31 |     type:dfn; text:xr device
 32 |     type:dfn; text:viewer
 33 |     type:dfn; text:xr animation frame
 34 |     type:dfn; text:capable of supporting
 35 |     type:dfn; text:list of supported modes
 36 |     type:dfn; text:inline xr device
 37 |     type:dfn; text:list of immersive xr devices
 38 |     type:dfn; text:primary action
 39 |     type:dfn; text:primary squeeze action
 40 |     type:dfn; text:xr input source
 41 |     type:dfn; text:view; url: view
 42 |     for: view;
 43 |         type:dfn; text:eye; url: view-eye
 44 |     for: XRBoundedReferenceSpace;
 45 |         type:dfn; text:native bounds geometry; url: xrboundedreferencespace-native-bounds-geometry
 46 |     for: XRSpace;
 47 |         type:dfn; text:native origin; url: xrspace-native-origin
 48 |     type: interface; text: XRViewGeometry; url: xrviewgeometry-mixin
 49 |     type: dfn; text: view geometry; url: view-geometry
 50 |     for: view geometry;
 51 |         type: dfn; text: containing object; url: view-geometry-containing-object
 52 |         type: dfn; text: projection matrix; url: view-geometry-projection-matrix
 53 |         type: dfn; text: view offset; url: view-geometry-view-offset
 54 | spec: Gamepad; urlPrefix: https://www.w3.org/TR/gamepad/#
 55 |     type: enum-value; text: "xr-standard"; for: GamepadMappingType; url: dfn-xr-standard
 56 | spec:webxr-dom-overlays; urlPrefix: https://immersive-web.github.io/dom-overlays#
 57 |     type:dfn; text:beforexrselect
 58 | spec:WebXR Anchors Module; urlPrefix: https://immersive-web.github.io/anchors/#
 59 |     for: XRAnchor;
 60 |         type: method; text: delete(); url: dom-xranchor-delete
 61 |         type: dfn; text: native origin; url: xranchor-native-origin
 62 | spec:WebXR Lighting Estimation; urlPrefix: https://immersive-web.github.io/lighting-estimation/
 63 |     type:interface; text: XRLightEstimate; url: xrlightestimate-interface
 64 | spec: WebXR Depth Sensing Module; urlPrefix: https://immersive-web.github.io/depth-sensing/#
 65 |     type: dfn; text: create a CPU depth information instance; url: create-a-cpu-depth-information-instance
 66 |     type: dfn; text: create a GPU depth information instance; url: create-a-gpu-depth-information-instance
 67 |     type: dfn; text: depth coordinates transformation matrix; url: depth-coordinates-transformation-matrix
 68 |     type: dfn; text: native depth sensing; url: native-depth-sensing
 69 |     type:attribute; for:XRDepthInformation; text:sensor; url:xrdepthinformation-sensor
 70 |     type: dfn; text: finding a supported configuration combination; url: find-supported-configuration-combination
 71 |     type: enum; text: XRDepthType; url: enumdef-xrdepthtype
 72 |     type: enum; text: XRDepthFormat; url: enumdef-xrdepthformat
 73 |     type: enum; text: XRDepthUsage; url: enumdef-xrdepthusage
 74 | spec: WebXR Raw Camera Access Module; urlPrefix: https://immersive-web.github.io/raw-camera-access/#
 75 |     type: dfn; text: obtain camera
 76 | 
77 | 78 | 79 | 80 | 81 | 135 | 136 | 137 | Introduction {#intro} 138 | ============ 139 | 140 |
141 | 142 | 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. 143 | 144 | 145 | 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. 146 | 147 |
148 | 149 | 150 | Conformance {#conformance} 151 | ============ 152 | 153 | 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. 154 | 155 | 156 | 157 | Simulated devices {#simulated-devices} 158 | ============ 159 | 160 | Simulated XR Device {#simulated-devices-xr} 161 | ------------ 162 | 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 device|simulated XR devices=] can be controlled by the associated {{FakeXRDevice}} object. 163 | 164 | Every [=simulated XR device=] may have an native bounds geometry which is an array of {{DOMPointReadOnly}}s, used to initialize the [=XRBoundedReferenceSpace/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. 165 | 166 | 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. 167 | 168 | 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. 169 | 170 | Every [=simulated XR device=] has an emulated position boolean which is a boolean used to set the {{XRPose/emulatedPosition}} of any {{XRPose}}s produced involving the [=viewer=]. This is initially false. 171 | 172 | Every [=simulated XR device=] has an visibility state which is an {{XRVisibilityState}} used to set the {{XRSession/visibilityState}} of any {{XRSession}}s associated with the [=simulated XR device=] . This is initially {{XRVisibilityState/"visible"}}. When it is changed, the associated changes must be reflected on the {{XRSession}}, including triggering {{XRSession/onvisibilitychange}} events if necessary. 173 | 174 | Every [=simulated XR device=] has a list of primary views which is a list of [=view|views=] that must be rendered to for an immersive experience. There must be at least one primary view. 175 | 176 | Every [=simulated XR device=] may have a list of secondary views which is a list of [=view|views=] that may or may not be rendered to. There may be any number of secondary views. 177 | 178 | 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. 179 | 180 | 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 {{XRRenderState/depthNear}} and {{XRRenderState/depthFar}} values. 181 | 182 | Every [=simulated XR device=] has a supported depth types which is a [=list=] of {{XRDepthType}} values, initially empty. If this list is empty, it implies all {{XRDepthType}} values are supported. 183 | 184 | Every [=simulated XR device=] has a supported depth formats which is a [=list=] of {{XRDepthFormat}} values, initially empty. If this list is empty, it implies all {{XRDepthFormat}} values are supported. 185 | 186 | Every [=simulated XR device=] has a supported depth usages which is a [=list=] of {{XRDepthUsage}} values, initially empty. If this list is empty, it implies all {{XRDepthUsage}} values are supported. 187 | 188 | Simulated Input Device {#simulated-devices-input} 189 | ------------ 190 | 191 | This API gives tests the ability to spin up a simulated XR input source which is an [=/XR input source=] which from the point of view of the WebXR API behaves like a normal [=/XR input source=]. These [=simulated XR input source|simulated XR input sources=] can be controlled by the associated {{FakeXRInputController}} object. 192 | 193 | Every [=simulated XR input source=] has a handedness which is an {{XRHandedness}} value that MUST be returned for the corresponding [=/XR input source|XR input source's=] {{XRInputSource/handedness}} attribute. 194 | 195 | Every [=simulated XR input source=] has a targetRayMode which is an {{XRTargetRayMode}} value that MUST be returned for the corresponding [=/XR input source|XR input source's=] {{XRInputSource/targetRayMode}} attribute. 196 | 197 | Every [=simulated XR input source=] has a pointerOrigin which is an {{XRRigidTransform}} used to note the origin of the {{XRInputSource/targetRaySpace}}. 198 | 199 | A [=simulated XR input source=] may have a gripOrigin which is an {{XRRigidTransform}} used to note the origin of the {{XRInputSource/gripSpace}}. If this is null the [=simulated XR input source=] is not tracked. 200 | 201 | Every [=simulated XR input source=] has a profiles array which is an array of {{DOMString}}s which MUST be returned for the corresponding [=/XR input source|XR input source's=] {{XRInputSource/profiles}} attribute. 202 | 203 | Every [=simulated XR input source=] has a buttonState array which is an array of {{FakeXRButtonStateInit}}s. If a {{FakeXRButtonType/"grip"}} button is specified, it SHOULD drive the [=/primary squeeze action=]. If a UA implements the WebXR Gamepads Module [=buttonState=] SHOULD be used to set the state for the corresponding [=/XR input source|XR input source's=] {{XRInputSource/gamepad}} object, which SHOULD be of type {{GamepadMappingType/"xr-standard"}} if enough buttons are specified to support it. 204 | 205 | Every [=simulated XR input source=] has a connectionState which is a boolean that is initially true and indicates whether the associated [=/XR input source=] should appear in {{XRSession/inputSources}}. When it is changed the associated changes must be reflected on the XRSession, including triggering the {{XRSession/inputsourceschange}} event if necessary by the [=next animation frame=]. 206 | 207 | Every [=simulated XR input source=] has a primaryActionStarted which is a boolean, initially set to false, that indicates whether or not the primary action of the XR input source has been started. 208 | 209 | Initialization {#initialization} 210 | ============== 211 | 212 | navigator.xr.test {#xr-test-attribute} 213 | ------------ 214 | 215 | 216 | 221 | 222 | The test attribute's getter MUST return the {{XRTest}} object that is associated with it. This object MAY be lazily created. 223 | 224 | 225 | XRTest {#xrtest-interface} 226 | ------------ 227 | 228 | The {{XRTest}} object is the entry point for all testing. 229 | 230 | 237 | 238 |
239 | The simulateDeviceConnection(|init|) method creates a new [=simulated XR device=]. 240 | 241 | When this method is invoked, the user agent MUST run the following steps: 242 | 243 | 1. Let |promise| be [=a new Promise=]. 244 | 1. Run the following steps [=in parallel=]: 245 | 1. Let |device| be a new [=simulated XR device=]. 246 | 1. For each |view| in |init|'s {{FakeXRDeviceInit/views}}: 247 | 1. Let |p| be the result of running [=parse a view=] on |view|. 248 | 1. If running [=parse a view=] threw an error, reject |promise| with this error and abort these steps. 249 | 1. [=list/Append=] |p| to |device|'s [=simulated XR device/list of primary views=]. 250 | 1. If |init|'s {{FakeXRDeviceInit/secondaryViews}} is set, for each |secondaryView| in |init|'s {{FakeXRDeviceInit/secondaryViews}}: 251 | 1. Let |s| be the result of running [=parse a view=] on |secondaryView|. 252 | 1. If running [=parse a view=] threw an error, reject |promise| with this error and abort these steps. 253 | 1. [=list/Append=] |s| to |device|'s [=simulated XR device/list of secondary views=]. 254 | 1. If |init|'s {{FakeXRDeviceInit/boundsCoordinates}} is set, perform the following steps: 255 | 1. If |init|'s {{FakeXRDeviceInit/boundsCoordinates}} has less than 3 elements, reject |promise| with {{TypeError}} and abort these steps. 256 | 1. Set |device|'s [=simulated XR device/native bounds geometry=] to |init|'s {{FakeXRDeviceInit/boundsCoordinates}}. 257 | 1. If |init|'s {{FakeXRDeviceInit/floorOrigin}} is set, set |device|'s [=simulated XR device/floor origin=] to |init|'s {{FakeXRDeviceInit/floorOrigin}}. 258 | 1. If |init|'s {{FakeXRDeviceInit/viewerOrigin}} is set, set |device|'s [=simulated XR device/viewer origin=] to |init|'s {{FakeXRDeviceInit/viewerOrigin}}. 259 | 1. Let |supportedModes| be an empty list of {{XRSessionMode}}s. 260 | 1. Modify |supportedModes| as follows: 261 |
262 |
If |init|'s {{FakeXRDeviceInit/supportedModes}} is present:
263 |
264 | 1. Append the contents of |init|'s {{FakeXRDeviceInit/supportedModes}} to |supportedModes|. 265 | 1. If |supportedModes| is empty, append "inline" to it. 266 |
267 |
Else
268 |
269 | 1. Append "inline" to |supportedModes|. 270 | 1. If |init|'s {{FakeXRDeviceInit/supportsImmersive}} is true, append "immersive-vr" to |supportedModes|. 271 |
272 |
273 | 1. If |init|'s {{FakeXRDeviceInit/supportedFeatures}} is set, for each |mode| in |supportedModes|: 274 | 1. Associate |init|'s {{FakeXRDeviceInit/supportedFeatures}} to |mode| 275 | 276 | 1. If |init|'s {{FakeXRDeviceInit/depthSupport}} is present: 277 | 1. Let |depthConfig| be |init|'s {{FakeXRDeviceInit/depthSupport}}. 278 | 1. If |depthConfig|'s {{FakeXRDepthConfigurationSupport/depthTypes}} is present and its [=list/size=] is greater than 0, set |device|'s [=simulated XR device/supported depth types=] to a copy of |depthConfig|'s {{FakeXRDepthConfigurationSupport/depthTypes}}. 279 | 1. If |depthConfig|'s {{FakeXRDepthConfigurationSupport/depthFormats}} is present and its [=list/size=] is greater than 0, set |device|'s [=simulated XR device/supported depth formats=] to a copy of |depthConfig|'s {{FakeXRDepthConfigurationSupport/depthFormats}}. 280 | 1. If |depthConfig|'s {{FakeXRDepthConfigurationSupport/depthUsages}} is present and its [=list/size=] is greater than 0, set |device|'s [=simulated XR device/supported depth usages=] to a copy of |depthConfig|'s {{FakeXRDepthConfigurationSupport/depthUsages}}. 281 | 282 | NOTE: each |device| stores a [=capable of supporting|list of features it is capable of supporting=] per {{XRSessionMode}}. Most tests only test one mode anyway so there isn't much to be gained by splitting features per mode in {{FakeXRDeviceInit}}. Users wishing different modes supporting different features should create multiple devices instead. 283 | 284 | 1. Set |device|'s [=list of supported modes=] to |supportedModes|. 285 | 1. Register |device| based on the following: 286 | 1. If |supportedModes| contains "immersive-vr" or "immersive-ar", append |device| to the {{Navigator/xr}}'s [=list of immersive XR devices=]. 287 | 1. If |supportedModes| contains "inline", set the [=inline XR device=] to |device|. 288 | 1. Let |d| be a new {{FakeXRDevice}} object with [=FakeXRDevice/device=] as |device|. 289 | 1. [=/Resolve=] |promise| with |d|. 290 | 1. Return |promise|. 291 | 292 |
293 | 294 | When simulateUserActivation(f) is called, invoke f as if it had [=transient activation=]. 295 | 296 | 297 | When disconnectAllDevices() is called, remove all [=simulated XR devices=] from the {{Navigator/xr}}'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=]. 298 | 299 | 300 | FakeXRDeviceInit {#fakexrdeviceinit-dict} 301 | ------------ 302 | 303 | 356 | 357 |

358 | The {{FakeXRDeviceInit/supportsImmersive}} is deprecated in favor of {{FakeXRDeviceInit/supportedModes}} and will be removed in future revisions of the specification. 359 |

360 | 361 |
362 | To parse a rigid transform given a {{FakeXRRigidTransformInit}} |init|, perform the following steps: 363 | 364 | 1. Let |p| be |init|'s {{FakeXRRigidTransformInit/position}}. 365 | 1. If |p| does not have three elements, throw a {{TypeError}}. 366 | 1. Let |o| be |init|'s {{FakeXRRigidTransformInit/orientation}}. 367 | 1. If |o| does not have four elements, throw a {{TypeError}}. 368 | 1. Let |position| be a {{DOMPointInit}} with {{DOMPointInit/x}}, {{DOMPointInit/y}} and {{DOMPointInit/z}} equal to the three elements of |p| in order, and {{DOMPointInit/w}} equal to 1. 369 | 1. Let |orientation| be a {{DOMPointInit}} with {{DOMPointInit/x}}, {{DOMPointInit/y}}, {{DOMPointInit/z}}, and {{DOMPointInit/w}} equal to the four elements of |o| in order. 370 | 1. {{XRRigidTransform/constructor|Construct an XRRigidTransform}} |transform| with {{XRRigidTransform/position}} |position| and {{XRRigidTransform/orientation}} |orientation|. 371 | 1. Return |transform|. 372 | 373 |
374 | 375 |
376 | To parse a view given a {{FakeXRViewInit}} |init|, perform the following steps: 377 | 378 | 1. Let |view| be a new [=view=]. 379 | 1. Set |view|'s [=view/eye=] to |init|'s {{FakeXRViewInit/eye}}. 380 | 1. If |init|'s {{FakeXRViewInit/projectionMatrix}} does not have 16 elements, throw a {{TypeError}}. 381 | 1. Set |view|'s [=view geometry/projection matrix=] to |init|'s {{FakeXRViewInit/projectionMatrix}}. 382 | 1. Set |view|'s [=view geometry/view offset=] to the result of running [=parse a rigid transform=] |init|'s {{FakeXRViewInit/viewOffset}}. 383 | 1. Set |view|'s [=view/device resolution=] to |init|'s {{FakeXRViewInit/resolution}}. 384 | 1. If |init|'s {{FakeXRViewInit/fieldOfView}} is set, perform the following steps: 385 | 1. Set |view|'s [=view/field of view=] to |init|'s {{FakeXRViewInit/fieldOfView}}. 386 | 1. Set |view|'s [=view geometry/projection matrix=] to the projection matrix corresponding to this field of view, and depth values equal to {{XRRenderState/depthNear}} and {{XRRenderState/depthFar}} of any {{XRSession}} associated with the device. If there currently is none, use the default values of near=0.1, far=1000.0. 387 | 1. Return |view|. 388 | 389 |
390 | 391 | FakeXRRigidTransformInit {#fakexrrigidtransform-base-space} 392 | ------------ 393 | 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 [=XRSpace/native origin=] is identity at initialization, unless otherwise specified. In this space, the {{XRReferenceSpaceType/"local"}} reference space has a [=XRSpace/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. 394 | 395 | Mocking {#mocking} 396 | ============== 397 | 398 | FakeXRDevice {#fakexrdevice-interface} 399 | ------------ 400 | 401 | 427 | 428 | 429 | Each {{FakeXRDevice}} object has an associated device, which is a [=simulated XR device=] that it is able to control. 430 | 431 |
432 | 433 | Operations on the {{FakeXRDevice}}'s [=FakeXRDevice/device=] typically take place on the next animation frame, i.e. they are not immediately observable until a future {{XRSession/requestAnimationFrame()}} callback. 434 | 435 | To determine when this frame is, for a given operation, choose a frame based on the following: 436 | 437 |
438 |
If such an operation is triggered within an [=XR animation frame=]:
439 |
Choose the next [=XR animation frame=], whenever it may occur
440 |
If such an operation is triggered outside of an [=XR animation frame=]:
441 |
Choose either the next or next-to-next [=XR animation frame=]. The precise choice is up to the user agent and may be dependent on the exact timing of these events. 442 |
443 |
444 | 445 | 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 {{XRSession/requestAnimationFrame()}}, and in case they are running outside of an [=XR animation frame=], should always wait two frames before expecting any updates to take effect. 446 |
447 | 448 |
449 | To parse a list of views on a [=list=] |views| run the following steps: 450 | 1. Let |l| be an empty [=list=] 451 | 1. For each |view| in |views|: 452 | 1. Let |v| be the result of running [=parse a view=] on |view|. 453 | 1. [=list/Append=] |v| to |l|. 454 | 1. Return |l|. 455 |
456 | 457 |
458 | The setViews(|views|, |secondaryViews|) method performs the following steps: 459 | 460 | 1. On the [=next animation frame=], run the following steps: 461 | 1. Let |p| be the result of running [=parse a list of views=] on |views|. 462 | 1. Set [=FakeXRDevice/device=]'s [=simulated XR device/list of primary views=] to |p|. 463 | 1. If |secondaryViews| is set, let |s| be the result of running [=parse a list of views=] on |secondaryViews|. 464 | 1. Set [=FakeXRDevice/device=]'s [=simulated XR device/list of secondary views=] to |s|. 465 | 466 |
467 | 468 | 469 |
470 | 471 | When disconnect() method is called, perform the following steps: 472 | 473 | 1. Remove [=FakeXRDevice/device=] from the {{Navigator/xr}}'s [=list of immersive XR devices=] as if it were disconnected. 474 | 1. If the [=inline XR device=] is equal to the {{FakeXRDevice}}, reset it to the default [=inline XR device=]. 475 | 476 |
477 | 478 |
479 | The setViewerOrigin(|origin|, |emulatedPosition|) performs the following steps: 480 | 481 | 1. Let |o| be the result of running [=parse a rigid transform=] on |origin|. 482 | 1. On the [=next animation frame=], perform the following steps: 483 | 1. Set [=FakeXRDevice/device=]'s [=simulated XR device/viewer origin=] to |o|. 484 | 1. Set [=FakeXRDevice/device=]'s [=simulated XR device/emulated position boolean=] to |emulatedPosition|. 485 | 486 |
487 | 488 | The clearViewerOrigin() method will, on the [=next animation frame=], set [=FakeXRDevice/device=]'s [=simulated XR device/viewer origin=] to null. 489 | 490 | The simulateVisibilityChange(|state|) method will, as soon as possible, set [=FakeXRDevice/device=]'s [=simulated XR device/visibility state=] to |state|. 491 | 492 | 493 | 494 | 495 |
496 | The setFloorOrigin(|origin|) performs the following steps: 497 | 498 | 1. Let |o| be the result of running [=parse a rigid transform=] on |origin|. 499 | 1. On the [=next animation frame=], set [=FakeXRDevice/device=]'s [=simulated XR device/floor origin=] to |o|. 500 | 501 |
502 | 503 | The clearFloorOrigin() method will, on the [=next animation frame=], set [=FakeXRDevice/device=]'s [=simulated XR device/floor origin=] to null. 504 | 505 | 506 |
507 | The setBoundsGeometry(|boundsCoordinates|) performs the following steps: 508 | 509 | 1. If |boundsCoordinates| has fewer than 3 elements, throw a {{TypeError}}. 510 | 1. On the [=next animation frame=], set [=FakeXRDevice/device=]'s [=simulated XR device/native bounds geometry=] to |boundsCoordinates|. 511 | 512 |
513 | 514 | The simulateResetPose() method will, as soon as possible, behave as if the [=FakeXRDevice/device=]'s [=viewer=]'s [=XRSpace/native origin=] had a discontinuity, triggering appropriate {{reset}} events. 515 | 516 |
517 | The simulateInputSourceConnection(|init|) method creates a new [=simulated XR input source=]. 518 | 519 | When this method is invoked, the user agent MUST run the following steps: 520 | 521 | 1. Let |inputSource| be a new [=simulated XR input source=]. 522 | 1. Set |inputSource|'s [=simulated XR input source/handedness=] to |init|'s {{FakeXRInputSourceInit/handedness}}. 523 | 1. Set |inputSource|'s [=simulated XR input source/targetRayMode=] to |init|'s {{FakeXRInputSourceInit/targetRayMode}}. 524 | 1. Set |inputSource|'s [=simulated XR input source/profiles=] to |init|'s {{FakeXRInputSourceInit/profiles}} 525 | 1. If |init|'s {{FakeXRInputSourceInit/gripOrigin}} is set, set |inputSource|'s [=simulated XR input source/gripOrigin=] to the result of running [=parse a rigid transform=] on |init|'s {{FakeXRInputSourceInit/gripOrigin}} 526 | 1. Set |inputSource|'s [=simulated XR input source/pointerOrigin=] to the result of running [=parse a rigid transform=] on |init|'s {{FakeXRInputSourceInit/pointerOrigin}} 527 | 1. If |init|'s {{FakeXRInputSourceInit/supportedButtons}} is set, set |inputSource|'s [=simulated XR input source/buttonState=] to the result of running [=parse supported buttons=] on |init|'s {{FakeXRInputSourceInit/supportedButtons}} 528 | 1. If |init|'s {{FakeXRInputSourceInit/selectionClicked}} is set to true, run [=simulate a full primary action=] on |inputSource|. 529 | 1. If |init|'s {{FakeXRInputSourceInit/selectionStarted}} is set to true, run [=start a primary action=] on |inputSource|. 530 | 1. By the [=next animation frame=] notify {{XRSession}} of the new [=/XR input source=]. 531 | 1. Let |c| be a new {{FakeXRInputController}} object with {[=FakeXRInputController/inputSource=] as |inputSource|. 532 | 1. Return |c|. 533 |
534 | 535 | FakeXRInputController {#fakexrinputcontroller-init} 536 | ------------ 537 | 538 | 586 | 587 | Each {{FakeXRInputController}} object has an associated inputSource, which is a [=simulated XR input source=] that it is able to control. 588 | 589 | Since user agents may opt to send input events on a per-frame basis, the results of all {{FakeXRInputController}} methods and {{FakeXRDevice/simulateInputSourceConnection()}} are not guaranteed to be visible (via, e.g. {{XRSession/inputSources}} or {{XRSession/oninputsourceschange}} events) until the [=next animation frame=]. 590 | 591 |
592 | To start a primary action on a [=simulated XR input source=] run the following steps: 593 | 1. If [=simulated XR input source/primaryActionStarted=] is true, abort these steps. 594 | 1. Set [=simulated XR input source/primaryActionStarted=] to true. 595 | 1. By the [=next animation frame=] indicate to the {{XRSession}} that the corresponding [=/XR input source|XR input source's=] [=/primary action=] has started. 596 |
597 | 598 |
599 | To stop a primary action on a [=simulated XR input source=] run the following steps: 600 | 1. If [=simulated XR input source/primaryActionStarted=] is false, abort these steps. 601 | 1. Set [=simulated XR input source/primaryActionStarted=] to false. 602 | 1. By the [=next animation frame=] indicate to the {{XRSession}} that the corresponding [=/XR input source|XR input source's=] [=/primary action=] has stopped. 603 |
604 | 605 |
606 | To simulate a full primary action on a [=simulated XR input source=] |source|, run the following steps: 607 | 1. Let |current| be the current value of [=simulated XR input source/primaryActionStarted=]. 608 | 1. Run [=start a primary action=] on |source| 609 | 1. Run [=stop a primary action=] on |source| 610 | 1. If |current| is true run [=start a primary action=] on |source| 611 |
612 | 613 | Note: If a gamepad is attached to the [=simulated XR input source=], then running [=start a primary action=] or [=stop a primary action=] should also ensure that the primary input's corresponding gamepad button is updated accordingly. 614 | 615 | Note: If both [=start a primary action=] and [=stop a primary action=] are run in the same frame, then by the [=next animation frame=] It is expected that {{XRSession/onselect}} and {{XRSession/onselectend}} events will fire. 616 | 617 |
618 | To parse supported buttons on a sequence of {{FakeXRButtonStateInit}}s, |buttons| run the following steps: 619 | 1. Let |l| be an empty list of {{FakeXRButtonStateInit}}s 620 | 1. For each |button| in |buttons|: 621 | 1. If |l| does not contain a {{FakeXRButtonStateInit}} whose {{FakeXRButtonStateInit/buttonType}} matches |button|'s {{FakeXRButtonStateInit/buttonType}}, append |button| to |l|. 622 | 1. Return |l| 623 |
624 | 625 | The setHandedness(|handedness|) method will, by the [=next animation frame=], set [=FakeXRInputController/inputSource=]'s [=simulated XR input source/handedness=] to |handedness|. 626 | 627 | The setTargetRayMode(|targetRayMode|) method will, by the [=next animation frame=], set [=FakeXRInputController/inputSource=]'s [=simulated XR input source/targetRayMode=] to |targetRayMode|. 628 | 629 | The setProfiles(|profiles|) method will, by the [=next animation frame=], set [=FakeXRInputController/inputSource=]'s [=simulated XR input source/profiles=] to |profiles|. 630 | 631 | The setGripOrigin(|gripOrigin|) method will, by the [=next animation frame=], set [=FakeXRInputController/inputSource=]'s [=simulated XR input source/gripOrigin=] to the result of running [=parse a rigid transform=] on |gripOrigin|. 632 | 633 | The clearGripOrigin() method will, by the [=next animation frame=], set [=FakeXRInputController/inputSource=]'s [=simulated XR input source/gripOrigin=] to null. 634 | 635 | The setPointerOrigin(|pointerOrigin|) method will, by the [=next animation frame=], set [=FakeXRInputController/inputSource=]'s [=simulated XR input source/pointerOrigin=] to the result of running [=parse a rigid transform=] on |pointerOrigin|. 636 | 637 |
638 | The disconnect() method will run the following steps: 639 | 1. If [=FakeXRInputController/inputSource=]'s [=simulated XR input source/connectionState=] is false, abort these steps. 640 | 1. Set [=FakeXRInputController/inputSource=]'s [=simulated XR input source/connectionState=] to false. 641 | 1. By the [=next animation frame=], notify the {{XRSession}} that this [=/XR input source=] has been removed. 642 |
643 | 644 |
645 | The reconnect() method will run the following steps: 646 | 1. If [=FakeXRInputController/inputSource=]'s [=simulated XR input source/connectionState=] is true, abort these steps. 647 | 1. Set [=FakeXRInputController/inputSource=]'s [=simulated XR input source/connectionState=] to true. 648 | 1. By the [=next animation frame=], notify the {{XRSession}} that this [=/XR input source=] has been added. 649 |
650 | 651 | The startSelection() method will run [=start a primary action=] on [=FakeXRInputController/inputSource=]. 652 | 653 | The endSelection() method will run [=stop a primary action=] on [=FakeXRInputController/inputSource=]. 654 | 655 | The simulateSelect() method will run [=simulate a full primary action=] on [=FakeXRInputController/inputSource=]. 656 | 657 | The setSupportedButtons(|supportedButtons|) will, by the [=next animation frame=], set [=FakeXRInputController/inputSource=]'s [=simulated XR input source/buttonState=] to the result of running [=parse supported buttons=] on |supportedButtons|. 658 | 659 | Note: As user agents may recreate the {{XRInputSource}} or {{XRInputSource/gamepad}} objects on buttons being changed, this method SHOULD NOT be used to simulate changes to button state. 660 | 661 |
662 | The updateButtonState(|buttonState|) will run the following steps: 663 | 1. Let |validState| equal the results of running [=validate a button state=] on |buttonState|. 664 | 1. Let |foundState| be null. 665 | 1. For every |state| in [=FakeXRInputController/inputSource=]'s [=simulated XR input source/buttonState=] array: 666 | 1. If |state|'s {{FakeXRButtonStateInit/buttonType}} matches |buttonState|'s {{FakeXRButtonStateInit/buttonType}}: 667 | 1. Set |foundState| to a reference of |state| 668 | 1. Break out of this loop 669 | 1. If |foundState| is null throw a {{NotFoundError}} 670 | 1. Update |foundState|'s attributes in [=FakeXRInputController/inputSource=]'s [=simulated XR input source/buttonState=] to match those of |validState|. Note: If {{FakeXRButtonStateInit/buttonType}} is {{FakeXRButtonType/grip}}, then [=XR input source=]'s [=/primary squeeze action=] should be updated. 671 |
672 | 673 |
674 | To validate a button state on a {{FakeXRButtonStateInit}} |buttonState| run the following steps: 675 | 1. Let |validState| equal |buttonState|. 676 | 1. If {{FakeXRButtonStateInit/pressed}} is true and {{FakeXRButtonStateInit/touched}} is false, throw a {{TypeError}}. 677 | 1. If {{FakeXRButtonStateInit/pressedValue}} is less than 0.0, throw a {{TypeError}}. 678 | 1. If {{FakeXRButtonStateInit/pressedValue}} is greater than 0.0 and {{FakeXRButtonStateInit/touched}} is false throw a {{TypeError}}. 679 | 1. If {{FakeXRButtonStateInit/buttonType}} is not one of: {{FakeXRButtonType/"touchpad"}}, {{FakeXRButtonType/"thumbstick"}}, or {{FakeXRButtonType/"optional-thumbstick"}}: 680 | 1. Set |validState|'s {{FakeXRButtonStateInit/xValue}} to 0.0. 681 | 1. Set |validState|'s {{FakeXRButtonStateInit/yValue}} to 0.0. 682 | 1. Return |validState|. 683 |
684 | 685 | Hit test extensions {#hit-test-extensions} 686 | =================== 687 | 688 | The hit test extensions for test API SHOULD be implemented by all user agents that implement WebXR Hit Test Module. 689 | 690 | 695 | 696 | {{FakeXRWorldInit}} dictionary describes the state of the world that will be used when computing hit test results on a {{FakeXRDevice}}. 697 | 698 | {{FakeXRWorldInit/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. 699 | 700 | 706 | 707 | {{FakeXRRegionInit}} dictionary describes the contents of a specific region of the world. 708 | 709 | {{FakeXRRegionInit/faces}} contains a collection of {{FakeXRTriangleInit}}s that enumerate all the faces contained by the region. The order of the faces does not matter. 710 | 711 | {{FakeXRRegionInit/type}} contains a type of the region that will be used during computation of hit test results. 712 | 713 | 718 | 719 | {{FakeXRTriangleInit}} dictionary describes a single face of a region. 720 | 721 | {{FakeXRTriangleInit/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. 722 | 723 | 730 | 731 | {{FakeXRRegionType}} enum is used to describe a type of the world region. 732 | 733 | 734 | DOM overlay extensions {#dom-overlay-extensions} 735 | ====================== 736 | 737 | The DOM Overlay extensions for test API SHOULD be implemented by all user agents that implement WebXR DOM Overlay Module. 738 | 739 | 744 | 745 | When setOverlayPointerPosition(x, y) is called, it sets a position within the DOM overlay in DOM coordinates for the next [=XR animation frame=], and is cleared after that frame. It is intended to be used along with a [=primary action=] for that frame, simulating that the user is interacting with the DOM overlay. The UA will emit a [=beforexrselect=] event at this location before generating XR select events. 746 | 747 | Anchors extensions {#anchors-extensions} 748 | ================== 749 | 750 | The anchors extensions for test API SHOULD be implemented by all user agents that implement WebXR Anchors. 751 | 752 | 764 | 765 | The {{FakeXRAnchorCreationCallback}} callback can be used by the Web Platform Tests to control the result of a call to create an anchor, and to be able to subsequently control the newly created anchor. 766 | 767 | The {{FakeXRDevice}} interface is extended with internal anchorCreationCallback, initially set to null. When the device receives a request to create an anchor, it MUST run the [=determine if the anchor creation succeeded=] algorithm. 768 | 769 |
770 | 771 | In order to determine if the anchor creation succeeded, the {{FakeXRDevice}} |device| MUST run the following steps: 772 | 1. If the |device|'s [=FakeXRDevice/anchorCreationCallback=] is null, return false and abort these steps. 773 | 1. Let |promise| be the result of invoking [=FakeXRDevice/anchorCreationCallback=] with parameters set so that they reflect the parameters passed to anchor creation request. 774 | 1. [=React=] to |promise|: 775 | - If |promise| was fulfilled with value |v|, then return |v| and abort these steps. 776 | - If |promise| was rejected, then return false and abort these steps. 777 | 778 |
779 | 780 | The WPTs can set the anchor creation callback by calling {{FakeXRDevice/setAnchorCreationCallback(callback)}}. 781 | 782 | The {{FakeXRAnchorCreationParameters/requestedAnchorOrigin}} attribute represents a transform expressed relative to [=base reference space=] used by the device. 783 | 784 | The {{FakeXRAnchorCreationParameters/isAttachedToEntity}} attribute will be set to true if the created anchor should be treated as attached to some entity. If so, the tests could emulate entity changing location by appropriately controlling the anchor via [=FakeXRAnchorCreationCallback/anchorController=]. 785 | 786 | The anchorController parameter passed in to {{FakeXRAnchorCreationCallback}} can be used to update the state of the anchor, assuming that the creation request was deemed successful. Tests SHOULD store it and issue commands to it for the entire duration of controlled anchor's lifetime. 787 | 788 | 801 | 802 | Successfully created anchors can be controlled by the test through the use of {{FakeXRAnchorController}} interface. 803 | 804 | The {{FakeXRAnchorController}} has an associated internal anchor origin, which is a {{FakeXRRigidTransformInit}} describing the current state of the anchor's [=XRAnchor/native origin=]. 805 | 806 | The {{FakeXRAnchorController/deleted}} attribute will be set to true when the application has invoked an {{XRAnchor/delete()}} method on the anchor - in that case, the changes to the fake anchor controller will be ignored. 807 | 808 | The {{FakeXRAnchorController/pauseTracking()}} method can be used by the tests to signal that the controlled anchor is temporarily untracked (i.e. its location will be unknown). Calling this method does not modify [=FakeXRAnchorController/anchor origin=] of the controller. 809 | 810 | The {{FakeXRAnchorController/resumeTracking()}} method can be used by the tests to signal that the controlled anchor should have its tracking resumed, if it was temporarily untracked. Calling this method does not modify [=FakeXRAnchorController/anchor origin=] of the controller. 811 | 812 | The {{FakeXRAnchorController/stopTracking()}} method can be used by the tests to signal that the controlled anchor is no longer tracked and that anchor tracking will not be resumed. After calling this method, the other calls on anchor controller will be ignored. 813 | 814 | The {{FakeXRAnchorController/setAnchorOrigin(anchorOrigin)}} method can be used to set the controller's [=FakeXRAnchorController/anchor origin=]. Tests can use this method to simulate updates in anchor pose. 815 | 816 | Lighting estimation extensions {#lighting-estimation-extensions} 817 | ================== 818 | 819 | The lighting estimation extensions for test API SHOULD be implemented by all user agents that implement WebXR Lighting Estimation. 820 | 821 | 832 | 833 | The {{FakeXRDevice}} is extended with internal light estimate which is a {{FakeXRLightEstimateInit}}, used to supply data for any requested {{XRLightEstimate}}. 834 | 835 |
836 | When the setLightEstimate(|init|) method is invoked on {{FakeXRDevice}} |device|, run the following steps: 837 | 838 | 1. Let |c| be |init|'s {{FakeXRLightEstimateInit/sphericalHarmonicsCoefficients}}. 839 | 1. If |c| does not have 27 elements, throw a {{TypeError}} and abort these steps. 840 | 1. Let |d| be |init|'s {{FakeXRLightEstimateInit/primaryLightDirection}}. 841 | 1. If |d| is set and |d|'s {{DOMPointInit/w}} value does not equal 0, throw a {{TypeError}} and abort these steps. 842 | 1. Let |i| be |init|'s {{FakeXRLightEstimateInit/primaryLightIntensity}}. 843 | 1. If |i| is set and |i|'s {{DOMPointInit/w}} value does not equal 1, throw a {{TypeError}} and abort these steps. 844 | 1. Set |device|'s [=FakeXRDevice/light estimate=] to |init| by the [=next animation frame=]. 845 | 846 |
847 | 848 | Depth sensing extensions {#depth-sensing-extensions} 849 | ======================== 850 | 851 | The depth sensing extensions for test API SHOULD be implemented by all user agents that implement WebXR Depth Sensing Module. 852 | 853 | The {{FakeXRDevice}} is extended with internal depth sensing data which is a {{FakeXRDepthSensingDataInit}}, used to supply data for requests to [=native depth sensing=]. 854 | 855 | 862 | 863 | The {{FakeXRDepthConfigurationSupport}} dictionary is used to define the [=native depth sensing=] capabilities of a [=simulated XR device=]. Missing or empty sequences for {{FakeXRDepthConfigurationSupport/depthTypes}}, {{FakeXRDepthConfigurationSupport/depthFormats}}, or {{FakeXRDepthConfigurationSupport/depthUsages}} indicate that all possible values for that respective enumeration are supported by the [=simulated XR device=]. If a User Agent does not support a particular value for any real device that it supports, it SHOULD ignore the presence of that value in any of these lists, in order to generate a more appropriate failure. 864 | 865 | Note: For simplicity, the [=simulated XR device=] is presumed to support the cross-product of all supported types, formats, and usages. There is currently no mechanism in this test API to specify support for only specific combinations (e.g., a particular format and type only with a particular usage). 866 | 867 | 879 | 880 | {{FakeXRDepthSensingDataInit}} dictionary describes the state of the depth sensing data that should be used when returning latest depth information in [=creating a CPU depth information instance=] and [=creating a GPU depth information instance=] algorithms. All keys present in {{FakeXRDepthSensingDataInit}} correspond to the data required to be returned by [=native depth sensing=] capabilities of the device. 881 | 882 | {{FakeXRDepthSensingDataInit/depthData}} corresponds to the desired depth buffer that is to be set on native depth information returned from querying the native device. Not setting {{FakeXRDepthSensingDataInit/depthData}} key in the dictionary signals that the returned native depth information should be null. 883 | 884 | {{FakeXRDepthSensingDataInit/depthFormat}} indicates the {{XRDepthFormat}} of the data set in {{FakeXRDepthSensingDataInit/depthData}}. 885 | 886 | {{FakeXRDepthSensingDataInit/normDepthBufferFromNormView}} corresponds to the desired [=depth coordinates transformation matrix=] that is to be set on native depth information returned from querying the native device. 887 | 888 | {{FakeXRDepthSensingDataInit/rawValueToMeters}} corresponds to the desired conversion factor that is to be set on native depth information returned from querying the native device. 889 | 890 | {{FakeXRDepthSensingDataInit/width}} and {{FakeXRDepthSensingDataInit/height}} correspond to the desired dimensions of the depth buffer that are to be set on native depth information returned from querying the native device. 891 | 892 | {{FakeXRDepthSensingDataInit/projectionMatrix}} is an optional 16-element sequence of floats representing a projection matrix. If present, this matrix is intended to define the [=view geometry/projection matrix=] for the {{XRDepthInformation/sensor}}'s {{XRViewGeometry}}. If not present, the sensor geometry's projection matrix is assumed to be aligned with the projection matrix of the {{XRViewGeometry}} of the [=view=] for which the depth information is being created. 893 | 894 | {{FakeXRDepthSensingDataInit/viewOffset}} is an optional {{FakeXRRigidTransformInit}}. If present, this transform is intended to define the [=view geometry/view offset=] for the {{XRDepthInformation/sensor}}'s {{XRViewGeometry}}, relative to the [=base reference space=]. If not present, the sensor geometry's transform is assumed to be aligned with the view offset of the {{XRViewGeometry}} of the [=view=] for which the depth information is being created. 895 | 896 |
897 | 898 | When the {{FakeXRDevice/setDepthSensingData()}} method is invoked on {{FakeXRDevice}} |device| with |depthSensingData|, run the following steps: 899 | 900 | 1. If |depthSensingData|'s {{FakeXRDepthSensingDataInit/depthData}} is null, throw a {{TypeError}} and abort these steps. 901 | 1. If |depthSensingData|'s {{FakeXRDepthSensingDataInit/projectionMatrix}} is set and its size is not 16, throw a {{TypeError}} and abort these steps. 902 | 1. If |depthSensingData|'s {{FakeXRDepthSensingDataInit/viewOffset}} is set, run [=parse a rigid transform=] on |depthSensingData|'s {{FakeXRDepthSensingDataInit/viewOffset}}. 903 | 1. Set |device|'s [=FakeXRDevice/depth sensing data=] to |depthSensingData|. 904 | 905 |
906 | 907 |
908 | 909 | When the {{FakeXRDevice/clearDepthSensingData()}} method is invoked on {{FakeXRDevice}} |device|, run the following steps: 910 | 911 | 1. Set |device|'s [=FakeXRDevice/depth sensing data=] to null. 912 | 913 |
914 | 915 | Raw camera access extensions {#raw-camera-access-extensions} 916 | ============================ 917 | 918 | The raw camera access extensions for test API SHOULD be implemented by all user agents that implement WebXR Raw Camera Access Module. 919 | 920 | The {{FakeXRViewInit}} dictionary is extended with {{FakeXRViewInit/cameraImageInit}} dictionary of type {{FakeXRDeviceResolution}}. This dictionary carries information about the camera image, and is intended to affect the camera image variable in [=obtain camera=] algorithm. If the {{FakeXRViewInit/cameraImageInit}} key is not present in the {{FakeXRViewInit}} dictionary, the [=obtain camera=] algorithm should treat this as null camera image (and thus the algorithm will return null). 921 | 922 | 930 | 931 | The {{FakeXRCameraImage/width}} controls the width of the camera image buffer in [=obtain camera=] algorithm. 932 | 933 | The {{FakeXRCameraImage/height}} controls the height of the camera image buffer in [=obtain camera=] algorithm. 934 | 935 | The {{FakeXRCameraImage/pixels}} control the camera image contents in [=obtain camera=] algorithm. The pixels will be used to initialize the camera image texture. 936 | 937 | The camera image will be initialized as if by a call to gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels), where width, height, and pixels are, respectively, {{FakeXRCameraImage/width}}, {{FakeXRCameraImage/height}}, and {{FakeXRCameraImage/pixels}}. In case {{FakeXRCameraImage/pixels}} key is not present in the dictionary, the behavior would be as if a call to gl.texImage2D() variant that omits the pixels parameter was made. 938 | 939 | Any time a [=simulated XR device=]'s [=simulated XR device/list of primary views=] and [=simulated XR device/list of secondary views=] is set, the user agent MUST verify that the camera images associated with the views present across both of those lists are all equal to each other. Camera images are considered equal when their {{FakeXRCameraImage/width}}s and {{FakeXRCameraImage/height}}s are equal, and their {{FakeXRCameraImage/pixels}} are the same instance (if present). If they are not equal, the user agent MUST throw an error from within the algorithm that attempted to set them. 940 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webxr-test-api", 3 | "description": "WebXR Test API", 4 | "version": "0.0.1", 5 | "devDependencies": { 6 | "browser-sync": "^2.18.8" 7 | }, 8 | "scripts": { 9 | "start": "npm run dev", 10 | "dev": "cross-env NODE_ENV=development npm run server", 11 | "prod": "cross-env NODE_ENV=production npm run server", 12 | "server": "browser-sync start --config scripts/browsersync-config.js", 13 | "build": "make" 14 | }, 15 | "dependencies": { 16 | "cross-env": "^4.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 96 | -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": 109735 3 | , "contacts": ["dontcallmedom", "himorin"] 4 | , "repo-type": "tests" 5 | } 6 | --------------------------------------------------------------------------------