summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/watcher/target-helpers/service-worker-helper.js
blob: 53fceead17b0569601293fca649e2810e846b8d2 (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
215
216
217
218
219
220
/* 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 { waitForTick } = require("resource://devtools/shared/DevToolsUtils.js");

const PROCESS_SCRIPT_URL =
  "resource://devtools/server/actors/watcher/target-helpers/service-worker-jsprocessactor-startup.js";

const PROCESS_ACTOR_NAME = "DevToolsServiceWorker";
const PROCESS_ACTOR_OPTIONS = {
  // Ignore the parent process.
  includeParent: false,

  parent: {
    esModuleURI:
      "resource://devtools/server/connectors/process-actor/DevToolsServiceWorkerParent.sys.mjs",
  },

  child: {
    esModuleURI:
      "resource://devtools/server/connectors/process-actor/DevToolsServiceWorkerChild.sys.mjs",

    observers: [
      // Tried various notification to ensure starting the actor
      // from webServiceWorker processes... but none of them worked.
      /*
      "chrome-event-target-created",
      "webnavigation-create",
      "chrome-webnavigation-create",
      "webnavigation-destroy",
      "chrome-webnavigation-destroy",
      "browsing-context-did-set-embedder",
      "browsing-context-discarded",
      "ipc:content-initializing",
      "ipc:content-created",
      */

      // Fallback on firing a very custom notification from a "process script" (loadProcessScript)
      "init-devtools-service-worker-actor",
    ],
  },
};

// List of all active watchers
const gWatchers = new Set();

/**
 * Register the DevToolsServiceWorker JS Process Actor,
 * if we are registering the first watcher actor.
 *
 * @param {Watcher Actor} watcher
 */
function maybeRegisterProcessActor(watcher) {
  const sizeBefore = gWatchers.size;
  gWatchers.add(watcher);

  if (sizeBefore == 0 && gWatchers.size == 1) {
    ChromeUtils.registerProcessActor(PROCESS_ACTOR_NAME, PROCESS_ACTOR_OPTIONS);

    // For some reason JSProcessActor doesn't work out of the box for `webServiceWorker` content processes.
    // So manually spawn our JSProcessActor from a process script emitting an observer service notification...
    // The Process script are correctly executed on all process types during their early startup.
    Services.ppmm.loadProcessScript(PROCESS_SCRIPT_URL, true);
  }
}

/**
 * Unregister the DevToolsServiceWorker JS Process Actor,
 * if we are unregistering the last watcher actor.
 *
 * @param {Watcher Actor} watcher
 */
function maybeUnregisterProcessActor(watcher) {
  const sizeBefore = gWatchers.size;
  gWatchers.delete(watcher);

  if (sizeBefore == 1 && gWatchers.size == 0) {
    ChromeUtils.unregisterProcessActor(
      PROCESS_ACTOR_NAME,
      PROCESS_ACTOR_OPTIONS
    );

    Services.ppmm.removeDelayedProcessScript(PROCESS_SCRIPT_URL);
  }
}

/**
 * Return the list of all DOM Processes except the one for the parent process
 *
 * @return Array<nsIDOMProcessParent>
 */
function getAllContentProcesses() {
  return ChromeUtils.getAllDOMProcesses().filter(
    process => process.childID !== 0
  );
}

/**
 * Force creating targets for all existing service workers for a given Watcher Actor.
 *
 * @param WatcherActor watcher
 *        The Watcher Actor requesting to watch for new targets.
 */
async function createTargets(watcher) {
  maybeRegisterProcessActor(watcher);
  // Go over all existing content process in order to:
  // - Force the instantiation of a DevToolsServiceWorkerChild
  // - Have the DevToolsServiceWorkerChild to spawn the WorkerTargetActors

  const promises = [];
  for (const process of getAllContentProcesses()) {
    const promise = process
      .getActor(PROCESS_ACTOR_NAME)
      .instantiateServiceWorkerTargets({
        watcherActorID: watcher.actorID,
        connectionPrefix: watcher.conn.prefix,
        sessionContext: watcher.sessionContext,
        sessionData: watcher.sessionData,
      });
    promises.push(promise);
  }

  // Await for the different queries in order to try to resolve only *after* we received
  // the already available worker targets.
  return Promise.all(promises);
}

/**
 * Force destroying all worker targets which were related to a given watcher.
 *
 * @param WatcherActor watcher
 *        The Watcher Actor requesting to stop watching for new targets.
 */
async function destroyTargets(watcher) {
  // Go over all existing content processes in order to destroy all targets
  for (const process of getAllContentProcesses()) {
    let processActor;
    try {
      processActor = process.getActor(PROCESS_ACTOR_NAME);
    } catch (e) {
      // Ignore any exception during destroy as we may be closing firefox/devtools/tab
      // and that can easily lead to many exceptions.
      continue;
    }

    processActor.destroyServiceWorkerTargets({
      watcherActorID: watcher.actorID,
      sessionContext: watcher.sessionContext,
    });
  }

  // browser_dbg-breakpoints-columns.js crashes if we unregister the Process Actor
  // in the same event loop as we call destroyServiceWorkerTargets.
  await waitForTick();

  maybeUnregisterProcessActor(watcher);
}

/**
 * Go over all existing JSProcessActor in order to communicate about new data entries
 *
 * @param WatcherActor watcher
 *        The Watcher Actor requesting to update data entries.
 * @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 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 function addOrSetSessionDataEntry({
  watcher,
  type,
  entries,
  updateType,
}) {
  maybeRegisterProcessActor(watcher);
  const promises = [];
  for (const process of getAllContentProcesses()) {
    const promise = process
      .getActor(PROCESS_ACTOR_NAME)
      .addOrSetSessionDataEntry({
        watcherActorID: watcher.actorID,
        sessionContext: watcher.sessionContext,
        type,
        entries,
        updateType,
      });
    promises.push(promise);
  }
  // Await for the queries in order to try to resolve only *after* the remote code processed the new data
  return Promise.all(promises);
}

/**
 * Notify all existing frame targets that some data entries have been removed
 *
 * See addOrSetSessionDataEntry for argument documentation.
 */
function removeSessionDataEntry({ watcher, type, entries }) {
  for (const process of getAllContentProcesses()) {
    process.getActor(PROCESS_ACTOR_NAME).removeSessionDataEntry({
      watcherActorID: watcher.actorID,
      sessionContext: watcher.sessionContext,
      type,
      entries,
    });
  }
}

module.exports = {
  createTargets,
  destroyTargets,
  addOrSetSessionDataEntry,
  removeSessionDataEntry,
};