|
1 /* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ |
|
2 /* vim: set 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 |
|
7 "use strict"; |
|
8 |
|
9 let {Cc, Ci, Cu} = require("chrome"); |
|
10 |
|
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
12 |
|
13 let { DebuggerServer, ActorPool } = require("devtools/server/main"); |
|
14 // Symbols from script.js |
|
15 let { ThreadActor, EnvironmentActor, ObjectActor, LongStringActor } = DebuggerServer; |
|
16 |
|
17 Cu.import("resource://gre/modules/jsdebugger.jsm"); |
|
18 addDebuggerToGlobal(this); |
|
19 |
|
20 XPCOMUtils.defineLazyModuleGetter(this, "Services", |
|
21 "resource://gre/modules/Services.jsm"); |
|
22 XPCOMUtils.defineLazyGetter(this, "NetworkMonitor", () => { |
|
23 return require("devtools/toolkit/webconsole/network-monitor") |
|
24 .NetworkMonitor; |
|
25 }); |
|
26 XPCOMUtils.defineLazyGetter(this, "NetworkMonitorChild", () => { |
|
27 return require("devtools/toolkit/webconsole/network-monitor") |
|
28 .NetworkMonitorChild; |
|
29 }); |
|
30 XPCOMUtils.defineLazyGetter(this, "ConsoleProgressListener", () => { |
|
31 return require("devtools/toolkit/webconsole/network-monitor") |
|
32 .ConsoleProgressListener; |
|
33 }); |
|
34 XPCOMUtils.defineLazyGetter(this, "events", () => { |
|
35 return require("sdk/event/core"); |
|
36 }); |
|
37 |
|
38 for (let name of ["WebConsoleUtils", "ConsoleServiceListener", |
|
39 "ConsoleAPIListener", "JSTermHelpers", "JSPropertyProvider", |
|
40 "ConsoleReflowListener"]) { |
|
41 Object.defineProperty(this, name, { |
|
42 get: function(prop) { |
|
43 if (prop == "WebConsoleUtils") { |
|
44 prop = "Utils"; |
|
45 } |
|
46 return require("devtools/toolkit/webconsole/utils")[prop]; |
|
47 }.bind(null, name), |
|
48 configurable: true, |
|
49 enumerable: true |
|
50 }); |
|
51 } |
|
52 |
|
53 |
|
54 /** |
|
55 * The WebConsoleActor implements capabilities needed for the Web Console |
|
56 * feature. |
|
57 * |
|
58 * @constructor |
|
59 * @param object aConnection |
|
60 * The connection to the client, DebuggerServerConnection. |
|
61 * @param object [aParentActor] |
|
62 * Optional, the parent actor. |
|
63 */ |
|
64 function WebConsoleActor(aConnection, aParentActor) |
|
65 { |
|
66 this.conn = aConnection; |
|
67 this.parentActor = aParentActor; |
|
68 |
|
69 this._actorPool = new ActorPool(this.conn); |
|
70 this.conn.addActorPool(this._actorPool); |
|
71 |
|
72 this._prefs = {}; |
|
73 |
|
74 this.dbg = new Debugger(); |
|
75 |
|
76 this._netEvents = new Map(); |
|
77 this._gripDepth = 0; |
|
78 |
|
79 this._onWillNavigate = this._onWillNavigate.bind(this); |
|
80 this._onObserverNotification = this._onObserverNotification.bind(this); |
|
81 if (this.parentActor.isRootActor) { |
|
82 Services.obs.addObserver(this._onObserverNotification, |
|
83 "last-pb-context-exited", false); |
|
84 } |
|
85 |
|
86 this.traits = { |
|
87 customNetworkRequest: !this._parentIsContentActor, |
|
88 }; |
|
89 } |
|
90 |
|
91 WebConsoleActor.l10n = new WebConsoleUtils.l10n("chrome://global/locale/console.properties"); |
|
92 |
|
93 WebConsoleActor.prototype = |
|
94 { |
|
95 /** |
|
96 * Debugger instance. |
|
97 * |
|
98 * @see jsdebugger.jsm |
|
99 */ |
|
100 dbg: null, |
|
101 |
|
102 /** |
|
103 * This is used by the ObjectActor to keep track of the depth of grip() calls. |
|
104 * @private |
|
105 * @type number |
|
106 */ |
|
107 _gripDepth: null, |
|
108 |
|
109 /** |
|
110 * Actor pool for all of the actors we send to the client. |
|
111 * @private |
|
112 * @type object |
|
113 * @see ActorPool |
|
114 */ |
|
115 _actorPool: null, |
|
116 |
|
117 /** |
|
118 * Web Console-related preferences. |
|
119 * @private |
|
120 * @type object |
|
121 */ |
|
122 _prefs: null, |
|
123 |
|
124 /** |
|
125 * Holds a map between nsIChannel objects and NetworkEventActors for requests |
|
126 * created with sendHTTPRequest. |
|
127 * |
|
128 * @private |
|
129 * @type Map |
|
130 */ |
|
131 _netEvents: null, |
|
132 |
|
133 /** |
|
134 * The debugger server connection instance. |
|
135 * @type object |
|
136 */ |
|
137 conn: null, |
|
138 |
|
139 /** |
|
140 * List of supported features by the console actor. |
|
141 * @type object |
|
142 */ |
|
143 traits: null, |
|
144 |
|
145 /** |
|
146 * Boolean getter that tells if the parent actor is a ContentActor. |
|
147 * |
|
148 * @private |
|
149 * @type boolean |
|
150 */ |
|
151 get _parentIsContentActor() { |
|
152 return "ContentActor" in DebuggerServer && |
|
153 this.parentActor instanceof DebuggerServer.ContentActor; |
|
154 }, |
|
155 |
|
156 /** |
|
157 * The window we work with. |
|
158 * @type nsIDOMWindow |
|
159 */ |
|
160 get window() { |
|
161 if (this.parentActor.isRootActor) { |
|
162 return this._getWindowForBrowserConsole(); |
|
163 } |
|
164 return this.parentActor.window; |
|
165 }, |
|
166 |
|
167 /** |
|
168 * Get a window to use for the browser console. |
|
169 * |
|
170 * @private |
|
171 * @return nsIDOMWindow |
|
172 * The window to use, or null if no window could be found. |
|
173 */ |
|
174 _getWindowForBrowserConsole: function WCA__getWindowForBrowserConsole() |
|
175 { |
|
176 // Check if our last used chrome window is still live. |
|
177 let window = this._lastChromeWindow && this._lastChromeWindow.get(); |
|
178 // If not, look for a new one. |
|
179 if (!window || window.closed) { |
|
180 window = this.parentActor.window; |
|
181 if (!window) { |
|
182 // Try to find the Browser Console window to use instead. |
|
183 window = Services.wm.getMostRecentWindow("devtools:webconsole"); |
|
184 // We prefer the normal chrome window over the console window, |
|
185 // so we'll look for those windows in order to replace our reference. |
|
186 let onChromeWindowOpened = () => { |
|
187 // We'll look for this window when someone next requests window() |
|
188 Services.obs.removeObserver(onChromeWindowOpened, "domwindowopened"); |
|
189 this._lastChromeWindow = null; |
|
190 }; |
|
191 Services.obs.addObserver(onChromeWindowOpened, "domwindowopened", false); |
|
192 } |
|
193 |
|
194 this._handleNewWindow(window); |
|
195 } |
|
196 |
|
197 return window; |
|
198 }, |
|
199 |
|
200 /** |
|
201 * Store a newly found window on the actor to be used in the future. |
|
202 * |
|
203 * @private |
|
204 * @param nsIDOMWindow window |
|
205 * The window to store on the actor (can be null). |
|
206 */ |
|
207 _handleNewWindow: function WCA__handleNewWindow(window) |
|
208 { |
|
209 if (window) { |
|
210 if (this._hadChromeWindow) { |
|
211 let contextChangedMsg = WebConsoleActor.l10n.getStr("evaluationContextChanged"); |
|
212 Services.console.logStringMessage(contextChangedMsg); |
|
213 } |
|
214 this._lastChromeWindow = Cu.getWeakReference(window); |
|
215 this._hadChromeWindow = true; |
|
216 } else { |
|
217 this._lastChromeWindow = null; |
|
218 } |
|
219 }, |
|
220 |
|
221 /** |
|
222 * Whether we've been using a window before. |
|
223 * |
|
224 * @private |
|
225 * @type boolean |
|
226 */ |
|
227 _hadChromeWindow: false, |
|
228 |
|
229 /** |
|
230 * A weak reference to the last chrome window we used to work with. |
|
231 * |
|
232 * @private |
|
233 * @type nsIWeakReference |
|
234 */ |
|
235 _lastChromeWindow: null, |
|
236 |
|
237 // The evalWindow is used at the scope for JS evaluation. |
|
238 _evalWindow: null, |
|
239 get evalWindow() { |
|
240 return this._evalWindow || this.window; |
|
241 }, |
|
242 |
|
243 set evalWindow(aWindow) { |
|
244 this._evalWindow = aWindow; |
|
245 |
|
246 if (!this._progressListenerActive) { |
|
247 events.on(this.parentActor, "will-navigate", this._onWillNavigate); |
|
248 this._progressListenerActive = true; |
|
249 } |
|
250 }, |
|
251 |
|
252 /** |
|
253 * Flag used to track if we are listening for events from the progress |
|
254 * listener of the tab actor. We use the progress listener to clear |
|
255 * this.evalWindow on page navigation. |
|
256 * |
|
257 * @private |
|
258 * @type boolean |
|
259 */ |
|
260 _progressListenerActive: false, |
|
261 |
|
262 /** |
|
263 * The ConsoleServiceListener instance. |
|
264 * @type object |
|
265 */ |
|
266 consoleServiceListener: null, |
|
267 |
|
268 /** |
|
269 * The ConsoleAPIListener instance. |
|
270 */ |
|
271 consoleAPIListener: null, |
|
272 |
|
273 /** |
|
274 * The NetworkMonitor instance. |
|
275 */ |
|
276 networkMonitor: null, |
|
277 |
|
278 /** |
|
279 * The ConsoleProgressListener instance. |
|
280 */ |
|
281 consoleProgressListener: null, |
|
282 |
|
283 /** |
|
284 * The ConsoleReflowListener instance. |
|
285 */ |
|
286 consoleReflowListener: null, |
|
287 |
|
288 /** |
|
289 * The JSTerm Helpers names cache. |
|
290 * @private |
|
291 * @type array |
|
292 */ |
|
293 _jstermHelpersCache: null, |
|
294 |
|
295 actorPrefix: "console", |
|
296 |
|
297 grip: function WCA_grip() |
|
298 { |
|
299 return { actor: this.actorID }; |
|
300 }, |
|
301 |
|
302 hasNativeConsoleAPI: function WCA_hasNativeConsoleAPI(aWindow) { |
|
303 let isNative = false; |
|
304 try { |
|
305 // We are very explicitly examining the "console" property of |
|
306 // the non-Xrayed object here. |
|
307 let console = aWindow.wrappedJSObject.console; |
|
308 isNative = console instanceof aWindow.Console; |
|
309 } |
|
310 catch (ex) { } |
|
311 return isNative; |
|
312 }, |
|
313 |
|
314 _createValueGrip: ThreadActor.prototype.createValueGrip, |
|
315 _stringIsLong: ThreadActor.prototype._stringIsLong, |
|
316 _findProtoChain: ThreadActor.prototype._findProtoChain, |
|
317 _removeFromProtoChain: ThreadActor.prototype._removeFromProtoChain, |
|
318 |
|
319 /** |
|
320 * Destroy the current WebConsoleActor instance. |
|
321 */ |
|
322 disconnect: function WCA_disconnect() |
|
323 { |
|
324 if (this.consoleServiceListener) { |
|
325 this.consoleServiceListener.destroy(); |
|
326 this.consoleServiceListener = null; |
|
327 } |
|
328 if (this.consoleAPIListener) { |
|
329 this.consoleAPIListener.destroy(); |
|
330 this.consoleAPIListener = null; |
|
331 } |
|
332 if (this.networkMonitor) { |
|
333 this.networkMonitor.destroy(); |
|
334 this.networkMonitor = null; |
|
335 } |
|
336 if (this.consoleProgressListener) { |
|
337 this.consoleProgressListener.destroy(); |
|
338 this.consoleProgressListener = null; |
|
339 } |
|
340 if (this.consoleReflowListener) { |
|
341 this.consoleReflowListener.destroy(); |
|
342 this.consoleReflowListener = null; |
|
343 } |
|
344 this.conn.removeActorPool(this._actorPool); |
|
345 if (this.parentActor.isRootActor) { |
|
346 Services.obs.removeObserver(this._onObserverNotification, |
|
347 "last-pb-context-exited"); |
|
348 } |
|
349 this._actorPool = null; |
|
350 |
|
351 this._jstermHelpersCache = null; |
|
352 this._evalWindow = null; |
|
353 this._netEvents.clear(); |
|
354 this.dbg.enabled = false; |
|
355 this.dbg = null; |
|
356 this.conn = null; |
|
357 }, |
|
358 |
|
359 /** |
|
360 * Create and return an environment actor that corresponds to the provided |
|
361 * Debugger.Environment. This is a straightforward clone of the ThreadActor's |
|
362 * method except that it stores the environment actor in the web console |
|
363 * actor's pool. |
|
364 * |
|
365 * @param Debugger.Environment aEnvironment |
|
366 * The lexical environment we want to extract. |
|
367 * @return The EnvironmentActor for aEnvironment or undefined for host |
|
368 * functions or functions scoped to a non-debuggee global. |
|
369 */ |
|
370 createEnvironmentActor: function WCA_createEnvironmentActor(aEnvironment) { |
|
371 if (!aEnvironment) { |
|
372 return undefined; |
|
373 } |
|
374 |
|
375 if (aEnvironment.actor) { |
|
376 return aEnvironment.actor; |
|
377 } |
|
378 |
|
379 let actor = new EnvironmentActor(aEnvironment, this); |
|
380 this._actorPool.addActor(actor); |
|
381 aEnvironment.actor = actor; |
|
382 |
|
383 return actor; |
|
384 }, |
|
385 |
|
386 /** |
|
387 * Create a grip for the given value. |
|
388 * |
|
389 * @param mixed aValue |
|
390 * @return object |
|
391 */ |
|
392 createValueGrip: function WCA_createValueGrip(aValue) |
|
393 { |
|
394 return this._createValueGrip(aValue, this._actorPool); |
|
395 }, |
|
396 |
|
397 /** |
|
398 * Make a debuggee value for the given value. |
|
399 * |
|
400 * @param mixed aValue |
|
401 * The value you want to get a debuggee value for. |
|
402 * @param boolean aUseObjectGlobal |
|
403 * If |true| the object global is determined and added as a debuggee, |
|
404 * otherwise |this.window| is used when makeDebuggeeValue() is invoked. |
|
405 * @return object |
|
406 * Debuggee value for |aValue|. |
|
407 */ |
|
408 makeDebuggeeValue: function WCA_makeDebuggeeValue(aValue, aUseObjectGlobal) |
|
409 { |
|
410 let global = this.window; |
|
411 if (aUseObjectGlobal && typeof aValue == "object") { |
|
412 try { |
|
413 global = Cu.getGlobalForObject(aValue); |
|
414 } |
|
415 catch (ex) { |
|
416 // The above can throw an exception if aValue is not an actual object. |
|
417 } |
|
418 } |
|
419 let dbgGlobal = this.dbg.makeGlobalObjectReference(global); |
|
420 return dbgGlobal.makeDebuggeeValue(aValue); |
|
421 }, |
|
422 |
|
423 /** |
|
424 * Create a grip for the given object. |
|
425 * |
|
426 * @param object aObject |
|
427 * The object you want. |
|
428 * @param object aPool |
|
429 * An ActorPool where the new actor instance is added. |
|
430 * @param object |
|
431 * The object grip. |
|
432 */ |
|
433 objectGrip: function WCA_objectGrip(aObject, aPool) |
|
434 { |
|
435 let actor = new ObjectActor(aObject, this); |
|
436 aPool.addActor(actor); |
|
437 return actor.grip(); |
|
438 }, |
|
439 |
|
440 /** |
|
441 * Create a grip for the given string. |
|
442 * |
|
443 * @param string aString |
|
444 * The string you want to create the grip for. |
|
445 * @param object aPool |
|
446 * An ActorPool where the new actor instance is added. |
|
447 * @return object |
|
448 * A LongStringActor object that wraps the given string. |
|
449 */ |
|
450 longStringGrip: function WCA_longStringGrip(aString, aPool) |
|
451 { |
|
452 let actor = new LongStringActor(aString, this); |
|
453 aPool.addActor(actor); |
|
454 return actor.grip(); |
|
455 }, |
|
456 |
|
457 /** |
|
458 * Create a long string grip if needed for the given string. |
|
459 * |
|
460 * @private |
|
461 * @param string aString |
|
462 * The string you want to create a long string grip for. |
|
463 * @return string|object |
|
464 * A string is returned if |aString| is not a long string. |
|
465 * A LongStringActor grip is returned if |aString| is a long string. |
|
466 */ |
|
467 _createStringGrip: function NEA__createStringGrip(aString) |
|
468 { |
|
469 if (aString && this._stringIsLong(aString)) { |
|
470 return this.longStringGrip(aString, this._actorPool); |
|
471 } |
|
472 return aString; |
|
473 }, |
|
474 |
|
475 /** |
|
476 * Get an object actor by its ID. |
|
477 * |
|
478 * @param string aActorID |
|
479 * @return object |
|
480 */ |
|
481 getActorByID: function WCA_getActorByID(aActorID) |
|
482 { |
|
483 return this._actorPool.get(aActorID); |
|
484 }, |
|
485 |
|
486 /** |
|
487 * Release an actor. |
|
488 * |
|
489 * @param object aActor |
|
490 * The actor instance you want to release. |
|
491 */ |
|
492 releaseActor: function WCA_releaseActor(aActor) |
|
493 { |
|
494 this._actorPool.removeActor(aActor.actorID); |
|
495 }, |
|
496 |
|
497 ////////////////// |
|
498 // Request handlers for known packet types. |
|
499 ////////////////// |
|
500 |
|
501 /** |
|
502 * Handler for the "startListeners" request. |
|
503 * |
|
504 * @param object aRequest |
|
505 * The JSON request object received from the Web Console client. |
|
506 * @return object |
|
507 * The response object which holds the startedListeners array. |
|
508 */ |
|
509 onStartListeners: function WCA_onStartListeners(aRequest) |
|
510 { |
|
511 let startedListeners = []; |
|
512 let window = !this.parentActor.isRootActor ? this.window : null; |
|
513 let appId = null; |
|
514 let messageManager = null; |
|
515 |
|
516 if (this._parentIsContentActor) { |
|
517 appId = this.parentActor.docShell.appId; |
|
518 messageManager = this.parentActor.messageManager; |
|
519 } |
|
520 |
|
521 while (aRequest.listeners.length > 0) { |
|
522 let listener = aRequest.listeners.shift(); |
|
523 switch (listener) { |
|
524 case "PageError": |
|
525 if (!this.consoleServiceListener) { |
|
526 this.consoleServiceListener = |
|
527 new ConsoleServiceListener(window, this); |
|
528 this.consoleServiceListener.init(); |
|
529 } |
|
530 startedListeners.push(listener); |
|
531 break; |
|
532 case "ConsoleAPI": |
|
533 if (!this.consoleAPIListener) { |
|
534 this.consoleAPIListener = |
|
535 new ConsoleAPIListener(window, this); |
|
536 this.consoleAPIListener.init(); |
|
537 } |
|
538 startedListeners.push(listener); |
|
539 break; |
|
540 case "NetworkActivity": |
|
541 if (!this.networkMonitor) { |
|
542 if (appId || messageManager) { |
|
543 this.networkMonitor = |
|
544 new NetworkMonitorChild(appId, messageManager, |
|
545 this.parentActor.actorID, this); |
|
546 } |
|
547 else { |
|
548 this.networkMonitor = new NetworkMonitor({ window: window }, this); |
|
549 } |
|
550 this.networkMonitor.init(); |
|
551 } |
|
552 startedListeners.push(listener); |
|
553 break; |
|
554 case "FileActivity": |
|
555 if (!this.consoleProgressListener) { |
|
556 this.consoleProgressListener = |
|
557 new ConsoleProgressListener(this.window, this); |
|
558 } |
|
559 this.consoleProgressListener.startMonitor(this.consoleProgressListener. |
|
560 MONITOR_FILE_ACTIVITY); |
|
561 startedListeners.push(listener); |
|
562 break; |
|
563 case "ReflowActivity": |
|
564 if (!this.consoleReflowListener) { |
|
565 this.consoleReflowListener = |
|
566 new ConsoleReflowListener(this.window, this); |
|
567 } |
|
568 startedListeners.push(listener); |
|
569 break; |
|
570 } |
|
571 } |
|
572 return { |
|
573 startedListeners: startedListeners, |
|
574 nativeConsoleAPI: this.hasNativeConsoleAPI(this.window), |
|
575 traits: this.traits, |
|
576 }; |
|
577 }, |
|
578 |
|
579 /** |
|
580 * Handler for the "stopListeners" request. |
|
581 * |
|
582 * @param object aRequest |
|
583 * The JSON request object received from the Web Console client. |
|
584 * @return object |
|
585 * The response packet to send to the client: holds the |
|
586 * stoppedListeners array. |
|
587 */ |
|
588 onStopListeners: function WCA_onStopListeners(aRequest) |
|
589 { |
|
590 let stoppedListeners = []; |
|
591 |
|
592 // If no specific listeners are requested to be detached, we stop all |
|
593 // listeners. |
|
594 let toDetach = aRequest.listeners || |
|
595 ["PageError", "ConsoleAPI", "NetworkActivity", |
|
596 "FileActivity"]; |
|
597 |
|
598 while (toDetach.length > 0) { |
|
599 let listener = toDetach.shift(); |
|
600 switch (listener) { |
|
601 case "PageError": |
|
602 if (this.consoleServiceListener) { |
|
603 this.consoleServiceListener.destroy(); |
|
604 this.consoleServiceListener = null; |
|
605 } |
|
606 stoppedListeners.push(listener); |
|
607 break; |
|
608 case "ConsoleAPI": |
|
609 if (this.consoleAPIListener) { |
|
610 this.consoleAPIListener.destroy(); |
|
611 this.consoleAPIListener = null; |
|
612 } |
|
613 stoppedListeners.push(listener); |
|
614 break; |
|
615 case "NetworkActivity": |
|
616 if (this.networkMonitor) { |
|
617 this.networkMonitor.destroy(); |
|
618 this.networkMonitor = null; |
|
619 } |
|
620 stoppedListeners.push(listener); |
|
621 break; |
|
622 case "FileActivity": |
|
623 if (this.consoleProgressListener) { |
|
624 this.consoleProgressListener.stopMonitor(this.consoleProgressListener. |
|
625 MONITOR_FILE_ACTIVITY); |
|
626 this.consoleProgressListener = null; |
|
627 } |
|
628 stoppedListeners.push(listener); |
|
629 break; |
|
630 case "ReflowActivity": |
|
631 if (this.consoleReflowListener) { |
|
632 this.consoleReflowListener.destroy(); |
|
633 this.consoleReflowListener = null; |
|
634 } |
|
635 stoppedListeners.push(listener); |
|
636 break; |
|
637 } |
|
638 } |
|
639 |
|
640 return { stoppedListeners: stoppedListeners }; |
|
641 }, |
|
642 |
|
643 /** |
|
644 * Handler for the "getCachedMessages" request. This method sends the cached |
|
645 * error messages and the window.console API calls to the client. |
|
646 * |
|
647 * @param object aRequest |
|
648 * The JSON request object received from the Web Console client. |
|
649 * @return object |
|
650 * The response packet to send to the client: it holds the cached |
|
651 * messages array. |
|
652 */ |
|
653 onGetCachedMessages: function WCA_onGetCachedMessages(aRequest) |
|
654 { |
|
655 let types = aRequest.messageTypes; |
|
656 if (!types) { |
|
657 return { |
|
658 error: "missingParameter", |
|
659 message: "The messageTypes parameter is missing.", |
|
660 }; |
|
661 } |
|
662 |
|
663 let messages = []; |
|
664 |
|
665 while (types.length > 0) { |
|
666 let type = types.shift(); |
|
667 switch (type) { |
|
668 case "ConsoleAPI": { |
|
669 if (!this.consoleAPIListener) { |
|
670 break; |
|
671 } |
|
672 let cache = this.consoleAPIListener |
|
673 .getCachedMessages(!this.parentActor.isRootActor); |
|
674 cache.forEach((aMessage) => { |
|
675 let message = this.prepareConsoleMessageForRemote(aMessage); |
|
676 message._type = type; |
|
677 messages.push(message); |
|
678 }); |
|
679 break; |
|
680 } |
|
681 case "PageError": { |
|
682 if (!this.consoleServiceListener) { |
|
683 break; |
|
684 } |
|
685 let cache = this.consoleServiceListener |
|
686 .getCachedMessages(!this.parentActor.isRootActor); |
|
687 cache.forEach((aMessage) => { |
|
688 let message = null; |
|
689 if (aMessage instanceof Ci.nsIScriptError) { |
|
690 message = this.preparePageErrorForRemote(aMessage); |
|
691 message._type = type; |
|
692 } |
|
693 else { |
|
694 message = { |
|
695 _type: "LogMessage", |
|
696 message: this._createStringGrip(aMessage.message), |
|
697 timeStamp: aMessage.timeStamp, |
|
698 }; |
|
699 } |
|
700 messages.push(message); |
|
701 }); |
|
702 break; |
|
703 } |
|
704 } |
|
705 } |
|
706 |
|
707 messages.sort(function(a, b) { return a.timeStamp - b.timeStamp; }); |
|
708 |
|
709 return { |
|
710 from: this.actorID, |
|
711 messages: messages, |
|
712 }; |
|
713 }, |
|
714 |
|
715 /** |
|
716 * Handler for the "evaluateJS" request. This method evaluates the given |
|
717 * JavaScript string and sends back the result. |
|
718 * |
|
719 * @param object aRequest |
|
720 * The JSON request object received from the Web Console client. |
|
721 * @return object |
|
722 * The evaluation response packet. |
|
723 */ |
|
724 onEvaluateJS: function WCA_onEvaluateJS(aRequest) |
|
725 { |
|
726 let input = aRequest.text; |
|
727 let timestamp = Date.now(); |
|
728 |
|
729 let evalOptions = { |
|
730 bindObjectActor: aRequest.bindObjectActor, |
|
731 frameActor: aRequest.frameActor, |
|
732 url: aRequest.url, |
|
733 }; |
|
734 let evalInfo = this.evalWithDebugger(input, evalOptions); |
|
735 let evalResult = evalInfo.result; |
|
736 let helperResult = evalInfo.helperResult; |
|
737 |
|
738 let result, errorMessage, errorGrip = null; |
|
739 if (evalResult) { |
|
740 if ("return" in evalResult) { |
|
741 result = evalResult.return; |
|
742 } |
|
743 else if ("yield" in evalResult) { |
|
744 result = evalResult.yield; |
|
745 } |
|
746 else if ("throw" in evalResult) { |
|
747 let error = evalResult.throw; |
|
748 errorGrip = this.createValueGrip(error); |
|
749 let errorToString = evalInfo.window |
|
750 .evalInGlobalWithBindings("ex + ''", {ex: error}); |
|
751 if (errorToString && typeof errorToString.return == "string") { |
|
752 errorMessage = errorToString.return; |
|
753 } |
|
754 } |
|
755 } |
|
756 |
|
757 return { |
|
758 from: this.actorID, |
|
759 input: input, |
|
760 result: this.createValueGrip(result), |
|
761 timestamp: timestamp, |
|
762 exception: errorGrip, |
|
763 exceptionMessage: errorMessage, |
|
764 helperResult: helperResult, |
|
765 }; |
|
766 }, |
|
767 |
|
768 /** |
|
769 * The Autocomplete request handler. |
|
770 * |
|
771 * @param object aRequest |
|
772 * The request message - what input to autocomplete. |
|
773 * @return object |
|
774 * The response message - matched properties. |
|
775 */ |
|
776 onAutocomplete: function WCA_onAutocomplete(aRequest) |
|
777 { |
|
778 let frameActorId = aRequest.frameActor; |
|
779 let dbgObject = null; |
|
780 let environment = null; |
|
781 |
|
782 // This is the case of the paused debugger |
|
783 if (frameActorId) { |
|
784 let frameActor = this.conn.getActor(frameActorId); |
|
785 if (frameActor) { |
|
786 let frame = frameActor.frame; |
|
787 environment = frame.environment; |
|
788 } |
|
789 else { |
|
790 Cu.reportError("Web Console Actor: the frame actor was not found: " + |
|
791 frameActorId); |
|
792 } |
|
793 } |
|
794 // This is the general case (non-paused debugger) |
|
795 else { |
|
796 dbgObject = this.dbg.makeGlobalObjectReference(this.evalWindow); |
|
797 } |
|
798 |
|
799 let result = JSPropertyProvider(dbgObject, environment, aRequest.text, |
|
800 aRequest.cursor, frameActorId) || {}; |
|
801 let matches = result.matches || []; |
|
802 let reqText = aRequest.text.substr(0, aRequest.cursor); |
|
803 |
|
804 // We consider '$' as alphanumerc because it is used in the names of some |
|
805 // helper functions. |
|
806 let lastNonAlphaIsDot = /[.][a-zA-Z0-9$]*$/.test(reqText); |
|
807 if (!lastNonAlphaIsDot) { |
|
808 if (!this._jstermHelpersCache) { |
|
809 let helpers = { |
|
810 sandbox: Object.create(null) |
|
811 }; |
|
812 JSTermHelpers(helpers); |
|
813 this._jstermHelpersCache = Object.getOwnPropertyNames(helpers.sandbox); |
|
814 } |
|
815 matches = matches.concat(this._jstermHelpersCache.filter(n => n.startsWith(result.matchProp))); |
|
816 } |
|
817 |
|
818 return { |
|
819 from: this.actorID, |
|
820 matches: matches.sort(), |
|
821 matchProp: result.matchProp, |
|
822 }; |
|
823 }, |
|
824 |
|
825 /** |
|
826 * The "clearMessagesCache" request handler. |
|
827 */ |
|
828 onClearMessagesCache: function WCA_onClearMessagesCache() |
|
829 { |
|
830 // TODO: Bug 717611 - Web Console clear button does not clear cached errors |
|
831 let windowId = !this.parentActor.isRootActor ? |
|
832 WebConsoleUtils.getInnerWindowId(this.window) : null; |
|
833 let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"] |
|
834 .getService(Ci.nsIConsoleAPIStorage); |
|
835 ConsoleAPIStorage.clearEvents(windowId); |
|
836 |
|
837 if (this.parentActor.isRootActor) { |
|
838 Services.console.logStringMessage(null); // for the Error Console |
|
839 Services.console.reset(); |
|
840 } |
|
841 return {}; |
|
842 }, |
|
843 |
|
844 /** |
|
845 * The "getPreferences" request handler. |
|
846 * |
|
847 * @param object aRequest |
|
848 * The request message - which preferences need to be retrieved. |
|
849 * @return object |
|
850 * The response message - a { key: value } object map. |
|
851 */ |
|
852 onGetPreferences: function WCA_onGetPreferences(aRequest) |
|
853 { |
|
854 let prefs = Object.create(null); |
|
855 for (let key of aRequest.preferences) { |
|
856 prefs[key] = !!this._prefs[key]; |
|
857 } |
|
858 return { preferences: prefs }; |
|
859 }, |
|
860 |
|
861 /** |
|
862 * The "setPreferences" request handler. |
|
863 * |
|
864 * @param object aRequest |
|
865 * The request message - which preferences need to be updated. |
|
866 */ |
|
867 onSetPreferences: function WCA_onSetPreferences(aRequest) |
|
868 { |
|
869 for (let key in aRequest.preferences) { |
|
870 this._prefs[key] = aRequest.preferences[key]; |
|
871 |
|
872 if (key == "NetworkMonitor.saveRequestAndResponseBodies" && |
|
873 this.networkMonitor) { |
|
874 this.networkMonitor.saveRequestAndResponseBodies = this._prefs[key]; |
|
875 } |
|
876 } |
|
877 return { updated: Object.keys(aRequest.preferences) }; |
|
878 }, |
|
879 |
|
880 ////////////////// |
|
881 // End of request handlers. |
|
882 ////////////////// |
|
883 |
|
884 /** |
|
885 * Create an object with the API we expose to the Web Console during |
|
886 * JavaScript evaluation. |
|
887 * This object inherits properties and methods from the Web Console actor. |
|
888 * |
|
889 * @private |
|
890 * @param object aDebuggerGlobal |
|
891 * A Debugger.Object that wraps a content global. This is used for the |
|
892 * JSTerm helpers. |
|
893 * @return object |
|
894 * The same object as |this|, but with an added |sandbox| property. |
|
895 * The sandbox holds methods and properties that can be used as |
|
896 * bindings during JS evaluation. |
|
897 */ |
|
898 _getJSTermHelpers: function WCA__getJSTermHelpers(aDebuggerGlobal) |
|
899 { |
|
900 let helpers = { |
|
901 window: this.evalWindow, |
|
902 chromeWindow: this.chromeWindow.bind(this), |
|
903 makeDebuggeeValue: aDebuggerGlobal.makeDebuggeeValue.bind(aDebuggerGlobal), |
|
904 createValueGrip: this.createValueGrip.bind(this), |
|
905 sandbox: Object.create(null), |
|
906 helperResult: null, |
|
907 consoleActor: this, |
|
908 }; |
|
909 JSTermHelpers(helpers); |
|
910 |
|
911 // Make sure the helpers can be used during eval. |
|
912 for (let name in helpers.sandbox) { |
|
913 let desc = Object.getOwnPropertyDescriptor(helpers.sandbox, name); |
|
914 if (desc.get || desc.set) { |
|
915 continue; |
|
916 } |
|
917 helpers.sandbox[name] = aDebuggerGlobal.makeDebuggeeValue(desc.value); |
|
918 } |
|
919 return helpers; |
|
920 }, |
|
921 |
|
922 /** |
|
923 * Evaluates a string using the debugger API. |
|
924 * |
|
925 * To allow the variables view to update properties from the Web Console we |
|
926 * provide the "bindObjectActor" mechanism: the Web Console tells the |
|
927 * ObjectActor ID for which it desires to evaluate an expression. The |
|
928 * Debugger.Object pointed at by the actor ID is bound such that it is |
|
929 * available during expression evaluation (evalInGlobalWithBindings()). |
|
930 * |
|
931 * Example: |
|
932 * _self['foobar'] = 'test' |
|
933 * where |_self| refers to the desired object. |
|
934 * |
|
935 * The |frameActor| property allows the Web Console client to provide the |
|
936 * frame actor ID, such that the expression can be evaluated in the |
|
937 * user-selected stack frame. |
|
938 * |
|
939 * For the above to work we need the debugger and the Web Console to share |
|
940 * a connection, otherwise the Web Console actor will not find the frame |
|
941 * actor. |
|
942 * |
|
943 * The Debugger.Frame comes from the jsdebugger's Debugger instance, which |
|
944 * is different from the Web Console's Debugger instance. This means that |
|
945 * for evaluation to work, we need to create a new instance for the jsterm |
|
946 * helpers - they need to be Debugger.Objects coming from the jsdebugger's |
|
947 * Debugger instance. |
|
948 * |
|
949 * When |bindObjectActor| is used objects can come from different iframes, |
|
950 * from different domains. To avoid permission-related errors when objects |
|
951 * come from a different window, we also determine the object's own global, |
|
952 * such that evaluation happens in the context of that global. This means that |
|
953 * evaluation will happen in the object's iframe, rather than the top level |
|
954 * window. |
|
955 * |
|
956 * @param string aString |
|
957 * String to evaluate. |
|
958 * @param object [aOptions] |
|
959 * Options for evaluation: |
|
960 * - bindObjectActor: the ObjectActor ID to use for evaluation. |
|
961 * |evalWithBindings()| will be called with one additional binding: |
|
962 * |_self| which will point to the Debugger.Object of the given |
|
963 * ObjectActor. |
|
964 * - frameActor: the FrameActor ID to use for evaluation. The given |
|
965 * debugger frame is used for evaluation, instead of the global window. |
|
966 * @return object |
|
967 * An object that holds the following properties: |
|
968 * - dbg: the debugger where the string was evaluated. |
|
969 * - frame: (optional) the frame where the string was evaluated. |
|
970 * - window: the Debugger.Object for the global where the string was |
|
971 * evaluated. |
|
972 * - result: the result of the evaluation. |
|
973 * - helperResult: any result coming from a JSTerm helper function. |
|
974 * - url: the url to evaluate the script as. Defaults to |
|
975 * "debugger eval code". |
|
976 */ |
|
977 evalWithDebugger: function WCA_evalWithDebugger(aString, aOptions = {}) |
|
978 { |
|
979 // The help function needs to be easy to guess, so we make the () optional. |
|
980 if (aString.trim() == "help" || aString.trim() == "?") { |
|
981 aString = "help()"; |
|
982 } |
|
983 |
|
984 // Find the Debugger.Frame of the given FrameActor. |
|
985 let frame = null, frameActor = null; |
|
986 if (aOptions.frameActor) { |
|
987 frameActor = this.conn.getActor(aOptions.frameActor); |
|
988 if (frameActor) { |
|
989 frame = frameActor.frame; |
|
990 } |
|
991 else { |
|
992 Cu.reportError("Web Console Actor: the frame actor was not found: " + |
|
993 aOptions.frameActor); |
|
994 } |
|
995 } |
|
996 |
|
997 // If we've been given a frame actor in whose scope we should evaluate the |
|
998 // expression, be sure to use that frame's Debugger (that is, the JavaScript |
|
999 // debugger's Debugger) for the whole operation, not the console's Debugger. |
|
1000 // (One Debugger will treat a different Debugger's Debugger.Object instances |
|
1001 // as ordinary objects, not as references to be followed, so mixing |
|
1002 // debuggers causes strange behaviors.) |
|
1003 let dbg = frame ? frameActor.threadActor.dbg : this.dbg; |
|
1004 let dbgWindow = dbg.makeGlobalObjectReference(this.evalWindow); |
|
1005 |
|
1006 // If we have an object to bind to |_self|, create a Debugger.Object |
|
1007 // referring to that object, belonging to dbg. |
|
1008 let bindSelf = null; |
|
1009 let dbgWindow = dbg.makeGlobalObjectReference(this.evalWindow); |
|
1010 if (aOptions.bindObjectActor) { |
|
1011 let objActor = this.getActorByID(aOptions.bindObjectActor); |
|
1012 if (objActor) { |
|
1013 let jsObj = objActor.obj.unsafeDereference(); |
|
1014 // If we use the makeDebuggeeValue method of jsObj's own global, then |
|
1015 // we'll get a D.O that sees jsObj as viewed from its own compartment - |
|
1016 // that is, without wrappers. The evalWithBindings call will then wrap |
|
1017 // jsObj appropriately for the evaluation compartment. |
|
1018 let global = Cu.getGlobalForObject(jsObj); |
|
1019 dbgWindow = dbg.makeGlobalObjectReference(global); |
|
1020 bindSelf = dbgWindow.makeDebuggeeValue(jsObj); |
|
1021 } |
|
1022 } |
|
1023 |
|
1024 // Get the JSTerm helpers for the given debugger window. |
|
1025 let helpers = this._getJSTermHelpers(dbgWindow); |
|
1026 let bindings = helpers.sandbox; |
|
1027 if (bindSelf) { |
|
1028 bindings._self = bindSelf; |
|
1029 } |
|
1030 |
|
1031 // Check if the Debugger.Frame or Debugger.Object for the global include |
|
1032 // $ or $$. We will not overwrite these functions with the jsterm helpers. |
|
1033 let found$ = false, found$$ = false; |
|
1034 if (frame) { |
|
1035 let env = frame.environment; |
|
1036 if (env) { |
|
1037 found$ = !!env.find("$"); |
|
1038 found$$ = !!env.find("$$"); |
|
1039 } |
|
1040 } |
|
1041 else { |
|
1042 found$ = !!dbgWindow.getOwnPropertyDescriptor("$"); |
|
1043 found$$ = !!dbgWindow.getOwnPropertyDescriptor("$$"); |
|
1044 } |
|
1045 |
|
1046 let $ = null, $$ = null; |
|
1047 if (found$) { |
|
1048 $ = bindings.$; |
|
1049 delete bindings.$; |
|
1050 } |
|
1051 if (found$$) { |
|
1052 $$ = bindings.$$; |
|
1053 delete bindings.$$; |
|
1054 } |
|
1055 |
|
1056 // Ready to evaluate the string. |
|
1057 helpers.evalInput = aString; |
|
1058 |
|
1059 let evalOptions; |
|
1060 if (typeof aOptions.url == "string") { |
|
1061 evalOptions = { url: aOptions.url }; |
|
1062 } |
|
1063 |
|
1064 let result; |
|
1065 if (frame) { |
|
1066 result = frame.evalWithBindings(aString, bindings, evalOptions); |
|
1067 } |
|
1068 else { |
|
1069 result = dbgWindow.evalInGlobalWithBindings(aString, bindings, evalOptions); |
|
1070 } |
|
1071 |
|
1072 let helperResult = helpers.helperResult; |
|
1073 delete helpers.evalInput; |
|
1074 delete helpers.helperResult; |
|
1075 |
|
1076 if ($) { |
|
1077 bindings.$ = $; |
|
1078 } |
|
1079 if ($$) { |
|
1080 bindings.$$ = $$; |
|
1081 } |
|
1082 |
|
1083 if (bindings._self) { |
|
1084 delete bindings._self; |
|
1085 } |
|
1086 |
|
1087 return { |
|
1088 result: result, |
|
1089 helperResult: helperResult, |
|
1090 dbg: dbg, |
|
1091 frame: frame, |
|
1092 window: dbgWindow, |
|
1093 }; |
|
1094 }, |
|
1095 |
|
1096 ////////////////// |
|
1097 // Event handlers for various listeners. |
|
1098 ////////////////// |
|
1099 |
|
1100 /** |
|
1101 * Handler for messages received from the ConsoleServiceListener. This method |
|
1102 * sends the nsIConsoleMessage to the remote Web Console client. |
|
1103 * |
|
1104 * @param nsIConsoleMessage aMessage |
|
1105 * The message we need to send to the client. |
|
1106 */ |
|
1107 onConsoleServiceMessage: function WCA_onConsoleServiceMessage(aMessage) |
|
1108 { |
|
1109 let packet; |
|
1110 if (aMessage instanceof Ci.nsIScriptError) { |
|
1111 packet = { |
|
1112 from: this.actorID, |
|
1113 type: "pageError", |
|
1114 pageError: this.preparePageErrorForRemote(aMessage), |
|
1115 }; |
|
1116 } |
|
1117 else { |
|
1118 packet = { |
|
1119 from: this.actorID, |
|
1120 type: "logMessage", |
|
1121 message: this._createStringGrip(aMessage.message), |
|
1122 timeStamp: aMessage.timeStamp, |
|
1123 }; |
|
1124 } |
|
1125 this.conn.send(packet); |
|
1126 }, |
|
1127 |
|
1128 /** |
|
1129 * Prepare an nsIScriptError to be sent to the client. |
|
1130 * |
|
1131 * @param nsIScriptError aPageError |
|
1132 * The page error we need to send to the client. |
|
1133 * @return object |
|
1134 * The object you can send to the remote client. |
|
1135 */ |
|
1136 preparePageErrorForRemote: function WCA_preparePageErrorForRemote(aPageError) |
|
1137 { |
|
1138 let lineText = aPageError.sourceLine; |
|
1139 if (lineText && lineText.length > DebuggerServer.LONG_STRING_INITIAL_LENGTH) { |
|
1140 lineText = lineText.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH); |
|
1141 } |
|
1142 |
|
1143 return { |
|
1144 errorMessage: this._createStringGrip(aPageError.errorMessage), |
|
1145 sourceName: aPageError.sourceName, |
|
1146 lineText: lineText, |
|
1147 lineNumber: aPageError.lineNumber, |
|
1148 columnNumber: aPageError.columnNumber, |
|
1149 category: aPageError.category, |
|
1150 timeStamp: aPageError.timeStamp, |
|
1151 warning: !!(aPageError.flags & aPageError.warningFlag), |
|
1152 error: !!(aPageError.flags & aPageError.errorFlag), |
|
1153 exception: !!(aPageError.flags & aPageError.exceptionFlag), |
|
1154 strict: !!(aPageError.flags & aPageError.strictFlag), |
|
1155 private: aPageError.isFromPrivateWindow, |
|
1156 }; |
|
1157 }, |
|
1158 |
|
1159 /** |
|
1160 * Handler for window.console API calls received from the ConsoleAPIListener. |
|
1161 * This method sends the object to the remote Web Console client. |
|
1162 * |
|
1163 * @see ConsoleAPIListener |
|
1164 * @param object aMessage |
|
1165 * The console API call we need to send to the remote client. |
|
1166 */ |
|
1167 onConsoleAPICall: function WCA_onConsoleAPICall(aMessage) |
|
1168 { |
|
1169 let packet = { |
|
1170 from: this.actorID, |
|
1171 type: "consoleAPICall", |
|
1172 message: this.prepareConsoleMessageForRemote(aMessage), |
|
1173 }; |
|
1174 this.conn.send(packet); |
|
1175 }, |
|
1176 |
|
1177 /** |
|
1178 * Handler for network events. This method is invoked when a new network event |
|
1179 * is about to be recorded. |
|
1180 * |
|
1181 * @see NetworkEventActor |
|
1182 * @see NetworkMonitor from webconsole/utils.js |
|
1183 * |
|
1184 * @param object aEvent |
|
1185 * The initial network request event information. |
|
1186 * @param nsIHttpChannel aChannel |
|
1187 * The network request nsIHttpChannel object. |
|
1188 * @return object |
|
1189 * A new NetworkEventActor is returned. This is used for tracking the |
|
1190 * network request and response. |
|
1191 */ |
|
1192 onNetworkEvent: function WCA_onNetworkEvent(aEvent, aChannel) |
|
1193 { |
|
1194 let actor = this.getNetworkEventActor(aChannel); |
|
1195 actor.init(aEvent); |
|
1196 |
|
1197 let packet = { |
|
1198 from: this.actorID, |
|
1199 type: "networkEvent", |
|
1200 eventActor: actor.grip(), |
|
1201 }; |
|
1202 |
|
1203 this.conn.send(packet); |
|
1204 |
|
1205 return actor; |
|
1206 }, |
|
1207 |
|
1208 /** |
|
1209 * Get the NetworkEventActor for a nsIChannel, if it exists, |
|
1210 * otherwise create a new one. |
|
1211 * |
|
1212 * @param nsIHttpChannel aChannel |
|
1213 * The channel for the network event. |
|
1214 * @return object |
|
1215 * The NetworkEventActor for the given channel. |
|
1216 */ |
|
1217 getNetworkEventActor: function WCA_getNetworkEventActor(aChannel) { |
|
1218 let actor = this._netEvents.get(aChannel); |
|
1219 if (actor) { |
|
1220 // delete from map as we should only need to do this check once |
|
1221 this._netEvents.delete(aChannel); |
|
1222 actor.channel = null; |
|
1223 return actor; |
|
1224 } |
|
1225 |
|
1226 actor = new NetworkEventActor(aChannel, this); |
|
1227 this._actorPool.addActor(actor); |
|
1228 return actor; |
|
1229 }, |
|
1230 |
|
1231 /** |
|
1232 * Send a new HTTP request from the target's window. |
|
1233 * |
|
1234 * @param object aMessage |
|
1235 * Object with 'request' - the HTTP request details. |
|
1236 */ |
|
1237 onSendHTTPRequest: function WCA_onSendHTTPRequest(aMessage) |
|
1238 { |
|
1239 let details = aMessage.request; |
|
1240 |
|
1241 // send request from target's window |
|
1242 let request = new this.window.XMLHttpRequest(); |
|
1243 request.open(details.method, details.url, true); |
|
1244 |
|
1245 for (let {name, value} of details.headers) { |
|
1246 request.setRequestHeader(name, value); |
|
1247 } |
|
1248 request.send(details.body); |
|
1249 |
|
1250 let actor = this.getNetworkEventActor(request.channel); |
|
1251 |
|
1252 // map channel to actor so we can associate future events with it |
|
1253 this._netEvents.set(request.channel, actor); |
|
1254 |
|
1255 return { |
|
1256 from: this.actorID, |
|
1257 eventActor: actor.grip() |
|
1258 }; |
|
1259 }, |
|
1260 |
|
1261 /** |
|
1262 * Handler for file activity. This method sends the file request information |
|
1263 * to the remote Web Console client. |
|
1264 * |
|
1265 * @see ConsoleProgressListener |
|
1266 * @param string aFileURI |
|
1267 * The requested file URI. |
|
1268 */ |
|
1269 onFileActivity: function WCA_onFileActivity(aFileURI) |
|
1270 { |
|
1271 let packet = { |
|
1272 from: this.actorID, |
|
1273 type: "fileActivity", |
|
1274 uri: aFileURI, |
|
1275 }; |
|
1276 this.conn.send(packet); |
|
1277 }, |
|
1278 |
|
1279 /** |
|
1280 * Handler for reflow activity. This method forwards reflow events to the |
|
1281 * remote Web Console client. |
|
1282 * |
|
1283 * @see ConsoleReflowListener |
|
1284 * @param Object aReflowInfo |
|
1285 */ |
|
1286 onReflowActivity: function WCA_onReflowActivity(aReflowInfo) |
|
1287 { |
|
1288 let packet = { |
|
1289 from: this.actorID, |
|
1290 type: "reflowActivity", |
|
1291 interruptible: aReflowInfo.interruptible, |
|
1292 start: aReflowInfo.start, |
|
1293 end: aReflowInfo.end, |
|
1294 sourceURL: aReflowInfo.sourceURL, |
|
1295 sourceLine: aReflowInfo.sourceLine, |
|
1296 functionName: aReflowInfo.functionName |
|
1297 }; |
|
1298 |
|
1299 this.conn.send(packet); |
|
1300 }, |
|
1301 |
|
1302 ////////////////// |
|
1303 // End of event handlers for various listeners. |
|
1304 ////////////////// |
|
1305 |
|
1306 /** |
|
1307 * Prepare a message from the console API to be sent to the remote Web Console |
|
1308 * instance. |
|
1309 * |
|
1310 * @param object aMessage |
|
1311 * The original message received from console-api-log-event. |
|
1312 * @return object |
|
1313 * The object that can be sent to the remote client. |
|
1314 */ |
|
1315 prepareConsoleMessageForRemote: |
|
1316 function WCA_prepareConsoleMessageForRemote(aMessage) |
|
1317 { |
|
1318 let result = WebConsoleUtils.cloneObject(aMessage); |
|
1319 delete result.wrappedJSObject; |
|
1320 delete result.ID; |
|
1321 delete result.innerID; |
|
1322 |
|
1323 result.arguments = Array.map(aMessage.arguments || [], (aObj) => { |
|
1324 let dbgObj = this.makeDebuggeeValue(aObj, true); |
|
1325 return this.createValueGrip(dbgObj); |
|
1326 }); |
|
1327 |
|
1328 result.styles = Array.map(aMessage.styles || [], (aString) => { |
|
1329 return this.createValueGrip(aString); |
|
1330 }); |
|
1331 |
|
1332 return result; |
|
1333 }, |
|
1334 |
|
1335 /** |
|
1336 * Find the XUL window that owns the content window. |
|
1337 * |
|
1338 * @return Window |
|
1339 * The XUL window that owns the content window. |
|
1340 */ |
|
1341 chromeWindow: function WCA_chromeWindow() |
|
1342 { |
|
1343 let window = null; |
|
1344 try { |
|
1345 window = this.window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
1346 .getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell) |
|
1347 .chromeEventHandler.ownerDocument.defaultView; |
|
1348 } |
|
1349 catch (ex) { |
|
1350 // The above can fail because chromeEventHandler is not available for all |
|
1351 // kinds of |this.window|. |
|
1352 } |
|
1353 |
|
1354 return window; |
|
1355 }, |
|
1356 |
|
1357 /** |
|
1358 * Notification observer for the "last-pb-context-exited" topic. |
|
1359 * |
|
1360 * @private |
|
1361 * @param object aSubject |
|
1362 * Notification subject - in this case it is the inner window ID that |
|
1363 * was destroyed. |
|
1364 * @param string aTopic |
|
1365 * Notification topic. |
|
1366 */ |
|
1367 _onObserverNotification: function WCA__onObserverNotification(aSubject, aTopic) |
|
1368 { |
|
1369 switch (aTopic) { |
|
1370 case "last-pb-context-exited": |
|
1371 this.conn.send({ |
|
1372 from: this.actorID, |
|
1373 type: "lastPrivateContextExited", |
|
1374 }); |
|
1375 break; |
|
1376 } |
|
1377 }, |
|
1378 |
|
1379 /** |
|
1380 * The "will-navigate" progress listener. This is used to clear the current |
|
1381 * eval scope. |
|
1382 */ |
|
1383 _onWillNavigate: function WCA__onWillNavigate({ window, isTopLevel }) |
|
1384 { |
|
1385 if (isTopLevel) { |
|
1386 this._evalWindow = null; |
|
1387 events.off(this.parentActor, "will-navigate", this._onWillNavigate); |
|
1388 this._progressListenerActive = false; |
|
1389 } |
|
1390 }, |
|
1391 }; |
|
1392 |
|
1393 WebConsoleActor.prototype.requestTypes = |
|
1394 { |
|
1395 startListeners: WebConsoleActor.prototype.onStartListeners, |
|
1396 stopListeners: WebConsoleActor.prototype.onStopListeners, |
|
1397 getCachedMessages: WebConsoleActor.prototype.onGetCachedMessages, |
|
1398 evaluateJS: WebConsoleActor.prototype.onEvaluateJS, |
|
1399 autocomplete: WebConsoleActor.prototype.onAutocomplete, |
|
1400 clearMessagesCache: WebConsoleActor.prototype.onClearMessagesCache, |
|
1401 getPreferences: WebConsoleActor.prototype.onGetPreferences, |
|
1402 setPreferences: WebConsoleActor.prototype.onSetPreferences, |
|
1403 sendHTTPRequest: WebConsoleActor.prototype.onSendHTTPRequest |
|
1404 }; |
|
1405 |
|
1406 /** |
|
1407 * Creates an actor for a network event. |
|
1408 * |
|
1409 * @constructor |
|
1410 * @param object aChannel |
|
1411 * The nsIChannel associated with this event. |
|
1412 * @param object aWebConsoleActor |
|
1413 * The parent WebConsoleActor instance for this object. |
|
1414 */ |
|
1415 function NetworkEventActor(aChannel, aWebConsoleActor) |
|
1416 { |
|
1417 this.parent = aWebConsoleActor; |
|
1418 this.conn = this.parent.conn; |
|
1419 this.channel = aChannel; |
|
1420 |
|
1421 this._request = { |
|
1422 method: null, |
|
1423 url: null, |
|
1424 httpVersion: null, |
|
1425 headers: [], |
|
1426 cookies: [], |
|
1427 headersSize: null, |
|
1428 postData: {}, |
|
1429 }; |
|
1430 |
|
1431 this._response = { |
|
1432 headers: [], |
|
1433 cookies: [], |
|
1434 content: {}, |
|
1435 }; |
|
1436 |
|
1437 this._timings = {}; |
|
1438 |
|
1439 // Keep track of LongStringActors owned by this NetworkEventActor. |
|
1440 this._longStringActors = new Set(); |
|
1441 } |
|
1442 |
|
1443 NetworkEventActor.prototype = |
|
1444 { |
|
1445 _request: null, |
|
1446 _response: null, |
|
1447 _timings: null, |
|
1448 _longStringActors: null, |
|
1449 |
|
1450 actorPrefix: "netEvent", |
|
1451 |
|
1452 /** |
|
1453 * Returns a grip for this actor for returning in a protocol message. |
|
1454 */ |
|
1455 grip: function NEA_grip() |
|
1456 { |
|
1457 return { |
|
1458 actor: this.actorID, |
|
1459 startedDateTime: this._startedDateTime, |
|
1460 url: this._request.url, |
|
1461 method: this._request.method, |
|
1462 isXHR: this._isXHR, |
|
1463 private: this._private, |
|
1464 }; |
|
1465 }, |
|
1466 |
|
1467 /** |
|
1468 * Releases this actor from the pool. |
|
1469 */ |
|
1470 release: function NEA_release() |
|
1471 { |
|
1472 for (let grip of this._longStringActors) { |
|
1473 let actor = this.parent.getActorByID(grip.actor); |
|
1474 if (actor) { |
|
1475 this.parent.releaseActor(actor); |
|
1476 } |
|
1477 } |
|
1478 this._longStringActors = new Set(); |
|
1479 |
|
1480 if (this.channel) { |
|
1481 this.parent._netEvents.delete(this.channel); |
|
1482 } |
|
1483 this.parent.releaseActor(this); |
|
1484 }, |
|
1485 |
|
1486 /** |
|
1487 * Handle a protocol request to release a grip. |
|
1488 */ |
|
1489 onRelease: function NEA_onRelease() |
|
1490 { |
|
1491 this.release(); |
|
1492 return {}; |
|
1493 }, |
|
1494 |
|
1495 /** |
|
1496 * Set the properties of this actor based on it's corresponding |
|
1497 * network event. |
|
1498 * |
|
1499 * @param object aNetworkEvent |
|
1500 * The network event associated with this actor. |
|
1501 */ |
|
1502 init: function NEA_init(aNetworkEvent) |
|
1503 { |
|
1504 this._startedDateTime = aNetworkEvent.startedDateTime; |
|
1505 this._isXHR = aNetworkEvent.isXHR; |
|
1506 |
|
1507 for (let prop of ['method', 'url', 'httpVersion', 'headersSize']) { |
|
1508 this._request[prop] = aNetworkEvent[prop]; |
|
1509 } |
|
1510 |
|
1511 this._discardRequestBody = aNetworkEvent.discardRequestBody; |
|
1512 this._discardResponseBody = aNetworkEvent.discardResponseBody; |
|
1513 this._private = aNetworkEvent.private; |
|
1514 }, |
|
1515 |
|
1516 /** |
|
1517 * The "getRequestHeaders" packet type handler. |
|
1518 * |
|
1519 * @return object |
|
1520 * The response packet - network request headers. |
|
1521 */ |
|
1522 onGetRequestHeaders: function NEA_onGetRequestHeaders() |
|
1523 { |
|
1524 return { |
|
1525 from: this.actorID, |
|
1526 headers: this._request.headers, |
|
1527 headersSize: this._request.headersSize, |
|
1528 }; |
|
1529 }, |
|
1530 |
|
1531 /** |
|
1532 * The "getRequestCookies" packet type handler. |
|
1533 * |
|
1534 * @return object |
|
1535 * The response packet - network request cookies. |
|
1536 */ |
|
1537 onGetRequestCookies: function NEA_onGetRequestCookies() |
|
1538 { |
|
1539 return { |
|
1540 from: this.actorID, |
|
1541 cookies: this._request.cookies, |
|
1542 }; |
|
1543 }, |
|
1544 |
|
1545 /** |
|
1546 * The "getRequestPostData" packet type handler. |
|
1547 * |
|
1548 * @return object |
|
1549 * The response packet - network POST data. |
|
1550 */ |
|
1551 onGetRequestPostData: function NEA_onGetRequestPostData() |
|
1552 { |
|
1553 return { |
|
1554 from: this.actorID, |
|
1555 postData: this._request.postData, |
|
1556 postDataDiscarded: this._discardRequestBody, |
|
1557 }; |
|
1558 }, |
|
1559 |
|
1560 /** |
|
1561 * The "getResponseHeaders" packet type handler. |
|
1562 * |
|
1563 * @return object |
|
1564 * The response packet - network response headers. |
|
1565 */ |
|
1566 onGetResponseHeaders: function NEA_onGetResponseHeaders() |
|
1567 { |
|
1568 return { |
|
1569 from: this.actorID, |
|
1570 headers: this._response.headers, |
|
1571 headersSize: this._response.headersSize, |
|
1572 }; |
|
1573 }, |
|
1574 |
|
1575 /** |
|
1576 * The "getResponseCookies" packet type handler. |
|
1577 * |
|
1578 * @return object |
|
1579 * The response packet - network response cookies. |
|
1580 */ |
|
1581 onGetResponseCookies: function NEA_onGetResponseCookies() |
|
1582 { |
|
1583 return { |
|
1584 from: this.actorID, |
|
1585 cookies: this._response.cookies, |
|
1586 }; |
|
1587 }, |
|
1588 |
|
1589 /** |
|
1590 * The "getResponseContent" packet type handler. |
|
1591 * |
|
1592 * @return object |
|
1593 * The response packet - network response content. |
|
1594 */ |
|
1595 onGetResponseContent: function NEA_onGetResponseContent() |
|
1596 { |
|
1597 return { |
|
1598 from: this.actorID, |
|
1599 content: this._response.content, |
|
1600 contentDiscarded: this._discardResponseBody, |
|
1601 }; |
|
1602 }, |
|
1603 |
|
1604 /** |
|
1605 * The "getEventTimings" packet type handler. |
|
1606 * |
|
1607 * @return object |
|
1608 * The response packet - network event timings. |
|
1609 */ |
|
1610 onGetEventTimings: function NEA_onGetEventTimings() |
|
1611 { |
|
1612 return { |
|
1613 from: this.actorID, |
|
1614 timings: this._timings, |
|
1615 totalTime: this._totalTime, |
|
1616 }; |
|
1617 }, |
|
1618 |
|
1619 /****************************************************************** |
|
1620 * Listeners for new network event data coming from NetworkMonitor. |
|
1621 ******************************************************************/ |
|
1622 |
|
1623 /** |
|
1624 * Add network request headers. |
|
1625 * |
|
1626 * @param array aHeaders |
|
1627 * The request headers array. |
|
1628 */ |
|
1629 addRequestHeaders: function NEA_addRequestHeaders(aHeaders) |
|
1630 { |
|
1631 this._request.headers = aHeaders; |
|
1632 this._prepareHeaders(aHeaders); |
|
1633 |
|
1634 let packet = { |
|
1635 from: this.actorID, |
|
1636 type: "networkEventUpdate", |
|
1637 updateType: "requestHeaders", |
|
1638 headers: aHeaders.length, |
|
1639 headersSize: this._request.headersSize, |
|
1640 }; |
|
1641 |
|
1642 this.conn.send(packet); |
|
1643 }, |
|
1644 |
|
1645 /** |
|
1646 * Add network request cookies. |
|
1647 * |
|
1648 * @param array aCookies |
|
1649 * The request cookies array. |
|
1650 */ |
|
1651 addRequestCookies: function NEA_addRequestCookies(aCookies) |
|
1652 { |
|
1653 this._request.cookies = aCookies; |
|
1654 this._prepareHeaders(aCookies); |
|
1655 |
|
1656 let packet = { |
|
1657 from: this.actorID, |
|
1658 type: "networkEventUpdate", |
|
1659 updateType: "requestCookies", |
|
1660 cookies: aCookies.length, |
|
1661 }; |
|
1662 |
|
1663 this.conn.send(packet); |
|
1664 }, |
|
1665 |
|
1666 /** |
|
1667 * Add network request POST data. |
|
1668 * |
|
1669 * @param object aPostData |
|
1670 * The request POST data. |
|
1671 */ |
|
1672 addRequestPostData: function NEA_addRequestPostData(aPostData) |
|
1673 { |
|
1674 this._request.postData = aPostData; |
|
1675 aPostData.text = this.parent._createStringGrip(aPostData.text); |
|
1676 if (typeof aPostData.text == "object") { |
|
1677 this._longStringActors.add(aPostData.text); |
|
1678 } |
|
1679 |
|
1680 let packet = { |
|
1681 from: this.actorID, |
|
1682 type: "networkEventUpdate", |
|
1683 updateType: "requestPostData", |
|
1684 dataSize: aPostData.text.length, |
|
1685 discardRequestBody: this._discardRequestBody, |
|
1686 }; |
|
1687 |
|
1688 this.conn.send(packet); |
|
1689 }, |
|
1690 |
|
1691 /** |
|
1692 * Add the initial network response information. |
|
1693 * |
|
1694 * @param object aInfo |
|
1695 * The response information. |
|
1696 */ |
|
1697 addResponseStart: function NEA_addResponseStart(aInfo) |
|
1698 { |
|
1699 this._response.httpVersion = aInfo.httpVersion; |
|
1700 this._response.status = aInfo.status; |
|
1701 this._response.statusText = aInfo.statusText; |
|
1702 this._response.headersSize = aInfo.headersSize; |
|
1703 this._discardResponseBody = aInfo.discardResponseBody; |
|
1704 |
|
1705 let packet = { |
|
1706 from: this.actorID, |
|
1707 type: "networkEventUpdate", |
|
1708 updateType: "responseStart", |
|
1709 response: aInfo, |
|
1710 }; |
|
1711 |
|
1712 this.conn.send(packet); |
|
1713 }, |
|
1714 |
|
1715 /** |
|
1716 * Add network response headers. |
|
1717 * |
|
1718 * @param array aHeaders |
|
1719 * The response headers array. |
|
1720 */ |
|
1721 addResponseHeaders: function NEA_addResponseHeaders(aHeaders) |
|
1722 { |
|
1723 this._response.headers = aHeaders; |
|
1724 this._prepareHeaders(aHeaders); |
|
1725 |
|
1726 let packet = { |
|
1727 from: this.actorID, |
|
1728 type: "networkEventUpdate", |
|
1729 updateType: "responseHeaders", |
|
1730 headers: aHeaders.length, |
|
1731 headersSize: this._response.headersSize, |
|
1732 }; |
|
1733 |
|
1734 this.conn.send(packet); |
|
1735 }, |
|
1736 |
|
1737 /** |
|
1738 * Add network response cookies. |
|
1739 * |
|
1740 * @param array aCookies |
|
1741 * The response cookies array. |
|
1742 */ |
|
1743 addResponseCookies: function NEA_addResponseCookies(aCookies) |
|
1744 { |
|
1745 this._response.cookies = aCookies; |
|
1746 this._prepareHeaders(aCookies); |
|
1747 |
|
1748 let packet = { |
|
1749 from: this.actorID, |
|
1750 type: "networkEventUpdate", |
|
1751 updateType: "responseCookies", |
|
1752 cookies: aCookies.length, |
|
1753 }; |
|
1754 |
|
1755 this.conn.send(packet); |
|
1756 }, |
|
1757 |
|
1758 /** |
|
1759 * Add network response content. |
|
1760 * |
|
1761 * @param object aContent |
|
1762 * The response content. |
|
1763 * @param boolean aDiscardedResponseBody |
|
1764 * Tells if the response content was recorded or not. |
|
1765 */ |
|
1766 addResponseContent: |
|
1767 function NEA_addResponseContent(aContent, aDiscardedResponseBody) |
|
1768 { |
|
1769 this._response.content = aContent; |
|
1770 aContent.text = this.parent._createStringGrip(aContent.text); |
|
1771 if (typeof aContent.text == "object") { |
|
1772 this._longStringActors.add(aContent.text); |
|
1773 } |
|
1774 |
|
1775 let packet = { |
|
1776 from: this.actorID, |
|
1777 type: "networkEventUpdate", |
|
1778 updateType: "responseContent", |
|
1779 mimeType: aContent.mimeType, |
|
1780 contentSize: aContent.text.length, |
|
1781 discardResponseBody: aDiscardedResponseBody, |
|
1782 }; |
|
1783 |
|
1784 this.conn.send(packet); |
|
1785 }, |
|
1786 |
|
1787 /** |
|
1788 * Add network event timing information. |
|
1789 * |
|
1790 * @param number aTotal |
|
1791 * The total time of the network event. |
|
1792 * @param object aTimings |
|
1793 * Timing details about the network event. |
|
1794 */ |
|
1795 addEventTimings: function NEA_addEventTimings(aTotal, aTimings) |
|
1796 { |
|
1797 this._totalTime = aTotal; |
|
1798 this._timings = aTimings; |
|
1799 |
|
1800 let packet = { |
|
1801 from: this.actorID, |
|
1802 type: "networkEventUpdate", |
|
1803 updateType: "eventTimings", |
|
1804 totalTime: aTotal, |
|
1805 }; |
|
1806 |
|
1807 this.conn.send(packet); |
|
1808 }, |
|
1809 |
|
1810 /** |
|
1811 * Prepare the headers array to be sent to the client by using the |
|
1812 * LongStringActor for the header values, when needed. |
|
1813 * |
|
1814 * @private |
|
1815 * @param array aHeaders |
|
1816 */ |
|
1817 _prepareHeaders: function NEA__prepareHeaders(aHeaders) |
|
1818 { |
|
1819 for (let header of aHeaders) { |
|
1820 header.value = this.parent._createStringGrip(header.value); |
|
1821 if (typeof header.value == "object") { |
|
1822 this._longStringActors.add(header.value); |
|
1823 } |
|
1824 } |
|
1825 }, |
|
1826 }; |
|
1827 |
|
1828 NetworkEventActor.prototype.requestTypes = |
|
1829 { |
|
1830 "release": NetworkEventActor.prototype.onRelease, |
|
1831 "getRequestHeaders": NetworkEventActor.prototype.onGetRequestHeaders, |
|
1832 "getRequestCookies": NetworkEventActor.prototype.onGetRequestCookies, |
|
1833 "getRequestPostData": NetworkEventActor.prototype.onGetRequestPostData, |
|
1834 "getResponseHeaders": NetworkEventActor.prototype.onGetResponseHeaders, |
|
1835 "getResponseCookies": NetworkEventActor.prototype.onGetResponseCookies, |
|
1836 "getResponseContent": NetworkEventActor.prototype.onGetResponseContent, |
|
1837 "getEventTimings": NetworkEventActor.prototype.onGetEventTimings, |
|
1838 }; |
|
1839 |
|
1840 exports.register = function(handle) { |
|
1841 handle.addGlobalActor(WebConsoleActor, "consoleActor"); |
|
1842 handle.addTabActor(WebConsoleActor, "consoleActor"); |
|
1843 }; |
|
1844 |
|
1845 exports.unregister = function(handle) { |
|
1846 handle.removeGlobalActor(WebConsoleActor, "consoleActor"); |
|
1847 handle.removeTabActor(WebConsoleActor, "consoleActor"); |
|
1848 }; |