diff options
Diffstat (limited to '')
-rw-r--r-- | devtools/server/actors/watcher/SessionDataHelpers.jsm | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/devtools/server/actors/watcher/SessionDataHelpers.jsm b/devtools/server/actors/watcher/SessionDataHelpers.jsm new file mode 100644 index 0000000000..c70df1744f --- /dev/null +++ b/devtools/server/actors/watcher/SessionDataHelpers.jsm @@ -0,0 +1,244 @@ +/* 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"; + +/** + * Helper module alongside WatcherRegistry, which focus on updating the "sessionData" object. + * This object is shared across processes and threads and have to be maintained in all these runtimes. + */ + +var EXPORTED_SYMBOLS = ["SessionDataHelpers"]; + +const lazy = {}; + +if (typeof module == "object") { + // Allow this JSM to also be loaded as a CommonJS module + // Because this module is used from the worker thread, + // (via target-actor-mixin), and workers can't load JSMs via ChromeUtils.import. + loader.lazyRequireGetter( + lazy, + "validateBreakpointLocation", + "resource://devtools/shared/validate-breakpoint.jsm", + true + ); + + loader.lazyRequireGetter( + lazy, + "validateEventBreakpoint", + "resource://devtools/server/actors/utils/event-breakpoints.js", + true + ); +} else { + ChromeUtils.defineLazyGetter(lazy, "validateBreakpointLocation", () => { + return ChromeUtils.import( + "resource://devtools/shared/validate-breakpoint.jsm" + ).validateBreakpointLocation; + }); + ChromeUtils.defineLazyGetter(lazy, "validateEventBreakpoint", () => { + const { loader } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" + ); + return loader.require( + "resource://devtools/server/actors/utils/event-breakpoints.js" + ).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; +} + +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; + }, +}; + +// Allow this JSM to also be loaded as a CommonJS module +// Because this module is used from the worker thread, +// (via target-actor-mixin), and workers can't load JSMs. +if (typeof module == "object") { + module.exports.SessionDataHelpers = SessionDataHelpers; +} |