Bug 1340450 - When containers are disabled, it should not be possible to reopen container tabs, r=mdeboer
☠☠ backed out by 68ada0ef9842 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 23 Feb 2017 10:51:52 +0100
changeset 344576 d59ad4198154c1d3d46f69fc7b81513c759039a5
parent 344575 dd6f04636eaa5d08570d19221885c693d4a7f713
child 344577 8b432965d1a6f503e402a24d0052362accfed774
push id31414
push usercbook@mozilla.com
push dateFri, 24 Feb 2017 10:47:41 +0000
treeherdermozilla-central@be661bae6cb9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmdeboer
bugs1340450
milestone54.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 1340450 - When containers are disabled, it should not be possible to reopen container tabs, r=mdeboer
browser/components/preferences/in-content/privacy.js
browser/components/sessionstore/SessionStore.jsm
browser/components/sessionstore/test/browser.ini
browser/components/sessionstore/test/browser_disable_containers.js
toolkit/components/contextualidentity/ContextualIdentityService.jsm
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -80,16 +80,17 @@ var gPrivacyPane = {
     let checkbox = document.getElementById("browserContainersCheckbox");
     if (checkbox.checked) {
       Services.prefs.setBoolPref("privacy.userContext.enabled", true);
       return;
     }
 
     let count = ContextualIdentityService.countContainerTabs();
     if (count == 0) {
+      ContextualIdentityService.disableContainers();
       Services.prefs.setBoolPref("privacy.userContext.enabled", false);
       return;
     }
 
     let bundlePreferences = document.getElementById("bundlePreferences");
 
     let title = bundlePreferences.getString("disableContainersAlertTitle");
     let message = PluralForm.get(count, bundlePreferences.getString("disableContainersMsg"))
@@ -100,16 +101,17 @@ var gPrivacyPane = {
 
     let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
                       (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
 
     let rv = Services.prompt.confirmEx(window, title, message, buttonFlags,
                                        okButton, cancelButton, null, null, {});
     if (rv == 0) {
       ContextualIdentityService.closeContainerTabs();
+      ContextualIdentityService.disableContainers();
       Services.prefs.setBoolPref("privacy.userContext.enabled", false);
       return;
     }
 
     checkbox.checked = true;
   },
 
   /**
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -40,17 +40,17 @@ const MAX_CONCURRENT_TAB_RESTORES = 3;
 const SCREEN_EDGE_SLOP = 8;
 
 // global notifications observed
 const OBSERVING = [
   "browser-window-before-show", "domwindowclosed",
   "quit-application-granted", "browser-lastwindow-close-granted",
   "quit-application", "browser:purge-session-history",
   "browser:purge-domain-data",
-  "idle-daily",
+  "idle-daily", "clear-origin-attributes-data"
 ];
 
 // XUL Window properties to (re)store
 // Restored in restoreDimensions()
 const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
 
 // Hideable window features to (re)store
 // Restored in restoreWindowFeatures()
@@ -743,16 +743,23 @@ var SessionStoreInternal = {
       case "nsPref:changed": // catch pref changes
         this.onPrefChange(aData);
         this._notifyOfClosedObjectsChange();
         break;
       case "idle-daily":
         this.onIdleDaily();
         this._notifyOfClosedObjectsChange();
         break;
+      case "clear-origin-attributes-data":
+        let userContextId = 0;
+        try {
+          userContextId = JSON.parse(aData).userContextId;
+        } catch(e) {}
+        if (userContextId)
+          this._forgetTabsWithUserContextId(userContextId);
     }
   },
 
   /**
    * This method handles incoming messages sent by the session store content
    * script via the Frame Message Manager or Parent Process Message Manager,
    * and thus enables communication with OOP tabs.
    */
@@ -2577,16 +2584,43 @@ var SessionStoreInternal = {
         }
       }
     }
 
     // Neither a tab nor a window was found, return undefined and let the caller decide what to do about it.
     return undefined;
   },
 
