Bug 1120546 - getUserMedia used in frames needs tests, r=felipe.

This commit is contained in:
Florian Quèze 2015-01-16 22:21:13 +01:00
parent 04850ee5f2
commit 9f685904df
3 changed files with 523 additions and 0 deletions

View File

@ -303,6 +303,8 @@ run-if = datareporting
skip-if = buildapp == 'mulet' || (os == "linux" && debug) || e10s # linux: bug 976544; e10s: bug 1071623
[browser_devices_get_user_media_about_urls.js]
skip-if = e10s # Bug 1071623
[browser_devices_get_user_media_in_frame.js]
skip-if = e10s # Bug 1071623
[browser_discovery.js]
[browser_double_close_tab.js]
skip-if = e10s

View File

@ -459,6 +459,50 @@ let gTests = [
}
},
{
desc: "getUserMedia audio+video: reloading the page removes all gUM UI",
run: function checkReloading() {
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting devices");
content.wrappedJSObject.requestDevice(true, true);
});
expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, true);
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
expectObserverCalled("getUserMedia:response:allow");
expectObserverCalled("recording-device-events");
is(getMediaCaptureState(), "CameraAndMicrophone",
"expected camera and microphone to be shared");
yield checkSharingUI({video: true, audio: true});
yield promiseNotificationShown(PopupNotifications.getNotification("webRTC-sharingDevices"));
info("reloading the web page");
let deferred = Promise.defer();
let browser = gBrowser.selectedBrowser;
browser.addEventListener("load", function onload() {
browser.removeEventListener("load", onload, true);
deferred.resolve();
}, true);
content.location.reload();
yield deferred.promise;
yield promiseNoPopupNotification("webRTC-sharingDevices");
if (gObservedTopics["recording-device-events"] == 2) {
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
--gObservedTopics["recording-device-events"];
}
expectObserverCalled("recording-device-events");
expectObserverCalled("recording-window-ended");
expectNoObserverCalled();
yield checkNotSharing();
}
},
{
desc: "getUserMedia prompt: Always/Never Share",
run: function checkRememberCheckbox() {

View File

@ -0,0 +1,477 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const kObservedTopics = [
"getUserMedia:response:allow",
"getUserMedia:revoke",
"getUserMedia:response:deny",
"getUserMedia:request",
"recording-device-events",
"recording-window-ended"
];
const PREF_PERMISSION_FAKE = "media.navigator.permission.fake";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
"@mozilla.org/mediaManagerService;1",
"nsIMediaManagerService");
var gObservedTopics = {};
function observer(aSubject, aTopic, aData) {
if (!(aTopic in gObservedTopics))
gObservedTopics[aTopic] = 1;
else
++gObservedTopics[aTopic];
}
function promiseObserverCalled(aTopic, aAction) {
let deferred = Promise.defer();
Services.obs.addObserver(function observer() {
ok(true, "got " + aTopic + " notification");
Services.obs.removeObserver(observer, aTopic);
if (kObservedTopics.indexOf(aTopic) != -1) {
if (!(aTopic in gObservedTopics))
gObservedTopics[aTopic] = -1;
else
--gObservedTopics[aTopic];
}
deferred.resolve();
}, aTopic, false);
if (aAction)
aAction();
return deferred.promise;
}
function expectObserverCalled(aTopic) {
is(gObservedTopics[aTopic], 1, "expected notification " + aTopic);
if (aTopic in gObservedTopics)
--gObservedTopics[aTopic];
}
function expectNoObserverCalled() {
for (let topic in gObservedTopics) {
if (gObservedTopics[topic])
is(gObservedTopics[topic], 0, topic + " notification unexpected");
}
gObservedTopics = {};
}
function promiseMessage(aMessage, aAction) {
let deferred = Promise.defer();
content.addEventListener("message", function messageListener(event) {
content.removeEventListener("message", messageListener);
is(event.data, aMessage, "received " + aMessage);
if (event.data == aMessage)
deferred.resolve();
else
deferred.reject();
});
if (aAction)
aAction();
return deferred.promise;
}
function promisePopupNotificationShown(aName, aAction) {
let deferred = Promise.defer();
PopupNotifications.panel.addEventListener("popupshown", function popupNotifShown() {
PopupNotifications.panel.removeEventListener("popupshown", popupNotifShown);
ok(!!PopupNotifications.getNotification(aName), aName + " notification shown");
ok(PopupNotifications.isPanelOpen, "notification panel open");
ok(!!PopupNotifications.panel.firstChild, "notification panel populated");
deferred.resolve();
});
if (aAction)
aAction();
return deferred.promise;
}
function promisePopupNotification(aName) {
let deferred = Promise.defer();
waitForCondition(() => PopupNotifications.getNotification(aName),
() => {
ok(!!PopupNotifications.getNotification(aName),
aName + " notification appeared");
deferred.resolve();
}, "timeout waiting for popup notification " + aName);
return deferred.promise;
}
function promiseNoPopupNotification(aName) {
let deferred = Promise.defer();
waitForCondition(() => !PopupNotifications.getNotification(aName),
() => {
ok(!PopupNotifications.getNotification(aName),
aName + " notification removed");
deferred.resolve();
}, "timeout waiting for popup notification " + aName + " to disappear");
return deferred.promise;
}
const kActionAlways = 1;
const kActionDeny = 2;
const kActionNever = 3;
function activateSecondaryAction(aAction) {
let notification = PopupNotifications.panel.firstChild;
notification.button.focus();
let popup = notification.menupopup;
popup.addEventListener("popupshown", function () {
popup.removeEventListener("popupshown", arguments.callee, false);
// Press 'down' as many time as needed to select the requested action.
while (aAction--)
EventUtils.synthesizeKey("VK_DOWN", {});
// Activate
EventUtils.synthesizeKey("VK_RETURN", {});
}, false);
// One down event to open the popup
EventUtils.synthesizeKey("VK_DOWN",
{ altKey: !navigator.platform.contains("Mac") });
}
registerCleanupFunction(function() {
gBrowser.removeCurrentTab();
kObservedTopics.forEach(topic => {
Services.obs.removeObserver(observer, topic);
});
Services.prefs.clearUserPref(PREF_PERMISSION_FAKE);
});
function getMediaCaptureState() {
let hasVideo = {};
let hasAudio = {};
MediaManagerService.mediaCaptureWindowState(content, hasVideo, hasAudio);
if (hasVideo.value && hasAudio.value)
return "CameraAndMicrophone";
if (hasVideo.value)
return "Camera";
if (hasAudio.value)
return "Microphone";
return "none";
}
function* closeStream(aGlobal, aAlreadyClosed) {
expectNoObserverCalled();
info("closing the stream");
aGlobal.closeStream();
if (!aAlreadyClosed)
yield promiseObserverCalled("recording-device-events");
yield promiseNoPopupNotification("webRTC-sharingDevices");
if (!aAlreadyClosed)
expectObserverCalled("recording-window-ended");
yield* assertWebRTCIndicatorStatus(null);
}
function checkDeviceSelectors(aAudio, aVideo) {
let micSelector = document.getElementById("webRTC-selectMicrophone");
if (aAudio)
ok(!micSelector.hidden, "microphone selector visible");
else
ok(micSelector.hidden, "microphone selector hidden");
let cameraSelector = document.getElementById("webRTC-selectCamera");
if (aVideo)
ok(!cameraSelector.hidden, "camera selector visible");
else
ok(cameraSelector.hidden, "camera selector hidden");
}
function* checkSharingUI(aExpected) {
yield promisePopupNotification("webRTC-sharingDevices");
yield* assertWebRTCIndicatorStatus(aExpected);
}
function* checkNotSharing() {
is(getMediaCaptureState(), "none", "expected nothing to be shared");
ok(!PopupNotifications.getNotification("webRTC-sharingDevices"),
"no webRTC-sharingDevices popup notification");
yield* assertWebRTCIndicatorStatus(null);
}
function getFrameGlobal(aFrameId) {
return content.wrappedJSObject.document.getElementById(aFrameId).contentWindow;
}
const permissionError = "error: PermissionDeniedError: The user did not grant permission for the operation.";
let gTests = [
{
desc: "getUserMedia audio+video",
run: function checkAudioVideo() {
let global = getFrameGlobal("frame1");
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting devices");
global.requestDevice(true, true);
});
expectObserverCalled("getUserMedia:request");
is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
"webRTC-shareDevices-notification-icon", "anchored to device icon");
checkDeviceSelectors(true, true);
is(PopupNotifications.panel.firstChild.getAttribute("popupid"),
"webRTC-shareDevices", "panel using devices icon");
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
expectObserverCalled("getUserMedia:response:allow");
expectObserverCalled("recording-device-events");
is(getMediaCaptureState(), "CameraAndMicrophone",
"expected camera and microphone to be shared");
yield checkSharingUI({audio: true, video: true});
yield closeStream(global);
}
},
{
desc: "getUserMedia audio+video: stop sharing",
run: function checkStopSharing() {
let global = getFrameGlobal("frame1");
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting devices");
global.requestDevice(true, true);
});
expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, true);
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
expectObserverCalled("getUserMedia:response:allow");
expectObserverCalled("recording-device-events");
is(getMediaCaptureState(), "CameraAndMicrophone",
"expected camera and microphone to be shared");
yield checkSharingUI({video: true, audio: true});
yield promiseNotificationShown(PopupNotifications.getNotification("webRTC-sharingDevices"));
activateSecondaryAction(kActionDeny);
yield promiseObserverCalled("recording-device-events");
expectObserverCalled("getUserMedia:revoke");
yield promiseNoPopupNotification("webRTC-sharingDevices");
expectObserverCalled("recording-window-ended");
if (gObservedTopics["recording-device-events"] == 1) {
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
gObservedTopics["recording-device-events"] = 0;
}
expectNoObserverCalled();
yield checkNotSharing();
// the stream is already closed, but this will do some cleanup anyway
yield closeStream(global, true);
}
},
{
desc: "getUserMedia audio+video: reloading the frame removes all sharing UI",
run: function checkReloading() {
let global = getFrameGlobal("frame1");
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting devices");
global.requestDevice(true, true);
});
expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, true);
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
expectObserverCalled("getUserMedia:response:allow");
expectObserverCalled("recording-device-events");
is(getMediaCaptureState(), "CameraAndMicrophone",
"expected camera and microphone to be shared");
yield checkSharingUI({video: true, audio: true});
info("reloading the frame");
let deferred = Promise.defer();
let browser = gBrowser.selectedBrowser;
browser.addEventListener("load", function onload() {
browser.removeEventListener("load", onload, true);
deferred.resolve();
}, true);
global.location.reload();
yield deferred.promise;
yield promiseNoPopupNotification("webRTC-sharingDevices");
if (gObservedTopics["recording-device-events"] == 2) {
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
--gObservedTopics["recording-device-events"];
}
expectObserverCalled("recording-device-events");
expectObserverCalled("recording-window-ended");
expectNoObserverCalled();
yield checkNotSharing();
}
},
{
desc: "getUserMedia audio+video: reloading the frame removes prompts",
run: function checkReloadingRemovesPrompts() {
let global = getFrameGlobal("frame1");
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting devices");
global.requestDevice(true, true);
});
expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, true);
info("reloading the frame");
let deferred = Promise.defer();
let browser = gBrowser.selectedBrowser;
browser.addEventListener("load", function onload() {
browser.removeEventListener("load", onload, true);
deferred.resolve();
}, true);
global.location.reload();
yield deferred.promise;
yield promiseNoPopupNotification("webRTC-shareDevices");
expectObserverCalled("recording-window-ended");
expectNoObserverCalled();
yield checkNotSharing();
}
},
{
desc: "getUserMedia audio+video: reloading a frame updates the sharing UI",
run: function checkUpdateWhenReloading() {
// We'll share only the mic in the first frame, then share both in the
// second frame, then reload the second frame. After each step, we'll check
// the UI is in the correct state.
let g1 = getFrameGlobal("frame1"), g2 = getFrameGlobal("frame2");
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting microphone in the first frame");
g1.requestDevice(true, false);
});
expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, false);
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
expectObserverCalled("getUserMedia:response:allow");
expectObserverCalled("recording-device-events");
is(getMediaCaptureState(), "Microphone", "microphone to be shared");
yield checkSharingUI({video: false, audio: true});
expectNoObserverCalled();
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting both devices in the second frame");
g2.requestDevice(true, true);
});
expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, true);
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
expectObserverCalled("getUserMedia:response:allow");
expectObserverCalled("recording-device-events");
is(getMediaCaptureState(), "CameraAndMicrophone",
"expected camera and microphone to be shared");
yield checkSharingUI({video: true, audio: true});
expectNoObserverCalled();
info("reloading the second frame");
let deferred = Promise.defer();
let browser = gBrowser.selectedBrowser;
browser.addEventListener("load", function onload() {
browser.removeEventListener("load", onload, true);
deferred.resolve();
}, true);
g2.location.reload();
yield deferred.promise;
yield checkSharingUI({video: false, audio: true});
expectObserverCalled("recording-window-ended");
if (gObservedTopics["recording-device-events"] == 2) {
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
--gObservedTopics["recording-device-events"];
}
expectObserverCalled("recording-device-events");
expectNoObserverCalled();
yield closeStream(g1);
yield promiseNoPopupNotification("webRTC-sharingDevices");
expectNoObserverCalled();
yield checkNotSharing();
}
}
];
function test() {
waitForExplicitFinish();
let tab = gBrowser.addTab();
gBrowser.selectedTab = tab;
tab.linkedBrowser.addEventListener("load", function onload() {
tab.linkedBrowser.removeEventListener("load", onload, true);
kObservedTopics.forEach(topic => {
Services.obs.addObserver(observer, topic, false);
});
Services.prefs.setBoolPref(PREF_PERMISSION_FAKE, true);
is(PopupNotifications._currentNotifications.length, 0,
"should start the test without any prior popup notification");
Task.spawn(function () {
for (let test of gTests) {
info(test.desc);
yield test.run();
// Cleanup before the next test
expectNoObserverCalled();
}
}).then(finish, ex => {
ok(false, "Unexpected Exception: " + ex);
finish();
});
}, true);
let rootDir = getRootDirectory(gTestPath);
rootDir = rootDir.replace("chrome://mochitests/content/",
"https://example.com/");
let url = rootDir + "get_user_media.html";
content.location = 'data:text/html,<iframe id="frame1" src="' + url + '"></iframe><iframe id="frame2" src="' + url + '"></iframe>'
}