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 373603 d59ad4198154c1d3d46f69fc7b81513c759039a5
parent 373602 dd6f04636eaa5d08570d19221885c693d4a7f713
child 373604 8b432965d1a6f503e402a24d0052362accfed774
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmdeboer
bugs1340450
milestone54.0a1
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;
       }