fune/browser/components/sessionstore/TabStateCache.jsm
Andreas Farre f722be009c Bug 1572084 - Part 2: Make Session Store data collection work with fission. r=nika
Instead of collecting data from the entire tree of documents, we
collect data per document. The collected data is sent to the
corresponding parent window context and is applied incrementally to
the tab state cache.

Differential Revision: https://phabricator.services.mozilla.com/D107814
2021-03-26 08:59:33 +00:00

262 lines
7.6 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";
var EXPORTED_SYMBOLS = ["TabStateCache"];
/**
* 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.
*/
var TabStateCache = Object.freeze({
/**
* Retrieves cached data for a given |tab| or associated |browser|.
*
* @param browserOrTab (xul:tab or xul:browser)
* The tab or browser to retrieve cached data for.
* @return (object)
* The cached data stored for the given |tab|
* or associated |browser|.
*/
get(browserOrTab) {
return TabStateCacheInternal.get(browserOrTab);
},
/**
* Updates cached data for a given |tab| or associated |browser|.
*
* @param browserOrTab (xul:tab or xul:browser)
* 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(browserOrTab, newData) {
TabStateCacheInternal.update(browserOrTab, newData);
},
});
var TabStateCacheInternal = {
_data: new WeakMap(),
/**
* Retrieves cached data for a given |tab| or associated |browser|.
*
* @param browserOrTab (xul:tab or xul:browser)
* The tab or browser to retrieve cached data for.
* @return (object)
* The cached data stored for the given |tab|
* or associated |browser|.
*/
get(browserOrTab) {
return this._data.get(browserOrTab.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];
}
}
},
/**
* Helper function used by update (see below). To be fission compatible
* we need to be able to update scroll and formdata per entry in the
* cache. This is done by looking up the desired position and applying
* the update for that node only.
*
* @param data (object)
* The cached data where we want to update the changes.
* @param path (object)
* The path to the node to update specified by a list of indices
* to follow from the root downwards.
* @param includeChildren (booelan)
* Determines if the children of the changed node should be kept
* or not.
* @param change (object)
* Object containing the optional formdata and optional scroll
* position to be updated as well as information if the node
* should keep the data for its children.
*/
updatePartialWindowStateChange(data, path, includeChildren, change) {
if (!path.length) {
for (let key of Object.keys(change)) {
let children = includeChildren ? data[key]?.children : null;
if (!Object.keys(change[key]).length) {
data[key] = null;
} else {
data[key] = change[key];
}
if (children) {
data[key] = { ...data[key], children };
}
}
return data;
}
let index = path.pop();
let scroll = data?.scroll?.children?.[index];
let formdata = data?.formdata?.children?.[index];
change = this.updatePartialWindowStateChange(
{ scroll, formdata },
path,
includeChildren,
change
);
for (let key of Object.keys(change)) {
let value = change[key];
let children = data[key]?.children;
if (children) {
if (value) {
children[index] = value;
} else {
delete children[index];
}
if (!children.some(e => e)) {
data[key] = null;
}
} else if (value) {
children = new Array(index + 1);
children[index] = value;
data[key] = { ...data[key], children };
}
}
return data;
},
/**
* Updates cached data for a given |tab| or associated |browser|.
*
* @param browserOrTab (xul:tab or xul:browser)
* 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(browserOrTab, newData) {
let data = this._data.get(browserOrTab.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;
}
if (key == "windowstatechange") {
let { path, hasChildren, ...change } = newData.windowstatechange;
this.updatePartialWindowStateChange(data, path, hasChildren, change);
for (key of Object.keys(change)) {
let value = data[key];
if (value === null) {
delete data[key];
} else {
data[key] = value;
}
}
continue;
}
let value = newData[key];
if (value === null) {
delete data[key];
} else {
data[key] = value;
}
}
this._data.set(browserOrTab.permanentKey, data);
},
};