1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/base/content/test/general/browser_devices_get_user_media.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,835 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +const kObservedTopics = [ 1.9 + "getUserMedia:response:allow", 1.10 + "getUserMedia:revoke", 1.11 + "getUserMedia:response:deny", 1.12 + "getUserMedia:request", 1.13 + "recording-device-events", 1.14 + "recording-window-ended" 1.15 +]; 1.16 + 1.17 +const PREF_PERMISSION_FAKE = "media.navigator.permission.fake"; 1.18 + 1.19 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.20 +XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService", 1.21 + "@mozilla.org/mediaManagerService;1", 1.22 + "nsIMediaManagerService"); 1.23 + 1.24 +var gObservedTopics = {}; 1.25 +function observer(aSubject, aTopic, aData) { 1.26 + if (!(aTopic in gObservedTopics)) 1.27 + gObservedTopics[aTopic] = 1; 1.28 + else 1.29 + ++gObservedTopics[aTopic]; 1.30 +} 1.31 + 1.32 +function promiseObserverCalled(aTopic, aAction) { 1.33 + let deferred = Promise.defer(); 1.34 + 1.35 + Services.obs.addObserver(function observer() { 1.36 + ok(true, "got " + aTopic + " notification"); 1.37 + Services.obs.removeObserver(observer, aTopic); 1.38 + 1.39 + if (kObservedTopics.indexOf(aTopic) != -1) { 1.40 + if (!(aTopic in gObservedTopics)) 1.41 + gObservedTopics[aTopic] = -1; 1.42 + else 1.43 + --gObservedTopics[aTopic]; 1.44 + } 1.45 + 1.46 + deferred.resolve(); 1.47 + }, aTopic, false); 1.48 + 1.49 + if (aAction) 1.50 + aAction(); 1.51 + 1.52 + return deferred.promise; 1.53 +} 1.54 + 1.55 +function expectObserverCalled(aTopic) { 1.56 + is(gObservedTopics[aTopic], 1, "expected notification " + aTopic); 1.57 + if (aTopic in gObservedTopics) 1.58 + --gObservedTopics[aTopic]; 1.59 +} 1.60 + 1.61 +function expectNoObserverCalled() { 1.62 + for (let topic in gObservedTopics) { 1.63 + if (gObservedTopics[topic]) 1.64 + is(gObservedTopics[topic], 0, topic + " notification unexpected"); 1.65 + } 1.66 + gObservedTopics = {} 1.67 +} 1.68 + 1.69 +function promiseMessage(aMessage, aAction) { 1.70 + let deferred = Promise.defer(); 1.71 + 1.72 + content.addEventListener("message", function messageListener(event) { 1.73 + content.removeEventListener("message", messageListener); 1.74 + is(event.data, aMessage, "received " + aMessage); 1.75 + if (event.data == aMessage) 1.76 + deferred.resolve(); 1.77 + else 1.78 + deferred.reject(); 1.79 + }); 1.80 + 1.81 + if (aAction) 1.82 + aAction(); 1.83 + 1.84 + return deferred.promise; 1.85 +} 1.86 + 1.87 +function promisePopupNotificationShown(aName, aAction) { 1.88 + let deferred = Promise.defer(); 1.89 + 1.90 + PopupNotifications.panel.addEventListener("popupshown", function popupNotifShown() { 1.91 + PopupNotifications.panel.removeEventListener("popupshown", popupNotifShown); 1.92 + 1.93 + ok(!!PopupNotifications.getNotification(aName), aName + " notification shown"); 1.94 + ok(PopupNotifications.isPanelOpen, "notification panel open"); 1.95 + ok(!!PopupNotifications.panel.firstChild, "notification panel populated"); 1.96 + 1.97 + deferred.resolve(); 1.98 + }); 1.99 + 1.100 + if (aAction) 1.101 + aAction(); 1.102 + 1.103 + return deferred.promise; 1.104 +} 1.105 + 1.106 +function promisePopupNotification(aName) { 1.107 + let deferred = Promise.defer(); 1.108 + 1.109 + waitForCondition(() => PopupNotifications.getNotification(aName), 1.110 + () => { 1.111 + ok(!!PopupNotifications.getNotification(aName), 1.112 + aName + " notification appeared"); 1.113 + 1.114 + deferred.resolve(); 1.115 + }, "timeout waiting for popup notification " + aName); 1.116 + 1.117 + return deferred.promise; 1.118 +} 1.119 + 1.120 +function promiseNoPopupNotification(aName) { 1.121 + let deferred = Promise.defer(); 1.122 + 1.123 + waitForCondition(() => !PopupNotifications.getNotification(aName), 1.124 + () => { 1.125 + ok(!PopupNotifications.getNotification(aName), 1.126 + aName + " notification removed"); 1.127 + deferred.resolve(); 1.128 + }, "timeout waiting for popup notification " + aName + " to disappear"); 1.129 + 1.130 + return deferred.promise; 1.131 +} 1.132 + 1.133 +const kActionAlways = 1; 1.134 +const kActionDeny = 2; 1.135 +const kActionNever = 3; 1.136 + 1.137 +function activateSecondaryAction(aAction) { 1.138 + let notification = PopupNotifications.panel.firstChild; 1.139 + notification.button.focus(); 1.140 + let popup = notification.menupopup; 1.141 + popup.addEventListener("popupshown", function () { 1.142 + popup.removeEventListener("popupshown", arguments.callee, false); 1.143 + 1.144 + // Press 'down' as many time as needed to select the requested action. 1.145 + while (aAction--) 1.146 + EventUtils.synthesizeKey("VK_DOWN", {}); 1.147 + 1.148 + // Activate 1.149 + EventUtils.synthesizeKey("VK_RETURN", {}); 1.150 + }, false); 1.151 + 1.152 + // One down event to open the popup 1.153 + EventUtils.synthesizeKey("VK_DOWN", 1.154 + { altKey: !navigator.platform.contains("Mac") }); 1.155 +} 1.156 + 1.157 +registerCleanupFunction(function() { 1.158 + gBrowser.removeCurrentTab(); 1.159 + kObservedTopics.forEach(topic => { 1.160 + Services.obs.removeObserver(observer, topic); 1.161 + }); 1.162 + Services.prefs.clearUserPref(PREF_PERMISSION_FAKE); 1.163 +}); 1.164 + 1.165 +function getMediaCaptureState() { 1.166 + let hasVideo = {}; 1.167 + let hasAudio = {}; 1.168 + MediaManagerService.mediaCaptureWindowState(content, hasVideo, hasAudio); 1.169 + if (hasVideo.value && hasAudio.value) 1.170 + return "CameraAndMicrophone"; 1.171 + if (hasVideo.value) 1.172 + return "Camera"; 1.173 + if (hasAudio.value) 1.174 + return "Microphone"; 1.175 + return "none"; 1.176 +} 1.177 + 1.178 +function closeStream(aAlreadyClosed) { 1.179 + expectNoObserverCalled(); 1.180 + 1.181 + info("closing the stream"); 1.182 + content.wrappedJSObject.closeStream(); 1.183 + 1.184 + if (!aAlreadyClosed) 1.185 + yield promiseObserverCalled("recording-device-events"); 1.186 + 1.187 + yield promiseNoPopupNotification("webRTC-sharingDevices"); 1.188 + if (!aAlreadyClosed) 1.189 + expectObserverCalled("recording-window-ended"); 1.190 + 1.191 + let statusButton = document.getElementById("webrtc-status-button"); 1.192 + ok(statusButton.hidden, "WebRTC status button hidden"); 1.193 +} 1.194 + 1.195 +function checkDeviceSelectors(aAudio, aVideo) { 1.196 + let micSelector = document.getElementById("webRTC-selectMicrophone"); 1.197 + if (aAudio) 1.198 + ok(!micSelector.hidden, "microphone selector visible"); 1.199 + else 1.200 + ok(micSelector.hidden, "microphone selector hidden"); 1.201 + 1.202 + let cameraSelector = document.getElementById("webRTC-selectCamera"); 1.203 + if (aVideo) 1.204 + ok(!cameraSelector.hidden, "camera selector visible"); 1.205 + else 1.206 + ok(cameraSelector.hidden, "camera selector hidden"); 1.207 +} 1.208 + 1.209 +function checkSharingUI() { 1.210 + yield promisePopupNotification("webRTC-sharingDevices"); 1.211 + let statusButton = document.getElementById("webrtc-status-button"); 1.212 + ok(!statusButton.hidden, "WebRTC status button visible"); 1.213 +} 1.214 + 1.215 +function checkNotSharing() { 1.216 + is(getMediaCaptureState(), "none", "expected nothing to be shared"); 1.217 + 1.218 + ok(!PopupNotifications.getNotification("webRTC-sharingDevices"), 1.219 + "no webRTC-sharingDevices popup notification"); 1.220 + 1.221 + let statusButton = document.getElementById("webrtc-status-button"); 1.222 + ok(statusButton.hidden, "WebRTC status button hidden"); 1.223 +} 1.224 + 1.225 +let gTests = [ 1.226 + 1.227 +{ 1.228 + desc: "getUserMedia audio+video", 1.229 + run: function checkAudioVideo() { 1.230 + yield promisePopupNotificationShown("webRTC-shareDevices", () => { 1.231 + info("requesting devices"); 1.232 + content.wrappedJSObject.requestDevice(true, true); 1.233 + }); 1.234 + expectObserverCalled("getUserMedia:request"); 1.235 + 1.236 + is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID, 1.237 + "webRTC-shareDevices-notification-icon", "anchored to device icon"); 1.238 + checkDeviceSelectors(true, true); 1.239 + is(PopupNotifications.panel.firstChild.getAttribute("popupid"), 1.240 + "webRTC-shareDevices", "panel using devices icon"); 1.241 + 1.242 + yield promiseMessage("ok", () => { 1.243 + PopupNotifications.panel.firstChild.button.click(); 1.244 + }); 1.245 + expectObserverCalled("getUserMedia:response:allow"); 1.246 + expectObserverCalled("recording-device-events"); 1.247 + is(getMediaCaptureState(), "CameraAndMicrophone", 1.248 + "expected camera and microphone to be shared"); 1.249 + 1.250 + yield checkSharingUI(); 1.251 + yield closeStream(); 1.252 + } 1.253 +}, 1.254 + 1.255 +{ 1.256 + desc: "getUserMedia audio only", 1.257 + run: function checkAudioOnly() { 1.258 + yield promisePopupNotificationShown("webRTC-shareDevices", () => { 1.259 + info("requesting devices"); 1.260 + content.wrappedJSObject.requestDevice(true); 1.261 + }); 1.262 + expectObserverCalled("getUserMedia:request"); 1.263 + 1.264 + is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID, 1.265 + "webRTC-shareMicrophone-notification-icon", "anchored to mic icon"); 1.266 + checkDeviceSelectors(true); 1.267 + is(PopupNotifications.panel.firstChild.getAttribute("popupid"), 1.268 + "webRTC-shareMicrophone", "panel using microphone icon"); 1.269 + 1.270 + yield promiseMessage("ok", () => { 1.271 + PopupNotifications.panel.firstChild.button.click(); 1.272 + }); 1.273 + expectObserverCalled("getUserMedia:response:allow"); 1.274 + expectObserverCalled("recording-device-events"); 1.275 + is(getMediaCaptureState(), "Microphone", "expected microphone to be shared"); 1.276 + 1.277 + yield checkSharingUI(); 1.278 + yield closeStream(); 1.279 + } 1.280 +}, 1.281 + 1.282 +{ 1.283 + desc: "getUserMedia video only", 1.284 + run: function checkVideoOnly() { 1.285 + yield promisePopupNotificationShown("webRTC-shareDevices", () => { 1.286 + info("requesting devices"); 1.287 + content.wrappedJSObject.requestDevice(false, true); 1.288 + }); 1.289 + expectObserverCalled("getUserMedia:request"); 1.290 + 1.291 + is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID, 1.292 + "webRTC-shareDevices-notification-icon", "anchored to device icon"); 1.293 + checkDeviceSelectors(false, true); 1.294 + is(PopupNotifications.panel.firstChild.getAttribute("popupid"), 1.295 + "webRTC-shareDevices", "panel using devices icon"); 1.296 + 1.297 + yield promiseMessage("ok", () => { 1.298 + PopupNotifications.panel.firstChild.button.click(); 1.299 + }); 1.300 + expectObserverCalled("getUserMedia:response:allow"); 1.301 + expectObserverCalled("recording-device-events"); 1.302 + is(getMediaCaptureState(), "Camera", "expected camera to be shared"); 1.303 + 1.304 + yield checkSharingUI(); 1.305 + yield closeStream(); 1.306 + } 1.307 +}, 1.308 + 1.309 +{ 1.310 + desc: "getUserMedia audio+video, user disables video", 1.311 + run: function checkDisableVideo() { 1.312 + yield promisePopupNotificationShown("webRTC-shareDevices", () => { 1.313 + info("requesting devices"); 1.314 + content.wrappedJSObject.requestDevice(true, true); 1.315 + }); 1.316 + expectObserverCalled("getUserMedia:request"); 1.317 + checkDeviceSelectors(true, true); 1.318 + 1.319 + // disable the camera 1.320 + document.getElementById("webRTC-selectCamera-menulist").value = -1; 1.321 + 1.322 + yield promiseMessage("ok", () => { 1.323 + PopupNotifications.panel.firstChild.button.click(); 1.324 + }); 1.325 + 1.326 + // reset the menuitem to have no impact on the following tests. 1.327 + document.getElementById("webRTC-selectCamera-menulist").value = 0; 1.328 + 1.329 + expectObserverCalled("getUserMedia:response:allow"); 1.330 + expectObserverCalled("recording-device-events"); 1.331 + is(getMediaCaptureState(), "Microphone", 1.332 + "expected microphone to be shared"); 1.333 + 1.334 + yield checkSharingUI(); 1.335 + yield closeStream(); 1.336 + } 1.337 +}, 1.338 + 1.339 +{ 1.340 + desc: "getUserMedia audio+video, user disables audio", 1.341 + run: function checkDisableAudio() { 1.342 + yield promisePopupNotificationShown("webRTC-shareDevices", () => { 1.343 + info("requesting devices"); 1.344 + content.wrappedJSObject.requestDevice(true, true); 1.345 + }); 1.346 + expectObserverCalled("getUserMedia:request"); 1.347 + checkDeviceSelectors(true, true); 1.348 + 1.349 + // disable the microphone 1.350 + document.getElementById("webRTC-selectMicrophone-menulist").value = -1; 1.351 + 1.352 + yield promiseMessage("ok", () => { 1.353 + PopupNotifications.panel.firstChild.button.click(); 1.354 + }); 1.355 + 1.356 + // reset the menuitem to have no impact on the following tests. 1.357 + document.getElementById("webRTC-selectMicrophone-menulist").value = 0; 1.358 + 1.359 + expectObserverCalled("getUserMedia:response:allow"); 1.360 + expectObserverCalled("recording-device-events"); 1.361 + is(getMediaCaptureState(), "Camera", 1.362 + "expected microphone to be shared"); 1.363 + 1.364 + yield checkSharingUI(); 1.365 + yield closeStream(); 1.366 + } 1.367 +}, 1.368 + 1.369 +{ 1.370 + desc: "getUserMedia audio+video, user disables both audio and video", 1.371 + run: function checkDisableAudioVideo() { 1.372 + yield promisePopupNotificationShown("webRTC-shareDevices", () => { 1.373 + info("requesting devices"); 1.374 + content.wrappedJSObject.requestDevice(true, true); 1.375 + }); 1.376 + expectObserverCalled("getUserMedia:request"); 1.377 + checkDeviceSelectors(true, true); 1.378 + 1.379 + // disable the camera and microphone 1.380 + document.getElementById("webRTC-selectCamera-menulist").value = -1; 1.381 + document.getElementById("webRTC-selectMicrophone-menulist").value = -1; 1.382 + 1.383 + yield promiseMessage("error: PERMISSION_DENIED", () => { 1.384 + PopupNotifications.panel.firstChild.button.click(); 1.385 + }); 1.386 + 1.387 + // reset the menuitems to have no impact on the following tests. 1.388 + document.getElementById("webRTC-selectCamera-menulist").value = 0; 1.389 + document.getElementById("webRTC-selectMicrophone-menulist").value = 0; 1.390 + 1.391 + expectObserverCalled("getUserMedia:response:deny"); 1.392 + expectObserverCalled("recording-window-ended"); 1.393 + checkNotSharing(); 1.394 + } 1.395 +}, 1.396 + 1.397 +{ 1.398 + desc: "getUserMedia audio+video, user clicks \"Don't Share\"", 1.399 + run: function checkDontShare() { 1.400 + yield promisePopupNotificationShown("webRTC-shareDevices", () => { 1.401 + info("requesting devices"); 1.402 + content.wrappedJSObject.requestDevice(true, true); 1.403 + }); 1.404 + expectObserverCalled("getUserMedia:request"); 1.405 + checkDeviceSelectors(true, true); 1.406 + 1.407 + yield promiseMessage("error: PERMISSION_DENIED", () => { 1.408 + activateSecondaryAction(kActionDeny); 1.409 + }); 1.410 + 1.411 + expectObserverCalled("getUserMedia:response:deny"); 1.412 + expectObserverCalled("recording-window-ended"); 1.413 + checkNotSharing(); 1.414 + } 1.415 +}, 1.416 + 1.417 +{ 1.418 + desc: "getUserMedia audio+video: stop sharing", 1.419 + run: function checkStopSharing() { 1.420 + yield promisePopupNotificationShown("webRTC-shareDevices", () => { 1.421 + info("requesting devices"); 1.422 + content.wrappedJSObject.requestDevice(true, true); 1.423 + }); 1.424 + expectObserverCalled("getUserMedia:request"); 1.425 + checkDeviceSelectors(true, true); 1.426 + 1.427 + yield promiseMessage("ok", () => { 1.428 + PopupNotifications.panel.firstChild.button.click(); 1.429 + }); 1.430 + expectObserverCalled("getUserMedia:response:allow"); 1.431 + expectObserverCalled("recording-device-events"); 1.432 + is(getMediaCaptureState(), "CameraAndMicrophone", 1.433 + "expected camera and microphone to be shared"); 1.434 + 1.435 + yield checkSharingUI(); 1.436 + 1.437 + PopupNotifications.getNotification("webRTC-sharingDevices").reshow(); 1.438 + activateSecondaryAction(kActionDeny); 1.439 + 1.440 + yield promiseObserverCalled("recording-device-events"); 1.441 + expectObserverCalled("getUserMedia:revoke"); 1.442 + 1.443 + yield promiseNoPopupNotification("webRTC-sharingDevices"); 1.444 + 1.445 + if (gObservedTopics["recording-device-events"] == 1) { 1.446 + todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719"); 1.447 + gObservedTopics["recording-device-events"] = 0; 1.448 + } 1.449 + 1.450 + expectNoObserverCalled(); 1.451 + checkNotSharing(); 1.452 + 1.453 + // the stream is already closed, but this will do some cleanup anyway 1.454 + yield closeStream(true); 1.455 + } 1.456 +}, 1.457 + 1.458 +{ 1.459 + desc: "getUserMedia prompt: Always/Never Share", 1.460 + run: function checkRememberCheckbox() { 1.461 + let elt = id => document.getElementById(id); 1.462 + 1.463 + function checkPerm(aRequestAudio, aRequestVideo, aAllowAudio, aAllowVideo, 1.464 + aExpectedAudioPerm, aExpectedVideoPerm, aNever) { 1.465 + yield promisePopupNotificationShown("webRTC-shareDevices", () => { 1.466 + content.wrappedJSObject.requestDevice(aRequestAudio, aRequestVideo); 1.467 + }); 1.468 + expectObserverCalled("getUserMedia:request"); 1.469 + 1.470 + let noAudio = aAllowAudio === undefined; 1.471 + is(elt("webRTC-selectMicrophone").hidden, noAudio, 1.472 + "microphone selector expected to be " + (noAudio ? "hidden" : "visible")); 1.473 + if (!noAudio) 1.474 + elt("webRTC-selectMicrophone-menulist").value = (aAllowAudio || aNever) ? 0 : -1; 1.475 + 1.476 + let noVideo = aAllowVideo === undefined; 1.477 + is(elt("webRTC-selectCamera").hidden, noVideo, 1.478 + "camera selector expected to be " + (noVideo ? "hidden" : "visible")); 1.479 + if (!noVideo) 1.480 + elt("webRTC-selectCamera-menulist").value = (aAllowVideo || aNever) ? 0 : -1; 1.481 + 1.482 + let expectedMessage = 1.483 + (aAllowVideo || aAllowAudio) ? "ok" : "error: PERMISSION_DENIED"; 1.484 + yield promiseMessage(expectedMessage, () => { 1.485 + activateSecondaryAction(aNever ? kActionNever : kActionAlways); 1.486 + }); 1.487 + let expected = []; 1.488 + if (expectedMessage == "ok") { 1.489 + expectObserverCalled("getUserMedia:response:allow"); 1.490 + expectObserverCalled("recording-device-events"); 1.491 + if (aAllowVideo) 1.492 + expected.push("Camera"); 1.493 + if (aAllowAudio) 1.494 + expected.push("Microphone"); 1.495 + expected = expected.join("And"); 1.496 + } 1.497 + else { 1.498 + expectObserverCalled("getUserMedia:response:deny"); 1.499 + expectObserverCalled("recording-window-ended"); 1.500 + expected = "none"; 1.501 + } 1.502 + is(getMediaCaptureState(), expected, 1.503 + "expected " + expected + " to be shared"); 1.504 + 1.505 + function checkDevicePermissions(aDevice, aExpected) { 1.506 + let Perms = Services.perms; 1.507 + let uri = content.document.documentURIObject; 1.508 + let devicePerms = Perms.testExactPermission(uri, aDevice); 1.509 + if (aExpected === undefined) 1.510 + is(devicePerms, Perms.UNKNOWN_ACTION, "no " + aDevice + " persistent permissions"); 1.511 + else { 1.512 + is(devicePerms, aExpected ? Perms.ALLOW_ACTION : Perms.DENY_ACTION, 1.513 + aDevice + " persistently " + (aExpected ? "allowed" : "denied")); 1.514 + } 1.515 + Perms.remove(uri.host, aDevice); 1.516 + } 1.517 + checkDevicePermissions("microphone", aExpectedAudioPerm); 1.518 + checkDevicePermissions("camera", aExpectedVideoPerm); 1.519 + 1.520 + if (expectedMessage == "ok") 1.521 + yield closeStream(); 1.522 + } 1.523 + 1.524 + // 3 cases where the user accepts the device prompt. 1.525 + info("audio+video, user grants, expect both perms set to allow"); 1.526 + yield checkPerm(true, true, true, true, true, true); 1.527 + info("audio only, user grants, check audio perm set to allow, video perm not set"); 1.528 + yield checkPerm(true, false, true, undefined, true, undefined); 1.529 + info("video only, user grants, check video perm set to allow, audio perm not set"); 1.530 + yield checkPerm(false, true, undefined, true, undefined, true); 1.531 + 1.532 + // 3 cases where the user rejects the device request. 1.533 + // First test these cases by setting the device to 'No Audio'/'No Video' 1.534 + info("audio+video, user denies, expect both perms set to deny"); 1.535 + yield checkPerm(true, true, false, false, false, false); 1.536 + info("audio only, user denies, expect audio perm set to deny, video not set"); 1.537 + yield checkPerm(true, false, false, undefined, false, undefined); 1.538 + info("video only, user denies, expect video perm set to deny, audio perm not set"); 1.539 + yield checkPerm(false, true, undefined, false, undefined, false); 1.540 + // Now test these 3 cases again by using the 'Never Share' action. 1.541 + info("audio+video, user denies, expect both perms set to deny"); 1.542 + yield checkPerm(true, true, false, false, false, false, true); 1.543 + info("audio only, user denies, expect audio perm set to deny, video not set"); 1.544 + yield checkPerm(true, false, false, undefined, false, undefined, true); 1.545 + info("video only, user denies, expect video perm set to deny, audio perm not set"); 1.546 + yield checkPerm(false, true, undefined, false, undefined, false, true); 1.547 + 1.548 + // 2 cases where the user allows half of what's requested. 1.549 + info("audio+video, user denies video, grants audio, " + 1.550 + "expect video perm set to deny, audio perm set to allow."); 1.551 + yield checkPerm(true, true, true, false, true, false); 1.552 + info("audio+video, user denies audio, grants video, " + 1.553 + "expect video perm set to allow, audio perm set to deny."); 1.554 + yield checkPerm(true, true, false, true, false, true); 1.555 + 1.556 + // reset the menuitems to have no impact on the following tests. 1.557 + elt("webRTC-selectMicrophone-menulist").value = 0; 1.558 + elt("webRTC-selectCamera-menulist").value = 0; 1.559 + } 1.560 +}, 1.561 + 1.562 +{ 1.563 + desc: "getUserMedia without prompt: use persistent permissions", 1.564 + run: function checkUsePersistentPermissions() { 1.565 + function usePerm(aAllowAudio, aAllowVideo, aRequestAudio, aRequestVideo, 1.566 + aExpectStream) { 1.567 + let Perms = Services.perms; 1.568 + let uri = content.document.documentURIObject; 1.569 + if (aAllowAudio !== undefined) { 1.570 + Perms.add(uri, "microphone", aAllowAudio ? Perms.ALLOW_ACTION 1.571 + : Perms.DENY_ACTION); 1.572 + } 1.573 + if (aAllowVideo !== undefined) { 1.574 + Perms.add(uri, "camera", aAllowVideo ? Perms.ALLOW_ACTION 1.575 + : Perms.DENY_ACTION); 1.576 + } 1.577 + 1.578 + let gum = function() { 1.579 + content.wrappedJSObject.requestDevice(aRequestAudio, aRequestVideo); 1.580 + }; 1.581 + 1.582 + if (aExpectStream === undefined) { 1.583 + // Check that we get a prompt. 1.584 + yield promisePopupNotificationShown("webRTC-shareDevices", gum); 1.585 + expectObserverCalled("getUserMedia:request"); 1.586 + 1.587 + // Deny the request to cleanup... 1.588 + yield promiseMessage("error: PERMISSION_DENIED", () => { 1.589 + activateSecondaryAction(kActionDeny); 1.590 + }); 1.591 + expectObserverCalled("getUserMedia:response:deny"); 1.592 + expectObserverCalled("recording-window-ended"); 1.593 + } 1.594 + else { 1.595 + let expectedMessage = aExpectStream ? "ok" : "error: PERMISSION_DENIED"; 1.596 + yield promiseMessage(expectedMessage, gum); 1.597 + 1.598 + if (expectedMessage == "ok") { 1.599 + expectObserverCalled("getUserMedia:request"); 1.600 + yield promiseNoPopupNotification("webRTC-shareDevices"); 1.601 + expectObserverCalled("getUserMedia:response:allow"); 1.602 + expectObserverCalled("recording-device-events"); 1.603 + 1.604 + // Check what's actually shared. 1.605 + let expected = []; 1.606 + if (aAllowVideo && aRequestVideo) 1.607 + expected.push("Camera"); 1.608 + if (aAllowAudio && aRequestAudio) 1.609 + expected.push("Microphone"); 1.610 + expected = expected.join("And"); 1.611 + is(getMediaCaptureState(), expected, 1.612 + "expected " + expected + " to be shared"); 1.613 + 1.614 + yield closeStream(); 1.615 + } 1.616 + else { 1.617 + expectObserverCalled("recording-window-ended"); 1.618 + } 1.619 + } 1.620 + 1.621 + Perms.remove(uri.host, "camera"); 1.622 + Perms.remove(uri.host, "microphone"); 1.623 + } 1.624 + 1.625 + // Set both permissions identically 1.626 + info("allow audio+video, request audio+video, expect ok (audio+video)"); 1.627 + yield usePerm(true, true, true, true, true); 1.628 + info("deny audio+video, request audio+video, expect denied"); 1.629 + yield usePerm(false, false, true, true, false); 1.630 + 1.631 + // Allow audio, deny video. 1.632 + info("allow audio, deny video, request audio+video, expect ok (audio)"); 1.633 + yield usePerm(true, false, true, true, true); 1.634 + info("allow audio, deny video, request audio, expect ok (audio)"); 1.635 + yield usePerm(true, false, true, false, true); 1.636 + info("allow audio, deny video, request video, expect denied"); 1.637 + yield usePerm(true, false, false, true, false); 1.638 + 1.639 + // Deny audio, allow video. 1.640 + info("deny audio, allow video, request audio+video, expect ok (video)"); 1.641 + yield usePerm(false, true, true, true, true); 1.642 + info("deny audio, allow video, request audio, expect denied"); 1.643 + yield usePerm(false, true, true, false, false); 1.644 + info("deny audio, allow video, request video, expect ok (video)"); 1.645 + yield usePerm(false, true, false, true, true); 1.646 + 1.647 + // Allow audio, video not set. 1.648 + info("allow audio, request audio+video, expect prompt"); 1.649 + yield usePerm(true, undefined, true, true, undefined); 1.650 + info("allow audio, request audio, expect ok (audio)"); 1.651 + yield usePerm(true, undefined, true, false, true); 1.652 + info("allow audio, request video, expect prompt"); 1.653 + yield usePerm(true, undefined, false, true, undefined); 1.654 + 1.655 + // Deny audio, video not set. 1.656 + info("deny audio, request audio+video, expect prompt"); 1.657 + yield usePerm(false, undefined, true, true, undefined); 1.658 + info("deny audio, request audio, expect denied"); 1.659 + yield usePerm(false, undefined, true, false, false); 1.660 + info("deny audio, request video, expect prompt"); 1.661 + yield usePerm(false, undefined, false, true, undefined); 1.662 + 1.663 + // Allow video, video not set. 1.664 + info("allow video, request audio+video, expect prompt"); 1.665 + yield usePerm(undefined, true, true, true, undefined); 1.666 + info("allow video, request audio, expect prompt"); 1.667 + yield usePerm(undefined, true, true, false, undefined); 1.668 + info("allow video, request video, expect ok (video)"); 1.669 + yield usePerm(undefined, true, false, true, true); 1.670 + 1.671 + // Deny video, video not set. 1.672 + info("deny video, request audio+video, expect prompt"); 1.673 + yield usePerm(undefined, false, true, true, undefined); 1.674 + info("deny video, request audio, expect prompt"); 1.675 + yield usePerm(undefined, false, true, false, undefined); 1.676 + info("deny video, request video, expect denied"); 1.677 + yield usePerm(undefined, false, false, true, false); 1.678 + } 1.679 +}, 1.680 + 1.681 +{ 1.682 + desc: "Stop Sharing removes persistent permissions", 1.683 + run: function checkStopSharingRemovesPersistentPermissions() { 1.684 + function stopAndCheckPerm(aRequestAudio, aRequestVideo) { 1.685 + let Perms = Services.perms; 1.686 + let uri = content.document.documentURIObject; 1.687 + 1.688 + // Initially set both permissions to 'allow'. 1.689 + Perms.add(uri, "microphone", Perms.ALLOW_ACTION); 1.690 + Perms.add(uri, "camera", Perms.ALLOW_ACTION); 1.691 + 1.692 + // Start sharing what's been requested. 1.693 + yield promiseMessage("ok", () => { 1.694 + content.wrappedJSObject.requestDevice(aRequestAudio, aRequestVideo); 1.695 + }); 1.696 + expectObserverCalled("getUserMedia:request"); 1.697 + expectObserverCalled("getUserMedia:response:allow"); 1.698 + expectObserverCalled("recording-device-events"); 1.699 + yield checkSharingUI(); 1.700 + 1.701 + PopupNotifications.getNotification("webRTC-sharingDevices").reshow(); 1.702 + let expectedIcon = "webRTC-sharingDevices"; 1.703 + if (aRequestAudio && !aRequestVideo) 1.704 + expectedIcon = "webRTC-sharingMicrophone"; 1.705 + is(PopupNotifications.getNotification("webRTC-sharingDevices").anchorID, 1.706 + expectedIcon + "-notification-icon", "anchored to correct icon"); 1.707 + is(PopupNotifications.panel.firstChild.getAttribute("popupid"), expectedIcon, 1.708 + "panel using correct icon"); 1.709 + 1.710 + // Stop sharing. 1.711 + activateSecondaryAction(kActionDeny); 1.712 + 1.713 + yield promiseObserverCalled("recording-device-events"); 1.714 + expectObserverCalled("getUserMedia:revoke"); 1.715 + 1.716 + yield promiseNoPopupNotification("webRTC-sharingDevices"); 1.717 + 1.718 + if (gObservedTopics["recording-device-events"] == 1) { 1.719 + todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719"); 1.720 + gObservedTopics["recording-device-events"] = 0; 1.721 + } 1.722 + 1.723 + // Check that permissions have been removed as expected. 1.724 + let audioPerm = Perms.testExactPermission(uri, "microphone"); 1.725 + if (aRequestAudio) 1.726 + is(audioPerm, Perms.UNKNOWN_ACTION, "microphone permissions removed"); 1.727 + else 1.728 + is(audioPerm, Perms.ALLOW_ACTION, "microphone permissions untouched"); 1.729 + 1.730 + let videoPerm = Perms.testExactPermission(uri, "camera"); 1.731 + if (aRequestVideo) 1.732 + is(videoPerm, Perms.UNKNOWN_ACTION, "camera permissions removed"); 1.733 + else 1.734 + is(videoPerm, Perms.ALLOW_ACTION, "camera permissions untouched"); 1.735 + 1.736 + // Cleanup. 1.737 + yield closeStream(true); 1.738 + 1.739 + Perms.remove(uri.host, "camera"); 1.740 + Perms.remove(uri.host, "microphone"); 1.741 + } 1.742 + 1.743 + info("request audio+video, stop sharing resets both"); 1.744 + yield stopAndCheckPerm(true, true); 1.745 + info("request audio, stop sharing resets audio only"); 1.746 + yield stopAndCheckPerm(true, false); 1.747 + info("request video, stop sharing resets video only"); 1.748 + yield stopAndCheckPerm(false, true); 1.749 + } 1.750 +}, 1.751 + 1.752 +{ 1.753 + desc: "'Always Allow' ignored and not shown on http pages", 1.754 + run: function checkNoAlwaysOnHttp() { 1.755 + // Load an http page instead of the https version. 1.756 + let deferred = Promise.defer(); 1.757 + let browser = gBrowser.selectedTab.linkedBrowser; 1.758 + browser.addEventListener("load", function onload() { 1.759 + browser.removeEventListener("load", onload, true); 1.760 + deferred.resolve(); 1.761 + }, true); 1.762 + content.location = content.location.href.replace("https://", "http://"); 1.763 + yield deferred.promise; 1.764 + 1.765 + // Initially set both permissions to 'allow'. 1.766 + let Perms = Services.perms; 1.767 + let uri = content.document.documentURIObject; 1.768 + Perms.add(uri, "microphone", Perms.ALLOW_ACTION); 1.769 + Perms.add(uri, "camera", Perms.ALLOW_ACTION); 1.770 + 1.771 + // Request devices and expect a prompt despite the saved 'Allow' permission, 1.772 + // because the connection isn't secure. 1.773 + yield promisePopupNotificationShown("webRTC-shareDevices", () => { 1.774 + content.wrappedJSObject.requestDevice(true, true); 1.775 + }); 1.776 + expectObserverCalled("getUserMedia:request"); 1.777 + 1.778 + // Ensure that the 'Always Allow' action isn't shown. 1.779 + let alwaysLabel = gNavigatorBundle.getString("getUserMedia.always.label"); 1.780 + ok(!!alwaysLabel, "found the 'Always Allow' localized label"); 1.781 + let labels = []; 1.782 + let notification = PopupNotifications.panel.firstChild; 1.783 + for (let node of notification.childNodes) { 1.784 + if (node.localName == "menuitem") 1.785 + labels.push(node.getAttribute("label")); 1.786 + } 1.787 + is(labels.indexOf(alwaysLabel), -1, "The 'Always Allow' item isn't shown"); 1.788 + 1.789 + // Cleanup. 1.790 + yield closeStream(true); 1.791 + Perms.remove(uri.host, "camera"); 1.792 + Perms.remove(uri.host, "microphone"); 1.793 + } 1.794 +} 1.795 + 1.796 +]; 1.797 + 1.798 +function test() { 1.799 + waitForExplicitFinish(); 1.800 + 1.801 + let tab = gBrowser.addTab(); 1.802 + gBrowser.selectedTab = tab; 1.803 + tab.linkedBrowser.addEventListener("load", function onload() { 1.804 + tab.linkedBrowser.removeEventListener("load", onload, true); 1.805 + 1.806 + kObservedTopics.forEach(topic => { 1.807 + Services.obs.addObserver(observer, topic, false); 1.808 + }); 1.809 + Services.prefs.setBoolPref(PREF_PERMISSION_FAKE, true); 1.810 + 1.811 + is(PopupNotifications._currentNotifications.length, 0, 1.812 + "should start the test without any prior popup notification"); 1.813 + 1.814 + Task.spawn(function () { 1.815 + for (let test of gTests) { 1.816 + info(test.desc); 1.817 + yield test.run(); 1.818 + 1.819 + // Cleanup before the next test 1.820 + expectNoObserverCalled(); 1.821 + } 1.822 + }).then(finish, ex => { 1.823 + ok(false, "Unexpected Exception: " + ex); 1.824 + finish(); 1.825 + }); 1.826 + }, true); 1.827 + let rootDir = getRootDirectory(gTestPath) 1.828 + rootDir = rootDir.replace("chrome://mochitests/content/", 1.829 + "https://example.com/"); 1.830 + content.location = rootDir + "get_user_media.html"; 1.831 +} 1.832 + 1.833 + 1.834 +function wait(time) { 1.835 + let deferred = Promise.defer(); 1.836 + setTimeout(deferred.resolve, time); 1.837 + return deferred.promise; 1.838 +}