Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 "use strict";
8 /**
9 * Toolkit glue for the remote debugging protocol, loaded into the
10 * debugging global.
11 */
12 let { Ci, Cc, CC, Cu, Cr } = require("chrome");
13 let Debugger = require("Debugger");
14 let Services = require("Services");
15 let { ActorPool } = require("devtools/server/actors/common");
16 let { DebuggerTransport, LocalDebuggerTransport, ChildDebuggerTransport } = require("devtools/server/transport");
17 let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
18 let { dumpn, dbg_assert } = DevToolsUtils;
19 let Services = require("Services");
20 let EventEmitter = require("devtools/toolkit/event-emitter");
22 // Until all Debugger server code is converted to SDK modules,
23 // imports Components.* alias from chrome module.
24 var { Ci, Cc, CC, Cu, Cr } = require("chrome");
25 // On B2G, `this` != Global scope, so `Ci` won't be binded on `this`
26 // (i.e. this.Ci is undefined) Then later, when using loadSubScript,
27 // Ci,... won't be defined for sub scripts.
28 this.Ci = Ci;
29 this.Cc = Cc;
30 this.CC = CC;
31 this.Cu = Cu;
32 this.Cr = Cr;
33 this.Debugger = Debugger;
34 this.Services = Services;
35 this.ActorPool = ActorPool;
36 this.DevToolsUtils = DevToolsUtils;
37 this.dumpn = dumpn;
38 this.dbg_assert = dbg_assert;
40 // Overload `Components` to prevent SDK loader exception on Components
41 // object usage
42 Object.defineProperty(this, "Components", {
43 get: function () require("chrome").components
44 });
46 const DBG_STRINGS_URI = "chrome://global/locale/devtools/debugger.properties";
48 const nsFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
49 Cu.import("resource://gre/modules/reflect.jsm");
50 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
51 Cu.import("resource://gre/modules/NetUtil.jsm");
52 dumpn.wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
54 Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js");
56 function loadSubScript(aURL)
57 {
58 try {
59 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
60 .getService(Ci.mozIJSSubScriptLoader);
61 loader.loadSubScript(aURL, this);
62 } catch(e) {
63 let errorStr = "Error loading: " + aURL + ":\n" +
64 (e.fileName ? "at " + e.fileName + " : " + e.lineNumber + "\n" : "") +
65 e + " - " + e.stack + "\n";
66 dump(errorStr);
67 Cu.reportError(errorStr);
68 throw e;
69 }
70 }
72 let events = require("sdk/event/core");
73 let {defer, resolve, reject, all} = require("devtools/toolkit/deprecated-sync-thenables");
74 this.defer = defer;
75 this.resolve = resolve;
76 this.reject = reject;
77 this.all = all;
79 Cu.import("resource://gre/modules/devtools/SourceMap.jsm");
81 XPCOMUtils.defineLazyModuleGetter(this, "console",
82 "resource://gre/modules/devtools/Console.jsm");
84 XPCOMUtils.defineLazyGetter(this, "NetworkMonitorManager", () => {
85 return require("devtools/toolkit/webconsole/network-monitor").NetworkMonitorManager;
86 });
88 // XPCOM constructors
89 const ServerSocket = CC("@mozilla.org/network/server-socket;1",
90 "nsIServerSocket",
91 "initSpecialConnection");
92 const UnixDomainServerSocket = CC("@mozilla.org/network/server-socket;1",
93 "nsIServerSocket",
94 "initWithFilename");
96 var gRegisteredModules = Object.create(null);
98 /**
99 * The ModuleAPI object is passed to modules loaded using the
100 * DebuggerServer.registerModule() API. Modules can use this
101 * object to register actor factories.
102 * Factories registered through the module API will be removed
103 * when the module is unregistered or when the server is
104 * destroyed.
105 */
106 function ModuleAPI() {
107 let activeTabActors = new Set();
108 let activeGlobalActors = new Set();
110 return {
111 // See DebuggerServer.addGlobalActor for a description.
112 addGlobalActor: function(factory, name) {
113 DebuggerServer.addGlobalActor(factory, name);
114 activeGlobalActors.add(factory);
115 },
116 // See DebuggerServer.removeGlobalActor for a description.
117 removeGlobalActor: function(factory) {
118 DebuggerServer.removeGlobalActor(factory);
119 activeGlobalActors.delete(factory);
120 },
122 // See DebuggerServer.addTabActor for a description.
123 addTabActor: function(factory, name) {
124 DebuggerServer.addTabActor(factory, name);
125 activeTabActors.add(factory);
126 },
127 // See DebuggerServer.removeTabActor for a description.
128 removeTabActor: function(factory) {
129 DebuggerServer.removeTabActor(factory);
130 activeTabActors.delete(factory);
131 },
133 // Destroy the module API object, unregistering any
134 // factories registered by the module.
135 destroy: function() {
136 for (let factory of activeTabActors) {
137 DebuggerServer.removeTabActor(factory);
138 }
139 activeTabActors = null;
140 for (let factory of activeGlobalActors) {
141 DebuggerServer.removeGlobalActor(factory);
142 }
143 activeGlobalActors = null;
144 }
145 }
146 };
148 /***
149 * Public API
150 */
151 var DebuggerServer = {
152 _listener: null,
153 _initialized: false,
154 _transportInitialized: false,
155 xpcInspector: null,
156 // Number of currently open TCP connections.
157 _socketConnections: 0,
158 // Map of global actor names to actor constructors provided by extensions.
159 globalActorFactories: {},
160 // Map of tab actor names to actor constructors provided by extensions.
161 tabActorFactories: {},
163 LONG_STRING_LENGTH: 10000,
164 LONG_STRING_INITIAL_LENGTH: 1000,
165 LONG_STRING_READ_LENGTH: 65 * 1024,
167 /**
168 * A handler function that prompts the user to accept or decline the incoming
169 * connection.
170 */
171 _allowConnection: null,
173 /**
174 * The windowtype of the chrome window to use for actors that use the global
175 * window (i.e the global style editor). Set this to your main window type,
176 * for example "navigator:browser".
177 */
178 chromeWindowType: null,
180 /**
181 * Prompt the user to accept or decline the incoming connection. This is the
182 * default implementation that products embedding the debugger server may
183 * choose to override.
184 *
185 * @return true if the connection should be permitted, false otherwise
186 */
187 _defaultAllowConnection: function DS__defaultAllowConnection() {
188 let title = L10N.getStr("remoteIncomingPromptTitle");
189 let msg = L10N.getStr("remoteIncomingPromptMessage");
190 let disableButton = L10N.getStr("remoteIncomingPromptDisable");
191 let prompt = Services.prompt;
192 let flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_OK +
193 prompt.BUTTON_POS_1 * prompt.BUTTON_TITLE_CANCEL +
194 prompt.BUTTON_POS_2 * prompt.BUTTON_TITLE_IS_STRING +
195 prompt.BUTTON_POS_1_DEFAULT;
196 let result = prompt.confirmEx(null, title, msg, flags, null, null,
197 disableButton, null, { value: false });
198 if (result == 0) {
199 return true;
200 }
201 if (result == 2) {
202 DebuggerServer.closeListener(true);
203 Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false);
204 }
205 return false;
206 },
208 /**
209 * Initialize the debugger server.
210 *
211 * @param function aAllowConnectionCallback
212 * The embedder-provider callback, that decides whether an incoming
213 * remote protocol conection should be allowed or refused.
214 */
215 init: function DS_init(aAllowConnectionCallback) {
216 if (this.initialized) {
217 return;
218 }
220 this.xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
221 this.initTransport(aAllowConnectionCallback);
222 this.addActors("resource://gre/modules/devtools/server/actors/root.js");
224 this._initialized = true;
225 },
227 protocol: require("devtools/server/protocol"),
229 /**
230 * Initialize the debugger server's transport variables. This can be
231 * in place of init() for cases where the jsdebugger isn't needed.
232 *
233 * @param function aAllowConnectionCallback
234 * The embedder-provider callback, that decides whether an incoming
235 * remote protocol conection should be allowed or refused.
236 */
237 initTransport: function DS_initTransport(aAllowConnectionCallback) {
238 if (this._transportInitialized) {
239 return;
240 }
242 this._connections = {};
243 this._nextConnID = 0;
244 this._transportInitialized = true;
245 this._allowConnection = aAllowConnectionCallback ?
246 aAllowConnectionCallback :
247 this._defaultAllowConnection;
248 },
250 get initialized() this._initialized,
252 /**
253 * Performs cleanup tasks before shutting down the debugger server. Such tasks
254 * include clearing any actor constructors added at runtime. This method
255 * should be called whenever a debugger server is no longer useful, to avoid
256 * memory leaks. After this method returns, the debugger server must be
257 * initialized again before use.
258 */
259 destroy: function DS_destroy() {
260 if (!this._initialized) {
261 return;
262 }
264 for (let connID of Object.getOwnPropertyNames(this._connections)) {
265 this._connections[connID].close();
266 }
268 for (let id of Object.getOwnPropertyNames(gRegisteredModules)) {
269 let mod = gRegisteredModules[id];
270 mod.module.unregister(mod.api);
271 }
272 gRegisteredModules = {};
274 this.closeListener();
275 this.globalActorFactories = {};
276 this.tabActorFactories = {};
277 this._allowConnection = null;
278 this._transportInitialized = false;
279 this._initialized = false;
281 dumpn("Debugger server is shut down.");
282 },
284 /**
285 * Load a subscript into the debugging global.
286 *
287 * @param aURL string A url that will be loaded as a subscript into the
288 * debugging global. The user must load at least one script
289 * that implements a createRootActor() function to create the
290 * server's root actor.
291 */
292 addActors: function DS_addActors(aURL) {
293 loadSubScript.call(this, aURL);
294 },
296 /**
297 * Register a CommonJS module with the debugger server.
298 * @param id string
299 * The ID of a CommonJS module. This module must export
300 * 'register' and 'unregister' functions.
301 */
302 registerModule: function(id) {
303 if (id in gRegisteredModules) {
304 throw new Error("Tried to register a module twice: " + id + "\n");
305 }
307 let moduleAPI = ModuleAPI();
308 let mod = require(id);
309 mod.register(moduleAPI);
310 gRegisteredModules[id] = { module: mod, api: moduleAPI };
311 },
313 /**
314 * Returns true if a module id has been registered.
315 */
316 isModuleRegistered: function(id) {
317 return (id in gRegisteredModules);
318 },
320 /**
321 * Unregister a previously-loaded CommonJS module from the debugger server.
322 */
323 unregisterModule: function(id) {
324 let mod = gRegisteredModules[id];
325 if (!mod) {
326 throw new Error("Tried to unregister a module that was not previously registered.");
327 }
328 mod.module.unregister(mod.api);
329 mod.api.destroy();
330 delete gRegisteredModules[id];
331 },
333 /**
334 * Install Firefox-specific actors.
335 *
336 * /!\ Be careful when adding a new actor, especially global actors.
337 * Any new global actor will be exposed and returned by the root actor.
338 *
339 * That's the reason why tab actors aren't loaded on demand via
340 * restrictPrivileges=true, to prevent exposing them on b2g parent process's
341 * root actor.
342 */
343 addBrowserActors: function(aWindowType = "navigator:browser", restrictPrivileges = false) {
344 this.chromeWindowType = aWindowType;
345 this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
347 if (!restrictPrivileges) {
348 this.addTabActors();
349 this.addGlobalActor(this.ChromeDebuggerActor, "chromeDebugger");
350 this.registerModule("devtools/server/actors/preference");
351 }
353 this.addActors("resource://gre/modules/devtools/server/actors/webapps.js");
354 this.registerModule("devtools/server/actors/device");
355 },
357 /**
358 * Install tab actors in documents loaded in content childs
359 */
360 addChildActors: function () {
361 // In case of apps being loaded in parent process, DebuggerServer is already
362 // initialized and browser actors are already loaded,
363 // but childtab.js hasn't been loaded yet.
364 if (!DebuggerServer.tabActorFactories.hasOwnProperty("consoleActor")) {
365 this.addTabActors();
366 }
367 // But webbrowser.js and childtab.js aren't loaded from shell.js.
368 if (!("BrowserTabActor" in this)) {
369 this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
370 }
371 if (!("ContentActor" in this)) {
372 this.addActors("resource://gre/modules/devtools/server/actors/childtab.js");
373 }
374 },
376 /**
377 * Install tab actors.
378 */
379 addTabActors: function() {
380 this.addActors("resource://gre/modules/devtools/server/actors/script.js");
381 this.registerModule("devtools/server/actors/webconsole");
382 this.registerModule("devtools/server/actors/inspector");
383 this.registerModule("devtools/server/actors/call-watcher");
384 this.registerModule("devtools/server/actors/canvas");
385 this.registerModule("devtools/server/actors/webgl");
386 this.registerModule("devtools/server/actors/webaudio");
387 this.registerModule("devtools/server/actors/stylesheets");
388 this.registerModule("devtools/server/actors/styleeditor");
389 this.registerModule("devtools/server/actors/storage");
390 this.registerModule("devtools/server/actors/gcli");
391 this.registerModule("devtools/server/actors/tracer");
392 this.registerModule("devtools/server/actors/memory");
393 this.registerModule("devtools/server/actors/eventlooplag");
394 if ("nsIProfiler" in Ci) {
395 this.addActors("resource://gre/modules/devtools/server/actors/profiler.js");
396 }
397 },
399 /**
400 * Passes a set of options to the BrowserAddonActors for the given ID.
401 *
402 * @param aId string
403 * The ID of the add-on to pass the options to
404 * @param aOptions object
405 * The options.
406 * @return a promise that will be resolved when complete.
407 */
408 setAddonOptions: function DS_setAddonOptions(aId, aOptions) {
409 if (!this._initialized) {
410 return;
411 }
413 let promises = [];
415 // Pass to all connections
416 for (let connID of Object.getOwnPropertyNames(this._connections)) {
417 promises.push(this._connections[connID].setAddonOptions(aId, aOptions));
418 }
420 return all(promises);
421 },
423 /**
424 * Listens on the given port or socket file for remote debugger connections.
425 *
426 * @param aPortOrPath int, string
427 * If given an integer, the port to listen on.
428 * Otherwise, the path to the unix socket domain file to listen on.
429 */
430 openListener: function DS_openListener(aPortOrPath) {
431 if (!Services.prefs.getBoolPref("devtools.debugger.remote-enabled")) {
432 return false;
433 }
434 this._checkInit();
436 // Return early if the server is already listening.
437 if (this._listener) {
438 return true;
439 }
441 let flags = Ci.nsIServerSocket.KeepWhenOffline;
442 // A preference setting can force binding on the loopback interface.
443 if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
444 flags |= Ci.nsIServerSocket.LoopbackOnly;
445 }
447 try {
448 let backlog = 4;
449 let socket;
450 let port = Number(aPortOrPath);
451 if (port) {
452 socket = new ServerSocket(port, flags, backlog);
453 } else {
454 let file = nsFile(aPortOrPath);
455 if (file.exists())
456 file.remove(false);
457 socket = new UnixDomainServerSocket(file, parseInt("666", 8), backlog);
458 }
459 socket.asyncListen(this);
460 this._listener = socket;
461 } catch (e) {
462 dumpn("Could not start debugging listener on '" + aPortOrPath + "': " + e);
463 throw Cr.NS_ERROR_NOT_AVAILABLE;
464 }
465 this._socketConnections++;
467 return true;
468 },
470 /**
471 * Close a previously-opened TCP listener.
472 *
473 * @param aForce boolean [optional]
474 * If set to true, then the socket will be closed, regardless of the
475 * number of open connections.
476 */
477 closeListener: function DS_closeListener(aForce) {
478 if (!this._listener || this._socketConnections == 0) {
479 return false;
480 }
482 // Only close the listener when the last connection is closed, or if the
483 // aForce flag is passed.
484 if (--this._socketConnections == 0 || aForce) {
485 this._listener.close();
486 this._listener = null;
487 this._socketConnections = 0;
488 }
490 return true;
491 },
493 /**
494 * Creates a new connection to the local debugger speaking over a fake
495 * transport. This connection results in straightforward calls to the onPacket
496 * handlers of each side.
497 *
498 * @param aPrefix string [optional]
499 * If given, all actors in this connection will have names starting
500 * with |aPrefix + ':'|.
501 * @returns a client-side DebuggerTransport for communicating with
502 * the newly-created connection.
503 */
504 connectPipe: function DS_connectPipe(aPrefix) {
505 this._checkInit();
507 let serverTransport = new LocalDebuggerTransport;
508 let clientTransport = new LocalDebuggerTransport(serverTransport);
509 serverTransport.other = clientTransport;
510 let connection = this._onConnection(serverTransport, aPrefix);
512 // I'm putting this here because I trust you.
513 //
514 // There are times, when using a local connection, when you're going
515 // to be tempted to just get direct access to the server. Resist that
516 // temptation! If you succumb to that temptation, you will make the
517 // fine developers that work on Fennec and Firefox OS sad. They're
518 // professionals, they'll try to act like they understand, but deep
519 // down you'll know that you hurt them.
520 //
521 // This reference allows you to give in to that temptation. There are
522 // times this makes sense: tests, for example, and while porting a
523 // previously local-only codebase to the remote protocol.
524 //
525 // But every time you use this, you will feel the shame of having
526 // used a property that starts with a '_'.
527 clientTransport._serverConnection = connection;
529 return clientTransport;
530 },
532 /**
533 * In a content child process, create a new connection that exchanges
534 * nsIMessageSender messages with our parent process.
535 *
536 * @param aPrefix
537 * The prefix we should use in our nsIMessageSender message names and
538 * actor names. This connection will use messages named
539 * "debug:<prefix>:packet", and all its actors will have names
540 * beginning with "<prefix>:".
541 */
542 connectToParent: function(aPrefix, aMessageManager) {
543 this._checkInit();
545 let transport = new ChildDebuggerTransport(aMessageManager, aPrefix);
546 return this._onConnection(transport, aPrefix, true);
547 },
549 /**
550 * Connect to a child process.
551 *
552 * @param object aConnection
553 * The debugger server connection to use.
554 * @param nsIDOMElement aFrame
555 * The browser element that holds the child process.
556 * @param function [aOnDisconnect]
557 * Optional function to invoke when the child is disconnected.
558 * @return object
559 * A promise object that is resolved once the connection is
560 * established.
561 */
562 connectToChild: function(aConnection, aFrame, aOnDisconnect) {
563 let deferred = defer();
565 let mm = aFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader
566 .messageManager;
567 mm.loadFrameScript("resource://gre/modules/devtools/server/child.js", false);
569 let actor, childTransport;
570 let prefix = aConnection.allocID("child");
571 let netMonitor = null;
573 let onActorCreated = DevToolsUtils.makeInfallible(function (msg) {
574 mm.removeMessageListener("debug:actor", onActorCreated);
576 // Pipe Debugger message from/to parent/child via the message manager
577 childTransport = new ChildDebuggerTransport(mm, prefix);
578 childTransport.hooks = {
579 onPacket: aConnection.send.bind(aConnection),
580 onClosed: function () {}
581 };
582 childTransport.ready();
584 aConnection.setForwarding(prefix, childTransport);
586 dumpn("establishing forwarding for app with prefix " + prefix);
588 actor = msg.json.actor;
590 netMonitor = new NetworkMonitorManager(aFrame, actor.actor);
592 deferred.resolve(actor);
593 }).bind(this);
594 mm.addMessageListener("debug:actor", onActorCreated);
596 let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) {
597 if (subject == mm) {
598 Services.obs.removeObserver(onMessageManagerDisconnect, topic);
599 if (childTransport) {
600 // If we have a child transport, the actor has already
601 // been created. We need to stop using this message manager.
602 childTransport.close();
603 childTransport = null;
604 aConnection.cancelForwarding(prefix);
605 } else {
606 // Otherwise, the app has been closed before the actor
607 // had a chance to be created, so we are not able to create
608 // the actor.
609 deferred.resolve(null);
610 }
611 if (actor) {
612 // The ContentActor within the child process doesn't necessary
613 // have to time to uninitialize itself when the app is closed/killed.
614 // So ensure telling the client that the related actor is detached.
615 aConnection.send({ from: actor.actor, type: "tabDetached" });
616 actor = null;
617 }
619 if (netMonitor) {
620 netMonitor.destroy();
621 netMonitor = null;
622 }
624 if (aOnDisconnect) {
625 aOnDisconnect(mm);
626 }
627 }
628 }).bind(this);
629 Services.obs.addObserver(onMessageManagerDisconnect,
630 "message-manager-disconnect", false);
632 events.once(aConnection, "closed", () => {
633 if (childTransport) {
634 // When the client disconnects, we have to unplug the dedicated
635 // ChildDebuggerTransport...
636 childTransport.close();
637 childTransport = null;
638 aConnection.cancelForwarding(prefix);
640 // ... and notify the child process to clean the tab actors.
641 mm.sendAsyncMessage("debug:disconnect");
642 }
643 Services.obs.removeObserver(onMessageManagerDisconnect, "message-manager-disconnect");
644 });
646 mm.sendAsyncMessage("debug:connect", { prefix: prefix });
648 return deferred.promise;
649 },
651 // nsIServerSocketListener implementation
653 onSocketAccepted:
654 DevToolsUtils.makeInfallible(function DS_onSocketAccepted(aSocket, aTransport) {
655 if (Services.prefs.getBoolPref("devtools.debugger.prompt-connection") && !this._allowConnection()) {
656 return;
657 }
658 dumpn("New debugging connection on " + aTransport.host + ":" + aTransport.port);
660 let input = aTransport.openInputStream(0, 0, 0);
661 let output = aTransport.openOutputStream(0, 0, 0);
662 let transport = new DebuggerTransport(input, output);
663 DebuggerServer._onConnection(transport);
664 }, "DebuggerServer.onSocketAccepted"),
666 onStopListening: function DS_onStopListening(aSocket, status) {
667 dumpn("onStopListening, status: " + status);
668 },
670 /**
671 * Raises an exception if the server has not been properly initialized.
672 */
673 _checkInit: function DS_checkInit() {
674 if (!this._transportInitialized) {
675 throw "DebuggerServer has not been initialized.";
676 }
678 if (!this.createRootActor) {
679 throw "Use DebuggerServer.addActors() to add a root actor implementation.";
680 }
681 },
683 /**
684 * Create a new debugger connection for the given transport. Called after
685 * connectPipe(), from connectToParent, or from an incoming socket
686 * connection handler.
687 *
688 * If present, |aForwardingPrefix| is a forwarding prefix that a parent
689 * server is using to recognizes messages intended for this server. Ensure
690 * that all our actors have names beginning with |aForwardingPrefix + ':'|.
691 * In particular, the root actor's name will be |aForwardingPrefix + ':root'|.
692 */
693 _onConnection: function DS_onConnection(aTransport, aForwardingPrefix, aNoRootActor = false) {
694 let connID;
695 if (aForwardingPrefix) {
696 connID = aForwardingPrefix + ":";
697 } else {
698 connID = "conn" + this._nextConnID++ + '.';
699 }
700 let conn = new DebuggerServerConnection(connID, aTransport);
701 this._connections[connID] = conn;
703 // Create a root actor for the connection and send the hello packet.
704 if (!aNoRootActor) {
705 conn.rootActor = this.createRootActor(conn);
706 if (aForwardingPrefix)
707 conn.rootActor.actorID = aForwardingPrefix + ":root";
708 else
709 conn.rootActor.actorID = "root";
710 conn.addActor(conn.rootActor);
711 aTransport.send(conn.rootActor.sayHello());
712 }
713 aTransport.ready();
715 this.emit("connectionchange", "opened", conn);
716 return conn;
717 },
719 /**
720 * Remove the connection from the debugging server.
721 */
722 _connectionClosed: function DS_connectionClosed(aConnection) {
723 delete this._connections[aConnection.prefix];
724 this.emit("connectionchange", "closed", aConnection);
725 },
727 // DebuggerServer extension API.
729 /**
730 * Registers handlers for new tab-scoped request types defined dynamically.
731 * This is used for example by add-ons to augment the functionality of the tab
732 * actor. Note that the name or actorPrefix of the request type is not allowed
733 * to clash with existing protocol packet properties, like 'title', 'url' or
734 * 'actor', since that would break the protocol.
735 *
736 * @param aFunction function
737 * The constructor function for this request type. This expects to be
738 * called as a constructor (i.e. with 'new'), and passed two
739 * arguments: the DebuggerServerConnection, and the BrowserTabActor
740 * with which it will be associated.
741 *
742 * @param aName string [optional]
743 * The name of the new request type. If this is not present, the
744 * actorPrefix property of the constructor prototype is used.
745 */
746 addTabActor: function DS_addTabActor(aFunction, aName) {
747 let name = aName ? aName : aFunction.prototype.actorPrefix;
748 if (["title", "url", "actor"].indexOf(name) != -1) {
749 throw Error(name + " is not allowed");
750 }
751 if (DebuggerServer.tabActorFactories.hasOwnProperty(name)) {
752 throw Error(name + " already exists");
753 }
754 DebuggerServer.tabActorFactories[name] = aFunction;
755 },
757 /**
758 * Unregisters the handler for the specified tab-scoped request type.
759 * This may be used for example by add-ons when shutting down or upgrading.
760 *
761 * @param aFunction function
762 * The constructor function for this request type.
763 */
764 removeTabActor: function DS_removeTabActor(aFunction) {
765 for (let name in DebuggerServer.tabActorFactories) {
766 let handler = DebuggerServer.tabActorFactories[name];
767 if (handler.name == aFunction.name) {
768 delete DebuggerServer.tabActorFactories[name];
769 }
770 }
771 },
773 /**
774 * Registers handlers for new browser-scoped request types defined
775 * dynamically. This is used for example by add-ons to augment the
776 * functionality of the root actor. Note that the name or actorPrefix of the
777 * request type is not allowed to clash with existing protocol packet
778 * properties, like 'from', 'tabs' or 'selected', since that would break the
779 * protocol.
780 *
781 * @param aFunction function
782 * The constructor function for this request type. This expects to be
783 * called as a constructor (i.e. with 'new'), and passed two
784 * arguments: the DebuggerServerConnection, and the BrowserRootActor
785 * with which it will be associated.
786 *
787 * @param aName string [optional]
788 * The name of the new request type. If this is not present, the
789 * actorPrefix property of the constructor prototype is used.
790 */
791 addGlobalActor: function DS_addGlobalActor(aFunction, aName) {
792 let name = aName ? aName : aFunction.prototype.actorPrefix;
793 if (["from", "tabs", "selected"].indexOf(name) != -1) {
794 throw Error(name + " is not allowed");
795 }
796 if (DebuggerServer.globalActorFactories.hasOwnProperty(name)) {
797 throw Error(name + " already exists");
798 }
799 DebuggerServer.globalActorFactories[name] = aFunction;
800 },
802 /**
803 * Unregisters the handler for the specified browser-scoped request type.
804 * This may be used for example by add-ons when shutting down or upgrading.
805 *
806 * @param aFunction function
807 * The constructor function for this request type.
808 */
809 removeGlobalActor: function DS_removeGlobalActor(aFunction) {
810 for (let name in DebuggerServer.globalActorFactories) {
811 let handler = DebuggerServer.globalActorFactories[name];
812 if (handler.name == aFunction.name) {
813 delete DebuggerServer.globalActorFactories[name];
814 }
815 }
816 }
817 };
819 EventEmitter.decorate(DebuggerServer);
821 if (this.exports) {
822 exports.DebuggerServer = DebuggerServer;
823 }
824 // Needed on B2G (See header note)
825 this.DebuggerServer = DebuggerServer;
827 // Export ActorPool for requirers of main.js
828 if (this.exports) {
829 exports.ActorPool = ActorPool;
830 }
832 /**
833 * Creates a DebuggerServerConnection.
834 *
835 * Represents a connection to this debugging global from a client.
836 * Manages a set of actors and actor pools, allocates actor ids, and
837 * handles incoming requests.
838 *
839 * @param aPrefix string
840 * All actor IDs created by this connection should be prefixed
841 * with aPrefix.
842 * @param aTransport transport
843 * Packet transport for the debugging protocol.
844 */
845 function DebuggerServerConnection(aPrefix, aTransport)
846 {
847 this._prefix = aPrefix;
848 this._transport = aTransport;
849 this._transport.hooks = this;
850 this._nextID = 1;
852 this._actorPool = new ActorPool(this);
853 this._extraPools = [];
855 // Responses to a given actor must be returned the the client
856 // in the same order as the requests that they're replying to, but
857 // Implementations might finish serving requests in a different
858 // order. To keep things in order we generate a promise for each
859 // request, chained to the promise for the request before it.
860 // This map stores the latest request promise in the chain, keyed
861 // by an actor ID string.
862 this._actorResponses = new Map;
864 /*
865 * We can forward packets to other servers, if the actors on that server
866 * all use a distinct prefix on their names. This is a map from prefixes
867 * to transports: it maps a prefix P to a transport T if T conveys
868 * packets to the server whose actors' names all begin with P + ":".
869 */
870 this._forwardingPrefixes = new Map;
871 }
873 DebuggerServerConnection.prototype = {
874 _prefix: null,
875 get prefix() { return this._prefix },
877 _transport: null,
878 get transport() { return this._transport },
880 close: function() {
881 this._transport.close();
882 },
884 send: function DSC_send(aPacket) {
885 this.transport.send(aPacket);
886 },
888 allocID: function DSC_allocID(aPrefix) {
889 return this.prefix + (aPrefix || '') + this._nextID++;
890 },
892 /**
893 * Add a map of actor IDs to the connection.
894 */
895 addActorPool: function DSC_addActorPool(aActorPool) {
896 this._extraPools.push(aActorPool);
897 },
899 /**
900 * Remove a previously-added pool of actors to the connection.
901 *
902 * @param ActorPool aActorPool
903 * The ActorPool instance you want to remove.
904 * @param boolean aNoCleanup [optional]
905 * True if you don't want to disconnect each actor from the pool, false
906 * otherwise.
907 */
908 removeActorPool: function DSC_removeActorPool(aActorPool, aNoCleanup) {
909 let index = this._extraPools.lastIndexOf(aActorPool);
910 if (index > -1) {
911 let pool = this._extraPools.splice(index, 1);
912 if (!aNoCleanup) {
913 pool.map(function(p) { p.cleanup(); });
914 }
915 }
916 },
918 /**
919 * Add an actor to the default actor pool for this connection.
920 */
921 addActor: function DSC_addActor(aActor) {
922 this._actorPool.addActor(aActor);
923 },
925 /**
926 * Remove an actor to the default actor pool for this connection.
927 */
928 removeActor: function DSC_removeActor(aActor) {
929 this._actorPool.removeActor(aActor);
930 },
932 /**
933 * Match the api expected by the protocol library.
934 */
935 unmanage: function(aActor) {
936 return this.removeActor(aActor);
937 },
939 /**
940 * Look up an actor implementation for an actorID. Will search
941 * all the actor pools registered with the connection.
942 *
943 * @param aActorID string
944 * Actor ID to look up.
945 */
946 getActor: function DSC_getActor(aActorID) {
947 let pool = this.poolFor(aActorID);
948 if (pool) {
949 return pool.get(aActorID);
950 }
952 if (aActorID === "root") {
953 return this.rootActor;
954 }
956 return null;
957 },
959 poolFor: function DSC_actorPool(aActorID) {
960 if (this._actorPool && this._actorPool.has(aActorID)) {
961 return this._actorPool;
962 }
964 for (let pool of this._extraPools) {
965 if (pool.has(aActorID)) {
966 return pool;
967 }
968 }
969 return null;
970 },
972 _unknownError: function DSC__unknownError(aPrefix, aError) {
973 let errorString = aPrefix + ": " + DevToolsUtils.safeErrorString(aError);
974 Cu.reportError(errorString);
975 dumpn(errorString);
976 return {
977 error: "unknownError",
978 message: errorString
979 };
980 },
982 /**
983 * Passes a set of options to the BrowserAddonActors for the given ID.
984 *
985 * @param aId string
986 * The ID of the add-on to pass the options to
987 * @param aOptions object
988 * The options.
989 * @return a promise that will be resolved when complete.
990 */
991 setAddonOptions: function DSC_setAddonOptions(aId, aOptions) {
992 let addonList = this.rootActor._parameters.addonList;
993 if (!addonList) {
994 return resolve();
995 }
996 return addonList.getList().then((addonActors) => {
997 for (let actor of addonActors) {
998 if (actor.id != aId) {
999 continue;
1000 }
1001 actor.setOptions(aOptions);
1002 return;
1003 }
1004 });
1005 },
1007 /* Forwarding packets to other transports based on actor name prefixes. */
1009 /*
1010 * Arrange to forward packets to another server. This is how we
1011 * forward debugging connections to child processes.
1012 *
1013 * If we receive a packet for an actor whose name begins with |aPrefix|
1014 * followed by ':', then we will forward that packet to |aTransport|.
1015 *
1016 * This overrides any prior forwarding for |aPrefix|.
1017 *
1018 * @param aPrefix string
1019 * The actor name prefix, not including the ':'.
1020 * @param aTransport object
1021 * A packet transport to which we should forward packets to actors
1022 * whose names begin with |(aPrefix + ':').|
1023 */
1024 setForwarding: function(aPrefix, aTransport) {
1025 this._forwardingPrefixes.set(aPrefix, aTransport);
1026 },
1028 /*
1029 * Stop forwarding messages to actors whose names begin with
1030 * |aPrefix+':'|. Such messages will now elicit 'noSuchActor' errors.
1031 */
1032 cancelForwarding: function(aPrefix) {
1033 this._forwardingPrefixes.delete(aPrefix);
1034 },
1036 // Transport hooks.
1038 /**
1039 * Called by DebuggerTransport to dispatch incoming packets as appropriate.
1040 *
1041 * @param aPacket object
1042 * The incoming packet.
1043 */
1044 onPacket: function DSC_onPacket(aPacket) {
1045 // If the actor's name begins with a prefix we've been asked to
1046 // forward, do so.
1047 //
1048 // Note that the presence of a prefix alone doesn't indicate that
1049 // forwarding is needed: in DebuggerServerConnection instances in child
1050 // processes, every actor has a prefixed name.
1051 if (this._forwardingPrefixes.size > 0) {
1052 let colon = aPacket.to.indexOf(':');
1053 if (colon >= 0) {
1054 let forwardTo = this._forwardingPrefixes.get(aPacket.to.substring(0, colon));
1055 if (forwardTo) {
1056 forwardTo.send(aPacket);
1057 return;
1058 }
1059 }
1060 }
1062 let actor = this.getActor(aPacket.to);
1063 if (!actor) {
1064 this.transport.send({ from: aPacket.to ? aPacket.to : "root",
1065 error: "noSuchActor",
1066 message: "No such actor for ID: " + aPacket.to });
1067 return;
1068 }
1070 // Dyamically-loaded actors have to be created lazily.
1071 if (typeof actor == "function") {
1072 let instance;
1073 try {
1074 instance = new actor();
1075 } catch (e) {
1076 this.transport.send(this._unknownError(
1077 "Error occurred while creating actor '" + actor.name,
1078 e));
1079 }
1080 instance.parentID = actor.parentID;
1081 // We want the newly-constructed actor to completely replace the factory
1082 // actor. Reusing the existing actor ID will make sure ActorPool.addActor
1083 // does the right thing.
1084 instance.actorID = actor.actorID;
1085 actor.registeredPool.addActor(instance);
1086 actor = instance;
1087 }
1089 var ret = null;
1091 // handle "requestTypes" RDP request.
1092 if (aPacket.type == "requestTypes") {
1093 ret = { from: actor.actorID, requestTypes: Object.keys(actor.requestTypes) };
1094 } else if (actor.requestTypes && actor.requestTypes[aPacket.type]) {
1095 // Dispatch the request to the actor.
1096 try {
1097 this.currentPacket = aPacket;
1098 ret = actor.requestTypes[aPacket.type].bind(actor)(aPacket, this);
1099 } catch(e) {
1100 this.transport.send(this._unknownError(
1101 "error occurred while processing '" + aPacket.type,
1102 e));
1103 } finally {
1104 this.currentPacket = undefined;
1105 }
1106 } else {
1107 ret = { error: "unrecognizedPacketType",
1108 message: ('Actor "' + actor.actorID +
1109 '" does not recognize the packet type "' +
1110 aPacket.type + '"') };
1111 }
1113 if (!ret) {
1114 // This should become an error once we've converted every user
1115 // of this to promises in bug 794078.
1116 return;
1117 }
1119 let pendingResponse = this._actorResponses.get(actor.actorID) || resolve(null);
1120 let response = pendingResponse.then(() => {
1121 return ret;
1122 }).then(aResponse => {
1123 if (!aResponse.from) {
1124 aResponse.from = aPacket.to;
1125 }
1126 this.transport.send(aResponse);
1127 }).then(null, (e) => {
1128 let errorPacket = this._unknownError(
1129 "error occurred while processing '" + aPacket.type,
1130 e);
1131 errorPacket.from = aPacket.to;
1132 this.transport.send(errorPacket);
1133 });
1135 this._actorResponses.set(actor.actorID, response);
1136 },
1138 /**
1139 * Called by DebuggerTransport when the underlying stream is closed.
1140 *
1141 * @param aStatus nsresult
1142 * The status code that corresponds to the reason for closing
1143 * the stream.
1144 */
1145 onClosed: function DSC_onClosed(aStatus) {
1146 dumpn("Cleaning up connection.");
1147 if (!this._actorPool) {
1148 // Ignore this call if the connection is already closed.
1149 return;
1150 }
1151 events.emit(this, "closed", aStatus);
1153 this._actorPool.cleanup();
1154 this._actorPool = null;
1155 this._extraPools.map(function(p) { p.cleanup(); });
1156 this._extraPools = null;
1158 DebuggerServer._connectionClosed(this);
1159 },
1161 /*
1162 * Debugging helper for inspecting the state of the actor pools.
1163 */
1164 _dumpPools: function DSC_dumpPools() {
1165 dumpn("/-------------------- dumping pools:");
1166 if (this._actorPool) {
1167 dumpn("--------------------- actorPool actors: " +
1168 uneval(Object.keys(this._actorPool._actors)));
1169 }
1170 for each (let pool in this._extraPools)
1171 dumpn("--------------------- extraPool actors: " +
1172 uneval(Object.keys(pool._actors)));
1173 },
1175 /*
1176 * Debugging helper for inspecting the state of an actor pool.
1177 */
1178 _dumpPool: function DSC_dumpPools(aPool) {
1179 dumpn("/-------------------- dumping pool:");
1180 dumpn("--------------------- actorPool actors: " +
1181 uneval(Object.keys(aPool._actors)));
1182 }
1183 };
1185 /**
1186 * Localization convenience methods.
1187 */
1188 let L10N = {
1190 /**
1191 * L10N shortcut function.
1192 *
1193 * @param string aName
1194 * @return string
1195 */
1196 getStr: function L10N_getStr(aName) {
1197 return this.stringBundle.GetStringFromName(aName);
1198 }
1199 };
1201 XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() {
1202 return Services.strings.createBundle(DBG_STRINGS_URI);
1203 });