browser/devtools/netmonitor/netmonitor-controller.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:bbbcf7f9556c
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");

mercurial