+  // This method deletes all the closedTabs matching userContextId.
+  _forgetTabsWithUserContextId(userContextId) {
+    let windowsEnum = Services.wm.getEnumerator("navigator:browser");
+    while (windowsEnum.hasMoreElements()) {
+      let window = windowsEnum.getNext();
+      let windowState = this._windows[window.__SSi];
+      if (windowState) {
+        // In order to remove the tabs in the correct order, we store the
+        // indexes, into an array, then we revert the array and remove closed
+        // data from the last one going backward.
+        let indexes = [];
+        windowState._closedTabs.forEach((closedTab, index) => {
+          if (closedTab.state.userContextId == userContextId) {
+            indexes.push(index);
+          }
+        });
+
+        for (let index of indexes.reverse()) {
+          this.removeClosedTabData(windowState._closedTabs, index);
+        }
+      }
+    }
+
+    // Notify of changes to closed objects.
+    this._notifyOfClosedObjectsChange();
+  },
+
   /**
    * Restores the session state stored in LastSession. This will attempt
    * to merge data into the current session. If a window was opened at startup
    * with pinned tab(s), then the remaining data from the previous session for
    * that window will be opened into that window. Otherwise new windows will
    * be opened.
    */
   restoreLastSession: function ssi_restoreLastSession() {
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -238,8 +238,10 @@ run-if = e10s && crashreporter
 [browser_undoCloseById.js]
 skip-if = debug
 [browser_docshell_uuid_consistency.js]
 [browser_grouped_session_store.js]
 skip-if = !e10s # GroupedSHistory is e10s-only
 
 [browser_closed_objects_changed_notifications_tabs.js]
 [browser_closed_objects_changed_notifications_windows.js]
+
+[browser_disable_containers.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_disable_containers.js
@@ -0,0 +1,76 @@
+"use strict";
+
+/**
+ * This test is to see if tabs can be reopened when containers are disabled.
+ */
+
+async function openAndCloseTab(window, userContextId, url) {
+  let tab = window.gBrowser.addTab(url, { userContextId });
+  await promiseBrowserLoaded(tab.linkedBrowser, true, url);
+  await TabStateFlusher.flush(tab.linkedBrowser);
+  await promiseRemoveTab(tab);
+}
+
+async function openTab(window, userContextId, url) {
+  let tab = window.gBrowser.addTab(url, { userContextId });
+  await promiseBrowserLoaded(tab.linkedBrowser, true, url);
+  await TabStateFlusher.flush(tab.linkedBrowser);
+}
+
+async function openWindow(url) {
+  let win = await promiseNewWindowLoaded();
+  let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
+  win.gBrowser.selectedBrowser.loadURIWithFlags(url, flags);
+  await promiseBrowserLoaded(win.gBrowser.selectedBrowser, true, url);
+  return win;
+}
+
+add_task(function* test_undoCloseById() {
+  // Clear the lists of closed windows and tabs.
+  forgetClosedWindows();
+  while (SessionStore.getClosedTabCount(window)) {
+    SessionStore.forgetClosedTab(window, 0);
+  }
+
+  // Open a new window.
+  let win = yield openWindow("about:robots");
+
+  // Open and close a tab.
+  yield openAndCloseTab(win, 0, "about:mozilla");
+  is(SessionStore.lastClosedObjectType, "tab", "The last closed object is a tab");
+
+  // Open and close a container tab.
+  yield openAndCloseTab(win, 3, "about:about");
+  is(SessionStore.lastClosedObjectType, "tab", "The last closed object is a tab");
+
+  // Restore the last closed tab.
+  let tab = SessionStore.undoCloseTab(win, 0);
+  yield promiseBrowserLoaded(tab.linkedBrowser);
+  is(tab.linkedBrowser.currentURI.spec, "about:about", "The expected tab was re-opened");
+  is(tab.getAttribute("usercontextid"), 3, "Expected the tab userContextId to match");
+
+  // Open a few container tabs.
+  yield openTab(win, 1, "about:config");
+  yield openTab(win, 1, "about:preferences");
+  yield openTab(win, 2, "about:support");
+
+  // Let's simulate the disabling of containers.
+  ContextualIdentityService.closeContainerTabs();
+  ContextualIdentityService.disableContainers();
+  yield TabStateFlusher.flushWindow(win);
+
+  // Let's check we don't have container tab opened.
+  for (let i = 0; i < win.gBrowser.tabContainer.childNodes.length; ++i) {
+    let tab = win.gBrowser.tabContainer.childNodes[i];
+    ok(!tab.hasAttribute("usercontextid"), "No userContextId for this tab");
+  }
+
+  // Restore the last closed tab (we don't want the container tabs).
+  tab = SessionStore.undoCloseTab(win, 0);
+  yield promiseBrowserLoaded(tab.linkedBrowser);
+  is(tab.linkedBrowser.currentURI.spec, "about:mozilla", "The expected tab was re-opened");
+  ok(!tab.hasAttribute("usercontextid"), "No userContextId for this tab");
+
+  // Close the window again.
+  yield BrowserTestUtils.closeWindow(win);
+});
--- a/toolkit/components/contextualidentity/ContextualIdentityService.jsm
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.jsm
@@ -293,16 +293,23 @@ function _ContextualIdentityService(path
   },
 
   closeContainerTabs(userContextId = 0) {
     this._forEachContainerTab(function(tab, tabbrowser) {
       tabbrowser.removeTab(tab);
     }, userContextId);
   },
 
+  disableContainers() {
+    for (let identity of this._identities) {
+      Services.obs.notifyObservers(null, "clear-origin-attributes-data",
+                                   JSON.stringify({ userContextId: identity.userContextId }));
+    }
+  },
+
   _forEachContainerTab(callback, userContextId = 0) {
     let windowList = Services.wm.getEnumerator("navigator:browser");
     while (windowList.hasMoreElements()) {
       let win = windowList.getNext();
 
       if (win.closed || !win.gBrowser) {
 	continue;
       }