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) // Apply constrains to a supplied list of sources (removes items from the list)
template<class DeviceType> template<class DeviceType>
static void static const char*
SelectSettings(const MediaTrackConstraints &aConstraints, SelectSettings(const MediaTrackConstraints &aConstraints,
nsTArray<nsRefPtr<DeviceType>>& aSources) nsTArray<nsRefPtr<DeviceType>>& aSources)
{ {
@ -963,6 +979,7 @@ SelectSettings(const MediaTrackConstraints &aConstraints,
// Stack constraintSets that pass, starting with the required one, because the // 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 // 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). // (this avoids storing state or pushing algorithm into the lower-level code).
nsTArray<nsRefPtr<DeviceType>> unsatisfactory;
nsTArray<const MediaTrackConstraintSet*> aggregateConstraints; nsTArray<const MediaTrackConstraintSet*> aggregateConstraints;
aggregateConstraints.AppendElement(&c); aggregateConstraints.AppendElement(&c);
@ -971,6 +988,7 @@ SelectSettings(const MediaTrackConstraints &aConstraints,
for (uint32_t i = 0; i < aSources.Length();) { for (uint32_t i = 0; i < aSources.Length();) {
uint32_t distance = aSources[i]->GetBestFitnessDistance(aggregateConstraints); uint32_t distance = aSources[i]->GetBestFitnessDistance(aggregateConstraints);
if (distance == UINT32_MAX) { if (distance == UINT32_MAX) {
unsatisfactory.AppendElement(aSources[i]);
aSources.RemoveElementAt(i); aSources.RemoveElementAt(i);
} else { } else {
ordered.insert(std::pair<uint32_t, nsRefPtr<DeviceType>>(distance, ordered.insert(std::pair<uint32_t, nsRefPtr<DeviceType>>(distance,
@ -978,6 +996,49 @@ SelectSettings(const MediaTrackConstraints &aConstraints,
++i; ++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 // Order devices by shortest distance
for (auto& ordinal : ordered) { for (auto& ordinal : ordered) {
aSources.RemoveElement(ordinal.second); aSources.RemoveElement(ordinal.second);
@ -1006,9 +1067,10 @@ SelectSettings(const MediaTrackConstraints &aConstraints,
} }
} }
} }
return nullptr;
} }
static bool static const char*
SelectSettings(MediaStreamConstraints &aConstraints, SelectSettings(MediaStreamConstraints &aConstraints,
nsTArray<nsRefPtr<MediaDevice>>& aSources) nsTArray<nsRefPtr<MediaDevice>>& aSources)
{ {
@ -1016,7 +1078,6 @@ SelectSettings(MediaStreamConstraints &aConstraints,
// a candidate set is overconstrained (zero members), we must split up the // 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. // list into videos and audios, and put it back together again at the end.
bool overconstrained = false;
nsTArray<nsRefPtr<VideoDevice>> videos; nsTArray<nsRefPtr<VideoDevice>> videos;
nsTArray<nsRefPtr<AudioDevice>> audios; nsTArray<nsRefPtr<AudioDevice>> audios;
@ -1032,25 +1093,21 @@ SelectSettings(MediaStreamConstraints &aConstraints,
aSources.Clear(); aSources.Clear();
MOZ_ASSERT(!aSources.Length()); MOZ_ASSERT(!aSources.Length());
const char* badConstraint = nullptr;
if (IsOn(aConstraints.mVideo)) { if (IsOn(aConstraints.mVideo)) {
SelectSettings(GetInvariant(aConstraints.mVideo), videos); badConstraint = SelectSettings(GetInvariant(aConstraints.mVideo), videos);
if (!videos.Length()) {
overconstrained = true;
}
for (auto& video : videos) { for (auto& video : videos) {
aSources.AppendElement(video); aSources.AppendElement(video);
} }
} }
if (IsOn(aConstraints.mAudio)) { if (audios.Length() && IsOn(aConstraints.mAudio)) {
SelectSettings(GetInvariant(aConstraints.mAudio), audios); badConstraint = SelectSettings(GetInvariant(aConstraints.mAudio), audios);
if (!audios.Length()) {
overconstrained = true;
}
for (auto& audio : audios) { for (auto& audio : audios) {
aSources.AppendElement(audio); aSources.AppendElement(audio);
} }
} }
return !overconstrained; return badConstraint;
} }
/** /**
@ -1697,7 +1754,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
auto& vc = c.mVideo.GetAsMediaTrackConstraints(); auto& vc = c.mVideo.GetAsMediaTrackConstraints();
videoType = StringToEnum(dom::MediaSourceEnumValues::strings, videoType = StringToEnum(dom::MediaSourceEnumValues::strings,
vc.mMediaSource, vc.mMediaSource,
videoType); dom::MediaSourceEnum::Other);
switch (videoType) { switch (videoType) {
case dom::MediaSourceEnum::Camera: case dom::MediaSourceEnum::Camera:
break; break;
@ -1740,7 +1797,10 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
case dom::MediaSourceEnum::Other: case dom::MediaSourceEnum::Other:
default: { default: {
nsRefPtr<MediaStreamError> error = 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); onFailure->OnError(error);
return NS_OK; return NS_OK;
} }
@ -1785,7 +1845,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
auto& ac = c.mAudio.GetAsMediaTrackConstraints(); auto& ac = c.mAudio.GetAsMediaTrackConstraints();
audioType = StringToEnum(dom::MediaSourceEnumValues::strings, audioType = StringToEnum(dom::MediaSourceEnumValues::strings,
ac.mMediaSource, ac.mMediaSource,
audioType); dom::MediaSourceEnum::Other);
// Work around WebIDL default since spec uses same dictionary w/audio & video. // Work around WebIDL default since spec uses same dictionary w/audio & video.
if (audioType == dom::MediaSourceEnum::Camera) { if (audioType == dom::MediaSourceEnum::Camera) {
audioType = dom::MediaSourceEnum::Microphone; audioType = dom::MediaSourceEnum::Microphone;
@ -1812,7 +1872,10 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
case dom::MediaSourceEnum::Other: case dom::MediaSourceEnum::Other:
default: { default: {
nsRefPtr<MediaStreamError> error = 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); onFailure->OnError(error);
return NS_OK; return NS_OK;
} }
@ -1905,8 +1968,19 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
} }
// Apply any constraints. This modifies the list. // Apply any constraints. This modifies the list.
const char* badConstraint = SelectSettings(c, *devices);
if (!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 = nsRefPtr<MediaStreamError> error =
new MediaStreamError(window, NS_LITERAL_STRING("NotFoundError")); new MediaStreamError(window, NS_LITERAL_STRING("NotFoundError"));
onFailure->OnError(error); onFailure->OnError(error);

View File

@ -28,8 +28,9 @@ BaseMediaMgrError::BaseMediaMgrError(const nsAString& aName,
} else if (mName.EqualsLiteral("InternalError")) { } else if (mName.EqualsLiteral("InternalError")) {
mMessage.AssignLiteral("Internal error."); mMessage.AssignLiteral("Internal error.");
} else if (mName.EqualsLiteral("NotSupportedError")) { } else if (mName.EqualsLiteral("NotSupportedError")) {
mMessage.AssignLiteral("Constraints with no audio or video in it are not " mMessage.AssignLiteral("The operation is not supported.");
"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)); e => is(e.name, null, msg + " must succeed: " + e.message));
} }
function mustFailWith(msg, reason, f) { function mustFailWith(msg, reason, constraint, f) {
return f().then(() => ok(false, msg + " must fail"), return f().then(() => ok(false, msg + " must fail"), e => {
e => is(e.name, reason, msg + " must fail: " + e.message)); 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)); var pushPrefs = (...p) => new Promise(r => SpecialPowers.pushPrefEnv({set: p}, r));
@ -49,13 +53,15 @@ runTest(() =>
audio: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" }, audio: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
fake: true, fake: true,
}))) })))
.then(() => mustFailWith("unknown exact deviceId on video", "NotFoundError", .then(() => mustFailWith("unknown exact deviceId on video",
() => navigator.mediaDevices.getUserMedia({ "OverconstrainedError", "deviceId",
() => navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } }, video: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
fake: true, fake: true,
}))) })))
.then(() => mustFailWith("unknown exact deviceId on audio", "NotFoundError", .then(() => mustFailWith("unknown exact deviceId on audio",
() => navigator.mediaDevices.getUserMedia({ "OverconstrainedError", "deviceId",
() => navigator.mediaDevices.getUserMedia({
audio: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } }, audio: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
fake: true, fake: true,
}))) })))

View File

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