summaryrefslogtreecommitdiffstats
path: root/devtools/server/startup/worker.js
blob: 42034831ee9dd9ea548f205d5cf144c76407eea9 (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
/* 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";

/* global worker, loadSubScript, global */

/*
 * Worker debugger script that listens for requests to start a `DevToolsServer` for a
 * worker in a process.  Loaded into a specific worker during worker-connector.js'
 * `connectToWorker` which is called from the same process as the worker.
 */

// This function is used to do remote procedure calls from the worker to the
// main thread. It is exposed as a built-in global to every module by the
// worker loader. To make sure the worker loader can access it, it needs to be
// defined before loading the worker loader script below.
let nextId = 0;
this.rpc = function (method, ...params) {
  return new Promise((resolve, reject) => {
    const id = nextId++;
    this.addEventListener("message", function onMessageForRpc(event) {
      const packet = JSON.parse(event.data);
      if (packet.type !== "rpc" || packet.id !== id) {
        return;
      }
      if (packet.error) {
        reject(packet.error);
      } else {
        resolve(packet.result);
      }
      this.removeEventListener("message", onMessageForRpc);
    });

    postMessage(
      JSON.stringify({
        type: "rpc",
        method,
        params,
        id,
      })
    );
  });
}.bind(this);

loadSubScript("resource://devtools/shared/loader/worker-loader.js");

const { WorkerTargetActor } = worker.require(
  "resource://devtools/server/actors/targets/worker.js"
);
const { DevToolsServer } = worker.require(
  "resource://devtools/server/devtools-server.js"
);

DevToolsServer.createRootActor = function () {
  throw new Error("Should never get here!");
};

// This file is only instanciated once for a given WorkerDebugger, which means that
// multiple toolbox could end up using the same instance of this script. In order to handle
// that, we handle a Map of the different connections, keyed by forwarding prefix.
const connections = new Map();

this.addEventListener("message", async function (event) {
  const packet = JSON.parse(event.data);
  switch (packet.type) {
    case "connect":
      const { forwardingPrefix } = packet;

      // Force initializing the server each time on connect
      // as it may have been destroyed by a previous, now closed toolbox.
      // Once the last connection drops, the server auto destroy itself.
      DevToolsServer.init();

      // Step 3: Create a connection to the parent.
      const connection = DevToolsServer.connectToParent(forwardingPrefix, this);

      // Step 4: Create a WorkerTarget actor.
      const workerTargetActor = new WorkerTargetActor(
        connection,
        global,
        packet.workerDebuggerData,
        packet.options.sessionContext
      );
      // Make the worker manage itself so it is put in a Pool and assigned an actorID.
      workerTargetActor.manage(workerTargetActor);

      workerTargetActor.on(
        "worker-thread-attached",
        function onThreadAttached() {
          postMessage(JSON.stringify({ type: "worker-thread-attached" }));
        }
      );

      // Step 5: Send a response packet to the parent to notify
      // it that a connection has been established.
      connections.set(forwardingPrefix, {
        connection,
        workerTargetActor,
      });

      postMessage(
        JSON.stringify({
          type: "connected",
          forwardingPrefix,
          workerTargetForm: workerTargetActor.form(),
        })
      );

      // We might receive data to watch.
      if (packet.options.sessionData) {
        const promises = [];
        for (const [type, entries] of Object.entries(
          packet.options.sessionData
        )) {
          promises.push(
            workerTargetActor.addOrSetSessionDataEntry(
              type,
              entries,
              false,
              "set"
            )
          );
        }
        await Promise.all(promises);
      }

      break;

    case "add-or-set-session-data-entry":
      await connections
        .get(packet.forwardingPrefix)
        .workerTargetActor.addOrSetSessionDataEntry(
          packet.dataEntryType,
          packet.entries,
          packet.updateType
        );
      postMessage(JSON.stringify({ type: "session-data-entry-added-or-set" }));
      break;

    case "remove-session-data-entry":
      await connections
        .get(packet.forwardingPrefix)
        .workerTargetActor.removeSessionDataEntry(
          packet.dataEntryType,
          packet.entries
        );
      break;

    case "disconnect":
      // This will destroy the associate WorkerTargetActor (and the actors it manages).
      if (connections.has(packet.forwardingPrefix)) {
        connections.get(packet.forwardingPrefix).connection.close();
        connections.delete(packet.forwardingPrefix);
      }
      break;
  }
});