diff --git a/browser/components/sessionstore/content/content-sessionStore.js b/browser/components/sessionstore/content/content-sessionStore.js index 74da0a69bcb4..881fd18142fc 100644 --- a/browser/components/sessionstore/content/content-sessionStore.js +++ b/browser/components/sessionstore/content/content-sessionStore.js @@ -308,6 +308,30 @@ let SessionStorageListener = { Ci.nsISupportsWeakReference]) }; +/** + * Listen for changes to the privacy status of the tab. + * By definition, tabs start in non-private mode. + * + * Causes a SessionStore:update message to be sent for + * field "isPrivate". This message contains + * |true| if the tab is now private + * |null| if the tab is now public - the field is therefore + * not saved. + */ +let PrivacyListener = { + init: function() { + docShell.addWeakPrivacyTransitionObserver(this); + }, + + // Ci.nsIPrivacyTransitionObserver + privateModeChanged: function(enabled) { + MessageQueue.push("isPrivate", () => enabled || null); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrivacyTransitionObserver, + Ci.nsISupportsWeakReference]) +}; + /** * A message queue that takes collected data and will take care of sending it * to the chrome process. It allows flushing using synchronous messages and @@ -461,3 +485,4 @@ ProgressListener.init(); PageStyleListener.init(); SessionStorageListener.init(); DocShellCapabilitiesListener.init(); +PrivacyListener.init(); diff --git a/browser/components/sessionstore/src/SessionSaver.jsm b/browser/components/sessionstore/src/SessionSaver.jsm index 7d93bd220d0c..f04545810e42 100644 --- a/browser/components/sessionstore/src/SessionSaver.jsm +++ b/browser/components/sessionstore/src/SessionSaver.jsm @@ -192,12 +192,24 @@ let SessionSaverInternal = { stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS"); let state = SessionStore.getCurrentState(forceUpdateAllWindows); - // Forget about private windows. + // Forget about private windows and tabs. for (let i = state.windows.length - 1; i >= 0; i--) { - if (state.windows[i].isPrivate) { - state.windows.splice(i, 1); - if (state.selectedWindow >= i) { - state.selectedWindow--; + let win = state.windows[i]; + if (win.isPrivate || false) { // The whole window is private, remove it + state.windows.splice(i, 1); + if (state.selectedWindow >= i) { + state.selectedWindow--; + } + continue; + } + // The window is not private, but its tabs still might + for (let j = win.tabs.length - 1; j >= 0 ; --j) { + let tab = win.tabs[j]; + if (tab.isPrivate || false) { + win.tabs.splice(j, 1); + if (win.selected >= j) { + win.selected--; + } } } } @@ -209,6 +221,10 @@ let SessionSaverInternal = { } } + // Note that closed private tabs are never stored (see + // SessionStoreInternal.onTabClose), so we do not need to remove + // them. + // Make sure that we keep the previous session if we started with a single // private window and no non-private windows have been opened, yet. if (state.deferredInitialState) { diff --git a/browser/components/sessionstore/src/SessionStore.jsm b/browser/components/sessionstore/src/SessionStore.jsm index a5497b485255..cd906c93c85f 100644 --- a/browser/components/sessionstore/src/SessionStore.jsm +++ b/browser/components/sessionstore/src/SessionStore.jsm @@ -1320,6 +1320,11 @@ let SessionStoreInternal = { // Get the latest data for this tab (generally, from the cache) let tabState = TabState.collectSync(aTab); + // Don't save private tabs + if (tabState.isPrivate || false) { + return; + } + // store closed-tab data for undo if (this._shouldSaveTabState(tabState)) { let tabTitle = aTab.label; diff --git a/browser/components/sessionstore/test/browser.ini b/browser/components/sessionstore/test/browser.ini index 6ed3b4915046..cdee1041720e 100644 --- a/browser/components/sessionstore/test/browser.ini +++ b/browser/components/sessionstore/test/browser.ini @@ -58,6 +58,7 @@ support-files = [browser_input.js] [browser_pageshow.js] [browser_pageStyle.js] +[browser_privatetabs.js] [browser_sessionStorage.js] [browser_swapDocShells.js] [browser_tabStateCache.js] diff --git a/browser/components/sessionstore/test/browser_privatetabs.js b/browser/components/sessionstore/test/browser_privatetabs.js new file mode 100644 index 000000000000..8eb5d61e104b --- /dev/null +++ b/browser/components/sessionstore/test/browser_privatetabs.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +let Imports = {}; +Cu.import("resource://gre/modules/Task.jsm", Imports); +Cu.import("resource://gre/modules/Promise.jsm", Imports); +let {Promise, Task} = Imports; + +add_task(function cleanup() { + while (ss.getClosedTabCount(window)) { + ss.forgetClosedTab(window, 0); + } +}); + +add_task(function() { + let URL_PUBLIC = "http://example.com/public/" + Math.random(); + let URL_PRIVATE = "http://example.com/private/" + Math.random(); + let tab1, tab2; + try { + // Setup a public tab and a private tab + info("Setting up public tab"); + tab1 = gBrowser.addTab(URL_PUBLIC); + yield promiseBrowserLoaded(tab1.linkedBrowser); + + info("Setting up private tab"); + tab2 = gBrowser.addTab(); + yield promiseBrowserLoaded(tab2.linkedBrowser); + setUsePrivateBrowsing(tab2.linkedBrowser, true); + tab2.linkedBrowser.loadURI(URL_PRIVATE); + yield promiseBrowserLoaded(tab2.linkedBrowser); + + info("Flush to make sure chrome received all data."); + SyncHandlers.get(tab2.linkedBrowser).flush(); + + info("Checking out state"); + yield forceSaveState(); + let state = new TextDecoder().decode((yield OS.File.read(OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js")))); + info("State: " + state); + // Ensure that sessionstore.js only knows about the public tab + ok(state.indexOf(URL_PUBLIC) != -1, "State contains public tab"); + ok(state.indexOf(URL_PRIVATE) == -1, "State does not contain private tab"); + + // Ensure that we can close and undo close the public tab but not the private tab + gBrowser.removeTab(tab2); + tab2 = null; + + gBrowser.removeTab(tab1); + tab1 = null; + + tab1 = ss.undoCloseTab(window, 0); + ok(true, "Public tab supports undo close"); + + is(ss.getClosedTabCount(window), 0, "Private tab does not support undo close"); + + } finally { + if (tab1) { + gBrowser.removeTab(tab1); + } + if (tab2) { + gBrowser.removeTab(tab2); + } + } +}); + +function setUsePrivateBrowsing(browser, val) { + return sendMessage(browser, "ss-test:setUsePrivateBrowsing", val); +} diff --git a/browser/components/sessionstore/test/content.js b/browser/components/sessionstore/test/content.js index e839d1375651..ac70f745a641 100644 --- a/browser/components/sessionstore/test/content.js +++ b/browser/components/sessionstore/test/content.js @@ -50,3 +50,10 @@ addMessageListener("ss-test:setAuthorStyleDisabled", function (msg) { markupDocumentViewer.authorStyleDisabled = msg.data; sendSyncMessage("ss-test:setAuthorStyleDisabled"); }); + +addMessageListener("ss-test:setUsePrivateBrowsing", function (msg) { + let loadContext = + docShell.QueryInterface(Ci.nsILoadContext); + loadContext.usePrivateBrowsing = msg.data; + sendSyncMessage("ss-test:setAuthorStyleDisabled"); +});