/* 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/. */ /** * A cache for tabs data. * * This cache implements a weak map from tabs (as XUL elements) * to tab data (as objects). * * Note that we should never cache private data, as: * - that data is used very seldom by SessionStore; * - caching private data in addition to public data is memory consuming. */ export var TabStateCache = Object.freeze({ /** * Retrieves cached data for a given |tab| or associated |browser|. * * @param permanentKey (object) * The tab or browser to retrieve cached data for. * @return (object) * The cached data stored for the given |tab| * or associated |browser|. */ get(permanentKey) { return TabStateCacheInternal.get(permanentKey); }, /** * Updates cached data for a given |tab| or associated |browser|. * * @param permanentKey (object) * The tab or browser belonging to the given tab data. * @param newData (object) * The new data to be stored for the given |tab| * or associated |browser|. */ update(permanentKey, newData) { TabStateCacheInternal.update(permanentKey, newData); }, }); var TabStateCacheInternal = { _data: new WeakMap(), /** * Retrieves cached data for a given |tab| or associated |browser|. * * @param permanentKey (object) * The tab or browser to retrieve cached data for. * @return (object) * The cached data stored for the given |tab| * or associated |browser|. */ get(permanentKey) { return this._data.get(permanentKey); }, /** * Helper function used by update (see below). For message size * optimization sometimes we don't update the whole session storage * only the values that have been changed. * * @param data (object) * The cached data where we want to update the changes. * @param change (object) * The actual changed values per domain. */ updatePartialStorageChange(data, change) { if (!data.storage) { data.storage = {}; } let storage = data.storage; for (let domain of Object.keys(change)) { if (!change[domain]) { // We were sent null in place of the change object, which means // we should delete session storage entirely for this domain. delete storage[domain]; } else { for (let key of Object.keys(change[domain])) { let value = change[domain][key]; if (value === null) { if (storage[domain] && storage[domain][key]) { delete storage[domain][key]; } } else { if (!storage[domain]) { storage[domain] = {}; } storage[domain][key] = value; } } } } }, /** * Helper function used by update (see below). For message size * optimization sometimes we don't update the whole browser history * only the current index and the tail of the history from a certain * index (specified by change.fromIdx) * * @param data (object) * The cached data where we want to update the changes. * @param change (object) * Object containing the tail of the history array, and * some additional metadata. */ updatePartialHistoryChange(data, change) { const kLastIndex = Number.MAX_SAFE_INTEGER - 1; if (!data.history) { data.history = { entries: [] }; } let history = data.history; let toIdx = history.entries.length; if ("toIdx" in change) { toIdx = Math.min(toIdx, change.toIdx + 1); } for (let key of Object.keys(change)) { if (key == "entries") { if (change.fromIdx != kLastIndex) { let start = change.fromIdx + 1; history.entries.splice.apply( history.entries, [start, toIdx - start].concat(change.entries) ); } } else if (key != "fromIdx" && key != "toIdx") { history[key] = change[key]; } } }, /** * Updates cached data for a given |tab| or associated |browser|. * * @param permanentKey (object) * The tab or browser belonging to the given tab data. * @param newData (object) * The new data to be stored for the given |tab| * or associated |browser|. */ update(permanentKey, newData) { let data = this._data.get(permanentKey) || {}; for (let key of Object.keys(newData)) { if (key == "storagechange") { this.updatePartialStorageChange(data, newData.storagechange); continue; } if (key == "historychange") { this.updatePartialHistoryChange(data, newData.historychange); continue; } let value = newData[key]; if (value === null) { delete data[key]; } else { data[key] = value; } } this._data.set(permanentKey, data); }, };