gecko/browser/components/sessionstore/TabState.jsm

269 lines
8.2 KiB
JavaScript

/* 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 = ["TabState"];
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/devtools/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
"resource:///modules/sessionstore/PrivacyFilter.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
"resource:///modules/sessionstore/TabStateCache.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
"resource:///modules/sessionstore/TabAttributes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
"resource:///modules/sessionstore/Utils.jsm");
/**
* Module that contains tab state collection methods.
*/
this.TabState = Object.freeze({
setSyncHandler: function (browser, handler) {
TabStateInternal.setSyncHandler(browser, handler);
},
update: function (browser, data) {
TabStateInternal.update(browser, data);
},
flush: function (browser) {
TabStateInternal.flush(browser);
},
flushAsync: function (browser) {
TabStateInternal.flushAsync(browser);
},
flushWindow: function (window) {
TabStateInternal.flushWindow(window);
},
collect: function (tab) {
return TabStateInternal.collect(tab);
},
clone: function (tab) {
return TabStateInternal.clone(tab);
}
});
let TabStateInternal = {
// A map (xul:browser -> handler) that maps a tab to the
// synchronous collection handler object for that tab.
// See SyncHandler in content-sessionStore.js.
_syncHandlers: new WeakMap(),
// A map (xul:browser -> int) that maps a browser to the
// last "SessionStore:update" message ID we received for it.
_latestMessageID: new WeakMap(),
/**
* Install the sync handler object from a given tab.
*/
setSyncHandler: function (browser, handler) {
this._syncHandlers.set(browser.permanentKey, handler);
this._latestMessageID.set(browser.permanentKey, 0);
},
/**
* Processes a data update sent by the content script.
*/
update: function (browser, {id, data}) {
// Only ever process messages that have an ID higher than the last one we
// saw. This ensures we don't use stale data that has already been received
// synchronously.
if (id > this._latestMessageID.get(browser.permanentKey)) {
this._latestMessageID.set(browser.permanentKey, id);
TabStateCache.update(browser, data);
}
},
/**
* Flushes all data currently queued in the given browser's content script.
*/
flush: function (browser) {
if (this._syncHandlers.has(browser.permanentKey)) {
let lastID = this._latestMessageID.get(browser.permanentKey);
this._syncHandlers.get(browser.permanentKey).flush(lastID);
}
},
/**
* DO NOT USE - DEBUGGING / TESTING ONLY
*
* This function is used to simulate certain situations where race conditions
* can occur by sending data shortly before flushing synchronously.
*/
flushAsync: function(browser) {
if (this._syncHandlers.has(browser.permanentKey)) {
this._syncHandlers.get(browser.permanentKey).flushAsync();
}
},
/**
* Flushes queued content script data for all browsers of a given window.
*/
flushWindow: function (window) {
for (let browser of window.gBrowser.browsers) {
this.flush(browser);
}
},
/**
* Collect data related to a single tab, synchronously.
*
* @param tab
* tabbrowser tab
*
* @returns {TabData} An object with the data for this tab. If the
* tab has not been invalidated since the last call to
* collect(aTab), the same object is returned.
*/
collect: function (tab) {
return this._collectBaseTabData(tab);
},
/**
* Collect data related to a single tab, including private data.
* Use with caution.
*
* @param tab
* tabbrowser tab
*
* @returns {object} An object with the data for this tab. This data is never
* cached, it will always be read from the tab and thus be
* up-to-date.
*/
clone: function (tab) {
return this._collectBaseTabData(tab, {includePrivateData: true});
},
/**
* Collects basic tab data for a given tab.
*
* @param tab
* tabbrowser tab
* @param options (object)
* {includePrivateData: true} to always include private data
*
* @returns {object} An object with the basic data for this tab.
*/
_collectBaseTabData: function (tab, options) {
let tabData = {entries: [], lastAccessed: tab.lastAccessed };
let browser = tab.linkedBrowser;
if (!browser || !browser.currentURI) {
// can happen when calling this function right after .addTab()
return tabData;
}
if (browser.__SS_data) {
// Use the data to be restored when the tab hasn't been
// completely loaded. We clone the data, since we're updating it
// here and the caller may update it further.
tabData = Utils.shallowCopy(browser.__SS_data);
if (tab.pinned)
tabData.pinned = true;
else
delete tabData.pinned;
tabData.hidden = tab.hidden;
// If __SS_extdata is set then we'll use that since it might be newer.
if (tab.__SS_extdata)
tabData.extData = tab.__SS_extdata;
// If it exists but is empty then a key was likely deleted. In that case just
// delete extData.
if (tabData.extData && !Object.keys(tabData.extData).length)
delete tabData.extData;
return tabData;
}
// If there is a userTypedValue set, then either the user has typed something
// in the URL bar, or a new tab was opened with a URI to load. userTypedClear
// is used to indicate whether the tab was in some sort of loading state with
// userTypedValue.
if (browser.userTypedValue) {
tabData.userTypedValue = browser.userTypedValue;
tabData.userTypedClear = browser.userTypedClear;
} else {
delete tabData.userTypedValue;
delete tabData.userTypedClear;
}
if (tab.pinned)
tabData.pinned = true;
else
delete tabData.pinned;
tabData.hidden = tab.hidden;
// Save tab attributes.
tabData.attributes = TabAttributes.get(tab);
// Store the tab icon.
let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
tabData.image = tabbrowser.getIcon(tab);
if (tab.__SS_extdata)
tabData.extData = tab.__SS_extdata;
else if (tabData.extData)
delete tabData.extData;
// Copy data from the tab state cache only if the tab has fully finished
// restoring. We don't want to overwrite data contained in __SS_data.
this._copyFromCache(tab, tabData, options);
return tabData;
},
/**
* Copy tab data for the given |tab| from the cache to |tabData|.
*
* @param tab (xul:tab)
* The tab belonging to the given |tabData| object.
* @param tabData (object)
* The tab data belonging to the given |tab|.
* @param options (object)
* {includePrivateData: true} to always include private data
*/
_copyFromCache: function (tab, tabData, options = {}) {
let data = TabStateCache.get(tab.linkedBrowser);
if (!data) {
return;
}
// The caller may explicitly request to omit privacy checks.
let includePrivateData = options && options.includePrivateData;
for (let key of Object.keys(data)) {
let value = data[key];
// Filter sensitive data according to the current privacy level.
if (!includePrivateData) {
if (key === "storage") {
value = PrivacyFilter.filterSessionStorageData(value, tab.pinned);
} else if (key === "formdata") {
value = PrivacyFilter.filterFormData(value, tab.pinned);
}
}
if (key === "history") {
tabData.entries = value.entries;
if (value.hasOwnProperty("index")) {
tabData.index = value.index;
}
} else if (value) {
tabData[key] = value;
}
}
}
};