Bug 1181896 - make gUM fail w/OverconstrainedError and candidate argument r=jesup

This commit is contained in:
Jan-Ivar Bruaroey 2015-09-18 14:04:41 -04:00
parent 1fedf527e6
commit 6dccc3ae57
4 changed files with 123 additions and 32 deletions

View File

@ -949,10 +949,26 @@ GetSources(MediaEngine *engine, dom::MediaSourceEnum aSrcType,
}
}
template<class DeviceType>
static bool
AreUnfitSettings(const MediaTrackConstraints &aConstraints,
nsTArray<nsRefPtr<DeviceType>>& aSources)
{
nsTArray<const MediaTrackConstraintSet*> aggregateConstraints;
aggregateConstraints.AppendElement(&aConstraints);
for (auto& source : aSources) {
if (source->GetBestFitnessDistance(aggregateConstraints) != UINT32_MAX) {
return false;
}
}
return true;
}
// Apply constrains to a supplied list of sources (removes items from the list)
template<class DeviceType>
static void
static const char*
SelectSettings(const MediaTrackConstraints &aConstraints,
nsTArray<nsRefPtr<DeviceType>>& aSources)
{
@ -963,6 +979,7 @@ SelectSettings(const MediaTrackConstraints &aConstraints,
// Stack constraintSets that pass, starting with the required one, because the
// whole stack must be re-satisfied each time a capability-set is ruled out
// (this avoids storing state or pushing algorithm into the lower-level code).
nsTArray<nsRefPtr<DeviceType>> unsatisfactory;
nsTArray<const MediaTrackConstraintSet*> aggregateConstraints;
aggregateConstraints.AppendElement(&c);
@ -971,6 +988,7 @@ SelectSettings(const MediaTrackConstraints &aConstraints,
for (uint32_t i = 0; i < aSources.Length();) {
uint32_t distance = aSources[i]->GetBestFitnessDistance(aggregateConstraints);
if (distance == UINT32_MAX) {
unsatisfactory.AppendElement(aSources[i]);
aSources.RemoveElementAt(i);
} else {
ordered.insert(std::pair<uint32_t, nsRefPtr<DeviceType>>(distance,
@ -978,6 +996,49 @@ SelectSettings(const MediaTrackConstraints &aConstraints,
++i;
}
}
if (!aSources.Length()) {
// None selected. The spec says to report a constraint that satisfies NONE
// of the sources. Unfortunately, this is a bit laborious to find out, and
// requires updating as new constraints are added!
if (c.mDeviceId.IsConstrainDOMStringParameters()) {
MediaTrackConstraints fresh;
fresh.mDeviceId = c.mDeviceId;
if (AreUnfitSettings(fresh, unsatisfactory)) {
return "deviceId";
}
}
if (c.mWidth.IsConstrainLongRange()) {
MediaTrackConstraints fresh;
fresh.mWidth = c.mWidth;
if (AreUnfitSettings(fresh, unsatisfactory)) {
return "width";
}
}
if (c.mHeight.IsConstrainLongRange()) {
MediaTrackConstraints fresh;
fresh.mHeight = c.mHeight;
if (AreUnfitSettings(fresh, unsatisfactory)) {
return "height";
}
}
if (c.mFrameRate.IsConstrainDoubleRange()) {
MediaTrackConstraints fresh;
fresh.mFrameRate = c.mFrameRate;
if (AreUnfitSettings(fresh, unsatisfactory)) {
return "frameRate";
}
}
if (c.mFacingMode.IsConstrainDOMStringParameters()) {
MediaTrackConstraints fresh;
fresh.mFacingMode = c.mFacingMode;
if (AreUnfitSettings(fresh, unsatisfactory)) {
return "facingMode";
}
}
return "";
}
// Order devices by shortest distance
for (auto& ordinal : ordered) {
aSources.RemoveElement(ordinal.second);
@ -1006,9 +1067,10 @@ SelectSettings(const MediaTrackConstraints &aConstraints,
}
}
}
return nullptr;
}
static bool
static const char*
SelectSettings(MediaStreamConstraints &aConstraints,
nsTArray<nsRefPtr<MediaDevice>>& aSources)
{
@ -1016,7 +1078,6 @@ SelectSettings(MediaStreamConstraints &aConstraints,
// a candidate set is overconstrained (zero members), we must split up the
// list into videos and audios, and put it back together again at the end.
bool overconstrained = false;
nsTArray<nsRefPtr<VideoDevice>> videos;
nsTArray<nsRefPtr<AudioDevice>> audios;
@ -1032,25 +1093,21 @@ SelectSettings(MediaStreamConstraints &aConstraints,
aSources.Clear();
MOZ_ASSERT(!aSources.Length());
const char* badConstraint = nullptr;
if (IsOn(aConstraints.mVideo)) {
SelectSettings(GetInvariant(aConstraints.mVideo), videos);
if (!videos.Length()) {
overconstrained = true;
}
badConstraint = SelectSettings(GetInvariant(aConstraints.mVideo), videos);
for (auto& video : videos) {
aSources.AppendElement(video);
}
}
if (IsOn(aConstraints.mAudio)) {
SelectSettings(GetInvariant(aConstraints.mAudio), audios);
if (!audios.Length()) {
overconstrained = true;
}
if (audios.Length() && IsOn(aConstraints.mAudio)) {
badConstraint = SelectSettings(GetInvariant(aConstraints.mAudio), audios);
for (auto& audio : audios) {
aSources.AppendElement(audio);
}
}
return !overconstrained;
return badConstraint;
}
/**
@ -1697,7 +1754,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
auto& vc = c.mVideo.GetAsMediaTrackConstraints();
videoType = StringToEnum(dom::MediaSourceEnumValues::strings,
vc.mMediaSource,
videoType);
dom::MediaSourceEnum::Other);
switch (videoType) {
case dom::MediaSourceEnum::Camera:
break;
@ -1740,7 +1797,10 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
case dom::MediaSourceEnum::Other:
default: {
nsRefPtr<MediaStreamError> error =
new MediaStreamError(aWindow, NS_LITERAL_STRING("NotFoundError"));
new MediaStreamError(aWindow,
NS_LITERAL_STRING("OverconstrainedError"),
NS_LITERAL_STRING(""),
NS_LITERAL_STRING("mediaSource"));
onFailure->OnError(error);
return NS_OK;
}
@ -1785,7 +1845,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
auto& ac = c.mAudio.GetAsMediaTrackConstraints();
audioType = StringToEnum(dom::MediaSourceEnumValues::strings,
ac.mMediaSource,
audioType);
dom::MediaSourceEnum::Other);
// Work around WebIDL default since spec uses same dictionary w/audio & video.
if (audioType == dom::MediaSourceEnum::Camera) {
audioType = dom::MediaSourceEnum::Microphone;
@ -1812,7 +1872,10 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
case dom::MediaSourceEnum::Other:
default: {
nsRefPtr<MediaStreamError> error =
new MediaStreamError(aWindow, NS_LITERAL_STRING("NotFoundError"));
new MediaStreamError(aWindow,
NS_LITERAL_STRING("OverconstrainedError"),
NS_LITERAL_STRING(""),
NS_LITERAL_STRING("mediaSource"));
onFailure->OnError(error);
return NS_OK;
}
@ -1905,8 +1968,19 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
}
// Apply any constraints. This modifies the list.
if (!SelectSettings(c, *devices)) {
const char* badConstraint = SelectSettings(c, *devices);
if (badConstraint) {
nsString constraint;
constraint.AssignASCII(badConstraint);
nsRefPtr<MediaStreamError> error =
new MediaStreamError(window,
NS_LITERAL_STRING("OverconstrainedError"),
NS_LITERAL_STRING(""),
constraint);
onFailure->OnError(error);
return;
}
if (!devices->Length()) {
nsRefPtr<MediaStreamError> error =
new MediaStreamError(window, NS_LITERAL_STRING("NotFoundError"));
onFailure->OnError(error);

View File

@ -28,8 +28,9 @@ BaseMediaMgrError::BaseMediaMgrError(const nsAString& aName,
} else if (mName.EqualsLiteral("InternalError")) {
mMessage.AssignLiteral("Internal error.");
} else if (mName.EqualsLiteral("NotSupportedError")) {
mMessage.AssignLiteral("Constraints with no audio or video in it are not "
"supported");
mMessage.AssignLiteral("The operation is not supported.");
} else if (mName.EqualsLiteral("OverconstrainedError")) {
mMessage.AssignLiteral("Constraints could be not satisfied.");
}
}
}

View File

@ -16,9 +16,13 @@ function mustSucceed(msg, f) {
e => is(e.name, null, msg + " must succeed: " + e.message));
}
function mustFailWith(msg, reason, f) {
return f().then(() => ok(false, msg + " must fail"),
e => is(e.name, reason, msg + " must fail: " + e.message));
function mustFailWith(msg, reason, constraint, f) {
return f().then(() => ok(false, msg + " must fail"), e => {
is(e.name, reason, msg + " must fail: " + e.message);
if (constraint) {
is(e.constraint, constraint, msg + " must fail w/correct constraint.");
}
});
}
var pushPrefs = (...p) => new Promise(r => SpecialPowers.pushPrefEnv({set: p}, r));
@ -49,13 +53,15 @@ runTest(() =>
audio: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
fake: true,
})))
.then(() => mustFailWith("unknown exact deviceId on video", "NotFoundError",
() => navigator.mediaDevices.getUserMedia({
.then(() => mustFailWith("unknown exact deviceId on video",
"OverconstrainedError", "deviceId",
() => navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
fake: true,
})))
.then(() => mustFailWith("unknown exact deviceId on audio", "NotFoundError",
() => navigator.mediaDevices.getUserMedia({
.then(() => mustFailWith("unknown exact deviceId on audio",
"OverconstrainedError", "deviceId",
() => navigator.mediaDevices.getUserMedia({
audio: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
fake: true,
})))

View File

@ -39,9 +39,14 @@ var tests = [
{ message: "browser screensharing requires permission",
constraints: { video: { mediaSource: 'browser' } },
error: "PermissionDeniedError" },
{ message: "unknown mediaSource fails",
{ message: "unknown mediaSource in video fails",
constraints: { video: { mediaSource: 'uncle' } },
error: "NotFoundError" },
error: "OverconstrainedError",
constraint: "mediaSource" },
{ message: "unknown mediaSource in audio fails",
constraints: { audio: { mediaSource: 'uncle' } },
error: "OverconstrainedError",
constraint: "mediaSource" },
{ message: "emtpy constraint fails",
constraints: { },
error: "NotSupportedError" },
@ -106,8 +111,13 @@ runTest(function() {
return tests.reduce((p, test) =>
p.then(() => navigator.mediaDevices.getUserMedia(test.constraints))
.then(() => is(null, test.error, test.message),
e => is(e.name, test.error, test.message + ": " + e.message)), p);
.then(() => is(null, test.error, test.message), e => {
is(e.name, test.error, test.message + ": " + e.message);
if (test.constraint) {
is(e.constraint, test.constraint,
test.message + " w/correct constraint.");
}
}), p);
});
</script>