Bug 1297336 - Preserve device sharing indicator when tearing off a tab, r=johannh.
authorFlorian Quèze <florian@queze.net>
Thu, 08 Sep 2016 12:46:25 +0200
changeset 354498 47080eb5264154cdc8726628981c1a7948eec96d
parent 354429 9295128924d1496d7cea2a62c8b39c073a1cb12b
child 354499 beda515683970341d1338dbc687bb929c0ce291f
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh
bugs1297336
milestone51.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 1297336 - Preserve device sharing indicator when tearing off a tab, r=johannh.
browser/base/content/tabbrowser.xml
browser/base/content/test/webrtc/browser.ini
browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
browser/base/content/test/webrtc/head.js
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2658,16 +2658,18 @@
             }
             if (aOtherTab.hasAttribute("usercontextid")) {
               aOurTab.setUserContextId(aOtherTab.getAttribute("usercontextid"));
               modifiedAttrs.push("usercontextid");
             }
             if (aOtherTab.hasAttribute("sharing")) {
               aOurTab.setAttribute("sharing", aOtherTab.getAttribute("sharing"));
               modifiedAttrs.push("sharing");
+              aOurTab._sharingState = aOtherTab._sharingState;
+              webrtcUI.swapBrowserForNotification(otherBrowser, ourBrowser);
             }
 
             // If the other tab is pending (i.e. has not been restored, yet)
             // then do not switch docShells but retrieve the other tab's state
             // and apply it to our tab.
             if (isPending) {
               SessionStore.setTabState(aOurTab, SessionStore.getTabState(aOtherTab));
 
--- a/browser/base/content/test/webrtc/browser.ini
+++ b/browser/base/content/test/webrtc/browser.ini
@@ -3,8 +3,9 @@ support-files =
   get_user_media.html
   get_user_media_content_script.js
   head.js
 
 [browser_devices_get_user_media.js]
 skip-if = buildapp == 'mulet' || (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_tear_off_tab.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.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();
+});
+
+var gTests = [
+
+{
+  desc: "getUserMedia: tearing-off a tab keeps sharing indicators",
+  run: function* checkTearingOff() {
+    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");
+    is((yield getMediaCaptureState()), "CameraAndMicrophone",
+       "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);
+
+    // Clicking the global sharing indicator should open the control center in
+    // the second window.
+    ok(win.gIdentityHandler._identityPopup.hidden, "control center should be hidden");
+    let activeStreams = webrtcUI.getActiveStreams(true, false, false);
+    webrtcUI.showSharingDoorhanger(activeStreams[0], "Devices");
+    ok(!win.gIdentityHandler._identityPopup.hidden,
+       "control center should be open in the second window");
+    ok(gIdentityHandler._identityPopup.hidden,
+       "control center should be hidden in the first window");
+    win.gIdentityHandler._identityPopup.hidden = true;
+
+    // Closing the new window should remove all sharing indicators.
+    // We need to load the content script in the first window so that we can
+    // catch the notifications fired globally when closing the second window.
+    gBrowser.selectedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+    yield BrowserTestUtils.closeWindow(win);
+
+    if ((yield promiseTodoObserverNotCalled("recording-device-events")) == 1) {
+      todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
+    }
+
+    yield expectObserverCalled("recording-window-ended");
+    yield expectNoObserverCalled();
+    yield checkNotSharing();
+  }
+}
+
+];
+
+function test() {
+  waitForExplicitFinish();
+
+  // 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 test of gTests) {
+        info(test.desc);
+        yield test.run();
+
+        // Cleanup before the next test
+        yield expectNoObserverCalled();
+      }
+    }).then(finish, 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/head.js
+++ b/browser/base/content/test/webrtc/head.js
@@ -58,16 +58,28 @@ function promiseWindow(url) {
 
         Services.obs.removeObserver(obs, "domwindowopened");
         resolve(win);
       });
     }, "domwindowopened", false);
   });
 }
 
+function whenDelayedStartupFinished(aWindow) {
+  return new Promise(resolve => {
+    info("Waiting for delayed startup to finish");
+    Services.obs.addObserver(function observer(aSubject, aTopic) {
+      if (aWindow == aSubject) {
+        Services.obs.removeObserver(observer, aTopic);
+        resolve();
+      }
+    }, "browser-delayed-startup-finished", false);
+  });
+}
+
 function promiseIndicatorWindow() {
   // We don't show the indicator window on Mac.
   if ("nsISystemStatusBar" in Ci)
     return Promise.resolve();
 
   return promiseWindow("chrome://browser/content/webrtcIndicator.xul");
 }
 
@@ -402,55 +414,56 @@ function checkDeviceSelectors(aAudio, aV
 
   let cameraSelector = document.getElementById("webRTC-selectCamera");
   if (aVideo)
     ok(!cameraSelector.hidden, "camera selector visible");
   else
     ok(cameraSelector.hidden, "camera selector hidden");
 }
 
-function* checkSharingUI(aExpected) {
+function* checkSharingUI(aExpected, aWin = window) {
+  let doc = aWin.document;
   // First check the icon above the control center (i) icon.
-  let identityBox = document.getElementById("identity-box");
+  let identityBox = doc.getElementById("identity-box");
   ok(identityBox.hasAttribute("sharing"), "sharing attribute is set");
   let sharing = identityBox.getAttribute("sharing");
   if (aExpected.video)
     is(sharing, "camera", "showing camera icon on the control center icon");
   else if (aExpected.audio)
     is(sharing, "microphone", "showing mic icon on the control center icon");
 
   // Then check the sharing indicators inside the control center panel.
   identityBox.click();
-  let permissions = document.getElementById("identity-popup-permission-list");
+  let permissions = doc.getElementById("identity-popup-permission-list");
   for (let id of ["microphone", "camera", "screen"]) {
     let convertId = id => {
       if (id == "camera")
         return "video";
       if (id == "microphone")
         return "audio";
       return id;
     };
     let expected = aExpected[convertId(id)];
-    is(!!gIdentityHandler._sharingState[id], !!expected,
+    is(!!aWin.gIdentityHandler._sharingState[id], !!expected,
        "sharing state for " + id + " as expected");
     let icon = permissions.querySelectorAll(
       ".identity-popup-permission-icon." + id + "-icon");
     if (expected) {
       is(icon.length, 1, "should show " + id + " icon in control center panel");
       ok(icon[0].classList.contains("in-use"), "icon should have the in-use class");
     } else if (!icon.length) {
       ok(true, "should not show " + id + " icon in the control center panel");
     } else {
       // This will happen if there are persistent permissions set.
       ok(!icon[0].classList.contains("in-use"),
          "if shown, the " + id + " icon should not have the in-use class");
       is(icon.length, 1, "should not show more than 1 " + id + " icon");
     }
   }
-  gIdentityHandler._identityPopup.hidden = true;
+  aWin.gIdentityHandler._identityPopup.hidden = true;
 
   // Check the global indicators.
   yield* assertWebRTCIndicatorStatus(aExpected);
 }
 
 function* checkNotSharing() {
   is((yield getMediaCaptureState()), "none", "expected nothing to be shared");