browser/devtools/netmonitor/netmonitor-controller.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial