Bug 1270572 - write tests for the umprompted gUM request; r=florian draft
authorMunro Mengjue Chiang <mchiang@mozilla.com>
Tue, 20 Dec 2016 14:51:36 +0800
changeset 456729 b18566d5072b4bc907777d579f672fa2414dcbdf
parent 456728 e08139e1dbe62ea0b1a4b219b18718bf23b0b21e
child 541297 e67b79181536ef3e6ac88093d2e053a422621608
push id40577
push usermchiang@mozilla.com
push dateFri, 06 Jan 2017 02:53:47 +0000
reviewersflorian
bugs1270572
milestone53.0a1
Bug 1270572 - write tests for the umprompted gUM request; r=florian MozReview-Commit-ID: LYWTNnfli6K
browser/base/content/test/webrtc/browser.ini
browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access.js
browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_in_frame.js
browser/base/content/test/webrtc/get_user_media.html
browser/base/content/test/webrtc/head.js
--- a/browser/base/content/test/webrtc/browser.ini
+++ b/browser/base/content/test/webrtc/browser.ini
@@ -6,9 +6,11 @@ support-files =
 
 [browser_devices_get_user_media.js]
 skip-if = (os == "linux" && debug) # linux: bug 976544
 [browser_devices_get_user_media_anim.js]
 [browser_devices_get_user_media_in_frame.js]
 [browser_devices_get_user_media_screen.js]
 skip-if = (e10s && debug) || (os == "linux" && !debug) # bug 1320754 for e10s debug, and bug 1320994 for linux opt
 [browser_devices_get_user_media_tear_off_tab.js]
