michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["SessionStorage"]; michael@0: michael@0: const Cu = Components.utils; michael@0: const Ci = Components.interfaces; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "console", michael@0: "resource://gre/modules/devtools/Console.jsm"); michael@0: michael@0: // Returns the principal for a given |frame| contained in a given |docShell|. michael@0: function getPrincipalForFrame(docShell, frame) { michael@0: let ssm = Services.scriptSecurityManager; michael@0: let uri = frame.document.documentURIObject; michael@0: return ssm.getDocShellCodebasePrincipal(uri, docShell); michael@0: } michael@0: michael@0: this.SessionStorage = Object.freeze({ michael@0: /** michael@0: * Updates all sessionStorage "super cookies" michael@0: * @param docShell michael@0: * That tab's docshell (containing the sessionStorage) michael@0: * @param frameTree michael@0: * The docShell's FrameTree instance. michael@0: * @return Returns a nested object that will have hosts as keys and per-host michael@0: * session storage data as values. For example: michael@0: * {"example.com": {"key": "value", "my_number": 123}} michael@0: */ michael@0: collect: function (docShell, frameTree) { michael@0: return SessionStorageInternal.collect(docShell, frameTree); michael@0: }, michael@0: michael@0: /** michael@0: * Restores all sessionStorage "super cookies". michael@0: * @param aDocShell michael@0: * A tab's docshell (containing the sessionStorage) michael@0: * @param aStorageData michael@0: * A nested object with storage data to be restored that has hosts as michael@0: * keys and per-host session storage data as values. For example: michael@0: * {"example.com": {"key": "value", "my_number": 123}} michael@0: */ michael@0: restore: function (aDocShell, aStorageData) { michael@0: SessionStorageInternal.restore(aDocShell, aStorageData); michael@0: } michael@0: }); michael@0: michael@0: let SessionStorageInternal = { michael@0: /** michael@0: * Reads all session storage data from the given docShell. michael@0: * @param docShell michael@0: * A tab's docshell (containing the sessionStorage) michael@0: * @param frameTree michael@0: * The docShell's FrameTree instance. michael@0: * @return Returns a nested object that will have hosts as keys and per-host michael@0: * session storage data as values. For example: michael@0: * {"example.com": {"key": "value", "my_number": 123}} michael@0: */ michael@0: collect: function (docShell, frameTree) { michael@0: let data = {}; michael@0: let visitedOrigins = new Set(); michael@0: michael@0: frameTree.forEach(frame => { michael@0: let principal = getPrincipalForFrame(docShell, frame); michael@0: if (!principal) { michael@0: return; michael@0: } michael@0: michael@0: // Get the root domain of the current history entry michael@0: // and use that as a key for the per-host storage data. michael@0: let origin = principal.jarPrefix + principal.origin; michael@0: if (visitedOrigins.has(origin)) { michael@0: // Don't read a host twice. michael@0: return; michael@0: } michael@0: michael@0: // Mark the current origin as visited. michael@0: visitedOrigins.add(origin); michael@0: michael@0: let originData = this._readEntry(principal, docShell); michael@0: if (Object.keys(originData).length) { michael@0: data[origin] = originData; michael@0: } michael@0: }); michael@0: michael@0: return Object.keys(data).length ? data : null; michael@0: }, michael@0: michael@0: /** michael@0: * Writes session storage data to the given tab. michael@0: * @param aDocShell michael@0: * A tab's docshell (containing the sessionStorage) michael@0: * @param aStorageData michael@0: * A nested object with storage data to be restored that has hosts as michael@0: * keys and per-host session storage data as values. For example: michael@0: * {"example.com": {"key": "value", "my_number": 123}} michael@0: */ michael@0: restore: function (aDocShell, aStorageData) { michael@0: for (let host of Object.keys(aStorageData)) { michael@0: let data = aStorageData[host]; michael@0: let uri = Services.io.newURI(host, null, null); michael@0: let principal = Services.scriptSecurityManager.getDocShellCodebasePrincipal(uri, aDocShell); michael@0: let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager); michael@0: michael@0: // There is no need to pass documentURI, it's only used to fill documentURI property of michael@0: // domstorage event, which in this case has no consumer. Prevention of events in case michael@0: // of missing documentURI will be solved in a followup bug to bug 600307. michael@0: // TODO: We should call createStorageForFirstParty() here. See comment michael@0: // inside _readEntry() for details. michael@0: let storage = storageManager.createStorage(principal, "", aDocShell.usePrivateBrowsing); michael@0: michael@0: for (let key of Object.keys(data)) { michael@0: try { michael@0: storage.setItem(key, data[key]); michael@0: } catch (e) { michael@0: // throws e.g. for URIs that can't have sessionStorage michael@0: console.error(e); michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Reads an entry in the session storage data contained in a tab's history. michael@0: * @param aURI michael@0: * That history entry uri michael@0: * @param aDocShell michael@0: * A tab's docshell (containing the sessionStorage) michael@0: */ michael@0: _readEntry: function (aPrincipal, aDocShell) { michael@0: let hostData = {}; michael@0: let storage; michael@0: michael@0: try { michael@0: let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager); michael@0: // TODO: We should call getStorageForFirstParty() here, but we need an michael@0: // nsIDocument to call thirdPartyUtil.getFirstPartyURI(), and michael@0: // unfortunately nsIDocument is not exposed to JavaScript. michael@0: // However, this code is not called in TBB because the michael@0: // browser.sessionstore.privacy_level pref. is set to 2. michael@0: storage = storageManager.getStorage(aPrincipal); michael@0: } catch (e) { michael@0: // sessionStorage might throw if it's turned off, see bug 458954 michael@0: } michael@0: michael@0: if (storage && storage.length) { michael@0: for (let i = 0; i < storage.length; i++) { michael@0: try { michael@0: let key = storage.key(i); michael@0: hostData[key] = storage.getItem(key); michael@0: } catch (e) { michael@0: // This currently throws for secured items (cf. bug 442048). michael@0: } michael@0: } michael@0: } michael@0: michael@0: return hostData; michael@0: } michael@0: };