зеркало из https://github.com/mozilla/pjs.git
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:
Родитель
28aa5d33a0
Коммит
0f78943054
|
@ -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>
|
Загрузка…
Ссылка в новой задаче