browser/base/content/test/general/browser_devices_get_user_media.js

changeset 0
6474c204b198
     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 +}

mercurial