getdisplaymedia.https.html (12352B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>getDisplayMedia</title> 4 <meta name="timeout" content="long"> 5 <button id="button">User gesture</button> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="/resources/testdriver.js"></script> 9 <script src="/resources/testdriver-vendor.js"></script> 10 <script> 11 'use strict'; 12 test(() => { 13 assert_idl_attribute(navigator.mediaDevices, 'getDisplayMedia'); 14 }, "getDisplayMedia in navigator.mediaDevices"); 15 16 const stopTracks = stream => stream.getTracks().forEach(track => track.stop()); 17 const j = obj => JSON.stringify(obj); 18 19 async function getDisplayMedia(constraints) { 20 const p = new Promise(r => button.onclick = r); 21 await test_driver.click(button); 22 await p; 23 return navigator.mediaDevices.getDisplayMedia(constraints); 24 } 25 26 promise_test(t => { 27 const p = navigator.mediaDevices.getDisplayMedia({video: true}); 28 t.add_cleanup(async () => { 29 try { stopTracks(await p) } catch {} 30 }); 31 // Race a settled promise to check that the returned promise is already 32 // rejected. 33 return promise_rejects_dom( 34 t, 'InvalidStateError', Promise.race([p, Promise.resolve()]), 35 'getDisplayMedia should have returned an already-rejected promise.'); 36 }, `getDisplayMedia() must require user activation`); 37 38 [ 39 {video: true}, 40 {video: true, audio: false}, 41 {video: {}}, 42 {audio: false}, 43 {}, 44 undefined 45 ].forEach(constraints => promise_test(async t => { 46 const stream = await getDisplayMedia(constraints); 47 t.add_cleanup(() => stopTracks(stream)); 48 assert_equals(stream.getTracks().length, 1); 49 assert_equals(stream.getVideoTracks().length, 1); 50 assert_equals(stream.getAudioTracks().length, 0); 51 }, `getDisplayMedia(${j(constraints)}) must succeed with video`)); 52 53 [ 54 {video: false}, 55 {video: {advanced: [{width: 320}]}}, 56 {video: {width: {min: 320}}}, 57 {video: {width: {exact: 320}}}, 58 {video: {height: {min: 240}}}, 59 {video: {height: {exact: 240}}}, 60 {video: {frameRate: {min: 4}}}, 61 {video: {frameRate: {exact: 4}}}, 62 ].forEach(constraints => promise_test(async t => { 63 await test_driver.bless('getDisplayMedia()'); 64 const p = navigator.mediaDevices.getDisplayMedia(constraints); 65 t.add_cleanup(async () => { 66 try { stopTracks(await p) } catch {} 67 }); 68 await promise_rejects_js( 69 t, TypeError, Promise.race([p, Promise.resolve()]), 70 'getDisplayMedia should have returned an already-rejected promise.'); 71 }, `getDisplayMedia(${j(constraints)}) must fail with TypeError`)); 72 73 [ 74 {video: true, audio: true}, 75 {audio: true}, 76 ].forEach(constraints => promise_test(async t => { 77 const stream = await getDisplayMedia(constraints); 78 t.add_cleanup(() => stopTracks(stream)); 79 assert_greater_than_equal(stream.getTracks().length, 1); 80 assert_less_than_equal(stream.getTracks().length, 2); 81 assert_equals(stream.getVideoTracks().length, 1); 82 assert_less_than_equal(stream.getAudioTracks().length, 1); 83 }, `getDisplayMedia(${j(constraints)}) must succeed with video maybe audio`)); 84 85 [ 86 {width: {max: 360}}, 87 {height: {max: 240}}, 88 {width: {max: 360}, height: {max: 240}}, 89 {frameRate: {max: 4}}, 90 {frameRate: {max: 4}, width: {max: 360}}, 91 {frameRate: {max: 4}, height: {max: 240}}, 92 {frameRate: {max: 4}, width: {max: 360}, height: {max: 240}}, 93 ].forEach(constraints => promise_test(async t => { 94 const stream = await getDisplayMedia({video: constraints}); 95 t.add_cleanup(() => stopTracks(stream)); 96 const {width, height, frameRate} = stream.getTracks()[0].getSettings(); 97 assert_greater_than_equal(width, 1); 98 assert_greater_than_equal(height, 1); 99 assert_greater_than_equal(frameRate, 1); 100 if (constraints.width) { 101 assert_less_than_equal(width, constraints.width.max); 102 } 103 if (constraints.height) { 104 assert_less_than_equal(height, constraints.height.max); 105 } 106 if (constraints.frameRate) { 107 assert_less_than_equal(frameRate, constraints.frameRate.max); 108 } 109 }, `getDisplayMedia({video: ${j(constraints)}}) must be constrained`)); 110 111 const someSizes = [ 112 {width: 160}, 113 {height: 120}, 114 {width: 80}, 115 {height: 60}, 116 {width: 158}, 117 {height: 118}, 118 ]; 119 120 someSizes.forEach(constraints => promise_test(async t => { 121 const stream = await getDisplayMedia({video: constraints}); 122 t.add_cleanup(() => stopTracks(stream)); 123 const {width, height, frameRate} = stream.getTracks()[0].getSettings(); 124 if (constraints.width) { 125 assert_equals(width, constraints.width); 126 } else { 127 assert_equals(height, constraints.height); 128 } 129 assert_greater_than_equal(frameRate, 1); 130 }, `getDisplayMedia({video: ${j(constraints)}}) must be downscaled precisely`)); 131 132 promise_test(async t => { 133 const video = {height: 240}; 134 const stream = await getDisplayMedia({video}); 135 t.add_cleanup(() => stopTracks(stream)); 136 const [track] = stream.getVideoTracks(); 137 const {height} = track.getSettings(); 138 assert_equals(height, video.height); 139 for (const constraints of someSizes) { 140 await track.applyConstraints(constraints); 141 const {width, height} = track.getSettings(); 142 if (constraints.width) { 143 assert_equals(width, constraints.width); 144 } else { 145 assert_equals(height, constraints.height); 146 } 147 } 148 }, `applyConstraints(width or height) must downscale precisely`); 149 150 [ 151 {video: {width: {max: 0}}}, 152 {video: {height: {max: 0}}}, 153 {video: {frameRate: {max: 0}}}, 154 {video: {width: {max: -1}}}, 155 {video: {height: {max: -1}}}, 156 {video: {frameRate: {max: -1}}}, 157 ].forEach(constraints => promise_test(async t => { 158 try { 159 stopTracks(await getDisplayMedia(constraints)); 160 } catch (err) { 161 assert_equals(err.name, 'OverconstrainedError', err.message); 162 return; 163 } 164 assert_unreached('getDisplayMedia should have failed'); 165 }, `getDisplayMedia(${j(constraints)}) must fail with OverconstrainedError`)); 166 167 [ 168 {width: {max: 0}}, 169 {height: {max: 0}}, 170 {frameRate: {max: 0}}, 171 {width: {max: -1}}, 172 {height: {max: -1}}, 173 {frameRate: {max: -1}}, 174 {width: {min: 100, max: 10}}, 175 {height: {min: 100, max: 10}}, 176 {frameRate: {min: 100, max: 10}}, 177 ].forEach(constraints => promise_test(async t => { 178 const stream = await getDisplayMedia(); 179 t.add_cleanup(() => stopTracks(stream)); 180 const [track] = stream.getTracks(); 181 try { 182 await track.applyConstraints(constraints); 183 } catch (err) { 184 assert_equals(err.name, 'OverconstrainedError', err.message); 185 assert_equals(err.constraint, Object.keys(constraints)[0], "Constraint is set"); 186 return; 187 } 188 assert_unreached('applyConstraints hould have failed'); 189 }, `applyConstraints(${j(constraints)}) for display media must fail with OverconstrainedError`)); 190 191 // Content shell picks a fake desktop device by default. 192 promise_test(async t => { 193 const stream = await getDisplayMedia({video: true}); 194 t.add_cleanup(() => stopTracks(stream)); 195 assert_equals(stream.getVideoTracks().length, 1); 196 const track = stream.getVideoTracks()[0]; 197 assert_equals(track.kind, "video"); 198 assert_equals(track.enabled, true); 199 assert_equals(track.readyState, "live"); 200 track.stop(); 201 assert_equals(track.readyState, "ended"); 202 }, 'getDisplayMedia() resolves with stream with video track'); 203 204 { 205 const displaySurfaces = ['monitor', 'window', 'browser']; 206 displaySurfaces.forEach((displaySurface) => { 207 promise_test(async t => { 208 const stream = await getDisplayMedia({video: {displaySurface}}); 209 t.add_cleanup(() => stopTracks(stream)); 210 const settings = stream.getVideoTracks()[0].getSettings(); 211 assert_equals(settings.displaySurface, displaySurface); 212 assert_any(assert_equals, settings.logicalSurface, [true, false]); 213 assert_any(assert_equals, settings.cursor, ['never', 'always', 'motion']); 214 assert_false("suppressLocalAudioPlayback" in settings); 215 }, `getDisplayMedia({"video":{"displaySurface":"${displaySurface}"}}) with getSettings`); 216 }) 217 } 218 219 { 220 const properties = ["displaySurface"]; 221 properties.forEach((property) => { 222 test(() => { 223 const supportedConstraints = 224 navigator.mediaDevices.getSupportedConstraints(); 225 assert_true(supportedConstraints[property]); 226 }, property + " is supported"); 227 }); 228 } 229 230 [ 231 {video: {displaySurface: "monitor"}}, 232 {video: {displaySurface: "window"}}, 233 {video: {displaySurface: "browser"}}, 234 {selfBrowserSurface: "include"}, 235 {selfBrowserSurface: "exclude"}, 236 {surfaceSwitching: "include"}, 237 {surfaceSwitching: "exclude"}, 238 {systemAudio: "include"}, 239 {systemAudio: "exclude"}, 240 {windowAudio: "exclude"}, 241 {windowAudio: "window"}, 242 {windowAudio: "system"}, 243 ].forEach(constraints => promise_test(async t => { 244 const stream = await getDisplayMedia(constraints); 245 t.add_cleanup(() => stopTracks(stream)); 246 }, `getDisplayMedia(${j(constraints)}) must succeed`)); 247 248 [ 249 {selfBrowserSurface: "invalid"}, 250 {surfaceSwitching: "invalid"}, 251 {systemAudio: "invalid"}, 252 {windowAudio: "invalid"}, 253 {monitorTypeSurfaces: "invalid"}, 254 ].forEach(constraints => promise_test(async t => { 255 await test_driver.bless('getDisplayMedia()'); 256 const p = navigator.mediaDevices.getDisplayMedia(constraints); 257 t.add_cleanup(async () => { 258 try { stopTracks(await p) } catch {} 259 }); 260 await promise_rejects_js( 261 t, TypeError, Promise.race([p, Promise.resolve()]), 262 'getDisplayMedia should have returned an already-rejected promise.'); 263 }, `getDisplayMedia(${j(constraints)}) must fail with TypeError`)); 264 265 test(() => { 266 const supportedConstraints = 267 navigator.mediaDevices.getSupportedConstraints(); 268 assert_true(supportedConstraints.suppressLocalAudioPlayback); 269 }, "suppressLocalAudioPlayback is supported"); 270 271 { 272 const suppressLocalAudioPlaybacks = [true, false]; 273 suppressLocalAudioPlaybacks.forEach((suppressLocalAudioPlayback) => { 274 promise_test(async (t) => { 275 const stream = await getDisplayMedia({ 276 audio: { suppressLocalAudioPlayback }, 277 }); 278 t.add_cleanup(() => stopTracks(stream)); 279 const [videoTrack] = stream.getVideoTracks(); 280 assert_false("suppressLocalAudioPlayback" in videoTrack.getSettings()); 281 const [audioTrack] = stream.getAudioTracks(); 282 const audioTrackSettings = audioTrack.getSettings(); 283 assert_true("suppressLocalAudioPlayback" in audioTrackSettings); 284 assert_equals( 285 audioTrackSettings.suppressLocalAudioPlayback, 286 suppressLocalAudioPlayback 287 ); 288 await audioTrack.applyConstraints(); 289 assert_true("suppressLocalAudioPlayback" in audioTrackSettings); 290 assert_equals( 291 audioTrackSettings.suppressLocalAudioPlayback, 292 suppressLocalAudioPlayback 293 ); 294 }, `getDisplayMedia({"audio":{"suppressLocalAudioPlayback":${suppressLocalAudioPlayback}}}) with getSettings`); 295 }); 296 } 297 298 promise_test(async t => { 299 const stream = await getDisplayMedia({video: true}); 300 t.add_cleanup(() => stopTracks(stream)); 301 const capabilities = stream.getVideoTracks()[0].getCapabilities(); 302 assert_any( 303 assert_equals, capabilities.displaySurface, 304 ['monitor', 'window', 'browser']); 305 }, 'getDisplayMedia() with getCapabilities'); 306 307 promise_test(async (t) => { 308 const constraints = { 309 video: { displaySurface: "monitor" }, 310 monitorTypeSurfaces: "exclude", 311 }; 312 await test_driver.bless('getDisplayMedia()'); 313 const p = navigator.mediaDevices.getDisplayMedia(constraints); 314 t.add_cleanup(async () => { 315 try { stopTracks(await p) } catch {} 316 }); 317 await promise_rejects_js( 318 t, TypeError, Promise.race([p, Promise.resolve()]), 319 'getDisplayMedia should have returned an already-rejected promise.'); 320 }, `getDisplayMedia({"video":{"displaySurface":"monitor"},"monitorTypeSurfaces":"exclude"}) rejects with TypeError`); 321 322 promise_test(async (t) => { 323 const stream = await getDisplayMedia({ 324 video: { displaySurface: "monitor" }, 325 monitorTypeSurfaces: "include", 326 }); 327 t.add_cleanup(() => stopTracks(stream)); 328 const { displaySurface } = stream.getTracks()[0].getSettings(); 329 assert_equals(displaySurface, "monitor"); 330 }, `getDisplayMedia({"video":{"displaySurface":"monitor"},"monitorTypeSurfaces":"include"}) resolves with a monitor track`); 331 332 promise_test(async (t) => { 333 const stream = await getDisplayMedia({ 334 monitorTypeSurfaces: "exclude", 335 }); 336 t.add_cleanup(() => stopTracks(stream)); 337 const { displaySurface } = stream.getTracks()[0].getSettings(); 338 assert_any(assert_equals, displaySurface, ["window", "browser"]); 339 }, `getDisplayMedia({"monitorTypeSurfaces":"exclude"}) resolves with a non monitor track`); 340 341 </script>