Bug 861716 - add a mochitest for gUM request queue in MediaManager. r?jib, florian draft
authorMunro Mengjue Chiang <mchiang@mozilla.com>
Wed, 17 May 2017 10:59:12 +0800
changeset 592987 a965439d0c7abfea15c999d330d9a1039f5695af
parent 592986 c28b246a96db93a0c7b4d6f06b3143697812b169
child 592988 54c8cd89415ceae251bb11ab926341c31615f41d
push id63567
push userbmo:mchiang@mozilla.com
push dateTue, 13 Jun 2017 02:25:20 +0000
reviewersjib, florian
bugs861716
milestone56.0a1
Bug 861716 - add a mochitest for gUM request queue in MediaManager. r?jib, florian MozReview-Commit-ID: 750T4pzvf95
browser/base/content/test/webrtc/browser.ini
browser/base/content/test/webrtc/browser_devices_get_user_media_queue_request.js
browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_queue_request.js
browser/base/content/test/webrtc/get_user_media.html
browser/base/content/test/webrtc/get_user_media_content_script.js
browser/base/content/test/webrtc/head.js
--- a/browser/base/content/test/webrtc/browser.ini
+++ b/browser/base/content/test/webrtc/browser.ini
@@ -13,9 +13,11 @@ skip-if = debug # bug 1369731
 [browser_devices_get_user_media_multi_process.js]
 skip-if = e10s && (asan || debug) # bug 1347625
 [browser_devices_get_user_media_screen.js]
 [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_devices_get_user_media_unprompted_access_tear_off_tab.js]
 skip-if = (os == "win" && bits == 64) # win8: bug 1334752
+[browser_devices_get_user_media_unprompted_access_queue_request.js]
 [browser_webrtc_hooks.js]
