diff options
Diffstat (limited to 'browser/components/sessionstore/TabState.sys.mjs')
-rw-r--r-- | browser/components/sessionstore/TabState.sys.mjs | 204 |
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; + } + } + }, +}; |