toolkit/devtools/server/actors/root.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 "use strict";
michael@0 8
michael@0 9 let devtools_ = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
michael@0 10 let { createExtraActors, appendExtraActors } = devtools_.require("devtools/server/actors/common");
michael@0 11
michael@0 12 /* Root actor for the remote debugging protocol. */
michael@0 13
michael@0 14 /**
michael@0 15 * Create a remote debugging protocol root actor.
michael@0 16 *
michael@0 17 * @param aConnection
michael@0 18 * The DebuggerServerConnection whose root actor we are constructing.
michael@0 19 *
michael@0 20 * @param aParameters
michael@0 21 * The properties of |aParameters| provide backing objects for the root
michael@0 22 * actor's requests; if a given property is omitted from |aParameters|, the
michael@0 23 * root actor won't implement the corresponding requests or notifications.
michael@0 24 * Supported properties:
michael@0 25 *
michael@0 26 * - tabList: a live list (see below) of tab actors. If present, the
michael@0 27 * new root actor supports the 'listTabs' request, providing the live
michael@0 28 * list's elements as its tab actors, and sending 'tabListChanged'
michael@0 29 * notifications when the live list's contents change. One actor in
michael@0 30 * this list must have a true '.selected' property.
michael@0 31 *
michael@0 32 * - addonList: a live list (see below) of addon actors. If present, the
michael@0 33 * new root actor supports the 'listAddons' request, providing the live
michael@0 34 * list's elements as its addon actors, and sending 'addonListchanged'
michael@0 35 * notifications when the live list's contents change.
michael@0 36 *
michael@0 37 * - globalActorFactories: an object |A| describing further actors to
michael@0 38 * attach to the 'listTabs' reply. This is the type accumulated by
michael@0 39 * DebuggerServer.addGlobalActor. For each own property |P| of |A|,
michael@0 40 * the root actor adds a property named |P| to the 'listTabs'
michael@0 41 * reply whose value is the name of an actor constructed by
michael@0 42 * |A[P]|.
michael@0 43 *
michael@0 44 * - onShutdown: a function to call when the root actor is disconnected.
michael@0 45 *
michael@0 46 * Instance properties:
michael@0 47 *
michael@0 48 * - applicationType: the string the root actor will include as the
michael@0 49 * "applicationType" property in the greeting packet. By default, this
michael@0 50 * is "browser".
michael@0 51 *
michael@0 52 * Live lists:
michael@0 53 *
michael@0 54 * A "live list", as used for the |tabList|, is an object that presents a
michael@0 55 * list of actors, and also notifies its clients of changes to the list. A
michael@0 56 * live list's interface is two properties:
michael@0 57 *
michael@0 58 * - getList: a method that returns a promise to the contents of the list.
michael@0 59 *
michael@0 60 * - onListChanged: a handler called, with no arguments, when the set of
michael@0 61 * values the iterator would produce has changed since the last
michael@0 62 * time 'iterator' was called. This may only be set to null or a
michael@0 63 * callable value (one for which the typeof operator returns
michael@0 64 * 'function'). (Note that the live list will not call the
michael@0 65 * onListChanged handler until the list has been iterated over
michael@0 66 * once; if nobody's seen the list in the first place, nobody
michael@0 67 * should care if its contents have changed!)
michael@0 68 *
michael@0 69 * When the list changes, the list implementation should ensure that any
michael@0 70 * actors yielded in previous iterations whose referents (tabs) still exist
michael@0 71 * get yielded again in subsequent iterations. If the underlying referent
michael@0 72 * is the same, the same actor should be presented for it.
michael@0 73 *
michael@0 74 * The root actor registers an 'onListChanged' handler on the appropriate
michael@0 75 * list when it may need to send the client 'tabListChanged' notifications,
michael@0 76 * and is careful to remove the handler whenever it does not need to send
michael@0 77 * such notifications (including when it is disconnected). This means that
michael@0 78 * live list implementations can use the state of the handler property (set
michael@0 79 * or null) to install and remove observers and event listeners.
michael@0 80 *
michael@0 81 * Note that, as the only way for the root actor to see the members of the
michael@0 82 * live list is to begin an iteration over the list, the live list need not
michael@0 83 * actually produce any actors until they are reached in the course of
michael@0 84 * iteration: alliterative lazy live lists.
michael@0 85 */
michael@0 86 function RootActor(aConnection, aParameters) {
michael@0 87 this.conn = aConnection;
michael@0 88 this._parameters = aParameters;
michael@0 89 this._onTabListChanged = this.onTabListChanged.bind(this);
michael@0 90 this._onAddonListChanged = this.onAddonListChanged.bind(this);
michael@0 91 this._extraActors = {};
michael@0 92 }
michael@0 93
michael@0 94 RootActor.prototype = {
michael@0 95 constructor: RootActor,
michael@0 96 applicationType: "browser",
michael@0 97
michael@0 98 /**
michael@0 99 * Return a 'hello' packet as specified by the Remote Debugging Protocol.
michael@0 100 */
michael@0 101 sayHello: function() {
michael@0 102 return {
michael@0 103 from: this.actorID,
michael@0 104 applicationType: this.applicationType,
michael@0 105 /* This is not in the spec, but it's used by tests. */
michael@0 106 testConnectionPrefix: this.conn.prefix,
michael@0 107 traits: {
michael@0 108 sources: true,
michael@0 109 editOuterHTML: true,
michael@0 110 // Wether the server-side highlighter actor exists and can be used to
michael@0 111 // remotely highlight nodes (see server/actors/highlighter.js)
michael@0 112 highlightable: true,
michael@0 113 // Wether the inspector actor implements the getImageDataFromURL
michael@0 114 // method that returns data-uris for image URLs. This is used for image
michael@0 115 // tooltips for instance
michael@0 116 urlToImageDataResolver: true,
michael@0 117 networkMonitor: true,
michael@0 118 // Wether the storage inspector actor to inspect cookies, etc.
michael@0 119 storageInspector: true,
michael@0 120 // Wether storage inspector is read only
michael@0 121 storageInspectorReadOnly: true,
michael@0 122 // Wether conditional breakpoints are supported
michael@0 123 conditionalBreakpoints: true
michael@0 124 }
michael@0 125 };
michael@0 126 },
michael@0 127
michael@0 128 /**
michael@0 129 * This is true for the root actor only, used by some child actors
michael@0 130 */
michael@0 131 get isRootActor() true,
michael@0 132
michael@0 133 /**
michael@0 134 * The (chrome) window, for use by child actors
michael@0 135 */
michael@0 136 get window() Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType),
michael@0 137
michael@0 138 /**
michael@0 139 * URL of the chrome window.
michael@0 140 */
michael@0 141 get url() { return this.window ? this.window.document.location.href : null; },
michael@0 142
michael@0 143 /**
michael@0 144 * Getter for the best nsIWebProgress for to watching this window.
michael@0 145 */
michael@0 146 get webProgress() {
michael@0 147 return this.window
michael@0 148 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 149 .getInterface(Ci.nsIDocShell)
michael@0 150 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 151 .getInterface(Ci.nsIWebProgress);
michael@0 152 },
michael@0 153
michael@0 154 /**
michael@0 155 * Disconnects the actor from the browser window.
michael@0 156 */
michael@0 157 disconnect: function() {
michael@0 158 /* Tell the live lists we aren't watching any more. */
michael@0 159 if (this._parameters.tabList) {
michael@0 160 this._parameters.tabList.onListChanged = null;
michael@0 161 }
michael@0 162 if (this._parameters.addonList) {
michael@0 163 this._parameters.addonList.onListChanged = null;
michael@0 164 }
michael@0 165 if (typeof this._parameters.onShutdown === 'function') {
michael@0 166 this._parameters.onShutdown();
michael@0 167 }
michael@0 168 this._extraActors = null;
michael@0 169 },
michael@0 170
michael@0 171 /* The 'listTabs' request and the 'tabListChanged' notification. */
michael@0 172
michael@0 173 /**
michael@0 174 * Handles the listTabs request. The actors will survive until at least
michael@0 175 * the next listTabs request.
michael@0 176 */
michael@0 177 onListTabs: function() {
michael@0 178 let tabList = this._parameters.tabList;
michael@0 179 if (!tabList) {
michael@0 180 return { from: this.actorID, error: "noTabs",
michael@0 181 message: "This root actor has no browser tabs." };
michael@0 182 }
michael@0 183
michael@0 184 /*
michael@0 185 * Walk the tab list, accumulating the array of tab actors for the
michael@0 186 * reply, and moving all the actors to a new ActorPool. We'll
michael@0 187 * replace the old tab actor pool with the one we build here, thus
michael@0 188 * retiring any actors that didn't get listed again, and preparing any
michael@0 189 * new actors to receive packets.
michael@0 190 */
michael@0 191 let newActorPool = new ActorPool(this.conn);
michael@0 192 let tabActorList = [];
michael@0 193 let selected;
michael@0 194 return tabList.getList().then((tabActors) => {
michael@0 195 for (let tabActor of tabActors) {
michael@0 196 if (tabActor.selected) {
michael@0 197 selected = tabActorList.length;
michael@0 198 }
michael@0 199 tabActor.parentID = this.actorID;
michael@0 200 newActorPool.addActor(tabActor);
michael@0 201 tabActorList.push(tabActor);
michael@0 202 }
michael@0 203
michael@0 204 /* DebuggerServer.addGlobalActor support: create actors. */
michael@0 205 if (!this._globalActorPool) {
michael@0 206 this._globalActorPool = new ActorPool(this.conn);
michael@0 207 this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool);
michael@0 208 this.conn.addActorPool(this._globalActorPool);
michael@0 209 }
michael@0 210
michael@0 211 /*
michael@0 212 * Drop the old actorID -> actor map. Actors that still mattered were
michael@0 213 * added to the new map; others will go away.
michael@0 214 */
michael@0 215 if (this._tabActorPool) {
michael@0 216 this.conn.removeActorPool(this._tabActorPool);
michael@0 217 }
michael@0 218 this._tabActorPool = newActorPool;
michael@0 219 this.conn.addActorPool(this._tabActorPool);
michael@0 220
michael@0 221 let reply = {
michael@0 222 "from": this.actorID,
michael@0 223 "selected": selected || 0,
michael@0 224 "tabs": [actor.form() for (actor of tabActorList)],
michael@0 225 };
michael@0 226
michael@0 227 /* If a root window is accessible, include its URL. */
michael@0 228 if (this.url) {
michael@0 229 reply.url = this.url;
michael@0 230 }
michael@0 231
michael@0 232 /* DebuggerServer.addGlobalActor support: name actors in 'listTabs' reply. */
michael@0 233 this._appendExtraActors(reply);
michael@0 234
michael@0 235 /*
michael@0 236 * Now that we're actually going to report the contents of tabList to
michael@0 237 * the client, we're responsible for letting the client know if it
michael@0 238 * changes.
michael@0 239 */
michael@0 240 tabList.onListChanged = this._onTabListChanged;
michael@0 241
michael@0 242 return reply;
michael@0 243 });
michael@0 244 },
michael@0 245
michael@0 246 onTabListChanged: function () {
michael@0 247 this.conn.send({ from: this.actorID, type:"tabListChanged" });
michael@0 248 /* It's a one-shot notification; no need to watch any more. */
michael@0 249 this._parameters.tabList.onListChanged = null;
michael@0 250 },
michael@0 251
michael@0 252 onListAddons: function () {
michael@0 253 let addonList = this._parameters.addonList;
michael@0 254 if (!addonList) {
michael@0 255 return { from: this.actorID, error: "noAddons",
michael@0 256 message: "This root actor has no browser addons." };
michael@0 257 }
michael@0 258
michael@0 259 return addonList.getList().then((addonActors) => {
michael@0 260 let addonActorPool = new ActorPool(this.conn);
michael@0 261 for (let addonActor of addonActors) {
michael@0 262 addonActorPool.addActor(addonActor);
michael@0 263 }
michael@0 264
michael@0 265 if (this._addonActorPool) {
michael@0 266 this.conn.removeActorPool(this._addonActorPool);
michael@0 267 }
michael@0 268 this._addonActorPool = addonActorPool;
michael@0 269 this.conn.addActorPool(this._addonActorPool);
michael@0 270
michael@0 271 addonList.onListChanged = this._onAddonListChanged;
michael@0 272
michael@0 273 return {
michael@0 274 "from": this.actorID,
michael@0 275 "addons": [addonActor.form() for (addonActor of addonActors)]
michael@0 276 };
michael@0 277 });
michael@0 278 },
michael@0 279
michael@0 280 onAddonListChanged: function () {
michael@0 281 this.conn.send({ from: this.actorID, type: "addonListChanged" });
michael@0 282 this._parameters.addonList.onListChanged = null;
michael@0 283 },
michael@0 284
michael@0 285 /* This is not in the spec, but it's used by tests. */
michael@0 286 onEcho: function (aRequest) {
michael@0 287 /*
michael@0 288 * Request packets are frozen. Copy aRequest, so that
michael@0 289 * DebuggerServerConnection.onPacket can attach a 'from' property.
michael@0 290 */
michael@0 291 return JSON.parse(JSON.stringify(aRequest));
michael@0 292 },
michael@0 293
michael@0 294 onProtocolDescription: function (aRequest) {
michael@0 295 return protocol.dumpProtocolSpec()
michael@0 296 },
michael@0 297
michael@0 298 /* Support for DebuggerServer.addGlobalActor. */
michael@0 299 _createExtraActors: createExtraActors,
michael@0 300 _appendExtraActors: appendExtraActors,
michael@0 301
michael@0 302 /* ThreadActor hooks. */
michael@0 303
michael@0 304 /**
michael@0 305 * Prepare to enter a nested event loop by disabling debuggee events.
michael@0 306 */
michael@0 307 preNest: function() {
michael@0 308 // Disable events in all open windows.
michael@0 309 let e = Services.wm.getEnumerator(null);
michael@0 310 while (e.hasMoreElements()) {
michael@0 311 let win = e.getNext();
michael@0 312 let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 313 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 314 windowUtils.suppressEventHandling(true);
michael@0 315 windowUtils.suspendTimeouts();
michael@0 316 }
michael@0 317 },
michael@0 318
michael@0 319 /**
michael@0 320 * Prepare to exit a nested event loop by enabling debuggee events.
michael@0 321 */
michael@0 322 postNest: function(aNestData) {
michael@0 323 // Enable events in all open windows.
michael@0 324 let e = Services.wm.getEnumerator(null);
michael@0 325 while (e.hasMoreElements()) {
michael@0 326 let win = e.getNext();
michael@0 327 let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 328 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 329 windowUtils.resumeTimeouts();
michael@0 330 windowUtils.suppressEventHandling(false);
michael@0 331 }
michael@0 332 }
michael@0 333 };
michael@0 334
michael@0 335 RootActor.prototype.requestTypes = {
michael@0 336 "listTabs": RootActor.prototype.onListTabs,
michael@0 337 "listAddons": RootActor.prototype.onListAddons,
michael@0 338 "echo": RootActor.prototype.onEcho,
michael@0 339 "protocolDescription": RootActor.prototype.onProtocolDescription
michael@0 340 };

mercurial