Backed out 4 changesets (bug 1651522) for causing dt failures on devtools/shared...
[gecko.git] / devtools / server / actors / watcher.js
blob72b9654cecaa9b24ac15b94a6077ff404043989c
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/. */
5 "use strict";
6 const { Actor } = require("resource://devtools/shared/protocol.js");
7 const { watcherSpec } = require("resource://devtools/shared/specs/watcher.js");
9 const Resources = require("resource://devtools/server/actors/resources/index.js");
10 const { TargetActorRegistry } = ChromeUtils.importESModule(
11   "resource://devtools/server/actors/targets/target-actor-registry.sys.mjs",
12   {
13     loadInDevToolsLoader: false,
14   }
16 const { WatcherRegistry } = ChromeUtils.importESModule(
17   "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs",
18   {
19     // WatcherRegistry needs to be a true singleton and loads ActorManagerParent
20     // which also has to be a true singleton.
21     loadInDevToolsLoader: false,
22   }
24 const Targets = require("resource://devtools/server/actors/targets/index.js");
25 const { getAllBrowsingContextsForContext } = ChromeUtils.importESModule(
26   "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs"
29 const TARGET_HELPERS = {};
30 loader.lazyRequireGetter(
31   TARGET_HELPERS,
32   Targets.TYPES.FRAME,
33   "resource://devtools/server/actors/watcher/target-helpers/frame-helper.js"
35 loader.lazyRequireGetter(
36   TARGET_HELPERS,
37   Targets.TYPES.PROCESS,
38   "resource://devtools/server/actors/watcher/target-helpers/process-helper.js"
40 loader.lazyRequireGetter(
41   TARGET_HELPERS,
42   Targets.TYPES.WORKER,
43   "resource://devtools/server/actors/watcher/target-helpers/worker-helper.js"
46 loader.lazyRequireGetter(
47   this,
48   "NetworkParentActor",
49   "resource://devtools/server/actors/network-monitor/network-parent.js",
50   true
52 loader.lazyRequireGetter(
53   this,
54   "BlackboxingActor",
55   "resource://devtools/server/actors/blackboxing.js",
56   true
58 loader.lazyRequireGetter(
59   this,
60   "BreakpointListActor",
61   "resource://devtools/server/actors/breakpoint-list.js",
62   true
64 loader.lazyRequireGetter(
65   this,
66   "TargetConfigurationActor",
67   "resource://devtools/server/actors/target-configuration.js",
68   true
70 loader.lazyRequireGetter(
71   this,
72   "ThreadConfigurationActor",
73   "resource://devtools/server/actors/thread-configuration.js",
74   true
77 exports.WatcherActor = class WatcherActor extends Actor {
78   /**
79    * Initialize a new WatcherActor which is the main entry point to debug
80    * something. The main features of this actor are to:
81    * - observe targets related to the context we are debugging.
82    *   This is done via watchTargets/unwatchTargets methods, and
83    *   target-available-form/target-destroyed-form events.
84    * - observe resources related to the observed targets.
85    *   This is done via watchResources/unwatchResources methods, and
86    *   resource-available-form/resource-updated-form/resource-destroyed-form events.
87    *   Note that these events are also emited on both the watcher actor,
88    *   for resources observed from the parent process, as well as on the
89    *   target actors, when the resources are observed from the target's process or thread.
90    *
91    * @param {DevToolsServerConnection} conn
92    *        The connection to use in order to communicate back to the client.
93    * @param {object} sessionContext
94    *        The Session Context to help know what is debugged.
95    *        See devtools/server/actors/watcher/session-context.js
96    * @param {Number} sessionContext.browserId: If this is a "browser-element" context type,
97    *        the "browserId" of the <browser> element we would like to debug.
98    * @param {Boolean} sessionContext.isServerTargetSwitchingEnabled: Flag to to know if we should
99    *        spawn new top level targets for the debugged context.
100    */
101   constructor(conn, sessionContext) {
102     super(conn, watcherSpec);
103     this._sessionContext = sessionContext;
104     if (sessionContext.type == "browser-element") {
105       // Retrieve the <browser> element for the given browser ID
106       const browsingContext = BrowsingContext.getCurrentTopByBrowserId(
107         sessionContext.browserId
108       );
109       if (!browsingContext) {
110         throw new Error(
111           "Unable to retrieve the <browser> element for browserId=" +
112             sessionContext.browserId
113         );
114       }
115       this._browserElement = browsingContext.embedderElement;
116     }
118     // Sometimes we get iframe targets before the top-level targets
119     // mostly when doing bfcache navigations, lets cache the early iframes targets and
120     // flush them after the top-level target is available. See Bug 1726568 for details.
121     this._earlyIframeTargets = {};
123     // All currently available WindowGlobal target's form, keyed by `innerWindowId`.
124     //
125     // This helps to:
126     // - determine if the iframe targets are early or not.
127     //   i.e. if it is notified before its parent target is available.
128     // - notify the destruction of all children targets when a parent is destroyed.
129     //   i.e. have a reliable order of destruction between parent and children.
130     //
131     // Note that there should be just one top-level window target at a time,
132     // but there are certain cases when a new target is available before the
133     // old target is destroyed.
134     this._currentWindowGlobalTargets = new Map();
135   }
137   get sessionContext() {
138     return this._sessionContext;
139   }
141   /**
142    * If we are debugging only one Tab or Document, returns its BrowserElement.
143    * For Tabs, it will be the <browser> element used to load the web page.
144    *
145    * This is typicaly used to fetch:
146    * - its `browserId` attribute, which uniquely defines it,
147    * - its `browsingContextID` or `browsingContext`, which helps inspecting its content.
148    */
149   get browserElement() {
150     return this._browserElement;
151   }
153   getAllBrowsingContexts(options) {
154     return getAllBrowsingContextsForContext(this.sessionContext, options);
155   }
157   /**
158    * Helper to know if the context we are debugging has been already destroyed
159    */
160   isContextDestroyed() {
161     if (this.sessionContext.type == "browser-element") {
162       return !this.browserElement.browsingContext;
163     } else if (this.sessionContext.type == "webextension") {
164       return !BrowsingContext.get(this.sessionContext.addonBrowsingContextID);
165     } else if (this.sessionContext.type == "all") {
166       return false;
167     }
168     throw new Error(
169       "Unsupported session context type: " + this.sessionContext.type
170     );
171   }
173   destroy() {
174     // Force unwatching for all types, even if we weren't watching.
175     // This is fine as unwatchTarget is NOOP if we weren't already watching for this target type.
176     for (const targetType of Object.values(Targets.TYPES)) {
177       this.unwatchTargets(targetType);
178     }
179     this.unwatchResources(Object.values(Resources.TYPES));
181     WatcherRegistry.unregisterWatcher(this);
183     // Destroy the actor at the end so that its actorID keeps being defined.
184     super.destroy();
185   }
187   /*
188    * Get the list of the currently watched resources for this watcher.
189    *
190    * @return Array<String>
191    *         Returns the list of currently watched resource types.
192    */
193   get sessionData() {
194     return WatcherRegistry.getSessionData(this);
195   }
197   form() {
198     return {
199       actor: this.actorID,
200       // The resources and target traits should be removed all at the same time since the
201       // client has generic ways to deal with all of them (See Bug 1680280).
202       traits: {
203         ...this.sessionContext.supportedTargets,
204         resources: this.sessionContext.supportedResources,
205       },
206     };
207   }
209   /**
210    * Start watching for a new target type.
211    *
212    * This will instantiate Target Actors for existing debugging context of this type,
213    * but will also create actors as context of this type get created.
214    * The actors are notified to the client via "target-available-form" RDP events.
215    * We also notify about target actors destruction via "target-destroyed-form".
216    * Note that we are guaranteed to receive all existing target actor by the time this method
217    * resolves.
218    *
219    * @param {string} targetType
220    *        Type of context to observe. See Targets.TYPES object.
221    */
222   async watchTargets(targetType) {
223     WatcherRegistry.watchTargets(this, targetType);
225     const targetHelperModule = TARGET_HELPERS[targetType];
226     // Await the registration in order to ensure receiving the already existing targets
227     await targetHelperModule.createTargets(this);
228   }
230   /**
231    * Stop watching for a given target type.
232    *
233    * @param {string} targetType
234    *        Type of context to observe. See Targets.TYPES object.
235    * @param {object} options
236    * @param {boolean} options.isModeSwitching
237    *        true when this is called as the result of a change to the devtools.browsertoolbox.scope pref
238    */
239   unwatchTargets(targetType, options = {}) {
240     const isWatchingTargets = WatcherRegistry.unwatchTargets(
241       this,
242       targetType,
243       options
244     );
245     if (!isWatchingTargets) {
246       return;
247     }
249     const targetHelperModule = TARGET_HELPERS[targetType];
250     targetHelperModule.destroyTargets(this, options);
252     // Unregister the JS Window Actor if there is no more DevTools code observing any target/resource,
253     // unless we're switching mode (having both condition at the same time should only
254     // happen in tests).
255     if (!options.isModeSwitching) {
256       WatcherRegistry.maybeUnregisteringJSWindowActor();
257     }
258   }
260   /**
261    * Flush any early iframe targets relating to this top level
262    * window target.
263    * @param {number} topInnerWindowID
264    */
265   _flushIframeTargets(topInnerWindowID) {
266     while (this._earlyIframeTargets[topInnerWindowID]?.length > 0) {
267       const actor = this._earlyIframeTargets[topInnerWindowID].shift();
268       this.emit("target-available-form", actor);
269     }
270   }
272   /**
273    * Called by a Watcher module, whenever a new target is available
274    */
275   notifyTargetAvailable(actor) {
276     // Emit immediately for worker, process & extension targets
277     // as they don't have a parent browsing context.
278     if (!actor.traits?.isBrowsingContext) {
279       this.emit("target-available-form", actor);
280       return;
281     }
283     // If isBrowsingContext trait is true, we are processing a WindowGlobalTarget.
284     // (this trait should be renamed)
285     this._currentWindowGlobalTargets.set(actor.innerWindowId, actor);
287     // The top-level is always the same for the browser-toolbox
288     if (this.sessionContext.type == "all") {
289       this.emit("target-available-form", actor);
290       return;
291     }
293     if (actor.isTopLevelTarget) {
294       this.emit("target-available-form", actor);
295       // Flush any existing early iframe targets
296       this._flushIframeTargets(actor.innerWindowId);
297     } else if (this._currentWindowGlobalTargets.has(actor.topInnerWindowId)) {
298       // Emit the event immediately if the top-level target is already available
299       this.emit("target-available-form", actor);
300     } else if (this._earlyIframeTargets[actor.topInnerWindowId]) {
301       // Add the early iframe target to the list of other early targets.
302       this._earlyIframeTargets[actor.topInnerWindowId].push(actor);
303     } else {
304       // Set the first early iframe target
305       this._earlyIframeTargets[actor.topInnerWindowId] = [actor];
306     }
307   }
309   /**
310    * Called by a Watcher module, whenever a target has been destroyed
311    *
312    * @param {object} actor
313    *        the actor form of the target being destroyed
314    * @param {object} options
315    * @param {boolean} options.isModeSwitching
316    *        true when this is called as the result of a change to the devtools.browsertoolbox.scope pref
317    */
318   async notifyTargetDestroyed(actor, options = {}) {
319     // Emit immediately for worker, process & extension targets
320     // as they don't have a parent browsing context.
321     if (!actor.innerWindowId) {
322       this.emit("target-destroyed-form", actor, options);
323       return;
324     }
325     // Flush all iframe targets if we are destroying a top level target.
326     if (actor.isTopLevelTarget) {
327       // First compute the list of children actors, as notifyTargetDestroy will mutate _currentWindowGlobalTargets
328       const childrenActors = [
329         ...this._currentWindowGlobalTargets.values(),
330       ].filter(
331         form =>
332           form.topInnerWindowId == actor.innerWindowId &&
333           // Ignore the top level target itself, because its topInnerWindowId will be its innerWindowId
334           form.innerWindowId != actor.innerWindowId
335       );
336       childrenActors.map(form => this.notifyTargetDestroyed(form, options));
337     }
338     if (this._earlyIframeTargets[actor.innerWindowId]) {
339       delete this._earlyIframeTargets[actor.innerWindowId];
340     }
341     this._currentWindowGlobalTargets.delete(actor.innerWindowId);
342     const documentEventWatcher = Resources.getResourceWatcher(
343       this,
344       Resources.TYPES.DOCUMENT_EVENT
345     );
346     // If we have a Watcher class instantiated, ensure that target-destroyed is sent
347     // *after* DOCUMENT_EVENT's will-navigate. Otherwise this resource will have an undefined
348     // `targetFront` attribute, as it is associated with the target from which we navigate
349     // and not the one we navigate to.
350     //
351     // About documentEventWatcher check: We won't have any watcher class if we aren't
352     // using server side Watcher classes.
353     // i.e. when we are using the legacy listener for DOCUMENT_EVENT.
354     // This is still the case for all toolboxes but the one for local and remote tabs.
355     //
356     // About isServerTargetSwitchingEnabled check: if we are using the watcher class
357     // we may still use client side target, which will still use legacy listeners for
358     // will-navigate and so will-navigate will be emitted by the target actor itself.
359     //
360     // About isTopLevelTarget check: only top level targets emit will-navigate,
361     // so there is no reason to delay target-destroy for remote iframes.
362     if (
363       documentEventWatcher &&
364       this.sessionContext.isServerTargetSwitchingEnabled &&
365       actor.isTopLevelTarget
366     ) {
367       await documentEventWatcher.onceWillNavigateIsEmitted(actor.innerWindowId);
368     }
369     this.emit("target-destroyed-form", actor, options);
370   }
372   /**
373    * Given a browsingContextID, returns its parent browsingContextID. Returns null if a
374    * parent browsing context couldn't be found. Throws if the browsing context
375    * corresponding to the passed browsingContextID couldn't be found.
376    *
377    * @param {Integer} browsingContextID
378    * @returns {Integer|null}
379    */
380   getParentBrowsingContextID(browsingContextID) {
381     const browsingContext = BrowsingContext.get(browsingContextID);
382     if (!browsingContext) {
383       throw new Error(
384         `BrowsingContext with ID=${browsingContextID} doesn't exist.`
385       );
386     }
387     // Top-level documents of tabs, loaded in a <browser> element expose a null `parent`.
388     // i.e. Their BrowsingContext has no parent and is considered top level.
389     // But... in the context of the Browser Toolbox, we still consider them as child of the browser window.
390     // So, for them, fallback on `embedderWindowGlobal`, which will typically be the WindowGlobal for browser.xhtml.
391     if (browsingContext.parent) {
392       return browsingContext.parent.id;
393     }
394     if (browsingContext.embedderWindowGlobal) {
395       return browsingContext.embedderWindowGlobal.browsingContext.id;
396     }
397     return null;
398   }
400   /**
401    * Called by Resource Watchers, when new resources are available, updated or destroyed.
402    *
403    * @param String updateType
404    *        Can be "available", "updated" or "destroyed"
405    * @param Array<json> resources
406    *        List of all resource's form. A resource is a JSON object piped over to the client.
407    *        It can contain actor IDs, actor forms, to be manually marshalled by the client.
408    */
409   notifyResources(updateType, resources) {
410     if (resources.length === 0) {
411       // Don't try to emit if the resources array is empty.
412       return;
413     }
415     if (this.sessionContext.type == "webextension") {
416       this._overrideResourceBrowsingContextForWebExtension(resources);
417     }
419     this.emit(`resource-${updateType}-form`, resources);
420   }
422   /**
423    * For WebExtension, we have to hack all resource's browsingContextID
424    * in order to ensure emitting them with the fixed, original browsingContextID
425    * related to the fallback document created by devtools which always exists.
426    * The target's form will always be relating to that BrowsingContext IDs (browsing context ID and inner window id).
427    * Even if the target switches internally to another document via WindowGlobalTargetActor._setWindow.
428    *
429    * @param {Array<Objects>} List of resources
430    */
431   _overrideResourceBrowsingContextForWebExtension(resources) {
432     resources.forEach(resource => {
433       resource.browsingContextID = this.sessionContext.addonBrowsingContextID;
434     });
435   }
437   /**
438    * Try to retrieve a parent process TargetActor which is ignored by the
439    * TARGET_HELPERS. Examples:
440    * - top level target for the browser toolbox
441    * - xpcshell target for xpcshell debugging
442    *
443    * See comment in `watchResources`.
444    *
445    * @return {TargetActor|null} Matching target actor if any, null otherwise.
446    */
447   getTargetActorInParentProcess() {
448     if (TargetActorRegistry.xpcShellTargetActor) {
449       return TargetActorRegistry.xpcShellTargetActor;
450     }
452     // Note: For browser-element debugging, the WindowGlobalTargetActor returned here is created
453     // for a parent process page and lives in the parent process.
454     const actors = TargetActorRegistry.getTargetActors(
455       this.sessionContext,
456       this.conn.prefix
457     );
459     switch (this.sessionContext.type) {
460       case "all":
461         return actors.find(actor => actor.typeName === "parentProcessTarget");
462       case "browser-element":
463       case "webextension":
464         // All target actors for browser-element and webextension sessions
465         // should be created using the JS Window actors.
466         return null;
467       default:
468         throw new Error(
469           "Unsupported session context type: " + this.sessionContext.type
470         );
471     }
472   }
474   /**
475    * Start watching for a list of resource types.
476    * This should only resolve once all "already existing" resources of these types
477    * are notified to the client via resource-available-form event on related target actors.
478    *
479    * @param {Array<string>} resourceTypes
480    *        List of all types to listen to.
481    */
482   async watchResources(resourceTypes) {
483     // First process resources which have to be listened from the parent process
484     // (the watcher actor always runs in the parent process)
485     await Resources.watchResources(
486       this,
487       Resources.getParentProcessResourceTypes(resourceTypes)
488     );
490     // Bail out early if all resources were watched from parent process.
491     // In this scenario, we do not need to update these resource types in the WatcherRegistry
492     // as targets do not care about them.
493     if (!Resources.hasResourceTypesForTargets(resourceTypes)) {
494       return;
495     }
497     WatcherRegistry.watchResources(this, resourceTypes);
499     // Fetch resources from all existing targets
500     for (const targetType in TARGET_HELPERS) {
501       // We process frame targets even if we aren't watching them,
502       // because frame target helper codepath handles the top level target, if it runs in the *content* process.
503       // It will do another check to `isWatchingTargets(FRAME)` internally.
504       // Note that the workaround at the end of this method, using TargetActorRegistry
505       // is specific to top level target running in the *parent* process.
506       if (
507         !WatcherRegistry.isWatchingTargets(this, targetType) &&
508         targetType != Targets.TYPES.FRAME
509       ) {
510         continue;
511       }
512       const targetResourceTypes = Resources.getResourceTypesForTargetType(
513         resourceTypes,
514         targetType
515       );
516       if (!targetResourceTypes.length) {
517         continue;
518       }
519       const targetHelperModule = TARGET_HELPERS[targetType];
520       await targetHelperModule.addOrSetSessionDataEntry({
521         watcher: this,
522         type: "resources",
523         entries: targetResourceTypes,
524         updateType: "add",
525       });
526     }
528     /*
529      * The Watcher actor doesn't support watching the top level target
530      * (bug 1644397 and possibly some other followup).
531      *
532      * Because of that, we miss reaching these targets in the previous lines of this function.
533      * Since all BrowsingContext target actors register themselves to the TargetActorRegistry,
534      * we use it here in order to reach those missing targets, which are running in the
535      * parent process (where this WatcherActor lives as well):
536      *  - the parent process target (which inherits from WindowGlobalTargetActor)
537      *  - top level tab target for documents loaded in the parent process (e.g. about:robots).
538      *    When the tab loads document in the content process, the FrameTargetHelper will
539      *    reach it via the JSWindowActor API. Even if it uses MessageManager for anything
540      *    else (RDP packet forwarding, creation and destruction).
541      *
542      * We will eventually get rid of this code once all targets are properly supported by
543      * the Watcher Actor and we have target helpers for all of them.
544      */
545     const targetActor = this.getTargetActorInParentProcess();
546     if (targetActor) {
547       const targetActorResourceTypes = Resources.getResourceTypesForTargetType(
548         resourceTypes,
549         targetActor.targetType
550       );
551       await targetActor.addOrSetSessionDataEntry(
552         "resources",
553         targetActorResourceTypes,
554         false,
555         "add"
556       );
557     }
558   }
560   /**
561    * Stop watching for a list of resource types.
562    *
563    * @param {Array<string>} resourceTypes
564    *        List of all types to listen to.
565    */
566   unwatchResources(resourceTypes) {
567     // First process resources which are listened from the parent process
568     // (the watcher actor always runs in the parent process)
569     Resources.unwatchResources(
570       this,
571       Resources.getParentProcessResourceTypes(resourceTypes)
572     );
574     // Bail out early if all resources were all watched from parent process.
575     // In this scenario, we do not need to update these resource types in the WatcherRegistry
576     // as targets do not care about them.
577     if (!Resources.hasResourceTypesForTargets(resourceTypes)) {
578       return;
579     }
581     const isWatchingResources = WatcherRegistry.unwatchResources(
582       this,
583       resourceTypes
584     );
585     if (!isWatchingResources) {
586       return;
587     }
589     // Prevent trying to unwatch when the related BrowsingContext has already
590     // been destroyed
591     if (!this.isContextDestroyed()) {
592       for (const targetType in TARGET_HELPERS) {
593         // Frame target helper handles the top level target, if it runs in the content process
594         // so we should always process it. It does a second check to isWatchingTargets.
595         if (
596           !WatcherRegistry.isWatchingTargets(this, targetType) &&
597           targetType != Targets.TYPES.FRAME
598         ) {
599           continue;
600         }
601         const targetResourceTypes = Resources.getResourceTypesForTargetType(
602           resourceTypes,
603           targetType
604         );
605         if (!targetResourceTypes.length) {
606           continue;
607         }
608         const targetHelperModule = TARGET_HELPERS[targetType];
609         targetHelperModule.removeSessionDataEntry({
610           watcher: this,
611           type: "resources",
612           entries: targetResourceTypes,
613         });
614       }
615     }
617     // See comment in watchResources.
618     const targetActor = this.getTargetActorInParentProcess();
619     if (targetActor) {
620       const targetActorResourceTypes = Resources.getResourceTypesForTargetType(
621         resourceTypes,
622         targetActor.targetType
623       );
624       targetActor.removeSessionDataEntry("resources", targetActorResourceTypes);
625     }
627     // Unregister the JS Window Actor if there is no more DevTools code observing any target/resource
628     WatcherRegistry.maybeUnregisteringJSWindowActor();
629   }
631   clearResources(resourceTypes) {
632     // First process resources which have to be listened from the parent process
633     // (the watcher actor always runs in the parent process)
634     // TODO: content process / worker thread resources are not cleared. See Bug 1774573
635     Resources.clearResources(
636       this,
637       Resources.getParentProcessResourceTypes(resourceTypes)
638     );
639   }
641   /**
642    * Returns the network actor.
643    *
644    * @return {Object} actor
645    *        The network actor.
646    */
647   getNetworkParentActor() {
648     if (!this._networkParentActor) {
649       this._networkParentActor = new NetworkParentActor(this);
650     }
652     return this._networkParentActor;
653   }
655   /**
656    * Returns the blackboxing actor.
657    *
658    * @return {Object} actor
659    *        The blackboxing actor.
660    */
661   getBlackboxingActor() {
662     if (!this._blackboxingActor) {
663       this._blackboxingActor = new BlackboxingActor(this);
664     }
666     return this._blackboxingActor;
667   }
669   /**
670    * Returns the breakpoint list actor.
671    *
672    * @return {Object} actor
673    *        The breakpoint list actor.
674    */
675   getBreakpointListActor() {
676     if (!this._breakpointListActor) {
677       this._breakpointListActor = new BreakpointListActor(this);
678     }
680     return this._breakpointListActor;
681   }
683   /**
684    * Returns the target configuration actor.
685    *
686    * @return {Object} actor
687    *        The configuration actor.
688    */
689   getTargetConfigurationActor() {
690     if (!this._targetConfigurationListActor) {
691       this._targetConfigurationListActor = new TargetConfigurationActor(this);
692     }
693     return this._targetConfigurationListActor;
694   }
696   /**
697    * Returns the thread configuration actor.
698    *
699    * @return {Object} actor
700    *        The configuration actor.
701    */
702   getThreadConfigurationActor() {
703     if (!this._threadConfigurationListActor) {
704       this._threadConfigurationListActor = new ThreadConfigurationActor(this);
705     }
706     return this._threadConfigurationListActor;
707   }
709   /**
710    * Server internal API, called by other actors, but not by the client.
711    * Used to agrement some new entries for a given data type (watchers target, resources,
712    * breakpoints,...)
713    *
714    * @param {String} type
715    *        Data type to contribute to.
716    * @param {Array<*>} entries
717    *        List of values to add or set for this data type.
718    * @param {String} updateType
719    *        "add" will only add the new entries in the existing data set.
720    *        "set" will update the data set with the new entries.
721    */
722   async addOrSetDataEntry(type, entries, updateType) {
723     WatcherRegistry.addOrSetSessionDataEntry(this, type, entries, updateType);
725     await Promise.all(
726       Object.values(Targets.TYPES)
727         .filter(
728           targetType =>
729             // We process frame targets even if we aren't watching them,
730             // because frame target helper codepath handles the top level target, if it runs in the *content* process.
731             // It will do another check to `isWatchingTargets(FRAME)` internally.
732             // Note that the workaround at the end of this method, using TargetActorRegistry
733             // is specific to top level target running in the *parent* process.
734             WatcherRegistry.isWatchingTargets(this, targetType) ||
735             targetType === Targets.TYPES.FRAME
736         )
737         .map(async targetType => {
738           const targetHelperModule = TARGET_HELPERS[targetType];
739           await targetHelperModule.addOrSetSessionDataEntry({
740             watcher: this,
741             type,
742             entries,
743             updateType,
744           });
745         })
746     );
748     // See comment in watchResources
749     const targetActor = this.getTargetActorInParentProcess();
750     if (targetActor) {
751       await targetActor.addOrSetSessionDataEntry(
752         type,
753         entries,
754         false,
755         updateType
756       );
757     }
758   }
760   /**
761    * Server internal API, called by other actors, but not by the client.
762    * Used to remve some existing entries for a given data type (watchers target, resources,
763    * breakpoints,...)
764    *
765    * @param {String} type
766    *        Data type to modify.
767    * @param {Array<*>} entries
768    *        List of values to remove from this data type.
769    */
770   removeDataEntry(type, entries) {
771     WatcherRegistry.removeSessionDataEntry(this, type, entries);
773     Object.values(Targets.TYPES)
774       .filter(
775         targetType =>
776           // See comment in addOrSetDataEntry
777           WatcherRegistry.isWatchingTargets(this, targetType) ||
778           targetType === Targets.TYPES.FRAME
779       )
780       .forEach(targetType => {
781         const targetHelperModule = TARGET_HELPERS[targetType];
782         targetHelperModule.removeSessionDataEntry({
783           watcher: this,
784           type,
785           entries,
786         });
787       });
789     // See comment in addOrSetDataEntry
790     const targetActor = this.getTargetActorInParentProcess();
791     if (targetActor) {
792       targetActor.removeSessionDataEntry(type, entries);
793     }
794   }
796   /**
797    * Retrieve the current watched data for the provided type.
798    *
799    * @param {String} type
800    *        Data type to retrieve.
801    */
802   getSessionDataForType(type) {
803     return this.sessionData?.[type];
804   }