Bug 393716 - "Add single tab save/restore to session store API" [p=zeniko@gmail.com (Simon B��nzli) r=dietrich a1.9=schrep]

This commit is contained in:
reed%reedloden.com 2008-02-07 09:31:57 +00:00
Родитель 28aa5d33a0
Коммит 0f78943054
4 изменённых файлов: 196 добавлений и 20 удалений

Просмотреть файл

@ -46,7 +46,7 @@ interface nsIDOMNode;
* - and allows to restore everything into one window. * - 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 interface nsISessionStore : nsISupports
{ {
/** /**
@ -83,6 +83,29 @@ interface nsISessionStore : nsISupports
*/ */
void setWindowState(in nsIDOMWindow aWindow, in AString aState, in boolean aOverwrite); 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 * Get the number of restore-able tabs for a window
*/ */

Просмотреть файл

@ -682,6 +682,39 @@ SessionStoreService.prototype = {
this.restoreWindow(aWindow, "(" + aState + ")", aOverwrite); 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) { getClosedTabCount: function sss_getClosedTabCount(aWindow) {
if (!aWindow.__SSi && aWindow.__SS_dyingCache) if (!aWindow.__SSi && aWindow.__SS_dyingCache)
return aWindow.__SS_dyingCache._closedTabs.length; return aWindow.__SS_dyingCache._closedTabs.length;
@ -824,9 +857,11 @@ SessionStoreService.prototype = {
* Collect data related to a single tab * Collect data related to a single tab
* @param aTab * @param aTab
* tabbrowser tab * tabbrowser tab
* @param aFullData
* always return privacy sensitive data (use with care)
* @returns object * @returns object
*/ */
_collectTabData: function sss_collectTabData(aTab) { _collectTabData: function sss_collectTabData(aTab, aFullData) {
var tabData = { entries: [], index: 0 }; var tabData = { entries: [], index: 0 };
var browser = aTab.linkedBrowser; var browser = aTab.linkedBrowser;
@ -844,7 +879,7 @@ SessionStoreService.prototype = {
catch (ex) { } // this could happen if we catch a tab during (de)initialization catch (ex) { } // this could happen if we catch a tab during (de)initialization
if (history && browser.parentNode.__SS_data && 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 = browser.parentNode.__SS_data;
tabData.index = history.index + 1; tabData.index = history.index + 1;
} }
@ -854,11 +889,13 @@ SessionStoreService.prototype = {
var startIndex = -1 < cap && cap < history.index ? history.index - cap : 0; var startIndex = -1 < cap && cap < history.index ? history.index - cap : 0;
for (var j = startIndex; j < history.count; j++) 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; 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 { else {
tabData.entries[0] = { url: browser.currentURI.spec }; tabData.entries[0] = { url: browser.currentURI.spec };
@ -896,9 +933,11 @@ SessionStoreService.prototype = {
* Used for data storage * Used for data storage
* @param aEntry * @param aEntry
* nsISHEntry instance * nsISHEntry instance
* @param aFullData
* always return privacy sensitive data (use with care)
* @returns object * @returns object
*/ */
_serializeHistoryEntry: function sss_serializeHistoryEntry(aEntry) { _serializeHistoryEntry: function sss_serializeHistoryEntry(aEntry, aFullData) {
var entry = { url: aEntry.URI.spec }; var entry = { url: aEntry.URI.spec };
if (aEntry.title && aEntry.title != entry.url) { if (aEntry.title && aEntry.title != entry.url) {
@ -930,7 +969,8 @@ SessionStoreService.prototype = {
try { try {
var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata"); 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). aEntry.postData.QueryInterface(Ci.nsISeekableStream).
seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
var stream = Cc["@mozilla.org/binaryinputstream;1"]. var stream = Cc["@mozilla.org/binaryinputstream;1"].
@ -938,7 +978,7 @@ SessionStoreService.prototype = {
stream.setInputStream(aEntry.postData); stream.setInputStream(aEntry.postData);
var postBytes = stream.readByteArray(stream.available()); var postBytes = stream.readByteArray(stream.available());
var postdata = String.fromCharCode.apply(null, postBytes); var postdata = String.fromCharCode.apply(null, postBytes);
if (prefPostdata == -1 || if (aFullData || prefPostdata == -1 ||
postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <= postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <=
prefPostdata) { prefPostdata) {
// We can stop doing base64 encoding once our serialization into JSON // 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++) { for (var i = 0; i < aEntry.childCount; i++) {
var child = aEntry.GetChildAt(i); var child = aEntry.GetChildAt(i);
if (child) { 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 else { // to maintain the correct frame order, insert a dummy entry
entry.children.push({ url: "about:blank" }); entry.children.push({ url: "about:blank" });
@ -1075,12 +1115,14 @@ SessionStoreService.prototype = {
* single browser reference * single browser reference
* @param aTabData * @param aTabData
* tabData object to add the information to * tabData object to add the information to
* @param aFullData
* always return privacy sensitive data (use with care)
*/ */
_updateTextAndScrollDataForTab: _updateTextAndScrollDataForTab:
function sss_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData) { function sss_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) {
var text = []; var text = [];
if (aBrowser.parentNode.__SS_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--) { for (var ix = aBrowser.parentNode.__SS_text.length - 1; ix >= 0; ix--) {
var data = aBrowser.parentNode.__SS_text[ix]; var data = aBrowser.parentNode.__SS_text[ix];
if (!data.cache) if (!data.cache)
@ -1097,8 +1139,11 @@ SessionStoreService.prototype = {
else if (aTabData.text) else if (aTabData.text)
delete aTabData.text; delete aTabData.text;
this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow, var tabIndex = (aTabData.index || aTabData.entries.length) - 1;
aTabData.entries[aTabData.index - 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 * frame reference
* @param aData * @param aData
* part of a tabData object to add the information to * part of a tabData object to add the information to
* @param aFullData
* always return privacy sensitive data (use with care)
*/ */
_updateTextAndScrollDataForFrame: _updateTextAndScrollDataForFrame:
function sss_updateTextAndScrollDataForFrame(aWindow, aContent, aData) { function sss_updateTextAndScrollDataForFrame(aWindow, aContent, aData, aFullData) {
for (var i = 0; i < aContent.frames.length; i++) { for (var i = 0; i < aContent.frames.length; i++) {
if (aData.children && aData.children[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) // designMode is undefined e.g. for XUL documents (as about:config)
var isHTTPS = this._getURIFromString((aContent.parent || aContent). var isHTTPS = this._getURIFromString((aContent.parent || aContent).
document.location.href).schemeIs("https"); document.location.href).schemeIs("https");
if ((aContent.document.designMode || "") == "on" && this._checkPrivacyLevel(isHTTPS)) { if ((aContent.document.designMode || "") == "on" &&
if (aData.innerHTML === undefined) { (aFullData || this._checkPrivacyLevel(isHTTPS))) {
if (aData.innerHTML === undefined && !aFullData) {
// we get no "input" events from iframes - listen for keypress here // we get no "input" events from iframes - listen for keypress here
var _this = this; var _this = this;
aContent.addEventListener("keypress", function(aEvent) { aContent.addEventListener("keypress", function(aEvent) {
@ -1410,6 +1458,8 @@ SessionStoreService.prototype = {
* Array of tab references * Array of tab references
* @param aSelectTab * @param aSelectTab
* Index of selected tab * Index of selected tab
* @param aIx
* Index of the next tab to check readyness for
* @param aCount * @param aCount
* Counter for number of times delaying b/c browser or history aren't ready * Counter for number of times delaying b/c browser or history aren't ready
*/ */

Просмотреть файл

@ -43,8 +43,10 @@ relativesrcdir = browser/components/sessionstore/test/chrome
include $(DEPTH)/config/autoconf.mk include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk include $(topsrcdir)/config/rules.mk
_TEST_FILES = test_bug350525.xul \ _TEST_FILES = \
$(NULL) test_bug350525.xul \
test_bug393716.xul \
$(NULL)
libs:: $(_TEST_FILES) libs:: $(_TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)

Просмотреть файл

@ -0,0 +1,101 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=393716
-->
<window title="Mozilla Bug 393716"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<title>Test for Bug 393716</title>
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=393716">Mozilla Bug 393716</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript">
<![CDATA[
const Cc = Components.classes;
const Ci = Components.interfaces;
// set up the basics (SessionStore service, tabbrowser)
try {
var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
} catch (ex) {
ok(false, "SessionStore service available?");
}
try {
var windowEnumerator = Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator).
getEnumerator("navigator:browser");
var currentWindow = windowEnumerator.getNext();
var tabbrowser = currentWindow.getBrowser();
} catch (ex) {
ok(false, "tabbrowser available?");
}
/**************
* getTabState
**************/
var key = "key1", value = "value1";
// create a new tab
var newTab = tabbrowser.addTab();
ss.setTabValue(newTab, key, value);
// get the tab's state
var state = ss.getTabState(newTab);
ok(state, "Able to get the tab's state?");
// verify the tab state's integrity
state = eval("(" + state + ")");
ok(state instanceof Object && state.entries instanceof Array && state.entries.length > 0,
"Got a valid state object?");
ok(state.entries.length == 1 && state.entries[0].url == "about:blank",
"Got the expected state object (test URL)?");
ok(state.extData && state.extData[key] == value,
"Got the expected state object (test manually set tab value)?");
// clean up
tabbrowser.removeTab(newTab);
/*****************************
* setTabState / duplicateTab
*****************************/
key = "key2";
value = "value2";
state = { entries: [{ url: "about:blank" }], extData: { key2: value } };
// create a new tab
newTab = tabbrowser.addTab();
// set the tab's state
ss.setTabState(newTab, state.toSource());
// verify the correctness of the restored tab
ok(ss.getTabValue(newTab, key) == value, "Correctly restored the tab's state?");
// duplicate the tab
var duplicateTab = ss.duplicateTab(currentWindow, newTab);
// verify the correctness of the duplicated tab
ok(ss.getTabValue(duplicateTab, key) == value, "Correctly duplicated the tab's state?");
// clean up
tabbrowser.removeTab(newTab);
tabbrowser.removeTab(duplicateTab);
]]>
</script>
</pre>
</body>
</window>