summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/targets/base-target-actor.js
blob: f3fc2a89e718e928d3903082ed2af0a39941624d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { Actor } = require("resource://devtools/shared/protocol.js");
const {
  TYPES,
  getResourceWatcher,
} = require("resource://devtools/server/actors/resources/index.js");
const Targets = require("devtools/server/actors/targets/index");

loader.lazyRequireGetter(
  this,
  "SessionDataProcessors",
  "resource://devtools/server/actors/targets/session-data-processors/index.js",
  true
);

class BaseTargetActor extends Actor {
  constructor(conn, targetType, spec) {
    super(conn, spec);

    /**
     * Type of target, a string of Targets.TYPES.
     * @return {string}
     */
    this.targetType = targetType;
  }

  /**
   * Process a new data entry, which can be watched resources, breakpoints, ...
   *
   * @param string type
   *        The type of data to be added
   * @param Array<Object> entries
   *        The values to be added to this type of data
   * @param Boolean isDocumentCreation
   *        Set to true if this function is called just after a new document (and its
   *        associated target) is created.
   * @param String updateType
   *        "add" will only add the new entries in the existing data set.
   *        "set" will update the data set with the new entries.
   */
  async addOrSetSessionDataEntry(
    type,
    entries,
    isDocumentCreation = false,
    updateType
  ) {
    const processor = SessionDataProcessors[type];
    if (processor) {
      await processor.addOrSetSessionDataEntry(
        this,
        entries,
        isDocumentCreation,
        updateType
      );
    }
  }

  /**
   * Remove data entries that have been previously added via addOrSetSessionDataEntry
   *
   * See addOrSetSessionDataEntry for argument description.
   */
  removeSessionDataEntry(type, entries) {
    const processor = SessionDataProcessors[type];
    if (processor) {
      processor.removeSessionDataEntry(this, entries);
    }
  }

  /**
   * Called by Resource Watchers, when new resources are available, updated or destroyed.
   *
   * @param String updateType
   *        Can be "available", "updated" or "destroyed"
   * @param Array<json> resources
   *        List of all resource's form. A resource is a JSON object piped over to the client.
   *        It can contain actor IDs, actor forms, to be manually marshalled by the client.
   */
  notifyResources(updateType, resources) {
    if (resources.length === 0 || this.isDestroyed()) {
      // Don't try to emit if the resources array is empty or the actor was
      // destroyed.
      return;
    }

    if (this.devtoolsSpawnedBrowsingContextForWebExtension) {
      this.overrideResourceBrowsingContextForWebExtension(resources);
    }

    this.emit(`resource-${updateType}-form`, resources);
  }

  /**
   * For WebExtension, we have to hack all resource's browsingContextID
   * in order to ensure emitting them with the fixed, original browsingContextID
   * related to the fallback document created by devtools which always exists.
   * The target's form will always be relating to that BrowsingContext IDs (browsing context ID and inner window id).
   * Even if the target switches internally to another document via WindowGlobalTargetActor._setWindow.
   *
   * @param {Array<Objects>} List of resources
   */
  overrideResourceBrowsingContextForWebExtension(resources) {
    const browsingContextID =
      this.devtoolsSpawnedBrowsingContextForWebExtension.id;
    resources.forEach(
      resource => (resource.browsingContextID = browsingContextID)
    );
  }

  // List of actor prefixes (string) which have already been instantiated via getTargetScopedActor method.
  #instantiatedTargetScopedActors = new Set();

  /**
   * Try to return any target scoped actor instance, if it exists.
   * They are lazily instantiated and so will only be available
   * if the client called at least one of their method.
   *
   * @param {String} prefix
   *        Prefix for the actor we would like to retrieve.
   *        Defined in devtools/server/actors/utils/actor-registry.js
   */
  getTargetScopedActor(prefix) {
    if (this.isDestroyed()) {
      return null;
    }
    const form = this.form();
    this.#instantiatedTargetScopedActors.add(prefix);
    return this.conn._getOrCreateActor(form[prefix + "Actor"]);
  }

  /**
   * Returns true, if the related target scoped actor has already been queried
   * and instantiated via `getTargetScopedActor` method.
   *
   * @param {String} prefix
   *        See getTargetScopedActor definition
   * @return Boolean
   *         True, if the actor has already been instantiated.
   */
  hasTargetScopedActor(prefix) {
    return this.#instantiatedTargetScopedActors.has(prefix);
  }

  /**
   * Apply target-specific options.
   *
   * This will be called by the watcher when the DevTools target-configuration
   * is updated, or when a target is created via JSWindowActors.
   *
   * @param {JSON} options
   *        Configuration object provided by the client.
   *        See target-configuration actor.
   * @param {Boolean} calledFromDocumentCreate
   *        True, when this is called with initial configuration when the related target
   *        actor is instantiated.
   */
  updateTargetConfiguration(options = {}, calledFromDocumentCreation = false) {
    // If there is some tracer options, we should start tracing, otherwise we should stop (if we were)
    if (options.tracerOptions) {
      // Ignore the SessionData update if the user requested to start the tracer on next page load and:
      //   - we apply it to an already loaded WindowGlobal,
      //   - the target isn't the top level one.
      if (
        options.tracerOptions.traceOnNextLoad &&
        (!calledFromDocumentCreation || !this.isTopLevelTarget)
      ) {
        if (this.isTopLevelTarget) {
          const consoleMessageWatcher = getResourceWatcher(
            this,
            TYPES.CONSOLE_MESSAGE
          );
          if (consoleMessageWatcher) {
            consoleMessageWatcher.emitMessages([
              {
                arguments: [
                  "Waiting for next navigation or page reload before starting tracing",
                ],
                styles: [],
                level: "jstracer",
                chromeContext: false,
                timeStamp: ChromeUtils.dateNow(),
              },
            ]);
          }
        }
        return;
      }
      // Bug 1874204: For now, in the browser toolbox, only frame and workers are traced.
      // Content process targets are ignored as they would also include each document/frame target.
      // This would require some work to ignore FRAME targets from here, only in case of browser toolbox,
      // and also handle all content process documents for DOM Event logging.
      //
      // Bug 1874219: Also ignore extensions for now as they are all running in the same process,
      // whereas we can only spawn one tracer per thread.
      if (
        this.targetType == Targets.TYPES.PROCESS ||
        this.url?.startsWith("moz-extension://")
      ) {
        return;
      }
      const tracerActor = this.getTargetScopedActor("tracer");
      tracerActor.startTracing(options.tracerOptions);
    } else if (this.hasTargetScopedActor("tracer")) {
      const tracerActor = this.getTargetScopedActor("tracer");
      tracerActor.stopTracing();
    }
  }
}
exports.BaseTargetActor = BaseTargetActor;