Bug 1270572 - write tests for the umprompted gUM request; r=florian
authorMunro Mengjue Chiang <mchiang@mozilla.com>
Tue, 20 Dec 2016 14:51:36 +0800
changeset 377457 8516638f3e905ba0542fe6432e2946c365460cfa
parent 377456 bb87f60ef23997dd5b53036ac3b2cb51cc3dedf1
child 377458 937f596dc88ab9d71bf49daf3289d1c6b53e96cd
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflorian
bugs1270572
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
@@ -170,16 +170,34 @@ 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();
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
   }
 },
 
 {
   desc: "getUserMedia audio+video: reloading the page removes all gUM UI",
   run: function* checkReloading() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, true);
@@ -195,16 +213,34 @@ 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();
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
   }
 },
 
 {
   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,291 @@
+/* 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");
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+
+    // 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();
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+  }
+},
+
+{
+  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");
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+
+    // 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");
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+
+    // 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");
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+
+    // 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");
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+
+    // 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");
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+
+    // 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,248 @@
+/* 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");
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+
+    // 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");
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+  }
+},
+
+{
+  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");
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+  }
+},
+
+{
+  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);
+    SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
+    SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
+  }
+}
+
+];
+
+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
@@ -366,35 +366,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();
@@ -489,8 +493,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();
+  });
+}