michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const kObservedTopics = [ michael@0: "getUserMedia:response:allow", michael@0: "getUserMedia:revoke", michael@0: "getUserMedia:response:deny", michael@0: "getUserMedia:request", michael@0: "recording-device-events", michael@0: "recording-window-ended" michael@0: ]; michael@0: michael@0: const PREF_PERMISSION_FAKE = "media.navigator.permission.fake"; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService", michael@0: "@mozilla.org/mediaManagerService;1", michael@0: "nsIMediaManagerService"); michael@0: michael@0: var gObservedTopics = {}; michael@0: function observer(aSubject, aTopic, aData) { michael@0: if (!(aTopic in gObservedTopics)) michael@0: gObservedTopics[aTopic] = 1; michael@0: else michael@0: ++gObservedTopics[aTopic]; michael@0: } michael@0: michael@0: function promiseObserverCalled(aTopic, aAction) { michael@0: let deferred = Promise.defer(); michael@0: michael@0: Services.obs.addObserver(function observer() { michael@0: ok(true, "got " + aTopic + " notification"); michael@0: Services.obs.removeObserver(observer, aTopic); michael@0: michael@0: if (kObservedTopics.indexOf(aTopic) != -1) { michael@0: if (!(aTopic in gObservedTopics)) michael@0: gObservedTopics[aTopic] = -1; michael@0: else michael@0: --gObservedTopics[aTopic]; michael@0: } michael@0: michael@0: deferred.resolve(); michael@0: }, aTopic, false); michael@0: michael@0: if (aAction) michael@0: aAction(); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function expectObserverCalled(aTopic) { michael@0: is(gObservedTopics[aTopic], 1, "expected notification " + aTopic); michael@0: if (aTopic in gObservedTopics) michael@0: --gObservedTopics[aTopic]; michael@0: } michael@0: michael@0: function expectNoObserverCalled() { michael@0: for (let topic in gObservedTopics) { michael@0: if (gObservedTopics[topic]) michael@0: is(gObservedTopics[topic], 0, topic + " notification unexpected"); michael@0: } michael@0: gObservedTopics = {} michael@0: } michael@0: michael@0: function promiseMessage(aMessage, aAction) { michael@0: let deferred = Promise.defer(); michael@0: michael@0: content.addEventListener("message", function messageListener(event) { michael@0: content.removeEventListener("message", messageListener); michael@0: is(event.data, aMessage, "received " + aMessage); michael@0: if (event.data == aMessage) michael@0: deferred.resolve(); michael@0: else michael@0: deferred.reject(); michael@0: }); michael@0: michael@0: if (aAction) michael@0: aAction(); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function promisePopupNotificationShown(aName, aAction) { michael@0: let deferred = Promise.defer(); michael@0: michael@0: PopupNotifications.panel.addEventListener("popupshown", function popupNotifShown() { michael@0: PopupNotifications.panel.removeEventListener("popupshown", popupNotifShown); michael@0: michael@0: ok(!!PopupNotifications.getNotification(aName), aName + " notification shown"); michael@0: ok(PopupNotifications.isPanelOpen, "notification panel open"); michael@0: ok(!!PopupNotifications.panel.firstChild, "notification panel populated"); michael@0: michael@0: deferred.resolve(); michael@0: }); michael@0: michael@0: if (aAction) michael@0: aAction(); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function promisePopupNotification(aName) { michael@0: let deferred = Promise.defer(); michael@0: michael@0: waitForCondition(() => PopupNotifications.getNotification(aName), michael@0: () => { michael@0: ok(!!PopupNotifications.getNotification(aName), michael@0: aName + " notification appeared"); michael@0: michael@0: deferred.resolve(); michael@0: }, "timeout waiting for popup notification " + aName); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function promiseNoPopupNotification(aName) { michael@0: let deferred = Promise.defer(); michael@0: michael@0: waitForCondition(() => !PopupNotifications.getNotification(aName), michael@0: () => { michael@0: ok(!PopupNotifications.getNotification(aName), michael@0: aName + " notification removed"); michael@0: deferred.resolve(); michael@0: }, "timeout waiting for popup notification " + aName + " to disappear"); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: const kActionAlways = 1; michael@0: const kActionDeny = 2; michael@0: const kActionNever = 3; michael@0: michael@0: function activateSecondaryAction(aAction) { michael@0: let notification = PopupNotifications.panel.firstChild; michael@0: notification.button.focus(); michael@0: let popup = notification.menupopup; michael@0: popup.addEventListener("popupshown", function () { michael@0: popup.removeEventListener("popupshown", arguments.callee, false); michael@0: michael@0: // Press 'down' as many time as needed to select the requested action. michael@0: while (aAction--) michael@0: EventUtils.synthesizeKey("VK_DOWN", {}); michael@0: michael@0: // Activate michael@0: EventUtils.synthesizeKey("VK_RETURN", {}); michael@0: }, false); michael@0: michael@0: // One down event to open the popup michael@0: EventUtils.synthesizeKey("VK_DOWN", michael@0: { altKey: !navigator.platform.contains("Mac") }); michael@0: } michael@0: michael@0: registerCleanupFunction(function() { michael@0: gBrowser.removeCurrentTab(); michael@0: kObservedTopics.forEach(topic => { michael@0: Services.obs.removeObserver(observer, topic); michael@0: }); michael@0: Services.prefs.clearUserPref(PREF_PERMISSION_FAKE); michael@0: }); michael@0: michael@0: function getMediaCaptureState() { michael@0: let hasVideo = {}; michael@0: let hasAudio = {}; michael@0: MediaManagerService.mediaCaptureWindowState(content, hasVideo, hasAudio); michael@0: if (hasVideo.value && hasAudio.value) michael@0: return "CameraAndMicrophone"; michael@0: if (hasVideo.value) michael@0: return "Camera"; michael@0: if (hasAudio.value) michael@0: return "Microphone"; michael@0: return "none"; michael@0: } michael@0: michael@0: function closeStream(aAlreadyClosed) { michael@0: expectNoObserverCalled(); michael@0: michael@0: info("closing the stream"); michael@0: content.wrappedJSObject.closeStream(); michael@0: michael@0: if (!aAlreadyClosed) michael@0: yield promiseObserverCalled("recording-device-events"); michael@0: michael@0: yield promiseNoPopupNotification("webRTC-sharingDevices"); michael@0: if (!aAlreadyClosed) michael@0: expectObserverCalled("recording-window-ended"); michael@0: michael@0: let statusButton = document.getElementById("webrtc-status-button"); michael@0: ok(statusButton.hidden, "WebRTC status button hidden"); michael@0: } michael@0: michael@0: function checkDeviceSelectors(aAudio, aVideo) { michael@0: let micSelector = document.getElementById("webRTC-selectMicrophone"); michael@0: if (aAudio) michael@0: ok(!micSelector.hidden, "microphone selector visible"); michael@0: else michael@0: ok(micSelector.hidden, "microphone selector hidden"); michael@0: michael@0: let cameraSelector = document.getElementById("webRTC-selectCamera"); michael@0: if (aVideo) michael@0: ok(!cameraSelector.hidden, "camera selector visible"); michael@0: else michael@0: ok(cameraSelector.hidden, "camera selector hidden"); michael@0: } michael@0: michael@0: function checkSharingUI() { michael@0: yield promisePopupNotification("webRTC-sharingDevices"); michael@0: let statusButton = document.getElementById("webrtc-status-button"); michael@0: ok(!statusButton.hidden, "WebRTC status button visible"); michael@0: } michael@0: michael@0: function checkNotSharing() { michael@0: is(getMediaCaptureState(), "none", "expected nothing to be shared"); michael@0: michael@0: ok(!PopupNotifications.getNotification("webRTC-sharingDevices"), michael@0: "no webRTC-sharingDevices popup notification"); michael@0: michael@0: let statusButton = document.getElementById("webrtc-status-button"); michael@0: ok(statusButton.hidden, "WebRTC status button hidden"); michael@0: } michael@0: michael@0: let gTests = [ michael@0: michael@0: { michael@0: desc: "getUserMedia audio+video", michael@0: run: function checkAudioVideo() { michael@0: yield promisePopupNotificationShown("webRTC-shareDevices", () => { michael@0: info("requesting devices"); michael@0: content.wrappedJSObject.requestDevice(true, true); michael@0: }); michael@0: expectObserverCalled("getUserMedia:request"); michael@0: michael@0: is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID, michael@0: "webRTC-shareDevices-notification-icon", "anchored to device icon"); michael@0: checkDeviceSelectors(true, true); michael@0: is(PopupNotifications.panel.firstChild.getAttribute("popupid"), michael@0: "webRTC-shareDevices", "panel using devices icon"); michael@0: michael@0: yield promiseMessage("ok", () => { michael@0: PopupNotifications.panel.firstChild.button.click(); michael@0: }); michael@0: expectObserverCalled("getUserMedia:response:allow"); michael@0: expectObserverCalled("recording-device-events"); michael@0: is(getMediaCaptureState(), "CameraAndMicrophone", michael@0: "expected camera and microphone to be shared"); michael@0: michael@0: yield checkSharingUI(); michael@0: yield closeStream(); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "getUserMedia audio only", michael@0: run: function checkAudioOnly() { michael@0: yield promisePopupNotificationShown("webRTC-shareDevices", () => { michael@0: info("requesting devices"); michael@0: content.wrappedJSObject.requestDevice(true); michael@0: }); michael@0: expectObserverCalled("getUserMedia:request"); michael@0: michael@0: is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID, michael@0: "webRTC-shareMicrophone-notification-icon", "anchored to mic icon"); michael@0: checkDeviceSelectors(true); michael@0: is(PopupNotifications.panel.firstChild.getAttribute("popupid"), michael@0: "webRTC-shareMicrophone", "panel using microphone icon"); michael@0: michael@0: yield promiseMessage("ok", () => { michael@0: PopupNotifications.panel.firstChild.button.click(); michael@0: }); michael@0: expectObserverCalled("getUserMedia:response:allow"); michael@0: expectObserverCalled("recording-device-events"); michael@0: is(getMediaCaptureState(), "Microphone", "expected microphone to be shared"); michael@0: michael@0: yield checkSharingUI(); michael@0: yield closeStream(); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "getUserMedia video only", michael@0: run: function checkVideoOnly() { michael@0: yield promisePopupNotificationShown("webRTC-shareDevices", () => { michael@0: info("requesting devices"); michael@0: content.wrappedJSObject.requestDevice(false, true); michael@0: }); michael@0: expectObserverCalled("getUserMedia:request"); michael@0: michael@0: is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID, michael@0: "webRTC-shareDevices-notification-icon", "anchored to device icon"); michael@0: checkDeviceSelectors(false, true); michael@0: is(PopupNotifications.panel.firstChild.getAttribute("popupid"), michael@0: "webRTC-shareDevices", "panel using devices icon"); michael@0: michael@0: yield promiseMessage("ok", () => { michael@0: PopupNotifications.panel.firstChild.button.click(); michael@0: }); michael@0: expectObserverCalled("getUserMedia:response:allow"); michael@0: expectObserverCalled("recording-device-events"); michael@0: is(getMediaCaptureState(), "Camera", "expected camera to be shared"); michael@0: michael@0: yield checkSharingUI(); michael@0: yield closeStream(); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "getUserMedia audio+video, user disables video", michael@0: run: function checkDisableVideo() { michael@0: yield promisePopupNotificationShown("webRTC-shareDevices", () => { michael@0: info("requesting devices"); michael@0: content.wrappedJSObject.requestDevice(true, true); michael@0: }); michael@0: expectObserverCalled("getUserMedia:request"); michael@0: checkDeviceSelectors(true, true); michael@0: michael@0: // disable the camera michael@0: document.getElementById("webRTC-selectCamera-menulist").value = -1; michael@0: michael@0: yield promiseMessage("ok", () => { michael@0: PopupNotifications.panel.firstChild.button.click(); michael@0: }); michael@0: michael@0: // reset the menuitem to have no impact on the following tests. michael@0: document.getElementById("webRTC-selectCamera-menulist").value = 0; michael@0: michael@0: expectObserverCalled("getUserMedia:response:allow"); michael@0: expectObserverCalled("recording-device-events"); michael@0: is(getMediaCaptureState(), "Microphone", michael@0: "expected microphone to be shared"); michael@0: michael@0: yield checkSharingUI(); michael@0: yield closeStream(); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "getUserMedia audio+video, user disables audio", michael@0: run: function checkDisableAudio() { michael@0: yield promisePopupNotificationShown("webRTC-shareDevices", () => { michael@0: info("requesting devices"); michael@0: content.wrappedJSObject.requestDevice(true, true); michael@0: }); michael@0: expectObserverCalled("getUserMedia:request"); michael@0: checkDeviceSelectors(true, true); michael@0: michael@0: // disable the microphone michael@0: document.getElementById("webRTC-selectMicrophone-menulist").value = -1; michael@0: michael@0: yield promiseMessage("ok", () => { michael@0: PopupNotifications.panel.firstChild.button.click(); michael@0: }); michael@0: michael@0: // reset the menuitem to have no impact on the following tests. michael@0: document.getElementById("webRTC-selectMicrophone-menulist").value = 0; michael@0: michael@0: expectObserverCalled("getUserMedia:response:allow"); michael@0: expectObserverCalled("recording-device-events"); michael@0: is(getMediaCaptureState(), "Camera", michael@0: "expected microphone to be shared"); michael@0: michael@0: yield checkSharingUI(); michael@0: yield closeStream(); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "getUserMedia audio+video, user disables both audio and video", michael@0: run: function checkDisableAudioVideo() { michael@0: yield promisePopupNotificationShown("webRTC-shareDevices", () => { michael@0: info("requesting devices"); michael@0: content.wrappedJSObject.requestDevice(true, true); michael@0: }); michael@0: expectObserverCalled("getUserMedia:request"); michael@0: checkDeviceSelectors(true, true); michael@0: michael@0: // disable the camera and microphone michael@0: document.getElementById("webRTC-selectCamera-menulist").value = -1; michael@0: document.getElementById("webRTC-selectMicrophone-menulist").value = -1; michael@0: michael@0: yield promiseMessage("error: PERMISSION_DENIED", () => { michael@0: PopupNotifications.panel.firstChild.button.click(); michael@0: }); michael@0: michael@0: // reset the menuitems to have no impact on the following tests. michael@0: document.getElementById("webRTC-selectCamera-menulist").value = 0; michael@0: document.getElementById("webRTC-selectMicrophone-menulist").value = 0; michael@0: michael@0: expectObserverCalled("getUserMedia:response:deny"); michael@0: expectObserverCalled("recording-window-ended"); michael@0: checkNotSharing(); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "getUserMedia audio+video, user clicks \"Don't Share\"", michael@0: run: function checkDontShare() { michael@0: yield promisePopupNotificationShown("webRTC-shareDevices", () => { michael@0: info("requesting devices"); michael@0: content.wrappedJSObject.requestDevice(true, true); michael@0: }); michael@0: expectObserverCalled("getUserMedia:request"); michael@0: checkDeviceSelectors(true, true); michael@0: michael@0: yield promiseMessage("error: PERMISSION_DENIED", () => { michael@0: activateSecondaryAction(kActionDeny); michael@0: }); michael@0: michael@0: expectObserverCalled("getUserMedia:response:deny"); michael@0: expectObserverCalled("recording-window-ended"); michael@0: checkNotSharing(); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "getUserMedia audio+video: stop sharing", michael@0: run: function checkStopSharing() { michael@0: yield promisePopupNotificationShown("webRTC-shareDevices", () => { michael@0: info("requesting devices"); michael@0: content.wrappedJSObject.requestDevice(true, true); michael@0: }); michael@0: expectObserverCalled("getUserMedia:request"); michael@0: checkDeviceSelectors(true, true); michael@0: michael@0: yield promiseMessage("ok", () => { michael@0: PopupNotifications.panel.firstChild.button.click(); michael@0: }); michael@0: expectObserverCalled("getUserMedia:response:allow"); michael@0: expectObserverCalled("recording-device-events"); michael@0: is(getMediaCaptureState(), "CameraAndMicrophone", michael@0: "expected camera and microphone to be shared"); michael@0: michael@0: yield checkSharingUI(); michael@0: michael@0: PopupNotifications.getNotification("webRTC-sharingDevices").reshow(); michael@0: activateSecondaryAction(kActionDeny); michael@0: michael@0: yield promiseObserverCalled("recording-device-events"); michael@0: expectObserverCalled("getUserMedia:revoke"); michael@0: michael@0: yield promiseNoPopupNotification("webRTC-sharingDevices"); michael@0: michael@0: if (gObservedTopics["recording-device-events"] == 1) { michael@0: todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719"); michael@0: gObservedTopics["recording-device-events"] = 0; michael@0: } michael@0: michael@0: expectNoObserverCalled(); michael@0: checkNotSharing(); michael@0: michael@0: // the stream is already closed, but this will do some cleanup anyway michael@0: yield closeStream(true); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "getUserMedia prompt: Always/Never Share", michael@0: run: function checkRememberCheckbox() { michael@0: let elt = id => document.getElementById(id); michael@0: michael@0: function checkPerm(aRequestAudio, aRequestVideo, aAllowAudio, aAllowVideo, michael@0: aExpectedAudioPerm, aExpectedVideoPerm, aNever) { michael@0: yield promisePopupNotificationShown("webRTC-shareDevices", () => { michael@0: content.wrappedJSObject.requestDevice(aRequestAudio, aRequestVideo); michael@0: }); michael@0: expectObserverCalled("getUserMedia:request"); michael@0: michael@0: let noAudio = aAllowAudio === undefined; michael@0: is(elt("webRTC-selectMicrophone").hidden, noAudio, michael@0: "microphone selector expected to be " + (noAudio ? "hidden" : "visible")); michael@0: if (!noAudio) michael@0: elt("webRTC-selectMicrophone-menulist").value = (aAllowAudio || aNever) ? 0 : -1; michael@0: michael@0: let noVideo = aAllowVideo === undefined; michael@0: is(elt("webRTC-selectCamera").hidden, noVideo, michael@0: "camera selector expected to be " + (noVideo ? "hidden" : "visible")); michael@0: if (!noVideo) michael@0: elt("webRTC-selectCamera-menulist").value = (aAllowVideo || aNever) ? 0 : -1; michael@0: michael@0: let expectedMessage = michael@0: (aAllowVideo || aAllowAudio) ? "ok" : "error: PERMISSION_DENIED"; michael@0: yield promiseMessage(expectedMessage, () => { michael@0: activateSecondaryAction(aNever ? kActionNever : kActionAlways); michael@0: }); michael@0: let expected = []; michael@0: if (expectedMessage == "ok") { michael@0: expectObserverCalled("getUserMedia:response:allow"); michael@0: expectObserverCalled("recording-device-events"); michael@0: if (aAllowVideo) michael@0: expected.push("Camera"); michael@0: if (aAllowAudio) michael@0: expected.push("Microphone"); michael@0: expected = expected.join("And"); michael@0: } michael@0: else { michael@0: expectObserverCalled("getUserMedia:response:deny"); michael@0: expectObserverCalled("recording-window-ended"); michael@0: expected = "none"; michael@0: } michael@0: is(getMediaCaptureState(), expected, michael@0: "expected " + expected + " to be shared"); michael@0: michael@0: function checkDevicePermissions(aDevice, aExpected) { michael@0: let Perms = Services.perms; michael@0: let uri = content.document.documentURIObject; michael@0: let devicePerms = Perms.testExactPermission(uri, aDevice); michael@0: if (aExpected === undefined) michael@0: is(devicePerms, Perms.UNKNOWN_ACTION, "no " + aDevice + " persistent permissions"); michael@0: else { michael@0: is(devicePerms, aExpected ? Perms.ALLOW_ACTION : Perms.DENY_ACTION, michael@0: aDevice + " persistently " + (aExpected ? "allowed" : "denied")); michael@0: } michael@0: Perms.remove(uri.host, aDevice); michael@0: } michael@0: checkDevicePermissions("microphone", aExpectedAudioPerm); michael@0: checkDevicePermissions("camera", aExpectedVideoPerm); michael@0: michael@0: if (expectedMessage == "ok") michael@0: yield closeStream(); michael@0: } michael@0: michael@0: // 3 cases where the user accepts the device prompt. michael@0: info("audio+video, user grants, expect both perms set to allow"); michael@0: yield checkPerm(true, true, true, true, true, true); michael@0: info("audio only, user grants, check audio perm set to allow, video perm not set"); michael@0: yield checkPerm(true, false, true, undefined, true, undefined); michael@0: info("video only, user grants, check video perm set to allow, audio perm not set"); michael@0: yield checkPerm(false, true, undefined, true, undefined, true); michael@0: michael@0: // 3 cases where the user rejects the device request. michael@0: // First test these cases by setting the device to 'No Audio'/'No Video' michael@0: info("audio+video, user denies, expect both perms set to deny"); michael@0: yield checkPerm(true, true, false, false, false, false); michael@0: info("audio only, user denies, expect audio perm set to deny, video not set"); michael@0: yield checkPerm(true, false, false, undefined, false, undefined); michael@0: info("video only, user denies, expect video perm set to deny, audio perm not set"); michael@0: yield checkPerm(false, true, undefined, false, undefined, false); michael@0: // Now test these 3 cases again by using the 'Never Share' action. michael@0: info("audio+video, user denies, expect both perms set to deny"); michael@0: yield checkPerm(true, true, false, false, false, false, true); michael@0: info("audio only, user denies, expect audio perm set to deny, video not set"); michael@0: yield checkPerm(true, false, false, undefined, false, undefined, true); michael@0: info("video only, user denies, expect video perm set to deny, audio perm not set"); michael@0: yield checkPerm(false, true, undefined, false, undefined, false, true); michael@0: michael@0: // 2 cases where the user allows half of what's requested. michael@0: info("audio+video, user denies video, grants audio, " + michael@0: "expect video perm set to deny, audio perm set to allow."); michael@0: yield checkPerm(true, true, true, false, true, false); michael@0: info("audio+video, user denies audio, grants video, " + michael@0: "expect video perm set to allow, audio perm set to deny."); michael@0: yield checkPerm(true, true, false, true, false, true); michael@0: michael@0: // reset the menuitems to have no impact on the following tests. michael@0: elt("webRTC-selectMicrophone-menulist").value = 0; michael@0: elt("webRTC-selectCamera-menulist").value = 0; michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "getUserMedia without prompt: use persistent permissions", michael@0: run: function checkUsePersistentPermissions() { michael@0: function usePerm(aAllowAudio, aAllowVideo, aRequestAudio, aRequestVideo, michael@0: aExpectStream) { michael@0: let Perms = Services.perms; michael@0: let uri = content.document.documentURIObject; michael@0: if (aAllowAudio !== undefined) { michael@0: Perms.add(uri, "microphone", aAllowAudio ? Perms.ALLOW_ACTION michael@0: : Perms.DENY_ACTION); michael@0: } michael@0: if (aAllowVideo !== undefined) { michael@0: Perms.add(uri, "camera", aAllowVideo ? Perms.ALLOW_ACTION michael@0: : Perms.DENY_ACTION); michael@0: } michael@0: michael@0: let gum = function() { michael@0: content.wrappedJSObject.requestDevice(aRequestAudio, aRequestVideo); michael@0: }; michael@0: michael@0: if (aExpectStream === undefined) { michael@0: // Check that we get a prompt. michael@0: yield promisePopupNotificationShown("webRTC-shareDevices", gum); michael@0: expectObserverCalled("getUserMedia:request"); michael@0: michael@0: // Deny the request to cleanup... michael@0: yield promiseMessage("error: PERMISSION_DENIED", () => { michael@0: activateSecondaryAction(kActionDeny); michael@0: }); michael@0: expectObserverCalled("getUserMedia:response:deny"); michael@0: expectObserverCalled("recording-window-ended"); michael@0: } michael@0: else { michael@0: let expectedMessage = aExpectStream ? "ok" : "error: PERMISSION_DENIED"; michael@0: yield promiseMessage(expectedMessage, gum); michael@0: michael@0: if (expectedMessage == "ok") { michael@0: expectObserverCalled("getUserMedia:request"); michael@0: yield promiseNoPopupNotification("webRTC-shareDevices"); michael@0: expectObserverCalled("getUserMedia:response:allow"); michael@0: expectObserverCalled("recording-device-events"); michael@0: michael@0: // Check what's actually shared. michael@0: let expected = []; michael@0: if (aAllowVideo && aRequestVideo) michael@0: expected.push("Camera"); michael@0: if (aAllowAudio && aRequestAudio) michael@0: expected.push("Microphone"); michael@0: expected = expected.join("And"); michael@0: is(getMediaCaptureState(), expected, michael@0: "expected " + expected + " to be shared"); michael@0: michael@0: yield closeStream(); michael@0: } michael@0: else { michael@0: expectObserverCalled("recording-window-ended"); michael@0: } michael@0: } michael@0: michael@0: Perms.remove(uri.host, "camera"); michael@0: Perms.remove(uri.host, "microphone"); michael@0: } michael@0: michael@0: // Set both permissions identically michael@0: info("allow audio+video, request audio+video, expect ok (audio+video)"); michael@0: yield usePerm(true, true, true, true, true); michael@0: info("deny audio+video, request audio+video, expect denied"); michael@0: yield usePerm(false, false, true, true, false); michael@0: michael@0: // Allow audio, deny video. michael@0: info("allow audio, deny video, request audio+video, expect ok (audio)"); michael@0: yield usePerm(true, false, true, true, true); michael@0: info("allow audio, deny video, request audio, expect ok (audio)"); michael@0: yield usePerm(true, false, true, false, true); michael@0: info("allow audio, deny video, request video, expect denied"); michael@0: yield usePerm(true, false, false, true, false); michael@0: michael@0: // Deny audio, allow video. michael@0: info("deny audio, allow video, request audio+video, expect ok (video)"); michael@0: yield usePerm(false, true, true, true, true); michael@0: info("deny audio, allow video, request audio, expect denied"); michael@0: yield usePerm(false, true, true, false, false); michael@0: info("deny audio, allow video, request video, expect ok (video)"); michael@0: yield usePerm(false, true, false, true, true); michael@0: michael@0: // Allow audio, video not set. michael@0: info("allow audio, request audio+video, expect prompt"); michael@0: yield usePerm(true, undefined, true, true, undefined); michael@0: info("allow audio, request audio, expect ok (audio)"); michael@0: yield usePerm(true, undefined, true, false, true); michael@0: info("allow audio, request video, expect prompt"); michael@0: yield usePerm(true, undefined, false, true, undefined); michael@0: michael@0: // Deny audio, video not set. michael@0: info("deny audio, request audio+video, expect prompt"); michael@0: yield usePerm(false, undefined, true, true, undefined); michael@0: info("deny audio, request audio, expect denied"); michael@0: yield usePerm(false, undefined, true, false, false); michael@0: info("deny audio, request video, expect prompt"); michael@0: yield usePerm(false, undefined, false, true, undefined); michael@0: michael@0: // Allow video, video not set. michael@0: info("allow video, request audio+video, expect prompt"); michael@0: yield usePerm(undefined, true, true, true, undefined); michael@0: info("allow video, request audio, expect prompt"); michael@0: yield usePerm(undefined, true, true, false, undefined); michael@0: info("allow video, request video, expect ok (video)"); michael@0: yield usePerm(undefined, true, false, true, true); michael@0: michael@0: // Deny video, video not set. michael@0: info("deny video, request audio+video, expect prompt"); michael@0: yield usePerm(undefined, false, true, true, undefined); michael@0: info("deny video, request audio, expect prompt"); michael@0: yield usePerm(undefined, false, true, false, undefined); michael@0: info("deny video, request video, expect denied"); michael@0: yield usePerm(undefined, false, false, true, false); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "Stop Sharing removes persistent permissions", michael@0: run: function checkStopSharingRemovesPersistentPermissions() { michael@0: function stopAndCheckPerm(aRequestAudio, aRequestVideo) { michael@0: let Perms = Services.perms; michael@0: let uri = content.document.documentURIObject; michael@0: michael@0: // Initially set both permissions to 'allow'. michael@0: Perms.add(uri, "microphone", Perms.ALLOW_ACTION); michael@0: Perms.add(uri, "camera", Perms.ALLOW_ACTION); michael@0: michael@0: // Start sharing what's been requested. michael@0: yield promiseMessage("ok", () => { michael@0: content.wrappedJSObject.requestDevice(aRequestAudio, aRequestVideo); michael@0: }); michael@0: expectObserverCalled("getUserMedia:request"); michael@0: expectObserverCalled("getUserMedia:response:allow"); michael@0: expectObserverCalled("recording-device-events"); michael@0: yield checkSharingUI(); michael@0: michael@0: PopupNotifications.getNotification("webRTC-sharingDevices").reshow(); michael@0: let expectedIcon = "webRTC-sharingDevices"; michael@0: if (aRequestAudio && !aRequestVideo) michael@0: expectedIcon = "webRTC-sharingMicrophone"; michael@0: is(PopupNotifications.getNotification("webRTC-sharingDevices").anchorID, michael@0: expectedIcon + "-notification-icon", "anchored to correct icon"); michael@0: is(PopupNotifications.panel.firstChild.getAttribute("popupid"), expectedIcon, michael@0: "panel using correct icon"); michael@0: michael@0: // Stop sharing. michael@0: activateSecondaryAction(kActionDeny); michael@0: michael@0: yield promiseObserverCalled("recording-device-events"); michael@0: expectObserverCalled("getUserMedia:revoke"); michael@0: michael@0: yield promiseNoPopupNotification("webRTC-sharingDevices"); michael@0: michael@0: if (gObservedTopics["recording-device-events"] == 1) { michael@0: todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719"); michael@0: gObservedTopics["recording-device-events"] = 0; michael@0: } michael@0: michael@0: // Check that permissions have been removed as expected. michael@0: let audioPerm = Perms.testExactPermission(uri, "microphone"); michael@0: if (aRequestAudio) michael@0: is(audioPerm, Perms.UNKNOWN_ACTION, "microphone permissions removed"); michael@0: else michael@0: is(audioPerm, Perms.ALLOW_ACTION, "microphone permissions untouched"); michael@0: michael@0: let videoPerm = Perms.testExactPermission(uri, "camera"); michael@0: if (aRequestVideo) michael@0: is(videoPerm, Perms.UNKNOWN_ACTION, "camera permissions removed"); michael@0: else michael@0: is(videoPerm, Perms.ALLOW_ACTION, "camera permissions untouched"); michael@0: michael@0: // Cleanup. michael@0: yield closeStream(true); michael@0: michael@0: Perms.remove(uri.host, "camera"); michael@0: Perms.remove(uri.host, "microphone"); michael@0: } michael@0: michael@0: info("request audio+video, stop sharing resets both"); michael@0: yield stopAndCheckPerm(true, true); michael@0: info("request audio, stop sharing resets audio only"); michael@0: yield stopAndCheckPerm(true, false); michael@0: info("request video, stop sharing resets video only"); michael@0: yield stopAndCheckPerm(false, true); michael@0: } michael@0: }, michael@0: michael@0: { michael@0: desc: "'Always Allow' ignored and not shown on http pages", michael@0: run: function checkNoAlwaysOnHttp() { michael@0: // Load an http page instead of the https version. michael@0: let deferred = Promise.defer(); michael@0: let browser = gBrowser.selectedTab.linkedBrowser; michael@0: browser.addEventListener("load", function onload() { michael@0: browser.removeEventListener("load", onload, true); michael@0: deferred.resolve(); michael@0: }, true); michael@0: content.location = content.location.href.replace("https://", "http://"); michael@0: yield deferred.promise; michael@0: michael@0: // Initially set both permissions to 'allow'. michael@0: let Perms = Services.perms; michael@0: let uri = content.document.documentURIObject; michael@0: Perms.add(uri, "microphone", Perms.ALLOW_ACTION); michael@0: Perms.add(uri, "camera", Perms.ALLOW_ACTION); michael@0: michael@0: // Request devices and expect a prompt despite the saved 'Allow' permission, michael@0: // because the connection isn't secure. michael@0: yield promisePopupNotificationShown("webRTC-shareDevices", () => { michael@0: content.wrappedJSObject.requestDevice(true, true); michael@0: }); michael@0: expectObserverCalled("getUserMedia:request"); michael@0: michael@0: // Ensure that the 'Always Allow' action isn't shown. michael@0: let alwaysLabel = gNavigatorBundle.getString("getUserMedia.always.label"); michael@0: ok(!!alwaysLabel, "found the 'Always Allow' localized label"); michael@0: let labels = []; michael@0: let notification = PopupNotifications.panel.firstChild; michael@0: for (let node of notification.childNodes) { michael@0: if (node.localName == "menuitem") michael@0: labels.push(node.getAttribute("label")); michael@0: } michael@0: is(labels.indexOf(alwaysLabel), -1, "The 'Always Allow' item isn't shown"); michael@0: michael@0: // Cleanup. michael@0: yield closeStream(true); michael@0: Perms.remove(uri.host, "camera"); michael@0: Perms.remove(uri.host, "microphone"); michael@0: } michael@0: } michael@0: michael@0: ]; michael@0: michael@0: function test() { michael@0: waitForExplicitFinish(); michael@0: michael@0: let tab = gBrowser.addTab(); michael@0: gBrowser.selectedTab = tab; michael@0: tab.linkedBrowser.addEventListener("load", function onload() { michael@0: tab.linkedBrowser.removeEventListener("load", onload, true); michael@0: michael@0: kObservedTopics.forEach(topic => { michael@0: Services.obs.addObserver(observer, topic, false); michael@0: }); michael@0: Services.prefs.setBoolPref(PREF_PERMISSION_FAKE, true); michael@0: michael@0: is(PopupNotifications._currentNotifications.length, 0, michael@0: "should start the test without any prior popup notification"); michael@0: michael@0: Task.spawn(function () { michael@0: for (let test of gTests) { michael@0: info(test.desc); michael@0: yield test.run(); michael@0: michael@0: // Cleanup before the next test michael@0: expectNoObserverCalled(); michael@0: } michael@0: }).then(finish, ex => { michael@0: ok(false, "Unexpected Exception: " + ex); michael@0: finish(); michael@0: }); michael@0: }, true); michael@0: let rootDir = getRootDirectory(gTestPath) michael@0: rootDir = rootDir.replace("chrome://mochitests/content/", michael@0: "https://example.com/"); michael@0: content.location = rootDir + "get_user_media.html"; michael@0: } michael@0: michael@0: michael@0: function wait(time) { michael@0: let deferred = Promise.defer(); michael@0: setTimeout(deferred.resolve, time); michael@0: return deferred.promise; michael@0: }