ar_dom_overlay.https.html (10746B)
1 <!DOCTYPE html> 2 <script src="/resources/testharness.js"></script> 3 <script src="/resources/testharnessreport.js"></script> 4 <script src="../resources/webxr_util.js"></script> 5 <script src="../resources/webxr_test_constants.js"></script> 6 <script src="../resources/webxr_test_asserts.js"></script> 7 8 <style type="text/css"> 9 div { 10 padding: 10px; 11 min-width: 10px; 12 min-height: 10px; 13 } 14 iframe { 15 border: 0; 16 width: 20px; 17 height: 20px; 18 } 19 </style> 20 <div id="div_overlay"> 21 <div id="inner_a"> 22 </div> 23 <div id="inner_b"> 24 </div> 25 <!-- This SVG iframe is treated as cross-origin content. --> 26 <iframe id="iframe" src='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect height="20" width="20" fill="red" fill-opacity="0.3"/></svg>'> 27 </iframe> 28 <canvas> 29 </canvas> 30 </div> 31 <div id="div_other"> 32 <p>test text</p> 33 </div> 34 35 <script> 36 37 const fakeDeviceInitParams = { 38 supportedModes: ["immersive-ar"], 39 views: VALID_VIEWS, 40 viewerOrigin: IDENTITY_TRANSFORM, 41 supportedFeatures: ALL_FEATURES, 42 environmentBlendMode: "alpha-blend", 43 interactionMode: "screen-space" 44 }; 45 46 let testBasicProperties = function(overlayElement, session, fakeDeviceController, t) { 47 assert_equals(session.mode, 'immersive-ar'); 48 assert_not_equals(session.environmentBlendMode, 'opaque'); 49 50 assert_true(overlayElement != null); 51 assert_true(overlayElement instanceof Element); 52 53 // Verify that the DOM overlay type is one of the known types. 54 assert_in_array(session.domOverlayState.type, 55 ["screen", "floating", "head-locked"]); 56 57 // Verify SameObject property for domOverlayState 58 assert_equals(session.domOverlayState, session.domOverlayState); 59 60 // The overlay element should have a transparent background. 61 assert_equals(window.getComputedStyle(overlayElement).backgroundColor, 62 'rgba(0, 0, 0, 0)'); 63 64 // Check that the pseudostyle is set. 65 assert_equals(document.querySelector(':xr-overlay'), overlayElement); 66 67 return new Promise((resolve) => { 68 session.requestAnimationFrame((time, xrFrame) => { 69 resolve(); 70 }); 71 }); 72 }; 73 74 let testFullscreen = async function(overlayElement, session, fakeDeviceController, t) { 75 // If the browser implements DOM Overlay using Fullscreen API, 76 // it must not be possible to change the DOM Overlay element by using 77 // Fullscreen API, and attempts to do so must be rejected. 78 // Since this is up to the UA, this test also passes if the fullscreen 79 // element is different from the overlay element. 80 81 // Wait for a rAF call before proceeding. 82 await new Promise((resolve) => session.requestAnimationFrame(resolve)); 83 84 assert_implements_optional(document.fullscreenElement == overlayElement, 85 "WebXR DOM overlay is not using Fullscreen API"); 86 let elem = document.getElementById('div_other'); 87 assert_not_equals(elem, null); 88 assert_not_equals(elem, overlayElement); 89 90 try { 91 await elem.requestFullscreen(); 92 assert_unreached("fullscreen change should be blocked"); 93 } catch { 94 // pass if the call rejects 95 } 96 // This is an async function, its return value is automatically a promise. 97 }; 98 99 let watcherStep = new Event("watcherstep"); 100 let watcherDone = new Event("watcherdone"); 101 102 let testInput = function(overlayElement, session, fakeDeviceController, t) { 103 let debug = xr_debug.bind(this, 'testInput'); 104 105 // Use two DIVs for this test. "inner_a" uses a "beforexrselect" handler 106 // that uses preventDefault(). Controller interactions with it should trigger 107 // that event, and not generate an XR select event. 108 109 let inner_a = document.getElementById('inner_a'); 110 assert_true(inner_a != null); 111 let inner_b = document.getElementById('inner_b'); 112 assert_true(inner_b != null); 113 114 let got_beforexrselect = false; 115 inner_a.addEventListener('beforexrselect', (ev) => { 116 ev.preventDefault(); 117 got_beforexrselect = true; 118 }); 119 120 let eventWatcher = new EventWatcher( 121 t, session, ["watcherstep", "select", "watcherdone"]); 122 123 // Set up the expected sequence of events. The test triggers two select 124 // actions, but only the second one should generate a "select" event. 125 // Use a "watcherstep" in between to verify this. 126 let eventPromise = eventWatcher.wait_for( 127 ["watcherstep", "select", "watcherdone"]); 128 129 let input_source = 130 fakeDeviceController.simulateInputSourceConnection(SCREEN_CONTROLLER); 131 session.requestReferenceSpace('viewer').then(function(viewerSpace) { 132 // Press the primary input button and then release it a short time later. 133 debug('got viewerSpace'); 134 requestSkipAnimationFrame(session, (time, xrFrame) => { 135 debug('got rAF 1'); 136 input_source.setOverlayPointerPosition(inner_a.offsetLeft + 1, 137 inner_a.offsetTop + 1); 138 input_source.startSelection(); 139 140 session.requestAnimationFrame((time, xrFrame) => { 141 debug('got rAF 2'); 142 input_source.endSelection(); 143 144 session.requestAnimationFrame((time, xrFrame) => { 145 debug('got rAF 3'); 146 // Need to process one more frame to allow select to propagate. 147 session.requestAnimationFrame((time, xrFrame) => { 148 debug('got rAF 4'); 149 session.dispatchEvent(watcherStep); 150 151 assert_true(got_beforexrselect); 152 153 session.requestAnimationFrame((time, xrFrame) => { 154 debug('got rAF 5'); 155 input_source.setOverlayPointerPosition(inner_b.offsetLeft + 1, 156 inner_b.offsetTop + 1); 157 input_source.startSelection(); 158 159 session.requestAnimationFrame((time, xrFrame) => { 160 debug('got rAF 6'); 161 input_source.endSelection(); 162 163 session.requestAnimationFrame((time, xrFrame) => { 164 debug('got rAF 7'); 165 // Need to process one more frame to allow select to propagate. 166 session.dispatchEvent(watcherDone); 167 }); 168 }); 169 }); 170 }); 171 }); 172 }); 173 }); 174 }); 175 return eventPromise; 176 }; 177 178 let testCrossOriginContent = function(overlayElement, session, fakeDeviceController, t) { 179 let debug = xr_debug.bind(this, 'testCrossOriginContent'); 180 181 let iframe = document.getElementById('iframe'); 182 assert_true(iframe != null); 183 let inner_b = document.getElementById('inner_b'); 184 assert_true(inner_b != null); 185 186 let eventWatcher = new EventWatcher( 187 t, session, ["watcherstep", "select", "watcherdone"]); 188 189 // Set up the expected sequence of events. The test triggers two select 190 // actions, but only the second one should generate a "select" event. 191 // Use a "watcherstep" in between to verify this. 192 let eventPromise = eventWatcher.wait_for( 193 ["watcherstep", "select", "watcherdone"]); 194 195 let input_source = 196 fakeDeviceController.simulateInputSourceConnection(SCREEN_CONTROLLER); 197 session.requestReferenceSpace('viewer').then(function(viewerSpace) { 198 // Press the primary input button and then release it a short time later. 199 requestSkipAnimationFrame(session, (time, xrFrame) => { 200 debug('got rAF 1'); 201 input_source.setOverlayPointerPosition(iframe.offsetLeft + 1, 202 iframe.offsetTop + 1); 203 input_source.startSelection(); 204 205 session.requestAnimationFrame((time, xrFrame) => { 206 debug('got rAF 2'); 207 input_source.endSelection(); 208 209 session.requestAnimationFrame((time, xrFrame) => { 210 debug('got rAF 3'); 211 // Need to process one more frame to allow select to propagate. 212 session.requestAnimationFrame((time, xrFrame) => { 213 debug('got rAF 4'); 214 session.dispatchEvent(watcherStep); 215 216 session.requestAnimationFrame((time, xrFrame) => { 217 debug('got rAF 5'); 218 input_source.setOverlayPointerPosition(inner_b.offsetLeft + 1, 219 inner_b.offsetTop + 1); 220 input_source.startSelection(); 221 222 session.requestAnimationFrame((time, xrFrame) => { 223 debug('got rAF 6'); 224 input_source.endSelection(); 225 226 session.requestAnimationFrame((time, xrFrame) => { 227 debug('got rAF 7'); 228 // Need to process one more frame to allow select to propagate. 229 session.dispatchEvent(watcherDone); 230 }); 231 }); 232 }); 233 }); 234 }); 235 }); 236 }); 237 }); 238 return eventPromise; 239 }; 240 241 xr_promise_test( 242 "Ensures DOM Overlay rejected without root element", 243 (t) => { 244 return navigator.xr.test.simulateDeviceConnection(fakeDeviceInitParams) 245 .then(() => { 246 return new Promise((resolve, reject) => { 247 navigator.xr.test.simulateUserActivation(() => { 248 resolve( 249 promise_rejects_dom(t, "NotSupportedError", 250 navigator.xr.requestSession('immersive-ar', 251 {requiredFeatures: ['dom-overlay']}) 252 .then(session => session.end()), 253 "Should reject when not specifying DOM overlay root") 254 ); 255 }); 256 }); 257 }); 258 }); 259 260 xr_session_promise_test( 261 "Ensures DOM Overlay feature works for immersive-ar, body element", 262 testBasicProperties.bind(this, document.body), 263 fakeDeviceInitParams, 'immersive-ar', 264 {requiredFeatures: ['dom-overlay'], 265 domOverlay: { root: document.body } }); 266 267 xr_session_promise_test( 268 "Ensures DOM Overlay feature works for immersive-ar, div element", 269 testBasicProperties.bind(this, document.getElementById('div_overlay')), 270 fakeDeviceInitParams, 'immersive-ar', 271 {requiredFeatures: ['dom-overlay'], 272 domOverlay: { root: document.getElementById('div_overlay') } }); 273 274 xr_session_promise_test( 275 "Ensures DOM Overlay input deduplication works", 276 testInput.bind(this, document.getElementById('div_overlay')), 277 fakeDeviceInitParams, 'immersive-ar', { 278 requiredFeatures: ['dom-overlay'], 279 domOverlay: { root: document.getElementById('div_overlay') } 280 }); 281 282 xr_session_promise_test( 283 "Ensures DOM Overlay Fullscreen API doesn't change DOM overlay", 284 testFullscreen.bind(this, document.getElementById('div_overlay')), 285 fakeDeviceInitParams, 'immersive-ar', { 286 requiredFeatures: ['dom-overlay'], 287 domOverlay: { root: document.getElementById('div_overlay') } 288 }); 289 290 xr_session_promise_test( 291 "Ensures DOM Overlay interactions on cross origin iframe are ignored", 292 testCrossOriginContent.bind(this, document.getElementById('div_overlay')), 293 fakeDeviceInitParams, 'immersive-ar', { 294 requiredFeatures: ['dom-overlay'], 295 domOverlay: { root: document.getElementById('div_overlay') } 296 }); 297 298 </script>