michael@0: /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: "use strict"; michael@0: michael@0: const { classes: Cc, interfaces: Ci, utils: Cu } = Components; michael@0: michael@0: const NET_STRINGS_URI = "chrome://browser/locale/devtools/netmonitor.properties"; michael@0: const LISTENERS = [ "NetworkActivity" ]; michael@0: const NET_PREFS = { "NetworkMonitor.saveRequestAndResponseBodies": true }; michael@0: michael@0: // The panel's window global is an EventEmitter firing the following events: michael@0: const EVENTS = { michael@0: // When the monitored target begins and finishes navigating. michael@0: TARGET_WILL_NAVIGATE: "NetMonitor:TargetWillNavigate", michael@0: TARGET_DID_NAVIGATE: "NetMonitor:TargetNavigate", michael@0: michael@0: // When a network event is received. michael@0: // See https://developer.mozilla.org/docs/Tools/Web_Console/remoting for michael@0: // more information about what each packet is supposed to deliver. michael@0: NETWORK_EVENT: "NetMonitor:NetworkEvent", michael@0: michael@0: // When request headers begin and finish receiving. michael@0: UPDATING_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdating:RequestHeaders", michael@0: RECEIVED_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdated:RequestHeaders", michael@0: michael@0: // When request cookies begin and finish receiving. michael@0: UPDATING_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdating:RequestCookies", michael@0: RECEIVED_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdated:RequestCookies", michael@0: michael@0: // When request post data begins and finishes receiving. michael@0: UPDATING_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdating:RequestPostData", michael@0: RECEIVED_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdated:RequestPostData", michael@0: michael@0: // When response headers begin and finish receiving. michael@0: UPDATING_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdating:ResponseHeaders", michael@0: RECEIVED_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdated:ResponseHeaders", michael@0: michael@0: // When response cookies begin and finish receiving. michael@0: UPDATING_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdating:ResponseCookies", michael@0: RECEIVED_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdated:ResponseCookies", michael@0: michael@0: // When event timings begin and finish receiving. michael@0: UPDATING_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdating:EventTimings", michael@0: RECEIVED_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdated:EventTimings", michael@0: michael@0: // When response content begins, updates and finishes receiving. michael@0: STARTED_RECEIVING_RESPONSE: "NetMonitor:NetworkEventUpdating:ResponseStart", michael@0: UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent", michael@0: RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent", michael@0: michael@0: // When the request post params are displayed in the UI. michael@0: REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable", michael@0: michael@0: // When the response body is displayed in the UI. michael@0: RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable", michael@0: michael@0: // When the html response preview is displayed in the UI. michael@0: RESPONSE_HTML_PREVIEW_DISPLAYED: "NetMonitor:ResponseHtmlPreviewAvailable", michael@0: michael@0: // When the image response thumbnail is displayed in the UI. michael@0: RESPONSE_IMAGE_THUMBNAIL_DISPLAYED: "NetMonitor:ResponseImageThumbnailAvailable", michael@0: michael@0: // When a tab is selected in the NetworkDetailsView and subsequently rendered. michael@0: TAB_UPDATED: "NetMonitor:TabUpdated", michael@0: michael@0: // Fired when Sidebar has finished being populated. michael@0: SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated", michael@0: michael@0: // Fired when NetworkDetailsView has finished being populated. michael@0: NETWORKDETAILSVIEW_POPULATED: "NetMonitor:NetworkDetailsViewPopulated", michael@0: michael@0: // Fired when CustomRequestView has finished being populated. michael@0: CUSTOMREQUESTVIEW_POPULATED: "NetMonitor:CustomRequestViewPopulated", michael@0: michael@0: // Fired when charts have been displayed in the PerformanceStatisticsView. michael@0: PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed", michael@0: PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed", michael@0: EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed", michael@0: michael@0: // Fired once the NetMonitorController establishes a connection to the debug michael@0: // target. michael@0: CONNECTED: "connected", michael@0: }; michael@0: michael@0: // Descriptions for what this frontend is currently doing. michael@0: const ACTIVITY_TYPE = { michael@0: // Standing by and handling requests normally. michael@0: NONE: 0, michael@0: michael@0: // Forcing the target to reload with cache enabled or disabled. michael@0: RELOAD: { michael@0: WITH_CACHE_ENABLED: 1, michael@0: WITH_CACHE_DISABLED: 2 michael@0: }, michael@0: michael@0: // Enabling or disabling the cache without triggering a reload. michael@0: ENABLE_CACHE: 3, michael@0: DISABLE_CACHE: 4 michael@0: }; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource:///modules/devtools/SideMenuWidget.jsm"); michael@0: Cu.import("resource:///modules/devtools/VariablesView.jsm"); michael@0: Cu.import("resource:///modules/devtools/VariablesViewController.jsm"); michael@0: Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); michael@0: michael@0: const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; michael@0: const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise; michael@0: const EventEmitter = require("devtools/toolkit/event-emitter"); michael@0: const Editor = require("devtools/sourceeditor/editor"); michael@0: const {Tooltip} = require("devtools/shared/widgets/Tooltip"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Chart", michael@0: "resource:///modules/devtools/Chart.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Curl", michael@0: "resource:///modules/devtools/Curl.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "CurlUtils", michael@0: "resource:///modules/devtools/Curl.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Task", michael@0: "resource://gre/modules/Task.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", michael@0: "resource://gre/modules/PluralForm.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "DevToolsUtils", michael@0: "resource://gre/modules/devtools/DevToolsUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper", michael@0: "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper"); michael@0: michael@0: Object.defineProperty(this, "NetworkHelper", { michael@0: get: function() { michael@0: return require("devtools/toolkit/webconsole/network-helper"); michael@0: }, michael@0: configurable: true, michael@0: enumerable: true michael@0: }); michael@0: michael@0: /** michael@0: * Object defining the network monitor controller components. michael@0: */ michael@0: let NetMonitorController = { michael@0: /** michael@0: * Initializes the view. michael@0: * michael@0: * @return object michael@0: * A promise that is resolved when the monitor finishes startup. michael@0: */ michael@0: startupNetMonitor: function() { michael@0: if (this._startup) { michael@0: return this._startup; michael@0: } michael@0: michael@0: NetMonitorView.initialize(); michael@0: michael@0: // Startup is synchronous, for now. michael@0: return this._startup = promise.resolve(); michael@0: }, michael@0: michael@0: /** michael@0: * Destroys the view and disconnects the monitor client from the server. michael@0: * michael@0: * @return object michael@0: * A promise that is resolved when the monitor finishes shutdown. michael@0: */ michael@0: shutdownNetMonitor: function() { michael@0: if (this._shutdown) { michael@0: return this._shutdown; michael@0: } michael@0: michael@0: NetMonitorView.destroy(); michael@0: this.TargetEventsHandler.disconnect(); michael@0: this.NetworkEventsHandler.disconnect(); michael@0: this.disconnect(); michael@0: michael@0: // Shutdown is synchronous, for now. michael@0: return this._shutdown = promise.resolve(); michael@0: }, michael@0: michael@0: /** michael@0: * Initiates remote or chrome network monitoring based on the current target, michael@0: * wiring event handlers as necessary. michael@0: * michael@0: * @return object michael@0: * A promise that is resolved when the monitor finishes connecting. michael@0: */ michael@0: connect: Task.async(function*() { michael@0: if (this._connection) { michael@0: return this._connection; michael@0: } michael@0: michael@0: let deferred = promise.defer(); michael@0: this._connection = deferred.promise; michael@0: michael@0: let target = this._target; michael@0: let { client, form } = target; michael@0: if (target.chrome) { michael@0: this._startChromeMonitoring(client, form.consoleActor, deferred.resolve); michael@0: } else { michael@0: this._startMonitoringTab(client, form, deferred.resolve); michael@0: } michael@0: michael@0: yield deferred.promise; michael@0: window.emit(EVENTS.CONNECTED); michael@0: }), michael@0: michael@0: /** michael@0: * Disconnects the debugger client and removes event handlers as necessary. michael@0: */ michael@0: disconnect: function() { michael@0: // When debugging local or a remote instance, the connection is closed by michael@0: // the RemoteTarget. michael@0: this._connection = null; michael@0: this.client = null; michael@0: this.tabClient = null; michael@0: this.webConsoleClient = null; michael@0: }, michael@0: michael@0: /** michael@0: * Sets up a monitoring session. michael@0: * michael@0: * @param DebuggerClient aClient michael@0: * The debugger client. michael@0: * @param object aTabGrip michael@0: * The remote protocol grip of the tab. michael@0: * @param function aCallback michael@0: * A function to invoke once the client attached to the console client. michael@0: */ michael@0: _startMonitoringTab: function(aClient, aTabGrip, aCallback) { michael@0: if (!aClient) { michael@0: Cu.reportError("No client found!"); michael@0: return; michael@0: } michael@0: this.client = aClient; michael@0: michael@0: aClient.attachTab(aTabGrip.actor, (aResponse, aTabClient) => { michael@0: if (!aTabClient) { michael@0: Cu.reportError("No tab client found!"); michael@0: return; michael@0: } michael@0: this.tabClient = aTabClient; michael@0: michael@0: aClient.attachConsole(aTabGrip.consoleActor, LISTENERS, (aResponse, aWebConsoleClient) => { michael@0: if (!aWebConsoleClient) { michael@0: Cu.reportError("Couldn't attach to console: " + aResponse.error); michael@0: return; michael@0: } michael@0: this.webConsoleClient = aWebConsoleClient; michael@0: this.webConsoleClient.setPreferences(NET_PREFS, () => { michael@0: this.TargetEventsHandler.connect(); michael@0: this.NetworkEventsHandler.connect(); michael@0: michael@0: if (aCallback) { michael@0: aCallback(); michael@0: } michael@0: }); michael@0: }); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Sets up a chrome monitoring session. michael@0: * michael@0: * @param DebuggerClient aClient michael@0: * The debugger client. michael@0: * @param object aConsoleActor michael@0: * The remote protocol grip of the chrome debugger. michael@0: * @param function aCallback michael@0: * A function to invoke once the client attached to the console client. michael@0: */ michael@0: _startChromeMonitoring: function(aClient, aConsoleActor, aCallback) { michael@0: if (!aClient) { michael@0: Cu.reportError("No client found!"); michael@0: return; michael@0: } michael@0: this.client = aClient; michael@0: michael@0: aClient.attachConsole(aConsoleActor, LISTENERS, (aResponse, aWebConsoleClient) => { michael@0: if (!aWebConsoleClient) { michael@0: Cu.reportError("Couldn't attach to console: " + aResponse.error); michael@0: return; michael@0: } michael@0: this.webConsoleClient = aWebConsoleClient; michael@0: this.webConsoleClient.setPreferences(NET_PREFS, () => { michael@0: this.TargetEventsHandler.connect(); michael@0: this.NetworkEventsHandler.connect(); michael@0: michael@0: if (aCallback) { michael@0: aCallback(); michael@0: } michael@0: }); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Gets the activity currently performed by the frontend. michael@0: * @return number michael@0: */ michael@0: getCurrentActivity: function() { michael@0: return this._currentActivity || ACTIVITY_TYPE.NONE; michael@0: }, michael@0: michael@0: /** michael@0: * Triggers a specific "activity" to be performed by the frontend. This can be, michael@0: * for example, triggering reloads or enabling/disabling cache. michael@0: * michael@0: * @param number aType michael@0: * The activity type. See the ACTIVITY_TYPE const. michael@0: * @return object michael@0: * A promise resolved once the activity finishes and the frontend michael@0: * is back into "standby" mode. michael@0: */ michael@0: triggerActivity: function(aType) { michael@0: // Puts the frontend into "standby" (when there's no particular activity). michael@0: let standBy = () => { michael@0: this._currentActivity = ACTIVITY_TYPE.NONE; michael@0: }; michael@0: michael@0: // Waits for a series of "navigation start" and "navigation stop" events. michael@0: let waitForNavigation = () => { michael@0: let deferred = promise.defer(); michael@0: this._target.once("will-navigate", () => { michael@0: this._target.once("navigate", () => { michael@0: deferred.resolve(); michael@0: }); michael@0: }); michael@0: return deferred.promise; michael@0: }; michael@0: michael@0: // Reconfigures the tab, optionally triggering a reload. michael@0: let reconfigureTab = aOptions => { michael@0: let deferred = promise.defer(); michael@0: this._target.activeTab.reconfigure(aOptions, deferred.resolve); michael@0: return deferred.promise; michael@0: }; michael@0: michael@0: // Reconfigures the tab and waits for the target to finish navigating. michael@0: let reconfigureTabAndWaitForNavigation = aOptions => { michael@0: aOptions.performReload = true; michael@0: let navigationFinished = waitForNavigation(); michael@0: return reconfigureTab(aOptions).then(() => navigationFinished); michael@0: } michael@0: michael@0: if (aType == ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED) { michael@0: this._currentActivity = ACTIVITY_TYPE.ENABLE_CACHE; michael@0: this._target.once("will-navigate", () => this._currentActivity = aType); michael@0: return reconfigureTabAndWaitForNavigation({ cacheEnabled: true }).then(standBy); michael@0: } michael@0: if (aType == ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED) { michael@0: this._currentActivity = ACTIVITY_TYPE.DISABLE_CACHE; michael@0: this._target.once("will-navigate", () => this._currentActivity = aType); michael@0: return reconfigureTabAndWaitForNavigation({ cacheEnabled: false }).then(standBy); michael@0: } michael@0: if (aType == ACTIVITY_TYPE.ENABLE_CACHE) { michael@0: this._currentActivity = aType; michael@0: return reconfigureTab({ cacheEnabled: true, performReload: false }).then(standBy); michael@0: } michael@0: if (aType == ACTIVITY_TYPE.DISABLE_CACHE) { michael@0: this._currentActivity = aType; michael@0: return reconfigureTab({ cacheEnabled: false, performReload: false }).then(standBy); michael@0: } michael@0: this._currentActivity = ACTIVITY_TYPE.NONE; michael@0: return promise.reject(new Error("Invalid activity type")); michael@0: }, michael@0: michael@0: /** michael@0: * Getter that tells if the server supports sending custom network requests. michael@0: * @type boolean michael@0: */ michael@0: get supportsCustomRequest() { michael@0: return this.webConsoleClient && michael@0: (this.webConsoleClient.traits.customNetworkRequest || michael@0: !this._target.isApp); michael@0: }, michael@0: michael@0: /** michael@0: * Getter that tells if the server can do network performance statistics. michael@0: * @type boolean michael@0: */ michael@0: get supportsPerfStats() { michael@0: return this.tabClient && michael@0: (this.tabClient.traits.reconfigure || !this._target.isApp); michael@0: }, michael@0: michael@0: _startup: null, michael@0: _shutdown: null, michael@0: _connection: null, michael@0: _currentActivity: null, michael@0: client: null, michael@0: tabClient: null, michael@0: webConsoleClient: null michael@0: }; michael@0: michael@0: /** michael@0: * Functions handling target-related lifetime events. michael@0: */ michael@0: function TargetEventsHandler() { michael@0: this._onTabNavigated = this._onTabNavigated.bind(this); michael@0: this._onTabDetached = this._onTabDetached.bind(this); michael@0: } michael@0: michael@0: TargetEventsHandler.prototype = { michael@0: get target() NetMonitorController._target, michael@0: get webConsoleClient() NetMonitorController.webConsoleClient, michael@0: michael@0: /** michael@0: * Listen for events emitted by the current tab target. michael@0: */ michael@0: connect: function() { michael@0: dumpn("TargetEventsHandler is connecting..."); michael@0: this.target.on("close", this._onTabDetached); michael@0: this.target.on("navigate", this._onTabNavigated); michael@0: this.target.on("will-navigate", this._onTabNavigated); michael@0: }, michael@0: michael@0: /** michael@0: * Remove events emitted by the current tab target. michael@0: */ michael@0: disconnect: function() { michael@0: if (!this.target) { michael@0: return; michael@0: } michael@0: dumpn("TargetEventsHandler is disconnecting..."); michael@0: this.target.off("close", this._onTabDetached); michael@0: this.target.off("navigate", this._onTabNavigated); michael@0: this.target.off("will-navigate", this._onTabNavigated); michael@0: }, michael@0: michael@0: /** michael@0: * Called for each location change in the monitored tab. michael@0: * michael@0: * @param string aType michael@0: * Packet type. michael@0: * @param object aPacket michael@0: * Packet received from the server. michael@0: */ michael@0: _onTabNavigated: function(aType, aPacket) { michael@0: switch (aType) { michael@0: case "will-navigate": { michael@0: // Reset UI. michael@0: if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) { michael@0: NetMonitorView.RequestsMenu.reset(); michael@0: NetMonitorView.Sidebar.toggle(false); michael@0: } michael@0: // Switch to the default network traffic inspector view. michael@0: if (NetMonitorController.getCurrentActivity() == ACTIVITY_TYPE.NONE) { michael@0: NetMonitorView.showNetworkInspectorView(); michael@0: } michael@0: michael@0: window.emit(EVENTS.TARGET_WILL_NAVIGATE); michael@0: break; michael@0: } michael@0: case "navigate": { michael@0: window.emit(EVENTS.TARGET_DID_NAVIGATE); michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Called when the monitored tab is closed. michael@0: */ michael@0: _onTabDetached: function() { michael@0: NetMonitorController.shutdownNetMonitor(); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Functions handling target network events. michael@0: */ michael@0: function NetworkEventsHandler() { michael@0: this._onNetworkEvent = this._onNetworkEvent.bind(this); michael@0: this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this); michael@0: this._onRequestHeaders = this._onRequestHeaders.bind(this); michael@0: this._onRequestCookies = this._onRequestCookies.bind(this); michael@0: this._onRequestPostData = this._onRequestPostData.bind(this); michael@0: this._onResponseHeaders = this._onResponseHeaders.bind(this); michael@0: this._onResponseCookies = this._onResponseCookies.bind(this); michael@0: this._onResponseContent = this._onResponseContent.bind(this); michael@0: this._onEventTimings = this._onEventTimings.bind(this); michael@0: } michael@0: michael@0: NetworkEventsHandler.prototype = { michael@0: get client() NetMonitorController._target.client, michael@0: get webConsoleClient() NetMonitorController.webConsoleClient, michael@0: michael@0: /** michael@0: * Connect to the current target client. michael@0: */ michael@0: connect: function() { michael@0: dumpn("NetworkEventsHandler is connecting..."); michael@0: this.client.addListener("networkEvent", this._onNetworkEvent); michael@0: this.client.addListener("networkEventUpdate", this._onNetworkEventUpdate); michael@0: }, michael@0: michael@0: /** michael@0: * Disconnect from the client. michael@0: */ michael@0: disconnect: function() { michael@0: if (!this.client) { michael@0: return; michael@0: } michael@0: dumpn("NetworkEventsHandler is disconnecting..."); michael@0: this.client.removeListener("networkEvent", this._onNetworkEvent); michael@0: this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate); michael@0: }, michael@0: michael@0: /** michael@0: * The "networkEvent" message type handler. michael@0: * michael@0: * @param string aType michael@0: * Message type. michael@0: * @param object aPacket michael@0: * The message received from the server. michael@0: */ michael@0: _onNetworkEvent: function(aType, aPacket) { michael@0: if (aPacket.from != this.webConsoleClient.actor) { michael@0: // Skip events from different console actors. michael@0: return; michael@0: } michael@0: michael@0: let { actor, startedDateTime, method, url, isXHR } = aPacket.eventActor; michael@0: NetMonitorView.RequestsMenu.addRequest(actor, startedDateTime, method, url, isXHR); michael@0: window.emit(EVENTS.NETWORK_EVENT); michael@0: }, michael@0: michael@0: /** michael@0: * The "networkEventUpdate" message type handler. michael@0: * michael@0: * @param string aType michael@0: * Message type. michael@0: * @param object aPacket michael@0: * The message received from the server. michael@0: */ michael@0: _onNetworkEventUpdate: function(aType, aPacket) { michael@0: let actor = aPacket.from; michael@0: if (!NetMonitorView.RequestsMenu.getItemByValue(actor)) { michael@0: // Skip events from unknown actors. michael@0: return; michael@0: } michael@0: michael@0: switch (aPacket.updateType) { michael@0: case "requestHeaders": michael@0: this.webConsoleClient.getRequestHeaders(actor, this._onRequestHeaders); michael@0: window.emit(EVENTS.UPDATING_REQUEST_HEADERS); michael@0: break; michael@0: case "requestCookies": michael@0: this.webConsoleClient.getRequestCookies(actor, this._onRequestCookies); michael@0: window.emit(EVENTS.UPDATING_REQUEST_COOKIES); michael@0: break; michael@0: case "requestPostData": michael@0: this.webConsoleClient.getRequestPostData(actor, this._onRequestPostData); michael@0: window.emit(EVENTS.UPDATING_REQUEST_POST_DATA); michael@0: break; michael@0: case "responseHeaders": michael@0: this.webConsoleClient.getResponseHeaders(actor, this._onResponseHeaders); michael@0: window.emit(EVENTS.UPDATING_RESPONSE_HEADERS); michael@0: break; michael@0: case "responseCookies": michael@0: this.webConsoleClient.getResponseCookies(actor, this._onResponseCookies); michael@0: window.emit(EVENTS.UPDATING_RESPONSE_COOKIES); michael@0: break; michael@0: case "responseStart": michael@0: NetMonitorView.RequestsMenu.updateRequest(aPacket.from, { michael@0: httpVersion: aPacket.response.httpVersion, michael@0: status: aPacket.response.status, michael@0: statusText: aPacket.response.statusText, michael@0: headersSize: aPacket.response.headersSize michael@0: }); michael@0: window.emit(EVENTS.STARTED_RECEIVING_RESPONSE); michael@0: break; michael@0: case "responseContent": michael@0: NetMonitorView.RequestsMenu.updateRequest(aPacket.from, { michael@0: contentSize: aPacket.contentSize, michael@0: mimeType: aPacket.mimeType michael@0: }); michael@0: this.webConsoleClient.getResponseContent(actor, this._onResponseContent); michael@0: window.emit(EVENTS.UPDATING_RESPONSE_CONTENT); michael@0: break; michael@0: case "eventTimings": michael@0: NetMonitorView.RequestsMenu.updateRequest(aPacket.from, { michael@0: totalTime: aPacket.totalTime michael@0: }); michael@0: this.webConsoleClient.getEventTimings(actor, this._onEventTimings); michael@0: window.emit(EVENTS.UPDATING_EVENT_TIMINGS); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Handles additional information received for a "requestHeaders" packet. michael@0: * michael@0: * @param object aResponse michael@0: * The message received from the server. michael@0: */ michael@0: _onRequestHeaders: function(aResponse) { michael@0: NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { michael@0: requestHeaders: aResponse michael@0: }); michael@0: window.emit(EVENTS.RECEIVED_REQUEST_HEADERS); michael@0: }, michael@0: michael@0: /** michael@0: * Handles additional information received for a "requestCookies" packet. michael@0: * michael@0: * @param object aResponse michael@0: * The message received from the server. michael@0: */ michael@0: _onRequestCookies: function(aResponse) { michael@0: NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { michael@0: requestCookies: aResponse michael@0: }); michael@0: window.emit(EVENTS.RECEIVED_REQUEST_COOKIES); michael@0: }, michael@0: michael@0: /** michael@0: * Handles additional information received for a "requestPostData" packet. michael@0: * michael@0: * @param object aResponse michael@0: * The message received from the server. michael@0: */ michael@0: _onRequestPostData: function(aResponse) { michael@0: NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { michael@0: requestPostData: aResponse michael@0: }); michael@0: window.emit(EVENTS.RECEIVED_REQUEST_POST_DATA); michael@0: }, michael@0: michael@0: /** michael@0: * Handles additional information received for a "responseHeaders" packet. michael@0: * michael@0: * @param object aResponse michael@0: * The message received from the server. michael@0: */ michael@0: _onResponseHeaders: function(aResponse) { michael@0: NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { michael@0: responseHeaders: aResponse michael@0: }); michael@0: window.emit(EVENTS.RECEIVED_RESPONSE_HEADERS); michael@0: }, michael@0: michael@0: /** michael@0: * Handles additional information received for a "responseCookies" packet. michael@0: * michael@0: * @param object aResponse michael@0: * The message received from the server. michael@0: */ michael@0: _onResponseCookies: function(aResponse) { michael@0: NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { michael@0: responseCookies: aResponse michael@0: }); michael@0: window.emit(EVENTS.RECEIVED_RESPONSE_COOKIES); michael@0: }, michael@0: michael@0: /** michael@0: * Handles additional information received for a "responseContent" packet. michael@0: * michael@0: * @param object aResponse michael@0: * The message received from the server. michael@0: */ michael@0: _onResponseContent: function(aResponse) { michael@0: NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { michael@0: responseContent: aResponse michael@0: }); michael@0: window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT); michael@0: }, michael@0: michael@0: /** michael@0: * Handles additional information received for a "eventTimings" packet. michael@0: * michael@0: * @param object aResponse michael@0: * The message received from the server. michael@0: */ michael@0: _onEventTimings: function(aResponse) { michael@0: NetMonitorView.RequestsMenu.updateRequest(aResponse.from, { michael@0: eventTimings: aResponse michael@0: }); michael@0: window.emit(EVENTS.RECEIVED_EVENT_TIMINGS); michael@0: }, michael@0: michael@0: /** michael@0: * Fetches the full text of a LongString. michael@0: * michael@0: * @param object | string aStringGrip michael@0: * The long string grip containing the corresponding actor. michael@0: * If you pass in a plain string (by accident or because you're lazy), michael@0: * then a promise of the same string is simply returned. michael@0: * @return object Promise michael@0: * A promise that is resolved when the full string contents michael@0: * are available, or rejected if something goes wrong. michael@0: */ michael@0: getString: function(aStringGrip) { michael@0: // Make sure this is a long string. michael@0: if (typeof aStringGrip != "object" || aStringGrip.type != "longString") { michael@0: return promise.resolve(aStringGrip); // Go home string, you're drunk. michael@0: } michael@0: // Fetch the long string only once. michael@0: if (aStringGrip._fullText) { michael@0: return aStringGrip._fullText.promise; michael@0: } michael@0: michael@0: let deferred = aStringGrip._fullText = promise.defer(); michael@0: let { actor, initial, length } = aStringGrip; michael@0: let longStringClient = this.webConsoleClient.longString(aStringGrip); michael@0: michael@0: longStringClient.substring(initial.length, length, aResponse => { michael@0: if (aResponse.error) { michael@0: Cu.reportError(aResponse.error + ": " + aResponse.message); michael@0: deferred.reject(aResponse); michael@0: return; michael@0: } michael@0: deferred.resolve(initial + aResponse.substring); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Localization convenience methods. michael@0: */ michael@0: let L10N = new ViewHelpers.L10N(NET_STRINGS_URI); michael@0: michael@0: /** michael@0: * Shortcuts for accessing various network monitor preferences. michael@0: */ michael@0: let Prefs = new ViewHelpers.Prefs("devtools.netmonitor", { michael@0: networkDetailsWidth: ["Int", "panes-network-details-width"], michael@0: networkDetailsHeight: ["Int", "panes-network-details-height"], michael@0: statistics: ["Bool", "statistics"], michael@0: filters: ["Json", "filters"] michael@0: }); michael@0: michael@0: /** michael@0: * Returns true if this is document is in RTL mode. michael@0: * @return boolean michael@0: */ michael@0: XPCOMUtils.defineLazyGetter(window, "isRTL", function() { michael@0: return window.getComputedStyle(document.documentElement, null).direction == "rtl"; michael@0: }); michael@0: michael@0: /** michael@0: * Convenient way of emitting events from the panel window. michael@0: */ michael@0: EventEmitter.decorate(this); michael@0: michael@0: /** michael@0: * Preliminary setup for the NetMonitorController object. michael@0: */ michael@0: NetMonitorController.TargetEventsHandler = new TargetEventsHandler(); michael@0: NetMonitorController.NetworkEventsHandler = new NetworkEventsHandler(); michael@0: michael@0: /** michael@0: * Export some properties to the global scope for easier access. michael@0: */ michael@0: Object.defineProperties(window, { michael@0: "gNetwork": { michael@0: get: function() NetMonitorController.NetworkEventsHandler michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Makes sure certain properties are available on all objects in a data store. michael@0: * michael@0: * @param array aDataStore michael@0: * A list of objects for which to check the availability of properties. michael@0: * @param array aMandatoryFields michael@0: * A list of strings representing properties of objects in aDataStore. michael@0: * @return object michael@0: * A promise resolved when all objects in aDataStore contain the michael@0: * properties defined in aMandatoryFields. michael@0: */ michael@0: function whenDataAvailable(aDataStore, aMandatoryFields) { michael@0: let deferred = promise.defer(); michael@0: michael@0: let interval = setInterval(() => { michael@0: if (aDataStore.every(item => aMandatoryFields.every(field => field in item))) { michael@0: clearInterval(interval); michael@0: clearTimeout(timer); michael@0: deferred.resolve(); michael@0: } michael@0: }, WDA_DEFAULT_VERIFY_INTERVAL); michael@0: michael@0: let timer = setTimeout(() => { michael@0: clearInterval(interval); michael@0: deferred.reject(new Error("Timed out while waiting for data")); michael@0: }, WDA_DEFAULT_GIVE_UP_TIMEOUT); michael@0: michael@0: return deferred.promise; michael@0: }; michael@0: michael@0: const WDA_DEFAULT_VERIFY_INTERVAL = 50; // ms michael@0: const WDA_DEFAULT_GIVE_UP_TIMEOUT = 2000; // ms michael@0: michael@0: /** michael@0: * Helper method for debugging. michael@0: * @param string michael@0: */ michael@0: function dumpn(str) { michael@0: if (wantLogging) { michael@0: dump("NET-FRONTEND: " + str + "\n"); michael@0: } michael@0: } michael@0: michael@0: let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");