webxr_util.js 9.7 KB

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