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 01:31:56 -08:00
Родитель 8bea0085ad
Коммит b82f4c4fc8
4 изменённых файлов: 196 добавлений и 20 удалений

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

@ -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
*/

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

@ -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
*/

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

@ -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)

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

@ -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>