mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Bug 1321221 - Implement getDisplayMedia() and update related wpt tests. r=pehrsons,smaug
Differential Revision: https://phabricator.services.mozilla.com/D15058 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
ddd69f99e8
commit
c8bbcb8047
@ -129,6 +129,33 @@ already_AddRefed<Promise> MediaDevices::EnumerateDevices(CallerType aCallerType,
|
||||
return p.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> MediaDevices::GetDisplayMedia(
|
||||
const DisplayMediaStreamConstraints& aConstraints, CallerType aCallerType,
|
||||
ErrorResult& aRv) {
|
||||
RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
RefPtr<MediaDevices> self(this);
|
||||
MediaManager::Get()
|
||||
->GetDisplayMedia(GetOwner(), aConstraints, aCallerType)
|
||||
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
||||
[this, self, p](RefPtr<DOMMediaStream>&& aStream) {
|
||||
if (!GetWindowIfCurrent()) {
|
||||
return; // leave promise pending after navigation.
|
||||
}
|
||||
p->MaybeResolve(std::move(aStream));
|
||||
},
|
||||
[this, self, p](RefPtr<MediaMgrError>&& error) {
|
||||
nsPIDOMWindowInner* window = GetWindowIfCurrent();
|
||||
if (!window) {
|
||||
return; // leave promise pending after navigation.
|
||||
}
|
||||
p->MaybeReject(MakeRefPtr<MediaStreamError>(window, *error));
|
||||
});
|
||||
return p.forget();
|
||||
}
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(MediaDevices, DOMEventTargetHelper)
|
||||
NS_IMPL_RELEASE_INHERITED(MediaDevices, DOMEventTargetHelper)
|
||||
NS_INTERFACE_MAP_BEGIN(MediaDevices)
|
||||
|
@ -17,6 +17,7 @@ namespace dom {
|
||||
|
||||
class Promise;
|
||||
struct MediaStreamConstraints;
|
||||
struct DisplayMediaStreamConstraints;
|
||||
struct MediaTrackSupportedConstraints;
|
||||
|
||||
#define MOZILLA_DOM_MEDIADEVICES_IMPLEMENTATION_IID \
|
||||
@ -48,6 +49,10 @@ class MediaDevices final : public DOMEventTargetHelper,
|
||||
already_AddRefed<Promise> EnumerateDevices(CallerType aCallerType,
|
||||
ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise> GetDisplayMedia(
|
||||
const DisplayMediaStreamConstraints& aConstraints, CallerType aCallerType,
|
||||
ErrorResult& aRv);
|
||||
|
||||
virtual void OnDeviceChange() override;
|
||||
|
||||
mozilla::dom::EventHandlerNonNull* GetOndevicechange();
|
||||
|
@ -198,6 +198,9 @@ LazyLogModule gMediaManagerLog("MediaManager");
|
||||
|
||||
using dom::BasicTrackSource;
|
||||
using dom::ConstrainDOMStringParameters;
|
||||
using dom::ConstrainDoubleRange;
|
||||
using dom::ConstrainLongRange;
|
||||
using dom::DisplayMediaStreamConstraints;
|
||||
using dom::File;
|
||||
using dom::GetUserMediaRequest;
|
||||
using dom::MediaSourceEnum;
|
||||
@ -2849,6 +2852,92 @@ RefPtr<MediaManager::StreamPromise> MediaManager::GetUserMedia(
|
||||
});
|
||||
};
|
||||
|
||||
RefPtr<MediaManager::StreamPromise> MediaManager::GetDisplayMedia(
|
||||
nsPIDOMWindowInner* aWindow,
|
||||
const DisplayMediaStreamConstraints& aConstraintsPassedIn,
|
||||
dom::CallerType aCallerType) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aWindow);
|
||||
|
||||
if (!IsOn(aConstraintsPassedIn.mVideo)) {
|
||||
return StreamPromise::CreateAndReject(
|
||||
MakeRefPtr<MediaMgrError>(MediaMgrError::Name::TypeError,
|
||||
NS_LITERAL_STRING("video is required")),
|
||||
__func__);
|
||||
}
|
||||
|
||||
MediaStreamConstraints c;
|
||||
auto& vc = c.mVideo.SetAsMediaTrackConstraints();
|
||||
|
||||
if (aConstraintsPassedIn.mVideo.IsMediaTrackConstraints()) {
|
||||
vc = aConstraintsPassedIn.mVideo.GetAsMediaTrackConstraints();
|
||||
if (vc.mAdvanced.WasPassed()) {
|
||||
return StreamPromise::CreateAndReject(
|
||||
MakeRefPtr<MediaMgrError>(MediaMgrError::Name::TypeError,
|
||||
NS_LITERAL_STRING("advanced not allowed")),
|
||||
__func__);
|
||||
}
|
||||
auto getCLR = [](const auto& aCon) -> const ConstrainLongRange& {
|
||||
static ConstrainLongRange empty;
|
||||
return (aCon.WasPassed() && !aCon.Value().IsLong())
|
||||
? aCon.Value().GetAsConstrainLongRange()
|
||||
: empty;
|
||||
};
|
||||
auto getCDR = [](auto&& aCon) -> const ConstrainDoubleRange& {
|
||||
static ConstrainDoubleRange empty;
|
||||
return (aCon.WasPassed() && !aCon.Value().IsDouble())
|
||||
? aCon.Value().GetAsConstrainDoubleRange()
|
||||
: empty;
|
||||
};
|
||||
const auto& w = getCLR(vc.mWidth);
|
||||
const auto& h = getCLR(vc.mHeight);
|
||||
const auto& f = getCDR(vc.mFrameRate);
|
||||
if (w.mMin.WasPassed() || h.mMin.WasPassed() || f.mMin.WasPassed()) {
|
||||
return StreamPromise::CreateAndReject(
|
||||
MakeRefPtr<MediaMgrError>(MediaMgrError::Name::TypeError,
|
||||
NS_LITERAL_STRING("min not allowed")),
|
||||
__func__);
|
||||
}
|
||||
if (w.mExact.WasPassed() || h.mExact.WasPassed() || f.mExact.WasPassed()) {
|
||||
return StreamPromise::CreateAndReject(
|
||||
MakeRefPtr<MediaMgrError>(MediaMgrError::Name::TypeError,
|
||||
NS_LITERAL_STRING("exact not allowed")),
|
||||
__func__);
|
||||
}
|
||||
// As a UA optimization, we fail early without incurring a prompt, on
|
||||
// known-to-fail constraint values that don't reveal anything about the
|
||||
// user's system.
|
||||
const char* badConstraint = nullptr;
|
||||
if (w.mMax.WasPassed() && w.mMax.Value() < 1) {
|
||||
badConstraint = "width";
|
||||
}
|
||||
if (h.mMax.WasPassed() && h.mMax.Value() < 1) {
|
||||
badConstraint = "height";
|
||||
}
|
||||
if (f.mMax.WasPassed() && f.mMax.Value() < 1) {
|
||||
badConstraint = "frameRate";
|
||||
}
|
||||
if (badConstraint) {
|
||||
return StreamPromise::CreateAndReject(
|
||||
MakeRefPtr<MediaMgrError>(MediaMgrError::Name::OverconstrainedError,
|
||||
NS_LITERAL_STRING(""),
|
||||
NS_ConvertASCIItoUTF16(badConstraint)),
|
||||
__func__);
|
||||
}
|
||||
}
|
||||
// We ask for "screen" sharing.
|
||||
//
|
||||
// If this is a privileged call or permission is disabled, this gives us full
|
||||
// screen sharing by default, which is useful for internal testing.
|
||||
//
|
||||
// If this is a non-priviliged call, GetUserMedia() will change it to "window"
|
||||
// for us.
|
||||
vc.mMediaSource.AssignASCII(EnumToASCII(dom::MediaSourceEnumValues::strings,
|
||||
MediaSourceEnum::Screen));
|
||||
|
||||
return MediaManager::GetUserMedia(aWindow, c, aCallerType);
|
||||
}
|
||||
|
||||
/* static */ void MediaManager::AnonymizeDevices(MediaDeviceSet& aDevices,
|
||||
const nsACString& aOriginKey) {
|
||||
if (!aOriginKey.IsEmpty()) {
|
||||
|
@ -237,6 +237,11 @@ class MediaManager final : public nsIMediaManagerService,
|
||||
nsresult EnumerateDevices(nsPIDOMWindowInner* aWindow,
|
||||
dom::Promise& aPromise);
|
||||
|
||||
RefPtr<StreamPromise> GetDisplayMedia(
|
||||
nsPIDOMWindowInner* aWindow,
|
||||
const dom::DisplayMediaStreamConstraints& aConstraintsPassedIn,
|
||||
dom::CallerType aCallerType);
|
||||
|
||||
// Get the sink that corresponds to the given device id.
|
||||
// It is resposible to check if an application is
|
||||
// authorized to play audio through the requested device.
|
||||
|
@ -21,4 +21,7 @@ interface MediaDevices : EventTarget {
|
||||
|
||||
[Throws, NeedsCallerType]
|
||||
Promise<MediaStream> getUserMedia(optional MediaStreamConstraints constraints);
|
||||
|
||||
[SecureContext, Throws, NeedsCallerType]
|
||||
Promise<MediaStream> getDisplayMedia(optional DisplayMediaStreamConstraints constraints);
|
||||
};
|
||||
|
@ -23,6 +23,11 @@ dictionary MediaStreamConstraints {
|
||||
DOMString? peerIdentity = null;
|
||||
};
|
||||
|
||||
dictionary DisplayMediaStreamConstraints {
|
||||
(boolean or MediaTrackConstraints) video = true;
|
||||
(boolean or MediaTrackConstraints) audio = false;
|
||||
};
|
||||
|
||||
[Exposed=Window,
|
||||
Constructor,
|
||||
Constructor (MediaStream stream),
|
||||
|
2
testing/web-platform/meta/screen-capture/__dir__.ini
Normal file
2
testing/web-platform/meta/screen-capture/__dir__.ini
Normal file
@ -0,0 +1,2 @@
|
||||
prefs: [media.navigator.permission.disabled:true, media.navigator.streams.fake:true, dom.security.featurePolicy.enabled:true, dom.security.featurePolicy.header.enabled:true, dom.security.featurePolicy.webidl.enabled:true]
|
||||
lsan-allowed: [Alloc, NewEmptyScopeData, XPCWrappedNative::GetNewOrUsed, js_new, mozilla::BasePrincipal::CreateCodebasePrincipal, mozilla::dom::ChromeUtils::GenerateQI, nsStringBuffer::Alloc]
|
@ -1,46 +1,75 @@
|
||||
[getdisplaymedia.https.html]
|
||||
[getDisplayMedia() call with non-bool constraint]
|
||||
expected: FAIL
|
||||
[getDisplayMedia({"video":true}) must succeed with video]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
PASS
|
||||
|
||||
[getDisplayMedia() with audio false]
|
||||
expected: FAIL
|
||||
[getDisplayMedia({"video":true,"audio":false}) must succeed with video]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
PASS
|
||||
|
||||
[getDisplayMedia() with video false]
|
||||
expected: FAIL
|
||||
[getDisplayMedia({"audio":false}) must succeed with video]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
PASS
|
||||
|
||||
[getDisplayMedia() with no constraints]
|
||||
expected: FAIL
|
||||
[getDisplayMedia({}) must succeed with video]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
PASS
|
||||
|
||||
[getDisplayMedia() with audio true]
|
||||
expected: FAIL
|
||||
[getDisplayMedia(undefined) must succeed with video]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
PASS
|
||||
|
||||
[getDisplayMedia() with video true]
|
||||
expected: FAIL
|
||||
[getDisplayMedia({"video":true,"audio":true}) must succeed with video maybe audio]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
PASS
|
||||
|
||||
[getDisplayMedia({"audio":true}) must succeed with video maybe audio]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
PASS
|
||||
|
||||
[getDisplayMedia({"video":{"width":{"max":360}}}) must be constrained]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
FAIL
|
||||
|
||||
[getDisplayMedia({"video":{"height":{"max":240}}}) must be constrained]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
FAIL
|
||||
|
||||
[getDisplayMedia({"video":{"width":{"max":360},"height":{"max":240}}}) must be constrained]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
FAIL
|
||||
|
||||
[getDisplayMedia({"video":{"frameRate":{"max":4}}}) must be constrained]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
FAIL
|
||||
|
||||
[getDisplayMedia({"video":{"frameRate":{"max":4},"width":{"max":360}}}) must be constrained]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
FAIL
|
||||
|
||||
[getDisplayMedia({"video":{"frameRate":{"max":4},"height":{"max":240}}}) must be constrained]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
FAIL
|
||||
|
||||
[getDisplayMedia({"video":{"frameRate":{"max":4},"width":{"max":360},"height":{"max":240}}}) must be constrained]
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
FAIL
|
||||
|
||||
[getDisplayMedia() with getSettings]
|
||||
expected: FAIL
|
||||
|
||||
[getDisplayMedia() call with max constraint]
|
||||
expected: FAIL
|
||||
|
||||
[getDisplayMedia() call with exact constraint]
|
||||
expected: FAIL
|
||||
|
||||
[getDisplayMedia() call with min constraint]
|
||||
expected: FAIL
|
||||
|
||||
[getDisplayMedia() call with advanced constraint]
|
||||
expected: FAIL
|
||||
|
||||
[getDisplayMedia() with constraints applied]
|
||||
expected: FAIL
|
||||
|
||||
[getDisplayMedia() with max constraint]
|
||||
expected: FAIL
|
||||
|
||||
[getDisplayMedia() overconstrained]
|
||||
expected: FAIL
|
||||
|
||||
[getDisplayMedia in navigator.mediaDevices]
|
||||
expected: FAIL
|
||||
|
||||
expected:
|
||||
if os == "android": FAIL
|
||||
FAIL
|
||||
|
@ -9,133 +9,101 @@ test(() => {
|
||||
assert_idl_attribute(navigator.mediaDevices, 'getDisplayMedia');
|
||||
}, "getDisplayMedia in navigator.mediaDevices");
|
||||
|
||||
promise_test(async t => {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia({video: true});
|
||||
const [track] = stream.getTracks();
|
||||
t.add_cleanup(() => track.stop());
|
||||
const stopTracks = stream => stream.getTracks().forEach(track => track.stop());
|
||||
const j = obj => JSON.stringify(obj);
|
||||
|
||||
[
|
||||
{video: true},
|
||||
{video: true, audio: false},
|
||||
{audio: false},
|
||||
{},
|
||||
undefined
|
||||
].forEach(constraints => promise_test(async t => {
|
||||
const stream = await navigator.mediaDevices.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() with video true');
|
||||
}, `getDisplayMedia(${j(constraints)}) must succeed with video`));
|
||||
|
||||
// Empty constraint parameter and boolean values of false defaults to
|
||||
// {video: true}. This is described in items 3-4 of section 5.1, see
|
||||
// https://w3c.github.io/mediacapture-screen-share/#navigator-additions.
|
||||
// Note that this results in some non-intuitive cases returning a video track,
|
||||
// i.e. {video: false}.
|
||||
promise_test(async t => {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia();
|
||||
const [track] = stream.getTracks();
|
||||
t.add_cleanup(() => track.stop());
|
||||
assert_equals(stream.getTracks().length, 1);
|
||||
assert_equals(stream.getVideoTracks().length, 1);
|
||||
assert_equals(stream.getAudioTracks().length, 0);
|
||||
}, 'getDisplayMedia() with no constraints');
|
||||
|
||||
promise_test(async t => {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia({video: false});
|
||||
const [track] = stream.getTracks();
|
||||
t.add_cleanup(() => track.stop());
|
||||
assert_equals(stream.getTracks().length, 1);
|
||||
assert_equals(stream.getVideoTracks().length, 1);
|
||||
assert_equals(stream.getAudioTracks().length, 0);
|
||||
}, 'getDisplayMedia() with video false');
|
||||
|
||||
promise_test(async t => {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia({audio: false});
|
||||
const [track] = stream.getTracks();
|
||||
t.add_cleanup(() => track.stop());
|
||||
assert_equals(stream.getTracks().length, 1);
|
||||
assert_equals(stream.getVideoTracks().length, 1);
|
||||
assert_equals(stream.getAudioTracks().length, 0);
|
||||
}, 'getDisplayMedia() with audio false');
|
||||
|
||||
promise_test(async t => {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia({audio: true});
|
||||
const [track] = stream.getTracks();
|
||||
t.add_cleanup(() => track.stop());
|
||||
assert_equals(stream.getTracks().length, 1);
|
||||
assert_equals(stream.getVideoTracks().length, 0);
|
||||
assert_equals(stream.getAudioTracks().length, 1);
|
||||
}, 'getDisplayMedia() with audio true');
|
||||
|
||||
promise_test(async t => {
|
||||
[
|
||||
{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 => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia(
|
||||
{video: {advanced: [{zoom: 1}]}});
|
||||
stopTracks(await navigator.mediaDevices.getDisplayMedia(constraints));
|
||||
} catch (err) {
|
||||
assert_equals(err.name, 'TypeError');
|
||||
assert_equals(err.name, 'TypeError', err.message);
|
||||
return;
|
||||
}
|
||||
assert_unreached('getDisplayMedia should have failed');
|
||||
}, 'getDisplayMedia() with advanced constraint');
|
||||
}, `getDisplayMedia(${j(constraints)}) must fail with TypeError`));
|
||||
|
||||
promise_test(async t => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia(
|
||||
{video: {width: {min: 360}}});
|
||||
} catch (err) {
|
||||
assert_equals(err.name, 'TypeError');
|
||||
return;
|
||||
}
|
||||
assert_unreached('getDisplayMedia should have failed');
|
||||
}, 'getDisplayMedia() with min constraint');
|
||||
|
||||
promise_test(async t => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia(
|
||||
{video: {width: {exact: 360}}});
|
||||
} catch (err) {
|
||||
assert_equals(err.name, 'TypeError');
|
||||
return;
|
||||
}
|
||||
assert_unreached('getDisplayMedia should have failed');
|
||||
}, 'getDisplayMedia() with exact constraint');
|
||||
|
||||
promise_test(async t => {
|
||||
const maxWidth = 360;
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia(
|
||||
{video: {width: {max: maxWidth}}});
|
||||
const [track] = stream.getTracks();
|
||||
t.add_cleanup(() => track.stop());
|
||||
[
|
||||
{video: true, audio: true},
|
||||
{audio: true},
|
||||
].forEach(constraints => promise_test(async t => {
|
||||
const stream = await navigator.mediaDevices.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_equals(stream.getAudioTracks().length, 0);
|
||||
assert_less_than_equal(
|
||||
stream.getVideoTracks()[0].getSettings().width, maxWidth);
|
||||
}, 'getDisplayMedia() with max constraint');
|
||||
assert_less_than_equal(stream.getAudioTracks().length, 1);
|
||||
}, `getDisplayMedia(${j(constraints)}) must succeed with video maybe audio`));
|
||||
|
||||
promise_test(async t => {
|
||||
const maxWidth = 360;
|
||||
const maxFrameRate = 4;
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia(
|
||||
{video: {width: {max: maxWidth}, frameRate: {max: maxFrameRate}}});
|
||||
const [track] = stream.getTracks();
|
||||
t.add_cleanup(() => track.stop());
|
||||
assert_equals(stream.getVideoTracks().length, 1);
|
||||
assert_equals(stream.getAudioTracks().length, 0);
|
||||
const settings = stream.getVideoTracks()[0].getSettings();
|
||||
assert_less_than_equal(settings.width, maxWidth);
|
||||
assert_less_than_equal(settings.frameRate, maxFrameRate);
|
||||
}, 'getDisplayMedia() with constraints applied');
|
||||
[
|
||||
{video: {width: {max: 360}}},
|
||||
{video: {height: {max: 240}}},
|
||||
{video: {width: {max: 360}, height: {max: 240}}},
|
||||
{video: {frameRate: {max: 4}}},
|
||||
{video: {frameRate: {max: 4}, width: {max: 360}}},
|
||||
{video: {frameRate: {max: 4}, height: {max: 240}}},
|
||||
{video: {frameRate: {max: 4}, width: {max: 360}, height: {max: 240}}},
|
||||
].forEach(constraints => promise_test(async t => {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia(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(${j(constraints)}) must be constrained`));
|
||||
|
||||
promise_test(async t => {
|
||||
[
|
||||
{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 {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia(
|
||||
{video: {width: {max: 0}}});
|
||||
stopTracks(await navigator.mediaDevices.getDisplayMedia(constraints));
|
||||
} catch (err) {
|
||||
assert_equals(err.name, 'OverconstrainedError');
|
||||
assert_equals(err.name, 'OverconstrainedError', err.message);
|
||||
return;
|
||||
}
|
||||
assert_unreached('getDisplayMedia should have failed');
|
||||
}, 'getDisplayMedia() overconstrained');
|
||||
}, `getDisplayMedia(${j(constraints)}) must fail with OverconstrainedError`));
|
||||
|
||||
// Content shell picks a fake desktop device by default.
|
||||
promise_test(async t => {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia({video: true});
|
||||
const [track] = stream.getTracks();
|
||||
t.add_cleanup(() => track.stop());
|
||||
assert_equals(stream.getVideoTracks().length, 1);
|
||||
assert_equals(stream.getAudioTracks().length, 0);
|
||||
t.add_cleanup(() => stopTracks(stream));
|
||||
const settings = stream.getVideoTracks()[0].getSettings();
|
||||
assert_any(
|
||||
assert_equals, settings.displaySurface,
|
||||
|
Loading…
Reference in New Issue
Block a user