+[browser_devices_get_user_media_unprompted_access.js]
+[browser_devices_get_user_media_unprompted_access_in_frame.js]
 [browser_webrtc_hooks.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access.js
@@ -0,0 +1,284 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+requestLongerTimeout(2);
+
+registerCleanupFunction(function() {
+  gBrowser.removeCurrentTab();
+});
+
+const permissionError = "error: NotAllowedError: The request is not allowed " +
+    "by the user agent or the platform in the current context.";
+
+var gTests = [
+
+{
+  desc: "If there's an active audio+camera stream, " +
+        "gUM(audio+camera) returns a stream without prompting; " +
+        "gUM(screen) causes a prompt. " +
+        "After closing all streams, " +
+        "gUM(audio+camera) causes a prompt.",
+  run: function* checkAudioVideoWhileLiveTracksExist_audio_camera() {
+    let promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(true, true);
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    let indicator = promiseIndicatorWindow();
+
+    yield promiseMessage("ok", () => {
+      PopupNotifications.panel.firstChild.button.click();
+    });
+
+    yield expectObserverCalled("getUserMedia:response:allow");
+    yield expectObserverCalled("recording-device-events");
+    Assert.deepEqual((yield getMediaCaptureState()), {audio: true, video: true},
+                     "expected camera and microphone to be shared");
+    yield indicator;
+    yield checkSharingUI({audio: true, video: true});
+
+	// If there's an active audio+camera stream,
+	// gUM(audio+camera) returns a stream without prompting;
+    promise = promiseMessage("ok");
+    yield promiseRequestDevice(true, true);
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    yield promiseNoPopupNotification("webRTC-shareDevices");
+    yield expectObserverCalled("getUserMedia:response:allow");
+    yield expectObserverCalled("recording-device-events");
+
+    Assert.deepEqual((yield getMediaCaptureState()), {audio: true, video: true},
+                     "expected camera and microphone to be shared");
+
+    yield checkSharingUI({audio: true, video: true});
+
+    // gUM(screen) causes a prompt.
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(false, true, null, "screen");
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+
+    is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
+       "webRTC-shareScreen-notification-icon", "anchored to device icon");
+    checkDeviceSelectors(false, false, true);
+
+    yield promiseMessage(permissionError, () => {
+      PopupNotifications.panel.firstChild.button.click();
+    });
+
+    yield expectObserverCalled("getUserMedia:response:deny");
+
+    // After closing all streams,
+    // gUM(audio+camera) causes a prompt.
+    yield closeStream(false, 0, 2);
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(true, true);
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    checkDeviceSelectors(true, true);
+
+    yield promiseMessage(permissionError, () => {
+      activateSecondaryAction(kActionDeny);
+    });
+
+    yield expectObserverCalled("getUserMedia:response:deny");
+    yield expectObserverCalled("recording-window-ended");
+    yield checkNotSharing();
+  }
+},
+
+{
+  desc: "If there's an active camera stream, " +
+        "gUM(audio) causes a prompt; " +
+        "gUM(audio+camera) causes a prompt; " +
+        "gUM(screen) causes a prompt; " +
+        "gUM(camera) returns a stream without prompting.",
+  run: function* checkAudioVideoWhileLiveTracksExist_camera() {
+    let promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(false, true);
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    let indicator = promiseIndicatorWindow();
+
+    yield promiseMessage("ok", () => {
+      PopupNotifications.panel.firstChild.button.click();
+    });
+
+    yield expectObserverCalled("getUserMedia:response:allow");
+    yield expectObserverCalled("recording-device-events");
+    Assert.deepEqual((yield getMediaCaptureState()), {video: true},
+                     "expected camera to be shared");
+    yield indicator;
+    yield checkSharingUI({audio: false, video: true});
+
+    // If there's an active camera stream,
+    // gUM(audio) causes a prompt;
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(true, false);
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    checkDeviceSelectors(true, false);
+
+    yield promiseMessage(permissionError, () => {
+      activateSecondaryAction(kActionDeny);
+    });
+
+    yield expectObserverCalled("getUserMedia:response:deny");
+
+    // gUM(audio+camera) causes a prompt;
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(true, true);
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    checkDeviceSelectors(true, true);
+
+    yield promiseMessage(permissionError, () => {
+      activateSecondaryAction(kActionDeny);
+    });
+
+    yield expectObserverCalled("getUserMedia:response:deny");
+
+    // gUM(screen) causes a prompt;
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(false, true, null, "screen");
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+
+    is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
+       "webRTC-shareScreen-notification-icon", "anchored to device icon");
+    checkDeviceSelectors(false, false, true);
+
+    yield promiseMessage(permissionError, () => {
+      PopupNotifications.panel.firstChild.button.click();
+    });
+
+    yield expectObserverCalled("getUserMedia:response:deny");
+
+    // gUM(camera) returns a stream without prompting.
+    promise = promiseMessage("ok");
+    yield promiseRequestDevice(false, true);
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    yield promiseNoPopupNotification("webRTC-shareDevices");
+    yield expectObserverCalled("getUserMedia:response:allow");
+    yield expectObserverCalled("recording-device-events");
+
+    Assert.deepEqual((yield getMediaCaptureState()), {video: true},
+                     "expected camera to be shared");
+
+    yield checkSharingUI({audio: false, video: true});
+
+    // close all streams
+    yield closeStream(false, 0, 2);
+  }
+},
+
+{
+  desc: "If there's an active audio stream, " +
+        "gUM(camera) causes a prompt; " +
+        "gUM(audio+camera) causes a prompt; " +
+        "gUM(audio) returns a stream without prompting.",
+  run: function* checkAudioVideoWhileLiveTracksExist_audio() {
+    let promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(true, false);
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    let indicator = promiseIndicatorWindow();
+
+    yield promiseMessage("ok", () => {
+      PopupNotifications.panel.firstChild.button.click();
+    });
+
+    yield expectObserverCalled("getUserMedia:response:allow");
+    yield expectObserverCalled("recording-device-events");
+    Assert.deepEqual((yield getMediaCaptureState()), {audio: true},
+                     "expected microphone to be shared");
+    yield indicator;
+    yield checkSharingUI({audio: true, video: false});
+
+    // If there's an active audio stream,
+    // gUM(camera) causes a prompt;
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(false, true);
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    checkDeviceSelectors(false, true);
+
+    yield promiseMessage(permissionError, () => {
+      activateSecondaryAction(kActionDeny);
+    });
+
+    yield expectObserverCalled("getUserMedia:response:deny");
+
+    // gUM(audio+camera) causes a prompt;
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(true, true);
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    checkDeviceSelectors(true, true);
+
+    yield promiseMessage(permissionError, () => {
+      activateSecondaryAction(kActionDeny);
+    });
+
+    yield expectObserverCalled("getUserMedia:response:deny");
+
+    // gUM(audio) returns a stream without prompting.
+    promise = promiseMessage("ok");
+    yield promiseRequestDevice(true, false);
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    yield promiseNoPopupNotification("webRTC-shareDevices");
+    yield expectObserverCalled("getUserMedia:response:allow");
+    yield expectObserverCalled("recording-device-events");
+
+    Assert.deepEqual((yield getMediaCaptureState()), {audio: true},
+                     "expected microphone to be shared");
+
+    yield checkSharingUI({audio: true, video: false});
+
+    // close all streams
+    yield closeStream(false, 0, 2);
+  }
+}
+
+];
+
+function test() {
+  waitForExplicitFinish();
+
+  let tab = gBrowser.addTab();
+  gBrowser.selectedTab = tab;
+  let browser = tab.linkedBrowser;
+
+  browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+  browser.addEventListener("load", function onload() {
+    browser.removeEventListener("load", onload, true);
+
+    is(PopupNotifications._currentNotifications.length, 0,
+       "should start the test without any prior popup notification");
+    ok(gIdentityHandler._identityPopup.hidden,
+       "should start the test with the control center hidden");
+
+    Task.spawn(function* () {
+      yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]});
+
+      for (let testCase of gTests) {
+        info(testCase.desc);
+        yield testCase.run();
+
+        // Cleanup before the next test
+        yield expectNoObserverCalled();
+      }
+    }).then(finish, ex => {
+     Cu.reportError(ex);
+     ok(false, "Unexpected Exception: " + ex);
+     finish();
+    });
+  }, true);
+  let rootDir = getRootDirectory(gTestPath);
+  rootDir = rootDir.replace("chrome://mochitests/content/",
+                            "https://example.com/");
+  content.location = rootDir + "get_user_media.html";
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_in_frame.js
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+registerCleanupFunction(function() {
+  gBrowser.removeCurrentTab();
+});
+
+function promiseReloadFrame(aFrameId) {
+  return ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(contentFrameId) {
+    content.wrappedJSObject
+           .document
+           .getElementById(contentFrameId)
+           .contentWindow
+           .location
+           .reload();
+  });
+}
+
+const permissionError = "error: NotAllowedError: The request is not allowed " +
+    "by the user agent or the platform in the current context.";
+
+var gTests = [
+
+{
+  desc: "If there's an active audio+camera stream in frame 1, " +
+        "gUM(audio+camera) in frame 2 causes a prompt.",
+  run: function* checkAudioVideoWhileLiveTracksExist_frame() {
+    let promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(true, true, "frame1");
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    checkDeviceSelectors(true, true);
+
+    let indicator = promiseIndicatorWindow();
+    yield promiseMessage("ok", () => {
+      PopupNotifications.panel.firstChild.button.click();
+    });
+    yield expectObserverCalled("getUserMedia:response:allow");
+    yield expectObserverCalled("recording-device-events");
+    Assert.deepEqual((yield getMediaCaptureState()), {audio: true, video: true},
+                     "expected camera and microphone to be shared");
+
+    yield indicator;
+    yield checkSharingUI({video: true, audio: true});
+    yield expectNoObserverCalled();
+
+    // If there's an active audio+camera stream in frame 1,
+    // gUM(audio+camera) in frame 2 causes a prompt;
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(true, true, "frame2");
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    checkDeviceSelectors(true, true);
+
+    yield promiseMessage(permissionError, () => {
+      activateSecondaryAction(kActionDeny);
+    });
+
+    yield expectObserverCalled("getUserMedia:response:deny");
+    yield expectObserverCalled("recording-window-ended");
+
+    // close the stream
+    yield closeStream(false, "frame1");
+  }
+}
+
+];
+
+function test() {
+  waitForExplicitFinish();
+
+  let tab = gBrowser.addTab();
+  gBrowser.selectedTab = tab;
+  let browser = tab.linkedBrowser;
+
+  browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+  browser.addEventListener("load", function onload() {
+    browser.removeEventListener("load", onload, true);
+
+    is(PopupNotifications._currentNotifications.length, 0,
+       "should start the test without any prior popup notification");
+
+    Task.spawn(function* () {
+      yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]});
+
+      for (let testCase of gTests) {
+        info(testCase.desc);
+        yield testCase.run();
+
+        // Cleanup before the next test
+        yield expectNoObserverCalled();
+      }
+    }).then(finish, ex => {
+     Cu.reportError(ex);
+     ok(false, "Unexpected Exception: " + ex);
+     finish();
+    });
+  }, true);
+  let rootDir = getRootDirectory(gTestPath);
+  rootDir = rootDir.replace("chrome://mochitests/content/",
+                            "https://example.com/");
+  let url = rootDir + "get_user_media.html";
+  content.location = 'data:text/html,<iframe id="frame1" src="' + url + '"></iframe><iframe id="frame2" src="' + url + '"></iframe>'
+}
--- a/browser/base/content/test/webrtc/get_user_media.html
+++ b/browser/base/content/test/webrtc/get_user_media.html
@@ -17,39 +17,42 @@ try {
   useFakeStreams = true;
 }
 
 function message(m) {
   document.getElementById("message").innerHTML = m;
   window.parent.postMessage(m, "*");
 }
 
