summaryrefslogtreecommitdiffstats
path: root/devtools/shared/protocol/lazy-pool.js
blob: 0829fef1e0df1e4ddeffdc2ea292c769af09ffbf (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
221
222
223
224
/* 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 { extend } = require("devtools/shared/extend");
const { Pool } = require("devtools/shared/protocol");

/**
 * A Special Pool for RootActor and WindowGlobalTargetActor, which allows lazy loaded
 * actors to be added to the pool.
 *
 * Like the Pool, this is a protocol object that can manage the lifetime of other protocol
 * objects. Pools are used on both sides of the connection to help coordinate lifetimes.
 *
 * @param conn
 *   Is a DevToolsServerConnection.  Must have
 *   addActorPool, removeActorPool, and poolFor.
 * @constructor
 */
function LazyPool(conn) {
  this.conn = conn;
}

LazyPool.prototype = extend(Pool.prototype, {
  // The actor for a given actor id stored in this pool
  getActorByID(actorID) {
    if (this.__poolMap) {
      const entry = this._poolMap.get(actorID);
      if (entry instanceof LazyActor) {
        return entry.createActor();
      }
      return entry;
    }
    return null;
  },
});

exports.LazyPool = LazyPool;

/**
 * Populate |parent._extraActors| as specified by |registeredActors|, reusing whatever
 * actors are already there. Add all actors in the final extra actors table to
 * |pool|. _extraActors is treated as a cache for lazy actors
 *
 * The target actor uses this to instantiate actors that other
 * parts of the browser have specified with ActorRegistry.addTargetScopedActor
 *
 * @param factories
 *     An object whose own property names are the names of properties to add to
 *     some reply packet (say, a target actor grip or the "listTabs" response
 *     form), and whose own property values are actor constructor functions, as
 *     documented for addTargetScopedActor
 *
 * @param parent
 *     The parent TargetActor with which the new actors
 *     will be associated. It should support whatever API the |factories|
 *     constructor functions might be interested in, as it is passed to them.
 *     For the sake of CommonCreateExtraActors itself, it should have at least
 *     the following properties:
 *
 *     - _extraActors
 *        An object whose own property names are factory table (and packet)
 *        property names, and whose values are no-argument actor constructors,
 *        of the sort that one can add to a Pool.
 *
 *     - conn
 *        The DevToolsServerConnection in which the new actors will participate.
 *
 *     - actorID
 *        The actor's name, for use as the new actors' parentID.
 * @param pool
 *     An object which implements the protocol.js Pool interface, and has the
 *     following properties
 *
 *     - manage
 *       a function which adds a given actor to an actor pool
 */
function createExtraActors(registeredActors, pool, parent) {
  // Walk over global actors added by extensions.
  const nameMap = {};
  for (const name in registeredActors) {
    let actor = parent._extraActors[name];
    if (!actor) {
      // Register another factory, but this time specific to this connection.
      // It creates a fake actor that looks like an regular actor in the pool,
      // but without actually instantiating the actor.
      // It will only be instantiated on the first request made to the actor.
      actor = new LazyActor(registeredActors[name], parent, pool);
      parent._extraActors[name] = actor;
    }

    // If the actor already exists in the pool, it may have been instantiated,
    // so make sure not to overwrite it by a non-instantiated version.
    if (!pool.has(actor.actorID)) {
      pool.manage(actor);
    }
    nameMap[name] = actor.actorID;
  }
  return nameMap;
}

exports.createExtraActors = createExtraActors;

/**
 * Creates an "actor-like" object which responds in the same way as an ordinary actor
 * but has fewer capabilities (ie, does not manage lifetimes or have it's own pool).
 *
 *
 * @param factories
 *     An object whose own property names are the names of properties to add to
 *     some reply packet (say, a target actor grip or the "listTabs" response
 *     form), and whose own property values are actor constructor functions, as
 *     documented for addTargetScopedActor
 *
 * @param parent
 *     The parent TargetActor with which the new actors
 *     will be associated. It should support whatever API the |factories|
 *     constructor functions might be interested in, as it is passed to them.
 *     For the sake of CommonCreateExtraActors itself, it should have at least
 *     the following properties:
 *
 *     - _extraActors
 *        An object whose own property names are factory table (and packet)
 *        property names, and whose values are no-argument actor constructors,
 *        of the sort that one can add to a Pool.
 *
 *     - conn
 *        The DevToolsServerConnection in which the new actors will participate.
 *
 *     - actorID
 *        The actor's name, for use as the new actors' parentID.
 * @param pool
 *     An object which implements the protocol.js Pool interface, and has the
 *     following properties
 *
 *     - manage
 *       a function which adds a given actor to an actor pool
 */

function LazyActor(factory, parent, pool) {
  this._options = factory.options;
  this._parentActor = parent;
  this._name = factory.name;
  this._pool = pool;

  // needed for taking a place in a pool
  this.typeName = factory.name;
}

LazyActor.prototype = {
  loadModule(id) {
    const options = this._options;
    try {
      return require(id);
      // Fetch the actor constructor
    } catch (e) {
      throw new Error(
        `Unable to load actor module '${options.id}'\n${e.message}\n${e.stack}\n`
      );
    }
  },

  getConstructor() {
    const options = this._options;
    if (options.constructorFun) {
      // Actor definition registered by testing helpers
      return options.constructorFun;
    }
    // Lazy actor definition, where options contains all the information
    // required to load the actor lazily.
    // Exposes `name` attribute in order to allow removeXXXActor to match
    // the actor by its actor constructor name.
    this.name = options.constructorName;
    const module = this.loadModule(options.id);
    const constructor = module[options.constructorName];
    if (!constructor) {
      throw new Error(
        `Unable to find actor constructor named '${this.name}'. (Is it exported?)`
      );
    }
    return constructor;
  },

  /**
   * Return the parent pool for this lazy actor.
   */
  getParent() {
    return this.conn && this.conn.poolFor(this.actorID);
  },

  /**
   * This will only happen if the actor is destroyed before it is created
   * We do not want to use the Pool destruction method, because this actor
   * has no pool. However, it might have a parent that should unmange this
   * actor
   */
  destroy() {
    const parent = this.getParent();
    if (parent) {
      parent.unmanage(this);
    }
  },

  createActor() {
    // Fetch the actor constructor
    const Constructor = this.getConstructor();
    // Instantiate a new actor instance
    const conn = this._parentActor.conn;
    // this should be taken care of once all actors are moved to protocol.js
    const instance = new Constructor(conn, this._parentActor);
    instance.conn = conn;

    // We want the newly-constructed actor to completely replace the factory
    // actor. Reusing the existing actor ID will make sure Pool.manage
    // replaces the old actor with the new actor.
    instance.actorID = this.actorID;

    this._pool.manage(instance);

    return instance;
  },
};