+[browser_devices_get_user_media_queue_request.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_queue_request.js
@@ -0,0 +1,158 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const permissionError = "error: NotAllowedError: The request is not allowed " +
+    "by the user agent or the platform in the current context.";
+
+const badDeviceError =
+    "error: NotReadableError: Failed to allocate videosource";
+
+var gTests = [
+
+{
+  desc: "test queueing deny audio behind allow video",
+  run: async function testQueuingDenyAudioBehindAllowVideo() {
+    let promise = promisePopupNotificationShown("webRTC-shareDevices");
+    await promiseRequestDevice(false, true);
+    await promiseRequestDevice(true, false);
+    await promise;
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+    checkDeviceSelectors(false, true);
+    await expectObserverCalled("getUserMedia:request");
+    let indicator = promiseIndicatorWindow();
+
+    await promiseMessage("ok", () => {
+      PopupNotifications.panel.firstChild.button.click();
+    });
+
+    await expectObserverCalled("getUserMedia:response:allow");
+    await expectObserverCalled("recording-device-events");
+    Assert.deepEqual((await getMediaCaptureState()), {video: true},
+                     "expected camera to be shared");
+    await indicator;
+    await checkSharingUI({audio: false, video: true});
+
+    await promise;
+    await expectObserverCalled("getUserMedia:request");
+    checkDeviceSelectors(true, false);
+
+    await promiseMessage(permissionError, () => {
+      activateSecondaryAction(kActionDeny);
+    });
+
+    await expectObserverCalled("getUserMedia:response:deny");
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+
+    // close all streams
+    await closeStream();
+  }
+},
+
+{
+  desc: "test queueing allow video behind deny audio",
+  run: async function testQueuingAllowVideoBehindDenyAudio() {
+    let promise = promisePopupNotificationShown("webRTC-shareDevices");
+    await promiseRequestDevice(true, false);
+    await promiseRequestDevice(false, true);
+    await promise;
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+    await expectObserverCalled("getUserMedia:request");
+    checkDeviceSelectors(true, false);
+
+    await promiseMessage(permissionError, () => {
+      activateSecondaryAction(kActionDeny);
+    });
+
+    await expectObserverCalled("getUserMedia:response:deny");
+
+    await promise;
+    checkDeviceSelectors(false, true);
+    await expectObserverCalled("getUserMedia:request");
+
+    let indicator = promiseIndicatorWindow();
+
+    await promiseMessage("ok", () => {
+      PopupNotifications.panel.firstChild.button.click();
+    });
+
+    await expectObserverCalled("getUserMedia:response:allow");
+    await expectObserverCalled("recording-device-events");
+    Assert.deepEqual((await getMediaCaptureState()), {video: true},
+                     "expected camera to be shared");
+    await indicator;
+    await checkSharingUI({audio: false, video: true});
+
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+
+    // close all streams
+    await closeStream();
+  }
+},
+
+{
+  desc: "test queueing allow audio behind allow video with error",
+  run: async function testQueuingAllowAudioBehindAllowVideoWithError() {
+    let promise = promisePopupNotificationShown("webRTC-shareDevices");
+    await promiseRequestDevice(false, true, null, null, gBrowser.selectedBrowser, true);
+    await promiseRequestDevice(true, false);
+    await promise;
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+
+    checkDeviceSelectors(false, true);
+
+    await expectObserverCalled("getUserMedia:request");
+
+    await promiseMessage(badDeviceError, () => {
+      PopupNotifications.panel.firstChild.button.click();
+    });
+
+    await expectObserverCalled("getUserMedia:response:allow");
+
+    await promise;
+    checkDeviceSelectors(true, false);
+    await expectObserverCalled("getUserMedia:request");
+    let indicator = promiseIndicatorWindow();
+
+    await promiseMessage("ok", () => {
+      PopupNotifications.panel.firstChild.button.click();
+    });
+
+    await expectObserverCalled("getUserMedia:response:allow");
+    await expectObserverCalled("recording-device-events");
+    Assert.deepEqual((await getMediaCaptureState()), {audio: true},
+                     "expected microphone to be shared");
+    await indicator;
+    await checkSharingUI({audio: true, video: false});
+
+    // close all streams
+    await closeStream();
+  }
+},
+
+{
+  desc: "test queueing audio+video behind deny audio",
+  run: async function testQueuingAllowVideoBehindDenyAudio() {
+    let promise = promisePopupNotificationShown("webRTC-shareDevices");
+    await promiseRequestDevice(true, false);
+    await promiseRequestDevice(true, true);
+    await promise;
+    await expectObserverCalled("getUserMedia:request");
+    checkDeviceSelectors(true, false);
+
+    promise = promiseSpecificMessageReceived(permissionError, 2);
+    activateSecondaryAction(kActionDeny);
+    await promise;
+
+    await expectObserverCalled("getUserMedia:request");
+    await expectObserverCalled("getUserMedia:response:deny", 2);
+    await expectObserverCalled("recording-window-ended");
+
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+  }
+}
+
+];
+
+add_task(async function test() {
+  await runTests(gTests);
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_queue_request.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gTests = [
+
+{
+  desc: "test queueing allow video behind allow video",
+  run: async function testQueuingAllowVideoBehindAllowVideo() {
+    let promise = promisePopupNotificationShown("webRTC-shareDevices");
+    await promiseRequestDevice(false, true);
+    await promiseRequestDevice(false, true);
+    await promise;
+    checkDeviceSelectors(false, true);
+    await expectObserverCalled("getUserMedia:request");
+
+    let promiseOK = promiseSpecificMessageReceived("ok", 2);
+    PopupNotifications.panel.firstChild.button.click();
+    await promiseOK;
+
+    await promiseNoPopupNotification("webRTC-shareDevices");
+    await expectObserverCalled("getUserMedia:request");
+    await expectObserverCalled("getUserMedia:response:allow", 2);
+    Assert.deepEqual((await getMediaCaptureState()), {video: true},
+                     "expected camera to be shared");
+    await expectObserverCalled("recording-device-events", 2);
+
+    // close all streams
+    await closeStream();
+  }
+}
+
+];
+
+add_task(async function test() {
+  SimpleTest.requestCompleteLog();
+  await runTests(gTests);
+});
--- a/browser/base/content/test/webrtc/get_user_media.html
+++ b/browser/base/content/test/webrtc/get_user_media.html
@@ -19,27 +19,41 @@ try {
 
 function message(m) {
   document.getElementById("message").innerHTML = m;
   window.parent.postMessage(m, "*");
 }
 
 var gStreams = [];
 
-function requestDevice(aAudio, aVideo, aShare) {
+function requestDevice(aAudio, aVideo, aShare, aBadDevice = false) {
   var opts = {video: aVideo, audio: aAudio};
   if (aShare) {
     opts.video = {
       mozMediaSource: aShare,
       mediaSource: aShare
     }
   } else if (useFakeStreams) {
     opts.fake = true;
   }
 
+  if (aVideo && aBadDevice) {
+    opts.video = {
+      deviceId: "bad device"
+    }
+    opts.fake = true;
+  }
+
+  if (aAudio && aBadDevice) {
+    opts.audio = {
+      deviceId: "bad device"
+    }
+    opts.fake = true;
+  }
+
   window.navigator.mediaDevices.getUserMedia(opts)
     .then(stream => {
       gStreams.push(stream);
       message("ok");
     }, err => message("error: " + err));
 }
 message("pending");
 
--- a/browser/base/content/test/webrtc/get_user_media_content_script.js
+++ b/browser/base/content/test/webrtc/get_user_media_content_script.js
@@ -46,21 +46,22 @@ function observer(aSubject, aTopic, aDat
   else
     ++gObservedTopics[aTopic];
 }
 
 kObservedTopics.forEach(topic => {
   Services.obs.addObserver(observer, topic);
 });
 
-addMessageListener("Test:ExpectObserverCalled", ({data}) => {
+addMessageListener("Test:ExpectObserverCalled", ({ data: { topic, count } }) => {
   sendAsyncMessage("Test:ExpectObserverCalled:Reply",
-                   {count: gObservedTopics[data]});
-  if (data in gObservedTopics)
-    --gObservedTopics[data];
+                   {count: gObservedTopics[topic]});
+  if (topic in gObservedTopics) {
+    gObservedTopics[topic] -= count;
+  }
 });
 
 addMessageListener("Test:ExpectNoObserverCalled", data => {
   sendAsyncMessage("Test:ExpectNoObserverCalled:Reply", gObservedTopics);
   gObservedTopics = {};
 });
 
 function _getMediaCaptureState() {
@@ -115,13 +116,23 @@ addMessageListener("Test:WaitForObserver
       if (!(topic in gObservedTopics))
         gObservedTopics[topic] = -1;
       else
         --gObservedTopics[topic];
     }
   }, topic);
 });
 
+function messageListener({data}) {
+  sendAsyncMessage("Test:MessageReceived", data);
+}
+
 addMessageListener("Test:WaitForMessage", () => {
-  content.addEventListener("message", ({data}) => {
-    sendAsyncMessage("Test:MessageReceived", data);
-  }, {once: true});
+  content.addEventListener("message", messageListener, {once: true});
 });
+
+addMessageListener("Test:WaitForMultipleMessages", () => {
+  content.addEventListener("message", messageListener);
+});
+
+addMessageListener("Test:StopWaitForMultipleMessages", () => {
+  content.removeEventListener("message", messageListener);
+});
--- a/browser/base/content/test/webrtc/head.js
+++ b/browser/base/content/test/webrtc/head.js
@@ -197,26 +197,26 @@ function promiseObserverCalled(aTopic) {
         mm.removeMessageListener("Test:ObserverCalled", listener);
         resolve();
       }
     });
     mm.sendAsyncMessage("Test:WaitForObserverCall", aTopic);
   });
 }
 
