mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-10 02:08:38 +09:00
Add Pale Moon
This commit is contained in:
parent
dcd9973243
commit
fe8028fa2e
1173 changed files with 143053 additions and 934 deletions
230
application/palemoon/components/sessionstore/DocumentUtils.jsm
Normal file
230
application/palemoon/components/sessionstore/DocumentUtils.jsm
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
this.EXPORTED_SYMBOLS = [ "DocumentUtils" ];
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/sessionstore/XPathGenerator.jsm");
|
||||
|
||||
this.DocumentUtils = {
|
||||
/**
|
||||
* Obtain form data for a DOMDocument instance.
|
||||
*
|
||||
* The returned object has 2 keys, "id" and "xpath". Each key holds an object
|
||||
* which further defines form data.
|
||||
*
|
||||
* The "id" object maps element IDs to values. The "xpath" object maps the
|
||||
* XPath of an element to its value.
|
||||
*
|
||||
* @param aDocument
|
||||
* DOMDocument instance to obtain form data for.
|
||||
* @return object
|
||||
* Form data encoded in an object.
|
||||
*/
|
||||
getFormData: function DocumentUtils_getFormData(aDocument) {
|
||||
let formNodes = aDocument.evaluate(
|
||||
XPathGenerator.restorableFormNodes,
|
||||
aDocument,
|
||||
XPathGenerator.resolveNS,
|
||||
Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null
|
||||
);
|
||||
|
||||
let node;
|
||||
let ret = {id: {}, xpath: {}};
|
||||
|
||||
// Limit the number of XPath expressions for performance reasons. See
|
||||
// bug 477564.
|
||||
const MAX_TRAVERSED_XPATHS = 100;
|
||||
let generatedCount = 0;
|
||||
|
||||
while (node = formNodes.iterateNext()) {
|
||||
let nId = node.id;
|
||||
let hasDefaultValue = true;
|
||||
let value;
|
||||
|
||||
// Only generate a limited number of XPath expressions for perf reasons
|
||||
// (cf. bug 477564)
|
||||
if (!nId && generatedCount > MAX_TRAVERSED_XPATHS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node instanceof Ci.nsIDOMHTMLInputElement ||
|
||||
node instanceof Ci.nsIDOMHTMLTextAreaElement) {
|
||||
switch (node.type) {
|
||||
case "checkbox":
|
||||
case "radio":
|
||||
value = node.checked;
|
||||
hasDefaultValue = value == node.defaultChecked;
|
||||
break;
|
||||
case "file":
|
||||
value = { type: "file", fileList: node.mozGetFileNameArray() };
|
||||
hasDefaultValue = !value.fileList.length;
|
||||
break;
|
||||
default: // text, textarea
|
||||
value = node.value;
|
||||
hasDefaultValue = value == node.defaultValue;
|
||||
break;
|
||||
}
|
||||
} else if (!node.multiple) {
|
||||
// <select>s without the multiple attribute are hard to determine the
|
||||
// default value, so assume we don't have the default.
|
||||
hasDefaultValue = false;
|
||||
value = { selectedIndex: node.selectedIndex, value: node.value };
|
||||
} else {
|
||||
// <select>s with the multiple attribute are easier to determine the
|
||||
// default value since each <option> has a defaultSelected
|
||||
let options = Array.map(node.options, function(aOpt, aIx) {
|
||||
let oSelected = aOpt.selected;
|
||||
hasDefaultValue = hasDefaultValue && (oSelected == aOpt.defaultSelected);
|
||||
return oSelected ? aOpt.value : -1;
|
||||
});
|
||||
value = options.filter(function(aIx) aIx !== -1);
|
||||
}
|
||||
|
||||
// In order to reduce XPath generation (which is slow), we only save data
|
||||
// for form fields that have been changed. (cf. bug 537289)
|
||||
if (!hasDefaultValue) {
|
||||
if (nId) {
|
||||
ret.id[nId] = value;
|
||||
} else {
|
||||
generatedCount++;
|
||||
ret.xpath[XPathGenerator.generate(node)] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* Merges form data on a document from previously obtained data.
|
||||
*
|
||||
* This is the inverse of getFormData(). The data argument is the same object
|
||||
* type which is returned by getFormData(): an object containing the keys
|
||||
* "id" and "xpath" which are each objects mapping element identifiers to
|
||||
* form values.
|
||||
*
|
||||
* Where the document has existing form data for an element, the value
|
||||
* will be replaced. Where the document has a form element but no matching
|
||||
* data in the passed object, the element is untouched.
|
||||
*
|
||||
* @param aDocument
|
||||
* DOMDocument instance to which to restore form data.
|
||||
* @param aData
|
||||
* Object defining form data.
|
||||
*/
|
||||
mergeFormData: function DocumentUtils_mergeFormData(aDocument, aData) {
|
||||
if ("xpath" in aData) {
|
||||
for each (let [xpath, value] in Iterator(aData.xpath)) {
|
||||
let node = XPathGenerator.resolve(aDocument, xpath);
|
||||
|
||||
if (node) {
|
||||
this.restoreFormValue(node, value, aDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ("id" in aData) {
|
||||
for each (let [id, value] in Iterator(aData.id)) {
|
||||
let node = aDocument.getElementById(id);
|
||||
|
||||
if (node) {
|
||||
this.restoreFormValue(node, value, aDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Low-level function to restore a form value to a DOMNode.
|
||||
*
|
||||
* If you want a higher-level interface, see mergeFormData().
|
||||
*
|
||||
* When the value is changed, the function will fire the appropriate DOM
|
||||
* events.
|
||||
*
|
||||
* @param aNode
|
||||
* DOMNode to set form value on.
|
||||
* @param aValue
|
||||
* Value to set form element to.
|
||||
* @param aDocument [optional]
|
||||
* DOMDocument node belongs to. If not defined, node.ownerDocument
|
||||
* is used.
|
||||
*/
|
||||
restoreFormValue: function DocumentUtils_restoreFormValue(aNode, aValue, aDocument) {
|
||||
aDocument = aDocument || aNode.ownerDocument;
|
||||
|
||||
let eventType;
|
||||
|
||||
if (typeof aValue == "string" && aNode.type != "file") {
|
||||
// Don't dispatch an input event if there is no change.
|
||||
if (aNode.value == aValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
aNode.value = aValue;
|
||||
eventType = "input";
|
||||
} else if (typeof aValue == "boolean") {
|
||||
// Don't dispatch a change event for no change.
|
||||
if (aNode.checked == aValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
aNode.checked = aValue;
|
||||
eventType = "change";
|
||||
} else if (typeof aValue == "number") {
|
||||
// handle select backwards compatibility, example { "#id" : index }
|
||||
// We saved the value blindly since selects take more work to determine
|
||||
// default values. So now we should check to avoid unnecessary events.
|
||||
if (aNode.selectedIndex == aValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aValue < aNode.options.length) {
|
||||
aNode.selectedIndex = aValue;
|
||||
eventType = "change";
|
||||
}
|
||||
} else if (aValue && aValue.selectedIndex >= 0 && aValue.value) {
|
||||
// handle select new format
|
||||
|
||||
// Don't dispatch a change event for no change
|
||||
if (aNode.options[aNode.selectedIndex].value == aValue.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find first option with matching aValue if possible
|
||||
for (let i = 0; i < aNode.options.length; i++) {
|
||||
if (aNode.options[i].value == aValue.value) {
|
||||
aNode.selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
eventType = "change";
|
||||
} else if (aValue && aValue.fileList && aValue.type == "file" &&
|
||||
aNode.type == "file") {
|
||||
aNode.mozSetFileNameArray(aValue.fileList, aValue.fileList.length);
|
||||
eventType = "input";
|
||||
} else if (aValue && typeof aValue.indexOf == "function" && aNode.options) {
|
||||
Array.forEach(aNode.options, function(opt, index) {
|
||||
// don't worry about malformed options with same values
|
||||
opt.selected = aValue.indexOf(opt.value) > -1;
|
||||
|
||||
// Only fire the event here if this wasn't selected by default
|
||||
if (!opt.defaultSelected) {
|
||||
eventType = "change";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fire events for this node if applicable
|
||||
if (eventType) {
|
||||
let event = aDocument.createEvent("UIEvents");
|
||||
event.initUIEvent(eventType, true, true, aDocument.defaultView, 0);
|
||||
aNode.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
165
application/palemoon/components/sessionstore/SessionStorage.jsm
Normal file
165
application/palemoon/components/sessionstore/SessionStorage.jsm
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["SessionStorage"];
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
|
||||
"resource:///modules/sessionstore/SessionStore.jsm");
|
||||
|
||||
this.SessionStorage = {
|
||||
/**
|
||||
* Updates all sessionStorage "super cookies"
|
||||
* @param aDocShell
|
||||
* That tab's docshell (containing the sessionStorage)
|
||||
* @param aFullData
|
||||
* always return privacy sensitive data (use with care)
|
||||
*/
|
||||
serialize: function ssto_serialize(aDocShell, aFullData) {
|
||||
return DomStorage.read(aDocShell, aFullData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores all sessionStorage "super cookies".
|
||||
* @param aDocShell
|
||||
* A tab's docshell (containing the sessionStorage)
|
||||
* @param aStorageData
|
||||
* Storage data to be restored
|
||||
*/
|
||||
deserialize: function ssto_deserialize(aDocShell, aStorageData) {
|
||||
DomStorage.write(aDocShell, aStorageData);
|
||||
}
|
||||
};
|
||||
|
||||
Object.freeze(SessionStorage);
|
||||
|
||||
let DomStorage = {
|
||||
/**
|
||||
* Reads all session storage data from the given docShell.
|
||||
* @param aDocShell
|
||||
* A tab's docshell (containing the sessionStorage)
|
||||
* @param aFullData
|
||||
* Always return privacy sensitive data (use with care)
|
||||
*/
|
||||
read: function DomStorage_read(aDocShell, aFullData) {
|
||||
let data = {};
|
||||
let isPinned = aDocShell.isAppTab;
|
||||
let shistory = aDocShell.sessionHistory;
|
||||
|
||||
for (let i = 0; i < shistory.count; i++) {
|
||||
let principal = History.getPrincipalForEntry(shistory, i, aDocShell);
|
||||
if (!principal)
|
||||
continue;
|
||||
|
||||
// Check if we're allowed to store sessionStorage data.
|
||||
let isHTTPS = principal.URI && principal.URI.schemeIs("https");
|
||||
if (aFullData || SessionStore.checkPrivacyLevel(isHTTPS, isPinned)) {
|
||||
let origin = principal.extendedOrigin;
|
||||
|
||||
// Don't read a host twice.
|
||||
if (!(origin in data)) {
|
||||
let originData = this._readEntry(principal, aDocShell);
|
||||
if (Object.keys(originData).length) {
|
||||
data[origin] = originData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes session storage data to the given tab.
|
||||
* @param aDocShell
|
||||
* A tab's docshell (containing the sessionStorage)
|
||||
* @param aStorageData
|
||||
* Storage data to be restored
|
||||
*/
|
||||
write: function DomStorage_write(aDocShell, aStorageData) {
|
||||
for (let [host, data] in Iterator(aStorageData)) {
|
||||
let uri = Services.io.newURI(host, null, null);
|
||||
let principal = Services.scriptSecurityManager.getDocShellCodebasePrincipal(uri, aDocShell);
|
||||
let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager);
|
||||
let window = aDocShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindow);
|
||||
|
||||
// There is no need to pass documentURI, it's only used to fill documentURI property of
|
||||
// domstorage event, which in this case has no consumer. Prevention of events in case
|
||||
// of missing documentURI will be solved in a followup bug to bug 600307.
|
||||
try {
|
||||
let storage = storageManager.createStorage(window, principal, "", aDocShell.usePrivateBrowsing);
|
||||
} catch(e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
||||
for (let [key, value] in Iterator(data)) {
|
||||
try {
|
||||
storage.setItem(key, value);
|
||||
} catch (e) {
|
||||
// throws e.g. for URIs that can't have sessionStorage
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reads an entry in the session storage data contained in a tab's history.
|
||||
* @param aURI
|
||||
* That history entry uri
|
||||
* @param aDocShell
|
||||
* A tab's docshell (containing the sessionStorage)
|
||||
*/
|
||||
_readEntry: function DomStorage_readEntry(aPrincipal, aDocShell) {
|
||||
let hostData = {};
|
||||
let storage;
|
||||
|
||||
try {
|
||||
let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager);
|
||||
storage = storageManager.getStorage(aPrincipal);
|
||||
} catch (e) {
|
||||
// sessionStorage might throw if it's turned off, see bug 458954
|
||||
}
|
||||
|
||||
if (storage && storage.length) {
|
||||
for (let i = 0; i < storage.length; i++) {
|
||||
try {
|
||||
let key = storage.key(i);
|
||||
hostData[key] = storage.getItem(key);
|
||||
} catch (e) {
|
||||
// This currently throws for secured items (cf. bug 442048).
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hostData;
|
||||
}
|
||||
};
|
||||
|
||||
let History = {
|
||||
/**
|
||||
* Returns a given history entry's URI.
|
||||
* @param aHistory
|
||||
* That tab's session history
|
||||
* @param aIndex
|
||||
* The history entry's index
|
||||
* @param aDocShell
|
||||
* That tab's docshell
|
||||
*/
|
||||
getPrincipalForEntry: function History_getPrincipalForEntry(aHistory,
|
||||
aIndex,
|
||||
aDocShell) {
|
||||
try {
|
||||
return Services.scriptSecurityManager.getDocShellCodebasePrincipal(
|
||||
aHistory.getEntryAtIndex(aIndex, false).URI, aDocShell);
|
||||
} catch (e) {
|
||||
// This might throw for some reason.
|
||||
}
|
||||
},
|
||||
};
|
||||
4733
application/palemoon/components/sessionstore/SessionStore.jsm
Normal file
4733
application/palemoon/components/sessionstore/SessionStore.jsm
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,97 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["XPathGenerator"];
|
||||
|
||||
this.XPathGenerator = {
|
||||
// these two hashes should be kept in sync
|
||||
namespaceURIs: { "xhtml": "http://www.w3.org/1999/xhtml" },
|
||||
namespacePrefixes: { "http://www.w3.org/1999/xhtml": "xhtml" },
|
||||
|
||||
/**
|
||||
* Generates an approximate XPath query to an (X)HTML node
|
||||
*/
|
||||
generate: function sss_xph_generate(aNode) {
|
||||
// have we reached the document node already?
|
||||
if (!aNode.parentNode)
|
||||
return "";
|
||||
|
||||
// Access localName, namespaceURI just once per node since it's expensive.
|
||||
let nNamespaceURI = aNode.namespaceURI;
|
||||
let nLocalName = aNode.localName;
|
||||
|
||||
let prefix = this.namespacePrefixes[nNamespaceURI] || null;
|
||||
let tag = (prefix ? prefix + ":" : "") + this.escapeName(nLocalName);
|
||||
|
||||
// stop once we've found a tag with an ID
|
||||
if (aNode.id)
|
||||
return "//" + tag + "[@id=" + this.quoteArgument(aNode.id) + "]";
|
||||
|
||||
// count the number of previous sibling nodes of the same tag
|
||||
// (and possible also the same name)
|
||||
let count = 0;
|
||||
let nName = aNode.name || null;
|
||||
for (let n = aNode; (n = n.previousSibling); )
|
||||
if (n.localName == nLocalName && n.namespaceURI == nNamespaceURI &&
|
||||
(!nName || n.name == nName))
|
||||
count++;
|
||||
|
||||
// recurse until hitting either the document node or an ID'd node
|
||||
return this.generate(aNode.parentNode) + "/" + tag +
|
||||
(nName ? "[@name=" + this.quoteArgument(nName) + "]" : "") +
|
||||
(count ? "[" + (count + 1) + "]" : "");
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves an XPath query generated by XPathGenerator.generate
|
||||
*/
|
||||
resolve: function sss_xph_resolve(aDocument, aQuery) {
|
||||
let xptype = Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE;
|
||||
return aDocument.evaluate(aQuery, aDocument, this.resolveNS, xptype, null).singleNodeValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Namespace resolver for the above XPath resolver
|
||||
*/
|
||||
resolveNS: function sss_xph_resolveNS(aPrefix) {
|
||||
return XPathGenerator.namespaceURIs[aPrefix] || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns valid XPath for the given node (usually just the local name itself)
|
||||
*/
|
||||
escapeName: function sss_xph_escapeName(aName) {
|
||||
// we can't just use the node's local name, if it contains
|
||||
// special characters (cf. bug 485482)
|
||||
return /^\w+$/.test(aName) ? aName :
|
||||
"*[local-name()=" + this.quoteArgument(aName) + "]";
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns a properly quoted string to insert into an XPath query
|
||||
*/
|
||||
quoteArgument: function sss_xph_quoteArgument(aArg) {
|
||||
return !/'/.test(aArg) ? "'" + aArg + "'" :
|
||||
!/"/.test(aArg) ? '"' + aArg + '"' :
|
||||
"concat('" + aArg.replace(/'+/g, "',\"$&\",'") + "')";
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns an XPath query to all savable form field nodes
|
||||
*/
|
||||
get restorableFormNodes() {
|
||||
// for a comprehensive list of all available <INPUT> types see
|
||||
// http://mxr.mozilla.org/mozilla-central/search?string=kInputTypeTable
|
||||
let ignoreTypes = ["password", "hidden", "button", "image", "submit", "reset"];
|
||||
// XXXzeniko work-around until lower-case has been implemented (bug 398389)
|
||||
let toLowerCase = '"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"';
|
||||
let ignore = "not(translate(@type, " + toLowerCase + ")='" +
|
||||
ignoreTypes.join("' or translate(@type, " + toLowerCase + ")='") + "')";
|
||||
let formNodesXPath = "//textarea|//select|//xhtml:textarea|//xhtml:select|" +
|
||||
"//input[" + ignore + "]|//xhtml:input[" + ignore + "]";
|
||||
|
||||
delete this.restorableFormNodes;
|
||||
return (this.restorableFormNodes = formNodesXPath);
|
||||
}
|
||||
};
|
||||
311
application/palemoon/components/sessionstore/_SessionFile.jsm
Normal file
311
application/palemoon/components/sessionstore/_SessionFile.jsm
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["_SessionFile"];
|
||||
|
||||
/**
|
||||
* Implementation of all the disk I/O required by the session store.
|
||||
* This is a private API, meant to be used only by the session store.
|
||||
* It will change. Do not use it for any other purpose.
|
||||
*
|
||||
* Note that this module implicitly depends on one of two things:
|
||||
* 1. either the asynchronous file I/O system enqueues its requests
|
||||
* and never attempts to simultaneously execute two I/O requests on
|
||||
* the files used by this module from two distinct threads; or
|
||||
* 2. the clients of this API are well-behaved and do not place
|
||||
* concurrent requests to the files used by this module.
|
||||
*
|
||||
* Otherwise, we could encounter bugs, especially under Windows,
|
||||
* e.g. if a request attempts to write sessionstore.js while
|
||||
* another attempts to copy that file.
|
||||
*
|
||||
* This implementation uses OS.File, which guarantees property 1.
|
||||
*/
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
// An encoder to UTF-8.
|
||||
XPCOMUtils.defineLazyGetter(this, "gEncoder", function () {
|
||||
return new TextEncoder();
|
||||
});
|
||||
// A decoder.
|
||||
XPCOMUtils.defineLazyGetter(this, "gDecoder", function () {
|
||||
return new TextDecoder();
|
||||
});
|
||||
|
||||
this._SessionFile = {
|
||||
/**
|
||||
* A promise fulfilled once initialization (either synchronous or
|
||||
* asynchronous) is complete.
|
||||
*/
|
||||
promiseInitialized: function SessionFile_initialized() {
|
||||
return SessionFileInternal.promiseInitialized;
|
||||
},
|
||||
/**
|
||||
* Read the contents of the session file, asynchronously.
|
||||
*/
|
||||
read: function SessionFile_read() {
|
||||
return SessionFileInternal.read();
|
||||
},
|
||||
/**
|
||||
* Read the contents of the session file, synchronously.
|
||||
*/
|
||||
syncRead: function SessionFile_syncRead() {
|
||||
return SessionFileInternal.syncRead();
|
||||
},
|
||||
/**
|
||||
* Write the contents of the session file, asynchronously.
|
||||
*/
|
||||
write: function SessionFile_write(aData) {
|
||||
return SessionFileInternal.write(aData);
|
||||
},
|
||||
/**
|
||||
* Create a backup copy, asynchronously.
|
||||
*/
|
||||
createBackupCopy: function SessionFile_createBackupCopy() {
|
||||
return SessionFileInternal.createBackupCopy();
|
||||
},
|
||||
/**
|
||||
* Wipe the contents of the session file, asynchronously.
|
||||
*/
|
||||
wipe: function SessionFile_wipe() {
|
||||
return SessionFileInternal.wipe();
|
||||
}
|
||||
};
|
||||
|
||||
Object.freeze(_SessionFile);
|
||||
|
||||
/**
|
||||
* Utilities for dealing with promises and Task.jsm
|
||||
*/
|
||||
const TaskUtils = {
|
||||
/**
|
||||
* Add logging to a promise.
|
||||
*
|
||||
* @param {Promise} promise
|
||||
* @return {Promise} A promise behaving as |promise|, but with additional
|
||||
* logging in case of uncaught error.
|
||||
*/
|
||||
captureErrors: function captureErrors(promise) {
|
||||
return promise.then(
|
||||
null,
|
||||
function onError(reason) {
|
||||
console.error("Uncaught asynchronous error:", reason);
|
||||
throw reason;
|
||||
}
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Spawn a new Task from a generator.
|
||||
*
|
||||
* This function behaves as |Task.spawn|, with the exception that it
|
||||
* adds logging in case of uncaught error. For more information, see
|
||||
* the documentation of |Task.jsm|.
|
||||
*
|
||||
* @param {generator} gen Some generator.
|
||||
* @return {Promise} A promise built from |gen|, with the same semantics
|
||||
* as |Task.spawn(gen)|.
|
||||
*/
|
||||
spawn: function spawn(gen) {
|
||||
return this.captureErrors(Task.spawn(gen));
|
||||
}
|
||||
};
|
||||
|
||||
let SessionFileInternal = {
|
||||
/**
|
||||
* A promise fulfilled once initialization is complete
|
||||
*/
|
||||
promiseInitialized: Promise.defer(),
|
||||
|
||||
/**
|
||||
* The path to sessionstore.js
|
||||
*/
|
||||
path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"),
|
||||
|
||||
/**
|
||||
* The path to sessionstore.bak
|
||||
*/
|
||||
backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
|
||||
|
||||
/**
|
||||
* Utility function to safely read a file synchronously.
|
||||
* @param aPath
|
||||
* A path to read the file from.
|
||||
* @returns string if successful, undefined otherwise.
|
||||
*/
|
||||
readAuxSync: function ssfi_readAuxSync(aPath) {
|
||||
let text;
|
||||
try {
|
||||
let file = new FileUtils.File(aPath);
|
||||
let chan = NetUtil.newChannel(file);
|
||||
let stream = chan.open();
|
||||
text = NetUtil.readInputStreamToString(stream, stream.available(),
|
||||
{charset: "utf-8"});
|
||||
} catch (e if e.result == Components.results.NS_ERROR_FILE_NOT_FOUND) {
|
||||
// Ignore exceptions about non-existent files.
|
||||
} catch (ex) {
|
||||
// Any other error.
|
||||
console.error("Uncaught error:", ex);
|
||||
} finally {
|
||||
return text;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Read the sessionstore file synchronously.
|
||||
*
|
||||
* This function is meant to serve as a fallback in case of race
|
||||
* between a synchronous usage of the API and asynchronous
|
||||
* initialization.
|
||||
*
|
||||
* In case if sessionstore.js file does not exist or is corrupted (something
|
||||
* happened between backup and write), attempt to read the sessionstore.bak
|
||||
* instead.
|
||||
*/
|
||||
syncRead: function ssfi_syncRead() {
|
||||
// First read the sessionstore.js.
|
||||
let text = this.readAuxSync(this.path);
|
||||
if (typeof text === "undefined") {
|
||||
// If sessionstore.js does not exist or is corrupted, read sessionstore.bak.
|
||||
text = this.readAuxSync(this.backupPath);
|
||||
}
|
||||
return text || "";
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility function to safely read a file asynchronously.
|
||||
* @param aPath
|
||||
* A path to read the file from.
|
||||
* @param aReadOptions
|
||||
* Read operation options.
|
||||
* |outExecutionDuration| option will be reused and can be
|
||||
* incrementally updated by the worker process.
|
||||
* @returns string if successful, undefined otherwise.
|
||||
*/
|
||||
readAux: function ssfi_readAux(aPath, aReadOptions) {
|
||||
let self = this;
|
||||
return TaskUtils.spawn(function () {
|
||||
let text;
|
||||
try {
|
||||
let bytes = yield OS.File.read(aPath, undefined, aReadOptions);
|
||||
text = gDecoder.decode(bytes);
|
||||
} catch (ex if self._isNoSuchFile(ex)) {
|
||||
// Ignore exceptions about non-existent files.
|
||||
} catch (ex) {
|
||||
// Any other error.
|
||||
console.error("Uncaught error - with the file: " + self.path, ex);
|
||||
}
|
||||
throw new Task.Result(text);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Read the sessionstore file asynchronously.
|
||||
*
|
||||
* In case sessionstore.js file does not exist or is corrupted (something
|
||||
* happened between backup and write), attempt to read the sessionstore.bak
|
||||
* instead.
|
||||
*/
|
||||
read: function ssfi_read() {
|
||||
let self = this;
|
||||
return TaskUtils.spawn(function task() {
|
||||
// Specify |outExecutionDuration| option to hold the combined duration of
|
||||
// the asynchronous reads off the main thread (of both sessionstore.js and
|
||||
// sessionstore.bak, if necessary). If sessionstore.js does not exist or
|
||||
// is corrupted, |outExecutionDuration| will register the time it took to
|
||||
// attempt to read the file. It will then be subsequently incremented by
|
||||
// the read time of sessionsore.bak.
|
||||
let readOptions = {
|
||||
outExecutionDuration: null
|
||||
};
|
||||
// First read the sessionstore.js.
|
||||
let text = yield self.readAux(self.path, readOptions);
|
||||
if (typeof text === "undefined") {
|
||||
// If sessionstore.js does not exist or is corrupted, read the
|
||||
// sessionstore.bak.
|
||||
text = yield self.readAux(self.backupPath, readOptions);
|
||||
}
|
||||
// Return either the content of the sessionstore.bak if it was read
|
||||
// successfully or an empty string otherwise.
|
||||
throw new Task.Result(text || "");
|
||||
});
|
||||
},
|
||||
|
||||
write: function ssfi_write(aData) {
|
||||
let refObj = {};
|
||||
let self = this;
|
||||
return TaskUtils.spawn(function task() {
|
||||
let bytes = gEncoder.encode(aData);
|
||||
|
||||
try {
|
||||
let promise = OS.File.writeAtomic(self.path, bytes, {tmpPath: self.path + ".tmp"});
|
||||
yield promise;
|
||||
} catch (ex) {
|
||||
console.error("Could not write session state file: " + self.path, ex);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
createBackupCopy: function ssfi_createBackupCopy() {
|
||||
let backupCopyOptions = {
|
||||
outExecutionDuration: null
|
||||
};
|
||||
let self = this;
|
||||
return TaskUtils.spawn(function task() {
|
||||
try {
|
||||
yield OS.File.move(self.path, self.backupPath, backupCopyOptions);
|
||||
} catch (ex if self._isNoSuchFile(ex)) {
|
||||
// Ignore exceptions about non-existent files.
|
||||
} catch (ex) {
|
||||
console.error("Could not backup session state file: " + self.path, ex);
|
||||
throw ex;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
wipe: function ssfi_wipe() {
|
||||
let self = this;
|
||||
return TaskUtils.spawn(function task() {
|
||||
try {
|
||||
yield OS.File.remove(self.path);
|
||||
} catch (ex if self._isNoSuchFile(ex)) {
|
||||
// Ignore exceptions about non-existent files.
|
||||
} catch (ex) {
|
||||
console.error("Could not remove session state file: " + self.path, ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
try {
|
||||
yield OS.File.remove(self.backupPath);
|
||||
} catch (ex if self._isNoSuchFile(ex)) {
|
||||
// Ignore exceptions about non-existent files.
|
||||
} catch (ex) {
|
||||
console.error("Could not remove session state backup file: " + self.path, ex);
|
||||
throw ex;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_isNoSuchFile: function ssfi_isNoSuchFile(aReason) {
|
||||
return aReason instanceof OS.File.Error && aReason.becauseNoSuchFile;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
var gStateObject;
|
||||
var gTreeData;
|
||||
|
||||
// Page initialization
|
||||
|
||||
window.onload = function() {
|
||||
// the crashed session state is kept inside a textbox so that SessionStore picks it up
|
||||
// (for when the tab is closed or the session crashes right again)
|
||||
var sessionData = document.getElementById("sessionData");
|
||||
if (!sessionData.value) {
|
||||
document.getElementById("errorTryAgain").disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// remove unneeded braces (added for compatibility with Firefox 2.0 and 3.0)
|
||||
if (sessionData.value.charAt(0) == '(')
|
||||
sessionData.value = sessionData.value.slice(1, -1);
|
||||
try {
|
||||
gStateObject = JSON.parse(sessionData.value);
|
||||
}
|
||||
catch (exJSON) {
|
||||
var s = new Cu.Sandbox("about:blank", {sandboxName: 'aboutSessionRestore'});
|
||||
gStateObject = Cu.evalInSandbox("(" + sessionData.value + ")", s);
|
||||
// If we couldn't parse the string with JSON.parse originally, make sure
|
||||
// that the value in the textbox will be parsable.
|
||||
sessionData.value = JSON.stringify(gStateObject);
|
||||
}
|
||||
|
||||
// make sure the data is tracked to be restored in case of a subsequent crash
|
||||
var event = document.createEvent("UIEvents");
|
||||
event.initUIEvent("input", true, true, window, 0);
|
||||
sessionData.dispatchEvent(event);
|
||||
|
||||
initTreeView();
|
||||
|
||||
document.getElementById("errorTryAgain").focus();
|
||||
};
|
||||
|
||||
function initTreeView() {
|
||||
var tabList = document.getElementById("tabList");
|
||||
var winLabel = tabList.getAttribute("_window_label");
|
||||
|
||||
gTreeData = [];
|
||||
gStateObject.windows.forEach(function(aWinData, aIx) {
|
||||
var winState = {
|
||||
label: winLabel.replace("%S", (aIx + 1)),
|
||||
open: true,
|
||||
checked: true,
|
||||
ix: aIx
|
||||
};
|
||||
winState.tabs = aWinData.tabs.map(function(aTabData) {
|
||||
var entry = aTabData.entries[aTabData.index - 1] || { url: "about:blank" };
|
||||
var iconURL = aTabData.attributes && aTabData.attributes.image || null;
|
||||
// don't initiate a connection just to fetch a favicon (see bug 462863)
|
||||
if (/^https?:/.test(iconURL))
|
||||
iconURL = "moz-anno:favicon:" + iconURL;
|
||||
return {
|
||||
label: entry.title || entry.url,
|
||||
checked: true,
|
||||
src: iconURL,
|
||||
parent: winState
|
||||
};
|
||||
});
|
||||
gTreeData.push(winState);
|
||||
for (let tab of winState.tabs)
|
||||
gTreeData.push(tab);
|
||||
}, this);
|
||||
|
||||
tabList.view = treeView;
|
||||
tabList.view.selection.select(0);
|
||||
}
|
||||
|
||||
// User actions
|
||||
|
||||
function restoreSession() {
|
||||
document.getElementById("errorTryAgain").disabled = true;
|
||||
|
||||
// remove all unselected tabs from the state before restoring it
|
||||
var ix = gStateObject.windows.length - 1;
|
||||
for (var t = gTreeData.length - 1; t >= 0; t--) {
|
||||
if (treeView.isContainer(t)) {
|
||||
if (gTreeData[t].checked === 0)
|
||||
// this window will be restored partially
|
||||
gStateObject.windows[ix].tabs =
|
||||
gStateObject.windows[ix].tabs.filter(function(aTabData, aIx)
|
||||
gTreeData[t].tabs[aIx].checked);
|
||||
else if (!gTreeData[t].checked)
|
||||
// this window won't be restored at all
|
||||
gStateObject.windows.splice(ix, 1);
|
||||
ix--;
|
||||
}
|
||||
}
|
||||
var stateString = JSON.stringify(gStateObject);
|
||||
|
||||
var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
|
||||
var top = getBrowserWindow();
|
||||
|
||||
// if there's only this page open, reuse the window for restoring the session
|
||||
if (top.gBrowser.tabs.length == 1) {
|
||||
ss.setWindowState(top, stateString, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// restore the session into a new window and close the current tab
|
||||
var newWindow = top.openDialog(top.location, "_blank", "chrome,dialog=no,all");
|
||||
newWindow.addEventListener("load", function() {
|
||||
newWindow.removeEventListener("load", arguments.callee, true);
|
||||
ss.setWindowState(newWindow, stateString, true);
|
||||
|
||||
var tabbrowser = top.gBrowser;
|
||||
var tabIndex = tabbrowser.getBrowserIndexForDocument(document);
|
||||
tabbrowser.removeTab(tabbrowser.tabs[tabIndex]);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function startNewSession() {
|
||||
var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
|
||||
if (prefBranch.getIntPref("browser.startup.page") == 0)
|
||||
getBrowserWindow().gBrowser.loadURI("about:logopage");
|
||||
else
|
||||
getBrowserWindow().BrowserHome();
|
||||
}
|
||||
|
||||
function onListClick(aEvent) {
|
||||
// don't react to right-clicks
|
||||
if (aEvent.button == 2)
|
||||
return;
|
||||
|
||||
var cell = treeView.treeBox.getCellAt(aEvent.clientX, aEvent.clientY);
|
||||
if (cell.col) {
|
||||
// Restore this specific tab in the same window for middle/double/accel clicking
|
||||
// on a tab's title.
|
||||
#ifdef XP_MACOSX
|
||||
let accelKey = aEvent.metaKey;
|
||||
#else
|
||||
let accelKey = aEvent.ctrlKey;
|
||||
#endif
|
||||
if ((aEvent.button == 1 || aEvent.button == 0 && aEvent.detail == 2 || accelKey) &&
|
||||
cell.col.id == "title" &&
|
||||
!treeView.isContainer(cell.row)) {
|
||||
restoreSingleTab(cell.row, aEvent.shiftKey);
|
||||
aEvent.stopPropagation();
|
||||
}
|
||||
else if (cell.col.id == "restore")
|
||||
toggleRowChecked(cell.row);
|
||||
}
|
||||
}
|
||||
|
||||
function onListKeyDown(aEvent) {
|
||||
switch (aEvent.keyCode)
|
||||
{
|
||||
case KeyEvent.DOM_VK_SPACE:
|
||||
toggleRowChecked(document.getElementById("tabList").currentIndex);
|
||||
break;
|
||||
case KeyEvent.DOM_VK_RETURN:
|
||||
var ix = document.getElementById("tabList").currentIndex;
|
||||
if (aEvent.ctrlKey && !treeView.isContainer(ix))
|
||||
restoreSingleTab(ix, aEvent.shiftKey);
|
||||
break;
|
||||
case KeyEvent.DOM_VK_UP:
|
||||
case KeyEvent.DOM_VK_DOWN:
|
||||
case KeyEvent.DOM_VK_PAGE_UP:
|
||||
case KeyEvent.DOM_VK_PAGE_DOWN:
|
||||
case KeyEvent.DOM_VK_HOME:
|
||||
case KeyEvent.DOM_VK_END:
|
||||
aEvent.preventDefault(); // else the page scrolls unwantedly
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
function getBrowserWindow() {
|
||||
return window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
|
||||
}
|
||||
|
||||
function toggleRowChecked(aIx) {
|
||||
var item = gTreeData[aIx];
|
||||
item.checked = !item.checked;
|
||||
treeView.treeBox.invalidateRow(aIx);
|
||||
|
||||
function isChecked(aItem) aItem.checked;
|
||||
|
||||
if (treeView.isContainer(aIx)) {
|
||||
// (un)check all tabs of this window as well
|
||||
for (let tab of item.tabs) {
|
||||
tab.checked = item.checked;
|
||||
treeView.treeBox.invalidateRow(gTreeData.indexOf(tab));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// update the window's checkmark as well (0 means "partially checked")
|
||||
item.parent.checked = item.parent.tabs.every(isChecked) ? true :
|
||||
item.parent.tabs.some(isChecked) ? 0 : false;
|
||||
treeView.treeBox.invalidateRow(gTreeData.indexOf(item.parent));
|
||||
}
|
||||
|
||||
document.getElementById("errorTryAgain").disabled = !gTreeData.some(isChecked);
|
||||
}
|
||||
|
||||
function restoreSingleTab(aIx, aShifted) {
|
||||
var tabbrowser = getBrowserWindow().gBrowser;
|
||||
var newTab = tabbrowser.addTab();
|
||||
var item = gTreeData[aIx];
|
||||
|
||||
var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
|
||||
var tabState = gStateObject.windows[item.parent.ix]
|
||||
.tabs[aIx - gTreeData.indexOf(item.parent) - 1];
|
||||
// ensure tab would be visible on the tabstrip.
|
||||
tabState.hidden = false;
|
||||
ss.setTabState(newTab, JSON.stringify(tabState));
|
||||
|
||||
// respect the preference as to whether to select the tab (the Shift key inverses)
|
||||
var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
|
||||
if (prefBranch.getBoolPref("browser.tabs.loadInBackground") != !aShifted)
|
||||
tabbrowser.selectedTab = newTab;
|
||||
}
|
||||
|
||||
// Tree controller
|
||||
|
||||
var treeView = {
|
||||
treeBox: null,
|
||||
selection: null,
|
||||
|
||||
get rowCount() { return gTreeData.length; },
|
||||
setTree: function(treeBox) { this.treeBox = treeBox; },
|
||||
getCellText: function(idx, column) { return gTreeData[idx].label; },
|
||||
isContainer: function(idx) { return "open" in gTreeData[idx]; },
|
||||
getCellValue: function(idx, column){ return gTreeData[idx].checked; },
|
||||
isContainerOpen: function(idx) { return gTreeData[idx].open; },
|
||||
isContainerEmpty: function(idx) { return false; },
|
||||
isSeparator: function(idx) { return false; },
|
||||
isSorted: function() { return false; },
|
||||
isEditable: function(idx, column) { return false; },
|
||||
canDrop: function(idx, orientation, dt) { return false; },
|
||||
getLevel: function(idx) { return this.isContainer(idx) ? 0 : 1; },
|
||||
|
||||
getParentIndex: function(idx) {
|
||||
if (!this.isContainer(idx))
|
||||
for (var t = idx - 1; t >= 0 ; t--)
|
||||
if (this.isContainer(t))
|
||||
return t;
|
||||
return -1;
|
||||
},
|
||||
|
||||
hasNextSibling: function(idx, after) {
|
||||
var thisLevel = this.getLevel(idx);
|
||||
for (var t = after + 1; t < gTreeData.length; t++)
|
||||
if (this.getLevel(t) <= thisLevel)
|
||||
return this.getLevel(t) == thisLevel;
|
||||
return false;
|
||||
},
|
||||
|
||||
toggleOpenState: function(idx) {
|
||||
if (!this.isContainer(idx))
|
||||
return;
|
||||
var item = gTreeData[idx];
|
||||
if (item.open) {
|
||||
// remove this window's tab rows from the view
|
||||
var thisLevel = this.getLevel(idx);
|
||||
for (var t = idx + 1; t < gTreeData.length && this.getLevel(t) > thisLevel; t++);
|
||||
var deletecount = t - idx - 1;
|
||||
gTreeData.splice(idx + 1, deletecount);
|
||||
this.treeBox.rowCountChanged(idx + 1, -deletecount);
|
||||
}
|
||||
else {
|
||||
// add this window's tab rows to the view
|
||||
var toinsert = gTreeData[idx].tabs;
|
||||
for (var i = 0; i < toinsert.length; i++)
|
||||
gTreeData.splice(idx + i + 1, 0, toinsert[i]);
|
||||
this.treeBox.rowCountChanged(idx + 1, toinsert.length);
|
||||
}
|
||||
item.open = !item.open;
|
||||
this.treeBox.invalidateRow(idx);
|
||||
},
|
||||
|
||||
getCellProperties: function(idx, column) {
|
||||
if (column.id == "restore" && this.isContainer(idx) && gTreeData[idx].checked === 0)
|
||||
return "partial";
|
||||
if (column.id == "title")
|
||||
return this.getImageSrc(idx, column) ? "icon" : "noicon";
|
||||
|
||||
return "";
|
||||
},
|
||||
|
||||
getRowProperties: function(idx) {
|
||||
var winState = gTreeData[idx].parent || gTreeData[idx];
|
||||
if (winState.ix % 2 != 0)
|
||||
return "alternate";
|
||||
|
||||
return "";
|
||||
},
|
||||
|
||||
getImageSrc: function(idx, column) {
|
||||
if (column.id == "title")
|
||||
return gTreeData[idx].src || null;
|
||||
return null;
|
||||
},
|
||||
|
||||
getProgressMode : function(idx, column) { },
|
||||
cycleHeader: function(column) { },
|
||||
cycleCell: function(idx, column) { },
|
||||
selectionChanged: function() { },
|
||||
performAction: function(action) { },
|
||||
performActionOnCell: function(action, index, column) { },
|
||||
getColumnProperties: function(column) { return ""; }
|
||||
};
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % netErrorDTD SYSTEM "chrome://global/locale/netError.dtd">
|
||||
%netErrorDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % restorepageDTD SYSTEM "chrome://browser/locale/aboutSessionRestore.dtd">
|
||||
%restorepageDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&restorepage.tabtitle;</title>
|
||||
<link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutSessionRestore.css" type="text/css" media="all"/>
|
||||
<link rel="icon" type="image/png" href="chrome://global/skin/icons/warning-16.png"/>
|
||||
|
||||
<script type="application/javascript;version=1.8" src="chrome://browser/content/aboutSessionRestore.js"/>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
|
||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
||||
<div id="errorPageContainer">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div id="errorTitle">
|
||||
<h1 id="errorTitleText">&restorepage.errorTitle;</h1>
|
||||
</div>
|
||||
|
||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
||||
<div id="errorLongContent">
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p id="errorShortDescText">&restorepage.problemDesc;</p>
|
||||
</div>
|
||||
|
||||
<!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
|
||||
<div id="errorLongDesc">
|
||||
<p>&restorepage.tryThis;</p>
|
||||
<ul>
|
||||
<li>&restorepage.restoreSome;</li>
|
||||
<li>&restorepage.startNew;</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorTrailerDesc">
|
||||
<tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
id="tabList" flex="1" seltype="single" hidecolumnpicker="true"
|
||||
onclick="onListClick(event);" onkeydown="onListKeyDown(event);"
|
||||
_window_label="&restorepage.windowLabel;">
|
||||
<treecols>
|
||||
<treecol cycler="true" id="restore" type="checkbox" label="&restorepage.restoreHeader;"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol primary="true" id="title" label="&restorepage.listHeader;" flex="1"/>
|
||||
</treecols>
|
||||
<treechildren flex="1"/>
|
||||
</tree>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="buttons">
|
||||
#ifdef XP_UNIX
|
||||
<button id="errorCancel" label="&restorepage.closeButton;"
|
||||
accesskey="&restorepage.close.access;"
|
||||
oncommand="startNewSession();"/>
|
||||
<button id="errorTryAgain" label="&restorepage.tryagainButton;"
|
||||
accesskey="&restorepage.restore.access;"
|
||||
oncommand="restoreSession();"/>
|
||||
#else
|
||||
<button id="errorTryAgain" label="&restorepage.tryagainButton;"
|
||||
accesskey="&restorepage.restore.access;"
|
||||
oncommand="restoreSession();"/>
|
||||
<button id="errorCancel" label="&restorepage.closeButton;"
|
||||
accesskey="&restorepage.close.access;"
|
||||
oncommand="startNewSession();"/>
|
||||
#endif
|
||||
</hbox>
|
||||
<!-- holds the session data for when the tab is closed -->
|
||||
<input type="text" id="sessionData" style="display: none;"/>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
function debug(msg) {
|
||||
Services.console.logStringMessage("SessionStoreContent: " + msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for and handles content events that we need for the
|
||||
* session store service to be notified of state changes in content.
|
||||
*/
|
||||
let EventListener = {
|
||||
|
||||
DOM_EVENTS: [
|
||||
"pageshow", "change", "input"
|
||||
],
|
||||
|
||||
init: function () {
|
||||
this.DOM_EVENTS.forEach(e => addEventListener(e, this, true));
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
switch (event.type) {
|
||||
case "pageshow":
|
||||
if (event.persisted)
|
||||
sendAsyncMessage("SessionStore:pageshow");
|
||||
break;
|
||||
case "input":
|
||||
case "change":
|
||||
sendAsyncMessage("SessionStore:input");
|
||||
break;
|
||||
default:
|
||||
debug("received unknown event '" + event.type + "'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
EventListener.init();
|
||||
8
application/palemoon/components/sessionstore/jar.mn
Normal file
8
application/palemoon/components/sessionstore/jar.mn
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
browser.jar:
|
||||
* content/browser/aboutSessionRestore.xhtml (content/aboutSessionRestore.xhtml)
|
||||
* content/browser/aboutSessionRestore.js (content/aboutSessionRestore.js)
|
||||
content/browser/content-sessionStore.js (content/content-sessionStore.js)
|
||||
31
application/palemoon/components/sessionstore/moz.build
Normal file
31
application/palemoon/components/sessionstore/moz.build
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsISessionStartup.idl',
|
||||
'nsISessionStore.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'sessionstore'
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'nsSessionStartup.js',
|
||||
'nsSessionStore.js',
|
||||
'nsSessionStore.manifest',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.sessionstore = [
|
||||
'_SessionFile.jsm',
|
||||
'DocumentUtils.jsm',
|
||||
'SessionStorage.jsm',
|
||||
'XPathGenerator.jsm',
|
||||
]
|
||||
|
||||
EXTRA_PP_JS_MODULES.sessionstore += [
|
||||
'SessionStore.jsm',
|
||||
]
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* nsISessionStore keeps track of the current browsing state - i.e.
|
||||
* tab history, cookies, scroll state, form data, POSTDATA and window features
|
||||
* - and allows to restore everything into one window.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(51f4b9f0-f3d2-11e2-bb62-2c24dd830245)]
|
||||
interface nsISessionStartup: nsISupports
|
||||
{
|
||||
/**
|
||||
* Return a promise that is resolved once initialization
|
||||
* is complete.
|
||||
*/
|
||||
readonly attribute jsval onceInitialized;
|
||||
|
||||
// Get session state
|
||||
readonly attribute jsval state;
|
||||
|
||||
/**
|
||||
* Determines whether there is a pending session restore and makes sure that
|
||||
* we're initialized before returning. If we're not yet this will read the
|
||||
* session file synchronously.
|
||||
*/
|
||||
boolean doRestore();
|
||||
|
||||
/**
|
||||
* Returns whether we will restore a session that ends up replacing the
|
||||
* homepage. The browser uses this to not start loading the homepage if
|
||||
* we're going to stop its load anyway shortly after.
|
||||
*
|
||||
* This is meant to be an optimization for the average case that loading the
|
||||
* session file finishes before we may want to start loading the default
|
||||
* homepage. Should this be called before the session file has been read it
|
||||
* will just return false.
|
||||
*/
|
||||
readonly attribute bool willOverrideHomepage;
|
||||
|
||||
/**
|
||||
* What type of session we're restoring.
|
||||
* NO_SESSION There is no data available from the previous session
|
||||
* RECOVER_SESSION The last session crashed. It will either be restored or
|
||||
* about:sessionrestore will be shown.
|
||||
* RESUME_SESSION The previous session should be restored at startup
|
||||
* DEFER_SESSION The previous session is fine, but it shouldn't be restored
|
||||
* without explicit action (with the exception of pinned tabs)
|
||||
*/
|
||||
const unsigned long NO_SESSION = 0;
|
||||
const unsigned long RECOVER_SESSION = 1;
|
||||
const unsigned long RESUME_SESSION = 2;
|
||||
const unsigned long DEFER_SESSION = 3;
|
||||
|
||||
readonly attribute unsigned long sessionType;
|
||||
};
|
||||
206
application/palemoon/components/sessionstore/nsISessionStore.idl
Normal file
206
application/palemoon/components/sessionstore/nsISessionStore.idl
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIDOMWindow;
|
||||
interface nsIDOMNode;
|
||||
|
||||
/**
|
||||
* nsISessionStore keeps track of the current browsing state - i.e.
|
||||
* tab history, cookies, scroll state, form data, POSTDATA and window features
|
||||
* - and allows to restore everything into one browser window.
|
||||
*
|
||||
* The nsISessionStore API operates mostly on browser windows and the tabbrowser
|
||||
* tabs contained in them:
|
||||
*
|
||||
* * "Browser windows" are those DOM windows having loaded
|
||||
* chrome://browser/content/browser.xul . From overlays you can just pass the
|
||||
* global |window| object to the API, though (or |top| from a sidebar).
|
||||
* From elsewhere you can get browser windows through the nsIWindowMediator
|
||||
* by looking for "navigator:browser" windows.
|
||||
*
|
||||
* * "Tabbrowser tabs" are all the child nodes of a browser window's
|
||||
* |gBrowser.tabContainer| such as e.g. |gBrowser.selectedTab|.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(43ec216b-f002-4424-bfc5-fc555c87dbc4)]
|
||||
interface nsISessionStore : nsISupports
|
||||
{
|
||||
/**
|
||||
* Initialize the service
|
||||
*/
|
||||
jsval init(in nsIDOMWindow aWindow);
|
||||
|
||||
/**
|
||||
* Is it possible to restore the previous session. Will always be false when
|
||||
* in Private Browsing mode.
|
||||
*/
|
||||
attribute boolean canRestoreLastSession;
|
||||
|
||||
/**
|
||||
* Restore the previous session if possible. This will not overwrite the
|
||||
* current session. Instead the previous session will be merged into the
|
||||
* current session. Current windows will be reused if they were windows that
|
||||
* pinned tabs were previously restored into. New windows will be opened as
|
||||
* needed.
|
||||
*
|
||||
* Note: This will throw if there is no previous state to restore. Check with
|
||||
* canRestoreLastSession first to avoid thrown errors.
|
||||
*/
|
||||
void restoreLastSession();
|
||||
|
||||
/**
|
||||
* Get the current browsing state.
|
||||
* @returns a JSON string representing the session state.
|
||||
*/
|
||||
AString getBrowserState();
|
||||
|
||||
/**
|
||||
* Set the browsing state.
|
||||
* This will immediately restore the state of the whole application to the state
|
||||
* passed in, *replacing* the current session.
|
||||
*
|
||||
* @param aState is a JSON string representing the session state.
|
||||
*/
|
||||
void setBrowserState(in AString aState);
|
||||
|
||||
/**
|
||||
* @param aWindow is the browser window whose state is to be returned.
|
||||
*
|
||||
* @returns a JSON string representing a session state with only one window.
|
||||
*/
|
||||
AString getWindowState(in nsIDOMWindow aWindow);
|
||||
|
||||
/**
|
||||
* @param aWindow is the browser window whose state is to be set.
|
||||
* @param aState is a JSON string representing a session state.
|
||||
* @param aOverwrite boolean overwrite existing tabs
|
||||
*/
|
||||
void setWindowState(in nsIDOMWindow aWindow, in AString aState, in boolean aOverwrite);
|
||||
|
||||
/**
|
||||
* @param aTab is the tabbrowser tab whose state is to be returned.
|
||||
*
|
||||
* @returns 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 tabbrowser 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 browser window into which the tab will be duplicated.
|
||||
* @param aTab is the tabbrowser tab to duplicate (can be from a different window).
|
||||
* @param aDelta is the offset to the history entry to load in the duplicated tab.
|
||||
* @returns a reference to the newly created tab.
|
||||
*/
|
||||
nsIDOMNode duplicateTab(in nsIDOMWindow aWindow, in nsIDOMNode aTab,
|
||||
[optional] in long aDelta);
|
||||
|
||||
/**
|
||||
* Get the number of restore-able tabs for a browser window
|
||||
*/
|
||||
unsigned long getClosedTabCount(in nsIDOMWindow aWindow);
|
||||
|
||||
/**
|
||||
* Get closed tab data
|
||||
*
|
||||
* @param aWindow is the browser window for which to get closed tab data
|
||||
* @returns a JSON string representing the list of closed tabs.
|
||||
*/
|
||||
AString getClosedTabData(in nsIDOMWindow aWindow);
|
||||
|
||||
/**
|
||||
* @param aWindow is the browser window to reopen a closed tab in.
|
||||
* @param aIndex is the index of the tab to be restored (FIFO ordered).
|
||||
* @returns a reference to the reopened tab.
|
||||
*/
|
||||
nsIDOMNode undoCloseTab(in nsIDOMWindow aWindow, in unsigned long aIndex);
|
||||
|
||||
/**
|
||||
* @param aWindow is the browser window associated with the closed tab.
|
||||
* @param aIndex is the index of the closed tab to be removed (FIFO ordered).
|
||||
*/
|
||||
nsIDOMNode forgetClosedTab(in nsIDOMWindow aWindow, in unsigned long aIndex);
|
||||
|
||||
/**
|
||||
* Get the number of restore-able windows
|
||||
*/
|
||||
unsigned long getClosedWindowCount();
|
||||
|
||||
/**
|
||||
* Get closed windows data
|
||||
*
|
||||
* @returns a JSON string representing the list of closed windows.
|
||||
*/
|
||||
AString getClosedWindowData();
|
||||
|
||||
/**
|
||||
* @param aIndex is the index of the windows to be restored (FIFO ordered).
|
||||
* @returns the nsIDOMWindow object of the reopened window
|
||||
*/
|
||||
nsIDOMWindow undoCloseWindow(in unsigned long aIndex);
|
||||
|
||||
/**
|
||||
* @param aIndex is the index of the closed window to be removed (FIFO ordered).
|
||||
*
|
||||
* @throws NS_ERROR_INVALID_ARG
|
||||
* when aIndex does not map to a closed window
|
||||
*/
|
||||
nsIDOMNode forgetClosedWindow(in unsigned long aIndex);
|
||||
|
||||
/**
|
||||
* @param aWindow is the window to get the value for.
|
||||
* @param aKey is the value's name.
|
||||
*
|
||||
* @returns A string value or an empty string if none is set.
|
||||
*/
|
||||
AString getWindowValue(in nsIDOMWindow aWindow, in AString aKey);
|
||||
|
||||
/**
|
||||
* @param aWindow is the browser window to set the value for.
|
||||
* @param aKey is the value's name.
|
||||
* @param aStringValue is the value itself (use JSON.stringify/parse before setting JS objects).
|
||||
*/
|
||||
void setWindowValue(in nsIDOMWindow aWindow, in AString aKey, in AString aStringValue);
|
||||
|
||||
/**
|
||||
* @param aWindow is the browser window to get the value for.
|
||||
* @param aKey is the value's name.
|
||||
*/
|
||||
void deleteWindowValue(in nsIDOMWindow aWindow, in AString aKey);
|
||||
|
||||
/**
|
||||
* @param aTab is the tabbrowser tab to get the value for.
|
||||
* @param aKey is the value's name.
|
||||
*
|
||||
* @returns A string value or an empty string if none is set.
|
||||
*/
|
||||
AString getTabValue(in nsIDOMNode aTab, in AString aKey);
|
||||
|
||||
/**
|
||||
* @param aTab is the tabbrowser tab to set the value for.
|
||||
* @param aKey is the value's name.
|
||||
* @param aStringValue is the value itself (use JSON.stringify/parse before setting JS objects).
|
||||
*/
|
||||
void setTabValue(in nsIDOMNode aTab, in AString aKey, in AString aStringValue);
|
||||
|
||||
/**
|
||||
* @param aTab is the tabbrowser tab to get the value for.
|
||||
* @param aKey is the value's name.
|
||||
*/
|
||||
void deleteTabValue(in nsIDOMNode aTab, in AString aKey);
|
||||
|
||||
/**
|
||||
* @param aName is the name of the attribute to save/restore for all tabbrowser tabs.
|
||||
*/
|
||||
void persistTabAttribute(in AString aName);
|
||||
};
|
||||
291
application/palemoon/components/sessionstore/nsSessionStartup.js
Normal file
291
application/palemoon/components/sessionstore/nsSessionStartup.js
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Session Storage and Restoration
|
||||
*
|
||||
* Overview
|
||||
* This service reads user's session file at startup, and makes a determination
|
||||
* as to whether the session should be restored. It will restore the session
|
||||
* under the circumstances described below. If the auto-start Private Browsing
|
||||
* mode is active, however, the session is never restored.
|
||||
*
|
||||
* Crash Detection
|
||||
* The session file stores a session.state property, that
|
||||
* indicates whether the browser is currently running. When the browser shuts
|
||||
* down, the field is changed to "stopped". At startup, this field is read, and
|
||||
* if its value is "running", then it's assumed that the browser had previously
|
||||
* crashed, or at the very least that something bad happened, and that we should
|
||||
* restore the session.
|
||||
*
|
||||
* Forced Restarts
|
||||
* In the event that a restart is required due to application update or extension
|
||||
* installation, set the browser.sessionstore.resume_session_once pref to true,
|
||||
* and the session will be restored the next time the browser starts.
|
||||
*
|
||||
* Always Resume
|
||||
* This service will always resume the session if the integer pref
|
||||
* browser.startup.page is set to 3.
|
||||
*/
|
||||
|
||||
/* :::::::: Constants and Helpers ::::::::::::::: */
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
|
||||
"resource:///modules/sessionstore/_SessionFile.jsm");
|
||||
|
||||
const STATE_RUNNING_STR = "running";
|
||||
|
||||
function debug(aMsg) {
|
||||
aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n");
|
||||
Services.console.logStringMessage(aMsg);
|
||||
}
|
||||
|
||||
let gOnceInitializedDeferred = Promise.defer();
|
||||
|
||||
/* :::::::: The Service ::::::::::::::: */
|
||||
|
||||
function SessionStartup() {
|
||||
}
|
||||
|
||||
SessionStartup.prototype = {
|
||||
|
||||
// the state to restore at startup
|
||||
_initialState: null,
|
||||
_sessionType: Ci.nsISessionStartup.NO_SESSION,
|
||||
_initialized: false,
|
||||
|
||||
/* ........ Global Event Handlers .............. */
|
||||
|
||||
/**
|
||||
* Initialize the component
|
||||
*/
|
||||
init: function sss_init() {
|
||||
// do not need to initialize anything in auto-started private browsing sessions
|
||||
if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
|
||||
this._initialized = true;
|
||||
gOnceInitializedDeferred.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
_SessionFile.read().then(
|
||||
this._onSessionFileRead.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
// Wrap a string as a nsISupports
|
||||
_createSupportsString: function ssfi_createSupportsString(aData) {
|
||||
let string = Cc["@mozilla.org/supports-string;1"]
|
||||
.createInstance(Ci.nsISupportsString);
|
||||
string.data = aData;
|
||||
return string;
|
||||
},
|
||||
|
||||
_onSessionFileRead: function sss_onSessionFileRead(aStateString) {
|
||||
if (this._initialized) {
|
||||
// Initialization is complete, nothing else to do
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this._initialized = true;
|
||||
|
||||
// Let observers modify the state before it is used
|
||||
let supportsStateString = this._createSupportsString(aStateString);
|
||||
Services.obs.notifyObservers(supportsStateString, "sessionstore-state-read", "");
|
||||
aStateString = supportsStateString.data;
|
||||
|
||||
// No valid session found.
|
||||
if (!aStateString) {
|
||||
this._sessionType = Ci.nsISessionStartup.NO_SESSION;
|
||||
return;
|
||||
}
|
||||
|
||||
// parse the session state into a JS object
|
||||
// remove unneeded braces (added for compatibility with Firefox 2.0 and 3.0)
|
||||
if (aStateString.charAt(0) == '(')
|
||||
aStateString = aStateString.slice(1, -1);
|
||||
let corruptFile = false;
|
||||
try {
|
||||
this._initialState = JSON.parse(aStateString);
|
||||
}
|
||||
catch (ex) {
|
||||
debug("The session file contained un-parse-able JSON: " + ex);
|
||||
// This is not valid JSON, but this might still be valid JavaScript,
|
||||
// as used in FF2/FF3, so we need to eval.
|
||||
// evalInSandbox will throw if aStateString is not parse-able.
|
||||
try {
|
||||
var s = new Cu.Sandbox("about:blank", {sandboxName: 'nsSessionStartup'});
|
||||
this._initialState = Cu.evalInSandbox("(" + aStateString + ")", s);
|
||||
} catch(ex) {
|
||||
debug("The session file contained un-eval-able JSON: " + ex);
|
||||
corruptFile = true;
|
||||
}
|
||||
}
|
||||
let doResumeSessionOnce = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
|
||||
let doResumeSession = doResumeSessionOnce ||
|
||||
Services.prefs.getIntPref("browser.startup.page") == 3;
|
||||
|
||||
// If this is a normal restore then throw away any previous session
|
||||
if (!doResumeSessionOnce)
|
||||
delete this._initialState.lastSessionState;
|
||||
|
||||
let resumeFromCrash = Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash");
|
||||
let lastSessionCrashed =
|
||||
this._initialState && this._initialState.session &&
|
||||
this._initialState.session.state &&
|
||||
this._initialState.session.state == STATE_RUNNING_STR;
|
||||
|
||||
// set the startup type
|
||||
if (lastSessionCrashed && resumeFromCrash)
|
||||
this._sessionType = Ci.nsISessionStartup.RECOVER_SESSION;
|
||||
else if (!lastSessionCrashed && doResumeSession)
|
||||
this._sessionType = Ci.nsISessionStartup.RESUME_SESSION;
|
||||
else if (this._initialState)
|
||||
this._sessionType = Ci.nsISessionStartup.DEFER_SESSION;
|
||||
else
|
||||
this._initialState = null; // reset the state
|
||||
|
||||
Services.obs.addObserver(this, "sessionstore-windows-restored", true);
|
||||
|
||||
if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
|
||||
Services.obs.addObserver(this, "browser:purge-session-history", true);
|
||||
|
||||
} finally {
|
||||
// We're ready. Notify everyone else.
|
||||
Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
|
||||
gOnceInitializedDeferred.resolve();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle notifications
|
||||
*/
|
||||
observe: function sss_observe(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "app-startup":
|
||||
Services.obs.addObserver(this, "final-ui-startup", true);
|
||||
Services.obs.addObserver(this, "quit-application", true);
|
||||
break;
|
||||
case "final-ui-startup":
|
||||
Services.obs.removeObserver(this, "final-ui-startup");
|
||||
Services.obs.removeObserver(this, "quit-application");
|
||||
this.init();
|
||||
break;
|
||||
case "quit-application":
|
||||
// no reason for initializing at this point (cf. bug 409115)
|
||||
Services.obs.removeObserver(this, "final-ui-startup");
|
||||
Services.obs.removeObserver(this, "quit-application");
|
||||
if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
|
||||
Services.obs.removeObserver(this, "browser:purge-session-history");
|
||||
break;
|
||||
case "sessionstore-windows-restored":
|
||||
Services.obs.removeObserver(this, "sessionstore-windows-restored");
|
||||
// free _initialState after nsSessionStore is done with it
|
||||
this._initialState = null;
|
||||
break;
|
||||
case "browser:purge-session-history":
|
||||
Services.obs.removeObserver(this, "browser:purge-session-history");
|
||||
// reset all state on sanitization
|
||||
this._sessionType = Ci.nsISessionStartup.NO_SESSION;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/* ........ Public API ................*/
|
||||
|
||||
get onceInitialized() {
|
||||
return gOnceInitializedDeferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the session state as a jsval
|
||||
*/
|
||||
get state() {
|
||||
this._ensureInitialized();
|
||||
return this._initialState;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether there is a pending session restore and makes sure that
|
||||
* we're initialized before returning. If we're not yet this will read the
|
||||
* session file synchronously.
|
||||
* @returns bool
|
||||
*/
|
||||
doRestore: function sss_doRestore() {
|
||||
this._ensureInitialized();
|
||||
return this._willRestore();
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether there is a pending session restore.
|
||||
* @returns bool
|
||||
*/
|
||||
_willRestore: function () {
|
||||
return this._sessionType == Ci.nsISessionStartup.RECOVER_SESSION ||
|
||||
this._sessionType == Ci.nsISessionStartup.RESUME_SESSION;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns whether we will restore a session that ends up replacing the
|
||||
* homepage. The browser uses this to not start loading the homepage if
|
||||
* we're going to stop its load anyway shortly after.
|
||||
*
|
||||
* This is meant to be an optimization for the average case that loading the
|
||||
* session file finishes before we may want to start loading the default
|
||||
* homepage. Should this be called before the session file has been read it
|
||||
* will just return false.
|
||||
*
|
||||
* @returns bool
|
||||
*/
|
||||
get willOverrideHomepage() {
|
||||
if (this._initialState && this._willRestore()) {
|
||||
let windows = this._initialState.windows || null;
|
||||
// If there are valid windows with not only pinned tabs, signal that we
|
||||
// will override the default homepage by restoring a session.
|
||||
return windows && windows.some(w => w.tabs.some(t => !t.pinned));
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the type of pending session store, if any.
|
||||
*/
|
||||
get sessionType() {
|
||||
this._ensureInitialized();
|
||||
return this._sessionType;
|
||||
},
|
||||
|
||||
// Ensure that initialization is complete.
|
||||
// If initialization is not complete yet, fall back to a synchronous
|
||||
// initialization and kill ongoing asynchronous initialization
|
||||
_ensureInitialized: function sss__ensureInitialized() {
|
||||
try {
|
||||
if (this._initialized) {
|
||||
// Initialization is complete, nothing else to do
|
||||
return;
|
||||
}
|
||||
let contents = _SessionFile.syncRead();
|
||||
this._onSessionFileRead(contents);
|
||||
} catch(ex) {
|
||||
debug("ensureInitialized: could not read session " + ex + ", " + ex.stack);
|
||||
throw ex;
|
||||
}
|
||||
},
|
||||
|
||||
/* ........ QueryInterface .............. */
|
||||
QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference,
|
||||
Ci.nsISessionStartup]),
|
||||
classID: Components.ID("{ec7a6c20-e081-11da-8ad9-0800200c9a66}")
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStartup]);
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Session Storage and Restoration
|
||||
*
|
||||
* Overview
|
||||
* This service keeps track of a user's session, storing the various bits
|
||||
* required to return the browser to its current state. The relevant data is
|
||||
* stored in memory, and is periodically saved to disk in a file in the
|
||||
* profile directory. The service is started at first window load, in
|
||||
* delayedStartup, and will restore the session from the data received from
|
||||
* the nsSessionStartup service.
|
||||
*/
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/sessionstore/SessionStore.jsm");
|
||||
|
||||
function SessionStoreService() {}
|
||||
|
||||
// The SessionStore module's object is frozen. We need to modify our prototype
|
||||
// and add some properties so let's just copy the SessionStore object.
|
||||
Object.keys(SessionStore).forEach(function (aName) {
|
||||
let desc = Object.getOwnPropertyDescriptor(SessionStore, aName);
|
||||
Object.defineProperty(SessionStoreService.prototype, aName, desc);
|
||||
});
|
||||
|
||||
SessionStoreService.prototype.classID =
|
||||
Components.ID("{5280606b-2510-4fe0-97ef-9b5a22eafe6b}");
|
||||
SessionStoreService.prototype.QueryInterface =
|
||||
XPCOMUtils.generateQI([Ci.nsISessionStore]);
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStoreService]);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# WebappRT doesn't need these instructions, and they don't necessarily work
|
||||
# with it, but it does use a GRE directory that the GRE shares with Firefox,
|
||||
# so in order to prevent the instructions from being processed for WebappRT,
|
||||
# we need to restrict them to the applications that depend on them, i.e.:
|
||||
#
|
||||
# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
|
||||
# browser: {8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
|
||||
# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
|
||||
# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
|
||||
#
|
||||
# In theory we should do this for all these instructions, but in practice it is
|
||||
# sufficient to do it for the app-startup one, and the file is simpler that way.
|
||||
|
||||
component {5280606b-2510-4fe0-97ef-9b5a22eafe6b} nsSessionStore.js
|
||||
contract @mozilla.org/browser/sessionstore;1 {5280606b-2510-4fe0-97ef-9b5a22eafe6b}
|
||||
component {ec7a6c20-e081-11da-8ad9-0800200c9a66} nsSessionStartup.js
|
||||
contract @mozilla.org/browser/sessionstartup;1 {ec7a6c20-e081-11da-8ad9-0800200c9a66}
|
||||
category app-startup nsSessionStartup service,@mozilla.org/browser/sessionstartup;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66}
|
||||
Loading…
Add table
Add a link
Reference in a new issue