<!doctype html> <meta charset=utf-8> <title>getDisplayMedia</title> <meta name="timeout" content="long"> <button id="button">User gesture</button> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="/resources/testdriver.js"></script> <script src="/resources/testdriver-vendor.js"></script> <script> 'use strict'; test(() => { assert_idl_attribute(navigator.mediaDevices, 'getDisplayMedia'); }, "getDisplayMedia in navigator.mediaDevices"); const stopTracks = stream => stream.getTracks().forEach(track => track.stop()); const j = obj => JSON.stringify(obj); async function getDisplayMedia(constraints) { const p = new Promise(r => button.onclick = r); await test_driver.click(button); await p; return navigator.mediaDevices.getDisplayMedia(constraints); } promise_test(t => { const p = navigator.mediaDevices.getDisplayMedia({video: true}); t.add_cleanup(async () => { try { stopTracks(await p) } catch {} }); // Race a settled promise to check that the returned promise is already // rejected. return promise_rejects_dom( t, 'InvalidStateError', Promise.race([p, Promise.resolve()]), 'getDisplayMedia should have returned an already-rejected promise.'); }, `getDisplayMedia() must require user activation`); [ {video: true}, {video: true, audio: false}, {video: {}}, {audio: false}, {}, undefined ].forEach(constraints => promise_test(async t => { const stream = await getDisplayMedia(constraints); t.add_cleanup(() => stopTracks(stream)); assert_equals(stream.getTracks().length, 1); assert_equals(stream.getVideoTracks().length, 1); assert_equals(stream.getAudioTracks().length, 0); }, `getDisplayMedia(${j(constraints)}) must succeed with video`)); [ {video: false}, {video: {advanced: [{width: 320}]}}, {video: {width: {min: 320}}}, {video: {width: {exact: 320}}}, {video: {height: {min: 240}}}, {video: {height: {exact: 240}}}, {video: {frameRate: {min: 4}}}, {video: {frameRate: {exact: 4}}}, {video: false, audio: true}, ].forEach(constraints => promise_test(async t => { await test_driver.bless('getDisplayMedia()'); const p = navigator.mediaDevices.getDisplayMedia(constraints); t.add_cleanup(async () => { try { stopTracks(await p) } catch {} }); await promise_rejects_js( t, TypeError, Promise.race([p, Promise.resolve()]), 'getDisplayMedia should have returned an already-rejected promise.'); }, `getDisplayMedia(${j(constraints)}) must fail with TypeError`)); [ {video: true, audio: true}, {audio: true}, ].forEach(constraints => promise_test(async t => { const stream = await getDisplayMedia(constraints); t.add_cleanup(() => stopTracks(stream)); assert_greater_than_equal(stream.getTracks().length, 1); assert_less_than_equal(stream.getTracks().length, 2); assert_equals(stream.getVideoTracks().length, 1); assert_less_than_equal(stream.getAudioTracks().length, 1); }, `getDisplayMedia(${j(constraints)}) must succeed with video maybe audio`)); [ {width: {max: 360}}, {height: {max: 240}}, {width: {max: 360}, height: {max: 240}}, {frameRate: {max: 4}}, {frameRate: {max: 4}, width: {max: 360}}, {frameRate: {max: 4}, height: {max: 240}}, {frameRate: {max: 4}, width: {max: 360}, height: {max: 240}}, ].forEach(constraints => promise_test(async t => { const stream = await getDisplayMedia({video: constraints}); t.add_cleanup(() => stopTracks(stream)); const {width, height, frameRate} = stream.getTracks()[0].getSettings(); assert_greater_than_equal(width, 1); assert_greater_than_equal(height, 1); assert_greater_than_equal(frameRate, 1); if (constraints.width) { assert_less_than_equal(width, constraints.width.max); } if (constraints.height) { assert_less_than_equal(height, constraints.height.max); } if (constraints.frameRate) { assert_less_than_equal(frameRate, constraints.frameRate.max); } }, `getDisplayMedia({video: ${j(constraints)}}) must be constrained`)); const someSizes = [ {width: 160}, {height: 120}, {width: 80}, {height: 60}, {width: 158}, {height: 118}, ]; someSizes.forEach(constraints => promise_test(async t => { const stream = await getDisplayMedia({video: constraints}); t.add_cleanup(() => stopTracks(stream)); const {width, height, frameRate} = stream.getTracks()[0].getSettings(); if (constraints.width) { assert_equals(width, constraints.width); } else { assert_equals(height, constraints.height); } assert_greater_than_equal(frameRate, 1); }, `getDisplayMedia({video: ${j(constraints)}}) must be downscaled precisely`)); promise_test(async t => { const video = {height: 240}; const stream = await getDisplayMedia({video}); t.add_cleanup(() => stopTracks(stream)); const [track] = stream.getVideoTracks(); const {height} = track.getSettings(); assert_equals(height, video.height); for (const constraints of someSizes) { await track.applyConstraints(constraints); const {width, height} = track.getSettings(); if (constraints.width) { assert_equals(width, constraints.width); } else { assert_equals(height, constraints.height); } } }, `applyConstraints(width or height) must downscale precisely`); [ {video: {width: {max: 0}}}, {video: {height: {max: 0}}}, {video: {frameRate: {max: 0}}}, {video: {width: {max: -1}}}, {video: {height: {max: -1}}}, {video: {frameRate: {max: -1}}}, ].forEach(constraints => promise_test(async t => { try { stopTracks(await getDisplayMedia(constraints)); } catch (err) { assert_equals(err.name, 'OverconstrainedError', err.message); return; } assert_unreached('getDisplayMedia should have failed'); }, `getDisplayMedia(${j(constraints)}) must fail with OverconstrainedError`)); // Content shell picks a fake desktop device by default. promise_test(async t => { const stream = await getDisplayMedia({video: true}); t.add_cleanup(() => stopTracks(stream)); assert_equals(stream.getVideoTracks().length, 1); const track = stream.getVideoTracks()[0]; assert_equals(track.kind, "video"); assert_equals(track.enabled, true); assert_equals(track.readyState, "live"); track.stop(); assert_equals(track.readyState, "ended"); }, 'getDisplayMedia() resolves with stream with video track'); { const displaySurfaces = ['monitor', 'window', 'browser']; displaySurfaces.forEach((displaySurface) => { promise_test(async t => { const stream = await getDisplayMedia({video: {displaySurface}}); t.add_cleanup(() => stopTracks(stream)); const settings = stream.getVideoTracks()[0].getSettings(); assert_equals(settings.displaySurface, displaySurface); assert_any(assert_equals, settings.logicalSurface, [true, false]); assert_any(assert_equals, settings.cursor, ['never', 'always', 'motion']); assert_false("suppressLocalAudioPlayback" in settings); }, `getDisplayMedia({"video":{"displaySurface":"${displaySurface}"}}) with getSettings`); }) } { const properties = ["displaySurface"]; properties.forEach((property) => { test(() => { const supportedConstraints = navigator.mediaDevices.getSupportedConstraints(); assert_true(supportedConstraints[property]); }, property + " is supported"); }); } [ {video: {displaySurface: "monitor"}}, {video: {displaySurface: "window"}}, {video: {displaySurface: "browser"}}, {selfBrowserSurface: "include"}, {selfBrowserSurface: "exclude"}, {surfaceSwitching: "include"}, {surfaceSwitching: "exclude"}, {systemAudio: "include"}, {systemAudio: "exclude"}, ].forEach(constraints => promise_test(async t => { const stream = await getDisplayMedia(constraints); t.add_cleanup(() => stopTracks(stream)); }, `getDisplayMedia(${j(constraints)}) must succeed`)); [ {selfBrowserSurface: "invalid"}, {surfaceSwitching: "invalid"}, {systemAudio: "invalid"}, ].forEach(constraints => promise_test(async t => { await test_driver.bless('getDisplayMedia()'); const p = navigator.mediaDevices.getDisplayMedia(constraints); t.add_cleanup(async () => { try { stopTracks(await p) } catch {} }); await promise_rejects_js( t, TypeError, Promise.race([p, Promise.resolve()]), 'getDisplayMedia should have returned an already-rejected promise.'); }, `getDisplayMedia(${j(constraints)}) must fail with TypeError`)); test(() => { const supportedConstraints = navigator.mediaDevices.getSupportedConstraints(); assert_true(supportedConstraints.suppressLocalAudioPlayback); }, "suppressLocalAudioPlayback is supported"); { const suppressLocalAudioPlaybacks = [true, false]; suppressLocalAudioPlaybacks.forEach((suppressLocalAudioPlayback) => { promise_test(async (t) => { const stream = await getDisplayMedia({ audio: { suppressLocalAudioPlayback }, }); t.add_cleanup(() => stopTracks(stream)); const [videoTrack] = stream.getVideoTracks(); assert_false("suppressLocalAudioPlayback" in videoTrack.getSettings()); const [audioTrack] = stream.getAudioTracks(); const audioTrackSettings = audioTrack.getSettings(); assert_true("suppressLocalAudioPlayback" in audioTrackSettings); assert_equals( audioTrackSettings.suppressLocalAudioPlayback, suppressLocalAudioPlayback ); await audioTrack.applyConstraints(); assert_true("suppressLocalAudioPlayback" in audioTrackSettings); assert_equals( audioTrackSettings.suppressLocalAudioPlayback, suppressLocalAudioPlayback ); }, `getDisplayMedia({"audio":{"suppressLocalAudioPlayback":${suppressLocalAudioPlayback}}}) with getSettings`); }); } promise_test(async t => { const stream = await getDisplayMedia({video: true}); t.add_cleanup(() => stopTracks(stream)); const capabilities = stream.getVideoTracks()[0].getCapabilities(); assert_any( assert_equals, capabilities.displaySurface, ['monitor', 'window', 'browser']); }, 'getDisplayMedia() with getCapabilities'); </script>