-function expectObserverCalled(aTopic) {
+function expectObserverCalled(aTopic, aCount = 1) {
   return new Promise(resolve => {
     let mm = _mm();
     mm.addMessageListener("Test:ExpectObserverCalled:Reply",
                           function listener({data}) {
-      is(data.count, 1, "expected notification " + aTopic);
+      is(data.count, aCount, "expected notification " + aTopic);
       mm.removeMessageListener("Test:ExpectObserverCalled:Reply", listener);
       resolve();
     });
-    mm.sendAsyncMessage("Test:ExpectObserverCalled", aTopic);
+    mm.sendAsyncMessage("Test:ExpectObserverCalled", {topic: aTopic, count: aCount});
   });
 }
 
 function expectNoObserverCalled() {
   return new Promise(resolve => {
     let mm = _mm();
     mm.addMessageListener("Test:ExpectNoObserverCalled:Reply",
                           function listener({data}) {
@@ -251,16 +251,34 @@ function promiseMessageReceived() {
     mm.addMessageListener("Test:MessageReceived", function listener({data}) {
       mm.removeMessageListener("Test:MessageReceived", listener);
       resolve(data);
     });
     mm.sendAsyncMessage("Test:WaitForMessage");
   });
 }
 
+function promiseSpecificMessageReceived(aMessage, aCount = 1) {
+  return new Promise(resolve => {
+    let mm = _mm();
+    let counter = 0;
+    mm.addMessageListener("Test:MessageReceived", function listener({data}) {
+      if (data == aMessage) {
+        counter++;
+        if (counter == aCount) {
+          mm.sendAsyncMessage("Test:StopWaitForMultipleMessages");
+          mm.removeMessageListener("Test:MessageReceived", listener);
+          resolve(data);
+        }
+      }
+    });
+    mm.sendAsyncMessage("Test:WaitForMultipleMessages");
+  });
+}
+
 function promiseMessage(aMessage, aAction) {
   let promise = new Promise((resolve, reject) => {
     promiseMessageReceived(aAction).then(data => {
       is(data, aMessage, "received " + aMessage);
       if (data == aMessage)
         resolve();
       else
         reject();
@@ -365,25 +383,26 @@ async function stopSharing(aType = "came
 
   await expectNoObserverCalled();
 
   if (!aShouldKeepSharing)
     await checkNotSharing();
 }
 
 function promiseRequestDevice(aRequestAudio, aRequestVideo, aFrameId, aType,
-                              aBrowser = gBrowser.selectedBrowser) {
+                              aBrowser = gBrowser.selectedBrowser,
+                              aBadDevice = false) {
   info("requesting devices");
   return ContentTask.spawn(aBrowser,
-                           {aRequestAudio, aRequestVideo, aFrameId, aType},
+                           {aRequestAudio, aRequestVideo, aFrameId, aType, aBadDevice},
                            async function(args) {
     let global = content.wrappedJSObject;
     if (args.aFrameId)
       global = global.document.getElementById(args.aFrameId).contentWindow;
-    global.requestDevice(args.aRequestAudio, args.aRequestVideo, args.aType);
+    global.requestDevice(args.aRequestAudio, args.aRequestVideo, args.aType, args.aBadDevice);
   });
 }
 
 async function closeStream(aAlreadyClosed, aFrameId) {
   await expectNoObserverCalled();
 
   let promises;
   if (!aAlreadyClosed) {