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 462486 b9f565351005e48fd1316fbe0a17f0c9bde5385f
parent 462485 889b1be7057c6b5fb8d7eae0b9fe64aee5db3cd7
child 542412 e96aef880b8dd439d2b0d8d4cc924eaf9fa8079c
push id41771
push usermchiang@mozilla.com
push dateTue, 17 Jan 2017 15:35:06 +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.js
browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js
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/browser_devices_get_user_media_unprompted_access_tear_off_tab.js
browser/base/content/test/webrtc/get_user_media.html
browser/base/content/test/webrtc/get_user_media_in_frame.html
browser/base/content/test/webrtc/head.js
--- a/browser/base/content/test/webrtc/browser.ini
+++ b/browser/base/content/test/webrtc/browser.ini
@@ -1,14 +1,19 @@
 [DEFAULT]
 support-files =
   get_user_media.html
+  get_user_media_in_frame.html
   get_user_media_content_script.js
   head.js
 
 [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_devices_get_user_media_unprompted_access_tear_off_tab.js]
+skip-if = (os == "linux") # linux: bug 1331616
 [browser_webrtc_hooks.js]
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media.js
@@ -142,16 +142,31 @@ var gTests = [
 
     yield indicator;
     yield checkSharingUI({video: true, audio: true});
 
     yield stopSharing();
 
     // the stream is already closed, but this will do some cleanup anyway
     yield closeStream(true);
+
+    // After stop sharing, 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");
+    yield expectObserverCalled("recording-window-ended");
+    yield checkNotSharing();
   }
 },
 
 {
   desc: "getUserMedia audio+video: reloading the page removes all gUM UI",
   run: function* checkReloading() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, true);
