index.js (20071B)
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 "use strict"; 6 7 const Targets = require("resource://devtools/server/actors/targets/index.js"); 8 9 const TYPES = { 10 CONSOLE_MESSAGE: "console-message", 11 CSS_CHANGE: "css-change", 12 CSS_MESSAGE: "css-message", 13 CSS_REGISTERED_PROPERTIES: "css-registered-properties", 14 DOCUMENT_EVENT: "document-event", 15 ERROR_MESSAGE: "error-message", 16 LAST_PRIVATE_CONTEXT_EXIT: "last-private-context-exit", 17 NETWORK_EVENT: "network-event", 18 NETWORK_EVENT_STACKTRACE: "network-event-stacktrace", 19 PLATFORM_MESSAGE: "platform-message", 20 REFLOW: "reflow", 21 SERVER_SENT_EVENT: "server-sent-event", 22 SOURCE: "source", 23 STYLESHEET: "stylesheet", 24 THREAD_STATE: "thread-state", 25 JSTRACER_TRACE: "jstracer-trace", 26 JSTRACER_STATE: "jstracer-state", 27 WEBSOCKET: "websocket", 28 WEBTRANSPORT: "webtransport", 29 30 // storage types 31 CACHE_STORAGE: "Cache", 32 COOKIE: "cookies", 33 EXTENSION_STORAGE: "extension-storage", 34 INDEXED_DB: "indexed-db", 35 LOCAL_STORAGE: "local-storage", 36 SESSION_STORAGE: "session-storage", 37 38 // root types 39 EXTENSIONS_BGSCRIPT_STATUS: "extensions-backgroundscript-status", 40 }; 41 exports.TYPES = TYPES; 42 43 // Helper dictionaries, which will contain data specific to each resource type. 44 // - `path` is the absolute path to the module defining the Resource Watcher class. 45 // 46 // Also see the attributes added by `augmentResourceDictionary` for each type: 47 // - `watchers` is a weak map which will store Resource Watchers 48 // (i.e. devtools/server/actors/resources/ class instances) 49 // keyed by target actor -or- watcher actor. 50 // - `WatcherClass` is a shortcut to the Resource Watcher module. 51 // Each module exports a Resource Watcher class. 52 // 53 // These are several dictionaries, which depend how the resource watcher classes are instantiated. 54 55 // Frame target resources are spawned via a BrowsingContext Target Actor. 56 // Their watcher class receives a target actor as first argument. 57 // They are instantiated for each observed BrowsingContext, from the content process where it runs. 58 // They are meant to observe all resources related to a given Browsing Context. 59 const FrameTargetResources = augmentResourceDictionary({ 60 [TYPES.CACHE_STORAGE]: { 61 path: "devtools/server/actors/resources/storage-cache", 62 }, 63 [TYPES.CONSOLE_MESSAGE]: { 64 path: "devtools/server/actors/resources/console-messages", 65 }, 66 [TYPES.CSS_CHANGE]: { 67 path: "devtools/server/actors/resources/css-changes", 68 }, 69 [TYPES.CSS_MESSAGE]: { 70 path: "devtools/server/actors/resources/css-messages", 71 }, 72 [TYPES.CSS_REGISTERED_PROPERTIES]: { 73 path: "devtools/server/actors/resources/css-registered-properties", 74 }, 75 [TYPES.DOCUMENT_EVENT]: { 76 path: "devtools/server/actors/resources/document-event", 77 }, 78 [TYPES.ERROR_MESSAGE]: { 79 path: "devtools/server/actors/resources/error-messages", 80 }, 81 [TYPES.JSTRACER_STATE]: { 82 path: "devtools/server/actors/resources/jstracer-state", 83 }, 84 [TYPES.JSTRACER_TRACE]: { 85 path: "devtools/server/actors/resources/jstracer-trace", 86 }, 87 [TYPES.LOCAL_STORAGE]: { 88 path: "devtools/server/actors/resources/storage-local-storage", 89 }, 90 [TYPES.PLATFORM_MESSAGE]: { 91 path: "devtools/server/actors/resources/platform-messages", 92 }, 93 [TYPES.SESSION_STORAGE]: { 94 path: "devtools/server/actors/resources/storage-session-storage", 95 }, 96 [TYPES.STYLESHEET]: { 97 path: "devtools/server/actors/resources/stylesheets", 98 }, 99 [TYPES.NETWORK_EVENT]: { 100 path: "devtools/server/actors/resources/network-events-content", 101 }, 102 [TYPES.NETWORK_EVENT_STACKTRACE]: { 103 path: "devtools/server/actors/resources/network-events-stacktraces", 104 }, 105 [TYPES.REFLOW]: { 106 path: "devtools/server/actors/resources/reflow", 107 }, 108 [TYPES.SOURCE]: { 109 path: "devtools/server/actors/resources/sources", 110 }, 111 [TYPES.THREAD_STATE]: { 112 path: "devtools/server/actors/resources/thread-states", 113 }, 114 [TYPES.SERVER_SENT_EVENT]: { 115 path: "devtools/server/actors/resources/server-sent-events", 116 }, 117 [TYPES.WEBSOCKET]: { 118 path: "devtools/server/actors/resources/websockets", 119 }, 120 [TYPES.WEBTRANSPORT]: { 121 path: "devtools/server/actors/resources/webtransport", 122 }, 123 }); 124 125 // Process target resources are spawned via a Process Target Actor. 126 // Their watcher class receives a process target actor as first argument. 127 // They are instantiated for each observed Process (parent and all content processes). 128 // They are meant to observe all resources related to a given process. 129 const ProcessTargetResources = augmentResourceDictionary({ 130 [TYPES.CONSOLE_MESSAGE]: { 131 path: "devtools/server/actors/resources/console-messages", 132 }, 133 [TYPES.JSTRACER_TRACE]: { 134 path: "devtools/server/actors/resources/jstracer-trace", 135 }, 136 [TYPES.JSTRACER_STATE]: { 137 path: "devtools/server/actors/resources/jstracer-state", 138 }, 139 [TYPES.ERROR_MESSAGE]: { 140 path: "devtools/server/actors/resources/error-messages", 141 }, 142 [TYPES.PLATFORM_MESSAGE]: { 143 path: "devtools/server/actors/resources/platform-messages", 144 }, 145 [TYPES.SOURCE]: { 146 path: "devtools/server/actors/resources/sources", 147 }, 148 [TYPES.THREAD_STATE]: { 149 path: "devtools/server/actors/resources/thread-states", 150 }, 151 }); 152 153 // Worker target resources are spawned via a Worker Target Actor. 154 // Their watcher class receives a worker target actor as first argument. 155 // They are instantiated for each observed worker, from the worker thread. 156 // They are meant to observe all resources related to a given worker. 157 // 158 // We'll only support a few resource types in Workers (console-message, source, 159 // thread state, …) as error and platform messages are not supported since we need access 160 // to Ci, which isn't available in worker context. 161 // Errors are emitted from the content process main thread so the user would still get them. 162 const WorkerTargetResources = augmentResourceDictionary({ 163 [TYPES.CONSOLE_MESSAGE]: { 164 path: "devtools/server/actors/resources/console-messages", 165 }, 166 [TYPES.JSTRACER_TRACE]: { 167 path: "devtools/server/actors/resources/jstracer-trace", 168 }, 169 [TYPES.JSTRACER_STATE]: { 170 path: "devtools/server/actors/resources/jstracer-state", 171 }, 172 [TYPES.SOURCE]: { 173 path: "devtools/server/actors/resources/sources", 174 }, 175 [TYPES.THREAD_STATE]: { 176 path: "devtools/server/actors/resources/thread-states", 177 }, 178 }); 179 180 // Web Extension Content Script resources are spawn for each content script, 181 // from the web page's main thread it relates to. 182 // 183 // Similarly to workers, it doesn't relate to any DOM Document, 184 // but only a JavaScript context. So it only expose a subset of resources. 185 const WebExtensionContentScriptTargetResources = augmentResourceDictionary({ 186 [TYPES.CONSOLE_MESSAGE]: { 187 path: "devtools/server/actors/resources/console-messages", 188 }, 189 [TYPES.JSTRACER_TRACE]: { 190 path: "devtools/server/actors/resources/jstracer-trace", 191 }, 192 [TYPES.JSTRACER_STATE]: { 193 path: "devtools/server/actors/resources/jstracer-state", 194 }, 195 [TYPES.SOURCE]: { 196 path: "devtools/server/actors/resources/sources", 197 }, 198 [TYPES.THREAD_STATE]: { 199 path: "devtools/server/actors/resources/thread-states", 200 }, 201 }); 202 203 // Parent process resources are spawned via the Watcher Actor. 204 // Their watcher class receives the watcher actor as first argument. 205 // They are instantiated once per watcher from the parent process. 206 // They are meant to observe all resources related to a given context designated by the Watcher (and its sessionContext) 207 // they should be observed from the parent process. 208 const ParentProcessResources = augmentResourceDictionary({ 209 [TYPES.NETWORK_EVENT]: { 210 path: "devtools/server/actors/resources/network-events", 211 }, 212 [TYPES.COOKIE]: { 213 path: "devtools/server/actors/resources/storage-cookie", 214 }, 215 [TYPES.EXTENSION_STORAGE]: { 216 path: "devtools/server/actors/resources/storage-extension", 217 }, 218 [TYPES.INDEXED_DB]: { 219 path: "devtools/server/actors/resources/storage-indexed-db", 220 }, 221 [TYPES.DOCUMENT_EVENT]: { 222 path: "devtools/server/actors/resources/parent-process-document-event", 223 }, 224 [TYPES.LAST_PRIVATE_CONTEXT_EXIT]: { 225 path: "devtools/server/actors/resources/last-private-context-exit", 226 }, 227 }); 228 229 // Root resources are spawned via the Root Actor. 230 // Their watcher class receives the root actor as first argument. 231 // They are instantiated only once from the parent process. 232 // They are meant to observe anything easily observable from the parent process 233 // that isn't related to any particular context/target. 234 // This is especially useful when you need to observe something without having to instantiate a Watcher actor. 235 const RootResources = augmentResourceDictionary({ 236 [TYPES.EXTENSIONS_BGSCRIPT_STATUS]: { 237 path: "devtools/server/actors/resources/extensions-backgroundscript-status", 238 }, 239 }); 240 exports.RootResources = RootResources; 241 242 function augmentResourceDictionary(dict) { 243 for (const resource of Object.values(dict)) { 244 resource.watchers = new WeakMap(); 245 246 loader.lazyRequireGetter(resource, "WatcherClass", resource.path); 247 } 248 return dict; 249 } 250 251 /** 252 * For a given actor, return the related dictionary defined just before, 253 * that contains info about how to listen for a given resource type, from a given actor. 254 * 255 * @param Actor rootOrWatcherOrTargetActor 256 * Either a RootActor or WatcherActor or a TargetActor which can be listening to a resource. 257 */ 258 function getResourceTypeDictionary(rootOrWatcherOrTargetActor) { 259 const { typeName } = rootOrWatcherOrTargetActor; 260 if (typeName == "root") { 261 return RootResources; 262 } 263 if (typeName == "watcher") { 264 return ParentProcessResources; 265 } 266 const { targetType } = rootOrWatcherOrTargetActor; 267 return getResourceTypeDictionaryForTargetType(targetType); 268 } 269 270 /** 271 * For a targetType, return the related dictionary. 272 * 273 * @param String targetType 274 * A targetType string (See Targets.TYPES) 275 */ 276 function getResourceTypeDictionaryForTargetType(targetType) { 277 switch (targetType) { 278 case Targets.TYPES.FRAME: 279 return FrameTargetResources; 280 case Targets.TYPES.PROCESS: 281 return ProcessTargetResources; 282 case Targets.TYPES.WORKER: 283 return WorkerTargetResources; 284 case Targets.TYPES.SERVICE_WORKER: 285 return WorkerTargetResources; 286 case Targets.TYPES.SHARED_WORKER: 287 return WorkerTargetResources; 288 case Targets.TYPES.CONTENT_SCRIPT: 289 return WebExtensionContentScriptTargetResources; 290 default: 291 throw new Error(`Unsupported target actor typeName '${targetType}'`); 292 } 293 } 294 295 /** 296 * For a given actor, return the object stored in one of the previous dictionary 297 * that contains info about how to listen for a given resource type, from a given actor. 298 * 299 * @param Actor rootOrWatcherOrTargetActor 300 * Either a RootActor or WatcherActor or a TargetActor which can be listening to a resource. 301 * @param String resourceType 302 * The resource type to be observed. 303 */ 304 function getResourceTypeEntry(rootOrWatcherOrTargetActor, resourceType) { 305 const dict = getResourceTypeDictionary(rootOrWatcherOrTargetActor); 306 if (!(resourceType in dict)) { 307 throw new Error( 308 `Unsupported resource type '${resourceType}' for ${rootOrWatcherOrTargetActor.typeName}` 309 ); 310 } 311 return dict[resourceType]; 312 } 313 314 /** 315 * Start watching for a new list of resource types. 316 * This will also emit all already existing resources before resolving. 317 * 318 * @param Actor rootOrWatcherOrTargetActor 319 * Either a RootActor or WatcherActor or a TargetActor which can be listening to a resource: 320 * * RootActor will be used for resources observed from the parent process and aren't related to any particular 321 * context/descriptor. They can be observed right away when connecting to the RDP server 322 * without instantiating any actor other than the root actor. 323 * * WatcherActor will be used for resources listened from the parent process. 324 * * TargetActor will be used for resources listened from the content process. 325 * This actor: 326 * - defines what context to observe (browsing context, process, worker, ...) 327 * Via browsingContextID, windows, docShells attributes for the target actor. 328 * Via the `sessionContext` object for the watcher actor. 329 * (only for Watcher and Target actors. Root actor is context-less.) 330 * - exposes `notifyResources` method to be notified about all the resources updates 331 * This method will receive two arguments: 332 * - {String} updateType, which can be "available", "updated", or "destroyed" 333 * - {Array<Object>} resources, which will be the list of resource's forms 334 * or special update object for "updated" scenario. 335 * @param Array<String> resourceTypes 336 * List of all type of resource to listen to. 337 */ 338 async function watchResources(rootOrWatcherOrTargetActor, resourceTypes) { 339 // If we are given a target actor, filter out the resource types supported by the target. 340 // When using sharedData to pass types between processes, we are passing them for all target types. 341 const { targetType } = rootOrWatcherOrTargetActor; 342 // Only target actors usecase will have a target type. 343 // For Root and Watcher we process the `resourceTypes` list unfiltered. 344 if (targetType) { 345 resourceTypes = getResourceTypesForTargetType(resourceTypes, targetType); 346 } 347 const promises = []; 348 for (const resourceType of resourceTypes) { 349 const { watchers, WatcherClass } = getResourceTypeEntry( 350 rootOrWatcherOrTargetActor, 351 resourceType 352 ); 353 354 // Ignore resources we're already listening to 355 if (watchers.has(rootOrWatcherOrTargetActor)) { 356 continue; 357 } 358 359 // Don't watch for console messages from the worker target if worker messages are still 360 // being cloned to the main process, otherwise we'll get duplicated messages in the 361 // console output (See Bug 1778852). 362 if ( 363 resourceType == TYPES.CONSOLE_MESSAGE && 364 rootOrWatcherOrTargetActor.workerConsoleApiMessagesDispatchedToMainThread 365 ) { 366 continue; 367 } 368 369 const watcher = new WatcherClass(); 370 promises.push( 371 watcher.watch(rootOrWatcherOrTargetActor, { 372 onAvailable: rootOrWatcherOrTargetActor.notifyResources.bind( 373 rootOrWatcherOrTargetActor, 374 "available", 375 resourceType 376 ), 377 onUpdated: rootOrWatcherOrTargetActor.notifyResources.bind( 378 rootOrWatcherOrTargetActor, 379 "updated", 380 resourceType 381 ), 382 onDestroyed: rootOrWatcherOrTargetActor.notifyResources.bind( 383 rootOrWatcherOrTargetActor, 384 "destroyed", 385 resourceType 386 ), 387 }) 388 ); 389 watchers.set(rootOrWatcherOrTargetActor, watcher); 390 } 391 await Promise.all(promises); 392 393 // Force sending resources to the client before we resolve. 394 // So that resources are received by the client 395 // before WatcherActor.watchResources resolves. 396 // This is important when ResourceCommand.watchResources's `ignoreExistingResources` flag is set to false (default behavior). 397 // The client code expects all resources to be emitted when this server method resolves. 398 if (rootOrWatcherOrTargetActor.emitResources) { 399 rootOrWatcherOrTargetActor.emitResources(); 400 } 401 } 402 exports.watchResources = watchResources; 403 404 function getParentProcessResourceTypes(resourceTypes) { 405 return resourceTypes.filter(resourceType => { 406 return resourceType in ParentProcessResources; 407 }); 408 } 409 exports.getParentProcessResourceTypes = getParentProcessResourceTypes; 410 411 function getResourceTypesForTargetType(resourceTypes, targetType) { 412 const resourceDictionnary = 413 getResourceTypeDictionaryForTargetType(targetType); 414 return resourceTypes.filter(resourceType => { 415 return resourceType in resourceDictionnary; 416 }); 417 } 418 exports.getResourceTypesForTargetType = getResourceTypesForTargetType; 419 420 function hasResourceTypesForTargets(resourceTypes) { 421 return resourceTypes.some(resourceType => { 422 return resourceType in FrameTargetResources; 423 }); 424 } 425 exports.hasResourceTypesForTargets = hasResourceTypesForTargets; 426 427 /** 428 * Stop watching for a list of resource types. 429 * 430 * @param Actor rootOrWatcherOrTargetActor 431 * The related actor, already passed to watchResources. 432 * @param Array<String> resourceTypes 433 * List of all type of resource to stop listening to. 434 */ 435 function unwatchResources(rootOrWatcherOrTargetActor, resourceTypes) { 436 // If we are given a target actor, filter out the resource types supported by the target. 437 // When using sharedData to pass types between processes, we are passing them for all target types. 438 const { targetType } = rootOrWatcherOrTargetActor; 439 // Only target actors usecase will have a target type. 440 // For Root and Watcher we process the `resourceTypes` list unfiltered. 441 if (targetType) { 442 resourceTypes = getResourceTypesForTargetType(resourceTypes, targetType); 443 } 444 for (const resourceType of resourceTypes) { 445 // Pull all info about this resource type from `Resources` global object 446 const { watchers } = getResourceTypeEntry( 447 rootOrWatcherOrTargetActor, 448 resourceType 449 ); 450 451 const watcher = watchers.get(rootOrWatcherOrTargetActor); 452 if (watcher) { 453 watcher.destroy(); 454 watchers.delete(rootOrWatcherOrTargetActor); 455 } 456 } 457 } 458 exports.unwatchResources = unwatchResources; 459 460 /** 461 * Clear resources for a list of resource types. 462 * 463 * @param Actor rootOrWatcherOrTargetActor 464 * The related actor, already passed to watchResources. 465 * @param Array<String> resourceTypes 466 * List of all type of resource to clear. 467 */ 468 function clearResources(rootOrWatcherOrTargetActor, resourceTypes) { 469 // If we are given a target actor, filter out the resource types supported by the target. 470 // When using sharedData to pass types between processes, we are passing them for all target types. 471 const { targetType } = rootOrWatcherOrTargetActor; 472 // Only target actors usecase will have a target type. 473 // For Root and Watcher we process the `resourceTypes` list unfiltered. 474 if (targetType) { 475 resourceTypes = getResourceTypesForTargetType(resourceTypes, targetType); 476 } 477 for (const resourceType of resourceTypes) { 478 const { watchers } = getResourceTypeEntry( 479 rootOrWatcherOrTargetActor, 480 resourceType 481 ); 482 483 const watcher = watchers.get(rootOrWatcherOrTargetActor); 484 if (watcher && typeof watcher.clear == "function") { 485 watcher.clear(); 486 } 487 } 488 } 489 490 exports.clearResources = clearResources; 491 492 /** 493 * Stop watching for all watched resources on a given actor. 494 * 495 * @param Actor rootOrWatcherOrTargetActor 496 * The related actor, already passed to watchResources. 497 */ 498 function unwatchAllResources(rootOrWatcherOrTargetActor) { 499 for (const { watchers } of Object.values( 500 getResourceTypeDictionary(rootOrWatcherOrTargetActor) 501 )) { 502 const watcher = watchers.get(rootOrWatcherOrTargetActor); 503 if (watcher) { 504 watcher.destroy(); 505 watchers.delete(rootOrWatcherOrTargetActor); 506 } 507 } 508 } 509 exports.unwatchAllResources = unwatchAllResources; 510 511 /** 512 * If we are watching for the given resource type, 513 * return the current ResourceWatcher instance used by this target actor 514 * in order to observe this resource type. 515 * 516 * @param Actor watcherOrTargetActor 517 * Either a WatcherActor or a TargetActor which can be listening to a resource. 518 * WatcherActor will be used for resources listened from the parent process, 519 * and TargetActor will be used for resources listened from the content process. 520 * @param String resourceType 521 * The resource type to query 522 * @return ResourceWatcher 523 * The resource watcher instance, defined in devtools/server/actors/resources/ 524 */ 525 function getResourceWatcher(watcherOrTargetActor, resourceType) { 526 const { watchers } = getResourceTypeEntry(watcherOrTargetActor, resourceType); 527 528 return watchers.get(watcherOrTargetActor); 529 } 530 exports.getResourceWatcher = getResourceWatcher;