author | David Rajchenbach-Teller <dteller@mozilla.com> |
Wed, 18 Dec 2013 12:39:53 -0500 | |
changeset 161092 | 7289c4384fc7c34ba594a0149b949ec07898d142 |
parent 161091 | 40978aa5238d1de302fc1f5782466f4d47ad19f2 |
child 161093 | e6750e3b3b66c46af5252c1646dbe496dd8219ed |
push id | 25867 |
push user | ryanvm@gmail.com |
push date | Thu, 19 Dec 2013 02:19:33 +0000 |
treeherder | mozilla-central@04a70c8908de [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | ttaubert |
bugs | 899276 |
milestone | 29.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
|
--- a/browser/components/sessionstore/content/content-sessionStore.js +++ b/browser/components/sessionstore/content/content-sessionStore.js @@ -304,16 +304,40 @@ let SessionStorageListener = { MessageQueue.push("storage", () => SessionStorage.collect(docShell)); }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, 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 * takes care of any race conditions that might occur because of that. Changes * will be batched if they're pushed in quick succession to avoid a message * flood. */ let MessageQueue = { /** @@ -461,8 +485,9 @@ let MessageQueue = { EventListener.init(); MessageListener.init(); SyncHandler.init(); ProgressListener.init(); PageStyleListener.init(); SessionStorageListener.init(); DocShellCapabilitiesListener.init(); +PrivacyListener.init();
--- a/browser/components/sessionstore/src/SessionSaver.jsm +++ b/browser/components/sessionstore/src/SessionSaver.jsm @@ -67,17 +67,17 @@ let stopWatchFinish = stopWatch("finish" /** * The external API implemented by the SessionSaver module. */ this.SessionSaver = Object.freeze({ /** * Immediately saves the current session to disk. */ run: function () { - SessionSaverInternal.run(); + return SessionSaverInternal.run(); }, /** * Saves the current session to disk delayed by a given amount of time. Should * another delayed run be scheduled already, we will ignore the given delay * and state saving may occur a little earlier. */ runDelayed: function () { @@ -124,17 +124,17 @@ let SessionSaverInternal = { * the configured session write interval. */ _lastSaveTime: 0, /** * Immediately saves the current session to disk. */ run: function () { - this._saveState(true /* force-update all windows */); + return this._saveState(true /* force-update all windows */); }, /** * Saves the current session to disk delayed by a given amount of time. Should * another delayed run be scheduled already, we will ignore the given delay * and state saving may occur a little earlier. * * @param delay (optional) @@ -187,33 +187,49 @@ let SessionSaverInternal = { */ _saveState: function (forceUpdateAllWindows = false) { // Cancel any pending timeouts. this.cancel(); 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--; + } } } } // Remove private windows from the list of closed windows. for (let i = state._closedWindows.length - 1; i >= 0; i--) { if (state._closedWindows[i].isPrivate) { state._closedWindows.splice(i, 1); } } + // 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) { state.windows = state.deferredInitialState.windows || []; delete state.deferredInitialState; } #ifndef XP_MACOSX @@ -230,17 +246,17 @@ let SessionSaverInternal = { } delete state._closedWindows[i]._shouldRestore; state.windows.unshift(state._closedWindows.pop()); } #endif stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS"); - this._writeState(state); + return this._writeState(state); }, /** * Saves the current session state. Collects data asynchronously and calls * _saveState() to collect data again (with a cache hit rate of hopefully * 100%) and write to disk afterwards. */ _saveStateAsync: function () { @@ -273,29 +289,29 @@ let SessionSaverInternal = { let data = JSON.stringify(state); stopWatchFinish("SERIALIZE_DATA_MS", "SERIALIZE_DATA_LONGEST_OP_MS"); // Give observers a chance to modify session data. data = this._notifyObserversBeforeStateWrite(data); // Don't touch the file if an observer has deleted all state data. if (!data) { - return; + return Promise.resolve(); } // We update the time stamp before writing so that we don't write again // too soon, if saving is requested before the write completes. Without // this update we may save repeatedly if actions cause a runDelayed // before writing has completed. See Bug 902280 this.updateLastSaveTime(); // Write (atomically) to a session file, using a tmp file. Once the session // file is successfully updated, save the time stamp of the last save and // notify the observers. - SessionFile.write(data).then(() => { + return SessionFile.write(data).then(() => { this.updateLastSaveTime(); notify(null, "sessionstore-state-write-complete"); }, Cu.reportError); }, /** * Notify sessionstore-state-write observer and give them a * chance to modify session data before we'll write it to disk.
--- a/browser/components/sessionstore/src/SessionStore.jsm +++ b/browser/components/sessionstore/src/SessionStore.jsm @@ -1306,16 +1306,21 @@ let SessionStoreInternal = { } // Flush all data queued in the content script before the tab is gone. TabState.flush(aTab.linkedBrowser); // 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; let tabbrowser = aWindow.gBrowser; tabTitle = this._replaceLoadingTitle(tabTitle, tabbrowser, aTab); this._windows[aWindow.__SSi]._closedTabs.unshift({ state: tabState,
--- a/browser/components/sessionstore/test/browser.ini +++ b/browser/components/sessionstore/test/browser.ini @@ -54,16 +54,17 @@ support-files = [browser_dying_cache.js] [browser_form_restore_events.js] [browser_formdata_format.js] [browser_global_store.js] [browser_input.js] [browser_merge_closed_tabs.js] [browser_pageshow.js] [browser_pageStyle.js] +[browser_privatetabs.js] [browser_sessionStorage.js] [browser_swapDocShells.js] [browser_tabStateCache.js] [browser_upgrade_backup.js] [browser_windowRestore_perwindowpb.js] [browser_248970_b_perwindowpb.js] # Disabled because of leaks. # Re-enabling and rewriting this test is tracked in bug 936919.
new file mode 100644 --- /dev/null +++ b/browser/components/sessionstore/test/browser_privatetabs.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +let Imports = {}; +Cu.import("resource:///modules/sessionstore/SessionSaver.jsm", Imports); +let {Promise, Task, SessionSaver} = Imports; + +add_task(function cleanup() { + info("Forgetting closed tabs"); + 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); + yield 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 SessionSaver.run(); + let path = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"); + let data = yield OS.File.read(path); + let state = new TextDecoder().decode(data); + 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); +} +
--- a/browser/components/sessionstore/test/content.js +++ b/browser/components/sessionstore/test/content.js @@ -47,8 +47,15 @@ addMessageListener("ss-test:getAuthorSty }); addMessageListener("ss-test:setAuthorStyleDisabled", function (msg) { let markupDocumentViewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer); markupDocumentViewer.authorStyleDisabled = msg.data; sendSyncMessage("ss-test:setAuthorStyleDisabled"); }); + +addMessageListener("ss-test:setUsePrivateBrowsing", function (msg) { + let loadContext = + docShell.QueryInterface(Ci.nsILoadContext); + loadContext.usePrivateBrowsing = msg.data; + sendAsyncMessage("ss-test:setUsePrivateBrowsing"); +});