From b82f4c4fc844ca48a2a1c0beec2c29ccee07e3ec Mon Sep 17 00:00:00 2001 From: "reed@reedloden.com" Date: Thu, 7 Feb 2008 01:31:56 -0800 Subject: [PATCH] =?UTF-8?q?Bug=20393716=20-=20"Add=20single=20tab=20save/r?= =?UTF-8?q?estore=20to=20session=20store=20API"=20[p=3Dzeniko@gmail.com=20?= =?UTF-8?q?(Simon=20B=C3=BCnzli)=20r=3Ddietrich=20a1.9=3Dschrep]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sessionstore/nsISessionStore.idl | 25 ++++- .../sessionstore/src/nsSessionStore.js | 84 ++++++++++++--- .../sessionstore/test/chrome/Makefile.in | 6 +- .../test/chrome/test_bug393716.xul | 101 ++++++++++++++++++ 4 files changed, 196 insertions(+), 20 deletions(-) create mode 100644 browser/components/sessionstore/test/chrome/test_bug393716.xul diff --git a/browser/components/sessionstore/nsISessionStore.idl b/browser/components/sessionstore/nsISessionStore.idl index f72b7e836fbd..85ea19a85c12 100644 --- a/browser/components/sessionstore/nsISessionStore.idl +++ b/browser/components/sessionstore/nsISessionStore.idl @@ -46,7 +46,7 @@ interface nsIDOMNode; * - and allows to restore everything into one window. */ -[scriptable, uuid(11852a90-20de-11db-a98b-0800200c9a66)] +[scriptable, uuid(58d17e12-a80f-11dc-8314-0800200c9a66)] interface nsISessionStore : nsISupports { /** @@ -83,6 +83,29 @@ interface nsISessionStore : nsISupports */ void setWindowState(in nsIDOMWindow aWindow, in AString aState, in boolean aOverwrite); + /** + * @param aTab is the tab whose state is to be returned. + * + * @return a JSON string representing the state of the tab + * (note: doesn't contain cookies - if you need them, use getWindowState instead). + */ + AString getTabState(in nsIDOMNode aTab); + + /** + * @param aTab is the tab whose state is to be set. + * @param aState is a JSON string representing a session state. + */ + void setTabState(in nsIDOMNode aTab, in AString aState); + + /** + * Duplicates a given tab as thoroughly as possible. + * + * @param aWindow is the window into which the tab will be duplicated. + * @param aTab is the tab to duplicate (can be from a different window). + * @return a reference to the newly created tab. + */ + nsIDOMNode duplicateTab(in nsIDOMWindow aWindow, in nsIDOMNode aTab); + /** * Get the number of restore-able tabs for a window */ diff --git a/browser/components/sessionstore/src/nsSessionStore.js b/browser/components/sessionstore/src/nsSessionStore.js index 6620cce183b3..1e149b5711a6 100644 --- a/browser/components/sessionstore/src/nsSessionStore.js +++ b/browser/components/sessionstore/src/nsSessionStore.js @@ -682,6 +682,39 @@ SessionStoreService.prototype = { this.restoreWindow(aWindow, "(" + aState + ")", aOverwrite); }, + getTabState: function sss_getTabState(aTab) { + var tabState = this._collectTabData(aTab); + + var window = aTab.ownerDocument.defaultView; + this._updateTextAndScrollDataForTab(window, aTab.linkedBrowser, tabState); + + return this._toJSONString(tabState); + }, + + setTabState: function sss_setTabState(aTab, aState) { + var tabState = this._safeEval("(" + aState + ")"); + if (!tabState.entries || !tabState.entries.length) { + Components.returnCode = Cr.NS_ERROR_INVALID_ARG; + return; + } + tabState._tab = aTab; + + var window = aTab.ownerDocument.defaultView; + this.restoreHistoryPrecursor(window, [tabState], 0, 0, 0); + }, + + duplicateTab: function sss_duplicateTab(aWindow, aTab) { + var tabState = this._collectTabData(aTab, true); + var sourceWindow = aTab.ownerDocument.defaultView; + this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true); + + var newTab = aWindow.getBrowser().addTab(); + tabState._tab = newTab; + this.restoreHistoryPrecursor(aWindow, [tabState], 0, 0, 0); + + return newTab; + }, + getClosedTabCount: function sss_getClosedTabCount(aWindow) { if (!aWindow.__SSi && aWindow.__SS_dyingCache) return aWindow.__SS_dyingCache._closedTabs.length; @@ -824,9 +857,11 @@ SessionStoreService.prototype = { * Collect data related to a single tab * @param aTab * tabbrowser tab + * @param aFullData + * always return privacy sensitive data (use with care) * @returns object */ - _collectTabData: function sss_collectTabData(aTab) { + _collectTabData: function sss_collectTabData(aTab, aFullData) { var tabData = { entries: [], index: 0 }; var browser = aTab.linkedBrowser; @@ -844,7 +879,7 @@ SessionStoreService.prototype = { catch (ex) { } // this could happen if we catch a tab during (de)initialization if (history && browser.parentNode.__SS_data && - browser.parentNode.__SS_data.entries[history.index]) { + browser.parentNode.__SS_data.entries[history.index] && !aFullData) { tabData = browser.parentNode.__SS_data; tabData.index = history.index + 1; } @@ -854,11 +889,13 @@ SessionStoreService.prototype = { var startIndex = -1 < cap && cap < history.index ? history.index - cap : 0; for (var j = startIndex; j < history.count; j++) - tabData.entries.push(this._serializeHistoryEntry(history.getEntryAtIndex(j, false))); - + tabData.entries.push(this._serializeHistoryEntry(history.getEntryAtIndex(j, false), + aFullData)); tabData.index = history.index - startIndex + 1; - browser.parentNode.__SS_data = tabData; + // make sure not to cache privacy sensitive data which shouldn't get out + if (!aFullData) + browser.parentNode.__SS_data = tabData; } else { tabData.entries[0] = { url: browser.currentURI.spec }; @@ -896,9 +933,11 @@ SessionStoreService.prototype = { * Used for data storage * @param aEntry * nsISHEntry instance + * @param aFullData + * always return privacy sensitive data (use with care) * @returns object */ - _serializeHistoryEntry: function sss_serializeHistoryEntry(aEntry) { + _serializeHistoryEntry: function sss_serializeHistoryEntry(aEntry, aFullData) { var entry = { url: aEntry.URI.spec }; if (aEntry.title && aEntry.title != entry.url) { @@ -930,7 +969,8 @@ SessionStoreService.prototype = { try { var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata"); - if (prefPostdata && aEntry.postData && this._checkPrivacyLevel(aEntry.URI.schemeIs("https"))) { + if (aEntry.postData && (aFullData || + prefPostdata && this._checkPrivacyLevel(aEntry.URI.schemeIs("https")))) { aEntry.postData.QueryInterface(Ci.nsISeekableStream). seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); var stream = Cc["@mozilla.org/binaryinputstream;1"]. @@ -938,7 +978,7 @@ SessionStoreService.prototype = { stream.setInputStream(aEntry.postData); var postBytes = stream.readByteArray(stream.available()); var postdata = String.fromCharCode.apply(null, postBytes); - if (prefPostdata == -1 || + if (aFullData || prefPostdata == -1 || postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <= prefPostdata) { // We can stop doing base64 encoding once our serialization into JSON @@ -985,7 +1025,7 @@ SessionStoreService.prototype = { for (var i = 0; i < aEntry.childCount; i++) { var child = aEntry.GetChildAt(i); if (child) { - entry.children.push(this._serializeHistoryEntry(child)); + entry.children.push(this._serializeHistoryEntry(child), aFullData); } else { // to maintain the correct frame order, insert a dummy entry entry.children.push({ url: "about:blank" }); @@ -1075,12 +1115,14 @@ SessionStoreService.prototype = { * single browser reference * @param aTabData * tabData object to add the information to + * @param aFullData + * always return privacy sensitive data (use with care) */ _updateTextAndScrollDataForTab: - function sss_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData) { + function sss_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) { var text = []; if (aBrowser.parentNode.__SS_text && - this._checkPrivacyLevel(aBrowser.currentURI.schemeIs("https"))) { + (aFullData || this._checkPrivacyLevel(aBrowser.currentURI.schemeIs("https")))) { for (var ix = aBrowser.parentNode.__SS_text.length - 1; ix >= 0; ix--) { var data = aBrowser.parentNode.__SS_text[ix]; if (!data.cache) @@ -1097,8 +1139,11 @@ SessionStoreService.prototype = { else if (aTabData.text) delete aTabData.text; - this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow, - aTabData.entries[aTabData.index - 1]); + var tabIndex = (aTabData.index || aTabData.entries.length) - 1; + // entry data needn't exist for tabs just initialized with an incomplete session state + if (aTabData.entries[tabIndex]) + this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow, + aTabData.entries[tabIndex], aFullData); }, /** @@ -1110,18 +1155,21 @@ SessionStoreService.prototype = { * frame reference * @param aData * part of a tabData object to add the information to + * @param aFullData + * always return privacy sensitive data (use with care) */ _updateTextAndScrollDataForFrame: - function sss_updateTextAndScrollDataForFrame(aWindow, aContent, aData) { + function sss_updateTextAndScrollDataForFrame(aWindow, aContent, aData, aFullData) { for (var i = 0; i < aContent.frames.length; i++) { if (aData.children && aData.children[i]) - this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i], aData.children[i]); + this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i], aData.children[i], aFullData); } // designMode is undefined e.g. for XUL documents (as about:config) var isHTTPS = this._getURIFromString((aContent.parent || aContent). document.location.href).schemeIs("https"); - if ((aContent.document.designMode || "") == "on" && this._checkPrivacyLevel(isHTTPS)) { - if (aData.innerHTML === undefined) { + if ((aContent.document.designMode || "") == "on" && + (aFullData || this._checkPrivacyLevel(isHTTPS))) { + if (aData.innerHTML === undefined && !aFullData) { // we get no "input" events from iframes - listen for keypress here var _this = this; aContent.addEventListener("keypress", function(aEvent) { @@ -1410,6 +1458,8 @@ SessionStoreService.prototype = { * Array of tab references * @param aSelectTab * Index of selected tab + * @param aIx + * Index of the next tab to check readyness for * @param aCount * Counter for number of times delaying b/c browser or history aren't ready */ diff --git a/browser/components/sessionstore/test/chrome/Makefile.in b/browser/components/sessionstore/test/chrome/Makefile.in index ff9ab6e8e0e8..1710e60dece2 100644 --- a/browser/components/sessionstore/test/chrome/Makefile.in +++ b/browser/components/sessionstore/test/chrome/Makefile.in @@ -43,8 +43,10 @@ relativesrcdir = browser/components/sessionstore/test/chrome include $(DEPTH)/config/autoconf.mk include $(topsrcdir)/config/rules.mk -_TEST_FILES = test_bug350525.xul \ - $(NULL) +_TEST_FILES = \ + test_bug350525.xul \ + test_bug393716.xul \ + $(NULL) libs:: $(_TEST_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir) diff --git a/browser/components/sessionstore/test/chrome/test_bug393716.xul b/browser/components/sessionstore/test/chrome/test_bug393716.xul new file mode 100644 index 000000000000..33015f7c3450 --- /dev/null +++ b/browser/components/sessionstore/test/chrome/test_bug393716.xul @@ -0,0 +1,101 @@ + + + + + + + Test for Bug 393716 + + + + Mozilla Bug 393716 + +

+ +
+  
+  
+ + +