Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
/**
* Helper module alongside ParentProcessWatcherRegistry, which focus on updating the "sessionData" object.
* This object is shared across processes and threads and have to be maintained in all these runtimes.
*/
const lazy = {};
ChromeUtils.defineESModuleGetters(
lazy,
{
validateBreakpointLocation:
},
{ global: "contextual" }
);
ChromeUtils.defineLazyGetter(lazy, "validateEventBreakpoint", () => {
const { loader } = ChromeUtils.importESModule(
{ global: "contextual" }
);
return loader.require(
).validateEventBreakpoint;
});
// List of all arrays stored in `sessionData`, which are replicated across processes and threads
const SUPPORTED_DATA = {
BLACKBOXING: "blackboxing",
BREAKPOINTS: "breakpoints",
BROWSER_ELEMENT_HOST: "browser-element-host",
XHR_BREAKPOINTS: "xhr-breakpoints",
EVENT_BREAKPOINTS: "event-breakpoints",
RESOURCES: "resources",
TARGET_CONFIGURATION: "target-configuration",
THREAD_CONFIGURATION: "thread-configuration",
TARGETS: "targets",
};
// Optional function, if data isn't a primitive data type in order to produce a key
// for the given data entry
const DATA_KEY_FUNCTION = {
[SUPPORTED_DATA.BLACKBOXING]({ url, range }) {
return (
url +
(range
? `:${range.start.line}:${range.start.column}-${range.end.line}:${range.end.column}`
: "")
);
},
[SUPPORTED_DATA.BREAKPOINTS]({ location }) {
lazy.validateBreakpointLocation(location);
const { sourceUrl, sourceId, line, column } = location;
return `${sourceUrl}:${sourceId}:${line}:${column}`;
},
[SUPPORTED_DATA.TARGET_CONFIGURATION]({ key }) {
// Configuration data entries are { key, value } objects, `key` can be used
// as the unique identifier for the entry.
return key;
},
[SUPPORTED_DATA.THREAD_CONFIGURATION]({ key }) {
// See target configuration comment
return key;
},
[SUPPORTED_DATA.XHR_BREAKPOINTS]({ path, method }) {
if (typeof path != "string") {
throw new Error(
`XHR Breakpoints expect to have path string, got ${typeof path} instead.`
);
}
if (typeof method != "string") {
throw new Error(
`XHR Breakpoints expect to have method string, got ${typeof method} instead.`
);
}
return `${path}:${method}`;
},
[SUPPORTED_DATA.EVENT_BREAKPOINTS](id) {
if (typeof id != "string") {
throw new Error(
`Event Breakpoints expect the id to be a string , got ${typeof id} instead.`
);
}
if (!lazy.validateEventBreakpoint(id)) {
throw new Error(
`The id string should be a valid event breakpoint id, ${id} is not.`
);
}
return id;
},
};
// Optional validation method to assert the shape of each session data entry
const DATA_VALIDATION_FUNCTION = {
[SUPPORTED_DATA.BREAKPOINTS]({ location }) {
lazy.validateBreakpointLocation(location);
},
[SUPPORTED_DATA.XHR_BREAKPOINTS]({ path, method }) {
if (typeof path != "string") {
throw new Error(
`XHR Breakpoints expect to have path string, got ${typeof path} instead.`
);
}
if (typeof method != "string") {
throw new Error(
`XHR Breakpoints expect to have method string, got ${typeof method} instead.`
);
}
},
[SUPPORTED_DATA.EVENT_BREAKPOINTS](id) {
if (typeof id != "string") {
throw new Error(
`Event Breakpoints expect the id to be a string , got ${typeof id} instead.`
);
}
if (!lazy.validateEventBreakpoint(id)) {
throw new Error(
`The id string should be a valid event breakpoint id, ${id} is not.`
);
}
},
};
function idFunction(v) {
if (typeof v != "string") {
throw new Error(
`Expect data entry values to be string, or be using custom data key functions. Got ${typeof v} type instead.`
);
}
return v;
}
export const SessionDataHelpers = {
SUPPORTED_DATA,
/**
* Add new values to the shared "sessionData" object.
*
* @param Object sessionData
* The data object to update.
* @param string type
* The type of data to be added
* @param Array<Object> entries
* The values to be added to this type of data
* @param String updateType
* "add" will only add the new entries in the existing data set.
* "set" will update the data set with the new entries.
*/
addOrSetSessionDataEntry(sessionData, type, entries, updateType) {
const validationFunction = DATA_VALIDATION_FUNCTION[type];
if (validationFunction) {
entries.forEach(validationFunction);
}
// When we are replacing the whole entries, things are significantly simplier
if (updateType == "set") {
sessionData[type] = entries;
return;
}
if (!sessionData[type]) {
sessionData[type] = [];
}
const toBeAdded = [];
const keyFunction = DATA_KEY_FUNCTION[type] || idFunction;
for (const entry of entries) {
const existingIndex = sessionData[type].findIndex(existingEntry => {
return keyFunction(existingEntry) === keyFunction(entry);
});
if (existingIndex === -1) {
// New entry.
toBeAdded.push(entry);
} else {
// Existing entry, update the value. This is relevant if the data-entry
// is not a primitive data-type, and the value can change for the same
// key.
sessionData[type][existingIndex] = entry;
}
}
sessionData[type].push(...toBeAdded);
},
/**
* Remove values from the shared "sessionData" object.
*
* @param Object sessionData
* The data object to update.
* @param string type
* The type of data to be remove
* @param Array<Object> entries
* The values to be removed from this type of data
* @return Boolean
* True, if at least one entries existed and has been removed.
* False, if none of the entries existed and none has been removed.
*/
removeSessionDataEntry(sessionData, type, entries) {
let includesAtLeastOne = false;
const keyFunction = DATA_KEY_FUNCTION[type] || idFunction;
for (const entry of entries) {
const idx = sessionData[type]
? sessionData[type].findIndex(existingEntry => {
return keyFunction(existingEntry) === keyFunction(entry);
})
: -1;
if (idx !== -1) {
sessionData[type].splice(idx, 1);
includesAtLeastOne = true;
}
}
if (!includesAtLeastOne) {
return false;
}
return true;
},
};