-var gStream;
+var gStreams = [];
 
 function requestDevice(aAudio, aVideo, aShare) {
   var opts = {video: aVideo, audio: aAudio};
   if (aShare) {
     opts.video = {
       mozMediaSource: aShare,
       mediaSource: aShare
     }
   } else if (useFakeStreams) {
     opts.fake = true;
   }
 
   window.navigator.mediaDevices.getUserMedia(opts)
     .then(stream => {
-      gStream = stream;
+      gStreams.push(stream);
       message("ok");
     }, err => message("error: " + err));
 }
 message("pending");
 
 function closeStream() {
-  if (!gStream)
-    return;
-  gStream.getTracks().forEach(t => t.stop());
-  gStream = null;
+  for (let stream of gStreams) {
+    if (stream) {
+      stream.getTracks().forEach(t => t.stop());
+      stream = null;
+    }
+  }
+  gStreams = [];
   message("closed");
 }
 </script>
 </body>
 </html>
--- a/browser/base/content/test/webrtc/head.js
+++ b/browser/base/content/test/webrtc/head.js
@@ -377,23 +377,26 @@ function promiseRequestDevice(aRequestAu
                            function*(args) {
     let global = content.wrappedJSObject;
     if (args.aFrameId)
       global = global.document.getElementById(args.aFrameId).contentWindow;
     global.requestDevice(args.aRequestAudio, args.aRequestVideo, args.aType);
   });
 }
 
-function* closeStream(aAlreadyClosed, aFrameId) {
+function* closeStream(aAlreadyClosed, aFrameId, aNumOfStreams = 1) {
   yield expectNoObserverCalled();
 
   let promises;
   if (!aAlreadyClosed) {
-    promises = [promiseObserverCalled("recording-device-events"),
-                promiseObserverCalled("recording-window-ended")];
+    promises = [];
+    for (let i = 0; i < aNumOfStreams; i++) {
+      promises.push(promiseObserverCalled("recording-device-events"));
+    }
+    promises.push(promiseObserverCalled("recording-window-ended"));
   }
 
   info("closing the stream");
   yield ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(contentFrameId) {
     let global = content.wrappedJSObject;
     if (contentFrameId)
       global = global.document.getElementById(contentFrameId).contentWindow;
     global.closeStream();