Bug 1500098 - Reopen multi-selected tabs in container. r=Gijs
authorAbdoulaye O. Ly <ablayelyfondou@gmail.com>
Mon, 12 Nov 2018 18:43:48 +0000
changeset 445928 05331fb8f5338974b913217bc67815df25a32e86
parent 445927 ab2cc746e45119d3bfca34d222a3189036cbe101
child 445929 3b3cbe9ee6ac781648dd62d855a5757b4335c61b
child 445956 062c4368399f6cc17a690c63f0f0b2becf54e185
push id109798
push usercsabou@mozilla.com
push dateMon, 12 Nov 2018 21:56:37 +0000
treeherdermozilla-inbound@3b3cbe9ee6ac [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1500098
milestone65.0a1
first release with
nightly linux32
05331fb8f533 / 65.0a1 / 20181112220107 / files
nightly linux64
05331fb8f533 / 65.0a1 / 20181112220107 / files
nightly mac
05331fb8f533 / 65.0a1 / 20181112220107 / files
nightly win32
05331fb8f533 / 65.0a1 / 20181112220107 / files
nightly win64
05331fb8f533 / 65.0a1 / 20181112220107 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1500098 - Reopen multi-selected tabs in container. r=Gijs Differential Revision: https://phabricator.services.mozilla.com/D11145
browser/base/content/tabbrowser.js
browser/base/content/test/tabs/browser.ini
browser/base/content/test/tabs/browser_multiselect_tabs_reopen_in_container.js
browser/base/content/test/tabs/head.js
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -5542,35 +5542,59 @@ var TabContextMenu = {
     let newIndex = tabsToDuplicate[tabsToDuplicate.length - 1]._tPos + 1;
     for (let tab of tabsToDuplicate) {
       let newTab = SessionStore.duplicateTab(window, tab);
       gBrowser.moveTabTo(newTab, newIndex++);
     }
   },
   reopenInContainer(event) {
     let userContextId = parseInt(event.target.getAttribute("data-usercontextid"));
-    /* Create a triggering principal that is able to load the new tab
-       For codebase principals that are about: chrome: or resource: we need system to load them.
-       Anything other than system principal needs to have the new userContextId.
-    */
-    let triggeringPrincipal = this.contextTab.linkedBrowser.contentPrincipal;
-    if (triggeringPrincipal.isNullPrincipal) {
-      triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal({ userContextId });
-    } else if (triggeringPrincipal.isCodebasePrincipal) {
-      triggeringPrincipal = Services.scriptSecurityManager.createCodebasePrincipal(triggeringPrincipal.URI, { userContextId });
-    }
-    let newTab = gBrowser.addTab(this.contextTab.linkedBrowser.currentURI.spec, {
-      userContextId,
-      pinned: this.contextTab.pinned,
-      index: this.contextTab._tPos + 1,
-      triggeringPrincipal,
-    });
-
-    if (gBrowser.selectedTab == this.contextTab) {
-      gBrowser.selectedTab = newTab;
-    }
-    if (this.contextTab.muted) {
-      if (!newTab.muted) {
-        newTab.toggleMuteAudio(this.contextTab.muteReason);
+    let reopenedTabs = this.contextTab.multiselected ? gBrowser.selectedTabs : [this.contextTab];
+
+    for (let tab of reopenedTabs) {
+      if (tab.getAttribute("usercontextid") == userContextId) {
+        continue;
+      }
+
+      /* Create a triggering principal that is able to load the new tab
+         For codebase principals that are about: chrome: or resource: we need system to load them.
+         Anything other than system principal needs to have the new userContextId.
+      */
+      let triggeringPrincipal;
+
+      if (tab.linkedPanel) {
+        triggeringPrincipal = tab.linkedBrowser.contentPrincipal;
+      } else {
+        // For lazy tab browsers, get the original principal
+        // from SessionStore
+        let tabState = JSON.parse(SessionStore.getTabState(tab));
+        try {
+          triggeringPrincipal = Utils.deserializePrincipal(tabState.triggeringPrincipal_base64);
+        } catch (ex) {
+          continue;
+        }
+      }
+
+      if (!triggeringPrincipal || triggeringPrincipal.isNullPrincipal) {
+        // Ensure that we have a null principal if we couldn't
+        // deserialize it (for lazy tab browsers) ...
+        // This won't always work however is safe to use.
+        triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal({ userContextId });
+      } else if (triggeringPrincipal.isCodebasePrincipal) {
+        triggeringPrincipal = Services.scriptSecurityManager.createCodebasePrincipal(triggeringPrincipal.URI, { userContextId });
+      }
+
+      let newTab = gBrowser.addTab(tab.linkedBrowser.currentURI.spec, {
+        userContextId,
+        pinned: tab.pinned,
+        index: tab._tPos + 1,
+        triggeringPrincipal,
+      });
+
+      if (gBrowser.selectedTab == tab) {
+        gBrowser.selectedTab = newTab;
+      }
+      if (tab.muted && !newTab.muted) {
+        newTab.toggleMuteAudio(tab.muteReason);
       }
     }
   },
 };
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -31,16 +31,17 @@ support-files =
 [browser_multiselect_tabs_duplicate.js]
 [browser_multiselect_tabs_event.js]
 [browser_multiselect_tabs_move_to_another_window_drag.js]
 [browser_multiselect_tabs_move_to_new_window_contextmenu.js]
 [browser_multiselect_tabs_mute_unmute.js]
 [browser_multiselect_tabs_pin_unpin.js]
 [browser_multiselect_tabs_positional_attrs.js]
 [browser_multiselect_tabs_reload.js]
+[browser_multiselect_tabs_reopen_in_container.js]
 [browser_multiselect_tabs_reorder.js]
 [browser_multiselect_tabs_using_Ctrl.js]
 [browser_multiselect_tabs_using_keyboard.js]
 skip-if = os == 'mac' # Skipped because macOS keyboard support requires changing system settings
 [browser_multiselect_tabs_using_selectedTabs.js]
 [browser_multiselect_tabs_using_Shift_and_Ctrl.js]
 [browser_multiselect_tabs_using_Shift.js]
 [browser_navigatePinnedTab.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_multiselect_tabs_reopen_in_container.js
@@ -0,0 +1,108 @@
+"use strict";
+
+const PREF_MULTISELECT_TABS = "browser.tabs.multiselect";
+const PREF_PRIVACY_USER_CONTEXT_ENABLED = "privacy.userContext.enabled";
+
+async function openTabMenuFor(tab) {
+  let tabMenu = tab.ownerDocument.getElementById("tabContextMenu");
+
+  let tabMenuShown = BrowserTestUtils.waitForEvent(tabMenu, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(tab, {type: "contextmenu"},
+                                     tab.ownerGlobal);
+  await tabMenuShown;
+
+  return tabMenu;
+}
+
+async function openReopenMenuForTab(tab) {
+  openTabMenuFor(tab);
+
+  let reopenItem = tab.ownerDocument.getElementById("context_reopenInContainer");
+  ok(!reopenItem.hidden, "Reopen in Container item should be shown");
+
+  let reopenMenu = reopenItem.getElementsByTagName("menupopup")[0];
+  let reopenMenuShown = BrowserTestUtils.waitForEvent(reopenMenu, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(reopenItem, {type: "mousemove"},
+                                     tab.ownerGlobal);
+  await reopenMenuShown;
+
+  return reopenMenu;
+}
+
+function checkMenuItem(reopenMenu, shown, hidden) {
+  for (let id of shown) {
+    ok(reopenMenu.querySelector(`menuitem[data-usercontextid="${id}"]`),
+       `User context id ${id} should exist`);
+  }
+  for (let id of hidden) {
+    ok(!reopenMenu.querySelector(`menuitem[data-usercontextid="${id}"]`),
+       `User context id ${id} shouldn't exist`);
+  }
+}
+
+function openTabInContainer(gBrowser, tab, reopenMenu, id) {
+  let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, getUrl(tab), true);
+  let menuitem = reopenMenu.querySelector(`menuitem[data-usercontextid="${id}"]`);
+  EventUtils.synthesizeMouseAtCenter(menuitem, {}, menuitem.ownerGlobal);
+  return tabPromise;
+}
+
+add_task(async function testReopen() {
+  await SpecialPowers.pushPrefEnv({"set": [
+      [PREF_PRIVACY_USER_CONTEXT_ENABLED, true],
+      [PREF_MULTISELECT_TABS, true],
+  ]});
+
+  let tab1 = await addTab("http://mochi.test:8888/1");
+  let tab2 = await addTab("http://mochi.test:8888/2");
+  let tab3 = await addTab("http://mochi.test:8888/3");
+  let tab4 = await addTab("http://mochi.test:8888/3", {createLazyBrowser: true});
+
+  await BrowserTestUtils.switchTab(gBrowser, tab1);
+
+  await triggerClickOn(tab2, { ctrlKey: true });
+  await triggerClickOn(tab4, { ctrlKey: true });
+
+  for (let tab of [tab1, tab2, tab3, tab4]) {
+    ok(!tab.hasAttribute("usercontextid"),
+        "Tab with No Container should be opened");
+  }
+
+  ok(tab1.multiselected, "Tab1 is multi-selected");
+  ok(tab2.multiselected, "Tab2 is multi-selected");
+  ok(!tab3.multiselected, "Tab3 is not multi-selected");
+  ok(tab4.multiselected, "Tab4 is multi-selected");
+
+  is(gBrowser.visibleTabs.length, 5, "We have 5 tabs open");
+
+  let reopenMenu1 = await openReopenMenuForTab(tab1);
+  checkMenuItem(reopenMenu1, [1, 2, 3, 4], [0]);
+  let containerTab1 = await openTabInContainer(gBrowser, tab1, reopenMenu1, "1");
+
+  let tabs = gBrowser.visibleTabs;
+  is(tabs.length, 8, "Now we have 8 tabs open");
+
+  is(containerTab1._tPos, 2, "containerTab1 position is 3");
+  is(containerTab1.getAttribute("usercontextid"), "1",
+     "Tab(1) with UCI=1 should be opened");
+  is(getUrl(containerTab1), getUrl(tab1),
+     "Same page (tab1) should be opened");
+
+  let containerTab2 = tabs[4];
+  is(containerTab2.getAttribute("usercontextid"), "1",
+     "Tab(2) with UCI=1 should be opened");
+  await TestUtils.waitForCondition(function() {
+    return getUrl(containerTab2) == getUrl(tab2);
+  }, "Same page (tab2) should be opened");
+
+  let containerTab4 = tabs[7];
+  is(containerTab2.getAttribute("usercontextid"), "1",
+     "Tab(4) with UCI=1 should be opened");
+  await TestUtils.waitForCondition(function() {
+    return getUrl(containerTab4) == getUrl(tab4);
+  }, "Same page (tab4) should be opened");
+
+  for (let tab of tabs.filter(t => t != tabs[0])) {
+    BrowserTestUtils.removeTab(tab);
+  }
+});
--- a/browser/base/content/test/tabs/head.js
+++ b/browser/base/content/test/tabs/head.js
@@ -16,18 +16,19 @@ function triggerClickOn(target, options)
           metaKey: options.ctrlKey,
           shiftKey: options.shiftKey,
       };
   }
   EventUtils.synthesizeMouseAtCenter(target, options);
   return promise;
 }
 
-async function addTab(url = "http://mochi.test:8888/") {
-  const tab = BrowserTestUtils.addTab(gBrowser, url, { skipAnimation: true });
+async function addTab(url = "http://mochi.test:8888/", params = {}) {
+  params.skipAnimation = true;
+  const tab = BrowserTestUtils.addTab(gBrowser, url, params);
   const browser = gBrowser.getBrowserForTab(tab);
   await BrowserTestUtils.browserLoaded(browser);
   return tab;
 }
 
 async function wait_for_tab_playing_event(tab, expectPlaying) {
   if (tab.soundPlaying == expectPlaying) {
     ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");