toolkit/devtools/webconsole/network-monitor.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 const {Cc, Ci, Cu} = require("chrome");
     9 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    11 loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper"));
    12 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
    13 loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm");
    14 loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
    15 loader.lazyImporter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
    16 loader.lazyServiceGetter(this, "gActivityDistributor",
    17                          "@mozilla.org/network/http-activity-distributor;1",
    18                          "nsIHttpActivityDistributor");
    20 ///////////////////////////////////////////////////////////////////////////////
    21 // Network logging
    22 ///////////////////////////////////////////////////////////////////////////////
    24 // The maximum uint32 value.
    25 const PR_UINT32_MAX = 4294967295;
    27 // HTTP status codes.
    28 const HTTP_MOVED_PERMANENTLY = 301;
    29 const HTTP_FOUND = 302;
    30 const HTTP_SEE_OTHER = 303;
    31 const HTTP_TEMPORARY_REDIRECT = 307;
    33 // The maximum number of bytes a NetworkResponseListener can hold.
    34 const RESPONSE_BODY_LIMIT = 1048576; // 1 MB
    36 /**
    37  * The network response listener implements the nsIStreamListener and
    38  * nsIRequestObserver interfaces. This is used within the NetworkMonitor feature
    39  * to get the response body of the request.
    40  *
    41  * The code is mostly based on code listings from:
    42  *
    43  *   http://www.softwareishard.com/blog/firebug/
    44  *      nsitraceablechannel-intercept-http-traffic/
    45  *
    46  * @constructor
    47  * @param object aOwner
    48  *        The response listener owner. This object needs to hold the
    49  *        |openResponses| object.
    50  * @param object aHttpActivity
    51  *        HttpActivity object associated with this request. See NetworkMonitor
    52  *        for more information.
    53  */
    54 function NetworkResponseListener(aOwner, aHttpActivity)
    55 {
    56   this.owner = aOwner;
    57   this.receivedData = "";
    58   this.httpActivity = aHttpActivity;
    59   this.bodySize = 0;
    60 }
    61 exports.NetworkResponseListener = NetworkResponseListener;
    63 NetworkResponseListener.prototype = {
    64   QueryInterface:
    65     XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback,
    66                            Ci.nsIRequestObserver, Ci.nsISupports]),
    68   /**
    69    * This NetworkResponseListener tracks the NetworkMonitor.openResponses object
    70    * to find the associated uncached headers.
    71    * @private
    72    */
    73   _foundOpenResponse: false,
    75   /**
    76    * The response listener owner.
    77    */
    78   owner: null,
    80   /**
    81    * The response will be written into the outputStream of this nsIPipe.
    82    * Both ends of the pipe must be blocking.
    83    */
    84   sink: null,
    86   /**
    87    * The HttpActivity object associated with this response.
    88    */
    89   httpActivity: null,
    91   /**
    92    * Stores the received data as a string.
    93    */
    94   receivedData: null,
    96   /**
    97    * The network response body size.
    98    */
    99   bodySize: null,
   101   /**
   102    * The nsIRequest we are started for.
   103    */
   104   request: null,
   106   /**
   107    * Set the async listener for the given nsIAsyncInputStream. This allows us to
   108    * wait asynchronously for any data coming from the stream.
   109    *
   110    * @param nsIAsyncInputStream aStream
   111    *        The input stream from where we are waiting for data to come in.
   112    * @param nsIInputStreamCallback aListener
   113    *        The input stream callback you want. This is an object that must have
   114    *        the onInputStreamReady() method. If the argument is null, then the
   115    *        current callback is removed.
   116    * @return void
   117    */
   118   setAsyncListener: function NRL_setAsyncListener(aStream, aListener)
   119   {
   120     // Asynchronously wait for the stream to be readable or closed.
   121     aStream.asyncWait(aListener, 0, 0, Services.tm.mainThread);
   122   },
   124   /**
   125    * Stores the received data, if request/response body logging is enabled. It
   126    * also does limit the number of stored bytes, based on the
   127    * RESPONSE_BODY_LIMIT constant.
   128    *
   129    * Learn more about nsIStreamListener at:
   130    * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIStreamListener
   131    *
   132    * @param nsIRequest aRequest
   133    * @param nsISupports aContext
   134    * @param nsIInputStream aInputStream
   135    * @param unsigned long aOffset
   136    * @param unsigned long aCount
   137    */
   138   onDataAvailable:
   139   function NRL_onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount)
   140   {
   141     this._findOpenResponse();
   142     let data = NetUtil.readInputStreamToString(aInputStream, aCount);
   144     this.bodySize += aCount;
   146     if (!this.httpActivity.discardResponseBody &&
   147         this.receivedData.length < RESPONSE_BODY_LIMIT) {
   148       this.receivedData += NetworkHelper.
   149                            convertToUnicode(data, aRequest.contentCharset);
   150     }
   151   },
   153   /**
   154    * See documentation at
   155    * https://developer.mozilla.org/En/NsIRequestObserver
   156    *
   157    * @param nsIRequest aRequest
   158    * @param nsISupports aContext
   159    */
   160   onStartRequest: function NRL_onStartRequest(aRequest)
   161   {
   162     this.request = aRequest;
   163     this._findOpenResponse();
   164     // Asynchronously wait for the data coming from the request.
   165     this.setAsyncListener(this.sink.inputStream, this);
   166   },
   168   /**
   169    * Handle the onStopRequest by closing the sink output stream.
   170    *
   171    * For more documentation about nsIRequestObserver go to:
   172    * https://developer.mozilla.org/En/NsIRequestObserver
   173    */
   174   onStopRequest: function NRL_onStopRequest()
   175   {
   176     this._findOpenResponse();
   177     this.sink.outputStream.close();
   178   },
   180   /**
   181    * Find the open response object associated to the current request. The
   182    * NetworkMonitor._httpResponseExaminer() method saves the response headers in
   183    * NetworkMonitor.openResponses. This method takes the data from the open
   184    * response object and puts it into the HTTP activity object, then sends it to
   185    * the remote Web Console instance.
   186    *
   187    * @private
   188    */
   189   _findOpenResponse: function NRL__findOpenResponse()
   190   {
   191     if (!this.owner || this._foundOpenResponse) {
   192       return;
   193     }
   195     let openResponse = null;
   197     for each (let item in this.owner.openResponses) {
   198       if (item.channel === this.httpActivity.channel) {
   199         openResponse = item;
   200         break;
   201       }
   202     }
   204     if (!openResponse) {
   205       return;
   206     }
   207     this._foundOpenResponse = true;
   209     delete this.owner.openResponses[openResponse.id];
   211     this.httpActivity.owner.addResponseHeaders(openResponse.headers);
   212     this.httpActivity.owner.addResponseCookies(openResponse.cookies);
   213   },
   215   /**
   216    * Clean up the response listener once the response input stream is closed.
   217    * This is called from onStopRequest() or from onInputStreamReady() when the
   218    * stream is closed.
   219    * @return void
   220    */
   221   onStreamClose: function NRL_onStreamClose()
   222   {
   223     if (!this.httpActivity) {
   224       return;
   225     }
   226     // Remove our listener from the request input stream.
   227     this.setAsyncListener(this.sink.inputStream, null);
   229     this._findOpenResponse();
   231     if (!this.httpActivity.discardResponseBody && this.receivedData.length) {
   232       this._onComplete(this.receivedData);
   233     }
   234     else if (!this.httpActivity.discardResponseBody &&
   235              this.httpActivity.responseStatus == 304) {
   236       // Response is cached, so we load it from cache.
   237       let charset = this.request.contentCharset || this.httpActivity.charset;
   238       NetworkHelper.loadFromCache(this.httpActivity.url, charset,
   239                                   this._onComplete.bind(this));
   240     }
   241     else {
   242       this._onComplete();
   243     }
   244   },
   246   /**
   247    * Handler for when the response completes. This function cleans up the
   248    * response listener.
   249    *
   250    * @param string [aData]
   251    *        Optional, the received data coming from the response listener or
   252    *        from the cache.
   253    */
   254   _onComplete: function NRL__onComplete(aData)
   255   {
   256     let response = {
   257       mimeType: "",
   258       text: aData || "",
   259     };
   261     response.size = response.text.length;
   263     try {
   264       response.mimeType = this.request.contentType;
   265     }
   266     catch (ex) { }
   268     if (!response.mimeType || !NetworkHelper.isTextMimeType(response.mimeType)) {
   269       response.encoding = "base64";
   270       response.text = btoa(response.text);
   271     }
   273     if (response.mimeType && this.request.contentCharset) {
   274       response.mimeType += "; charset=" + this.request.contentCharset;
   275     }
   277     this.receivedData = "";
   279     this.httpActivity.owner.
   280       addResponseContent(response, this.httpActivity.discardResponseBody);
   282     this.httpActivity.channel = null;
   283     this.httpActivity.owner = null;
   284     this.httpActivity = null;
   285     this.sink = null;
   286     this.inputStream = null;
   287     this.request = null;
   288     this.owner = null;
   289   },
   291   /**
   292    * The nsIInputStreamCallback for when the request input stream is ready -
   293    * either it has more data or it is closed.
   294    *
   295    * @param nsIAsyncInputStream aStream
   296    *        The sink input stream from which data is coming.
   297    * @returns void
   298    */
   299   onInputStreamReady: function NRL_onInputStreamReady(aStream)
   300   {
   301     if (!(aStream instanceof Ci.nsIAsyncInputStream) || !this.httpActivity) {
   302       return;
   303     }
   305     let available = -1;
   306     try {
   307       // This may throw if the stream is closed normally or due to an error.
   308       available = aStream.available();
   309     }
   310     catch (ex) { }
   312     if (available != -1) {
   313       if (available != 0) {
   314         // Note that passing 0 as the offset here is wrong, but the
   315         // onDataAvailable() method does not use the offset, so it does not
   316         // matter.
   317         this.onDataAvailable(this.request, null, aStream, 0, available);
   318       }
   319       this.setAsyncListener(aStream, this);
   320     }
   321     else {
   322       this.onStreamClose();
   323     }
   324   },
   325 }; // NetworkResponseListener.prototype
   328 /**
   329  * The network monitor uses the nsIHttpActivityDistributor to monitor network
   330  * requests. The nsIObserverService is also used for monitoring
   331  * http-on-examine-response notifications. All network request information is
   332  * routed to the remote Web Console.
   333  *
   334  * @constructor
   335  * @param object aFilters
   336  *        Object with the filters to use for network requests:
   337  *        - window (nsIDOMWindow): filter network requests by the associated
   338  *        window object.
   339  *        - appId (number): filter requests by the appId.
   340  *        - topFrame (nsIDOMElement): filter requests by their topFrameElement.
   341  *        Filters are optional. If any of these filters match the request is
   342  *        logged (OR is applied). If no filter is provided then all requests are
   343  *        logged.
   344  * @param object aOwner
   345  *        The network monitor owner. This object needs to hold:
   346  *        - onNetworkEvent(aRequestInfo, aChannel, aNetworkMonitor).
   347  *        This method is invoked once for every new network request and it is
   348  *        given the following arguments: the initial network request
   349  *        information, and the channel. The third argument is the NetworkMonitor
   350  *        instance.
   351  *        onNetworkEvent() must return an object which holds several add*()
   352  *        methods which are used to add further network request/response
   353  *        information.
   354  */
   355 function NetworkMonitor(aFilters, aOwner)
   356 {
   357   if (aFilters) {
   358     this.window = aFilters.window;
   359     this.appId = aFilters.appId;
   360     this.topFrame = aFilters.topFrame;
   361   }
   362   if (!this.window && !this.appId && !this.topFrame) {
   363     this._logEverything = true;
   364   }
   365   this.owner = aOwner;
   366   this.openRequests = {};
   367   this.openResponses = {};
   368   this._httpResponseExaminer =
   369     DevToolsUtils.makeInfallible(this._httpResponseExaminer).bind(this);
   370 }
   371 exports.NetworkMonitor = NetworkMonitor;
   373 NetworkMonitor.prototype = {
   374   _logEverything: false,
   375   window: null,
   376   appId: null,
   377   topFrame: null,
   379   httpTransactionCodes: {
   380     0x5001: "REQUEST_HEADER",
   381     0x5002: "REQUEST_BODY_SENT",
   382     0x5003: "RESPONSE_START",
   383     0x5004: "RESPONSE_HEADER",
   384     0x5005: "RESPONSE_COMPLETE",
   385     0x5006: "TRANSACTION_CLOSE",
   387     0x804b0003: "STATUS_RESOLVING",
   388     0x804b000b: "STATUS_RESOLVED",
   389     0x804b0007: "STATUS_CONNECTING_TO",
   390     0x804b0004: "STATUS_CONNECTED_TO",
   391     0x804b0005: "STATUS_SENDING_TO",
   392     0x804b000a: "STATUS_WAITING_FOR",
   393     0x804b0006: "STATUS_RECEIVING_FROM"
   394   },
   396   // Network response bodies are piped through a buffer of the given size (in
   397   // bytes).
   398   responsePipeSegmentSize: null,
   400   owner: null,
   402   /**
   403    * Whether to save the bodies of network requests and responses. Disabled by
   404    * default to save memory.
   405    * @type boolean
   406    */
   407   saveRequestAndResponseBodies: false,
   409   /**
   410    * Object that holds the HTTP activity objects for ongoing requests.
   411    */
   412   openRequests: null,
   414   /**
   415    * Object that holds response headers coming from this._httpResponseExaminer.
   416    */
   417   openResponses: null,
   419   /**
   420    * The network monitor initializer.
   421    */
   422   init: function NM_init()
   423   {
   424     this.responsePipeSegmentSize = Services.prefs
   425                                    .getIntPref("network.buffer.cache.size");
   427     gActivityDistributor.addObserver(this);
   429     if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
   430       Services.obs.addObserver(this._httpResponseExaminer,
   431                                "http-on-examine-response", false);
   432     }
   433   },
   435   /**
   436    * Observe notifications for the http-on-examine-response topic, coming from
   437    * the nsIObserverService.
   438    *
   439    * @private
   440    * @param nsIHttpChannel aSubject
   441    * @param string aTopic
   442    * @returns void
   443    */
   444   _httpResponseExaminer: function NM__httpResponseExaminer(aSubject, aTopic)
   445   {
   446     // The httpResponseExaminer is used to retrieve the uncached response
   447     // headers. The data retrieved is stored in openResponses. The
   448     // NetworkResponseListener is responsible with updating the httpActivity
   449     // object with the data from the new object in openResponses.
   451     if (!this.owner || aTopic != "http-on-examine-response" ||
   452         !(aSubject instanceof Ci.nsIHttpChannel)) {
   453       return;
   454     }
   456     let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
   458     if (!this._matchRequest(channel)) {
   459       return;
   460     }
   462     let response = {
   463       id: gSequenceId(),
   464       channel: channel,
   465       headers: [],
   466       cookies: [],
   467     };
   469     let setCookieHeader = null;
   471     channel.visitResponseHeaders({
   472       visitHeader: function NM__visitHeader(aName, aValue) {
   473         let lowerName = aName.toLowerCase();
   474         if (lowerName == "set-cookie") {
   475           setCookieHeader = aValue;
   476         }
   477         response.headers.push({ name: aName, value: aValue });
   478       }
   479     });
   481     if (!response.headers.length) {
   482       return; // No need to continue.
   483     }
   485     if (setCookieHeader) {
   486       response.cookies = NetworkHelper.parseSetCookieHeader(setCookieHeader);
   487     }
   489     // Determine the HTTP version.
   490     let httpVersionMaj = {};
   491     let httpVersionMin = {};
   493     channel.QueryInterface(Ci.nsIHttpChannelInternal);
   494     channel.getResponseVersion(httpVersionMaj, httpVersionMin);
   496     response.status = channel.responseStatus;
   497     response.statusText = channel.responseStatusText;
   498     response.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
   499                                      httpVersionMin.value;
   501     this.openResponses[response.id] = response;
   502   },
   504   /**
   505    * Begin observing HTTP traffic that originates inside the current tab.
   506    *
   507    * @see https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIHttpActivityObserver
   508    *
   509    * @param nsIHttpChannel aChannel
   510    * @param number aActivityType
   511    * @param number aActivitySubtype
   512    * @param number aTimestamp
   513    * @param number aExtraSizeData
   514    * @param string aExtraStringData
   515    */
   516   observeActivity: DevToolsUtils.makeInfallible(function NM_observeActivity(aChannel, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData, aExtraStringData)
   517   {
   518     if (!this.owner ||
   519         aActivityType != gActivityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION &&
   520         aActivityType != gActivityDistributor.ACTIVITY_TYPE_SOCKET_TRANSPORT) {
   521       return;
   522     }
   524     if (!(aChannel instanceof Ci.nsIHttpChannel)) {
   525       return;
   526     }
   528     aChannel = aChannel.QueryInterface(Ci.nsIHttpChannel);
   530     if (aActivitySubtype ==
   531         gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER) {
   532       this._onRequestHeader(aChannel, aTimestamp, aExtraStringData);
   533       return;
   534     }
   536     // Iterate over all currently ongoing requests. If aChannel can't
   537     // be found within them, then exit this function.
   538     let httpActivity = null;
   539     for each (let item in this.openRequests) {
   540       if (item.channel === aChannel) {
   541         httpActivity = item;
   542         break;
   543       }
   544     }
   546     if (!httpActivity) {
   547       return;
   548     }
   550     let transCodes = this.httpTransactionCodes;
   552     // Store the time information for this activity subtype.
   553     if (aActivitySubtype in transCodes) {
   554       let stage = transCodes[aActivitySubtype];
   555       if (stage in httpActivity.timings) {
   556         httpActivity.timings[stage].last = aTimestamp;
   557       }
   558       else {
   559         httpActivity.timings[stage] = {
   560           first: aTimestamp,
   561           last: aTimestamp,
   562         };
   563       }
   564     }
   566     switch (aActivitySubtype) {
   567       case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
   568         this._onRequestBodySent(httpActivity);
   569         break;
   570       case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
   571         this._onResponseHeader(httpActivity, aExtraStringData);
   572         break;
   573       case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
   574         this._onTransactionClose(httpActivity);
   575         break;
   576       default:
   577         break;
   578     }
   579   }),
   581   /**
   582    * Check if a given network request should be logged by this network monitor
   583    * instance based on the current filters.
   584    *
   585    * @private
   586    * @param nsIHttpChannel aChannel
   587    *        Request to check.
   588    * @return boolean
   589    *         True if the network request should be logged, false otherwise.
   590    */
   591   _matchRequest: function NM__matchRequest(aChannel)
   592   {
   593     if (this._logEverything) {
   594       return true;
   595     }
   597     if (this.window) {
   598       let win = NetworkHelper.getWindowForRequest(aChannel);
   599       if (win && win.top === this.window) {
   600         return true;
   601       }
   602     }
   604     if (this.topFrame) {
   605       let topFrame = NetworkHelper.getTopFrameForRequest(aChannel);
   606       if (topFrame && topFrame === this.topFrame) {
   607         return true;
   608       }
   609     }
   611     if (this.appId) {
   612       let appId = NetworkHelper.getAppIdForRequest(aChannel);
   613       if (appId && appId == this.appId) {
   614         return true;
   615       }
   616     }
   618     return false;
   619   },
   621   /**
   622    * Handler for ACTIVITY_SUBTYPE_REQUEST_HEADER. When a request starts the
   623    * headers are sent to the server. This method creates the |httpActivity|
   624    * object where we store the request and response information that is
   625    * collected through its lifetime.
   626    *
   627    * @private
   628    * @param nsIHttpChannel aChannel
   629    * @param number aTimestamp
   630    * @param string aExtraStringData
   631    * @return void
   632    */
   633   _onRequestHeader:
   634   function NM__onRequestHeader(aChannel, aTimestamp, aExtraStringData)
   635   {
   636     if (!this._matchRequest(aChannel)) {
   637       return;
   638     }
   640     let win = NetworkHelper.getWindowForRequest(aChannel);
   641     let httpActivity = this.createActivityObject(aChannel);
   643     // see NM__onRequestBodySent()
   644     httpActivity.charset = win ? win.document.characterSet : null;
   645     httpActivity.private = win ? PrivateBrowsingUtils.isWindowPrivate(win) : false;
   647     httpActivity.timings.REQUEST_HEADER = {
   648       first: aTimestamp,
   649       last: aTimestamp
   650     };
   652     let httpVersionMaj = {};
   653     let httpVersionMin = {};
   654     let event = {};
   655     event.startedDateTime = new Date(Math.round(aTimestamp / 1000)).toISOString();
   656     event.headersSize = aExtraStringData.length;
   657     event.method = aChannel.requestMethod;
   658     event.url = aChannel.URI.spec;
   659     event.private = httpActivity.private;
   661     // Determine if this is an XHR request.
   662     try {
   663       let callbacks = aChannel.notificationCallbacks;
   664       let xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null;
   665       httpActivity.isXHR = event.isXHR = !!xhrRequest;
   666     } catch (e) {
   667       httpActivity.isXHR = event.isXHR = false;
   668     }
   670     // Determine the HTTP version.
   671     aChannel.QueryInterface(Ci.nsIHttpChannelInternal);
   672     aChannel.getRequestVersion(httpVersionMaj, httpVersionMin);
   674     event.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
   675                                   httpVersionMin.value;
   677     event.discardRequestBody = !this.saveRequestAndResponseBodies;
   678     event.discardResponseBody = !this.saveRequestAndResponseBodies;
   680     let headers = [];
   681     let cookies = [];
   682     let cookieHeader = null;
   684     // Copy the request header data.
   685     aChannel.visitRequestHeaders({
   686       visitHeader: function NM__visitHeader(aName, aValue)
   687       {
   688         if (aName == "Cookie") {
   689           cookieHeader = aValue;
   690         }
   691         headers.push({ name: aName, value: aValue });
   692       }
   693     });
   695     if (cookieHeader) {
   696       cookies = NetworkHelper.parseCookieHeader(cookieHeader);
   697     }
   699     httpActivity.owner = this.owner.onNetworkEvent(event, aChannel, this);
   701     this._setupResponseListener(httpActivity);
   703     this.openRequests[httpActivity.id] = httpActivity;
   705     httpActivity.owner.addRequestHeaders(headers);
   706     httpActivity.owner.addRequestCookies(cookies);
   707   },
   709   /**
   710    * Create the empty HTTP activity object. This object is used for storing all
   711    * the request and response information.
   712    *
   713    * This is a HAR-like object. Conformance to the spec is not guaranteed at
   714    * this point.
   715    *
   716    * TODO: Bug 708717 - Add support for network log export to HAR
   717    *
   718    * @see http://www.softwareishard.com/blog/har-12-spec
   719    * @param nsIHttpChannel aChannel
   720    *        The HTTP channel for which the HTTP activity object is created.
   721    * @return object
   722    *         The new HTTP activity object.
   723    */
   724   createActivityObject: function NM_createActivityObject(aChannel)
   725   {
   726     return {
   727       id: gSequenceId(),
   728       channel: aChannel,
   729       charset: null, // see NM__onRequestHeader()
   730       url: aChannel.URI.spec,
   731       discardRequestBody: !this.saveRequestAndResponseBodies,
   732       discardResponseBody: !this.saveRequestAndResponseBodies,
   733       timings: {}, // internal timing information, see NM_observeActivity()
   734       responseStatus: null, // see NM__onResponseHeader()
   735       owner: null, // the activity owner which is notified when changes happen
   736     };
   737   },
   739   /**
   740    * Setup the network response listener for the given HTTP activity. The
   741    * NetworkResponseListener is responsible for storing the response body.
   742    *
   743    * @private
   744    * @param object aHttpActivity
   745    *        The HTTP activity object we are tracking.
   746    */
   747   _setupResponseListener: function NM__setupResponseListener(aHttpActivity)
   748   {
   749     let channel = aHttpActivity.channel;
   750     channel.QueryInterface(Ci.nsITraceableChannel);
   752     // The response will be written into the outputStream of this pipe.
   753     // This allows us to buffer the data we are receiving and read it
   754     // asynchronously.
   755     // Both ends of the pipe must be blocking.
   756     let sink = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
   758     // The streams need to be blocking because this is required by the
   759     // stream tee.
   760     sink.init(false, false, this.responsePipeSegmentSize, PR_UINT32_MAX, null);
   762     // Add listener for the response body.
   763     let newListener = new NetworkResponseListener(this, aHttpActivity);
   765     // Remember the input stream, so it isn't released by GC.
   766     newListener.inputStream = sink.inputStream;
   767     newListener.sink = sink;
   769     let tee = Cc["@mozilla.org/network/stream-listener-tee;1"].
   770               createInstance(Ci.nsIStreamListenerTee);
   772     let originalListener = channel.setNewListener(tee);
   774     tee.init(originalListener, sink.outputStream, newListener);
   775   },
   777   /**
   778    * Handler for ACTIVITY_SUBTYPE_REQUEST_BODY_SENT. The request body is logged
   779    * here.
   780    *
   781    * @private
   782    * @param object aHttpActivity
   783    *        The HTTP activity object we are working with.
   784    */
   785   _onRequestBodySent: function NM__onRequestBodySent(aHttpActivity)
   786   {
   787     if (aHttpActivity.discardRequestBody) {
   788       return;
   789     }
   791     let sentBody = NetworkHelper.
   792                    readPostTextFromRequest(aHttpActivity.channel,
   793                                            aHttpActivity.charset);
   795     if (!sentBody && this.window &&
   796         aHttpActivity.url == this.window.location.href) {
   797       // If the request URL is the same as the current page URL, then
   798       // we can try to get the posted text from the page directly.
   799       // This check is necessary as otherwise the
   800       //   NetworkHelper.readPostTextFromPageViaWebNav()
   801       // function is called for image requests as well but these
   802       // are not web pages and as such don't store the posted text
   803       // in the cache of the webpage.
   804       let webNav = this.window.QueryInterface(Ci.nsIInterfaceRequestor).
   805                    getInterface(Ci.nsIWebNavigation);
   806       sentBody = NetworkHelper.
   807                  readPostTextFromPageViaWebNav(webNav, aHttpActivity.charset);
   808     }
   810     if (sentBody) {
   811       aHttpActivity.owner.addRequestPostData({ text: sentBody });
   812     }
   813   },
   815   /**
   816    * Handler for ACTIVITY_SUBTYPE_RESPONSE_HEADER. This method stores
   817    * information about the response headers.
   818    *
   819    * @private
   820    * @param object aHttpActivity
   821    *        The HTTP activity object we are working with.
   822    * @param string aExtraStringData
   823    *        The uncached response headers.
   824    */
   825   _onResponseHeader:
   826   function NM__onResponseHeader(aHttpActivity, aExtraStringData)
   827   {
   828     // aExtraStringData contains the uncached response headers. The first line
   829     // contains the response status (e.g. HTTP/1.1 200 OK).
   830     //
   831     // Note: The response header is not saved here. Calling the
   832     // channel.visitResponseHeaders() methood at this point sometimes causes an
   833     // NS_ERROR_NOT_AVAILABLE exception.
   834     //
   835     // We could parse aExtraStringData to get the headers and their values, but
   836     // that is not trivial to do in an accurate manner. Hence, we save the
   837     // response headers in this._httpResponseExaminer().
   839     let headers = aExtraStringData.split(/\r\n|\n|\r/);
   840     let statusLine = headers.shift();
   841     let statusLineArray = statusLine.split(" ");
   843     let response = {};
   844     response.httpVersion = statusLineArray.shift();
   845     response.status = statusLineArray.shift();
   846     response.statusText = statusLineArray.join(" ");
   847     response.headersSize = aExtraStringData.length;
   849     aHttpActivity.responseStatus = response.status;
   851     // Discard the response body for known response statuses.
   852     switch (parseInt(response.status)) {
   853       case HTTP_MOVED_PERMANENTLY:
   854       case HTTP_FOUND:
   855       case HTTP_SEE_OTHER:
   856       case HTTP_TEMPORARY_REDIRECT:
   857         aHttpActivity.discardResponseBody = true;
   858         break;
   859     }
   861     response.discardResponseBody = aHttpActivity.discardResponseBody;
   863     aHttpActivity.owner.addResponseStart(response);
   864   },
   866   /**
   867    * Handler for ACTIVITY_SUBTYPE_TRANSACTION_CLOSE. This method updates the HAR
   868    * timing information on the HTTP activity object and clears the request
   869    * from the list of known open requests.
   870    *
   871    * @private
   872    * @param object aHttpActivity
   873    *        The HTTP activity object we work with.
   874    */
   875   _onTransactionClose: function NM__onTransactionClose(aHttpActivity)
   876   {
   877     let result = this._setupHarTimings(aHttpActivity);
   878     aHttpActivity.owner.addEventTimings(result.total, result.timings);
   879     delete this.openRequests[aHttpActivity.id];
   880   },
   882   /**
   883    * Update the HTTP activity object to include timing information as in the HAR
   884    * spec. The HTTP activity object holds the raw timing information in
   885    * |timings| - these are timings stored for each activity notification. The
   886    * HAR timing information is constructed based on these lower level data.
   887    *
   888    * @param object aHttpActivity
   889    *        The HTTP activity object we are working with.
   890    * @return object
   891    *         This object holds two properties:
   892    *         - total - the total time for all of the request and response.
   893    *         - timings - the HAR timings object.
   894    */
   895   _setupHarTimings: function NM__setupHarTimings(aHttpActivity)
   896   {
   897     let timings = aHttpActivity.timings;
   898     let harTimings = {};
   900     // Not clear how we can determine "blocked" time.
   901     harTimings.blocked = -1;
   903     // DNS timing information is available only in when the DNS record is not
   904     // cached.
   905     harTimings.dns = timings.STATUS_RESOLVING && timings.STATUS_RESOLVED ?
   906                      timings.STATUS_RESOLVED.last -
   907                      timings.STATUS_RESOLVING.first : -1;
   909     if (timings.STATUS_CONNECTING_TO && timings.STATUS_CONNECTED_TO) {
   910       harTimings.connect = timings.STATUS_CONNECTED_TO.last -
   911                            timings.STATUS_CONNECTING_TO.first;
   912     }
   913     else if (timings.STATUS_SENDING_TO) {
   914       harTimings.connect = timings.STATUS_SENDING_TO.first -
   915                            timings.REQUEST_HEADER.first;
   916     }
   917     else {
   918       harTimings.connect = -1;
   919     }
   921     if ((timings.STATUS_WAITING_FOR || timings.STATUS_RECEIVING_FROM) &&
   922         (timings.STATUS_CONNECTED_TO || timings.STATUS_SENDING_TO)) {
   923       harTimings.send = (timings.STATUS_WAITING_FOR ||
   924                          timings.STATUS_RECEIVING_FROM).first -
   925                         (timings.STATUS_CONNECTED_TO ||
   926                          timings.STATUS_SENDING_TO).last;
   927     }
   928     else {
   929       harTimings.send = -1;
   930     }
   932     if (timings.RESPONSE_START) {
   933       harTimings.wait = timings.RESPONSE_START.first -
   934                         (timings.REQUEST_BODY_SENT ||
   935                          timings.STATUS_SENDING_TO).last;
   936     }
   937     else {
   938       harTimings.wait = -1;
   939     }
   941     if (timings.RESPONSE_START && timings.RESPONSE_COMPLETE) {
   942       harTimings.receive = timings.RESPONSE_COMPLETE.last -
   943                            timings.RESPONSE_START.first;
   944     }
   945     else {
   946       harTimings.receive = -1;
   947     }
   949     let totalTime = 0;
   950     for (let timing in harTimings) {
   951       let time = Math.max(Math.round(harTimings[timing] / 1000), -1);
   952       harTimings[timing] = time;
   953       if (time > -1) {
   954         totalTime += time;
   955       }
   956     }
   958     return {
   959       total: totalTime,
   960       timings: harTimings,
   961     };
   962   },
   964   /**
   965    * Suspend Web Console activity. This is called when all Web Consoles are
   966    * closed.
   967    */
   968   destroy: function NM_destroy()
   969   {
   970     if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
   971       Services.obs.removeObserver(this._httpResponseExaminer,
   972                                   "http-on-examine-response");
   973     }
   975     gActivityDistributor.removeObserver(this);
   977     this.openRequests = {};
   978     this.openResponses = {};
   979     this.owner = null;
   980     this.window = null;
   981     this.topFrame = null;
   982   },
   983 }; // NetworkMonitor.prototype
   986 /**
   987  * The NetworkMonitorChild is used to proxy all of the network activity of the
   988  * child app process from the main process. The child WebConsoleActor creates an
   989  * instance of this object.
   990  *
   991  * Network requests for apps happen in the main process. As such,
   992  * a NetworkMonitor instance is used by the WebappsActor in the main process to
   993  * log the network requests for this child process.
   994  *
   995  * The main process creates NetworkEventActorProxy instances per request. These
   996  * send the data to this object using the nsIMessageManager. Here we proxy the
   997  * data to the WebConsoleActor or to a NetworkEventActor.
   998  *
   999  * @constructor
  1000  * @param number appId
  1001  *        The web appId of the child process.
  1002  * @param nsIMessageManager messageManager
  1003  *        The nsIMessageManager to use to communicate with the parent process.
  1004  * @param string connID
  1005  *        The connection ID to use for send messages to the parent process.
  1006  * @param object owner
  1007  *        The WebConsoleActor that is listening for the network requests.
  1008  */
  1009 function NetworkMonitorChild(appId, messageManager, connID, owner) {
  1010   this.appId = appId;
  1011   this.connID = connID;
  1012   this.owner = owner;
  1013   this._messageManager = messageManager;
  1014   this._onNewEvent = this._onNewEvent.bind(this);
  1015   this._onUpdateEvent = this._onUpdateEvent.bind(this);
  1016   this._netEvents = new Map();
  1018 exports.NetworkMonitorChild = NetworkMonitorChild;
  1020 NetworkMonitorChild.prototype = {
  1021   appId: null,
  1022   owner: null,
  1023   _netEvents: null,
  1024   _saveRequestAndResponseBodies: false,
  1026   get saveRequestAndResponseBodies() {
  1027     return this._saveRequestAndResponseBodies;
  1028   },
  1030   set saveRequestAndResponseBodies(val) {
  1031     this._saveRequestAndResponseBodies = val;
  1033     this._messageManager.sendAsyncMessage("debug:netmonitor:" + this.connID, {
  1034       appId: this.appId,
  1035       action: "setPreferences",
  1036       preferences: {
  1037         saveRequestAndResponseBodies: this._saveRequestAndResponseBodies,
  1038       },
  1039     });
  1040   },
  1042   init: function() {
  1043     let mm = this._messageManager;
  1044     mm.addMessageListener("debug:netmonitor:" + this.connID + ":newEvent",
  1045                           this._onNewEvent);
  1046     mm.addMessageListener("debug:netmonitor:" + this.connID + ":updateEvent",
  1047                           this._onUpdateEvent);
  1048     mm.sendAsyncMessage("debug:netmonitor:" + this.connID, {
  1049       appId: this.appId,
  1050       action: "start",
  1051     });
  1052   },
  1054   _onNewEvent: DevToolsUtils.makeInfallible(function _onNewEvent(msg) {
  1055     let {id, event} = msg.data;
  1056     let actor = this.owner.onNetworkEvent(event);
  1057     this._netEvents.set(id, Cu.getWeakReference(actor));
  1058   }),
  1060   _onUpdateEvent: DevToolsUtils.makeInfallible(function _onUpdateEvent(msg) {
  1061     let {id, method, args} = msg.data;
  1062     let weakActor = this._netEvents.get(id);
  1063     let actor = weakActor ? weakActor.get() : null;
  1064     if (!actor) {
  1065       Cu.reportError("Received debug:netmonitor:updateEvent for unknown event ID: " + id);
  1066       return;
  1068     if (!(method in actor)) {
  1069       Cu.reportError("Received debug:netmonitor:updateEvent unsupported method: " + method);
  1070       return;
  1072     actor[method].apply(actor, args);
  1073   }),
  1075   destroy: function() {
  1076     let mm = this._messageManager;
  1077     mm.removeMessageListener("debug:netmonitor:" + this.connID + ":newEvent",
  1078                              this._onNewEvent);
  1079     mm.removeMessageListener("debug:netmonitor:" + this.connID + ":updateEvent",
  1080                              this._onUpdateEvent);
  1081     mm.sendAsyncMessage("debug:netmonitor:" + this.connID, {
  1082       action: "disconnect",
  1083     });
  1084     this._netEvents.clear();
  1085     this._messageManager = null;
  1086     this.owner = null;
  1087   },
  1088 }; // NetworkMonitorChild.prototype
  1090 /**
  1091  * The NetworkEventActorProxy is used to send network request information from
  1092  * the main process to the child app process. One proxy is used per request.
  1093  * Similarly, one NetworkEventActor in the child app process is used per
  1094  * request. The client receives all network logs from the child actors.
  1096  * The child process has a NetworkMonitorChild instance that is listening for
  1097  * all network logging from the main process. The net monitor shim is used to
  1098  * proxy the data to the WebConsoleActor instance of the child process.
  1100  * @constructor
  1101  * @param nsIMessageManager messageManager
  1102  *        The message manager for the child app process. This is used for
  1103  *        communication with the NetworkMonitorChild instance of the process.
  1104  * @param string connID
  1105  *        The connection ID to use to send messages to the child process.
  1106  */
  1107 function NetworkEventActorProxy(messageManager, connID) {
  1108   this.id = gSequenceId();
  1109   this.connID = connID;
  1110   this.messageManager = messageManager;
  1112 exports.NetworkEventActorProxy = NetworkEventActorProxy;
  1114 NetworkEventActorProxy.methodFactory = function(method) {
  1115   return DevToolsUtils.makeInfallible(function() {
  1116     let args = Array.slice(arguments);
  1117     let mm = this.messageManager;
  1118     mm.sendAsyncMessage("debug:netmonitor:" + this.connID + ":updateEvent", {
  1119       id: this.id,
  1120       method: method,
  1121       args: args,
  1122     });
  1123   }, "NetworkEventActorProxy." + method);
  1124 };
  1126 NetworkEventActorProxy.prototype = {
  1127   /**
  1128    * Initialize the network event. This method sends the network request event
  1129    * to the content process.
  1131    * @param object event
  1132    *        Object describing the network request.
  1133    * @return object
  1134    *         This object.
  1135    */
  1136   init: DevToolsUtils.makeInfallible(function(event)
  1138     let mm = this.messageManager;
  1139     mm.sendAsyncMessage("debug:netmonitor:" + this.connID + ":newEvent", {
  1140       id: this.id,
  1141       event: event,
  1142     });
  1143     return this;
  1144   }),
  1145 };
  1147 (function() {
  1148   // Listeners for new network event data coming from the NetworkMonitor.
  1149   let methods = ["addRequestHeaders", "addRequestCookies", "addRequestPostData",
  1150                  "addResponseStart", "addResponseHeaders", "addResponseCookies",
  1151                  "addResponseContent", "addEventTimings"];
  1152   let factory = NetworkEventActorProxy.methodFactory;
  1153   for (let method of methods) {
  1154     NetworkEventActorProxy.prototype[method] = factory(method);
  1156 })();
  1159 /**
  1160  * The NetworkMonitor manager used by the Webapps actor in the main process.
  1161  * This object uses the message manager to listen for requests from the child
  1162  * process to start/stop the network monitor.
  1164  * @constructor
  1165  * @param nsIDOMElement frame
  1166  *        The browser frame to work with (mozbrowser).
  1167  * @param string id
  1168  *        Instance identifier to use for messages.
  1169  */
  1170 function NetworkMonitorManager(frame, id)
  1172   this.id = id;
  1173   let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
  1174   this.messageManager = mm;
  1175   this.frame = frame;
  1176   this.onNetMonitorMessage = this.onNetMonitorMessage.bind(this);
  1177   this.onNetworkEvent = this.onNetworkEvent.bind(this);
  1179   mm.addMessageListener("debug:netmonitor:" + id, this.onNetMonitorMessage);
  1181 exports.NetworkMonitorManager = NetworkMonitorManager;
  1183 NetworkMonitorManager.prototype = {
  1184   netMonitor: null,
  1185   frame: null,
  1186   messageManager: null,
  1188   /**
  1189    * Handler for "debug:monitor" messages received through the message manager
  1190    * from the content process.
  1192    * @param object msg
  1193    *        Message from the content.
  1194    */
  1195   onNetMonitorMessage: DevToolsUtils.makeInfallible(function _onNetMonitorMessage(msg) {
  1196     let { action, appId } = msg.json;
  1197     // Pipe network monitor data from parent to child via the message manager.
  1198     switch (action) {
  1199       case "start":
  1200         if (!this.netMonitor) {
  1201           this.netMonitor = new NetworkMonitor({
  1202             topFrame: this.frame,
  1203             appId: appId,
  1204           }, this);
  1205           this.netMonitor.init();
  1207         break;
  1209       case "setPreferences": {
  1210         let {preferences} = msg.json;
  1211         for (let key of Object.keys(preferences)) {
  1212           if (key == "saveRequestAndResponseBodies" && this.netMonitor) {
  1213             this.netMonitor.saveRequestAndResponseBodies = preferences[key];
  1216         break;
  1219       case "stop":
  1220         if (this.netMonitor) {
  1221           this.netMonitor.destroy();
  1222           this.netMonitor = null;
  1224         break;
  1226       case "disconnect":
  1227         this.destroy();
  1228         break;
  1230   }),
  1232   /**
  1233    * Handler for new network requests. This method is invoked by the current
  1234    * NetworkMonitor instance.
  1236    * @param object event
  1237    *        Object describing the network request.
  1238    * @return object
  1239    *         A NetworkEventActorProxy instance which is notified when further
  1240    *         data about the request is available.
  1241    */
  1242   onNetworkEvent: DevToolsUtils.makeInfallible(function _onNetworkEvent(event) {
  1243     return new NetworkEventActorProxy(this.messageManager, this.id).init(event);
  1244   }),
  1246   destroy: function()
  1248     if (this.messageManager) {
  1249       this.messageManager.removeMessageListener("debug:netmonitor:" + this.id,
  1250                                                 this.onNetMonitorMessage);
  1252     this.messageManager = null;
  1253     this.filters = null;
  1255     if (this.netMonitor) {
  1256       this.netMonitor.destroy();
  1257       this.netMonitor = null;
  1259   },
  1260 }; // NetworkMonitorManager.prototype
  1263 /**
  1264  * A WebProgressListener that listens for location changes.
  1266  * This progress listener is used to track file loads and other kinds of
  1267  * location changes.
  1269  * @constructor
  1270  * @param object aWindow
  1271  *        The window for which we need to track location changes.
  1272  * @param object aOwner
  1273  *        The listener owner which needs to implement two methods:
  1274  *        - onFileActivity(aFileURI)
  1275  *        - onLocationChange(aState, aTabURI, aPageTitle)
  1276  */
  1277 function ConsoleProgressListener(aWindow, aOwner)
  1279   this.window = aWindow;
  1280   this.owner = aOwner;
  1282 exports.ConsoleProgressListener = ConsoleProgressListener;
  1284 ConsoleProgressListener.prototype = {
  1285   /**
  1286    * Constant used for startMonitor()/stopMonitor() that tells you want to
  1287    * monitor file loads.
  1288    */
  1289   MONITOR_FILE_ACTIVITY: 1,
  1291   /**
  1292    * Constant used for startMonitor()/stopMonitor() that tells you want to
  1293    * monitor page location changes.
  1294    */
  1295   MONITOR_LOCATION_CHANGE: 2,
  1297   /**
  1298    * Tells if you want to monitor file activity.
  1299    * @private
  1300    * @type boolean
  1301    */
  1302   _fileActivity: false,
  1304   /**
  1305    * Tells if you want to monitor location changes.
  1306    * @private
  1307    * @type boolean
  1308    */
  1309   _locationChange: false,
  1311   /**
  1312    * Tells if the console progress listener is initialized or not.
  1313    * @private
  1314    * @type boolean
  1315    */
  1316   _initialized: false,
  1318   _webProgress: null,
  1320   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
  1321                                          Ci.nsISupportsWeakReference]),
  1323   /**
  1324    * Initialize the ConsoleProgressListener.
  1325    * @private
  1326    */
  1327   _init: function CPL__init()
  1329     if (this._initialized) {
  1330       return;
  1333     this._webProgress = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
  1334                         .getInterface(Ci.nsIWebNavigation)
  1335                         .QueryInterface(Ci.nsIWebProgress);
  1336     this._webProgress.addProgressListener(this,
  1337                                           Ci.nsIWebProgress.NOTIFY_STATE_ALL);
  1339     this._initialized = true;
  1340   },
  1342   /**
  1343    * Start a monitor/tracker related to the current nsIWebProgressListener
  1344    * instance.
  1346    * @param number aMonitor
  1347    *        Tells what you want to track. Available constants:
  1348    *        - this.MONITOR_FILE_ACTIVITY
  1349    *          Track file loads.
  1350    *        - this.MONITOR_LOCATION_CHANGE
  1351    *          Track location changes for the top window.
  1352    */
  1353   startMonitor: function CPL_startMonitor(aMonitor)
  1355     switch (aMonitor) {
  1356       case this.MONITOR_FILE_ACTIVITY:
  1357         this._fileActivity = true;
  1358         break;
  1359       case this.MONITOR_LOCATION_CHANGE:
  1360         this._locationChange = true;
  1361         break;
  1362       default:
  1363         throw new Error("ConsoleProgressListener: unknown monitor type " +
  1364                         aMonitor + "!");
  1366     this._init();
  1367   },
  1369   /**
  1370    * Stop a monitor.
  1372    * @param number aMonitor
  1373    *        Tells what you want to stop tracking. See this.startMonitor() for
  1374    *        the list of constants.
  1375    */
  1376   stopMonitor: function CPL_stopMonitor(aMonitor)
  1378     switch (aMonitor) {
  1379       case this.MONITOR_FILE_ACTIVITY:
  1380         this._fileActivity = false;
  1381         break;
  1382       case this.MONITOR_LOCATION_CHANGE:
  1383         this._locationChange = false;
  1384         break;
  1385       default:
  1386         throw new Error("ConsoleProgressListener: unknown monitor type " +
  1387                         aMonitor + "!");
  1390     if (!this._fileActivity && !this._locationChange) {
  1391       this.destroy();
  1393   },
  1395   onStateChange:
  1396   function CPL_onStateChange(aProgress, aRequest, aState, aStatus)
  1398     if (!this.owner) {
  1399       return;
  1402     if (this._fileActivity) {
  1403       this._checkFileActivity(aProgress, aRequest, aState, aStatus);
  1406     if (this._locationChange) {
  1407       this._checkLocationChange(aProgress, aRequest, aState, aStatus);
  1409   },
  1411   /**
  1412    * Check if there is any file load, given the arguments of
  1413    * nsIWebProgressListener.onStateChange. If the state change tells that a file
  1414    * URI has been loaded, then the remote Web Console instance is notified.
  1415    * @private
  1416    */
  1417   _checkFileActivity:
  1418   function CPL__checkFileActivity(aProgress, aRequest, aState, aStatus)
  1420     if (!(aState & Ci.nsIWebProgressListener.STATE_START)) {
  1421       return;
  1424     let uri = null;
  1425     if (aRequest instanceof Ci.imgIRequest) {
  1426       let imgIRequest = aRequest.QueryInterface(Ci.imgIRequest);
  1427       uri = imgIRequest.URI;
  1429     else if (aRequest instanceof Ci.nsIChannel) {
  1430       let nsIChannel = aRequest.QueryInterface(Ci.nsIChannel);
  1431       uri = nsIChannel.URI;
  1434     if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) {
  1435       return;
  1438     this.owner.onFileActivity(uri.spec);
  1439   },
  1441   /**
  1442    * Check if the current window.top location is changing, given the arguments
  1443    * of nsIWebProgressListener.onStateChange. If that is the case, the remote
  1444    * Web Console instance is notified.
  1445    * @private
  1446    */
  1447   _checkLocationChange:
  1448   function CPL__checkLocationChange(aProgress, aRequest, aState, aStatus)
  1450     let isStart = aState & Ci.nsIWebProgressListener.STATE_START;
  1451     let isStop = aState & Ci.nsIWebProgressListener.STATE_STOP;
  1452     let isNetwork = aState & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
  1453     let isWindow = aState & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
  1455     // Skip non-interesting states.
  1456     if (!isNetwork || !isWindow || aProgress.DOMWindow != this.window) {
  1457       return;
  1460     if (isStart && aRequest instanceof Ci.nsIChannel) {
  1461       this.owner.onLocationChange("start", aRequest.URI.spec, "");
  1463     else if (isStop) {
  1464       this.owner.onLocationChange("stop", this.window.location.href,
  1465                                   this.window.document.title);
  1467   },
  1469   onLocationChange: function() {},
  1470   onStatusChange: function() {},
  1471   onProgressChange: function() {},
  1472   onSecurityChange: function() {},
  1474   /**
  1475    * Destroy the ConsoleProgressListener.
  1476    */
  1477   destroy: function CPL_destroy()
  1479     if (!this._initialized) {
  1480       return;
  1483     this._initialized = false;
  1484     this._fileActivity = false;
  1485     this._locationChange = false;
  1487     try {
  1488       this._webProgress.removeProgressListener(this);
  1490     catch (ex) {
  1491       // This can throw during browser shutdown.
  1494     this._webProgress = null;
  1495     this.window = null;
  1496     this.owner = null;
  1497   },
  1498 }; // ConsoleProgressListener.prototype
  1500 function gSequenceId() { return gSequenceId.n++; }
  1501 gSequenceId.n = 1;

mercurial