Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that disables it given conditions:
- os == "android" : bug 1550895 (frequently fails on geckoview)
- (os == "mac") and (os_version == "14.70") and (processor == "x86_64") and debug : Bug 1931210
- (os == "mac") and (os_version == "14.70") and (processor == "x86_64") and not debug : Bug 1931210
- This test has a WPT meta file that expects 16 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /screen-capture/getdisplaymedia.https.html - WPT Dashboard Interop Dashboard
<!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}}},
].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"},
{monitorTypeSurfaces: "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');
promise_test(async (t) => {
const constraints = {
video: { displaySurface: "monitor" },
monitorTypeSurfaces: "exclude",
};
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({"video":{"displaySurface":"monitor"},"monitorTypeSurfaces":"exclude"}) rejects with TypeError`);
promise_test(async (t) => {
const stream = await getDisplayMedia({
video: { displaySurface: "monitor" },
monitorTypeSurfaces: "include",
});
t.add_cleanup(() => stopTracks(stream));
const { displaySurface } = stream.getTracks()[0].getSettings();
assert_equals(displaySurface, "monitor");
}, `getDisplayMedia({"video":{"displaySurface":"monitor"},"monitorTypeSurfaces":"include"}) resolves with a monitor track`);
promise_test(async (t) => {
const stream = await getDisplayMedia({
monitorTypeSurfaces: "exclude",
});
t.add_cleanup(() => stopTracks(stream));
const { displaySurface } = stream.getTracks()[0].getSettings();
assert_any(assert_equals, displaySurface, ["window", "browser"]);
}, `getDisplayMedia({"monitorTypeSurfaces":"exclude"}) resolves with a non monitor track`);
</script>