|
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 NET_STRINGS_URI = "chrome://browser/locale/devtools/netmonitor.properties"; |
|
11 const LISTENERS = [ "NetworkActivity" ]; |
|
12 const NET_PREFS = { "NetworkMonitor.saveRequestAndResponseBodies": true }; |
|
13 |
|
14 // The panel's window global is an EventEmitter firing the following events: |
|
15 const EVENTS = { |
|
16 // When the monitored target begins and finishes navigating. |
|
17 TARGET_WILL_NAVIGATE: "NetMonitor:TargetWillNavigate", |
|
18 TARGET_DID_NAVIGATE: "NetMonitor:TargetNavigate", |
|
19 |
|
20 // When a network event is received. |
|
21 // See https://developer.mozilla.org/docs/Tools/Web_Console/remoting for |
|
22 // more information about what each packet is supposed to deliver. |
|
23 NETWORK_EVENT: "NetMonitor:NetworkEvent", |
|
24 |
|
25 // When request headers begin and finish receiving. |
|
26 UPDATING_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdating:RequestHeaders", |
|
27 RECEIVED_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdated:RequestHeaders", |
|
28 |
|
29 // When request cookies begin and finish receiving. |
|
30 UPDATING_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdating:RequestCookies", |
|
31 RECEIVED_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdated:RequestCookies", |
|
32 |
|
33 // When request post data begins and finishes receiving. |
|
34 UPDATING_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdating:RequestPostData", |
|
35 RECEIVED_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdated:RequestPostData", |
|
36 |
|
37 // When response headers begin and finish receiving. |
|
38 UPDATING_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdating:ResponseHeaders", |
|
39 RECEIVED_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdated:ResponseHeaders", |
|
40 |
|
41 // When response cookies begin and finish receiving. |
|
42 UPDATING_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdating:ResponseCookies", |
|
43 RECEIVED_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdated:ResponseCookies", |
|
44 |
|
45 // When event timings begin and finish receiving. |
|
46 UPDATING_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdating:EventTimings", |
|
47 RECEIVED_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdated:EventTimings", |
|
48 |
|
49 // When response content begins, updates and finishes receiving. |
|
50 STARTED_RECEIVING_RESPONSE: "NetMonitor:NetworkEventUpdating:ResponseStart", |
|
51 UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent", |
|
52 RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent", |
|
53 |
|
54 // When the request post params are displayed in the UI. |
|
55 REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable", |
|
56 |
|
57 // When the response body is displayed in the UI. |
|
58 RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable", |
|
59 |
|
60 // When the html response preview is displayed in the UI. |
|
61 RESPONSE_HTML_PREVIEW_DISPLAYED: "NetMonitor:ResponseHtmlPreviewAvailable", |
|
62 |
|
63 // When the image response thumbnail is displayed in the UI. |
|
64 RESPONSE_IMAGE_THUMBNAIL_DISPLAYED: "NetMonitor:ResponseImageThumbnailAvailable", |
|
65 |
|
66 // When a tab is selected in the NetworkDetailsView and subsequently rendered. |
|
67 TAB_UPDATED: "NetMonitor:TabUpdated", |
|
68 |
|
69 // Fired when Sidebar has finished being populated. |
|
70 SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated", |
|
71 |
|
72 // Fired when NetworkDetailsView has finished being populated. |
|
73 NETWORKDETAILSVIEW_POPULATED: "NetMonitor:NetworkDetailsViewPopulated", |
|
74 |
|
75 // Fired when CustomRequestView has finished being populated. |
|
76 CUSTOMREQUESTVIEW_POPULATED: "NetMonitor:CustomRequestViewPopulated", |
|
77 |
|
78 // Fired when charts have been displayed in the PerformanceStatisticsView. |
|
79 PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed", |
|
80 PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed", |
|
81 EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed", |
|
82 |
|
83 // Fired once the NetMonitorController establishes a connection to the debug |
|
84 // target. |
|
85 CONNECTED: "connected", |
|
86 }; |
|
87 |
|
88 // Descriptions for what this frontend is currently doing. |
|
89 const ACTIVITY_TYPE = { |
|
90 // Standing by and handling requests normally. |
|
91 NONE: 0, |
|
92 |
|
93 // Forcing the target to reload with cache enabled or disabled. |
|
94 RELOAD: { |
|
95 WITH_CACHE_ENABLED: 1, |
|
96 WITH_CACHE_DISABLED: 2 |
|
97 }, |
|
98 |
|
99 // Enabling or disabling the cache without triggering a reload. |
|
100 ENABLE_CACHE: 3, |
|
101 DISABLE_CACHE: 4 |
|
102 }; |
|
103 |
|
104 Cu.import("resource://gre/modules/Services.jsm"); |
|
105 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
106 Cu.import("resource:///modules/devtools/SideMenuWidget.jsm"); |
|
107 Cu.import("resource:///modules/devtools/VariablesView.jsm"); |
|
108 Cu.import("resource:///modules/devtools/VariablesViewController.jsm"); |
|
109 Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); |
|
110 |
|
111 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; |
|
112 const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise; |
|
113 const EventEmitter = require("devtools/toolkit/event-emitter"); |
|
114 const Editor = require("devtools/sourceeditor/editor"); |
|
115 const {Tooltip} = require("devtools/shared/widgets/Tooltip"); |
|
116 |
|
117 XPCOMUtils.defineLazyModuleGetter(this, "Chart", |
|
118 "resource:///modules/devtools/Chart.jsm"); |
|
119 |
|
120 XPCOMUtils.defineLazyModuleGetter(this, "Curl", |
|
121 "resource:///modules/devtools/Curl.jsm"); |
|
122 |
|
123 XPCOMUtils.defineLazyModuleGetter(this, "CurlUtils", |
|
124 "resource:///modules/devtools/Curl.jsm"); |
|
125 |
|
126 XPCOMUtils.defineLazyModuleGetter(this, "Task", |
|
127 "resource://gre/modules/Task.jsm"); |
|
128 |
|
129 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", |
|
130 "resource://gre/modules/PluralForm.jsm"); |
|
131 |
|
132 XPCOMUtils.defineLazyModuleGetter(this, "DevToolsUtils", |
|
133 "resource://gre/modules/devtools/DevToolsUtils.jsm"); |
|
134 |
|
135 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper", |
|
136 "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper"); |
|
137 |
|
138 Object.defineProperty(this, "NetworkHelper", { |
|
139 get: function() { |
|
140 return require("devtools/toolkit/webconsole/network-helper"); |
|
141 }, |
|
142 configurable: true, |
|
143 enumerable: true |
|
144 }); |
|
145 |
|
146 /** |
|
147 * Object defining the network monitor controller components. |
|
148 */ |
|
149 let NetMonitorController = { |
|
150 /** |
|
151 * Initializes the view. |
|
152 * |
|
153 * @return object |
|
154 * A promise that is resolved when the monitor finishes startup. |
|
155 */ |
|
156 startupNetMonitor: function() { |
|
157 if (this._startup) { |
|
158 return this._startup; |
|
159 } |
|
160 |
|
161 NetMonitorView.initialize(); |
|
162 |
|
163 // Startup is synchronous, for now. |
|
164 return this._startup = promise.resolve(); |
|
165 }, |
|
166 |
|
167 /** |
|
168 * Destroys the view and disconnects the monitor client from the server. |
|
169 * |
|
170 * @return object |
|
171 * A promise that is resolved when the monitor finishes shutdown. |
|
172 */ |
|
173 shutdownNetMonitor: function() { |
|
174 if (this._shutdown) { |
|
175 return this._shutdown; |
|
176 } |
|
177 |
|
178 NetMonitorView.destroy(); |
|
179 this.TargetEventsHandler.disconnect(); |
|
180 this.NetworkEventsHandler.disconnect(); |
|
181 this.disconnect(); |
|
182 |
|
183 // Shutdown is synchronous, for now. |
|
184 return this._shutdown = promise.resolve(); |
|
185 }, |
|
186 |
|
187 /** |
|
188 * Initiates remote or chrome network monitoring based on the current target, |
|
189 * wiring event handlers as necessary. |
|
190 * |
|
191 * @return object |
|
192 * A promise that is resolved when the monitor finishes connecting. |
|
193 */ |
|
194 connect: Task.async(function*() { |
|
195 if (this._connection) { |
|
196 return this._connection; |
|
197 } |
|
198 |
|
199 let deferred = promise.defer(); |
|
200 this._connection = deferred.promise; |
|
201 |
|
202 let target = this._target; |
|
203 let { client, form } = target; |
|
204 if (target.chrome) { |
|
205 this._startChromeMonitoring(client, form.consoleActor, deferred.resolve); |
|
206 } else { |
|
207 this._startMonitoringTab(client, form, deferred.resolve); |
|
208 } |
|
209 |
|
210 yield deferred.promise; |
|
211 window.emit(EVENTS.CONNECTED); |
|
212 }), |
|
213 |
|
214 /** |
|
215 * Disconnects the debugger client and removes event handlers as necessary. |
|
216 */ |
|
217 disconnect: function() { |
|
218 // When debugging local or a remote instance, the connection is closed by |
|
219 // the RemoteTarget. |
|
220 this._connection = null; |
|
221 this.client = null; |
|
222 this.tabClient = null; |
|
223 this.webConsoleClient = null; |
|
224 }, |
|
225 |
|
226 /** |
|
227 * Sets up a monitoring session. |
|
228 * |
|
229 * @param DebuggerClient aClient |
|
230 * The debugger client. |
|
231 * @param object aTabGrip |
|
232 * The remote protocol grip of the tab. |
|
233 * @param function aCallback |
|
234 * A function to invoke once the client attached to the console client. |
|
235 */ |
|
236 _startMonitoringTab: function(aClient, aTabGrip, aCallback) { |
|
237 if (!aClient) { |
|
238 Cu.reportError("No client found!"); |
|
239 return; |
|
240 } |
|
241 this.client = aClient; |
|
242 |
|
243 aClient.attachTab(aTabGrip.actor, (aResponse, aTabClient) => { |
|
244 if (!aTabClient) { |
|
245 Cu.reportError("No tab client found!"); |
|
246 return; |
|
247 } |
|
248 this.tabClient = aTabClient; |
|
249 |
|
250 aClient.attachConsole(aTabGrip.consoleActor, LISTENERS, (aResponse, aWebConsoleClient) => { |
|
251 if (!aWebConsoleClient) { |
|
252 Cu.reportError("Couldn't attach to console: " + aResponse.error); |
|
253 return; |
|
254 } |
|
255 this.webConsoleClient = aWebConsoleClient; |
|
256 this.webConsoleClient.setPreferences(NET_PREFS, () => { |
|
257 this.TargetEventsHandler.connect(); |
|
258 this.NetworkEventsHandler.connect(); |
|
259 |
|
260 if (aCallback) { |
|
261 aCallback(); |
|
262 } |
|
263 }); |
|
264 }); |
|
265 }); |
|
266 }, |
|
267 |
|
268 /** |
|
269 * Sets up a chrome monitoring session. |
|
270 * |
|
271 * @param DebuggerClient aClient |
|
272 * The debugger client. |
|
273 * @param object aConsoleActor |
|
274 * The remote protocol grip of the chrome debugger. |
|
275 * @param function aCallback |
|
276 * A function to invoke once the client attached to the console client. |
|
277 */ |
|
278 _startChromeMonitoring: function(aClient, aConsoleActor, aCallback) { |
|
279 if (!aClient) { |
|
280 Cu.reportError("No client found!"); |
|
281 return; |
|
282 } |
|
283 this.client = aClient; |
|
284 |
|
285 aClient.attachConsole(aConsoleActor, LISTENERS, (aResponse, aWebConsoleClient) => { |
|
286 if (!aWebConsoleClient) { |
|
287 Cu.reportError("Couldn't attach to console: " + aResponse.error); |
|
288 return; |
|
289 } |
|
290 this.webConsoleClient = aWebConsoleClient; |
|
291 this.webConsoleClient.setPreferences(NET_PREFS, () => { |
|
292 this.TargetEventsHandler.connect(); |
|
293 this.NetworkEventsHandler.connect(); |
|
294 |
|
295 if (aCallback) { |
|
296 aCallback(); |
|
297 } |
|
298 }); |
|
299 }); |
|
300 }, |
|
301 |
|
302 /** |
|
303 * Gets the activity currently performed by the frontend. |
|
304 * @return number |
|
305 */ |
|
306 getCurrentActivity: function() { |
|
307 return this._currentActivity || ACTIVITY_TYPE.NONE; |
|
308 }, |
|
309 |
|
310 /** |
|
311 * Triggers a specific "activity" to be performed by the frontend. This can be, |
|
312 * for example, triggering reloads or enabling/disabling cache. |
|
313 * |
|
314 * @param number aType |
|
315 * The activity type. See the ACTIVITY_TYPE const. |
|
316 * @return object |
|
317 * A promise resolved once the activity finishes and the frontend |
|
318 * is back into "standby" mode. |
|
319 */ |
|
320 triggerActivity: function(aType) { |
|
321 // Puts the frontend into "standby" (when there's no particular activity). |
|
322 let standBy = () => { |
|
323 this._currentActivity = ACTIVITY_TYPE.NONE; |
|
324 }; |
|
325 |
|
326 // Waits for a series of "navigation start" and "navigation stop" events. |
|
327 let waitForNavigation = () => { |
|
328 let deferred = promise.defer(); |
|
329 this._target.once("will-navigate", () => { |
|
330 this._target.once("navigate", () => { |
|
331 deferred.resolve(); |
|
332 }); |
|
333 }); |
|
334 return deferred.promise; |
|
335 }; |
|
336 |
|
337 // Reconfigures the tab, optionally triggering a reload. |
|
338 let reconfigureTab = aOptions => { |
|
339 let deferred = promise.defer(); |
|
340 this._target.activeTab.reconfigure(aOptions, deferred.resolve); |
|
341 return deferred.promise; |
|
342 }; |
|
343 |
|
344 // Reconfigures the tab and waits for the target to finish navigating. |
|
345 let reconfigureTabAndWaitForNavigation = aOptions => { |
|
346 aOptions.performReload = true; |
|
347 let navigationFinished = waitForNavigation(); |
|
348 return reconfigureTab(aOptions).then(() => navigationFinished); |
|
349 } |
|
350 |
|
351 if (aType == ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED) { |
|
352 this._currentActivity = ACTIVITY_TYPE.ENABLE_CACHE; |
|
353 this._target.once("will-navigate", () => this._currentActivity = aType); |
|
354 return reconfigureTabAndWaitForNavigation({ cacheEnabled: true }).then(standBy); |
|
355 } |
|
356 if (aType == ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED) { |
|
357 this._currentActivity = ACTIVITY_TYPE.DISABLE_CACHE; |
|
358 this._target.once("will-navigate", () => this._currentActivity = aType); |
|
359 return reconfigureTabAndWaitForNavigation({ cacheEnabled: false }).then(standBy); |
|
360 } |
|
361 if (aType == ACTIVITY_TYPE.ENABLE_CACHE) { |
|
362 this._currentActivity = aType; |
|
363 return reconfigureTab({ cacheEnabled: true, performReload: false }).then(standBy); |
|
364 } |
|
365 if (aType == ACTIVITY_TYPE.DISABLE_CACHE) { |
|
366 this._currentActivity = aType; |
|
367 return reconfigureTab({ cacheEnabled: false, performReload: false }).then(standBy); |
|
368 } |
|
369 this._currentActivity = ACTIVITY_TYPE.NONE; |
|
370 return promise.reject(new Error("Invalid activity type")); |
|
371 }, |
|
372 |
|
373 /** |
|
374 * Getter that tells if the server supports sending custom network requests. |
|
375 * @type boolean |
|
376 */ |
|
377 get supportsCustomRequest() { |
|
378 return this.webConsoleClient && |
|
379 (this.webConsoleClient.traits.customNetworkRequest || |
|
380 !this._target.isApp); |
|
381 }, |
|
382 |
|
383 /** |
|
384 * Getter that tells if the server can do network performance statistics. |
|
385 * @type boolean |
|
386 */ |
|
387 get supportsPerfStats() { |
|
388 return this.tabClient && |
|
389 (this.tabClient.traits.reconfigure || !this._target.isApp); |
|
390 }, |
|
391 |
|
392 _startup: null, |
|
393 _shutdown: null, |
|
394 _connection: null, |
|
395 _currentActivity: null, |
|
396 client: null, |
|
397 tabClient: null, |
|
398 webConsoleClient: null |
|
399 }; |
|
400 |
|
401 /** |
|
402 * Functions handling target-related lifetime events. |
|
403 */ |
|
404 function TargetEventsHandler() { |
|
405 this._onTabNavigated = this._onTabNavigated.bind(this); |
|
406 this._onTabDetached = this._onTabDetached.bind(this); |
|
407 } |
|
408 |
|
409 TargetEventsHandler.prototype = { |
|
410 get target() NetMonitorController._target, |
|
411 get webConsoleClient() NetMonitorController.webConsoleClient, |
|
412 |
|
413 /** |
|
414 * Listen for events emitted by the current tab target. |
|
415 */ |
|
416 connect: function() { |
|
417 dumpn("TargetEventsHandler is connecting..."); |
|
418 this.target.on("close", this._onTabDetached); |
|
419 this.target.on("navigate", this._onTabNavigated); |
|
420 this.target.on("will-navigate", this._onTabNavigated); |
|
421 }, |
|
422 |
|
423 /** |
|
424 * Remove events emitted by the current tab target. |
|
425 */ |
|
426 disconnect: function() { |
|
427 if (!this.target) { |
|
428 return; |
|
429 } |
|
430 dumpn("TargetEventsHandler is disconnecting..."); |
|
431 this.target.off("close", this._onTabDetached); |
|
432 this.target.off("navigate", this._onTabNavigated); |
|
433 this.target.off("will-navigate", this._onTabNavigated); |
|
434 }, |
|
435 |
|
436 /** |
|
437 * Called for each location change in the monitored tab. |
|
438 * |
|
439 * @param string aType |
|
440 * Packet type. |
|
441 * @param object aPacket |
|
442 * Packet received from the server. |
|
443 */ |
|
444 _onTabNavigated: function(aType, aPacket) { |
|
445 switch (aType) { |
|
446 case "will-navigate": { |
|
447 // Reset UI. |
|
448 if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) { |
|
449 NetMonitorView.RequestsMenu.reset(); |
|
450 NetMonitorView.Sidebar.toggle(false); |
|
451 } |
|
452 // Switch to the default network traffic inspector view. |
|
453 if (NetMonitorController.getCurrentActivity() == ACTIVITY_TYPE.NONE) { |
|
454 NetMonitorView.showNetworkInspectorView(); |
|
455 } |
|
456 |
|
457 window.emit(EVENTS.TARGET_WILL_NAVIGATE); |
|
458 break; |
|
459 } |
|
460 case "navigate": { |
|
461 window.emit(EVENTS.TARGET_DID_NAVIGATE); |
|
462 break; |
|
463 } |
|
464 } |
|
465 }, |
|
466 |
|
467 /** |
|
468 * Called when the monitored tab is closed. |
|
469 */ |
|
470 _onTabDetached: function() { |
|
471 NetMonitorController.shutdownNetMonitor(); |
|
472 } |
|
473 }; |
|
474 |
|
475 /** |
|
476 * Functions handling target network events. |
|
477 */ |
|
478 function NetworkEventsHandler() { |
|
479 this._onNetworkEvent = this._onNetworkEvent.bind(this); |
|
480 this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this); |
|
481 this._onRequestHeaders = this._onRequestHeaders.bind(this); |
|
482 this._onRequestCookies = this._onRequestCookies.bind(this); |
|
483 this._onRequestPostData = this._onRequestPostData.bind(this); |
|
484 this._onResponseHeaders = this._onResponseHeaders.bind(this); |
|
485 this._onResponseCookies = this._onResponseCookies.bind(this); |
|
486 this._onResponseContent = this._onResponseContent.bind(this); |
|
487 this._onEventTimings = this._onEventTimings.bind(this); |
|
488 } |
|
489 |
|
490 NetworkEventsHandler.prototype = { |
|
491 get client() NetMonitorController._target.client, |
|
492 get webConsoleClient() NetMonitorController.webConsoleClient, |
|
493 |
|
494 /** |
|
495 * Connect to the current target client. |
|
496 */ |
|
497 connect: function() { |
|
498 dumpn("NetworkEventsHandler is connecting..."); |
|
499 this.client.addListener("networkEvent", this._onNetworkEvent); |
|
500 this.client.addListener("networkEventUpdate", this._onNetworkEventUpdate); |
|
501 }, |
|
502 |
|
503 /** |
|
504 * Disconnect from the client. |
|
505 */ |
|
506 disconnect: function() { |
|
507 if (!this.client) { |
|
508 return; |
|
509 } |
|
510 dumpn("NetworkEventsHandler is disconnecting..."); |
|
511 this.client.removeListener("networkEvent", this._onNetworkEvent); |
|
512 this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate); |
|
513 }, |
|
514 |
|
515 /** |
|
516 * The "networkEvent" message type handler. |
|
517 * |
|
518 * @param string aType |
|
519 * Message type. |
|
520 * @param object aPacket |
|
521 * The message received from the server. |
|
522 */ |
|
523 _onNetworkEvent: function(aType, aPacket) { |
|
524 if (aPacket.from != this.webConsoleClient.actor) { |
|
525 // Skip events from different console actors. |
|
526 return; |
|
527 } |
|
528 |
|
529 let { actor, startedDateTime, method, url, isXHR } = aPacket.eventActor; |
|
530 NetMonitorView.RequestsMenu.addRequest(actor, startedDateTime, method, url, isXHR); |
|
531 window.emit(EVENTS.NETWORK_EVENT); |
|
532 }, |
|
533 |
|
534 /** |
|
535 * The "networkEventUpdate" message type handler. |
|
536 * |
|
537 * @param string aType |
|
538 * Message type. |
|
539 * @param object aPacket |
|
540 * The message received from the server. |
|
541 */ |
|
542 _onNetworkEventUpdate: function(aType, aPacket) { |
|
543 let actor = aPacket.from; |
|
544 if (!NetMonitorView.RequestsMenu.getItemByValue(actor)) { |
|
545 // Skip events from unknown actors. |
|
546 return; |
|
547 } |
|
548 |
|
549 switch (aPacket.updateType) { |
|
550 case "requestHeaders": |
|
551 this.webConsoleClient.getRequestHeaders(actor, this._onRequestHeaders); |
|
552 window.emit(EVENTS.UPDATING_REQUEST_HEADERS); |
|
553 break; |
|
554 case "requestCookies": |
|
555 this.webConsoleClient.getRequestCookies(actor, this._onRequestCookies); |
|
556 window.emit(EVENTS.UPDATING_REQUEST_COOKIES); |
|
557 break; |
|
558 case "requestPostData": |
|
559 this.webConsoleClient.getRequestPostData(actor, this._onRequestPostData); |
|
560 window.emit(EVENTS.UPDATING_REQUEST_POST_DATA); |
|
561 break; |
|
562 case "responseHeaders": |
|
563 this.webConsoleClient.getResponseHeaders(actor, this._onResponseHeaders); |
|
564 window.emit(EVENTS.UPDATING_RESPONSE_HEADERS); |
|
565 break; |
|
566 case "responseCookies": |
|
567 this.webConsoleClient.getResponseCookies(actor, this._onResponseCookies); |
|
568 window.emit(EVENTS.UPDATING_RESPONSE_COOKIES); |
|
569 break; |
|
570 case "responseStart": |
|
571 NetMonitorView.RequestsMenu.updateRequest(aPacket.from, { |
|
572 httpVersion: aPacket.response.httpVersion, |
|
573 status: aPacket.response.status, |
|
574 statusText: aPacket.response.statusText, |
|
575 headersSize: aPacket.response.headersSize |
|
576 }); |
|
577 window.emit(EVENTS.STARTED_RECEIVING_RESPONSE); |
|
578 break; |
|
579 case "responseContent": |
|
580 NetMonitorView.RequestsMenu.updateRequest(aPacket.from, { |
|
581 contentSize: aPacket.contentSize, |
|
582 mimeType: aPacket.mimeType |
|
583 }); |
|
584 this.webConsoleClient.getResponseContent(actor, this._onResponseContent); |
|
585 window.emit(EVENTS.UPDATING_RESPONSE_CONTENT); |
|
586 break; |
|
587 case "eventTimings": |
|
588 NetMonitorView.RequestsMenu.updateRequest(aPacket.from, { |
|
589 totalTime: aPacket.totalTime |
|
590 }); |
|
591 this.webConsoleClient.getEventTimings(actor, this._onEventTimings); |
|
592 window.emit(EVENTS.UPDATING_EVENT_TIMINGS); |
|
593 break; |
|
594 } |
|
595 }, |
|
596 |
|
597 /** |
|
598 * Handles additional information received for a "requestHeaders" packet. |
|
599 * |
|
600 * @param object aResponse |
|
601 * The message received from the server. |
|
602 */ |
|
603 _onRequestHeaders: function(aResponse) { |
|
604 NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { |
|
605 requestHeaders: aResponse |
|
606 }); |
|
607 window.emit(EVENTS.RECEIVED_REQUEST_HEADERS); |
|
608 }, |
|
609 |
|
610 /** |
|
611 * Handles additional information received for a "requestCookies" packet. |
|
612 * |
|
613 * @param object aResponse |
|
614 * The message received from the server. |
|
615 */ |
|
616 _onRequestCookies: function(aResponse) { |
|
617 NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { |
|
618 requestCookies: aResponse |
|
619 }); |
|
620 window.emit(EVENTS.RECEIVED_REQUEST_COOKIES); |
|
621 }, |
|
622 |
|
623 /** |
|
624 * Handles additional information received for a "requestPostData" packet. |
|
625 * |
|
626 * @param object aResponse |
|
627 * The message received from the server. |
|
628 */ |
|
629 _onRequestPostData: function(aResponse) { |
|
630 NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { |
|
631 requestPostData: aResponse |
|
632 }); |
|
633 window.emit(EVENTS.RECEIVED_REQUEST_POST_DATA); |
|
634 }, |
|
635 |
|
636 /** |
|
637 * Handles additional information received for a "responseHeaders" packet. |
|
638 * |
|
639 * @param object aResponse |
|
640 * The message received from the server. |
|
641 */ |
|
642 _onResponseHeaders: function(aResponse) { |
|
643 NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { |
|
644 responseHeaders: aResponse |
|
645 }); |
|
646 window.emit(EVENTS.RECEIVED_RESPONSE_HEADERS); |
|
647 }, |
|
648 |
|
649 /** |
|
650 * Handles additional information received for a "responseCookies" packet. |
|
651 * |
|
652 * @param object aResponse |
|
653 * The message received from the server. |
|
654 */ |
|
655 _onResponseCookies: function(aResponse) { |
|
656 NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { |
|
657 responseCookies: aResponse |
|
658 }); |
|
659 window.emit(EVENTS.RECEIVED_RESPONSE_COOKIES); |
|
660 }, |
|
661 |
|
662 /** |
|
663 * Handles additional information received for a "responseContent" packet. |
|
664 * |
|
665 * @param object aResponse |
|
666 * The message received from the server. |
|
667 */ |
|
668 _onResponseContent: function(aResponse) { |
|
669 NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { |
|
670 responseContent: aResponse |
|
671 }); |
|
672 window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT); |
|
673 }, |
|
674 |
|
675 /** |
|
676 * Handles additional information received for a "eventTimings" packet. |
|
677 * |
|
678 * @param object aResponse |
|
679 * The message received from the server. |
|
680 */ |
|
681 _onEventTimings: function(aResponse) { |
|
682 NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { |
|
683 eventTimings: aResponse |
|
684 }); |
|
685 window.emit(EVENTS.RECEIVED_EVENT_TIMINGS); |
|
686 }, |
|
687 |
|
688 /** |
|
689 * Fetches the full text of a LongString. |
|
690 * |
|
691 * @param object | string aStringGrip |
|
692 * The long string grip containing the corresponding actor. |
|
693 * If you pass in a plain string (by accident or because you're lazy), |
|
694 * then a promise of the same string is simply returned. |
|
695 * @return object Promise |
|
696 * A promise that is resolved when the full string contents |
|
697 * are available, or rejected if something goes wrong. |
|
698 */ |
|
699 getString: function(aStringGrip) { |
|
700 // Make sure this is a long string. |
|
701 if (typeof aStringGrip != "object" || aStringGrip.type != "longString") { |
|
702 return promise.resolve(aStringGrip); // Go home string, you're drunk. |
|
703 } |
|
704 // Fetch the long string only once. |
|
705 if (aStringGrip._fullText) { |
|
706 return aStringGrip._fullText.promise; |
|
707 } |
|
708 |
|
709 let deferred = aStringGrip._fullText = promise.defer(); |
|
710 let { actor, initial, length } = aStringGrip; |
|
711 let longStringClient = this.webConsoleClient.longString(aStringGrip); |
|
712 |
|
713 longStringClient.substring(initial.length, length, aResponse => { |
|
714 if (aResponse.error) { |
|
715 Cu.reportError(aResponse.error + ": " + aResponse.message); |
|
716 deferred.reject(aResponse); |
|
717 return; |
|
718 } |
|
719 deferred.resolve(initial + aResponse.substring); |
|
720 }); |
|
721 |
|
722 return deferred.promise; |
|
723 } |
|
724 }; |
|
725 |
|
726 /** |
|
727 * Localization convenience methods. |
|
728 */ |
|
729 let L10N = new ViewHelpers.L10N(NET_STRINGS_URI); |
|
730 |
|
731 /** |
|
732 * Shortcuts for accessing various network monitor preferences. |
|
733 */ |
|
734 let Prefs = new ViewHelpers.Prefs("devtools.netmonitor", { |
|
735 networkDetailsWidth: ["Int", "panes-network-details-width"], |
|
736 networkDetailsHeight: ["Int", "panes-network-details-height"], |
|
737 statistics: ["Bool", "statistics"], |
|
738 filters: ["Json", "filters"] |
|
739 }); |
|
740 |
|
741 /** |
|
742 * Returns true if this is document is in RTL mode. |
|
743 * @return boolean |
|
744 */ |
|
745 XPCOMUtils.defineLazyGetter(window, "isRTL", function() { |
|
746 return window.getComputedStyle(document.documentElement, null).direction == "rtl"; |
|
747 }); |
|
748 |
|
749 /** |
|
750 * Convenient way of emitting events from the panel window. |
|
751 */ |
|
752 EventEmitter.decorate(this); |
|
753 |
|
754 /** |
|
755 * Preliminary setup for the NetMonitorController object. |
|
756 */ |
|
757 NetMonitorController.TargetEventsHandler = new TargetEventsHandler(); |
|
758 NetMonitorController.NetworkEventsHandler = new NetworkEventsHandler(); |
|
759 |
|
760 /** |
|
761 * Export some properties to the global scope for easier access. |
|
762 */ |
|
763 Object.defineProperties(window, { |
|
764 "gNetwork": { |
|
765 get: function() NetMonitorController.NetworkEventsHandler |
|
766 } |
|
767 }); |
|
768 |
|
769 /** |
|
770 * Makes sure certain properties are available on all objects in a data store. |
|
771 * |
|
772 * @param array aDataStore |
|
773 * A list of objects for which to check the availability of properties. |
|
774 * @param array aMandatoryFields |
|
775 * A list of strings representing properties of objects in aDataStore. |
|
776 * @return object |
|
777 * A promise resolved when all objects in aDataStore contain the |
|
778 * properties defined in aMandatoryFields. |
|
779 */ |
|
780 function whenDataAvailable(aDataStore, aMandatoryFields) { |
|
781 let deferred = promise.defer(); |
|
782 |
|
783 let interval = setInterval(() => { |
|
784 if (aDataStore.every(item => aMandatoryFields.every(field => field in item))) { |
|
785 clearInterval(interval); |
|
786 clearTimeout(timer); |
|
787 deferred.resolve(); |
|
788 } |
|
789 }, WDA_DEFAULT_VERIFY_INTERVAL); |
|
790 |
|
791 let timer = setTimeout(() => { |
|
792 clearInterval(interval); |
|
793 deferred.reject(new Error("Timed out while waiting for data")); |
|
794 }, WDA_DEFAULT_GIVE_UP_TIMEOUT); |
|
795 |
|
796 return deferred.promise; |
|
797 }; |
|
798 |
|
799 const WDA_DEFAULT_VERIFY_INTERVAL = 50; // ms |
|
800 const WDA_DEFAULT_GIVE_UP_TIMEOUT = 2000; // ms |
|
801 |
|
802 /** |
|
803 * Helper method for debugging. |
|
804 * @param string |
|
805 */ |
|
806 function dumpn(str) { |
|
807 if (wantLogging) { |
|
808 dump("NET-FRONTEND: " + str + "\n"); |
|
809 } |
|
810 } |
|
811 |
|
812 let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log"); |