summaryrefslogtreecommitdiffstats
path: root/browser/components/sessionstore/TabState.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/sessionstore/TabState.sys.mjs')
-rw-r--r--browser/components/sessionstore/TabState.sys.mjs204
1 files changed, 204 insertions, 0 deletions
diff --git a/browser/components/sessionstore/TabState.sys.mjs b/browser/components/sessionstore/TabState.sys.mjs
new file mode 100644
index 0000000000..26f5671c84
--- /dev/null
+++ b/browser/components/sessionstore/TabState.sys.mjs
@@ -0,0 +1,204 @@
+/* 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 lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.sys.mjs",
+ TabAttributes: "resource:///modules/sessionstore/TabAttributes.sys.mjs",
+ TabStateCache: "resource:///modules/sessionstore/TabStateCache.sys.mjs",
+});
+
+/**
+ * Module that contains tab state collection methods.
+ */
+export var TabState = Object.freeze({
+ update(permanentKey, data) {
+ TabStateInternal.update(permanentKey, data);
+ },
+
+ collect(tab, extData) {
+ return TabStateInternal.collect(tab, extData);
+ },
+
+ clone(tab, extData) {
+ return TabStateInternal.clone(tab, extData);
+ },
+
+ copyFromCache(permanentKey, tabData, options) {
+ TabStateInternal.copyFromCache(permanentKey, tabData, options);
+ },
+});
+
+var TabStateInternal = {
+ /**
+ * Processes a data update sent by the content script.
+ */
+ update(permanentKey, { data }) {
+ lazy.TabStateCache.update(permanentKey, data);
+ },
+
+ /**
+ * Collect data related to a single tab, synchronously.
+ *
+ * @param tab
+ * tabbrowser tab
+ * @param [extData]
+ * optional dictionary object, containing custom tab values.
+ *
+ * @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(tab, extData) {
+ return this._collectBaseTabData(tab, { extData });
+ },
+
+ /**
+ * Collect data related to a single tab, including private data.
+ * Use with caution.
+ *
+ * @param tab
+ * tabbrowser tab
+ * @param [extData]
+ * optional dictionary object, containing custom tab values.
+ *
+ * @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(tab, extData) {
+ return this._collectBaseTabData(tab, { extData, includePrivateData: true });
+ },
+
+ /**
+ * Collects basic tab data for a given tab.
+ *
+ * @param tab
+ * tabbrowser tab
+ * @param options (object)
+ * {extData: object} optional dictionary object, containing custom tab values
+ * {includePrivateData: true} to always include private data
+ *
+ * @returns {object} An object with the basic data for this tab.
+ */
+ _collectBaseTabData(tab, options) {
+ let tabData = { entries: [], lastAccessed: tab.lastAccessed };
+ let browser = tab.linkedBrowser;
+
+ if (tab.pinned) {
+ tabData.pinned = true;
+ }
+
+ tabData.hidden = tab.hidden;
+
+ if (browser.audioMuted) {
+ tabData.muted = true;
+ tabData.muteReason = tab.muteReason;
+ }
+
+ tabData.searchMode = tab.ownerGlobal.gURLBar.getSearchMode(browser, true);
+
+ tabData.userContextId = tab.userContextId || 0;
+
+ // Save tab attributes.
+ tabData.attributes = lazy.TabAttributes.get(tab);
+
+ if (options.extData) {
+ tabData.extData = options.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(browser.permanentKey, tabData, options);
+
+ // After copyFromCache() was called we check for properties that are kept
+ // in the cache only while the tab is pending or restoring. Once that
+ // happened those properties will be removed from the cache and will
+ // be read from the tab/browser every time we collect data.
+
+ // Store the tab icon.
+ if (!("image" in tabData)) {
+ let tabbrowser = tab.ownerGlobal.gBrowser;
+ tabData.image = tabbrowser.getIcon(tab);
+ }
+
+ // 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.
+ // If so, we also track whether we were still in the process of loading something.
+ if (!("userTypedValue" in tabData) && browser.userTypedValue) {
+ tabData.userTypedValue = browser.userTypedValue;
+ // We always used to keep track of the loading state as an integer, where
+ // '0' indicated the user had typed since the last load (or no load was
+ // ongoing), and any positive value indicated we had started a load since
+ // the last time the user typed in the URL bar. Mimic this to keep the
+ // session store representation in sync, even though we now represent this
+ // more explicitly:
+ tabData.userTypedClear = browser.didStartLoadSinceLastUserTyping()
+ ? 1
+ : 0;
+ }
+
+ return tabData;
+ },
+
+ /**
+ * Copy data for the given |browser| from the cache to |tabData|.
+ *
+ * @param permanentKey (object)
+ * The browser 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(permanentKey, tabData, options = {}) {
+ let data = lazy.TabStateCache.get(permanentKey);
+ 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 = lazy.PrivacyFilter.filterSessionStorageData(value);
+ } else if (key === "formdata") {
+ value = lazy.PrivacyFilter.filterFormData(value);
+ }
+ }
+
+ if (key === "history") {
+ // Make a shallow copy of the entries array. We (currently) don't update
+ // entries in place, so we don't have to worry about performing a deep
+ // copy.
+ tabData.entries = [...value.entries];
+
+ if (value.hasOwnProperty("index")) {
+ tabData.index = value.index;
+ }
+
+ if (value.hasOwnProperty("requestedIndex")) {
+ tabData.requestedIndex = value.requestedIndex;
+ }
+ } else if (!value && (key == "scroll" || key == "formdata")) {
+ // [Bug 1554512]
+
+ // If scroll or formdata null it indicates that the update to
+ // be performed is to remove them, and not copy a null
+ // value. Scroll will be null when the position is at the top
+ // of the document, formdata will be null when there is only
+ // default data.
+ delete tabData[key];
+ } else {
+ tabData[key] = value;
+ }
+ }
+ },
+};