summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webxr/resources/webxr_util.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webxr/resources/webxr_util.js')
-rw-r--r--testing/web-platform/tests/webxr/resources/webxr_util.js241
1 files changed, 241 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webxr/resources/webxr_util.js b/testing/web-platform/tests/webxr/resources/webxr_util.js
new file mode 100644
index 0000000000..625f76450e
--- /dev/null
+++ b/testing/web-platform/tests/webxr/resources/webxr_util.js
@@ -0,0 +1,241 @@
+'use strict';
+
+// These tests rely on the User Agent providing an implementation of the
+// WebXR Testing API (https://github.com/immersive-web/webxr-test-api).
+//
+// In Chromium-based browsers, this implementation is provided by a JavaScript
+// shim in order to reduce the amount of test-only code shipped to users. To
+// enable these tests the browser must be run with these options:
+//
+// --enable-blink-features=MojoJS,MojoJSTest
+
+// Debugging message helper, by default does nothing. Implementations can
+// override this.
+var xr_debug = function(name, msg) {};
+
+function xr_promise_test(name, func, properties, glContextType, glContextProperties) {
+ promise_test(async (t) => {
+ if (glContextType === 'webgl2') {
+ // Fast fail on platforms not supporting WebGL2.
+ assert_implements('WebGL2RenderingContext' in window, 'webgl2 not supported.');
+ }
+ // Perform any required test setup:
+ xr_debug(name, 'setup');
+
+ assert_implements(navigator.xr, 'missing navigator.xr - ensure test is run in a secure context.');
+
+ // Only set up once.
+ if (!navigator.xr.test) {
+ // Load test-only API helpers.
+ const script = document.createElement('script');
+ script.src = '/resources/test-only-api.js';
+ script.async = false;
+ const p = new Promise((resolve, reject) => {
+ script.onload = () => { resolve(); };
+ script.onerror = e => { reject(e); };
+ })
+ document.head.appendChild(script);
+ await p;
+
+ if (isChromiumBased) {
+ // Chrome setup
+ await loadChromiumResources();
+ } else if (isWebKitBased) {
+ // WebKit setup
+ await setupWebKitWebXRTestAPI();
+ }
+ }
+
+ // Either the test api needs to be polyfilled and it's not set up above, or
+ // something happened to one of the known polyfills and it failed to be
+ // setup properly. Either way, the fact that xr_promise_test is being used
+ // means that the tests expect navigator.xr.test to be set. By rejecting now
+ // we can hopefully provide a clearer indication of what went wrong.
+ assert_implements(navigator.xr.test, 'missing navigator.xr.test, even after attempted load');
+
+ let gl = null;
+ let canvas = null;
+ if (glContextType) {
+ canvas = document.createElement('canvas');
+ document.body.appendChild(canvas);
+ gl = canvas.getContext(glContextType, glContextProperties);
+ }
+
+ // Ensure that any devices are disconnected when done. If this were done in
+ // a .then() for the success case, a test that expected failure would
+ // already be marked done at the time that runs and the shutdown would
+ // interfere with the next test.
+ t.add_cleanup(async () => {
+ // Ensure system state is cleaned up.
+ xr_debug(name, 'cleanup');
+ await navigator.xr.test.disconnectAllDevices();
+ });
+
+ xr_debug(name, 'main');
+ return func(t, gl);
+ }, name, properties);
+}
+
+// A utility function for waiting one animation frame before running the callback
+//
+// This is only needed after calling FakeXRDevice methods outside of an animation frame
+//
+// This is so that we can paper over the potential race allowed by the "next animation frame"
+// concept https://immersive-web.github.io/webxr-test-api/#xrsession-next-animation-frame
+function requestSkipAnimationFrame(session, callback) {
+ session.requestAnimationFrame(() => {
+ session.requestAnimationFrame(callback);
+ });
+}
+
+// A test function which runs through the common steps of requesting a session.
+// Calls the passed in test function with the session, the controller for the
+// device, and the test object.
+function xr_session_promise_test(
+ name, func, fakeDeviceInit, sessionMode, sessionInit, properties,
+ glcontextPropertiesParam, gllayerPropertiesParam) {
+ const glcontextProperties = (glcontextPropertiesParam) ? glcontextPropertiesParam : {};
+ const gllayerProperties = (gllayerPropertiesParam) ? gllayerPropertiesParam : {};
+
+ function runTest(t, glContext) {
+ let testSession;
+ let testDeviceController;
+ let sessionObjects = {gl: glContext};
+
+ // Ensure that any pending sessions are ended when done. This needs to
+ // use a cleanup function to ensure proper sequencing. If this were
+ // done in a .then() for the success case, a test that expected
+ // failure would already be marked done at the time that runs, and the
+ // shutdown would interfere with the next test which may have started.
+ t.add_cleanup(async () => {
+ // If a session was created, end it.
+ if (testSession) {
+ await testSession.end().catch(() => {});
+ }
+ });
+
+ return navigator.xr.test.simulateDeviceConnection(fakeDeviceInit)
+ .then((controller) => {
+ testDeviceController = controller;
+ return sessionObjects.gl.makeXRCompatible();
+ })
+ .then(() => new Promise((resolve, reject) => {
+ // Perform the session request in a user gesture.
+ xr_debug(name, 'simulateUserActivation');
+ navigator.xr.test.simulateUserActivation(() => {
+ xr_debug(name, 'document.hasFocus()=' + document.hasFocus());
+ navigator.xr.requestSession(sessionMode, sessionInit || {})
+ .then((session) => {
+ xr_debug(name, 'session start');
+ testSession = session;
+ session.mode = sessionMode;
+ session.sessionInit = sessionInit;
+ let glLayer = new XRWebGLLayer(session, sessionObjects.gl, gllayerProperties);
+ glLayer.context = sessionObjects.gl;
+ // Session must have a baseLayer or frame requests
+ // will be ignored.
+ session.updateRenderState({
+ baseLayer: glLayer
+ });
+ sessionObjects.glLayer = glLayer;
+ xr_debug(name, 'session.visibilityState=' + session.visibilityState);
+ try {
+ resolve(func(session, testDeviceController, t, sessionObjects));
+ } catch(err) {
+ reject("Test function failed with: " + err);
+ }
+ })
+ .catch((err) => {
+ xr_debug(name, 'error: ' + err);
+ reject(
+ 'Session with params ' +
+ JSON.stringify(sessionMode) +
+ ' was rejected on device ' +
+ JSON.stringify(fakeDeviceInit) +
+ ' with error: ' + err);
+ });
+ });
+ }));
+ }
+
+ xr_promise_test(
+ name + ' - webgl',
+ runTest,
+ properties,
+ 'webgl',
+ {alpha: false, antialias: false, ...glcontextProperties}
+ );
+ xr_promise_test(
+ name + ' - webgl2',
+ runTest,
+ properties,
+ 'webgl2',
+ {alpha: false, antialias: false, ...glcontextProperties});
+}
+
+
+// This function wraps the provided function in a
+// simulateUserActivation() call, and resolves the promise with the
+// result of func(), or an error if one is thrown
+function promise_simulate_user_activation(func) {
+ return new Promise((resolve, reject) => {
+ navigator.xr.test.simulateUserActivation(() => {
+ try { let a = func(); resolve(a); } catch(e) { reject(e); }
+ });
+ });
+}
+
+// This functions calls a callback with each API object as specified
+// by https://immersive-web.github.io/webxr/spec/latest/, allowing
+// checks to be made on all ojects.
+// Arguements:
+// callback: A callback function with two arguements, the first
+// being the API object, the second being the name of
+// that API object.
+function forEachWebxrObject(callback) {
+ callback(window.navigator.xr, 'navigator.xr');
+ callback(window.XRSession, 'XRSession');
+ callback(window.XRSessionCreationOptions, 'XRSessionCreationOptions');
+ callback(window.XRFrameRequestCallback, 'XRFrameRequestCallback');
+ callback(window.XRPresentationContext, 'XRPresentationContext');
+ callback(window.XRFrame, 'XRFrame');
+ callback(window.XRLayer, 'XRLayer');
+ callback(window.XRView, 'XRView');
+ callback(window.XRViewport, 'XRViewport');
+ callback(window.XRViewerPose, 'XRViewerPose');
+ callback(window.XRWebGLLayer, 'XRWebGLLayer');
+ callback(window.XRWebGLLayerInit, 'XRWebGLLayerInit');
+ callback(window.XRCoordinateSystem, 'XRCoordinateSystem');
+ callback(window.XRFrameOfReference, 'XRFrameOfReference');
+ callback(window.XRStageBounds, 'XRStageBounds');
+ callback(window.XRSessionEvent, 'XRSessionEvent');
+ callback(window.XRCoordinateSystemEvent, 'XRCoordinateSystemEvent');
+}
+
+// Code for loading test API in Chromium.
+async function loadChromiumResources() {
+ await loadScript('/resources/chromium/webxr-test-math-helper.js');
+ await import('/resources/chromium/webxr-test.js');
+ await loadScript('/resources/testdriver.js');
+ await loadScript('/resources/testdriver-vendor.js');
+
+ // This infrastructure is also used by Chromium-specific internal tests that
+ // may need additional resources (e.g. internal API extensions), this allows
+ // those tests to rely on this infrastructure while ensuring that no tests
+ // make it into public WPTs that rely on APIs outside of the webxr test API.
+ if (typeof(additionalChromiumResources) !== 'undefined') {
+ for (const path of additionalChromiumResources) {
+ await loadScript(path);
+ }
+ }
+
+ xr_debug = navigator.xr.test.Debug;
+}
+
+function setupWebKitWebXRTestAPI() {
+ // WebKit setup. The internals object is used by the WebKit test runner
+ // to provide JS access to internal APIs. In this case it's used to
+ // ensure that XRTest is only exposed to wpt tests.
+ navigator.xr.test = internals.xrTest;
+ return Promise.resolve();
+}