Wed, 31 Dec 2014 06:09:35 +0100
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"); |