1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/webconsole/network-monitor.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1501 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const {Cc, Ci, Cu} = require("chrome"); 1.11 + 1.12 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.13 + 1.14 +loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper")); 1.15 +loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm"); 1.16 +loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm"); 1.17 +loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); 1.18 +loader.lazyImporter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); 1.19 +loader.lazyServiceGetter(this, "gActivityDistributor", 1.20 + "@mozilla.org/network/http-activity-distributor;1", 1.21 + "nsIHttpActivityDistributor"); 1.22 + 1.23 +/////////////////////////////////////////////////////////////////////////////// 1.24 +// Network logging 1.25 +/////////////////////////////////////////////////////////////////////////////// 1.26 + 1.27 +// The maximum uint32 value. 1.28 +const PR_UINT32_MAX = 4294967295; 1.29 + 1.30 +// HTTP status codes. 1.31 +const HTTP_MOVED_PERMANENTLY = 301; 1.32 +const HTTP_FOUND = 302; 1.33 +const HTTP_SEE_OTHER = 303; 1.34 +const HTTP_TEMPORARY_REDIRECT = 307; 1.35 + 1.36 +// The maximum number of bytes a NetworkResponseListener can hold. 1.37 +const RESPONSE_BODY_LIMIT = 1048576; // 1 MB 1.38 + 1.39 +/** 1.40 + * The network response listener implements the nsIStreamListener and 1.41 + * nsIRequestObserver interfaces. This is used within the NetworkMonitor feature 1.42 + * to get the response body of the request. 1.43 + * 1.44 + * The code is mostly based on code listings from: 1.45 + * 1.46 + * http://www.softwareishard.com/blog/firebug/ 1.47 + * nsitraceablechannel-intercept-http-traffic/ 1.48 + * 1.49 + * @constructor 1.50 + * @param object aOwner 1.51 + * The response listener owner. This object needs to hold the 1.52 + * |openResponses| object. 1.53 + * @param object aHttpActivity 1.54 + * HttpActivity object associated with this request. See NetworkMonitor 1.55 + * for more information. 1.56 + */ 1.57 +function NetworkResponseListener(aOwner, aHttpActivity) 1.58 +{ 1.59 + this.owner = aOwner; 1.60 + this.receivedData = ""; 1.61 + this.httpActivity = aHttpActivity; 1.62 + this.bodySize = 0; 1.63 +} 1.64 +exports.NetworkResponseListener = NetworkResponseListener; 1.65 + 1.66 +NetworkResponseListener.prototype = { 1.67 + QueryInterface: 1.68 + XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback, 1.69 + Ci.nsIRequestObserver, Ci.nsISupports]), 1.70 + 1.71 + /** 1.72 + * This NetworkResponseListener tracks the NetworkMonitor.openResponses object 1.73 + * to find the associated uncached headers. 1.74 + * @private 1.75 + */ 1.76 + _foundOpenResponse: false, 1.77 + 1.78 + /** 1.79 + * The response listener owner. 1.80 + */ 1.81 + owner: null, 1.82 + 1.83 + /** 1.84 + * The response will be written into the outputStream of this nsIPipe. 1.85 + * Both ends of the pipe must be blocking. 1.86 + */ 1.87 + sink: null, 1.88 + 1.89 + /** 1.90 + * The HttpActivity object associated with this response. 1.91 + */ 1.92 + httpActivity: null, 1.93 + 1.94 + /** 1.95 + * Stores the received data as a string. 1.96 + */ 1.97 + receivedData: null, 1.98 + 1.99 + /** 1.100 + * The network response body size. 1.101 + */ 1.102 + bodySize: null, 1.103 + 1.104 + /** 1.105 + * The nsIRequest we are started for. 1.106 + */ 1.107 + request: null, 1.108 + 1.109 + /** 1.110 + * Set the async listener for the given nsIAsyncInputStream. This allows us to 1.111 + * wait asynchronously for any data coming from the stream. 1.112 + * 1.113 + * @param nsIAsyncInputStream aStream 1.114 + * The input stream from where we are waiting for data to come in. 1.115 + * @param nsIInputStreamCallback aListener 1.116 + * The input stream callback you want. This is an object that must have 1.117 + * the onInputStreamReady() method. If the argument is null, then the 1.118 + * current callback is removed. 1.119 + * @return void 1.120 + */ 1.121 + setAsyncListener: function NRL_setAsyncListener(aStream, aListener) 1.122 + { 1.123 + // Asynchronously wait for the stream to be readable or closed. 1.124 + aStream.asyncWait(aListener, 0, 0, Services.tm.mainThread); 1.125 + }, 1.126 + 1.127 + /** 1.128 + * Stores the received data, if request/response body logging is enabled. It 1.129 + * also does limit the number of stored bytes, based on the 1.130 + * RESPONSE_BODY_LIMIT constant. 1.131 + * 1.132 + * Learn more about nsIStreamListener at: 1.133 + * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIStreamListener 1.134 + * 1.135 + * @param nsIRequest aRequest 1.136 + * @param nsISupports aContext 1.137 + * @param nsIInputStream aInputStream 1.138 + * @param unsigned long aOffset 1.139 + * @param unsigned long aCount 1.140 + */ 1.141 + onDataAvailable: 1.142 + function NRL_onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount) 1.143 + { 1.144 + this._findOpenResponse(); 1.145 + let data = NetUtil.readInputStreamToString(aInputStream, aCount); 1.146 + 1.147 + this.bodySize += aCount; 1.148 + 1.149 + if (!this.httpActivity.discardResponseBody && 1.150 + this.receivedData.length < RESPONSE_BODY_LIMIT) { 1.151 + this.receivedData += NetworkHelper. 1.152 + convertToUnicode(data, aRequest.contentCharset); 1.153 + } 1.154 + }, 1.155 + 1.156 + /** 1.157 + * See documentation at 1.158 + * https://developer.mozilla.org/En/NsIRequestObserver 1.159 + * 1.160 + * @param nsIRequest aRequest 1.161 + * @param nsISupports aContext 1.162 + */ 1.163 + onStartRequest: function NRL_onStartRequest(aRequest) 1.164 + { 1.165 + this.request = aRequest; 1.166 + this._findOpenResponse(); 1.167 + // Asynchronously wait for the data coming from the request. 1.168 + this.setAsyncListener(this.sink.inputStream, this); 1.169 + }, 1.170 + 1.171 + /** 1.172 + * Handle the onStopRequest by closing the sink output stream. 1.173 + * 1.174 + * For more documentation about nsIRequestObserver go to: 1.175 + * https://developer.mozilla.org/En/NsIRequestObserver 1.176 + */ 1.177 + onStopRequest: function NRL_onStopRequest() 1.178 + { 1.179 + this._findOpenResponse(); 1.180 + this.sink.outputStream.close(); 1.181 + }, 1.182 + 1.183 + /** 1.184 + * Find the open response object associated to the current request. The 1.185 + * NetworkMonitor._httpResponseExaminer() method saves the response headers in 1.186 + * NetworkMonitor.openResponses. This method takes the data from the open 1.187 + * response object and puts it into the HTTP activity object, then sends it to 1.188 + * the remote Web Console instance. 1.189 + * 1.190 + * @private 1.191 + */ 1.192 + _findOpenResponse: function NRL__findOpenResponse() 1.193 + { 1.194 + if (!this.owner || this._foundOpenResponse) { 1.195 + return; 1.196 + } 1.197 + 1.198 + let openResponse = null; 1.199 + 1.200 + for each (let item in this.owner.openResponses) { 1.201 + if (item.channel === this.httpActivity.channel) { 1.202 + openResponse = item; 1.203 + break; 1.204 + } 1.205 + } 1.206 + 1.207 + if (!openResponse) { 1.208 + return; 1.209 + } 1.210 + this._foundOpenResponse = true; 1.211 + 1.212 + delete this.owner.openResponses[openResponse.id]; 1.213 + 1.214 + this.httpActivity.owner.addResponseHeaders(openResponse.headers); 1.215 + this.httpActivity.owner.addResponseCookies(openResponse.cookies); 1.216 + }, 1.217 + 1.218 + /** 1.219 + * Clean up the response listener once the response input stream is closed. 1.220 + * This is called from onStopRequest() or from onInputStreamReady() when the 1.221 + * stream is closed. 1.222 + * @return void 1.223 + */ 1.224 + onStreamClose: function NRL_onStreamClose() 1.225 + { 1.226 + if (!this.httpActivity) { 1.227 + return; 1.228 + } 1.229 + // Remove our listener from the request input stream. 1.230 + this.setAsyncListener(this.sink.inputStream, null); 1.231 + 1.232 + this._findOpenResponse(); 1.233 + 1.234 + if (!this.httpActivity.discardResponseBody && this.receivedData.length) { 1.235 + this._onComplete(this.receivedData); 1.236 + } 1.237 + else if (!this.httpActivity.discardResponseBody && 1.238 + this.httpActivity.responseStatus == 304) { 1.239 + // Response is cached, so we load it from cache. 1.240 + let charset = this.request.contentCharset || this.httpActivity.charset; 1.241 + NetworkHelper.loadFromCache(this.httpActivity.url, charset, 1.242 + this._onComplete.bind(this)); 1.243 + } 1.244 + else { 1.245 + this._onComplete(); 1.246 + } 1.247 + }, 1.248 + 1.249 + /** 1.250 + * Handler for when the response completes. This function cleans up the 1.251 + * response listener. 1.252 + * 1.253 + * @param string [aData] 1.254 + * Optional, the received data coming from the response listener or 1.255 + * from the cache. 1.256 + */ 1.257 + _onComplete: function NRL__onComplete(aData) 1.258 + { 1.259 + let response = { 1.260 + mimeType: "", 1.261 + text: aData || "", 1.262 + }; 1.263 + 1.264 + response.size = response.text.length; 1.265 + 1.266 + try { 1.267 + response.mimeType = this.request.contentType; 1.268 + } 1.269 + catch (ex) { } 1.270 + 1.271 + if (!response.mimeType || !NetworkHelper.isTextMimeType(response.mimeType)) { 1.272 + response.encoding = "base64"; 1.273 + response.text = btoa(response.text); 1.274 + } 1.275 + 1.276 + if (response.mimeType && this.request.contentCharset) { 1.277 + response.mimeType += "; charset=" + this.request.contentCharset; 1.278 + } 1.279 + 1.280 + this.receivedData = ""; 1.281 + 1.282 + this.httpActivity.owner. 1.283 + addResponseContent(response, this.httpActivity.discardResponseBody); 1.284 + 1.285 + this.httpActivity.channel = null; 1.286 + this.httpActivity.owner = null; 1.287 + this.httpActivity = null; 1.288 + this.sink = null; 1.289 + this.inputStream = null; 1.290 + this.request = null; 1.291 + this.owner = null; 1.292 + }, 1.293 + 1.294 + /** 1.295 + * The nsIInputStreamCallback for when the request input stream is ready - 1.296 + * either it has more data or it is closed. 1.297 + * 1.298 + * @param nsIAsyncInputStream aStream 1.299 + * The sink input stream from which data is coming. 1.300 + * @returns void 1.301 + */ 1.302 + onInputStreamReady: function NRL_onInputStreamReady(aStream) 1.303 + { 1.304 + if (!(aStream instanceof Ci.nsIAsyncInputStream) || !this.httpActivity) { 1.305 + return; 1.306 + } 1.307 + 1.308 + let available = -1; 1.309 + try { 1.310 + // This may throw if the stream is closed normally or due to an error. 1.311 + available = aStream.available(); 1.312 + } 1.313 + catch (ex) { } 1.314 + 1.315 + if (available != -1) { 1.316 + if (available != 0) { 1.317 + // Note that passing 0 as the offset here is wrong, but the 1.318 + // onDataAvailable() method does not use the offset, so it does not 1.319 + // matter. 1.320 + this.onDataAvailable(this.request, null, aStream, 0, available); 1.321 + } 1.322 + this.setAsyncListener(aStream, this); 1.323 + } 1.324 + else { 1.325 + this.onStreamClose(); 1.326 + } 1.327 + }, 1.328 +}; // NetworkResponseListener.prototype 1.329 + 1.330 + 1.331 +/** 1.332 + * The network monitor uses the nsIHttpActivityDistributor to monitor network 1.333 + * requests. The nsIObserverService is also used for monitoring 1.334 + * http-on-examine-response notifications. All network request information is 1.335 + * routed to the remote Web Console. 1.336 + * 1.337 + * @constructor 1.338 + * @param object aFilters 1.339 + * Object with the filters to use for network requests: 1.340 + * - window (nsIDOMWindow): filter network requests by the associated 1.341 + * window object. 1.342 + * - appId (number): filter requests by the appId. 1.343 + * - topFrame (nsIDOMElement): filter requests by their topFrameElement. 1.344 + * Filters are optional. If any of these filters match the request is 1.345 + * logged (OR is applied). If no filter is provided then all requests are 1.346 + * logged. 1.347 + * @param object aOwner 1.348 + * The network monitor owner. This object needs to hold: 1.349 + * - onNetworkEvent(aRequestInfo, aChannel, aNetworkMonitor). 1.350 + * This method is invoked once for every new network request and it is 1.351 + * given the following arguments: the initial network request 1.352 + * information, and the channel. The third argument is the NetworkMonitor 1.353 + * instance. 1.354 + * onNetworkEvent() must return an object which holds several add*() 1.355 + * methods which are used to add further network request/response 1.356 + * information. 1.357 + */ 1.358 +function NetworkMonitor(aFilters, aOwner) 1.359 +{ 1.360 + if (aFilters) { 1.361 + this.window = aFilters.window; 1.362 + this.appId = aFilters.appId; 1.363 + this.topFrame = aFilters.topFrame; 1.364 + } 1.365 + if (!this.window && !this.appId && !this.topFrame) { 1.366 + this._logEverything = true; 1.367 + } 1.368 + this.owner = aOwner; 1.369 + this.openRequests = {}; 1.370 + this.openResponses = {}; 1.371 + this._httpResponseExaminer = 1.372 + DevToolsUtils.makeInfallible(this._httpResponseExaminer).bind(this); 1.373 +} 1.374 +exports.NetworkMonitor = NetworkMonitor; 1.375 + 1.376 +NetworkMonitor.prototype = { 1.377 + _logEverything: false, 1.378 + window: null, 1.379 + appId: null, 1.380 + topFrame: null, 1.381 + 1.382 + httpTransactionCodes: { 1.383 + 0x5001: "REQUEST_HEADER", 1.384 + 0x5002: "REQUEST_BODY_SENT", 1.385 + 0x5003: "RESPONSE_START", 1.386 + 0x5004: "RESPONSE_HEADER", 1.387 + 0x5005: "RESPONSE_COMPLETE", 1.388 + 0x5006: "TRANSACTION_CLOSE", 1.389 + 1.390 + 0x804b0003: "STATUS_RESOLVING", 1.391 + 0x804b000b: "STATUS_RESOLVED", 1.392 + 0x804b0007: "STATUS_CONNECTING_TO", 1.393 + 0x804b0004: "STATUS_CONNECTED_TO", 1.394 + 0x804b0005: "STATUS_SENDING_TO", 1.395 + 0x804b000a: "STATUS_WAITING_FOR", 1.396 + 0x804b0006: "STATUS_RECEIVING_FROM" 1.397 + }, 1.398 + 1.399 + // Network response bodies are piped through a buffer of the given size (in 1.400 + // bytes). 1.401 + responsePipeSegmentSize: null, 1.402 + 1.403 + owner: null, 1.404 + 1.405 + /** 1.406 + * Whether to save the bodies of network requests and responses. Disabled by 1.407 + * default to save memory. 1.408 + * @type boolean 1.409 + */ 1.410 + saveRequestAndResponseBodies: false, 1.411 + 1.412 + /** 1.413 + * Object that holds the HTTP activity objects for ongoing requests. 1.414 + */ 1.415 + openRequests: null, 1.416 + 1.417 + /** 1.418 + * Object that holds response headers coming from this._httpResponseExaminer. 1.419 + */ 1.420 + openResponses: null, 1.421 + 1.422 + /** 1.423 + * The network monitor initializer. 1.424 + */ 1.425 + init: function NM_init() 1.426 + { 1.427 + this.responsePipeSegmentSize = Services.prefs 1.428 + .getIntPref("network.buffer.cache.size"); 1.429 + 1.430 + gActivityDistributor.addObserver(this); 1.431 + 1.432 + if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) { 1.433 + Services.obs.addObserver(this._httpResponseExaminer, 1.434 + "http-on-examine-response", false); 1.435 + } 1.436 + }, 1.437 + 1.438 + /** 1.439 + * Observe notifications for the http-on-examine-response topic, coming from 1.440 + * the nsIObserverService. 1.441 + * 1.442 + * @private 1.443 + * @param nsIHttpChannel aSubject 1.444 + * @param string aTopic 1.445 + * @returns void 1.446 + */ 1.447 + _httpResponseExaminer: function NM__httpResponseExaminer(aSubject, aTopic) 1.448 + { 1.449 + // The httpResponseExaminer is used to retrieve the uncached response 1.450 + // headers. The data retrieved is stored in openResponses. The 1.451 + // NetworkResponseListener is responsible with updating the httpActivity 1.452 + // object with the data from the new object in openResponses. 1.453 + 1.454 + if (!this.owner || aTopic != "http-on-examine-response" || 1.455 + !(aSubject instanceof Ci.nsIHttpChannel)) { 1.456 + return; 1.457 + } 1.458 + 1.459 + let channel = aSubject.QueryInterface(Ci.nsIHttpChannel); 1.460 + 1.461 + if (!this._matchRequest(channel)) { 1.462 + return; 1.463 + } 1.464 + 1.465 + let response = { 1.466 + id: gSequenceId(), 1.467 + channel: channel, 1.468 + headers: [], 1.469 + cookies: [], 1.470 + }; 1.471 + 1.472 + let setCookieHeader = null; 1.473 + 1.474 + channel.visitResponseHeaders({ 1.475 + visitHeader: function NM__visitHeader(aName, aValue) { 1.476 + let lowerName = aName.toLowerCase(); 1.477 + if (lowerName == "set-cookie") { 1.478 + setCookieHeader = aValue; 1.479 + } 1.480 + response.headers.push({ name: aName, value: aValue }); 1.481 + } 1.482 + }); 1.483 + 1.484 + if (!response.headers.length) { 1.485 + return; // No need to continue. 1.486 + } 1.487 + 1.488 + if (setCookieHeader) { 1.489 + response.cookies = NetworkHelper.parseSetCookieHeader(setCookieHeader); 1.490 + } 1.491 + 1.492 + // Determine the HTTP version. 1.493 + let httpVersionMaj = {}; 1.494 + let httpVersionMin = {}; 1.495 + 1.496 + channel.QueryInterface(Ci.nsIHttpChannelInternal); 1.497 + channel.getResponseVersion(httpVersionMaj, httpVersionMin); 1.498 + 1.499 + response.status = channel.responseStatus; 1.500 + response.statusText = channel.responseStatusText; 1.501 + response.httpVersion = "HTTP/" + httpVersionMaj.value + "." + 1.502 + httpVersionMin.value; 1.503 + 1.504 + this.openResponses[response.id] = response; 1.505 + }, 1.506 + 1.507 + /** 1.508 + * Begin observing HTTP traffic that originates inside the current tab. 1.509 + * 1.510 + * @see https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIHttpActivityObserver 1.511 + * 1.512 + * @param nsIHttpChannel aChannel 1.513 + * @param number aActivityType 1.514 + * @param number aActivitySubtype 1.515 + * @param number aTimestamp 1.516 + * @param number aExtraSizeData 1.517 + * @param string aExtraStringData 1.518 + */ 1.519 + observeActivity: DevToolsUtils.makeInfallible(function NM_observeActivity(aChannel, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData, aExtraStringData) 1.520 + { 1.521 + if (!this.owner || 1.522 + aActivityType != gActivityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION && 1.523 + aActivityType != gActivityDistributor.ACTIVITY_TYPE_SOCKET_TRANSPORT) { 1.524 + return; 1.525 + } 1.526 + 1.527 + if (!(aChannel instanceof Ci.nsIHttpChannel)) { 1.528 + return; 1.529 + } 1.530 + 1.531 + aChannel = aChannel.QueryInterface(Ci.nsIHttpChannel); 1.532 + 1.533 + if (aActivitySubtype == 1.534 + gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER) { 1.535 + this._onRequestHeader(aChannel, aTimestamp, aExtraStringData); 1.536 + return; 1.537 + } 1.538 + 1.539 + // Iterate over all currently ongoing requests. If aChannel can't 1.540 + // be found within them, then exit this function. 1.541 + let httpActivity = null; 1.542 + for each (let item in this.openRequests) { 1.543 + if (item.channel === aChannel) { 1.544 + httpActivity = item; 1.545 + break; 1.546 + } 1.547 + } 1.548 + 1.549 + if (!httpActivity) { 1.550 + return; 1.551 + } 1.552 + 1.553 + let transCodes = this.httpTransactionCodes; 1.554 + 1.555 + // Store the time information for this activity subtype. 1.556 + if (aActivitySubtype in transCodes) { 1.557 + let stage = transCodes[aActivitySubtype]; 1.558 + if (stage in httpActivity.timings) { 1.559 + httpActivity.timings[stage].last = aTimestamp; 1.560 + } 1.561 + else { 1.562 + httpActivity.timings[stage] = { 1.563 + first: aTimestamp, 1.564 + last: aTimestamp, 1.565 + }; 1.566 + } 1.567 + } 1.568 + 1.569 + switch (aActivitySubtype) { 1.570 + case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT: 1.571 + this._onRequestBodySent(httpActivity); 1.572 + break; 1.573 + case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER: 1.574 + this._onResponseHeader(httpActivity, aExtraStringData); 1.575 + break; 1.576 + case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE: 1.577 + this._onTransactionClose(httpActivity); 1.578 + break; 1.579 + default: 1.580 + break; 1.581 + } 1.582 + }), 1.583 + 1.584 + /** 1.585 + * Check if a given network request should be logged by this network monitor 1.586 + * instance based on the current filters. 1.587 + * 1.588 + * @private 1.589 + * @param nsIHttpChannel aChannel 1.590 + * Request to check. 1.591 + * @return boolean 1.592 + * True if the network request should be logged, false otherwise. 1.593 + */ 1.594 + _matchRequest: function NM__matchRequest(aChannel) 1.595 + { 1.596 + if (this._logEverything) { 1.597 + return true; 1.598 + } 1.599 + 1.600 + if (this.window) { 1.601 + let win = NetworkHelper.getWindowForRequest(aChannel); 1.602 + if (win && win.top === this.window) { 1.603 + return true; 1.604 + } 1.605 + } 1.606 + 1.607 + if (this.topFrame) { 1.608 + let topFrame = NetworkHelper.getTopFrameForRequest(aChannel); 1.609 + if (topFrame && topFrame === this.topFrame) { 1.610 + return true; 1.611 + } 1.612 + } 1.613 + 1.614 + if (this.appId) { 1.615 + let appId = NetworkHelper.getAppIdForRequest(aChannel); 1.616 + if (appId && appId == this.appId) { 1.617 + return true; 1.618 + } 1.619 + } 1.620 + 1.621 + return false; 1.622 + }, 1.623 + 1.624 + /** 1.625 + * Handler for ACTIVITY_SUBTYPE_REQUEST_HEADER. When a request starts the 1.626 + * headers are sent to the server. This method creates the |httpActivity| 1.627 + * object where we store the request and response information that is 1.628 + * collected through its lifetime. 1.629 + * 1.630 + * @private 1.631 + * @param nsIHttpChannel aChannel 1.632 + * @param number aTimestamp 1.633 + * @param string aExtraStringData 1.634 + * @return void 1.635 + */ 1.636 + _onRequestHeader: 1.637 + function NM__onRequestHeader(aChannel, aTimestamp, aExtraStringData) 1.638 + { 1.639 + if (!this._matchRequest(aChannel)) { 1.640 + return; 1.641 + } 1.642 + 1.643 + let win = NetworkHelper.getWindowForRequest(aChannel); 1.644 + let httpActivity = this.createActivityObject(aChannel); 1.645 + 1.646 + // see NM__onRequestBodySent() 1.647 + httpActivity.charset = win ? win.document.characterSet : null; 1.648 + httpActivity.private = win ? PrivateBrowsingUtils.isWindowPrivate(win) : false; 1.649 + 1.650 + httpActivity.timings.REQUEST_HEADER = { 1.651 + first: aTimestamp, 1.652 + last: aTimestamp 1.653 + }; 1.654 + 1.655 + let httpVersionMaj = {}; 1.656 + let httpVersionMin = {}; 1.657 + let event = {}; 1.658 + event.startedDateTime = new Date(Math.round(aTimestamp / 1000)).toISOString(); 1.659 + event.headersSize = aExtraStringData.length; 1.660 + event.method = aChannel.requestMethod; 1.661 + event.url = aChannel.URI.spec; 1.662 + event.private = httpActivity.private; 1.663 + 1.664 + // Determine if this is an XHR request. 1.665 + try { 1.666 + let callbacks = aChannel.notificationCallbacks; 1.667 + let xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null; 1.668 + httpActivity.isXHR = event.isXHR = !!xhrRequest; 1.669 + } catch (e) { 1.670 + httpActivity.isXHR = event.isXHR = false; 1.671 + } 1.672 + 1.673 + // Determine the HTTP version. 1.674 + aChannel.QueryInterface(Ci.nsIHttpChannelInternal); 1.675 + aChannel.getRequestVersion(httpVersionMaj, httpVersionMin); 1.676 + 1.677 + event.httpVersion = "HTTP/" + httpVersionMaj.value + "." + 1.678 + httpVersionMin.value; 1.679 + 1.680 + event.discardRequestBody = !this.saveRequestAndResponseBodies; 1.681 + event.discardResponseBody = !this.saveRequestAndResponseBodies; 1.682 + 1.683 + let headers = []; 1.684 + let cookies = []; 1.685 + let cookieHeader = null; 1.686 + 1.687 + // Copy the request header data. 1.688 + aChannel.visitRequestHeaders({ 1.689 + visitHeader: function NM__visitHeader(aName, aValue) 1.690 + { 1.691 + if (aName == "Cookie") { 1.692 + cookieHeader = aValue; 1.693 + } 1.694 + headers.push({ name: aName, value: aValue }); 1.695 + } 1.696 + }); 1.697 + 1.698 + if (cookieHeader) { 1.699 + cookies = NetworkHelper.parseCookieHeader(cookieHeader); 1.700 + } 1.701 + 1.702 + httpActivity.owner = this.owner.onNetworkEvent(event, aChannel, this); 1.703 + 1.704 + this._setupResponseListener(httpActivity); 1.705 + 1.706 + this.openRequests[httpActivity.id] = httpActivity; 1.707 + 1.708 + httpActivity.owner.addRequestHeaders(headers); 1.709 + httpActivity.owner.addRequestCookies(cookies); 1.710 + }, 1.711 + 1.712 + /** 1.713 + * Create the empty HTTP activity object. This object is used for storing all 1.714 + * the request and response information. 1.715 + * 1.716 + * This is a HAR-like object. Conformance to the spec is not guaranteed at 1.717 + * this point. 1.718 + * 1.719 + * TODO: Bug 708717 - Add support for network log export to HAR 1.720 + * 1.721 + * @see http://www.softwareishard.com/blog/har-12-spec 1.722 + * @param nsIHttpChannel aChannel 1.723 + * The HTTP channel for which the HTTP activity object is created. 1.724 + * @return object 1.725 + * The new HTTP activity object. 1.726 + */ 1.727 + createActivityObject: function NM_createActivityObject(aChannel) 1.728 + { 1.729 + return { 1.730 + id: gSequenceId(), 1.731 + channel: aChannel, 1.732 + charset: null, // see NM__onRequestHeader() 1.733 + url: aChannel.URI.spec, 1.734 + discardRequestBody: !this.saveRequestAndResponseBodies, 1.735 + discardResponseBody: !this.saveRequestAndResponseBodies, 1.736 + timings: {}, // internal timing information, see NM_observeActivity() 1.737 + responseStatus: null, // see NM__onResponseHeader() 1.738 + owner: null, // the activity owner which is notified when changes happen 1.739 + }; 1.740 + }, 1.741 + 1.742 + /** 1.743 + * Setup the network response listener for the given HTTP activity. The 1.744 + * NetworkResponseListener is responsible for storing the response body. 1.745 + * 1.746 + * @private 1.747 + * @param object aHttpActivity 1.748 + * The HTTP activity object we are tracking. 1.749 + */ 1.750 + _setupResponseListener: function NM__setupResponseListener(aHttpActivity) 1.751 + { 1.752 + let channel = aHttpActivity.channel; 1.753 + channel.QueryInterface(Ci.nsITraceableChannel); 1.754 + 1.755 + // The response will be written into the outputStream of this pipe. 1.756 + // This allows us to buffer the data we are receiving and read it 1.757 + // asynchronously. 1.758 + // Both ends of the pipe must be blocking. 1.759 + let sink = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); 1.760 + 1.761 + // The streams need to be blocking because this is required by the 1.762 + // stream tee. 1.763 + sink.init(false, false, this.responsePipeSegmentSize, PR_UINT32_MAX, null); 1.764 + 1.765 + // Add listener for the response body. 1.766 + let newListener = new NetworkResponseListener(this, aHttpActivity); 1.767 + 1.768 + // Remember the input stream, so it isn't released by GC. 1.769 + newListener.inputStream = sink.inputStream; 1.770 + newListener.sink = sink; 1.771 + 1.772 + let tee = Cc["@mozilla.org/network/stream-listener-tee;1"]. 1.773 + createInstance(Ci.nsIStreamListenerTee); 1.774 + 1.775 + let originalListener = channel.setNewListener(tee); 1.776 + 1.777 + tee.init(originalListener, sink.outputStream, newListener); 1.778 + }, 1.779 + 1.780 + /** 1.781 + * Handler for ACTIVITY_SUBTYPE_REQUEST_BODY_SENT. The request body is logged 1.782 + * here. 1.783 + * 1.784 + * @private 1.785 + * @param object aHttpActivity 1.786 + * The HTTP activity object we are working with. 1.787 + */ 1.788 + _onRequestBodySent: function NM__onRequestBodySent(aHttpActivity) 1.789 + { 1.790 + if (aHttpActivity.discardRequestBody) { 1.791 + return; 1.792 + } 1.793 + 1.794 + let sentBody = NetworkHelper. 1.795 + readPostTextFromRequest(aHttpActivity.channel, 1.796 + aHttpActivity.charset); 1.797 + 1.798 + if (!sentBody && this.window && 1.799 + aHttpActivity.url == this.window.location.href) { 1.800 + // If the request URL is the same as the current page URL, then 1.801 + // we can try to get the posted text from the page directly. 1.802 + // This check is necessary as otherwise the 1.803 + // NetworkHelper.readPostTextFromPageViaWebNav() 1.804 + // function is called for image requests as well but these 1.805 + // are not web pages and as such don't store the posted text 1.806 + // in the cache of the webpage. 1.807 + let webNav = this.window.QueryInterface(Ci.nsIInterfaceRequestor). 1.808 + getInterface(Ci.nsIWebNavigation); 1.809 + sentBody = NetworkHelper. 1.810 + readPostTextFromPageViaWebNav(webNav, aHttpActivity.charset); 1.811 + } 1.812 + 1.813 + if (sentBody) { 1.814 + aHttpActivity.owner.addRequestPostData({ text: sentBody }); 1.815 + } 1.816 + }, 1.817 + 1.818 + /** 1.819 + * Handler for ACTIVITY_SUBTYPE_RESPONSE_HEADER. This method stores 1.820 + * information about the response headers. 1.821 + * 1.822 + * @private 1.823 + * @param object aHttpActivity 1.824 + * The HTTP activity object we are working with. 1.825 + * @param string aExtraStringData 1.826 + * The uncached response headers. 1.827 + */ 1.828 + _onResponseHeader: 1.829 + function NM__onResponseHeader(aHttpActivity, aExtraStringData) 1.830 + { 1.831 + // aExtraStringData contains the uncached response headers. The first line 1.832 + // contains the response status (e.g. HTTP/1.1 200 OK). 1.833 + // 1.834 + // Note: The response header is not saved here. Calling the 1.835 + // channel.visitResponseHeaders() methood at this point sometimes causes an 1.836 + // NS_ERROR_NOT_AVAILABLE exception. 1.837 + // 1.838 + // We could parse aExtraStringData to get the headers and their values, but 1.839 + // that is not trivial to do in an accurate manner. Hence, we save the 1.840 + // response headers in this._httpResponseExaminer(). 1.841 + 1.842 + let headers = aExtraStringData.split(/\r\n|\n|\r/); 1.843 + let statusLine = headers.shift(); 1.844 + let statusLineArray = statusLine.split(" "); 1.845 + 1.846 + let response = {}; 1.847 + response.httpVersion = statusLineArray.shift(); 1.848 + response.status = statusLineArray.shift(); 1.849 + response.statusText = statusLineArray.join(" "); 1.850 + response.headersSize = aExtraStringData.length; 1.851 + 1.852 + aHttpActivity.responseStatus = response.status; 1.853 + 1.854 + // Discard the response body for known response statuses. 1.855 + switch (parseInt(response.status)) { 1.856 + case HTTP_MOVED_PERMANENTLY: 1.857 + case HTTP_FOUND: 1.858 + case HTTP_SEE_OTHER: 1.859 + case HTTP_TEMPORARY_REDIRECT: 1.860 + aHttpActivity.discardResponseBody = true; 1.861 + break; 1.862 + } 1.863 + 1.864 + response.discardResponseBody = aHttpActivity.discardResponseBody; 1.865 + 1.866 + aHttpActivity.owner.addResponseStart(response); 1.867 + }, 1.868 + 1.869 + /** 1.870 + * Handler for ACTIVITY_SUBTYPE_TRANSACTION_CLOSE. This method updates the HAR 1.871 + * timing information on the HTTP activity object and clears the request 1.872 + * from the list of known open requests. 1.873 + * 1.874 + * @private 1.875 + * @param object aHttpActivity 1.876 + * The HTTP activity object we work with. 1.877 + */ 1.878 + _onTransactionClose: function NM__onTransactionClose(aHttpActivity) 1.879 + { 1.880 + let result = this._setupHarTimings(aHttpActivity); 1.881 + aHttpActivity.owner.addEventTimings(result.total, result.timings); 1.882 + delete this.openRequests[aHttpActivity.id]; 1.883 + }, 1.884 + 1.885 + /** 1.886 + * Update the HTTP activity object to include timing information as in the HAR 1.887 + * spec. The HTTP activity object holds the raw timing information in 1.888 + * |timings| - these are timings stored for each activity notification. The 1.889 + * HAR timing information is constructed based on these lower level data. 1.890 + * 1.891 + * @param object aHttpActivity 1.892 + * The HTTP activity object we are working with. 1.893 + * @return object 1.894 + * This object holds two properties: 1.895 + * - total - the total time for all of the request and response. 1.896 + * - timings - the HAR timings object. 1.897 + */ 1.898 + _setupHarTimings: function NM__setupHarTimings(aHttpActivity) 1.899 + { 1.900 + let timings = aHttpActivity.timings; 1.901 + let harTimings = {}; 1.902 + 1.903 + // Not clear how we can determine "blocked" time. 1.904 + harTimings.blocked = -1; 1.905 + 1.906 + // DNS timing information is available only in when the DNS record is not 1.907 + // cached. 1.908 + harTimings.dns = timings.STATUS_RESOLVING && timings.STATUS_RESOLVED ? 1.909 + timings.STATUS_RESOLVED.last - 1.910 + timings.STATUS_RESOLVING.first : -1; 1.911 + 1.912 + if (timings.STATUS_CONNECTING_TO && timings.STATUS_CONNECTED_TO) { 1.913 + harTimings.connect = timings.STATUS_CONNECTED_TO.last - 1.914 + timings.STATUS_CONNECTING_TO.first; 1.915 + } 1.916 + else if (timings.STATUS_SENDING_TO) { 1.917 + harTimings.connect = timings.STATUS_SENDING_TO.first - 1.918 + timings.REQUEST_HEADER.first; 1.919 + } 1.920 + else { 1.921 + harTimings.connect = -1; 1.922 + } 1.923 + 1.924 + if ((timings.STATUS_WAITING_FOR || timings.STATUS_RECEIVING_FROM) && 1.925 + (timings.STATUS_CONNECTED_TO || timings.STATUS_SENDING_TO)) { 1.926 + harTimings.send = (timings.STATUS_WAITING_FOR || 1.927 + timings.STATUS_RECEIVING_FROM).first - 1.928 + (timings.STATUS_CONNECTED_TO || 1.929 + timings.STATUS_SENDING_TO).last; 1.930 + } 1.931 + else { 1.932 + harTimings.send = -1; 1.933 + } 1.934 + 1.935 + if (timings.RESPONSE_START) { 1.936 + harTimings.wait = timings.RESPONSE_START.first - 1.937 + (timings.REQUEST_BODY_SENT || 1.938 + timings.STATUS_SENDING_TO).last; 1.939 + } 1.940 + else { 1.941 + harTimings.wait = -1; 1.942 + } 1.943 + 1.944 + if (timings.RESPONSE_START && timings.RESPONSE_COMPLETE) { 1.945 + harTimings.receive = timings.RESPONSE_COMPLETE.last - 1.946 + timings.RESPONSE_START.first; 1.947 + } 1.948 + else { 1.949 + harTimings.receive = -1; 1.950 + } 1.951 + 1.952 + let totalTime = 0; 1.953 + for (let timing in harTimings) { 1.954 + let time = Math.max(Math.round(harTimings[timing] / 1000), -1); 1.955 + harTimings[timing] = time; 1.956 + if (time > -1) { 1.957 + totalTime += time; 1.958 + } 1.959 + } 1.960 + 1.961 + return { 1.962 + total: totalTime, 1.963 + timings: harTimings, 1.964 + }; 1.965 + }, 1.966 + 1.967 + /** 1.968 + * Suspend Web Console activity. This is called when all Web Consoles are 1.969 + * closed. 1.970 + */ 1.971 + destroy: function NM_destroy() 1.972 + { 1.973 + if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) { 1.974 + Services.obs.removeObserver(this._httpResponseExaminer, 1.975 + "http-on-examine-response"); 1.976 + } 1.977 + 1.978 + gActivityDistributor.removeObserver(this); 1.979 + 1.980 + this.openRequests = {}; 1.981 + this.openResponses = {}; 1.982 + this.owner = null; 1.983 + this.window = null; 1.984 + this.topFrame = null; 1.985 + }, 1.986 +}; // NetworkMonitor.prototype 1.987 + 1.988 + 1.989 +/** 1.990 + * The NetworkMonitorChild is used to proxy all of the network activity of the 1.991 + * child app process from the main process. The child WebConsoleActor creates an 1.992 + * instance of this object. 1.993 + * 1.994 + * Network requests for apps happen in the main process. As such, 1.995 + * a NetworkMonitor instance is used by the WebappsActor in the main process to 1.996 + * log the network requests for this child process. 1.997 + * 1.998 + * The main process creates NetworkEventActorProxy instances per request. These 1.999 + * send the data to this object using the nsIMessageManager. Here we proxy the 1.1000 + * data to the WebConsoleActor or to a NetworkEventActor. 1.1001 + * 1.1002 + * @constructor 1.1003 + * @param number appId 1.1004 + * The web appId of the child process. 1.1005 + * @param nsIMessageManager messageManager 1.1006 + * The nsIMessageManager to use to communicate with the parent process. 1.1007 + * @param string connID 1.1008 + * The connection ID to use for send messages to the parent process. 1.1009 + * @param object owner 1.1010 + * The WebConsoleActor that is listening for the network requests. 1.1011 + */ 1.1012 +function NetworkMonitorChild(appId, messageManager, connID, owner) { 1.1013 + this.appId = appId; 1.1014 + this.connID = connID; 1.1015 + this.owner = owner; 1.1016 + this._messageManager = messageManager; 1.1017 + this._onNewEvent = this._onNewEvent.bind(this); 1.1018 + this._onUpdateEvent = this._onUpdateEvent.bind(this); 1.1019 + this._netEvents = new Map(); 1.1020 +} 1.1021 +exports.NetworkMonitorChild = NetworkMonitorChild; 1.1022 + 1.1023 +NetworkMonitorChild.prototype = { 1.1024 + appId: null, 1.1025 + owner: null, 1.1026 + _netEvents: null, 1.1027 + _saveRequestAndResponseBodies: false, 1.1028 + 1.1029 + get saveRequestAndResponseBodies() { 1.1030 + return this._saveRequestAndResponseBodies; 1.1031 + }, 1.1032 + 1.1033 + set saveRequestAndResponseBodies(val) { 1.1034 + this._saveRequestAndResponseBodies = val; 1.1035 + 1.1036 + this._messageManager.sendAsyncMessage("debug:netmonitor:" + this.connID, { 1.1037 + appId: this.appId, 1.1038 + action: "setPreferences", 1.1039 + preferences: { 1.1040 + saveRequestAndResponseBodies: this._saveRequestAndResponseBodies, 1.1041 + }, 1.1042 + }); 1.1043 + }, 1.1044 + 1.1045 + init: function() { 1.1046 + let mm = this._messageManager; 1.1047 + mm.addMessageListener("debug:netmonitor:" + this.connID + ":newEvent", 1.1048 + this._onNewEvent); 1.1049 + mm.addMessageListener("debug:netmonitor:" + this.connID + ":updateEvent", 1.1050 + this._onUpdateEvent); 1.1051 + mm.sendAsyncMessage("debug:netmonitor:" + this.connID, { 1.1052 + appId: this.appId, 1.1053 + action: "start", 1.1054 + }); 1.1055 + }, 1.1056 + 1.1057 + _onNewEvent: DevToolsUtils.makeInfallible(function _onNewEvent(msg) { 1.1058 + let {id, event} = msg.data; 1.1059 + let actor = this.owner.onNetworkEvent(event); 1.1060 + this._netEvents.set(id, Cu.getWeakReference(actor)); 1.1061 + }), 1.1062 + 1.1063 + _onUpdateEvent: DevToolsUtils.makeInfallible(function _onUpdateEvent(msg) { 1.1064 + let {id, method, args} = msg.data; 1.1065 + let weakActor = this._netEvents.get(id); 1.1066 + let actor = weakActor ? weakActor.get() : null; 1.1067 + if (!actor) { 1.1068 + Cu.reportError("Received debug:netmonitor:updateEvent for unknown event ID: " + id); 1.1069 + return; 1.1070 + } 1.1071 + if (!(method in actor)) { 1.1072 + Cu.reportError("Received debug:netmonitor:updateEvent unsupported method: " + method); 1.1073 + return; 1.1074 + } 1.1075 + actor[method].apply(actor, args); 1.1076 + }), 1.1077 + 1.1078 + destroy: function() { 1.1079 + let mm = this._messageManager; 1.1080 + mm.removeMessageListener("debug:netmonitor:" + this.connID + ":newEvent", 1.1081 + this._onNewEvent); 1.1082 + mm.removeMessageListener("debug:netmonitor:" + this.connID + ":updateEvent", 1.1083 + this._onUpdateEvent); 1.1084 + mm.sendAsyncMessage("debug:netmonitor:" + this.connID, { 1.1085 + action: "disconnect", 1.1086 + }); 1.1087 + this._netEvents.clear(); 1.1088 + this._messageManager = null; 1.1089 + this.owner = null; 1.1090 + }, 1.1091 +}; // NetworkMonitorChild.prototype 1.1092 + 1.1093 +/** 1.1094 + * The NetworkEventActorProxy is used to send network request information from 1.1095 + * the main process to the child app process. One proxy is used per request. 1.1096 + * Similarly, one NetworkEventActor in the child app process is used per 1.1097 + * request. The client receives all network logs from the child actors. 1.1098 + * 1.1099 + * The child process has a NetworkMonitorChild instance that is listening for 1.1100 + * all network logging from the main process. The net monitor shim is used to 1.1101 + * proxy the data to the WebConsoleActor instance of the child process. 1.1102 + * 1.1103 + * @constructor 1.1104 + * @param nsIMessageManager messageManager 1.1105 + * The message manager for the child app process. This is used for 1.1106 + * communication with the NetworkMonitorChild instance of the process. 1.1107 + * @param string connID 1.1108 + * The connection ID to use to send messages to the child process. 1.1109 + */ 1.1110 +function NetworkEventActorProxy(messageManager, connID) { 1.1111 + this.id = gSequenceId(); 1.1112 + this.connID = connID; 1.1113 + this.messageManager = messageManager; 1.1114 +} 1.1115 +exports.NetworkEventActorProxy = NetworkEventActorProxy; 1.1116 + 1.1117 +NetworkEventActorProxy.methodFactory = function(method) { 1.1118 + return DevToolsUtils.makeInfallible(function() { 1.1119 + let args = Array.slice(arguments); 1.1120 + let mm = this.messageManager; 1.1121 + mm.sendAsyncMessage("debug:netmonitor:" + this.connID + ":updateEvent", { 1.1122 + id: this.id, 1.1123 + method: method, 1.1124 + args: args, 1.1125 + }); 1.1126 + }, "NetworkEventActorProxy." + method); 1.1127 +}; 1.1128 + 1.1129 +NetworkEventActorProxy.prototype = { 1.1130 + /** 1.1131 + * Initialize the network event. This method sends the network request event 1.1132 + * to the content process. 1.1133 + * 1.1134 + * @param object event 1.1135 + * Object describing the network request. 1.1136 + * @return object 1.1137 + * This object. 1.1138 + */ 1.1139 + init: DevToolsUtils.makeInfallible(function(event) 1.1140 + { 1.1141 + let mm = this.messageManager; 1.1142 + mm.sendAsyncMessage("debug:netmonitor:" + this.connID + ":newEvent", { 1.1143 + id: this.id, 1.1144 + event: event, 1.1145 + }); 1.1146 + return this; 1.1147 + }), 1.1148 +}; 1.1149 + 1.1150 +(function() { 1.1151 + // Listeners for new network event data coming from the NetworkMonitor. 1.1152 + let methods = ["addRequestHeaders", "addRequestCookies", "addRequestPostData", 1.1153 + "addResponseStart", "addResponseHeaders", "addResponseCookies", 1.1154 + "addResponseContent", "addEventTimings"]; 1.1155 + let factory = NetworkEventActorProxy.methodFactory; 1.1156 + for (let method of methods) { 1.1157 + NetworkEventActorProxy.prototype[method] = factory(method); 1.1158 + } 1.1159 +})(); 1.1160 + 1.1161 + 1.1162 +/** 1.1163 + * The NetworkMonitor manager used by the Webapps actor in the main process. 1.1164 + * This object uses the message manager to listen for requests from the child 1.1165 + * process to start/stop the network monitor. 1.1166 + * 1.1167 + * @constructor 1.1168 + * @param nsIDOMElement frame 1.1169 + * The browser frame to work with (mozbrowser). 1.1170 + * @param string id 1.1171 + * Instance identifier to use for messages. 1.1172 + */ 1.1173 +function NetworkMonitorManager(frame, id) 1.1174 +{ 1.1175 + this.id = id; 1.1176 + let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; 1.1177 + this.messageManager = mm; 1.1178 + this.frame = frame; 1.1179 + this.onNetMonitorMessage = this.onNetMonitorMessage.bind(this); 1.1180 + this.onNetworkEvent = this.onNetworkEvent.bind(this); 1.1181 + 1.1182 + mm.addMessageListener("debug:netmonitor:" + id, this.onNetMonitorMessage); 1.1183 +} 1.1184 +exports.NetworkMonitorManager = NetworkMonitorManager; 1.1185 + 1.1186 +NetworkMonitorManager.prototype = { 1.1187 + netMonitor: null, 1.1188 + frame: null, 1.1189 + messageManager: null, 1.1190 + 1.1191 + /** 1.1192 + * Handler for "debug:monitor" messages received through the message manager 1.1193 + * from the content process. 1.1194 + * 1.1195 + * @param object msg 1.1196 + * Message from the content. 1.1197 + */ 1.1198 + onNetMonitorMessage: DevToolsUtils.makeInfallible(function _onNetMonitorMessage(msg) { 1.1199 + let { action, appId } = msg.json; 1.1200 + // Pipe network monitor data from parent to child via the message manager. 1.1201 + switch (action) { 1.1202 + case "start": 1.1203 + if (!this.netMonitor) { 1.1204 + this.netMonitor = new NetworkMonitor({ 1.1205 + topFrame: this.frame, 1.1206 + appId: appId, 1.1207 + }, this); 1.1208 + this.netMonitor.init(); 1.1209 + } 1.1210 + break; 1.1211 + 1.1212 + case "setPreferences": { 1.1213 + let {preferences} = msg.json; 1.1214 + for (let key of Object.keys(preferences)) { 1.1215 + if (key == "saveRequestAndResponseBodies" && this.netMonitor) { 1.1216 + this.netMonitor.saveRequestAndResponseBodies = preferences[key]; 1.1217 + } 1.1218 + } 1.1219 + break; 1.1220 + } 1.1221 + 1.1222 + case "stop": 1.1223 + if (this.netMonitor) { 1.1224 + this.netMonitor.destroy(); 1.1225 + this.netMonitor = null; 1.1226 + } 1.1227 + break; 1.1228 + 1.1229 + case "disconnect": 1.1230 + this.destroy(); 1.1231 + break; 1.1232 + } 1.1233 + }), 1.1234 + 1.1235 + /** 1.1236 + * Handler for new network requests. This method is invoked by the current 1.1237 + * NetworkMonitor instance. 1.1238 + * 1.1239 + * @param object event 1.1240 + * Object describing the network request. 1.1241 + * @return object 1.1242 + * A NetworkEventActorProxy instance which is notified when further 1.1243 + * data about the request is available. 1.1244 + */ 1.1245 + onNetworkEvent: DevToolsUtils.makeInfallible(function _onNetworkEvent(event) { 1.1246 + return new NetworkEventActorProxy(this.messageManager, this.id).init(event); 1.1247 + }), 1.1248 + 1.1249 + destroy: function() 1.1250 + { 1.1251 + if (this.messageManager) { 1.1252 + this.messageManager.removeMessageListener("debug:netmonitor:" + this.id, 1.1253 + this.onNetMonitorMessage); 1.1254 + } 1.1255 + this.messageManager = null; 1.1256 + this.filters = null; 1.1257 + 1.1258 + if (this.netMonitor) { 1.1259 + this.netMonitor.destroy(); 1.1260 + this.netMonitor = null; 1.1261 + } 1.1262 + }, 1.1263 +}; // NetworkMonitorManager.prototype 1.1264 + 1.1265 + 1.1266 +/** 1.1267 + * A WebProgressListener that listens for location changes. 1.1268 + * 1.1269 + * This progress listener is used to track file loads and other kinds of 1.1270 + * location changes. 1.1271 + * 1.1272 + * @constructor 1.1273 + * @param object aWindow 1.1274 + * The window for which we need to track location changes. 1.1275 + * @param object aOwner 1.1276 + * The listener owner which needs to implement two methods: 1.1277 + * - onFileActivity(aFileURI) 1.1278 + * - onLocationChange(aState, aTabURI, aPageTitle) 1.1279 + */ 1.1280 +function ConsoleProgressListener(aWindow, aOwner) 1.1281 +{ 1.1282 + this.window = aWindow; 1.1283 + this.owner = aOwner; 1.1284 +} 1.1285 +exports.ConsoleProgressListener = ConsoleProgressListener; 1.1286 + 1.1287 +ConsoleProgressListener.prototype = { 1.1288 + /** 1.1289 + * Constant used for startMonitor()/stopMonitor() that tells you want to 1.1290 + * monitor file loads. 1.1291 + */ 1.1292 + MONITOR_FILE_ACTIVITY: 1, 1.1293 + 1.1294 + /** 1.1295 + * Constant used for startMonitor()/stopMonitor() that tells you want to 1.1296 + * monitor page location changes. 1.1297 + */ 1.1298 + MONITOR_LOCATION_CHANGE: 2, 1.1299 + 1.1300 + /** 1.1301 + * Tells if you want to monitor file activity. 1.1302 + * @private 1.1303 + * @type boolean 1.1304 + */ 1.1305 + _fileActivity: false, 1.1306 + 1.1307 + /** 1.1308 + * Tells if you want to monitor location changes. 1.1309 + * @private 1.1310 + * @type boolean 1.1311 + */ 1.1312 + _locationChange: false, 1.1313 + 1.1314 + /** 1.1315 + * Tells if the console progress listener is initialized or not. 1.1316 + * @private 1.1317 + * @type boolean 1.1318 + */ 1.1319 + _initialized: false, 1.1320 + 1.1321 + _webProgress: null, 1.1322 + 1.1323 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, 1.1324 + Ci.nsISupportsWeakReference]), 1.1325 + 1.1326 + /** 1.1327 + * Initialize the ConsoleProgressListener. 1.1328 + * @private 1.1329 + */ 1.1330 + _init: function CPL__init() 1.1331 + { 1.1332 + if (this._initialized) { 1.1333 + return; 1.1334 + } 1.1335 + 1.1336 + this._webProgress = this.window.QueryInterface(Ci.nsIInterfaceRequestor) 1.1337 + .getInterface(Ci.nsIWebNavigation) 1.1338 + .QueryInterface(Ci.nsIWebProgress); 1.1339 + this._webProgress.addProgressListener(this, 1.1340 + Ci.nsIWebProgress.NOTIFY_STATE_ALL); 1.1341 + 1.1342 + this._initialized = true; 1.1343 + }, 1.1344 + 1.1345 + /** 1.1346 + * Start a monitor/tracker related to the current nsIWebProgressListener 1.1347 + * instance. 1.1348 + * 1.1349 + * @param number aMonitor 1.1350 + * Tells what you want to track. Available constants: 1.1351 + * - this.MONITOR_FILE_ACTIVITY 1.1352 + * Track file loads. 1.1353 + * - this.MONITOR_LOCATION_CHANGE 1.1354 + * Track location changes for the top window. 1.1355 + */ 1.1356 + startMonitor: function CPL_startMonitor(aMonitor) 1.1357 + { 1.1358 + switch (aMonitor) { 1.1359 + case this.MONITOR_FILE_ACTIVITY: 1.1360 + this._fileActivity = true; 1.1361 + break; 1.1362 + case this.MONITOR_LOCATION_CHANGE: 1.1363 + this._locationChange = true; 1.1364 + break; 1.1365 + default: 1.1366 + throw new Error("ConsoleProgressListener: unknown monitor type " + 1.1367 + aMonitor + "!"); 1.1368 + } 1.1369 + this._init(); 1.1370 + }, 1.1371 + 1.1372 + /** 1.1373 + * Stop a monitor. 1.1374 + * 1.1375 + * @param number aMonitor 1.1376 + * Tells what you want to stop tracking. See this.startMonitor() for 1.1377 + * the list of constants. 1.1378 + */ 1.1379 + stopMonitor: function CPL_stopMonitor(aMonitor) 1.1380 + { 1.1381 + switch (aMonitor) { 1.1382 + case this.MONITOR_FILE_ACTIVITY: 1.1383 + this._fileActivity = false; 1.1384 + break; 1.1385 + case this.MONITOR_LOCATION_CHANGE: 1.1386 + this._locationChange = false; 1.1387 + break; 1.1388 + default: 1.1389 + throw new Error("ConsoleProgressListener: unknown monitor type " + 1.1390 + aMonitor + "!"); 1.1391 + } 1.1392 + 1.1393 + if (!this._fileActivity && !this._locationChange) { 1.1394 + this.destroy(); 1.1395 + } 1.1396 + }, 1.1397 + 1.1398 + onStateChange: 1.1399 + function CPL_onStateChange(aProgress, aRequest, aState, aStatus) 1.1400 + { 1.1401 + if (!this.owner) { 1.1402 + return; 1.1403 + } 1.1404 + 1.1405 + if (this._fileActivity) { 1.1406 + this._checkFileActivity(aProgress, aRequest, aState, aStatus); 1.1407 + } 1.1408 + 1.1409 + if (this._locationChange) { 1.1410 + this._checkLocationChange(aProgress, aRequest, aState, aStatus); 1.1411 + } 1.1412 + }, 1.1413 + 1.1414 + /** 1.1415 + * Check if there is any file load, given the arguments of 1.1416 + * nsIWebProgressListener.onStateChange. If the state change tells that a file 1.1417 + * URI has been loaded, then the remote Web Console instance is notified. 1.1418 + * @private 1.1419 + */ 1.1420 + _checkFileActivity: 1.1421 + function CPL__checkFileActivity(aProgress, aRequest, aState, aStatus) 1.1422 + { 1.1423 + if (!(aState & Ci.nsIWebProgressListener.STATE_START)) { 1.1424 + return; 1.1425 + } 1.1426 + 1.1427 + let uri = null; 1.1428 + if (aRequest instanceof Ci.imgIRequest) { 1.1429 + let imgIRequest = aRequest.QueryInterface(Ci.imgIRequest); 1.1430 + uri = imgIRequest.URI; 1.1431 + } 1.1432 + else if (aRequest instanceof Ci.nsIChannel) { 1.1433 + let nsIChannel = aRequest.QueryInterface(Ci.nsIChannel); 1.1434 + uri = nsIChannel.URI; 1.1435 + } 1.1436 + 1.1437 + if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) { 1.1438 + return; 1.1439 + } 1.1440 + 1.1441 + this.owner.onFileActivity(uri.spec); 1.1442 + }, 1.1443 + 1.1444 + /** 1.1445 + * Check if the current window.top location is changing, given the arguments 1.1446 + * of nsIWebProgressListener.onStateChange. If that is the case, the remote 1.1447 + * Web Console instance is notified. 1.1448 + * @private 1.1449 + */ 1.1450 + _checkLocationChange: 1.1451 + function CPL__checkLocationChange(aProgress, aRequest, aState, aStatus) 1.1452 + { 1.1453 + let isStart = aState & Ci.nsIWebProgressListener.STATE_START; 1.1454 + let isStop = aState & Ci.nsIWebProgressListener.STATE_STOP; 1.1455 + let isNetwork = aState & Ci.nsIWebProgressListener.STATE_IS_NETWORK; 1.1456 + let isWindow = aState & Ci.nsIWebProgressListener.STATE_IS_WINDOW; 1.1457 + 1.1458 + // Skip non-interesting states. 1.1459 + if (!isNetwork || !isWindow || aProgress.DOMWindow != this.window) { 1.1460 + return; 1.1461 + } 1.1462 + 1.1463 + if (isStart && aRequest instanceof Ci.nsIChannel) { 1.1464 + this.owner.onLocationChange("start", aRequest.URI.spec, ""); 1.1465 + } 1.1466 + else if (isStop) { 1.1467 + this.owner.onLocationChange("stop", this.window.location.href, 1.1468 + this.window.document.title); 1.1469 + } 1.1470 + }, 1.1471 + 1.1472 + onLocationChange: function() {}, 1.1473 + onStatusChange: function() {}, 1.1474 + onProgressChange: function() {}, 1.1475 + onSecurityChange: function() {}, 1.1476 + 1.1477 + /** 1.1478 + * Destroy the ConsoleProgressListener. 1.1479 + */ 1.1480 + destroy: function CPL_destroy() 1.1481 + { 1.1482 + if (!this._initialized) { 1.1483 + return; 1.1484 + } 1.1485 + 1.1486 + this._initialized = false; 1.1487 + this._fileActivity = false; 1.1488 + this._locationChange = false; 1.1489 + 1.1490 + try { 1.1491 + this._webProgress.removeProgressListener(this); 1.1492 + } 1.1493 + catch (ex) { 1.1494 + // This can throw during browser shutdown. 1.1495 + } 1.1496 + 1.1497 + this._webProgress = null; 1.1498 + this.window = null; 1.1499 + this.owner = null; 1.1500 + }, 1.1501 +}; // ConsoleProgressListener.prototype 1.1502 + 1.1503 +function gSequenceId() { return gSequenceId.n++; } 1.1504 +gSequenceId.n = 1;