tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

webxr_util.js (10182B)


      1 'use strict';
      2 
      3 // These tests rely on the User Agent providing an implementation of the
      4 // WebXR Testing API (https://github.com/immersive-web/webxr-test-api).
      5 //
      6 // In Chromium-based browsers, this implementation is provided by a JavaScript
      7 // shim in order to reduce the amount of test-only code shipped to users. To
      8 // enable these tests the browser must be run with these options:
      9 //
     10 //   --enable-blink-features=MojoJS,MojoJSTest
     11 
     12 // Debugging message helper, by default does nothing. Implementations can
     13 // override this.
     14 var xr_debug = function(name, msg) {};
     15 
     16 let loaded = new Promise(resolve => document.addEventListener('DOMContentLoaded', resolve));
     17 
     18 function xr_promise_test(name, func, properties, glContextType, glContextProperties) {
     19  promise_test(async (t) => {
     20    if (glContextType === 'webgl2') {
     21      // Fast fail on platforms not supporting WebGL2.
     22      assert_implements('WebGL2RenderingContext' in window, 'webgl2 not supported.');
     23    }
     24    // Perform any required test setup:
     25    xr_debug(name, 'setup');
     26 
     27    assert_implements(navigator.xr, 'missing navigator.xr - ensure test is run in a secure context.');
     28 
     29    // Only set up once.
     30    if (!navigator.xr.test) {
     31 
     32      if (typeof isChromiumBased === 'undefined' || typeof isWebKitBased === 'undefined') {
     33        // Load test-only API helpers.
     34        const script = document.createElement('script');
     35        script.src = '/resources/test-only-api.js';
     36        script.async = false;
     37        const p = new Promise((resolve, reject) => {
     38          script.onload = () => { resolve(); };
     39          script.onerror = e => { reject(e); };
     40        });
     41        document.head.appendChild(script);
     42        await p;
     43      }
     44 
     45      if (isChromiumBased) {
     46        // Chrome setup
     47        await loadChromiumResources();
     48      } else if (isWebKitBased) {
     49        // WebKit setup
     50        await setupWebKitWebXRTestAPI();
     51      }
     52    }
     53 
     54    // Either the test api needs to be polyfilled and it's not set up above, or
     55    // something happened to one of the known polyfills and it failed to be
     56    // setup properly. Either way, the fact that xr_promise_test is being used
     57    // means that the tests expect navigator.xr.test to be set. By rejecting now
     58    // we can hopefully provide a clearer indication of what went wrong.
     59    assert_implements(navigator.xr.test, 'missing navigator.xr.test, even after attempted load');
     60 
     61    let gl = null;
     62    let canvas = null;
     63    if (glContextType) {
     64      canvas = document.createElement('canvas');
     65      await loaded;
     66      document.body.appendChild(canvas);
     67      gl = canvas.getContext(glContextType, glContextProperties);
     68    }
     69 
     70    // Ensure that any devices are disconnected when done. If this were done in
     71    // a .then() for the success case, a test that expected failure would
     72    // already be marked done at the time that runs and the shutdown would
     73    // interfere with the next test.
     74    t.add_cleanup(async () => {
     75      // Ensure system state is cleaned up.
     76      xr_debug(name, 'cleanup');
     77      await navigator.xr.test.disconnectAllDevices();
     78    });
     79 
     80    xr_debug(name, 'main');
     81    return func(t, gl);
     82  }, name, properties);
     83 }
     84 
     85 // A utility function for waiting one animation frame before running the callback
     86 //
     87 // This is only needed after calling FakeXRDevice methods outside of an animation frame
     88 //
     89 // This is so that we can paper over the potential race allowed by the "next animation frame"
     90 // concept https://immersive-web.github.io/webxr-test-api/#xrsession-next-animation-frame
     91 function requestSkipAnimationFrame(session, callback) {
     92 session.requestAnimationFrame(() => {
     93  session.requestAnimationFrame(callback);
     94 });
     95 }
     96 
     97 // A test function which runs through the common steps of requesting a session.
     98 // Calls the passed in test function with the session, the controller for the
     99 // device, and the test object.
    100 function xr_session_promise_test(
    101    name, func, fakeDeviceInit, sessionMode, sessionInit, properties,
    102    glcontextPropertiesParam, gllayerPropertiesParam) {
    103  const glcontextProperties = (glcontextPropertiesParam) ? glcontextPropertiesParam : {};
    104  const gllayerProperties = (gllayerPropertiesParam) ? gllayerPropertiesParam : {};
    105 
    106  function runTest(t, glContext) {
    107    let testSession;
    108    let testDeviceController;
    109    let sessionObjects = {gl: glContext};
    110 
    111    // Ensure that any pending sessions are ended when done. This needs to
    112    // use a cleanup function to ensure proper sequencing. If this were
    113    // done in a .then() for the success case, a test that expected
    114    // failure would already be marked done at the time that runs, and the
    115    // shutdown would interfere with the next test which may have started.
    116    t.add_cleanup(async () => {
    117      // If a session was created, end it.
    118      if (testSession) {
    119        await testSession.end().catch(() => {});
    120      }
    121    });
    122 
    123    return navigator.xr.test.simulateDeviceConnection(fakeDeviceInit)
    124        .then((controller) => {
    125          testDeviceController = controller;
    126          return sessionObjects.gl.makeXRCompatible();
    127        })
    128        .then(() => new Promise((resolve, reject) => {
    129                // Perform the session request in a user gesture.
    130                xr_debug(name, 'simulateUserActivation');
    131                navigator.xr.test.simulateUserActivation(() => {
    132                  xr_debug(name, 'document.hasFocus()=' + document.hasFocus());
    133                  navigator.xr.requestSession(sessionMode, sessionInit || {})
    134                      .then((session) => {
    135                        xr_debug(name, 'session start');
    136                        testSession = session;
    137                        session.mode = sessionMode;
    138                        session.sessionInit = sessionInit;
    139                        let glLayer = new XRWebGLLayer(session, sessionObjects.gl, gllayerProperties);
    140                        glLayer.context = sessionObjects.gl;
    141                        // Session must have a baseLayer or frame requests
    142                        // will be ignored.
    143                        session.updateRenderState({
    144                            baseLayer: glLayer
    145                        });
    146                        sessionObjects.glLayer = glLayer;
    147                        xr_debug(name, 'session.visibilityState=' + session.visibilityState);
    148                        try {
    149                          resolve(func(session, testDeviceController, t, sessionObjects));
    150                        } catch(err) {
    151                          reject("Test function failed with: " + err);
    152                        }
    153                      })
    154                      .catch((err) => {
    155                        xr_debug(name, 'error: ' + err);
    156                        reject(
    157                            'Session with params ' +
    158                            JSON.stringify(sessionMode) +
    159                            ' was rejected on device ' +
    160                            JSON.stringify(fakeDeviceInit) +
    161                            ' with error: ' + err);
    162                      });
    163                });
    164        }));
    165  }
    166 
    167  xr_promise_test(
    168    name + ' - webgl',
    169    runTest,
    170    properties,
    171    'webgl',
    172    {alpha: false, antialias: false, ...glcontextProperties}
    173  );
    174  xr_promise_test(
    175    name + ' - webgl2',
    176    runTest,
    177    properties,
    178    'webgl2',
    179    {alpha: false, antialias: false, ...glcontextProperties}
    180  );
    181 }
    182 
    183 
    184 // This function wraps the provided function in a
    185 // simulateUserActivation() call, and resolves the promise with the
    186 // result of func(), or an error if one is thrown
    187 function promise_simulate_user_activation(func) {
    188  return new Promise((resolve, reject) => {
    189    navigator.xr.test.simulateUserActivation(() => {
    190      try { let a = func(); resolve(a); } catch(e) { reject(e); }
    191    });
    192  });
    193 }
    194 
    195 // This functions calls a callback with each API object as specified
    196 // by https://immersive-web.github.io/webxr/spec/latest/, allowing
    197 // checks to be made on all ojects.
    198 // Arguements:
    199 //      callback: A callback function with two arguements, the first
    200 //                being the API object, the second being the name of
    201 //                that API object.
    202 function forEachWebxrObject(callback) {
    203  callback(window.navigator.xr, 'navigator.xr');
    204  callback(window.XRSession, 'XRSession');
    205  callback(window.XRSessionCreationOptions, 'XRSessionCreationOptions');
    206  callback(window.XRFrameRequestCallback, 'XRFrameRequestCallback');
    207  callback(window.XRPresentationContext, 'XRPresentationContext');
    208  callback(window.XRFrame, 'XRFrame');
    209  callback(window.XRLayer, 'XRLayer');
    210  callback(window.XRView, 'XRView');
    211  callback(window.XRViewport, 'XRViewport');
    212  callback(window.XRViewerPose, 'XRViewerPose');
    213  callback(window.XRWebGLLayer, 'XRWebGLLayer');
    214  callback(window.XRWebGLLayerInit, 'XRWebGLLayerInit');
    215  callback(window.XRCoordinateSystem, 'XRCoordinateSystem');
    216  callback(window.XRFrameOfReference, 'XRFrameOfReference');
    217  callback(window.XRStageBounds, 'XRStageBounds');
    218  callback(window.XRSessionEvent, 'XRSessionEvent');
    219  callback(window.XRCoordinateSystemEvent, 'XRCoordinateSystemEvent');
    220 }
    221 
    222 // Code for loading test API in Chromium.
    223 async function loadChromiumResources() {
    224  await loadScript('/resources/chromium/webxr-test-math-helper.js');
    225  await import('/resources/chromium/webxr-test.js');
    226  await loadScript('/resources/testdriver.js');
    227  await loadScript('/resources/testdriver-vendor.js');
    228 
    229  // This infrastructure is also used by Chromium-specific internal tests that
    230  // may need additional resources (e.g. internal API extensions), this allows
    231  // those tests to rely on this infrastructure while ensuring that no tests
    232  // make it into public WPTs that rely on APIs outside of the webxr test API.
    233  if (typeof(additionalChromiumResources) !== 'undefined') {
    234    for (const path of additionalChromiumResources) {
    235      await loadScript(path);
    236    }
    237  }
    238 
    239  xr_debug = navigator.xr.test.Debug;
    240 }
    241 
    242 function setupWebKitWebXRTestAPI() {
    243  // WebKit setup. The internals object is used by the WebKit test runner
    244  // to provide JS access to internal APIs. In this case it's used to
    245  // ensure that XRTest is only exposed to wpt tests.
    246  navigator.xr.test = internals.xrTest;
    247  return Promise.resolve();
    248 }