summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/watcher/SessionDataHelpers.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/watcher/SessionDataHelpers.sys.mjs')
-rw-r--r--devtools/server/actors/watcher/SessionDataHelpers.sys.mjs218
1 files changed, 218 insertions, 0 deletions
diff --git a/devtools/server/actors/watcher/SessionDataHelpers.sys.mjs b/devtools/server/actors/watcher/SessionDataHelpers.sys.mjs
new file mode 100644
index 0000000000..def31b77a8
--- /dev/null
+++ b/devtools/server/actors/watcher/SessionDataHelpers.sys.mjs
@@ -0,0 +1,218 @@
+/* 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:
+ "resource://devtools/shared/validate-breakpoint.sys.mjs",
+ },
+ { global: "contextual" }
+);
+
+ChromeUtils.defineLazyGetter(lazy, "validateEventBreakpoint", () => {
+ const { loader } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs",
+ { global: "contextual" }
+ );
+ 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;
+}
+
+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;
+ },
+};