resource-command.js (55426B)
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 { throttle } = require("resource://devtools/shared/throttle.js"); 8 9 let gLastResourceId = 0; 10 11 function cacheKey(resourceType, resourceId) { 12 return `${resourceType}:${resourceId}`; 13 } 14 15 class ResourceCommand { 16 #destroyed = false; 17 18 /** 19 * This class helps retrieving existing and listening to resources. 20 * A resource is something that: 21 * - the target you are debugging exposes 22 * - can be created as early as the process/worker/page starts loading 23 * - can already exist, or will be created later on 24 * - doesn't require any user data to be fetched, only a type/category 25 * 26 * @param object commands 27 * The commands object with all interfaces defined from devtools/shared/commands/ 28 */ 29 constructor({ commands }) { 30 this.targetCommand = commands.targetCommand; 31 32 // Public attribute set by tests to disable throttling 33 this.throttlingDisabled = false; 34 35 this._onTargetAvailable = this._onTargetAvailable.bind(this); 36 this._onTargetDestroyed = this._onTargetDestroyed.bind(this); 37 38 // Array of all the currently registered watchers, which contains object with attributes: 39 // - {String} resources: list of all resource watched by this one watcher 40 // - {Function} onAvailable: watcher's function to call when a new resource is available 41 // - {Function} onUpdated: watcher's function to call when a resource has been updated 42 // - {Function} onDestroyed: watcher's function to call when a resource is destroyed 43 this._watchers = []; 44 45 // Set of watchers currently going through watchResources, only used to handle 46 // early calls to unwatchResources. Using a Set instead of an array for easier 47 // delete operations. 48 this._pendingWatchers = new Set(); 49 50 // Caches for all resources by the order that the resource was taken. 51 this._cache = new Map(); 52 this._listenedResources = new Set(); 53 54 // WeakMap used to avoid starting a legacy listener twice for the same 55 // target + resource-type pair. Legacy listener creation can be subject to 56 // race conditions. 57 // Maps a target front to an array of resource types. 58 this._existingLegacyListeners = new WeakMap(); 59 this._processingExistingResources = new Set(); 60 61 // List of targetFront event listener unregistration functions keyed by target front. 62 // These are called when unwatching resources, so if a consumer starts watching resources again, 63 // we don't have listeners registered twice. 64 this._offTargetFrontListeners = new Map(); 65 66 // Bug 1914386: We used to throttle the resource on client and should try to remove it entirely. 67 const throttleDelay = 0; 68 this._notifyWatchers = this._notifyWatchers.bind(this); 69 this._throttledNotifyWatchers = throttle( 70 this._notifyWatchers, 71 throttleDelay 72 ); 73 } 74 75 destroy() { 76 this.#destroyed = true; 77 } 78 79 get watcherFront() { 80 return this.targetCommand.watcherFront; 81 } 82 83 addResourceToCache(resource) { 84 const { resourceId, resourceType } = resource; 85 if (TRANSIENT_RESOURCE_TYPES.includes(resourceType)) { 86 return; 87 } 88 this._cache.set(cacheKey(resourceType, resourceId), resource); 89 } 90 91 /** 92 * Clear all the resources related to specifed resource types. 93 * Should also trigger clearing of the caches that exists on the related 94 * serverside resource watchers. 95 * 96 * @param {Array:string} resourceTypes 97 * A list of all the resource types whose 98 * resources shouled be cleared. 99 */ 100 async clearResources(resourceTypes) { 101 if (!Array.isArray(resourceTypes)) { 102 throw new Error("clearResources expects a list of resources types"); 103 } 104 // Clear the cached resources of the type. 105 for (const [key, resource] of this._cache) { 106 if (resourceTypes.includes(resource.resourceType)) { 107 // NOTE: To anyone paranoid like me, yes it is okay to delete from a Map while iterating it. 108 this._cache.delete(key); 109 } 110 } 111 112 const resourcesToClear = resourceTypes.filter(resourceType => 113 this.hasResourceCommandSupport(resourceType) 114 ); 115 if (resourcesToClear.length) { 116 this.watcherFront.clearResources(resourcesToClear); 117 } 118 } 119 /** 120 * Return all specified resources cached in this watcher. 121 * 122 * @param {string} resourceType 123 * @return {Array} resources cached in this watcher 124 */ 125 getAllResources(resourceType) { 126 const result = []; 127 for (const resource of this._cache.values()) { 128 if (resource.resourceType === resourceType) { 129 result.push(resource); 130 } 131 } 132 return result; 133 } 134 135 /** 136 * Return the specified resource cached in this watcher. 137 * 138 * @param {string} resourceType 139 * @param {string} resourceId 140 * @return {object} resource cached in this watcher 141 */ 142 getResourceById(resourceType, resourceId) { 143 return this._cache.get(cacheKey(resourceType, resourceId)); 144 } 145 146 /** 147 * Request to start retrieving all already existing instances of given 148 * type of resources and also start watching for the one to be created after. 149 * 150 * @param {Array:string} resources 151 * List of all resources which should be fetched and observed. 152 * @param {object} options 153 * - {Function} onAvailable: This attribute is mandatory. 154 * Function which will be called with an array of resources 155 * each time resource(s) are created. 156 * A second dictionary argument with `areExistingResources` boolean 157 * attribute helps knowing if that's live resources, or some coming 158 * from ResourceCommand cache. 159 * - {Function} onUpdated: This attribute is optional. 160 * Function which will be called with an array of updates resources 161 * each time resource(s) are updated. 162 * These resources were previously notified via onAvailable. 163 * - {Function} onDestroyed: This attribute is optional. 164 * Function which will be called with an array of deleted resources 165 * each time resource(s) are destroyed. 166 * - {boolean} ignoreExistingResources: 167 * This attribute is optional. Default value is false. 168 * If set to true, onAvailable won't be called with 169 * existing resources. 170 */ 171 async watchResources(resources, options) { 172 const { 173 onAvailable, 174 onUpdated, 175 onDestroyed, 176 ignoreExistingResources = false, 177 } = options; 178 179 if (typeof onAvailable !== "function") { 180 throw new Error( 181 "ResourceCommand.watchResources expects an onAvailable function as argument" 182 ); 183 } 184 185 for (const type of resources) { 186 if (!this._isValidResourceType(type)) { 187 throw new Error( 188 `ResourceCommand.watchResources invoked with an unknown type: "${type}"` 189 ); 190 } 191 } 192 193 // Copy the array in order to avoid the callsite to modify the list of watched resources by mutating the array. 194 // You have to call (un)watchResources to update the list of resources being watched! 195 resources = [...resources]; 196 197 // Pending watchers are used in unwatchResources to remove watchers which 198 // are not fully registered yet. Store `onAvailable` which is the unique key 199 // for a watcher, as well as the resources array, so that unwatchResources 200 // can update the array if we stop watching a specific resource. 201 const pendingWatcher = { 202 resources, 203 onAvailable, 204 }; 205 this._pendingWatchers.add(pendingWatcher); 206 207 // Bug 1675763: Watcher actor is not available in all situations yet. 208 if (!this._listenerRegistered && this.watcherFront) { 209 this._listenerRegistered = true; 210 // Resources watched from the parent process will be emitted on the Watcher Actor. 211 // So that we also have to listen for this event on it, in addition to all targets. 212 this.watcherFront.on( 213 "resources-available-array", 214 this._onResourceAvailableArray.bind(this, { 215 watcherFront: this.watcherFront, 216 }) 217 ); 218 this.watcherFront.on( 219 "resources-updated-array", 220 this._onResourceUpdatedArray.bind(this, { 221 watcherFront: this.watcherFront, 222 }) 223 ); 224 this.watcherFront.on( 225 "resources-destroyed-array", 226 this._onResourceDestroyedArray.bind(this, { 227 watcherFront: this.watcherFront, 228 }) 229 ); 230 } 231 232 const promises = []; 233 for (const resource of resources) { 234 promises.push(this._startListening(resource)); 235 } 236 await Promise.all(promises); 237 238 // The resource cache is immediately filled when receiving the sources, but they are 239 // emitted with a delay due to throttling. Since the cache can contain resources that 240 // will soon be emitted, we have to flush it before adding the new listeners. 241 // Otherwise _forwardExistingResources might emit resources that will also be emitted by 242 // the next `_notifyWatchers` call done when calling `_startListening`, which will pull the 243 // "already existing" resources. 244 this._notifyWatchers(); 245 246 // Update the _pendingWatchers set before adding the watcher to _watchers. 247 this._pendingWatchers.delete(pendingWatcher); 248 249 // If unwatchResources was called in the meantime, use pendingWatcher's 250 // resources to get the updated list of watched resources. 251 const watchedResources = pendingWatcher.resources; 252 253 // If no resource needs to be watched anymore, do not add an empty watcher 254 // to _watchers, and do not notify about cached resources. 255 if (!watchedResources.length) { 256 return; 257 } 258 259 // Register the watcher just after calling _startListening in order to avoid it being called 260 // for already existing resources, which will optionally be notified via _forwardExistingResources 261 this._watchers.push({ 262 resources: watchedResources, 263 onAvailable, 264 onUpdated, 265 onDestroyed, 266 pendingEvents: [], 267 }); 268 269 if (!ignoreExistingResources) { 270 await this._forwardExistingResources(watchedResources, onAvailable); 271 } 272 } 273 274 /** 275 * Stop watching for given type of resources. 276 * See `watchResources` for the arguments as both methods receive the same. 277 * Note that `onUpdated` and `onDestroyed` attributes of `options` aren't used here. 278 * Only `onAvailable` attribute is looked up and we unregister all the other registered callbacks 279 * when a matching available callback is found. 280 */ 281 unwatchResources(resources, options) { 282 const { onAvailable } = options; 283 284 if (typeof onAvailable !== "function") { 285 throw new Error( 286 "ResourceCommand.unwatchResources expects an onAvailable function as argument" 287 ); 288 } 289 290 for (const type of resources) { 291 if (!this._isValidResourceType(type)) { 292 throw new Error( 293 `ResourceCommand.unwatchResources invoked with an unknown type: "${type}"` 294 ); 295 } 296 } 297 298 // Unregister the callbacks from the watchers registries. 299 // Check _watchers for the fully initialized watchers, as well as 300 // `_pendingWatchers` for new watchers still being created by `watchResources` 301 const allWatchers = [...this._watchers, ...this._pendingWatchers]; 302 for (const watcherEntry of allWatchers) { 303 // onAvailable is the only mandatory argument which ends up being used to match 304 // the right watcher entry. 305 if (watcherEntry.onAvailable == onAvailable) { 306 // Remove all resources that we stop watching. We may still watch for some others. 307 watcherEntry.resources = watcherEntry.resources.filter(resourceType => { 308 return !resources.includes(resourceType); 309 }); 310 } 311 } 312 this._watchers = this._watchers.filter(entry => { 313 // Remove entries entirely if it isn't watching for any resource type 314 return !!entry.resources.length; 315 }); 316 317 // Stop listening to all resources for which we removed the last watcher 318 for (const resource of resources) { 319 const isResourceWatched = allWatchers.some(watcherEntry => 320 watcherEntry.resources.includes(resource) 321 ); 322 323 // Also check in _listenedResources as we may call unwatchResources 324 // for resources that we haven't started watching for. 325 if (!isResourceWatched && this._listenedResources.has(resource)) { 326 this._stopListening(resource); 327 } 328 } 329 330 // Stop watching for targets if we removed the last listener. 331 if (this._listenedResources.size == 0) { 332 this._unwatchAllTargets(); 333 } 334 } 335 336 /** 337 * Wait for a single resource of the provided resourceType. 338 * 339 * @param {string} resourceType 340 * One of ResourceCommand.TYPES, type of the expected resource. 341 * @param {object} additional options 342 * - {Boolean} ignoreExistingResources: ignore existing resources or not. 343 * - {Function} predicate: if provided, will wait until a resource makes 344 * predicate(resource) return true. 345 * @return {Promise<object>} 346 * Return a promise which resolves once we fully settle the resource listener. 347 * You should await for its resolution before doing the action which may fire 348 * your resource. 349 * This promise will expose an object with `onResource` attribute, 350 * itself being a promise, which will resolve once a matching resource is received. 351 */ 352 async waitForNextResource( 353 resourceType, 354 { ignoreExistingResources = false, predicate } = {} 355 ) { 356 // If no predicate was provided, convert to boolean to avoid resolving for 357 // empty `resources` arrays. 358 predicate = predicate || (resource => !!resource); 359 360 let resolve; 361 const promise = new Promise(r => (resolve = r)); 362 const onAvailable = async resources => { 363 const matchingResource = resources.find(resource => predicate(resource)); 364 if (matchingResource) { 365 this.unwatchResources([resourceType], { onAvailable }); 366 resolve(matchingResource); 367 } 368 }; 369 370 await this.watchResources([resourceType], { 371 ignoreExistingResources, 372 onAvailable, 373 }); 374 return { onResource: promise }; 375 } 376 377 /** 378 * Check if there are any watchers for the specified resource. 379 * 380 * @param {string} resourceType 381 * One of ResourceCommand.TYPES 382 * @return {boolean} 383 * If the resources type is beibg watched. 384 */ 385 isResourceWatched(resourceType) { 386 return this._listenedResources.has(resourceType); 387 } 388 389 /** 390 * Start watching for all already existing and future targets. 391 * 392 * We are using ALL_TYPES, but this won't force listening to all types. 393 * It will only listen for types which are defined by `TargetCommand.startListening`. 394 */ 395 async _watchAllTargets() { 396 if (!this._watchTargetsPromise) { 397 // If this is the very first listener registered, of all kind of resource types: 398 // * we want to start observing targets via TargetCommand 399 // * _onTargetAvailable will be called for each already existing targets and the next one to come 400 this._watchTargetsPromise = this.targetCommand.watchTargets({ 401 types: this.targetCommand.ALL_TYPES, 402 onAvailable: this._onTargetAvailable, 403 onDestroyed: this._onTargetDestroyed, 404 }); 405 } 406 return this._watchTargetsPromise; 407 } 408 409 _unwatchAllTargets() { 410 if (!this._watchTargetsPromise) { 411 return; 412 } 413 414 for (const offList of this._offTargetFrontListeners.values()) { 415 offList.forEach(off => off()); 416 } 417 this._offTargetFrontListeners.clear(); 418 419 this._watchTargetsPromise = null; 420 this.targetCommand.unwatchTargets({ 421 types: this.targetCommand.ALL_TYPES, 422 onAvailable: this._onTargetAvailable, 423 onDestroyed: this._onTargetDestroyed, 424 }); 425 } 426 427 /** 428 * For a given resource type, start the legacy listeners for all already existing targets. 429 * Do that only if we have to. If this resourceType requires legacy listeners. 430 */ 431 async _startLegacyListenersForExistingTargets(resourceType) { 432 // If we were already listening to targets, we want to start the legacy listeners 433 // for all already existing targets. 434 // 435 // Only try instantiating the legacy listener, if this resource type: 436 // - has legacy listener implementation 437 // (new resource types may not be supported by old runtime and just not be received without breaking anything) 438 // - isn't supported by the server, or, the target type requires the a legacy listener implementation. 439 const shouldRunLegacyListeners = 440 resourceType in LegacyListeners && 441 (!this.hasResourceCommandSupport(resourceType) || 442 this._shouldRunLegacyListenerEvenWithWatcherSupport(resourceType)); 443 if (shouldRunLegacyListeners) { 444 const promises = []; 445 const targets = this.targetCommand.getAllTargets( 446 this.targetCommand.ALL_TYPES 447 ); 448 for (const targetFront of targets) { 449 // We disable warning in case we already registered the legacy listener for this target 450 // as this code may race with the call from onTargetAvailable if we end up having multiple 451 // calls to _startListening in parallel. 452 promises.push( 453 this._watchResourcesForTarget({ 454 targetFront, 455 resourceType, 456 disableWarning: true, 457 }) 458 ); 459 } 460 await Promise.all(promises); 461 } 462 } 463 464 /** 465 * Method called by the TargetCommand for each already existing or target which has just been created. 466 * 467 * @param {object} arg 468 * @param {Front} arg.targetFront 469 * The Front of the target that is available. 470 * This Front inherits from TargetMixin and is typically 471 * composed of a WindowGlobalTargetFront or ContentProcessTargetFront. 472 * @param {boolean} arg.isTargetSwitching 473 * true when the new target was created because of a target switching. 474 */ 475 async _onTargetAvailable({ targetFront, isTargetSwitching }) { 476 const resources = []; 477 if (isTargetSwitching) { 478 // WatcherActor currently only watches additional frame targets and 479 // explicitely ignores top level one that may be created when navigating 480 // to a new process. 481 // In order to keep working resources that are being watched via the 482 // Watcher actor, we have to unregister and re-register the resource 483 // types. This will force calling `Resources.watchResources` on the new top 484 // level target. 485 for (const resourceType of Object.values(ResourceCommand.TYPES)) { 486 // ...which has at least one listener... 487 if (!this._listenedResources.has(resourceType)) { 488 continue; 489 } 490 491 if (this._shouldRestartListenerOnTargetSwitching(resourceType)) { 492 this._stopListening(resourceType, { 493 bypassListenerCount: true, 494 }); 495 resources.push(resourceType); 496 } 497 } 498 } 499 500 if (targetFront.isDestroyed()) { 501 return; 502 } 503 504 // If we are target switching, we already stop & start listening to all the 505 // currently monitored resources. 506 if (!isTargetSwitching) { 507 // For each resource type... 508 for (const resourceType of Object.values(ResourceCommand.TYPES)) { 509 // ...which has at least one listener... 510 if (!this._listenedResources.has(resourceType)) { 511 continue; 512 } 513 // ...request existing resource and new one to come from this one target 514 // *but* only do that for backward compat, where we don't have the watcher API 515 // (See bug 1626647) 516 await this._watchResourcesForTarget({ targetFront, resourceType }); 517 } 518 } 519 520 // Compared to the TargetCommand and Watcher.watchTargets, 521 // We do call Watcher.watchResources, but the events are fired on the target. 522 // That's because the Watcher runs in the parent process/main thread, while resources 523 // are available from the target's process/thread. 524 const offResourceAvailableArray = targetFront.on( 525 "resources-available-array", 526 this._onResourceAvailableArray.bind(this, { targetFront }) 527 ); 528 const offResourceUpdatedArray = targetFront.on( 529 "resources-updated-array", 530 this._onResourceUpdatedArray.bind(this, { targetFront }) 531 ); 532 const offResourceDestroyedArray = targetFront.on( 533 "resources-destroyed-array", 534 this._onResourceDestroyedArray.bind(this, { targetFront }) 535 ); 536 537 const offList = this._offTargetFrontListeners.get(targetFront) || []; 538 offList.push( 539 offResourceAvailableArray, 540 offResourceUpdatedArray, 541 offResourceDestroyedArray 542 ); 543 544 if (isTargetSwitching) { 545 await Promise.all( 546 resources.map(resourceType => 547 this._startListening(resourceType, { 548 bypassListenerCount: true, 549 }) 550 ) 551 ); 552 } 553 554 // DOCUMENT_EVENT's will-navigate should replace target actor's will-navigate event, 555 // but only for targets provided by the watcher actor. 556 // Emit a fake DOCUMENT_EVENT's "will-navigate" out of target actor's will-navigate 557 // until watcher actor is supported by all descriptors (bug 1675763). 558 if (!this.targetCommand.hasTargetWatcherSupport()) { 559 const offWillNavigate = targetFront.on( 560 "will-navigate", 561 ({ url, isFrameSwitching }) => { 562 targetFront.emit("resource-available-form", [ 563 { 564 resourceType: this.TYPES.DOCUMENT_EVENT, 565 name: "will-navigate", 566 time: Date.now(), // will-navigate was not passing any timestamp 567 isFrameSwitching, 568 newURI: url, 569 }, 570 ]); 571 } 572 ); 573 offList.push(offWillNavigate); 574 } 575 576 this._offTargetFrontListeners.set(targetFront, offList); 577 } 578 579 _shouldRestartListenerOnTargetSwitching(resourceType) { 580 // Note that we aren't using isServerTargetSwitchingEnabled, nor checking the 581 // server side target switching preference as we may have server side targets 582 // even when this is false/disabled. 583 // This will happen for bfcache navigations, even with server side targets disabled. 584 // `followWindowGlobalLifeCycle` will be false for the first top level target 585 // and only become true when doing a bfcache navigation. 586 // (only server side targets follow the WindowGlobal lifecycle) 587 // When server side targets are enabled, this will always be true. 588 const isServerSideTarget = 589 this.targetCommand.targetFront.targetForm.followWindowGlobalLifeCycle; 590 if (isServerSideTarget) { 591 // For top-level targets created from the server, only restart legacy 592 // listeners. 593 return !this.hasResourceCommandSupport(resourceType); 594 } 595 596 // For top-level targets created from the client we should always restart 597 // listeners. 598 return true; 599 } 600 601 /** 602 * Method called by the TargetCommand when a target has just been destroyed 603 * 604 * @param {object} arg 605 * @param {Front} arg.targetFront 606 * The Front of the target that was destroyed 607 * @param {boolean} arg.isModeSwitching 608 * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref. 609 */ 610 _onTargetDestroyed({ targetFront, isModeSwitching }) { 611 // Clear the map of legacy listeners for this target. 612 this._existingLegacyListeners.set(targetFront, []); 613 this._offTargetFrontListeners.delete(targetFront); 614 615 // Purge the cache from any resource related to the destroyed target. 616 // Top level BrowsingContext target will be purge via DOCUMENT_EVENT will-navigate events. 617 // If we were to clean resources from target-destroyed, we will clear resources 618 // happening between will-navigate and target-destroyed. Typically the navigation request 619 // At the moment, isModeSwitching can only be true when targetFront.isTopLevel isn't true, 620 // so we don't need to add a specific check for isModeSwitching. 621 if (!targetFront.isTopLevel || !targetFront.isBrowsingContext) { 622 for (const [key, resource] of this._cache) { 623 if (resource.targetFront === targetFront) { 624 // NOTE: To anyone paranoid like me, yes it is okay to delete from a Map while iterating it. 625 this._cache.delete(key); 626 } 627 } 628 } 629 630 // Purge "available" pendingEvents for resources from the destroyed target when switching 631 // mode as we want to ignore those. 632 if (isModeSwitching) { 633 for (const watcherEntry of this._watchers) { 634 for (const pendingEvent of watcherEntry.pendingEvents) { 635 if (pendingEvent.callbackType == "available") { 636 pendingEvent.updates = pendingEvent.updates.filter( 637 update => update.targetFront !== targetFront 638 ); 639 } 640 } 641 } 642 } 643 } 644 645 async _onResourceAvailableArray({ targetFront, watcherFront }, array) { 646 let includesDocumentEventWillNavigate = false; 647 let includesDocumentEventDomLoading = false; 648 for (const [resourceType, resources] of array) { 649 const isAlreadyExistingResource = 650 this._processingExistingResources.has(resourceType); 651 const transformer = ResourceTransformers[resourceType]; 652 653 for (let i = 0; i < resources.length; i++) { 654 let resource = resources[i]; 655 if (!("resourceType" in resource)) { 656 resource.resourceType = resourceType; 657 } 658 659 if (watcherFront) { 660 try { 661 targetFront = await this._getTargetForWatcherResource(resource); 662 } catch (e) { 663 if (this.#destroyed) { 664 // If the resource-command was destroyed while waiting for 665 // _getTargetForWatcherResource, swallow the error. 666 return; 667 } 668 throw e; 669 } 670 // When we receive resources from the Watcher actor, 671 // there is no guarantee that the target front is fully initialized. 672 // The Target Front is initialized by the TargetCommand, by calling TargetFront.attachAndInitThread. 673 // We have to wait for its completion as resources watchers are expecting it to be completed. 674 // 675 // But when navigating, we may receive resources packets for a destroyed target. 676 // Or, in the context of the browser toolbox, they may not relate to any target. 677 if (targetFront) { 678 await targetFront.initialized; 679 } 680 } 681 682 // Put the targetFront on the resource for easy retrieval. 683 // (Resources from the legacy listeners may already have the attribute set) 684 if (!resource.targetFront) { 685 resource.targetFront = targetFront; 686 } 687 688 if (transformer) { 689 resource = transformer({ 690 resource, 691 targetCommand: this.targetCommand, 692 targetFront, 693 watcherFront: this.watcherFront, 694 }); 695 resources[i] = resource; 696 } 697 698 // isAlreadyExistingResource indicates that the resources already existed before 699 // the resource command started watching for this type of resource. 700 resource.isAlreadyExistingResource = isAlreadyExistingResource; 701 702 if (!resource.resourceId) { 703 resource.resourceId = `auto:${++gLastResourceId}`; 704 } 705 706 // Only consider top level document, and ignore remote iframes top document 707 let isWillNavigate = false; 708 if (resourceType == DOCUMENT_EVENT) { 709 isWillNavigate = resource.name === "will-navigate"; 710 const isBrowserToolbox = 711 this.targetCommand.descriptorFront.isBrowserProcessDescriptor; 712 if ( 713 isWillNavigate && 714 resource.targetFront.isTopLevel && 715 // When selecting a document in the Browser Toolbox iframe picker, we're getting 716 // a will-navigate event. In such case, we don't want to clear the cache, 717 // otherwise we'd miss some resources that we might already received (e.g. stylesheets) 718 // See Bug 1981937 719 (!isBrowserToolbox || !resource.isFrameSwitching) 720 ) { 721 includesDocumentEventWillNavigate = true; 722 this._onWillNavigate(resource.targetFront); 723 } 724 725 if ( 726 resource.name === "dom-loading" && 727 resource.targetFront.isTopLevel 728 ) { 729 includesDocumentEventDomLoading = true; 730 } 731 } 732 733 // Avoid storing will-navigate resource and consider it as a transcient resource. 734 // We do that to prevent leaking this resource (and its target) on navigation. 735 // We do clear the cache in _onWillNavigate, that we call a few lines before this. 736 if (!isWillNavigate) { 737 this.addResourceToCache(resource); 738 } 739 } 740 741 this._queueResourceEvent("available", resourceType, resources); 742 } 743 744 // If we receive the DOCUMENT_EVENT for: 745 // - will-navigate 746 // - dom-loading + we're using the service worker legacy listener 747 // then flush immediately the resources to notify about the navigation sooner than later. 748 // (this is especially useful for tests, even if they should probably avoid depending on this...) 749 if ( 750 includesDocumentEventWillNavigate || 751 (includesDocumentEventDomLoading && 752 !this.targetCommand.hasTargetWatcherSupport("service_worker")) || 753 this.throttlingDisabled 754 ) { 755 this._notifyWatchers(); 756 } else { 757 this._throttledNotifyWatchers(); 758 } 759 } 760 761 async _onResourceUpdatedArray(context, array) { 762 for (const [resourceType, resources] of array) { 763 for (const resource of resources) { 764 if (!("resourceType" in resource)) { 765 resource.resourceType = resourceType; 766 } 767 } 768 await this._onResourceUpdated(context, resources); 769 } 770 } 771 772 async _onResourceDestroyedArray(context, array) { 773 const resources = []; 774 for (const [resourceType, resourceIds] of array) { 775 for (const resourceId of resourceIds) { 776 resources.push({ resourceType, resourceId }); 777 } 778 } 779 await this._onResourceDestroyed(context, resources); 780 } 781 782 /** 783 * Called every time a resource is updated in the remote target. 784 * 785 * Method called either by: 786 * - the backward compatibility code (LegacyListeners) 787 * - target actors RDP events 788 * 789 * @param {object} source 790 * A dictionary object with only one of these two attributes: 791 * - targetFront: a Target Front, if the resource is watched from the 792 * target process or thread. 793 * - watcherFront: a Watcher Front, if the resource is watched from 794 * the parent process. 795 * @param {Array<object>} updates 796 * Depending on the listener. 797 * 798 * Among the element in the array, the following attributes are given special handling. 799 * - resourceType {String}: 800 * The type of resource to be updated. 801 * - resourceId {String}: 802 * The id of resource to be updated. 803 * - resourceUpdates {Object}: 804 * If resourceUpdates is in the element, a cached resource specified by resourceType 805 * and resourceId is updated by Object.assign(cachedResource, resourceUpdates). 806 * - nestedResourceUpdates {Object}: 807 * If `nestedResourceUpdates` is passed, update one nested attribute with a new value 808 * This allows updating one attribute of an object stored in a resource's attribute, 809 * as well as adding new elements to arrays. 810 * `path` is an array mentioning all nested attribute to walk through. 811 * `value` is the new nested attribute value to set. 812 * 813 * And also, the element is passed to the listener as it is as “update” object. 814 * So if we don't want to update a cached resource but have information want to 815 * pass on to the listener, can pass it on using attributes other than the ones 816 * listed above. 817 * For example, if the element consists of like 818 * "{ resourceType:… resourceId:…, testValue: “test”, }”, 819 * the listener can receive the value as follows. 820 * 821 * onResourceUpdate({ update }) { 822 * console.log(update.testValue); // “test” should be displayed 823 * } 824 */ 825 async _onResourceUpdated({ targetFront, watcherFront }, updates) { 826 for (const update of updates) { 827 const { 828 resourceType, 829 resourceId, 830 resourceUpdates, 831 nestedResourceUpdates, 832 } = update; 833 834 if (!resourceId) { 835 console.warn(`Expected resource ${resourceType} to have a resourceId`); 836 } 837 838 // See _onResourceAvailableArray() 839 // We also need to wait for the related targetFront to be initialized 840 // otherwise we would notify about the update *before* it's available 841 // and the resource won't be in _cache. 842 if (watcherFront) { 843 try { 844 targetFront = await this._getTargetForWatcherResource(update); 845 } catch (e) { 846 if (this.#destroyed) { 847 // If the resource-command was destroyed while waiting for 848 // _getTargetForWatcherResource, swallow the error. 849 return; 850 } 851 throw e; 852 } 853 854 // When we receive the navigation request, the target front has already been 855 // destroyed, but this is fine. The cached resource has the reference to 856 // the (destroyed) target front and it is fully initialized. 857 if (targetFront) { 858 await targetFront.initialized; 859 } 860 } 861 862 const existingResource = this._cache.get( 863 cacheKey(resourceType, resourceId) 864 ); 865 if (!existingResource) { 866 continue; 867 } 868 869 if (resourceUpdates) { 870 Object.assign(existingResource, resourceUpdates); 871 } 872 873 if (nestedResourceUpdates) { 874 for (const { path, value } of nestedResourceUpdates) { 875 let target = existingResource; 876 877 for (let i = 0; i < path.length - 1; i++) { 878 target = target[path[i]]; 879 } 880 881 target[path[path.length - 1]] = value; 882 } 883 } 884 this._queueResourceEvent("updated", resourceType, [ 885 { 886 resource: existingResource, 887 update, 888 }, 889 ]); 890 } 891 892 this._throttledNotifyWatchers(); 893 } 894 895 /** 896 * Called every time a resource is destroyed in the remote target. 897 * 898 * @param {object} source 899 * A dictionary object with only one of these two attributes: 900 * - targetFront: a Target Front, if the resource is watched from the 901 * target process or thread. 902 * - watcherFront: a Watcher Front, if the resource is watched from 903 * the parent process. 904 * @param {Array<json/Front>} resources 905 * Depending on the resource Type, it can be an Array composed of 906 * either JSON objects or Fronts, which describes the resource. 907 */ 908 async _onResourceDestroyed({ targetFront }, resources) { 909 for (const resource of resources) { 910 const { resourceType, resourceId } = resource; 911 this._cache.delete(cacheKey(resourceType, resourceId)); 912 if (!resource.targetFront) { 913 resource.targetFront = targetFront; 914 } 915 this._queueResourceEvent("destroyed", resourceType, [resource]); 916 } 917 this._throttledNotifyWatchers(); 918 } 919 920 _queueResourceEvent(callbackType, resourceType, updates) { 921 for (const { resources, pendingEvents } of this._watchers) { 922 // This watcher doesn't listen to this type of resource 923 if (!resources.includes(resourceType)) { 924 continue; 925 } 926 // Avoid trying to coalesce with last pending event as mutating `updates` may have side effects 927 // with other watchers as this array is shared between all the watchers. 928 pendingEvents.push({ 929 callbackType, 930 updates, 931 }); 932 } 933 } 934 935 /** 936 * Flush the pending event and notify all the currently registered watchers 937 * about all the available, updated and destroyed events that have been accumulated in 938 * `_watchers`'s `pendingEvents` arrays. 939 */ 940 _notifyWatchers() { 941 for (const watcherEntry of this._watchers) { 942 const { onAvailable, onUpdated, onDestroyed, pendingEvents } = 943 watcherEntry; 944 // Immediately clear the buffer in order to avoid possible races, where an event listener 945 // would end up somehow adding a new throttled resource 946 watcherEntry.pendingEvents = []; 947 948 for (const { callbackType, updates } of pendingEvents) { 949 try { 950 if (callbackType == "available") { 951 onAvailable(updates, { areExistingResources: false }); 952 } else if (callbackType == "updated" && onUpdated) { 953 onUpdated(updates); 954 } else if (callbackType == "destroyed" && onDestroyed) { 955 onDestroyed(updates); 956 } 957 } catch (e) { 958 console.error( 959 "Exception while calling a ResourceCommand", 960 callbackType, 961 "callback", 962 ":", 963 e 964 ); 965 } 966 } 967 } 968 } 969 970 // Compute the target front if the resource comes from the Watcher Actor. 971 // (`targetFront` will be null as the watcher is in the parent process 972 // and targets are in distinct processes) 973 _getTargetForWatcherResource(resource) { 974 const { browsingContextID, innerWindowId, resourceType } = resource; 975 976 // Some privileged resources aren't related to any BrowsingContext 977 // and so aren't bound to any Target Front. 978 // Server watchers should pass an explicit "-1" value in order to prevent 979 // silently ignoring an undefined browsingContextID attribute. 980 if (browsingContextID == -1) { 981 return this.targetCommand.targetFront; 982 } 983 984 if (innerWindowId && this.targetCommand.isServerTargetSwitchingEnabled()) { 985 return this.watcherFront.getWindowGlobalTargetByInnerWindowId( 986 innerWindowId 987 ); 988 } else if (browsingContextID) { 989 return this.watcherFront.getWindowGlobalTarget(browsingContextID); 990 } 991 console.error( 992 `Resource of ${resourceType} is missing a browsingContextID or innerWindowId attribute` 993 ); 994 return null; 995 } 996 997 _onWillNavigate() { 998 // Special case for toolboxes debugging a document, 999 // purge the cache entirely when we start navigating to a new document. 1000 // Other toolboxes and additional target for remote iframes or content process 1001 // will be purge from onTargetDestroyed. 1002 1003 // NOTE: we could `clear` the cache here, but technically if anything is 1004 // currently iterating over resources provided by getAllResources, that 1005 // would interfere with their iteration. We just assign a new Map here to 1006 // leave those iterators as is. 1007 this._cache = new Map(); 1008 } 1009 1010 /** 1011 * Tells if the server supports listening to the given resource type 1012 * via the watcher actor's watchResources method. 1013 * 1014 * @return {boolean} True, if the server supports this type. 1015 */ 1016 hasResourceCommandSupport(resourceType) { 1017 return this.watcherFront?.traits?.resources?.[resourceType]; 1018 } 1019 1020 /** 1021 * Tells if the server supports listening to the given resource type 1022 * via the watcher actor's watchResources method, and that, for a specific 1023 * target. 1024 * 1025 * @return {boolean} True, if the server supports this type. 1026 */ 1027 _hasResourceCommandSupportForTarget(resourceType, targetFront) { 1028 // First check if the watcher supports this target type. 1029 // If it doesn't, no resource type can be listened via the Watcher actor for this target. 1030 if (!this.targetCommand.hasTargetWatcherSupport(targetFront.targetType)) { 1031 return false; 1032 } 1033 1034 return this.hasResourceCommandSupport(resourceType); 1035 } 1036 1037 _isValidResourceType(type) { 1038 return this.ALL_TYPES.includes(type); 1039 } 1040 1041 /** 1042 * Start listening for a given type of resource. 1043 * For backward compatibility code, we register the legacy listeners on 1044 * each individual target 1045 * 1046 * @param {string} resourceType 1047 * One string of ResourceCommand.TYPES, which designates the types of resources 1048 * to be listened. 1049 * @param {object} 1050 * - {Boolean} bypassListenerCount 1051 * Pass true to avoid checking/updating the listenersCount map. 1052 * Exclusively used when target switching, to stop & start listening 1053 * to all resources. 1054 */ 1055 async _startListening(resourceType, { bypassListenerCount = false } = {}) { 1056 if (!bypassListenerCount) { 1057 if (this._listenedResources.has(resourceType)) { 1058 return; 1059 } 1060 this._listenedResources.add(resourceType); 1061 } 1062 1063 this._processingExistingResources.add(resourceType); 1064 1065 // Ensuring enabling listening to targets. 1066 // This will be a no-op expect for the very first call to `_startListening`, 1067 // where it is going to call `onTargetAvailable` for all already existing targets, 1068 // as well as for those who will be created later. 1069 // 1070 // Do this *before* calling WatcherActor.watchResources in order to register "resource-available" 1071 // listeners on targets before these events start being emitted. 1072 await this._watchAllTargets(resourceType); 1073 1074 // When we are calling _startListening for the first time, _watchAllTargets 1075 // will register legacylistener when it will call onTargetAvailable for all existing targets. 1076 // But for any next calls to _startListening, _watchAllTargets will be a no-op, 1077 // and nothing will start legacy listener for each already registered targets. 1078 await this._startLegacyListenersForExistingTargets(resourceType); 1079 1080 // If the server supports the Watcher API and the Watcher supports 1081 // this resource type, use this API 1082 if (this.hasResourceCommandSupport(resourceType)) { 1083 await this.watcherFront.watchResources([resourceType]); 1084 } 1085 this._processingExistingResources.delete(resourceType); 1086 } 1087 1088 /** 1089 * Return true if the resource should be watched via legacy listener, 1090 * even when watcher supports this resource type. 1091 * 1092 * Bug 1678385: In order to support watching for JS Source resource 1093 * for service workers and parent process workers, which aren't supported yet 1094 * by the watcher actor, we do not bail out here and allow to execute 1095 * the legacy listener for these targets. 1096 * Once bug 1608848 is fixed, we can remove this and never trigger 1097 * the legacy listeners codepath for these resource types. 1098 * 1099 * If this isn't fixed soon, we may add other resources we want to see 1100 * being fetched from these targets. 1101 */ 1102 _shouldRunLegacyListenerEvenWithWatcherSupport(resourceType) { 1103 return WORKER_RESOURCE_TYPES.includes(resourceType); 1104 } 1105 1106 async _forwardExistingResources(resourceTypes, onAvailable) { 1107 const existingResources = []; 1108 for (const resource of this._cache.values()) { 1109 if (resourceTypes.includes(resource.resourceType)) { 1110 existingResources.push(resource); 1111 } 1112 } 1113 if (existingResources.length) { 1114 await onAvailable(existingResources, { areExistingResources: true }); 1115 } 1116 } 1117 1118 /** 1119 * Call backward compatibility code from `LegacyListeners` in order to listen for a given 1120 * type of resource from a given target. 1121 */ 1122 async _watchResourcesForTarget({ 1123 targetFront, 1124 resourceType, 1125 disableWarning = false, 1126 }) { 1127 if (this._hasResourceCommandSupportForTarget(resourceType, targetFront)) { 1128 // This resource / target pair should already be handled by the watcher, 1129 // no need to start legacy listeners. 1130 return; 1131 } 1132 1133 // All workers target types are still not supported by the watcher 1134 // so that we have to spawn legacy listener for all their resources. 1135 // But some resources are irrelevant to workers, like network events. 1136 // And we removed the related legacy listener as they are no longer used. 1137 if ( 1138 targetFront.targetType.endsWith("worker") && 1139 !WORKER_RESOURCE_TYPES.includes(resourceType) 1140 ) { 1141 return; 1142 } 1143 1144 if (targetFront.isDestroyed()) { 1145 return; 1146 } 1147 1148 const onAvailableArray = this._onResourceAvailableArray.bind(this, { 1149 targetFront, 1150 }); 1151 const onUpdatedArray = this._onResourceUpdatedArray.bind(this, { 1152 targetFront, 1153 }); 1154 const onDestroyedArray = this._onResourceDestroyedArray.bind(this, { 1155 targetFront, 1156 }); 1157 1158 if (!(resourceType in LegacyListeners)) { 1159 throw new Error(`Missing legacy listener for ${resourceType}`); 1160 } 1161 1162 const legacyListeners = 1163 this._existingLegacyListeners.get(targetFront) || []; 1164 if (legacyListeners.includes(resourceType)) { 1165 if (!disableWarning) { 1166 console.warn( 1167 `Already started legacy listener for ${resourceType} on ${targetFront.actorID}` 1168 ); 1169 } 1170 return; 1171 } 1172 this._existingLegacyListeners.set( 1173 targetFront, 1174 legacyListeners.concat(resourceType) 1175 ); 1176 1177 try { 1178 await LegacyListeners[resourceType]({ 1179 targetCommand: this.targetCommand, 1180 targetFront, 1181 onAvailableArray, 1182 onDestroyedArray, 1183 onUpdatedArray, 1184 }); 1185 } catch (e) { 1186 // Swallow the error to avoid breaking calls to watchResources which will 1187 // loop on all existing targets to create legacy listeners. 1188 // If a legacy listener fails to handle a target for some reason, we 1189 // should still try to process other targets as much as possible. 1190 // See Bug 1687645. 1191 console.error( 1192 `Failed to start [${resourceType}] legacy listener for target ${targetFront.actorID}`, 1193 e 1194 ); 1195 } 1196 } 1197 1198 /** 1199 * Reverse of _startListening. Stop listening for a given type of resource. 1200 * For backward compatibility, we unregister from each individual target. 1201 * 1202 * See _startListening for parameters description. 1203 */ 1204 _stopListening(resourceType, { bypassListenerCount = false } = {}) { 1205 if (!bypassListenerCount) { 1206 if (!this._listenedResources.has(resourceType)) { 1207 throw new Error( 1208 `Stopped listening for resource '${resourceType}' that isn't being listened to` 1209 ); 1210 } 1211 this._listenedResources.delete(resourceType); 1212 } 1213 1214 // Clear the cached resources of the type. 1215 for (const [key, resource] of this._cache) { 1216 if (resource.resourceType == resourceType) { 1217 // NOTE: To anyone paranoid like me, yes it is okay to delete from a Map while iterating it. 1218 this._cache.delete(key); 1219 } 1220 } 1221 1222 // If the server supports the Watcher API and the Watcher supports 1223 // this resource type, use this API 1224 if (this.hasResourceCommandSupport(resourceType)) { 1225 if (!this.watcherFront.isDestroyed()) { 1226 this.watcherFront.unwatchResources([resourceType]); 1227 } 1228 1229 const shouldRunLegacyListeners = 1230 this._shouldRunLegacyListenerEvenWithWatcherSupport(resourceType); 1231 if (!shouldRunLegacyListeners) { 1232 return; 1233 } 1234 } 1235 // Otherwise, fallback on backward compat mode and use LegacyListeners. 1236 1237 // If this was the last listener, we should stop watching these events from the actors 1238 // and the actors should stop watching things from the platform 1239 const targets = this.targetCommand.getAllTargets( 1240 this.targetCommand.ALL_TYPES 1241 ); 1242 for (const target of targets) { 1243 this._unwatchResourcesForTarget(target, resourceType); 1244 } 1245 } 1246 1247 /** 1248 * Backward compatibility code, reverse of _watchResourcesForTarget. 1249 */ 1250 _unwatchResourcesForTarget(targetFront, resourceType) { 1251 if (this._hasResourceCommandSupportForTarget(resourceType, targetFront)) { 1252 // This resource / target pair should already be handled by the watcher, 1253 // no need to stop legacy listeners. 1254 } 1255 // Is there really a point in: 1256 // - unregistering `onAvailable` RDP event callbacks from target-scoped actors? 1257 // - calling `stopListeners()` as we are most likely closing the toolbox and destroying everything? 1258 // 1259 // It is important to keep this method synchronous and do as less as possible 1260 // in the case of toolbox destroy. 1261 // 1262 // We are aware of one case where that might be useful. 1263 // When a panel is disabled via the options panel, after it has been opened. 1264 // Would that justify doing this? Is there another usecase? 1265 1266 // XXX: This is most likely only needed to avoid growing the Map infinitely. 1267 // Unless in the "disabled panel" use case mentioned in the comment above, 1268 // we should not see the same target actorID again. 1269 const listeners = this._existingLegacyListeners.get(targetFront); 1270 if (listeners && listeners.includes(resourceType)) { 1271 const remainingListeners = listeners.filter(l => l !== resourceType); 1272 this._existingLegacyListeners.set(targetFront, remainingListeners); 1273 } 1274 } 1275 } 1276 1277 const DOCUMENT_EVENT = "document-event"; 1278 ResourceCommand.TYPES = ResourceCommand.prototype.TYPES = { 1279 CONSOLE_MESSAGE: "console-message", 1280 CSS_CHANGE: "css-change", 1281 CSS_MESSAGE: "css-message", 1282 CSS_REGISTERED_PROPERTIES: "css-registered-properties", 1283 ERROR_MESSAGE: "error-message", 1284 PLATFORM_MESSAGE: "platform-message", 1285 DOCUMENT_EVENT, 1286 ROOT_NODE: "root-node", 1287 STYLESHEET: "stylesheet", 1288 NETWORK_EVENT: "network-event", 1289 WEBSOCKET: "websocket", 1290 WEBTRANSPORT: "webtransport", 1291 COOKIE: "cookies", 1292 LOCAL_STORAGE: "local-storage", 1293 SESSION_STORAGE: "session-storage", 1294 CACHE_STORAGE: "Cache", 1295 EXTENSION_STORAGE: "extension-storage", 1296 INDEXED_DB: "indexed-db", 1297 NETWORK_EVENT_STACKTRACE: "network-event-stacktrace", 1298 REFLOW: "reflow", 1299 SOURCE: "source", 1300 THREAD_STATE: "thread-state", 1301 JSTRACER_TRACE: "jstracer-trace", 1302 JSTRACER_STATE: "jstracer-state", 1303 SERVER_SENT_EVENT: "server-sent-event", 1304 LAST_PRIVATE_CONTEXT_EXIT: "last-private-context-exit", 1305 }; 1306 ResourceCommand.ALL_TYPES = ResourceCommand.prototype.ALL_TYPES = Object.values( 1307 ResourceCommand.TYPES 1308 ); 1309 module.exports = ResourceCommand; 1310 1311 // This is the list of resource types supported by workers. 1312 // We need such list to know when forcing to run the legacy listeners 1313 // and when to avoid try to spawn some unsupported ones for workers. 1314 const WORKER_RESOURCE_TYPES = [ 1315 ResourceCommand.TYPES.CONSOLE_MESSAGE, 1316 ResourceCommand.TYPES.ERROR_MESSAGE, 1317 ResourceCommand.TYPES.SOURCE, 1318 ResourceCommand.TYPES.THREAD_STATE, 1319 ]; 1320 1321 // List of resource types which aren't stored in the internal ResourceCommand cache. 1322 // Only the first `watchResources()` call for a given resource type may receive already existing 1323 // resources. All subsequent call to `watchResources()` for the same resource type will 1324 // only receive future resource, and not the one already notified in the past. 1325 // This is typically used for resources with very high throughput. 1326 const TRANSIENT_RESOURCE_TYPES = [ 1327 ResourceCommand.TYPES.JSTRACER_TRACE, 1328 ResourceCommand.TYPES.JSTRACER_STATE, 1329 ]; 1330 1331 // Backward compat code for each type of resource. 1332 // Each section added here should eventually be removed once the equivalent server 1333 // code is implement in Firefox, in its release channel. 1334 const LegacyListeners = { 1335 async [ResourceCommand.TYPES.DOCUMENT_EVENT]({ targetFront, onAvailable }) { 1336 // DocumentEventsListener of webconsole handles only top level document. 1337 if (!targetFront.isTopLevel) { 1338 return; 1339 } 1340 1341 const webConsoleFront = await targetFront.getFront("console"); 1342 webConsoleFront.on("documentEvent", event => { 1343 event.resourceType = ResourceCommand.TYPES.DOCUMENT_EVENT; 1344 onAvailable([event]); 1345 }); 1346 await webConsoleFront.startListeners(["DocumentEvents"]); 1347 }, 1348 }; 1349 loader.lazyRequireGetter( 1350 LegacyListeners, 1351 ResourceCommand.TYPES.CONSOLE_MESSAGE, 1352 "resource://devtools/shared/commands/resource/legacy-listeners/console-messages.js" 1353 ); 1354 loader.lazyRequireGetter( 1355 LegacyListeners, 1356 ResourceCommand.TYPES.CSS_MESSAGE, 1357 "resource://devtools/shared/commands/resource/legacy-listeners/css-messages.js" 1358 ); 1359 loader.lazyRequireGetter( 1360 LegacyListeners, 1361 ResourceCommand.TYPES.ERROR_MESSAGE, 1362 "resource://devtools/shared/commands/resource/legacy-listeners/error-messages.js" 1363 ); 1364 loader.lazyRequireGetter( 1365 LegacyListeners, 1366 ResourceCommand.TYPES.PLATFORM_MESSAGE, 1367 "resource://devtools/shared/commands/resource/legacy-listeners/platform-messages.js" 1368 ); 1369 loader.lazyRequireGetter( 1370 LegacyListeners, 1371 ResourceCommand.TYPES.ROOT_NODE, 1372 "resource://devtools/shared/commands/resource/legacy-listeners/root-node.js" 1373 ); 1374 1375 loader.lazyRequireGetter( 1376 LegacyListeners, 1377 ResourceCommand.TYPES.SOURCE, 1378 "resource://devtools/shared/commands/resource/legacy-listeners/source.js" 1379 ); 1380 loader.lazyRequireGetter( 1381 LegacyListeners, 1382 ResourceCommand.TYPES.THREAD_STATE, 1383 "resource://devtools/shared/commands/resource/legacy-listeners/thread-states.js" 1384 ); 1385 1386 loader.lazyRequireGetter( 1387 LegacyListeners, 1388 ResourceCommand.TYPES.REFLOW, 1389 "resource://devtools/shared/commands/resource/legacy-listeners/reflow.js" 1390 ); 1391 1392 // Optional transformers for each type of resource. 1393 // Each module added here should be a function that will receive the resource, the target, … 1394 // and perform some transformation on the resource before it will be emitted. 1395 // This is a good place to handle backward compatibility and manual resource marshalling. 1396 const ResourceTransformers = {}; 1397 1398 loader.lazyRequireGetter( 1399 ResourceTransformers, 1400 ResourceCommand.TYPES.CONSOLE_MESSAGE, 1401 "resource://devtools/shared/commands/resource/transformers/console-messages.js" 1402 ); 1403 loader.lazyRequireGetter( 1404 ResourceTransformers, 1405 ResourceCommand.TYPES.ERROR_MESSAGE, 1406 "resource://devtools/shared/commands/resource/transformers/error-messages.js" 1407 ); 1408 loader.lazyRequireGetter( 1409 ResourceTransformers, 1410 ResourceCommand.TYPES.CACHE_STORAGE, 1411 "resource://devtools/shared/commands/resource/transformers/storage-cache.js" 1412 ); 1413 loader.lazyRequireGetter( 1414 ResourceTransformers, 1415 ResourceCommand.TYPES.COOKIE, 1416 "resource://devtools/shared/commands/resource/transformers/storage-cookie.js" 1417 ); 1418 loader.lazyRequireGetter( 1419 ResourceTransformers, 1420 ResourceCommand.TYPES.EXTENSION_STORAGE, 1421 "resource://devtools/shared/commands/resource/transformers/storage-extension.js" 1422 ); 1423 loader.lazyRequireGetter( 1424 ResourceTransformers, 1425 ResourceCommand.TYPES.INDEXED_DB, 1426 "resource://devtools/shared/commands/resource/transformers/storage-indexed-db.js" 1427 ); 1428 loader.lazyRequireGetter( 1429 ResourceTransformers, 1430 ResourceCommand.TYPES.LOCAL_STORAGE, 1431 "resource://devtools/shared/commands/resource/transformers/storage-local-storage.js" 1432 ); 1433 loader.lazyRequireGetter( 1434 ResourceTransformers, 1435 ResourceCommand.TYPES.SESSION_STORAGE, 1436 "resource://devtools/shared/commands/resource/transformers/storage-session-storage.js" 1437 ); 1438 loader.lazyRequireGetter( 1439 ResourceTransformers, 1440 ResourceCommand.TYPES.NETWORK_EVENT, 1441 "resource://devtools/shared/commands/resource/transformers/network-events.js" 1442 ); 1443 loader.lazyRequireGetter( 1444 ResourceTransformers, 1445 ResourceCommand.TYPES.THREAD_STATE, 1446 "resource://devtools/shared/commands/resource/transformers/thread-states.js" 1447 );