|
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/. */ |
|
6 "use strict"; |
|
7 |
|
8 const { classes: Cc, interfaces: Ci, utils: Cu } = Components; |
|
9 |
|
10 const DBG_XUL = "chrome://browser/content/devtools/framework/toolbox-process-window.xul"; |
|
11 const CHROME_DEBUGGER_PROFILE_NAME = "-chrome-debugger"; |
|
12 |
|
13 Cu.import("resource://gre/modules/Services.jsm"); |
|
14 Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); |
|
15 |
|
16 Cu.import("resource://gre/modules/devtools/Loader.jsm"); |
|
17 let require = devtools.require; |
|
18 let Telemetry = require("devtools/shared/telemetry"); |
|
19 let EventEmitter = require("devtools/toolkit/event-emitter"); |
|
20 const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); |
|
21 |
|
22 this.EXPORTED_SYMBOLS = ["BrowserToolboxProcess"]; |
|
23 |
|
24 let processes = Set(); |
|
25 |
|
26 /** |
|
27 * Constructor for creating a process that will hold a chrome toolbox. |
|
28 * |
|
29 * @param function aOnClose [optional] |
|
30 * A function called when the process stops running. |
|
31 * @param function aOnRun [optional] |
|
32 * A function called when the process starts running. |
|
33 * @param object aOptions [optional] |
|
34 * An object with properties for configuring BrowserToolboxProcess. |
|
35 */ |
|
36 this.BrowserToolboxProcess = function BrowserToolboxProcess(aOnClose, aOnRun, aOptions) { |
|
37 let emitter = new EventEmitter(); |
|
38 this.on = emitter.on.bind(emitter); |
|
39 this.off = emitter.off.bind(emitter); |
|
40 this.once = emitter.once.bind(emitter); |
|
41 // Forward any events to the shared emitter. |
|
42 this.emit = function(...args) { |
|
43 emitter.emit(...args); |
|
44 BrowserToolboxProcess.emit(...args); |
|
45 } |
|
46 |
|
47 // If first argument is an object, use those properties instead of |
|
48 // all three arguments |
|
49 if (typeof aOnClose === "object") { |
|
50 if (aOnClose.onClose) { |
|
51 this.on("close", aOnClose.onClose); |
|
52 } |
|
53 if (aOnClose.onRun) { |
|
54 this.on("run", aOnClose.onRun); |
|
55 } |
|
56 this._options = aOnClose; |
|
57 } else { |
|
58 if (aOnClose) { |
|
59 this.on("close", aOnClose); |
|
60 } |
|
61 if (aOnRun) { |
|
62 this.on("run", aOnRun); |
|
63 } |
|
64 this._options = aOptions || {}; |
|
65 } |
|
66 |
|
67 this._telemetry = new Telemetry(); |
|
68 |
|
69 this.close = this.close.bind(this); |
|
70 Services.obs.addObserver(this.close, "quit-application", false); |
|
71 this._initServer(); |
|
72 this._initProfile(); |
|
73 this._create(); |
|
74 |
|
75 processes.add(this); |
|
76 }; |
|
77 |
|
78 EventEmitter.decorate(BrowserToolboxProcess); |
|
79 |
|
80 /** |
|
81 * Initializes and starts a chrome toolbox process. |
|
82 * @return object |
|
83 */ |
|
84 BrowserToolboxProcess.init = function(aOnClose, aOnRun, aOptions) { |
|
85 return new BrowserToolboxProcess(aOnClose, aOnRun, aOptions); |
|
86 }; |
|
87 |
|
88 /** |
|
89 * Passes a set of options to the BrowserAddonActors for the given ID. |
|
90 * |
|
91 * @param aId string |
|
92 * The ID of the add-on to pass the options to |
|
93 * @param aOptions object |
|
94 * The options. |
|
95 * @return a promise that will be resolved when complete. |
|
96 */ |
|
97 BrowserToolboxProcess.setAddonOptions = function DSC_setAddonOptions(aId, aOptions) { |
|
98 let promises = []; |
|
99 |
|
100 for (let process of processes.values()) { |
|
101 promises.push(process.debuggerServer.setAddonOptions(aId, aOptions)); |
|
102 } |
|
103 |
|
104 return promise.all(promises); |
|
105 }; |
|
106 |
|
107 BrowserToolboxProcess.prototype = { |
|
108 /** |
|
109 * Initializes the debugger server. |
|
110 */ |
|
111 _initServer: function() { |
|
112 dumpn("Initializing the chrome toolbox server."); |
|
113 |
|
114 if (!this.loader) { |
|
115 // Create a separate loader instance, so that we can be sure to receive a |
|
116 // separate instance of the DebuggingServer from the rest of the devtools. |
|
117 // This allows us to safely use the tools against even the actors and |
|
118 // DebuggingServer itself, especially since we can mark this loader as |
|
119 // invisible to the debugger (unlike the usual loader settings). |
|
120 this.loader = new DevToolsLoader(); |
|
121 this.loader.invisibleToDebugger = true; |
|
122 this.loader.main("devtools/server/main"); |
|
123 this.debuggerServer = this.loader.DebuggerServer; |
|
124 dumpn("Created a separate loader instance for the DebuggerServer."); |
|
125 |
|
126 // Forward interesting events. |
|
127 this.debuggerServer.on("connectionchange", this.emit.bind(this)); |
|
128 } |
|
129 |
|
130 if (!this.debuggerServer.initialized) { |
|
131 this.debuggerServer.init(); |
|
132 this.debuggerServer.addBrowserActors(); |
|
133 dumpn("initialized and added the browser actors for the DebuggerServer."); |
|
134 } |
|
135 |
|
136 this.debuggerServer.openListener(Prefs.chromeDebuggingPort); |
|
137 |
|
138 dumpn("Finished initializing the chrome toolbox server."); |
|
139 dumpn("Started listening on port: " + Prefs.chromeDebuggingPort); |
|
140 }, |
|
141 |
|
142 /** |
|
143 * Initializes a profile for the remote debugger process. |
|
144 */ |
|
145 _initProfile: function() { |
|
146 dumpn("Initializing the chrome toolbox user profile."); |
|
147 |
|
148 let profileService = Cc["@mozilla.org/toolkit/profile-service;1"] |
|
149 .createInstance(Ci.nsIToolkitProfileService); |
|
150 |
|
151 let profileName; |
|
152 try { |
|
153 // Attempt to get the required chrome debugging profile name string. |
|
154 profileName = profileService.selectedProfile.name + CHROME_DEBUGGER_PROFILE_NAME; |
|
155 dumpn("Using chrome toolbox profile name: " + profileName); |
|
156 } catch (e) { |
|
157 // Requested profile string could not be retrieved. |
|
158 profileName = CHROME_DEBUGGER_PROFILE_NAME; |
|
159 let msg = "Querying the current profile failed. " + e.name + ": " + e.message; |
|
160 dumpn(msg); |
|
161 Cu.reportError(msg); |
|
162 } |
|
163 |
|
164 let profileObject; |
|
165 try { |
|
166 // Attempt to get the required chrome debugging profile toolkit object. |
|
167 profileObject = profileService.getProfileByName(profileName); |
|
168 dumpn("Using chrome toolbox profile object: " + profileObject); |
|
169 |
|
170 // The profile exists but the corresponding folder may have been deleted. |
|
171 var enumerator = Services.dirsvc.get("ProfD", Ci.nsIFile).parent.directoryEntries; |
|
172 while (enumerator.hasMoreElements()) { |
|
173 let profileDir = enumerator.getNext().QueryInterface(Ci.nsIFile); |
|
174 if (profileDir.leafName.contains(profileName)) { |
|
175 // Requested profile was found and the folder exists. |
|
176 this._dbgProfile = profileObject; |
|
177 return; |
|
178 } |
|
179 } |
|
180 // Requested profile was found but the folder was deleted. Cleanup needed. |
|
181 profileObject.remove(true); |
|
182 dumpn("The already existing chrome toolbox profile was invalid."); |
|
183 } catch (e) { |
|
184 // Requested profile object was not found. |
|
185 let msg = "Creating a profile failed. " + e.name + ": " + e.message; |
|
186 dumpn(msg); |
|
187 Cu.reportError(msg); |
|
188 } |
|
189 |
|
190 // Create a new chrome debugging profile. |
|
191 this._dbgProfile = profileService.createProfile(null, profileName); |
|
192 profileService.flush(); |
|
193 |
|
194 dumpn("Finished creating the chrome toolbox user profile."); |
|
195 dumpn("Flushed profile service with: " + profileName); |
|
196 }, |
|
197 |
|
198 /** |
|
199 * Creates and initializes the profile & process for the remote debugger. |
|
200 */ |
|
201 _create: function() { |
|
202 dumpn("Initializing chrome debugging process."); |
|
203 let process = this._dbgProcess = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); |
|
204 process.init(Services.dirsvc.get("XREExeF", Ci.nsIFile)); |
|
205 |
|
206 let xulURI = DBG_XUL; |
|
207 |
|
208 if (this._options.addonID) { |
|
209 xulURI += "?addonID=" + this._options.addonID; |
|
210 } |
|
211 |
|
212 dumpn("Running chrome debugging process."); |
|
213 let args = ["-no-remote", "-foreground", "-P", this._dbgProfile.name, "-chrome", xulURI]; |
|
214 |
|
215 process.runwAsync(args, args.length, { observe: () => this.close() }); |
|
216 |
|
217 this._telemetry.toolOpened("jsbrowserdebugger"); |
|
218 |
|
219 dumpn("Chrome toolbox is now running..."); |
|
220 this.emit("run", this); |
|
221 }, |
|
222 |
|
223 /** |
|
224 * Closes the remote debugging server and kills the toolbox process. |
|
225 */ |
|
226 close: function() { |
|
227 if (this.closed) { |
|
228 return; |
|
229 } |
|
230 |
|
231 dumpn("Cleaning up the chrome debugging process."); |
|
232 Services.obs.removeObserver(this.close, "quit-application"); |
|
233 |
|
234 if (this._dbgProcess.isRunning) { |
|
235 this._dbgProcess.kill(); |
|
236 } |
|
237 |
|
238 this._telemetry.toolClosed("jsbrowserdebugger"); |
|
239 if (this.debuggerServer) { |
|
240 this.debuggerServer.destroy(); |
|
241 } |
|
242 |
|
243 dumpn("Chrome toolbox is now closed..."); |
|
244 this.closed = true; |
|
245 this.emit("close", this); |
|
246 processes.delete(this); |
|
247 } |
|
248 }; |
|
249 |
|
250 /** |
|
251 * Shortcuts for accessing various debugger preferences. |
|
252 */ |
|
253 let Prefs = new ViewHelpers.Prefs("devtools.debugger", { |
|
254 chromeDebuggingHost: ["Char", "chrome-debugging-host"], |
|
255 chromeDebuggingPort: ["Int", "chrome-debugging-port"] |
|
256 }); |
|
257 |
|
258 /** |
|
259 * Helper method for debugging. |
|
260 * @param string |
|
261 */ |
|
262 function dumpn(str) { |
|
263 if (wantLogging) { |
|
264 dump("DBG-FRONTEND: " + str + "\n"); |
|
265 } |
|
266 } |
|
267 |
|
268 let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log"); |
|
269 |
|
270 Services.prefs.addObserver("devtools.debugger.log", { |
|
271 observe: (...args) => wantLogging = Services.prefs.getBoolPref(args.pop()) |
|
272 }, false); |