@@ -167,16 +182,31 @@ var gTests = [
     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 reloadAndAssertClosedStreams();
+
+    // After the reload, 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");
+    yield expectObserverCalled("recording-window-ended");
+    yield checkNotSharing();
   }
 },
 
 {
   desc: "getUserMedia prompt: Always/Never Share",
   run: function* checkRememberCheckbox() {
     let elt = id => document.getElementById(id);
 
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js
@@ -1,27 +1,16 @@
 /* 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();
-  });
-}
-
 var gTests = [
 
 {
   desc: "getUserMedia audio+video",
   run: function* checkAudioVideo() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, true, "frame1");
     yield promise;
@@ -106,20 +95,21 @@ var gTests = [
     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});
 
     info("reloading the frame");
-    promise = promiseObserverCalled("recording-device-events");
+    promise = promiseObserverCalled("recording-device-stopped");
     yield promiseReloadFrame("frame1");
     yield promise;
 
+    yield expectObserverCalled("recording-device-events");
     yield expectObserverCalled("recording-window-ended");
     yield expectNoObserverCalled();
     yield checkNotSharing();
   }
 },
 
 {
   desc: "getUserMedia audio+video: reloading the frame removes prompts",
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access.js
@@ -0,0 +1,270 @@
+/* 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();
+});
+
+const permissionError = "error: NotAllowedError: The request is not allowed " +
+    "by the user agent or the platform in the current context.";
+
+var gTests = [
+
+{
+  desc: "getUserMedia audio+camera",
+  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: "getUserMedia camera",
+  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: "getUserMedia audio",
+  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,236 @@
+/* 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();
+});
+
+const permissionError = "error: NotAllowedError: The request is not allowed " +
+    "by the user agent or the platform in the current context.";
+
+var gTests = [
+
+{
+  desc: "getUserMedia audio+camera in frame 1",
+  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();
+
+    info("gUM(audio+camera) in frame 2 should 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");
+
+    // If there's an active audio+camera stream in frame 1,
+    // gUM(audio+camera) in frame 1 returns a stream without prompting;
+    promise = promiseMessage("ok");
+    yield promiseRequestDevice(true, true, "frame1");
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    yield promiseNoPopupNotification("webRTC-shareDevices");
+    yield expectObserverCalled("getUserMedia:response:allow");
+    yield expectObserverCalled("recording-device-events");
+
+    // close the stream
+    yield closeStream(false, "frame1", 2);
+  }
+},
+
+{
+  desc: "getUserMedia audio+camera in frame 1 - part II",
+  run: function* checkAudioVideoWhileLiveTracksExist_frame_partII() {
+    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 the top level window 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");
+    yield expectObserverCalled("recording-window-ended");
+
+    // close the stream
+    yield closeStream(false, "frame1");
+  }
+},
+
+{
+  desc: "getUserMedia audio+camera in frame 1 - reload",
+  run: function* checkAudioVideoWhileLiveTracksExist_frame_reload() {
+    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();
+
+    // reload frame 1
+    promise = promiseObserverCalled("recording-device-stopped");
+    yield promiseReloadFrame("frame1");
+    yield promise;
+
+    yield checkNotSharing();
+    yield expectObserverCalled("recording-device-events");
+    yield expectObserverCalled("recording-window-ended");
+    yield expectNoObserverCalled();
+
+    // After the reload,
+    // gUM(audio+camera) in frame 1 causes a prompt.
+    promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(true, true, "frame1");
+    yield promise;
+    yield expectObserverCalled("getUserMedia:request");
+    checkDeviceSelectors(true, true);
+
+    yield promiseMessage(permissionError, () => {
+      activateSecondaryAction(kActionDeny);
+    });
+
+    yield expectObserverCalled("getUserMedia:response:deny");
+    yield expectObserverCalled("recording-window-ended");
+  }
+},
+
+{
+  desc: "getUserMedia audio+camera at the top level window",
+  run: function* checkAudioVideoWhileLiveTracksExist_topLevel() {
+    // create an active audio+camera stream at the top level window
+    let promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(true, true);
+    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({audio: true, video: true});
+
+    // If there's an active audio+camera stream at the top level window,
+    // 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);
+  }
+}
+
+];
+
+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/");
+  content.location = rootDir + "get_user_media_in_frame.html";
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_tear_off_tab.js
@@ -0,0 +1,102 @@
+/* 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();
+});
+
+var gTests = [
+
+{
+  desc: "getUserMedia: tearing-off a tab",
+  run: function* checkAudioVideoWhileLiveTracksExist_TearingOff() {
+    let promise = promisePopupNotificationShown("webRTC-shareDevices");
+    yield promiseRequestDevice(true, true);
+    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});
+
+    info("tearing off the tab");
+    let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+    yield whenDelayedStartupFinished(win);
+    yield checkSharingUI({audio: true, video: true}, win);
+
+    gBrowser.selectedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+    info("request audio+video and check if there is no prompt");
+    yield promiseRequestDevice(true, true, null, null, win.gBrowser.selectedBrowser);
+    yield promiseObserverCalled("getUserMedia:request");
+    yield promiseNoPopupNotification("webRTC-shareDevices");
+    yield expectObserverCalled("getUserMedia:response:allow");
+    yield expectObserverCalled("recording-device-events");
+
+    let promises = [promiseObserverCalled("recording-device-events"),
+                    promiseObserverCalled("recording-device-events"),
+                    promiseObserverCalled("recording-window-ended")];
+    yield BrowserTestUtils.closeWindow(win);
+    yield Promise.all(promises);
+
+    yield checkNotSharing();
+  }
+}
+
+];
+
+function test() {
+  waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 1]]}, runTest);
+}
+
+function runTest() {
+  // An empty tab where we can load the content script without leaving it
+  // behind at the end of the test.
+  gBrowser.addTab();
+
+  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";
+}
--- 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>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/webrtc/get_user_media_in_frame.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="UTF-8"></head>
+<body>
+<div id="message"></div>
+<script>
+// Specifies whether we are using fake streams to run this automation
+var useFakeStreams = true;
+try {
+  var audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev");
+  var videoDevice = SpecialPowers.getCharPref("media.video_loopback_dev");
+  dump("TEST DEVICES: Using media devices:\n");
+  dump("audio: " + audioDevice + "\nvideo: " + videoDevice + "\n");
+  useFakeStreams = false;
+} catch (e) {
+  dump("TEST DEVICES: No test devices found (in media.{audio,video}_loopback_dev, using fake streams.\n");
+  useFakeStreams = true;
+}
+
+function message(m) {
+  document.getElementById("message").innerHTML = m;
+  window.parent.postMessage(m, "*");
+}
+
+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 => {
+      gStreams.push(stream);
+      message("ok");
+    }, err => message("error: " + err));
+}
+message("pending");
+
+function closeStream() {
+  for (let stream of gStreams) {
+    if (stream) {
+      stream.getTracks().forEach(t => t.stop());
+      stream = null;
+    }
+  }
+  gStreams = [];
+  message("closed");
+}
+</script>
+<iframe id="frame1" src="get_user_media.html"></iframe>
+<iframe id="frame2" src="get_user_media.html"></iframe>
+</body>
+</html>
--- a/browser/base/content/test/webrtc/head.js
+++ b/browser/base/content/test/webrtc/head.js
@@ -365,35 +365,39 @@ function* stopSharing(aType = "camera", 
     yield expectObserverCalled("recording-window-ended");
 
   yield expectNoObserverCalled(aExpectDoubleRecordingEvent);
 
   if (!aShouldKeepSharing)
     yield* checkNotSharing();
 }
 
-function promiseRequestDevice(aRequestAudio, aRequestVideo, aFrameId, aType) {
+function promiseRequestDevice(aRequestAudio, aRequestVideo, aFrameId, aType,
+                              aBrowser = gBrowser.selectedBrowser) {
   info("requesting devices");
-  return ContentTask.spawn(gBrowser.selectedBrowser,
+  return ContentTask.spawn(aBrowser,
                            {aRequestAudio, aRequestVideo, aFrameId, aType},
                            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, aStreamCount = 1) {
   yield expectNoObserverCalled();
 
   let promises;
   if (!aAlreadyClosed) {
-    promises = [promiseObserverCalled("recording-device-events"),
-                promiseObserverCalled("recording-window-ended")];
+    promises = [];
+    for (let i = 0; i < aStreamCount; 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();
@@ -488,8 +492,19 @@ function* checkNotSharing() {
   Assert.deepEqual((yield getMediaCaptureState()), {},
                    "expected nothing to be shared");
 
   ok(!document.getElementById("identity-box").hasAttribute("sharing"),
      "no sharing indicator on the control center icon");
 
   yield* assertWebRTCIndicatorStatus(null);
 }
+
+function promiseReloadFrame(aFrameId) {
+  return ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(contentFrameId) {
+    content.wrappedJSObject
+           .document
+           .getElementById(contentFrameId)
+           .contentWindow
+           .location
+           .reload();
+  });
+}