SessionDataHelpers.sys.mjs (6990B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * Helper module alongside ParentProcessWatcherRegistry, which focus on updating the "sessionData" object. 7 * This object is shared across processes and threads and have to be maintained in all these runtimes. 8 */ 9 10 const lazy = {}; 11 ChromeUtils.defineESModuleGetters( 12 lazy, 13 { 14 validateBreakpointLocation: 15 "resource://devtools/shared/validate-breakpoint.sys.mjs", 16 }, 17 { global: "contextual" } 18 ); 19 20 ChromeUtils.defineLazyGetter(lazy, "validateEventBreakpoint", () => { 21 const { loader } = ChromeUtils.importESModule( 22 "resource://devtools/shared/loader/Loader.sys.mjs", 23 { global: "contextual" } 24 ); 25 return loader.require( 26 "resource://devtools/server/actors/utils/event-breakpoints.js" 27 ).validateEventBreakpoint; 28 }); 29 30 // List of all arrays stored in `sessionData`, which are replicated across processes and threads 31 const SUPPORTED_DATA = { 32 BLACKBOXING: "blackboxing", 33 BREAKPOINTS: "breakpoints", 34 BROWSER_ELEMENT_HOST: "browser-element-host", 35 XHR_BREAKPOINTS: "xhr-breakpoints", 36 EVENT_BREAKPOINTS: "event-breakpoints", 37 RESOURCES: "resources", 38 TARGET_CONFIGURATION: "target-configuration", 39 THREAD_CONFIGURATION: "thread-configuration", 40 TARGETS: "targets", 41 }; 42 43 // Optional function, if data isn't a primitive data type in order to produce a key 44 // for the given data entry 45 const DATA_KEY_FUNCTION = { 46 [SUPPORTED_DATA.BLACKBOXING]({ url, range }) { 47 return ( 48 url + 49 (range 50 ? `:${range.start.line}:${range.start.column}-${range.end.line}:${range.end.column}` 51 : "") 52 ); 53 }, 54 [SUPPORTED_DATA.BREAKPOINTS]({ location }) { 55 lazy.validateBreakpointLocation(location); 56 const { sourceUrl, sourceId, line, column } = location; 57 return `${sourceUrl}:${sourceId}:${line}:${column}`; 58 }, 59 [SUPPORTED_DATA.TARGET_CONFIGURATION]({ key }) { 60 // Configuration data entries are { key, value } objects, `key` can be used 61 // as the unique identifier for the entry. 62 return key; 63 }, 64 [SUPPORTED_DATA.THREAD_CONFIGURATION]({ key }) { 65 // See target configuration comment 66 return key; 67 }, 68 [SUPPORTED_DATA.XHR_BREAKPOINTS]({ path, method }) { 69 if (typeof path != "string") { 70 throw new Error( 71 `XHR Breakpoints expect to have path string, got ${typeof path} instead.` 72 ); 73 } 74 if (typeof method != "string") { 75 throw new Error( 76 `XHR Breakpoints expect to have method string, got ${typeof method} instead.` 77 ); 78 } 79 return `${path}:${method}`; 80 }, 81 [SUPPORTED_DATA.EVENT_BREAKPOINTS](id) { 82 if (typeof id != "string") { 83 throw new Error( 84 `Event Breakpoints expect the id to be a string , got ${typeof id} instead.` 85 ); 86 } 87 if (!lazy.validateEventBreakpoint(id)) { 88 throw new Error( 89 `The id string should be a valid event breakpoint id, ${id} is not.` 90 ); 91 } 92 return id; 93 }, 94 }; 95 // Optional validation method to assert the shape of each session data entry 96 const DATA_VALIDATION_FUNCTION = { 97 [SUPPORTED_DATA.BREAKPOINTS]({ location }) { 98 lazy.validateBreakpointLocation(location); 99 }, 100 [SUPPORTED_DATA.XHR_BREAKPOINTS]({ path, method }) { 101 if (typeof path != "string") { 102 throw new Error( 103 `XHR Breakpoints expect to have path string, got ${typeof path} instead.` 104 ); 105 } 106 if (typeof method != "string") { 107 throw new Error( 108 `XHR Breakpoints expect to have method string, got ${typeof method} instead.` 109 ); 110 } 111 }, 112 [SUPPORTED_DATA.EVENT_BREAKPOINTS](id) { 113 if (typeof id != "string") { 114 throw new Error( 115 `Event Breakpoints expect the id to be a string , got ${typeof id} instead.` 116 ); 117 } 118 if (!lazy.validateEventBreakpoint(id)) { 119 throw new Error( 120 `The id string should be a valid event breakpoint id, ${id} is not.` 121 ); 122 } 123 }, 124 }; 125 126 function idFunction(v) { 127 if (typeof v != "string") { 128 throw new Error( 129 `Expect data entry values to be string, or be using custom data key functions. Got ${typeof v} type instead.` 130 ); 131 } 132 return v; 133 } 134 135 export const SessionDataHelpers = { 136 SUPPORTED_DATA, 137 138 /** 139 * Add new values to the shared "sessionData" object. 140 * 141 * @param Object sessionData 142 * The data object to update. 143 * @param string type 144 * The type of data to be added 145 * @param Array<Object> entries 146 * The values to be added to this type of data 147 * @param String updateType 148 * "add" will only add the new entries in the existing data set. 149 * "set" will update the data set with the new entries. 150 */ 151 addOrSetSessionDataEntry(sessionData, type, entries, updateType) { 152 const validationFunction = DATA_VALIDATION_FUNCTION[type]; 153 if (validationFunction) { 154 entries.forEach(validationFunction); 155 } 156 157 // When we are replacing the whole entries, things are significantly simplier 158 if (updateType == "set") { 159 sessionData[type] = entries; 160 return; 161 } 162 163 if (!sessionData[type]) { 164 sessionData[type] = []; 165 } 166 const toBeAdded = []; 167 const keyFunction = DATA_KEY_FUNCTION[type] || idFunction; 168 for (const entry of entries) { 169 const existingIndex = sessionData[type].findIndex(existingEntry => { 170 return keyFunction(existingEntry) === keyFunction(entry); 171 }); 172 if (existingIndex === -1) { 173 // New entry. 174 toBeAdded.push(entry); 175 } else { 176 // Existing entry, update the value. This is relevant if the data-entry 177 // is not a primitive data-type, and the value can change for the same 178 // key. 179 sessionData[type][existingIndex] = entry; 180 } 181 } 182 sessionData[type].push(...toBeAdded); 183 }, 184 185 /** 186 * Remove values from the shared "sessionData" object. 187 * 188 * @param Object sessionData 189 * The data object to update. 190 * @param string type 191 * The type of data to be remove 192 * @param Array<Object> entries 193 * The values to be removed from this type of data 194 * @return Boolean 195 * True, if at least one entries existed and has been removed. 196 * False, if none of the entries existed and none has been removed. 197 */ 198 removeSessionDataEntry(sessionData, type, entries) { 199 let includesAtLeastOne = false; 200 const keyFunction = DATA_KEY_FUNCTION[type] || idFunction; 201 for (const entry of entries) { 202 const idx = sessionData[type] 203 ? sessionData[type].findIndex(existingEntry => { 204 return keyFunction(existingEntry) === keyFunction(entry); 205 }) 206 : -1; 207 if (idx !== -1) { 208 sessionData[type].splice(idx, 1); 209 includesAtLeastOne = true; 210 } 211 } 212 if (!includesAtLeastOne) { 213 return false; 214 } 215 216 return true; 217 }, 218 };