Backed out 35 changesets (bug 941158, bug 972518, bug 959520, bug 986063, bug 948895...
[gecko.git] / toolkit / devtools / webconsole / utils.js
blobb77bee53faec3baaebabe1eeb58beebd28019ef4
1 /* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 "use strict";
9 const {Cc, Ci, Cu, components} = require("chrome");
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
13 loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper"));
14 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
15 loader.lazyImporter(this, "ConsoleAPIStorage", "resource://gre/modules/ConsoleAPIStorage.jsm");
16 loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
17 loader.lazyImporter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
18 loader.lazyServiceGetter(this, "gActivityDistributor",
19                          "@mozilla.org/network/http-activity-distributor;1",
20                          "nsIHttpActivityDistributor");
22 // TODO: Bug 842672 - toolkit/ imports modules from browser/.
23 // Note that these are only used in JSTermHelpers, see $0 and pprint().
24 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
25 loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
26 loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
27 loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm");
29 // Match the function name from the result of toString() or toSource().
31 // Examples:
32 // (function foobar(a, b) { ...
33 // function foobar2(a) { ...
34 // function() { ...
35 const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
37 // Match the function arguments from the result of toString() or toSource().
38 const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/;
40 let WebConsoleUtils = {
41   /**
42    * Convenience function to unwrap a wrapped object.
43    *
44    * @param aObject the object to unwrap.
45    * @return aObject unwrapped.
46    */
47   unwrap: function WCU_unwrap(aObject)
48   {
49     try {
50       return XPCNativeWrapper.unwrap(aObject);
51     }
52     catch (ex) {
53       return aObject;
54     }
55   },
57   /**
58    * Wrap a string in an nsISupportsString object.
59    *
60    * @param string aString
61    * @return nsISupportsString
62    */
63   supportsString: function WCU_supportsString(aString)
64   {
65     let str = Cc["@mozilla.org/supports-string;1"].
66               createInstance(Ci.nsISupportsString);
67     str.data = aString;
68     return str;
69   },
71   /**
72    * Clone an object.
73    *
74    * @param object aObject
75    *        The object you want cloned.
76    * @param boolean aRecursive
77    *        Tells if you want to dig deeper into the object, to clone
78    *        recursively.
79    * @param function [aFilter]
80    *        Optional, filter function, called for every property. Three
81    *        arguments are passed: key, value and object. Return true if the
82    *        property should be added to the cloned object. Return false to skip
83    *        the property.
84    * @return object
85    *         The cloned object.
86    */
87   cloneObject: function WCU_cloneObject(aObject, aRecursive, aFilter)
88   {
89     if (typeof aObject != "object") {
90       return aObject;
91     }
93     let temp;
95     if (Array.isArray(aObject)) {
96       temp = [];
97       Array.forEach(aObject, function(aValue, aIndex) {
98         if (!aFilter || aFilter(aIndex, aValue, aObject)) {
99           temp.push(aRecursive ? WCU_cloneObject(aValue) : aValue);
100         }
101       });
102     }
103     else {
104       temp = {};
105       for (let key in aObject) {
106         let value = aObject[key];
107         if (aObject.hasOwnProperty(key) &&
108             (!aFilter || aFilter(key, value, aObject))) {
109           temp[key] = aRecursive ? WCU_cloneObject(value) : value;
110         }
111       }
112     }
114     return temp;
115   },
117   /**
118    * Copies certain style attributes from one element to another.
119    *
120    * @param nsIDOMNode aFrom
121    *        The target node.
122    * @param nsIDOMNode aTo
123    *        The destination node.
124    */
125   copyTextStyles: function WCU_copyTextStyles(aFrom, aTo)
126   {
127     let win = aFrom.ownerDocument.defaultView;
128     let style = win.getComputedStyle(aFrom);
129     aTo.style.fontFamily = style.getPropertyCSSValue("font-family").cssText;
130     aTo.style.fontSize = style.getPropertyCSSValue("font-size").cssText;
131     aTo.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText;
132     aTo.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
133   },
135   /**
136    * Gets the ID of the inner window of this DOM window.
137    *
138    * @param nsIDOMWindow aWindow
139    * @return integer
140    *         Inner ID for the given aWindow.
141    */
142   getInnerWindowId: function WCU_getInnerWindowId(aWindow)
143   {
144     return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
145              getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
146   },
148   /**
149    * Recursively gather a list of inner window ids given a
150    * top level window.
151    *
152    * @param nsIDOMWindow aWindow
153    * @return Array
154    *         list of inner window ids.
155    */
156   getInnerWindowIDsForFrames: function WCU_getInnerWindowIDsForFrames(aWindow)
157   {
158     let innerWindowID = this.getInnerWindowId(aWindow);
159     let ids = [innerWindowID];
161     if (aWindow.frames) {
162       for (let i = 0; i < aWindow.frames.length; i++) {
163         let frame = aWindow.frames[i];
164         ids = ids.concat(this.getInnerWindowIDsForFrames(frame));
165       }
166     }
168     return ids;
169   },
172   /**
173    * Gets the ID of the outer window of this DOM window.
174    *
175    * @param nsIDOMWindow aWindow
176    * @return integer
177    *         Outer ID for the given aWindow.
178    */
179   getOuterWindowId: function WCU_getOuterWindowId(aWindow)
180   {
181     return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
182            getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
183   },
185   /**
186    * Abbreviates the given source URL so that it can be displayed flush-right
187    * without being too distracting.
188    *
189    * @param string aSourceURL
190    *        The source URL to shorten.
191    * @return string
192    *         The abbreviated form of the source URL.
193    */
194   abbreviateSourceURL: function WCU_abbreviateSourceURL(aSourceURL)
195   {
196     if (aSourceURL.substr(0, 5) == "data:") {
197       let commaIndex = aSourceURL.indexOf(",");
198       if (commaIndex > -1) {
199         aSourceURL = "data:" + aSourceURL.substring(commaIndex + 1);
200       }
201     }
203     // Remove any query parameters.
204     let hookIndex = aSourceURL.indexOf("?");
205     if (hookIndex > -1) {
206       aSourceURL = aSourceURL.substring(0, hookIndex);
207     }
209     // Remove any hash fragments.
210     let hashIndex = aSourceURL.indexOf("#");
211     if (hashIndex > -1) {
212       aSourceURL = aSourceURL.substring(0, hashIndex);
213     }
215     // Remove a trailing "/".
216     if (aSourceURL[aSourceURL.length - 1] == "/") {
217       aSourceURL = aSourceURL.substring(0, aSourceURL.length - 1);
218     }
220     // Remove all but the last path component.
221     let slashIndex = aSourceURL.lastIndexOf("/");
222     if (slashIndex > -1) {
223       aSourceURL = aSourceURL.substring(slashIndex + 1);
224     }
226     return aSourceURL;
227   },
229   /**
230    * Tells if the given function is native or not.
231    *
232    * @param function aFunction
233    *        The function you want to check if it is native or not.
234    * @return boolean
235    *         True if the given function is native, false otherwise.
236    */
237   isNativeFunction: function WCU_isNativeFunction(aFunction)
238   {
239     return typeof aFunction == "function" && !("prototype" in aFunction);
240   },
242   /**
243    * Tells if the given property of the provided object is a non-native getter or
244    * not.
245    *
246    * @param object aObject
247    *        The object that contains the property.
248    * @param string aProp
249    *        The property you want to check if it is a getter or not.
250    * @return boolean
251    *         True if the given property is a getter, false otherwise.
252    */
253   isNonNativeGetter: function WCU_isNonNativeGetter(aObject, aProp)
254   {
255     if (typeof aObject != "object") {
256       return false;
257     }
258     let desc = this.getPropertyDescriptor(aObject, aProp);
259     return desc && desc.get && !this.isNativeFunction(desc.get);
260   },
262   /**
263    * Get the property descriptor for the given object.
264    *
265    * @param object aObject
266    *        The object that contains the property.
267    * @param string aProp
268    *        The property you want to get the descriptor for.
269    * @return object
270    *         Property descriptor.
271    */
272   getPropertyDescriptor: function WCU_getPropertyDescriptor(aObject, aProp)
273   {
274     let desc = null;
275     while (aObject) {
276       try {
277         if ((desc = Object.getOwnPropertyDescriptor(aObject, aProp))) {
278           break;
279         }
280       }
281       catch (ex if (ex.name == "NS_ERROR_XPC_BAD_CONVERT_JS" ||
282                     ex.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO" ||
283                     ex.name == "TypeError")) {
284         // Native getters throw here. See bug 520882.
285         // null throws TypeError.
286       }
287       try {
288         aObject = Object.getPrototypeOf(aObject);
289       }
290       catch (ex if (ex.name == "TypeError")) {
291         return desc;
292       }
293     }
294     return desc;
295   },
297   /**
298    * Sort function for object properties.
299    *
300    * @param object a
301    *        Property descriptor.
302    * @param object b
303    *        Property descriptor.
304    * @return integer
305    *         -1 if a.name < b.name,
306    *         1 if a.name > b.name,
307    *         0 otherwise.
308    */
309   propertiesSort: function WCU_propertiesSort(a, b)
310   {
311     // Convert the pair.name to a number for later sorting.
312     let aNumber = parseFloat(a.name);
313     let bNumber = parseFloat(b.name);
315     // Sort numbers.
316     if (!isNaN(aNumber) && isNaN(bNumber)) {
317       return -1;
318     }
319     else if (isNaN(aNumber) && !isNaN(bNumber)) {
320       return 1;
321     }
322     else if (!isNaN(aNumber) && !isNaN(bNumber)) {
323       return aNumber - bNumber;
324     }
325     // Sort string.
326     else if (a.name < b.name) {
327       return -1;
328     }
329     else if (a.name > b.name) {
330       return 1;
331     }
332     else {
333       return 0;
334     }
335   },
337   /**
338    * Create a grip for the given value. If the value is an object,
339    * an object wrapper will be created.
340    *
341    * @param mixed aValue
342    *        The value you want to create a grip for, before sending it to the
343    *        client.
344    * @param function aObjectWrapper
345    *        If the value is an object then the aObjectWrapper function is
346    *        invoked to give us an object grip. See this.getObjectGrip().
347    * @return mixed
348    *         The value grip.
349    */
350   createValueGrip: function WCU_createValueGrip(aValue, aObjectWrapper)
351   {
352     switch (typeof aValue) {
353       case "boolean":
354         return aValue;
355       case "string":
356         return aObjectWrapper(aValue);
357       case "number":
358         if (aValue === Infinity) {
359           return { type: "Infinity" };
360         }
361         else if (aValue === -Infinity) {
362           return { type: "-Infinity" };
363         }
364         else if (Number.isNaN(aValue)) {
365           return { type: "NaN" };
366         }
367         else if (!aValue && 1 / aValue === -Infinity) {
368           return { type: "-0" };
369         }
370         return aValue;
371       case "undefined":
372         return { type: "undefined" };
373       case "object":
374         if (aValue === null) {
375           return { type: "null" };
376         }
377       case "function":
378         return aObjectWrapper(aValue);
379       default:
380         Cu.reportError("Failed to provide a grip for value of " + typeof aValue
381                        + ": " + aValue);
382         return null;
383     }
384   },
386   /**
387    * Check if the given object is an iterator or a generator.
388    *
389    * @param object aObject
390    *        The object you want to check.
391    * @return boolean
392    *         True if the given object is an iterator or a generator, otherwise
393    *         false is returned.
394    */
395   isIteratorOrGenerator: function WCU_isIteratorOrGenerator(aObject)
396   {
397     if (aObject === null) {
398       return false;
399     }
401     if (typeof aObject == "object") {
402       if (typeof aObject.__iterator__ == "function" ||
403           aObject.constructor && aObject.constructor.name == "Iterator") {
404         return true;
405       }
407       try {
408         let str = aObject.toString();
409         if (typeof aObject.next == "function" &&
410             str.indexOf("[object Generator") == 0) {
411           return true;
412         }
413       }
414       catch (ex) {
415         // window.history.next throws in the typeof check above.
416         return false;
417       }
418     }
420     return false;
421   },
423   /**
424    * Determine if the given request mixes HTTP with HTTPS content.
425    *
426    * @param string aRequest
427    *        Location of the requested content.
428    * @param string aLocation
429    *        Location of the current page.
430    * @return boolean
431    *         True if the content is mixed, false if not.
432    */
433   isMixedHTTPSRequest: function WCU_isMixedHTTPSRequest(aRequest, aLocation)
434   {
435     try {
436       let requestURI = Services.io.newURI(aRequest, null, null);
437       let contentURI = Services.io.newURI(aLocation, null, null);
438       return (contentURI.scheme == "https" && requestURI.scheme != "https");
439     }
440     catch (ex) {
441       return false;
442     }
443   },
445   /**
446    * Helper function to deduce the name of the provided function.
447    *
448    * @param funtion aFunction
449    *        The function whose name will be returned.
450    * @return string
451    *         Function name.
452    */
453   getFunctionName: function WCF_getFunctionName(aFunction)
454   {
455     let name = null;
456     if (aFunction.name) {
457       name = aFunction.name;
458     }
459     else {
460       let desc;
461       try {
462         desc = aFunction.getOwnPropertyDescriptor("displayName");
463       }
464       catch (ex) { }
465       if (desc && typeof desc.value == "string") {
466         name = desc.value;
467       }
468     }
469     if (!name) {
470       try {
471         let str = (aFunction.toString() || aFunction.toSource()) + "";
472         name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
473       }
474       catch (ex) { }
475     }
476     return name;
477   },
479   /**
480    * Get the object class name. For example, the |window| object has the Window
481    * class name (based on [object Window]).
482    *
483    * @param object aObject
484    *        The object you want to get the class name for.
485    * @return string
486    *         The object class name.
487    */
488   getObjectClassName: function WCU_getObjectClassName(aObject)
489   {
490     if (aObject === null) {
491       return "null";
492     }
493     if (aObject === undefined) {
494       return "undefined";
495     }
497     let type = typeof aObject;
498     if (type != "object") {
499       // Grip class names should start with an uppercase letter.
500       return type.charAt(0).toUpperCase() + type.substr(1);
501     }
503     let className;
505     try {
506       className = ((aObject + "").match(/^\[object (\S+)\]$/) || [])[1];
507       if (!className) {
508         className = ((aObject.constructor + "").match(/^\[object (\S+)\]$/) || [])[1];
509       }
510       if (!className && typeof aObject.constructor == "function") {
511         className = this.getFunctionName(aObject.constructor);
512       }
513     }
514     catch (ex) { }
516     return className;
517   },
519   /**
520    * Check if the given value is a grip with an actor.
521    *
522    * @param mixed aGrip
523    *        Value you want to check if it is a grip with an actor.
524    * @return boolean
525    *         True if the given value is a grip with an actor.
526    */
527   isActorGrip: function WCU_isActorGrip(aGrip)
528   {
529     return aGrip && typeof(aGrip) == "object" && aGrip.actor;
530   },
532 exports.Utils = WebConsoleUtils;
534 //////////////////////////////////////////////////////////////////////////
535 // Localization
536 //////////////////////////////////////////////////////////////////////////
538 WebConsoleUtils.l10n = function WCU_l10n(aBundleURI)
540   this._bundleUri = aBundleURI;
543 WebConsoleUtils.l10n.prototype = {
544   _stringBundle: null,
546   get stringBundle()
547   {
548     if (!this._stringBundle) {
549       this._stringBundle = Services.strings.createBundle(this._bundleUri);
550     }
551     return this._stringBundle;
552   },
554   /**
555    * Generates a formatted timestamp string for displaying in console messages.
556    *
557    * @param integer [aMilliseconds]
558    *        Optional, allows you to specify the timestamp in milliseconds since
559    *        the UNIX epoch.
560    * @return string
561    *         The timestamp formatted for display.
562    */
563   timestampString: function WCU_l10n_timestampString(aMilliseconds)
564   {
565     let d = new Date(aMilliseconds ? aMilliseconds : null);
566     let hours = d.getHours(), minutes = d.getMinutes();
567     let seconds = d.getSeconds(), milliseconds = d.getMilliseconds();
568     let parameters = [hours, minutes, seconds, milliseconds];
569     return this.getFormatStr("timestampFormat", parameters);
570   },
572   /**
573    * Retrieve a localized string.
574    *
575    * @param string aName
576    *        The string name you want from the Web Console string bundle.
577    * @return string
578    *         The localized string.
579    */
580   getStr: function WCU_l10n_getStr(aName)
581   {
582     let result;
583     try {
584       result = this.stringBundle.GetStringFromName(aName);
585     }
586     catch (ex) {
587       Cu.reportError("Failed to get string: " + aName);
588       throw ex;
589     }
590     return result;
591   },
593   /**
594    * Retrieve a localized string formatted with values coming from the given
595    * array.
596    *
597    * @param string aName
598    *        The string name you want from the Web Console string bundle.
599    * @param array aArray
600    *        The array of values you want in the formatted string.
601    * @return string
602    *         The formatted local string.
603    */
604   getFormatStr: function WCU_l10n_getFormatStr(aName, aArray)
605   {
606     let result;
607     try {
608       result = this.stringBundle.formatStringFromName(aName, aArray, aArray.length);
609     }
610     catch (ex) {
611       Cu.reportError("Failed to format string: " + aName);
612       throw ex;
613     }
614     return result;
615   },
619 //////////////////////////////////////////////////////////////////////////
620 // JS Completer
621 //////////////////////////////////////////////////////////////////////////
623 (function _JSPP(WCU) {
624 const STATE_NORMAL = 0;
625 const STATE_QUOTE = 2;
626 const STATE_DQUOTE = 3;
628 const OPEN_BODY = "{[(".split("");
629 const CLOSE_BODY = "}])".split("");
630 const OPEN_CLOSE_BODY = {
631   "{": "}",
632   "[": "]",
633   "(": ")",
636 const MAX_COMPLETIONS = 1500;
639  * Analyses a given string to find the last statement that is interesting for
640  * later completion.
642  * @param   string aStr
643  *          A string to analyse.
645  * @returns object
646  *          If there was an error in the string detected, then a object like
648  *            { err: "ErrorMesssage" }
650  *          is returned, otherwise a object like
652  *            {
653  *              state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
654  *              startPos: index of where the last statement begins
655  *            }
656  */
657 function findCompletionBeginning(aStr)
659   let bodyStack = [];
661   let state = STATE_NORMAL;
662   let start = 0;
663   let c;
664   for (let i = 0; i < aStr.length; i++) {
665     c = aStr[i];
667     switch (state) {
668       // Normal JS state.
669       case STATE_NORMAL:
670         if (c == '"') {
671           state = STATE_DQUOTE;
672         }
673         else if (c == "'") {
674           state = STATE_QUOTE;
675         }
676         else if (c == ";") {
677           start = i + 1;
678         }
679         else if (c == " ") {
680           start = i + 1;
681         }
682         else if (OPEN_BODY.indexOf(c) != -1) {
683           bodyStack.push({
684             token: c,
685             start: start
686           });
687           start = i + 1;
688         }
689         else if (CLOSE_BODY.indexOf(c) != -1) {
690           var last = bodyStack.pop();
691           if (!last || OPEN_CLOSE_BODY[last.token] != c) {
692             return {
693               err: "syntax error"
694             };
695           }
696           if (c == "}") {
697             start = i + 1;
698           }
699           else {
700             start = last.start;
701           }
702         }
703         break;
705       // Double quote state > " <
706       case STATE_DQUOTE:
707         if (c == "\\") {
708           i++;
709         }
710         else if (c == "\n") {
711           return {
712             err: "unterminated string literal"
713           };
714         }
715         else if (c == '"') {
716           state = STATE_NORMAL;
717         }
718         break;
720       // Single quote state > ' <
721       case STATE_QUOTE:
722         if (c == "\\") {
723           i++;
724         }
725         else if (c == "\n") {
726           return {
727             err: "unterminated string literal"
728           };
729         }
730         else if (c == "'") {
731           state = STATE_NORMAL;
732         }
733         break;
734     }
735   }
737   return {
738     state: state,
739     startPos: start
740   };
744  * Provides a list of properties, that are possible matches based on the passed
745  * Debugger.Environment/Debugger.Object and inputValue.
747  * @param object aDbgObject
748  *        When the debugger is not paused this Debugger.Object wraps the scope for autocompletion.
749  *        It is null if the debugger is paused.
750  * @param object anEnvironment
751  *        When the debugger is paused this Debugger.Environment is the scope for autocompletion.
752  *        It is null if the debugger is not paused.
753  * @param string aInputValue
754  *        Value that should be completed.
755  * @param number [aCursor=aInputValue.length]
756  *        Optional offset in the input where the cursor is located. If this is
757  *        omitted then the cursor is assumed to be at the end of the input
758  *        value.
759  * @returns null or object
760  *          If no completion valued could be computed, null is returned,
761  *          otherwise a object with the following form is returned:
762  *            {
763  *              matches: [ string, string, string ],
764  *              matchProp: Last part of the inputValue that was used to find
765  *                         the matches-strings.
766  *            }
767  */
768 function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor)
770   if (aCursor === undefined) {
771     aCursor = aInputValue.length;
772   }
774   let inputValue = aInputValue.substring(0, aCursor);
776   // Analyse the inputValue and find the beginning of the last part that
777   // should be completed.
778   let beginning = findCompletionBeginning(inputValue);
780   // There was an error analysing the string.
781   if (beginning.err) {
782     return null;
783   }
785   // If the current state is not STATE_NORMAL, then we are inside of an string
786   // which means that no completion is possible.
787   if (beginning.state != STATE_NORMAL) {
788     return null;
789   }
791   let completionPart = inputValue.substring(beginning.startPos);
793   // Don't complete on just an empty string.
794   if (completionPart.trim() == "") {
795     return null;
796   }
798   let lastDot = completionPart.lastIndexOf(".");
799   if (lastDot > 0 &&
800       (completionPart[0] == "'" || completionPart[0] == '"') &&
801       completionPart[lastDot - 1] == completionPart[0]) {
802     // We are completing a string literal.
803     let matchProp = completionPart.slice(lastDot + 1);
804     return getMatchedProps(String.prototype, matchProp);
805   }
807   // We are completing a variable / a property lookup.
808   let properties = completionPart.split(".");
809   let matchProp = properties.pop().trimLeft();
810   let obj = aDbgObject;
812   // The first property must be found in the environment if the debugger is
813   // paused.
814   if (anEnvironment) {
815     if (properties.length == 0) {
816       return getMatchedPropsInEnvironment(anEnvironment, matchProp);
817     }
818     obj = getVariableInEnvironment(anEnvironment, properties.shift());
819   }
821   if (!isObjectUsable(obj)) {
822     return null;
823   }
825   // We get the rest of the properties recursively starting from the Debugger.Object
826   // that wraps the first property
827   for (let prop of properties) {
828     prop = prop.trim();
829     if (!prop) {
830       return null;
831     }
833     obj = DevToolsUtils.getProperty(obj, prop);
835     if (!isObjectUsable(obj)) {
836       return null;
837     }
838   }
840   // If the final property is a primitive
841   if (typeof obj != "object") {
842     return getMatchedProps(obj, matchProp);
843   }
845   return getMatchedPropsInDbgObject(obj, matchProp);
849  * Check if the given Debugger.Object can be used for autocomplete.
851  * @param Debugger.Object aObject
852  *        The Debugger.Object to check.
853  * @return boolean
854  *         True if further inspection into the object is possible, or false
855  *         otherwise.
856  */
857 function isObjectUsable(aObject)
859   if (aObject == null) {
860     return false;
861   }
863   if (typeof aObject == "object" && aObject.class == "DeadObject") {
864     return false;
865   }
867   return true;
871  * @see getExactMatch_impl()
872  */
873 function getVariableInEnvironment(anEnvironment, aName)
875   return getExactMatch_impl(anEnvironment, aName, DebuggerEnvironmentSupport);
879  * @see getMatchedProps_impl()
880  */
881 function getMatchedPropsInEnvironment(anEnvironment, aMatch)
883   return getMatchedProps_impl(anEnvironment, aMatch, DebuggerEnvironmentSupport);
887  * @see getMatchedProps_impl()
888  */
889 function getMatchedPropsInDbgObject(aDbgObject, aMatch)
891   return getMatchedProps_impl(aDbgObject, aMatch, DebuggerObjectSupport);
895  * @see getMatchedProps_impl()
896  */
897 function getMatchedProps(aObj, aMatch)
899   if (typeof aObj != "object") {
900     aObj = aObj.constructor.prototype;
901   }
902   return getMatchedProps_impl(aObj, aMatch, JSObjectSupport);
906  * Get all properties in the given object (and its parent prototype chain) that
907  * match a given prefix.
909  * @param mixed aObj
910  *        Object whose properties we want to filter.
911  * @param string aMatch
912  *        Filter for properties that match this string.
913  * @return object
914  *         Object that contains the matchProp and the list of names.
915  */
916 function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties})
918   let matches = new Set();
920   // We need to go up the prototype chain.
921   let iter = chainIterator(aObj);
922   for (let obj of iter) {
923     let props = getProperties(obj);
924     for (let prop of props) {
925       if (prop.indexOf(aMatch) != 0) {
926         continue;
927       }
929       // If it is an array index, we can't take it.
930       // This uses a trick: converting a string to a number yields NaN if
931       // the operation failed, and NaN is not equal to itself.
932       if (+prop != +prop) {
933         matches.add(prop);
934       }
936       if (matches.size > MAX_COMPLETIONS) {
937         break;
938       }
939     }
941     if (matches.size > MAX_COMPLETIONS) {
942       break;
943     }
944   }
946   return {
947     matchProp: aMatch,
948     matches: [...matches],
949   };
953  * Returns a property value based on its name from the given object, by
954  * recursively checking the object's prototype.
956  * @param object aObj
957  *        An object to look the property into.
958  * @param string aName
959  *        The property that is looked up.
960  * @returns object|undefined
961  *        A Debugger.Object if the property exists in the object's prototype
962  *        chain, undefined otherwise.
963  */
964 function getExactMatch_impl(aObj, aName, {chainIterator, getProperty})
966   // We need to go up the prototype chain.
967   let iter = chainIterator(aObj);
968   for (let obj of iter) {
969     let prop = getProperty(obj, aName, aObj);
970     if (prop) {
971       return prop.value;
972     }
973   }
974   return undefined;
978 let JSObjectSupport = {
979   chainIterator: function(aObj)
980   {
981     while (aObj) {
982       yield aObj;
983       aObj = Object.getPrototypeOf(aObj);
984     }
985   },
987   getProperties: function(aObj)
988   {
989     return Object.getOwnPropertyNames(aObj);
990   },
992   getProperty: function()
993   {
994     // getProperty is unsafe with raw JS objects.
995     throw "Unimplemented!";
996   },
999 let DebuggerObjectSupport = {
1000   chainIterator: function(aObj)
1001   {
1002     while (aObj) {
1003       yield aObj;
1004       aObj = aObj.proto;
1005     }
1006   },
1008   getProperties: function(aObj)
1009   {
1010     return aObj.getOwnPropertyNames();
1011   },
1013   getProperty: function(aObj, aName, aRootObj)
1014   {
1015     // This is left unimplemented in favor to DevToolsUtils.getProperty().
1016     throw "Unimplemented!";
1017   },
1020 let DebuggerEnvironmentSupport = {
1021   chainIterator: function(aObj)
1022   {
1023     while (aObj) {
1024       yield aObj;
1025       aObj = aObj.parent;
1026     }
1027   },
1029   getProperties: function(aObj)
1030   {
1031     return aObj.names();
1032   },
1034   getProperty: function(aObj, aName)
1035   {
1036     // TODO: we should use getVariableDescriptor() here - bug 725815.
1037     let result = aObj.getVariable(aName);
1038     return result === undefined ? null : { value: result };
1039   },
1043 exports.JSPropertyProvider = JSPropertyProvider;
1044 })(WebConsoleUtils);
1046 ///////////////////////////////////////////////////////////////////////////////
1047 // The page errors listener
1048 ///////////////////////////////////////////////////////////////////////////////
1051  * The nsIConsoleService listener. This is used to send all of the console
1052  * messages (JavaScript, CSS and more) to the remote Web Console instance.
1054  * @constructor
1055  * @param nsIDOMWindow [aWindow]
1056  *        Optional - the window object for which we are created. This is used
1057  *        for filtering out messages that belong to other windows.
1058  * @param object aListener
1059  *        The listener object must have one method:
1060  *        - onConsoleServiceMessage(). This method is invoked with one argument,
1061  *        the nsIConsoleMessage, whenever a relevant message is received.
1062  */
1063 function ConsoleServiceListener(aWindow, aListener)
1065   this.window = aWindow;
1066   this.listener = aListener;
1068 exports.ConsoleServiceListener = ConsoleServiceListener;
1070 ConsoleServiceListener.prototype =
1072   QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
1074   /**
1075    * The content window for which we listen to page errors.
1076    * @type nsIDOMWindow
1077    */
1078   window: null,
1080   /**
1081    * The listener object which is notified of messages from the console service.
1082    * @type object
1083    */
1084   listener: null,
1086   /**
1087    * Initialize the nsIConsoleService listener.
1088    */
1089   init: function CSL_init()
1090   {
1091     Services.console.registerListener(this);
1092   },
1094   /**
1095    * The nsIConsoleService observer. This method takes all the script error
1096    * messages belonging to the current window and sends them to the remote Web
1097    * Console instance.
1098    *
1099    * @param nsIConsoleMessage aMessage
1100    *        The message object coming from the nsIConsoleService.
1101    */
1102   observe: function CSL_observe(aMessage)
1103   {
1104     if (!this.listener) {
1105       return;
1106     }
1108     if (this.window) {
1109       if (!(aMessage instanceof Ci.nsIScriptError) ||
1110           !aMessage.outerWindowID ||
1111           !this.isCategoryAllowed(aMessage.category)) {
1112         return;
1113       }
1115       let errorWindow = Services.wm.getOuterWindowWithId(aMessage.outerWindowID);
1116       if (!errorWindow || errorWindow.top != this.window) {
1117         return;
1118       }
1119     }
1121     this.listener.onConsoleServiceMessage(aMessage);
1122   },
1124   /**
1125    * Check if the given message category is allowed to be tracked or not.
1126    * We ignore chrome-originating errors as we only care about content.
1127    *
1128    * @param string aCategory
1129    *        The message category you want to check.
1130    * @return boolean
1131    *         True if the category is allowed to be logged, false otherwise.
1132    */
1133   isCategoryAllowed: function CSL_isCategoryAllowed(aCategory)
1134   {
1135     if (!aCategory) {
1136       return false;
1137     }
1139     switch (aCategory) {
1140       case "XPConnect JavaScript":
1141       case "component javascript":
1142       case "chrome javascript":
1143       case "chrome registration":
1144       case "XBL":
1145       case "XBL Prototype Handler":
1146       case "XBL Content Sink":
1147       case "xbl javascript":
1148         return false;
1149     }
1151     return true;
1152   },
1154   /**
1155    * Get the cached page errors for the current inner window and its (i)frames.
1156    *
1157    * @param boolean [aIncludePrivate=false]
1158    *        Tells if you want to also retrieve messages coming from private
1159    *        windows. Defaults to false.
1160    * @return array
1161    *         The array of cached messages. Each element is an nsIScriptError or
1162    *         an nsIConsoleMessage
1163    */
1164   getCachedMessages: function CSL_getCachedMessages(aIncludePrivate = false)
1165   {
1166     let errors = Services.console.getMessageArray() || [];
1168     // if !this.window, we're in a browser console. Still need to filter
1169     // private messages.
1170     if (!this.window) {
1171       return errors.filter((aError) => {
1172         if (aError instanceof Ci.nsIScriptError) {
1173           if (!aIncludePrivate && aError.isFromPrivateWindow) {
1174             return false;
1175           }
1176         }
1178         return true;
1179       });
1180     }
1182     let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
1184     return errors.filter((aError) => {
1185       if (aError instanceof Ci.nsIScriptError) {
1186         if (!aIncludePrivate && aError.isFromPrivateWindow) {
1187           return false;
1188         }
1189         if (ids &&
1190             (ids.indexOf(aError.innerWindowID) == -1 ||
1191              !this.isCategoryAllowed(aError.category))) {
1192           return false;
1193         }
1194       }
1195       else if (ids && ids[0]) {
1196         // If this is not an nsIScriptError and we need to do window-based
1197         // filtering we skip this message.
1198         return false;
1199       }
1201       return true;
1202     });
1203   },
1205   /**
1206    * Remove the nsIConsoleService listener.
1207    */
1208   destroy: function CSL_destroy()
1209   {
1210     Services.console.unregisterListener(this);
1211     this.listener = this.window = null;
1212   },
1216 ///////////////////////////////////////////////////////////////////////////////
1217 // The window.console API observer
1218 ///////////////////////////////////////////////////////////////////////////////
1221  * The window.console API observer. This allows the window.console API messages
1222  * to be sent to the remote Web Console instance.
1224  * @constructor
1225  * @param nsIDOMWindow aWindow
1226  *        Optional - the window object for which we are created. This is used
1227  *        for filtering out messages that belong to other windows.
1228  * @param object aOwner
1229  *        The owner object must have the following methods:
1230  *        - onConsoleAPICall(). This method is invoked with one argument, the
1231  *        Console API message that comes from the observer service, whenever
1232  *        a relevant console API call is received.
1233  */
1234 function ConsoleAPIListener(aWindow, aOwner)
1236   this.window = aWindow;
1237   this.owner = aOwner;
1239 exports.ConsoleAPIListener = ConsoleAPIListener;
1241 ConsoleAPIListener.prototype =
1243   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
1245   /**
1246    * The content window for which we listen to window.console API calls.
1247    * @type nsIDOMWindow
1248    */
1249   window: null,
1251   /**
1252    * The owner object which is notified of window.console API calls. It must
1253    * have a onConsoleAPICall method which is invoked with one argument: the
1254    * console API call object that comes from the observer service.
1255    *
1256    * @type object
1257    * @see WebConsoleActor
1258    */
1259   owner: null,
1261   /**
1262    * Initialize the window.console API observer.
1263    */
1264   init: function CAL_init()
1265   {
1266     // Note that the observer is process-wide. We will filter the messages as
1267     // needed, see CAL_observe().
1268     Services.obs.addObserver(this, "console-api-log-event", false);
1269   },
1271   /**
1272    * The console API message observer. When messages are received from the
1273    * observer service we forward them to the remote Web Console instance.
1274    *
1275    * @param object aMessage
1276    *        The message object receives from the observer service.
1277    * @param string aTopic
1278    *        The message topic received from the observer service.
1279    */
1280   observe: function CAL_observe(aMessage, aTopic)
1281   {
1282     if (!this.owner) {
1283       return;
1284     }
1286     let apiMessage = aMessage.wrappedJSObject;
1287     if (this.window) {
1288       let msgWindow = Services.wm.getOuterWindowWithId(apiMessage.ID);
1289       if (!msgWindow || msgWindow.top != this.window) {
1290         // Not the same window!
1291         return;
1292       }
1293     }
1295     this.owner.onConsoleAPICall(apiMessage);
1296   },
1298   /**
1299    * Get the cached messages for the current inner window and its (i)frames.
1300    *
1301    * @param boolean [aIncludePrivate=false]
1302    *        Tells if you want to also retrieve messages coming from private
1303    *        windows. Defaults to false.
1304    * @return array
1305    *         The array of cached messages.
1306    */
1307   getCachedMessages: function CAL_getCachedMessages(aIncludePrivate = false)
1308   {
1309     let messages = [];
1311     // if !this.window, we're in a browser console. Retrieve all events
1312     // for filtering based on privacy.
1313     if (!this.window) {
1314       messages = ConsoleAPIStorage.getEvents();
1315     } else {
1316       let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
1317       ids.forEach((id) => {
1318         messages = messages.concat(ConsoleAPIStorage.getEvents(id));
1319       });
1320     }
1322     if (aIncludePrivate) {
1323       return messages;
1324     }
1326     return messages.filter((m) => !m.private);
1327   },
1329   /**
1330    * Destroy the console API listener.
1331    */
1332   destroy: function CAL_destroy()
1333   {
1334     Services.obs.removeObserver(this, "console-api-log-event");
1335     this.window = this.owner = null;
1336   },
1342  * JSTerm helper functions.
1344  * Defines a set of functions ("helper functions") that are available from the
1345  * Web Console but not from the web page.
1347  * A list of helper functions used by Firebug can be found here:
1348  *   http://getfirebug.com/wiki/index.php/Command_Line_API
1350  * @param object aOwner
1351  *        The owning object.
1352  */
1353 function JSTermHelpers(aOwner)
1355   /**
1356    * Find a node by ID.
1357    *
1358    * @param string aId
1359    *        The ID of the element you want.
1360    * @return nsIDOMNode or null
1361    *         The result of calling document.querySelector(aSelector).
1362    */
1363   aOwner.sandbox.$ = function JSTH_$(aSelector)
1364   {
1365     return aOwner.window.document.querySelector(aSelector);
1366   };
1368   /**
1369    * Find the nodes matching a CSS selector.
1370    *
1371    * @param string aSelector
1372    *        A string that is passed to window.document.querySelectorAll.
1373    * @return nsIDOMNodeList
1374    *         Returns the result of document.querySelectorAll(aSelector).
1375    */
1376   aOwner.sandbox.$$ = function JSTH_$$(aSelector)
1377   {
1378     return aOwner.window.document.querySelectorAll(aSelector);
1379   };
1381   /**
1382    * Runs an xPath query and returns all matched nodes.
1383    *
1384    * @param string aXPath
1385    *        xPath search query to execute.
1386    * @param [optional] nsIDOMNode aContext
1387    *        Context to run the xPath query on. Uses window.document if not set.
1388    * @return array of nsIDOMNode
1389    */
1390   aOwner.sandbox.$x = function JSTH_$x(aXPath, aContext)
1391   {
1392     let nodes = new aOwner.window.wrappedJSObject.Array();
1393     let doc = aOwner.window.document;
1394     aContext = aContext || doc;
1396     let results = doc.evaluate(aXPath, aContext, null,
1397                                Ci.nsIDOMXPathResult.ANY_TYPE, null);
1398     let node;
1399     while ((node = results.iterateNext())) {
1400       nodes.push(node);
1401     }
1403     return nodes;
1404   };
1406   /**
1407    * Returns the currently selected object in the highlighter.
1408    *
1409    * TODO: this implementation crosses the client/server boundaries! This is not
1410    * usable within a remote browser. To implement this feature correctly we need
1411    * support for remote inspection capabilities within the Inspector as well.
1412    * See bug 787975.
1413    *
1414    * @return nsIDOMElement|null
1415    *         The DOM element currently selected in the highlighter.
1416    */
1417    Object.defineProperty(aOwner.sandbox, "$0", {
1418     get: function() {
1419       let window = aOwner.chromeWindow();
1420       if (!window) {
1421         return null;
1422       }
1424       let target = null;
1425       try {
1426         target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab);
1427       }
1428       catch (ex) {
1429         // If we report this exception the user will get it in the Browser
1430         // Console every time when she evaluates any string.
1431       }
1433       if (!target) {
1434         return null;
1435       }
1437       let toolbox = gDevTools.getToolbox(target);
1438       let panel = toolbox ? toolbox.getPanel("inspector") : null;
1439       let node = panel ? panel.selection.node : null;
1441       return node ? aOwner.makeDebuggeeValue(node) : null;
1442     },
1443     enumerable: true,
1444     configurable: false
1445   });
1447   /**
1448    * Clears the output of the JSTerm.
1449    */
1450   aOwner.sandbox.clear = function JSTH_clear()
1451   {
1452     aOwner.helperResult = {
1453       type: "clearOutput",
1454     };
1455   };
1457   /**
1458    * Returns the result of Object.keys(aObject).
1459    *
1460    * @param object aObject
1461    *        Object to return the property names from.
1462    * @return array of strings
1463    */
1464   aOwner.sandbox.keys = function JSTH_keys(aObject)
1465   {
1466     return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject));
1467   };
1469   /**
1470    * Returns the values of all properties on aObject.
1471    *
1472    * @param object aObject
1473    *        Object to display the values from.
1474    * @return array of string
1475    */
1476   aOwner.sandbox.values = function JSTH_values(aObject)
1477   {
1478     let arrValues = new aOwner.window.wrappedJSObject.Array();
1479     let obj = WebConsoleUtils.unwrap(aObject);
1481     for (let prop in obj) {
1482       arrValues.push(obj[prop]);
1483     }
1485     return arrValues;
1486   };
1488   /**
1489    * Opens a help window in MDN.
1490    */
1491   aOwner.sandbox.help = function JSTH_help()
1492   {
1493     aOwner.helperResult = { type: "help" };
1494   };
1496   /**
1497    * Inspects the passed aObject. This is done by opening the PropertyPanel.
1498    *
1499    * @param object aObject
1500    *        Object to inspect.
1501    */
1502   aOwner.sandbox.inspect = function JSTH_inspect(aObject)
1503   {
1504     let dbgObj = aOwner.makeDebuggeeValue(aObject);
1505     let grip = aOwner.createValueGrip(dbgObj);
1506     aOwner.helperResult = {
1507       type: "inspectObject",
1508       input: aOwner.evalInput,
1509       object: grip,
1510     };
1511   };
1513   /**
1514    * Prints aObject to the output.
1515    *
1516    * @param object aObject
1517    *        Object to print to the output.
1518    * @return string
1519    */
1520   aOwner.sandbox.pprint = function JSTH_pprint(aObject)
1521   {
1522     if (aObject === null || aObject === undefined || aObject === true ||
1523         aObject === false) {
1524       aOwner.helperResult = {
1525         type: "error",
1526         message: "helperFuncUnsupportedTypeError",
1527       };
1528       return null;
1529     }
1531     aOwner.helperResult = { rawOutput: true };
1533     if (typeof aObject == "function") {
1534       return aObject + "\n";
1535     }
1537     let output = [];
1539     let obj = WebConsoleUtils.unwrap(aObject);
1540     for (let name in obj) {
1541       let desc = WebConsoleUtils.getPropertyDescriptor(obj, name) || {};
1542       if (desc.get || desc.set) {
1543         // TODO: Bug 842672 - toolkit/ imports modules from browser/.
1544         let getGrip = VariablesView.getGrip(desc.get);
1545         let setGrip = VariablesView.getGrip(desc.set);
1546         let getString = VariablesView.getString(getGrip);
1547         let setString = VariablesView.getString(setGrip);
1548         output.push(name + ":", "  get: " + getString, "  set: " + setString);
1549       }
1550       else {
1551         let valueGrip = VariablesView.getGrip(obj[name]);
1552         let valueString = VariablesView.getString(valueGrip);
1553         output.push(name + ": " + valueString);
1554       }
1555     }
1557     return "  " + output.join("\n  ");
1558   };
1560   /**
1561    * Print a string to the output, as-is.
1562    *
1563    * @param string aString
1564    *        A string you want to output.
1565    * @return void
1566    */
1567   aOwner.sandbox.print = function JSTH_print(aString)
1568   {
1569     aOwner.helperResult = { rawOutput: true };
1570     return String(aString);
1571   };
1573 exports.JSTermHelpers = JSTermHelpers;
1575 (function(WCU) {
1576 ///////////////////////////////////////////////////////////////////////////////
1577 // Network logging
1578 ///////////////////////////////////////////////////////////////////////////////
1580 // The maximum uint32 value.
1581 const PR_UINT32_MAX = 4294967295;
1583 // HTTP status codes.
1584 const HTTP_MOVED_PERMANENTLY = 301;
1585 const HTTP_FOUND = 302;
1586 const HTTP_SEE_OTHER = 303;
1587 const HTTP_TEMPORARY_REDIRECT = 307;
1589 // The maximum number of bytes a NetworkResponseListener can hold.
1590 const RESPONSE_BODY_LIMIT = 1048576; // 1 MB
1593  * The network response listener implements the nsIStreamListener and
1594  * nsIRequestObserver interfaces. This is used within the NetworkMonitor feature
1595  * to get the response body of the request.
1597  * The code is mostly based on code listings from:
1599  *   http://www.softwareishard.com/blog/firebug/
1600  *      nsitraceablechannel-intercept-http-traffic/
1602  * @constructor
1603  * @param object aOwner
1604  *        The response listener owner. This object needs to hold the
1605  *        |openResponses| object.
1606  * @param object aHttpActivity
1607  *        HttpActivity object associated with this request. See NetworkMonitor
1608  *        for more information.
1609  */
1610 function NetworkResponseListener(aOwner, aHttpActivity)
1612   this.owner = aOwner;
1613   this.receivedData = "";
1614   this.httpActivity = aHttpActivity;
1615   this.bodySize = 0;
1618 NetworkResponseListener.prototype = {
1619   QueryInterface:
1620     XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback,
1621                            Ci.nsIRequestObserver, Ci.nsISupports]),
1623   /**
1624    * This NetworkResponseListener tracks the NetworkMonitor.openResponses object
1625    * to find the associated uncached headers.
1626    * @private
1627    */
1628   _foundOpenResponse: false,
1630   /**
1631    * The response listener owner.
1632    */
1633   owner: null,
1635   /**
1636    * The response will be written into the outputStream of this nsIPipe.
1637    * Both ends of the pipe must be blocking.
1638    */
1639   sink: null,
1641   /**
1642    * The HttpActivity object associated with this response.
1643    */
1644   httpActivity: null,
1646   /**
1647    * Stores the received data as a string.
1648    */
1649   receivedData: null,
1651   /**
1652    * The network response body size.
1653    */
1654   bodySize: null,
1656   /**
1657    * The nsIRequest we are started for.
1658    */
1659   request: null,
1661   /**
1662    * Set the async listener for the given nsIAsyncInputStream. This allows us to
1663    * wait asynchronously for any data coming from the stream.
1664    *
1665    * @param nsIAsyncInputStream aStream
1666    *        The input stream from where we are waiting for data to come in.
1667    * @param nsIInputStreamCallback aListener
1668    *        The input stream callback you want. This is an object that must have
1669    *        the onInputStreamReady() method. If the argument is null, then the
1670    *        current callback is removed.
1671    * @return void
1672    */
1673   setAsyncListener: function NRL_setAsyncListener(aStream, aListener)
1674   {
1675     // Asynchronously wait for the stream to be readable or closed.
1676     aStream.asyncWait(aListener, 0, 0, Services.tm.mainThread);
1677   },
1679   /**
1680    * Stores the received data, if request/response body logging is enabled. It
1681    * also does limit the number of stored bytes, based on the
1682    * RESPONSE_BODY_LIMIT constant.
1683    *
1684    * Learn more about nsIStreamListener at:
1685    * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIStreamListener
1686    *
1687    * @param nsIRequest aRequest
1688    * @param nsISupports aContext
1689    * @param nsIInputStream aInputStream
1690    * @param unsigned long aOffset
1691    * @param unsigned long aCount
1692    */
1693   onDataAvailable:
1694   function NRL_onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount)
1695   {
1696     this._findOpenResponse();
1697     let data = NetUtil.readInputStreamToString(aInputStream, aCount);
1699     this.bodySize += aCount;
1701     if (!this.httpActivity.discardResponseBody &&
1702         this.receivedData.length < RESPONSE_BODY_LIMIT) {
1703       this.receivedData += NetworkHelper.
1704                            convertToUnicode(data, aRequest.contentCharset);
1705     }
1706   },
1708   /**
1709    * See documentation at
1710    * https://developer.mozilla.org/En/NsIRequestObserver
1711    *
1712    * @param nsIRequest aRequest
1713    * @param nsISupports aContext
1714    */
1715   onStartRequest: function NRL_onStartRequest(aRequest)
1716   {
1717     this.request = aRequest;
1718     this._findOpenResponse();
1719     // Asynchronously wait for the data coming from the request.
1720     this.setAsyncListener(this.sink.inputStream, this);
1721   },
1723   /**
1724    * Handle the onStopRequest by closing the sink output stream.
1725    *
1726    * For more documentation about nsIRequestObserver go to:
1727    * https://developer.mozilla.org/En/NsIRequestObserver
1728    */
1729   onStopRequest: function NRL_onStopRequest()
1730   {
1731     this._findOpenResponse();
1732     this.sink.outputStream.close();
1733   },
1735   /**
1736    * Find the open response object associated to the current request. The
1737    * NetworkMonitor._httpResponseExaminer() method saves the response headers in
1738    * NetworkMonitor.openResponses. This method takes the data from the open
1739    * response object and puts it into the HTTP activity object, then sends it to
1740    * the remote Web Console instance.
1741    *
1742    * @private
1743    */
1744   _findOpenResponse: function NRL__findOpenResponse()
1745   {
1746     if (!this.owner || this._foundOpenResponse) {
1747       return;
1748     }
1750     let openResponse = null;
1752     for each (let item in this.owner.openResponses) {
1753       if (item.channel === this.httpActivity.channel) {
1754         openResponse = item;
1755         break;
1756       }
1757     }
1759     if (!openResponse) {
1760       return;
1761     }
1762     this._foundOpenResponse = true;
1764     delete this.owner.openResponses[openResponse.id];
1766     this.httpActivity.owner.addResponseHeaders(openResponse.headers);
1767     this.httpActivity.owner.addResponseCookies(openResponse.cookies);
1768   },
1770   /**
1771    * Clean up the response listener once the response input stream is closed.
1772    * This is called from onStopRequest() or from onInputStreamReady() when the
1773    * stream is closed.
1774    * @return void
1775    */
1776   onStreamClose: function NRL_onStreamClose()
1777   {
1778     if (!this.httpActivity) {
1779       return;
1780     }
1781     // Remove our listener from the request input stream.
1782     this.setAsyncListener(this.sink.inputStream, null);
1784     this._findOpenResponse();
1786     if (!this.httpActivity.discardResponseBody && this.receivedData.length) {
1787       this._onComplete(this.receivedData);
1788     }
1789     else if (!this.httpActivity.discardResponseBody &&
1790              this.httpActivity.responseStatus == 304) {
1791       // Response is cached, so we load it from cache.
1792       let charset = this.request.contentCharset || this.httpActivity.charset;
1793       NetworkHelper.loadFromCache(this.httpActivity.url, charset,
1794                                   this._onComplete.bind(this));
1795     }
1796     else {
1797       this._onComplete();
1798     }
1799   },
1801   /**
1802    * Handler for when the response completes. This function cleans up the
1803    * response listener.
1804    *
1805    * @param string [aData]
1806    *        Optional, the received data coming from the response listener or
1807    *        from the cache.
1808    */
1809   _onComplete: function NRL__onComplete(aData)
1810   {
1811     let response = {
1812       mimeType: "",
1813       text: aData || "",
1814     };
1816     response.size = response.text.length;
1818     try {
1819       response.mimeType = this.request.contentType;
1820     }
1821     catch (ex) { }
1823     if (!response.mimeType || !NetworkHelper.isTextMimeType(response.mimeType)) {
1824       response.encoding = "base64";
1825       response.text = btoa(response.text);
1826     }
1828     if (response.mimeType && this.request.contentCharset) {
1829       response.mimeType += "; charset=" + this.request.contentCharset;
1830     }
1832     this.receivedData = "";
1834     this.httpActivity.owner.
1835       addResponseContent(response, this.httpActivity.discardResponseBody);
1837     this.httpActivity.channel = null;
1838     this.httpActivity.owner = null;
1839     this.httpActivity = null;
1840     this.sink = null;
1841     this.inputStream = null;
1842     this.request = null;
1843     this.owner = null;
1844   },
1846   /**
1847    * The nsIInputStreamCallback for when the request input stream is ready -
1848    * either it has more data or it is closed.
1849    *
1850    * @param nsIAsyncInputStream aStream
1851    *        The sink input stream from which data is coming.
1852    * @returns void
1853    */
1854   onInputStreamReady: function NRL_onInputStreamReady(aStream)
1855   {
1856     if (!(aStream instanceof Ci.nsIAsyncInputStream) || !this.httpActivity) {
1857       return;
1858     }
1860     let available = -1;
1861     try {
1862       // This may throw if the stream is closed normally or due to an error.
1863       available = aStream.available();
1864     }
1865     catch (ex) { }
1867     if (available != -1) {
1868       if (available != 0) {
1869         // Note that passing 0 as the offset here is wrong, but the
1870         // onDataAvailable() method does not use the offset, so it does not
1871         // matter.
1872         this.onDataAvailable(this.request, null, aStream, 0, available);
1873       }
1874       this.setAsyncListener(aStream, this);
1875     }
1876     else {
1877       this.onStreamClose();
1878     }
1879   },
1883  * The network monitor uses the nsIHttpActivityDistributor to monitor network
1884  * requests. The nsIObserverService is also used for monitoring
1885  * http-on-examine-response notifications. All network request information is
1886  * routed to the remote Web Console.
1888  * @constructor
1889  * @param nsIDOMWindow aWindow
1890  *        Optional, the window that we monitor network requests for. If no
1891  *        window is given, all browser network requests are logged.
1892  * @param object aOwner
1893  *        The network monitor owner. This object needs to hold:
1894  *        - onNetworkEvent(aRequestInfo, aChannel). This method is invoked once for
1895  *        every new network request and it is given two arguments: the initial network
1896  *        request information, and the channel. onNetworkEvent() must return an object
1897  *        which holds several add*() methods which are used to add further network
1898  *        request/response information.
1899  *        - saveRequestAndResponseBodies property which tells if you want to log
1900  *        request and response bodies.
1901  */
1902 function NetworkMonitor(aWindow, aOwner)
1904   this.window = aWindow;
1905   this.owner = aOwner;
1906   this.openRequests = {};
1907   this.openResponses = {};
1908   this._httpResponseExaminer = this._httpResponseExaminer.bind(this);
1911 NetworkMonitor.prototype = {
1912   httpTransactionCodes: {
1913     0x5001: "REQUEST_HEADER",
1914     0x5002: "REQUEST_BODY_SENT",
1915     0x5003: "RESPONSE_START",
1916     0x5004: "RESPONSE_HEADER",
1917     0x5005: "RESPONSE_COMPLETE",
1918     0x5006: "TRANSACTION_CLOSE",
1920     0x804b0003: "STATUS_RESOLVING",
1921     0x804b000b: "STATUS_RESOLVED",
1922     0x804b0007: "STATUS_CONNECTING_TO",
1923     0x804b0004: "STATUS_CONNECTED_TO",
1924     0x804b0005: "STATUS_SENDING_TO",
1925     0x804b000a: "STATUS_WAITING_FOR",
1926     0x804b0006: "STATUS_RECEIVING_FROM"
1927   },
1929   // Network response bodies are piped through a buffer of the given size (in
1930   // bytes).
1931   responsePipeSegmentSize: null,
1933   owner: null,
1935   /**
1936    * Whether to save the bodies of network requests and responses. Disabled by
1937    * default to save memory.
1938    */
1939   get saveRequestAndResponseBodies()
1940     this.owner && this.owner.saveRequestAndResponseBodies,
1942   /**
1943    * Object that holds the HTTP activity objects for ongoing requests.
1944    */
1945   openRequests: null,
1947   /**
1948    * Object that holds response headers coming from this._httpResponseExaminer.
1949    */
1950   openResponses: null,
1952   /**
1953    * The network monitor initializer.
1954    */
1955   init: function NM_init()
1956   {
1957     this.responsePipeSegmentSize = Services.prefs
1958                                    .getIntPref("network.buffer.cache.size");
1960     gActivityDistributor.addObserver(this);
1962     if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
1963       Services.obs.addObserver(this._httpResponseExaminer,
1964                                "http-on-examine-response", false);
1965     }
1966   },
1968   /**
1969    * Observe notifications for the http-on-examine-response topic, coming from
1970    * the nsIObserverService.
1971    *
1972    * @private
1973    * @param nsIHttpChannel aSubject
1974    * @param string aTopic
1975    * @returns void
1976    */
1977   _httpResponseExaminer: function NM__httpResponseExaminer(aSubject, aTopic)
1978   {
1979     // The httpResponseExaminer is used to retrieve the uncached response
1980     // headers. The data retrieved is stored in openResponses. The
1981     // NetworkResponseListener is responsible with updating the httpActivity
1982     // object with the data from the new object in openResponses.
1984     if (!this.owner || aTopic != "http-on-examine-response" ||
1985         !(aSubject instanceof Ci.nsIHttpChannel)) {
1986       return;
1987     }
1989     let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
1991     if (this.window) {
1992       // Try to get the source window of the request.
1993       let win = NetworkHelper.getWindowForRequest(channel);
1994       if (!win || win.top !== this.window) {
1995         return;
1996       }
1997     }
1999     let response = {
2000       id: gSequenceId(),
2001       channel: channel,
2002       headers: [],
2003       cookies: [],
2004     };
2006     let setCookieHeader = null;
2008     channel.visitResponseHeaders({
2009       visitHeader: function NM__visitHeader(aName, aValue) {
2010         let lowerName = aName.toLowerCase();
2011         if (lowerName == "set-cookie") {
2012           setCookieHeader = aValue;
2013         }
2014         response.headers.push({ name: aName, value: aValue });
2015       }
2016     });
2018     if (!response.headers.length) {
2019       return; // No need to continue.
2020     }
2022     if (setCookieHeader) {
2023       response.cookies = NetworkHelper.parseSetCookieHeader(setCookieHeader);
2024     }
2026     // Determine the HTTP version.
2027     let httpVersionMaj = {};
2028     let httpVersionMin = {};
2030     channel.QueryInterface(Ci.nsIHttpChannelInternal);
2031     channel.getResponseVersion(httpVersionMaj, httpVersionMin);
2033     response.status = channel.responseStatus;
2034     response.statusText = channel.responseStatusText;
2035     response.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
2036                                      httpVersionMin.value;
2038     this.openResponses[response.id] = response;
2039   },
2041   /**
2042    * Begin observing HTTP traffic that originates inside the current tab.
2043    *
2044    * @see https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIHttpActivityObserver
2045    *
2046    * @param nsIHttpChannel aChannel
2047    * @param number aActivityType
2048    * @param number aActivitySubtype
2049    * @param number aTimestamp
2050    * @param number aExtraSizeData
2051    * @param string aExtraStringData
2052    */
2053   observeActivity:
2054   function NM_observeActivity(aChannel, aActivityType, aActivitySubtype,
2055                               aTimestamp, aExtraSizeData, aExtraStringData)
2056   {
2057     if (!this.owner ||
2058         aActivityType != gActivityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION &&
2059         aActivityType != gActivityDistributor.ACTIVITY_TYPE_SOCKET_TRANSPORT) {
2060       return;
2061     }
2063     if (!(aChannel instanceof Ci.nsIHttpChannel)) {
2064       return;
2065     }
2067     aChannel = aChannel.QueryInterface(Ci.nsIHttpChannel);
2069     if (aActivitySubtype ==
2070         gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER) {
2071       this._onRequestHeader(aChannel, aTimestamp, aExtraStringData);
2072       return;
2073     }
2075     // Iterate over all currently ongoing requests. If aChannel can't
2076     // be found within them, then exit this function.
2077     let httpActivity = null;
2078     for each (let item in this.openRequests) {
2079       if (item.channel === aChannel) {
2080         httpActivity = item;
2081         break;
2082       }
2083     }
2085     if (!httpActivity) {
2086       return;
2087     }
2089     let transCodes = this.httpTransactionCodes;
2091     // Store the time information for this activity subtype.
2092     if (aActivitySubtype in transCodes) {
2093       let stage = transCodes[aActivitySubtype];
2094       if (stage in httpActivity.timings) {
2095         httpActivity.timings[stage].last = aTimestamp;
2096       }
2097       else {
2098         httpActivity.timings[stage] = {
2099           first: aTimestamp,
2100           last: aTimestamp,
2101         };
2102       }
2103     }
2105     switch (aActivitySubtype) {
2106       case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
2107         this._onRequestBodySent(httpActivity);
2108         break;
2109       case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
2110         this._onResponseHeader(httpActivity, aExtraStringData);
2111         break;
2112       case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
2113         this._onTransactionClose(httpActivity);
2114         break;
2115       default:
2116         break;
2117     }
2118   },
2120   /**
2121    * Handler for ACTIVITY_SUBTYPE_REQUEST_HEADER. When a request starts the
2122    * headers are sent to the server. This method creates the |httpActivity|
2123    * object where we store the request and response information that is
2124    * collected through its lifetime.
2125    *
2126    * @private
2127    * @param nsIHttpChannel aChannel
2128    * @param number aTimestamp
2129    * @param string aExtraStringData
2130    * @return void
2131    */
2132   _onRequestHeader:
2133   function NM__onRequestHeader(aChannel, aTimestamp, aExtraStringData)
2134   {
2135     let win = NetworkHelper.getWindowForRequest(aChannel);
2137     // Try to get the source window of the request.
2138     if (this.window && (!win || win.top !== this.window)) {
2139       return;
2140     }
2142     let httpActivity = this.createActivityObject(aChannel);
2144     // see NM__onRequestBodySent()
2145     httpActivity.charset = win ? win.document.characterSet : null;
2146     httpActivity.private = win ? PrivateBrowsingUtils.isWindowPrivate(win) : false;
2148     httpActivity.timings.REQUEST_HEADER = {
2149       first: aTimestamp,
2150       last: aTimestamp
2151     };
2153     let httpVersionMaj = {};
2154     let httpVersionMin = {};
2155     let event = {};
2156     event.startedDateTime = new Date(Math.round(aTimestamp / 1000)).toISOString();
2157     event.headersSize = aExtraStringData.length;
2158     event.method = aChannel.requestMethod;
2159     event.url = aChannel.URI.spec;
2160     event.private = httpActivity.private;
2162     // Determine if this is an XHR request.
2163     try {
2164       let callbacks = aChannel.notificationCallbacks;
2165       let xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null;
2166       httpActivity.isXHR = event.isXHR = !!xhrRequest;
2167     } catch (e) {
2168       httpActivity.isXHR = event.isXHR = false;
2169     }
2171     // Determine the HTTP version.
2172     aChannel.QueryInterface(Ci.nsIHttpChannelInternal);
2173     aChannel.getRequestVersion(httpVersionMaj, httpVersionMin);
2175     event.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
2176                                   httpVersionMin.value;
2178     event.discardRequestBody = !this.saveRequestAndResponseBodies;
2179     event.discardResponseBody = !this.saveRequestAndResponseBodies;
2181     let headers = [];
2182     let cookies = [];
2183     let cookieHeader = null;
2185     // Copy the request header data.
2186     aChannel.visitRequestHeaders({
2187       visitHeader: function NM__visitHeader(aName, aValue)
2188       {
2189         if (aName == "Cookie") {
2190           cookieHeader = aValue;
2191         }
2192         headers.push({ name: aName, value: aValue });
2193       }
2194     });
2196     if (cookieHeader) {
2197       cookies = NetworkHelper.parseCookieHeader(cookieHeader);
2198     }
2200     httpActivity.owner = this.owner.onNetworkEvent(event, aChannel);
2202     this._setupResponseListener(httpActivity);
2204     this.openRequests[httpActivity.id] = httpActivity;
2206     httpActivity.owner.addRequestHeaders(headers);
2207     httpActivity.owner.addRequestCookies(cookies);
2208   },
2210   /**
2211    * Create the empty HTTP activity object. This object is used for storing all
2212    * the request and response information.
2213    *
2214    * This is a HAR-like object. Conformance to the spec is not guaranteed at
2215    * this point.
2216    *
2217    * TODO: Bug 708717 - Add support for network log export to HAR
2218    *
2219    * @see http://www.softwareishard.com/blog/har-12-spec
2220    * @param nsIHttpChannel aChannel
2221    *        The HTTP channel for which the HTTP activity object is created.
2222    * @return object
2223    *         The new HTTP activity object.
2224    */
2225   createActivityObject: function NM_createActivityObject(aChannel)
2226   {
2227     return {
2228       id: gSequenceId(),
2229       channel: aChannel,
2230       charset: null, // see NM__onRequestHeader()
2231       url: aChannel.URI.spec,
2232       discardRequestBody: !this.saveRequestAndResponseBodies,
2233       discardResponseBody: !this.saveRequestAndResponseBodies,
2234       timings: {}, // internal timing information, see NM_observeActivity()
2235       responseStatus: null, // see NM__onResponseHeader()
2236       owner: null, // the activity owner which is notified when changes happen
2237     };
2238   },
2240   /**
2241    * Setup the network response listener for the given HTTP activity. The
2242    * NetworkResponseListener is responsible for storing the response body.
2243    *
2244    * @private
2245    * @param object aHttpActivity
2246    *        The HTTP activity object we are tracking.
2247    */
2248   _setupResponseListener: function NM__setupResponseListener(aHttpActivity)
2249   {
2250     let channel = aHttpActivity.channel;
2251     channel.QueryInterface(Ci.nsITraceableChannel);
2253     // The response will be written into the outputStream of this pipe.
2254     // This allows us to buffer the data we are receiving and read it
2255     // asynchronously.
2256     // Both ends of the pipe must be blocking.
2257     let sink = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
2259     // The streams need to be blocking because this is required by the
2260     // stream tee.
2261     sink.init(false, false, this.responsePipeSegmentSize, PR_UINT32_MAX, null);
2263     // Add listener for the response body.
2264     let newListener = new NetworkResponseListener(this, aHttpActivity);
2266     // Remember the input stream, so it isn't released by GC.
2267     newListener.inputStream = sink.inputStream;
2268     newListener.sink = sink;
2270     let tee = Cc["@mozilla.org/network/stream-listener-tee;1"].
2271               createInstance(Ci.nsIStreamListenerTee);
2273     let originalListener = channel.setNewListener(tee);
2275     tee.init(originalListener, sink.outputStream, newListener);
2276   },
2278   /**
2279    * Handler for ACTIVITY_SUBTYPE_REQUEST_BODY_SENT. The request body is logged
2280    * here.
2281    *
2282    * @private
2283    * @param object aHttpActivity
2284    *        The HTTP activity object we are working with.
2285    */
2286   _onRequestBodySent: function NM__onRequestBodySent(aHttpActivity)
2287   {
2288     if (aHttpActivity.discardRequestBody) {
2289       return;
2290     }
2292     let sentBody = NetworkHelper.
2293                    readPostTextFromRequest(aHttpActivity.channel,
2294                                            aHttpActivity.charset);
2296     if (!sentBody && this.window &&
2297         aHttpActivity.url == this.window.location.href) {
2298       // If the request URL is the same as the current page URL, then
2299       // we can try to get the posted text from the page directly.
2300       // This check is necessary as otherwise the
2301       //   NetworkHelper.readPostTextFromPageViaWebNav()
2302       // function is called for image requests as well but these
2303       // are not web pages and as such don't store the posted text
2304       // in the cache of the webpage.
2305       let webNav = this.window.QueryInterface(Ci.nsIInterfaceRequestor).
2306                    getInterface(Ci.nsIWebNavigation);
2307       sentBody = NetworkHelper.
2308                  readPostTextFromPageViaWebNav(webNav, aHttpActivity.charset);
2309     }
2311     if (sentBody) {
2312       aHttpActivity.owner.addRequestPostData({ text: sentBody });
2313     }
2314   },
2316   /**
2317    * Handler for ACTIVITY_SUBTYPE_RESPONSE_HEADER. This method stores
2318    * information about the response headers.
2319    *
2320    * @private
2321    * @param object aHttpActivity
2322    *        The HTTP activity object we are working with.
2323    * @param string aExtraStringData
2324    *        The uncached response headers.
2325    */
2326   _onResponseHeader:
2327   function NM__onResponseHeader(aHttpActivity, aExtraStringData)
2328   {
2329     // aExtraStringData contains the uncached response headers. The first line
2330     // contains the response status (e.g. HTTP/1.1 200 OK).
2331     //
2332     // Note: The response header is not saved here. Calling the
2333     // channel.visitResponseHeaders() methood at this point sometimes causes an
2334     // NS_ERROR_NOT_AVAILABLE exception.
2335     //
2336     // We could parse aExtraStringData to get the headers and their values, but
2337     // that is not trivial to do in an accurate manner. Hence, we save the
2338     // response headers in this._httpResponseExaminer().
2340     let headers = aExtraStringData.split(/\r\n|\n|\r/);
2341     let statusLine = headers.shift();
2342     let statusLineArray = statusLine.split(" ");
2344     let response = {};
2345     response.httpVersion = statusLineArray.shift();
2346     response.status = statusLineArray.shift();
2347     response.statusText = statusLineArray.join(" ");
2348     response.headersSize = aExtraStringData.length;
2350     aHttpActivity.responseStatus = response.status;
2352     // Discard the response body for known response statuses.
2353     switch (parseInt(response.status)) {
2354       case HTTP_MOVED_PERMANENTLY:
2355       case HTTP_FOUND:
2356       case HTTP_SEE_OTHER:
2357       case HTTP_TEMPORARY_REDIRECT:
2358         aHttpActivity.discardResponseBody = true;
2359         break;
2360     }
2362     response.discardResponseBody = aHttpActivity.discardResponseBody;
2364     aHttpActivity.owner.addResponseStart(response);
2365   },
2367   /**
2368    * Handler for ACTIVITY_SUBTYPE_TRANSACTION_CLOSE. This method updates the HAR
2369    * timing information on the HTTP activity object and clears the request
2370    * from the list of known open requests.
2371    *
2372    * @private
2373    * @param object aHttpActivity
2374    *        The HTTP activity object we work with.
2375    */
2376   _onTransactionClose: function NM__onTransactionClose(aHttpActivity)
2377   {
2378     let result = this._setupHarTimings(aHttpActivity);
2379     aHttpActivity.owner.addEventTimings(result.total, result.timings);
2380     delete this.openRequests[aHttpActivity.id];
2381   },
2383   /**
2384    * Update the HTTP activity object to include timing information as in the HAR
2385    * spec. The HTTP activity object holds the raw timing information in
2386    * |timings| - these are timings stored for each activity notification. The
2387    * HAR timing information is constructed based on these lower level data.
2388    *
2389    * @param object aHttpActivity
2390    *        The HTTP activity object we are working with.
2391    * @return object
2392    *         This object holds two properties:
2393    *         - total - the total time for all of the request and response.
2394    *         - timings - the HAR timings object.
2395    */
2396   _setupHarTimings: function NM__setupHarTimings(aHttpActivity)
2397   {
2398     let timings = aHttpActivity.timings;
2399     let harTimings = {};
2401     // Not clear how we can determine "blocked" time.
2402     harTimings.blocked = -1;
2404     // DNS timing information is available only in when the DNS record is not
2405     // cached.
2406     harTimings.dns = timings.STATUS_RESOLVING && timings.STATUS_RESOLVED ?
2407                      timings.STATUS_RESOLVED.last -
2408                      timings.STATUS_RESOLVING.first : -1;
2410     if (timings.STATUS_CONNECTING_TO && timings.STATUS_CONNECTED_TO) {
2411       harTimings.connect = timings.STATUS_CONNECTED_TO.last -
2412                            timings.STATUS_CONNECTING_TO.first;
2413     }
2414     else if (timings.STATUS_SENDING_TO) {
2415       harTimings.connect = timings.STATUS_SENDING_TO.first -
2416                            timings.REQUEST_HEADER.first;
2417     }
2418     else {
2419       harTimings.connect = -1;
2420     }
2422     if ((timings.STATUS_WAITING_FOR || timings.STATUS_RECEIVING_FROM) &&
2423         (timings.STATUS_CONNECTED_TO || timings.STATUS_SENDING_TO)) {
2424       harTimings.send = (timings.STATUS_WAITING_FOR ||
2425                          timings.STATUS_RECEIVING_FROM).first -
2426                         (timings.STATUS_CONNECTED_TO ||
2427                          timings.STATUS_SENDING_TO).last;
2428     }
2429     else {
2430       harTimings.send = -1;
2431     }
2433     if (timings.RESPONSE_START) {
2434       harTimings.wait = timings.RESPONSE_START.first -
2435                         (timings.REQUEST_BODY_SENT ||
2436                          timings.STATUS_SENDING_TO).last;
2437     }
2438     else {
2439       harTimings.wait = -1;
2440     }
2442     if (timings.RESPONSE_START && timings.RESPONSE_COMPLETE) {
2443       harTimings.receive = timings.RESPONSE_COMPLETE.last -
2444                            timings.RESPONSE_START.first;
2445     }
2446     else {
2447       harTimings.receive = -1;
2448     }
2450     let totalTime = 0;
2451     for (let timing in harTimings) {
2452       let time = Math.max(Math.round(harTimings[timing] / 1000), -1);
2453       harTimings[timing] = time;
2454       if (time > -1) {
2455         totalTime += time;
2456       }
2457     }
2459     return {
2460       total: totalTime,
2461       timings: harTimings,
2462     };
2463   },
2465   /**
2466    * Suspend Web Console activity. This is called when all Web Consoles are
2467    * closed.
2468    */
2469   destroy: function NM_destroy()
2470   {
2471     if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
2472       Services.obs.removeObserver(this._httpResponseExaminer,
2473                                   "http-on-examine-response");
2474     }
2476     gActivityDistributor.removeObserver(this);
2478     this.openRequests = {};
2479     this.openResponses = {};
2480     this.owner = null;
2481     this.window = null;
2482   },
2485 exports.NetworkMonitor = NetworkMonitor;
2486 exports.NetworkResponseListener = NetworkResponseListener;
2487 })(WebConsoleUtils);
2490  * A WebProgressListener that listens for location changes.
2492  * This progress listener is used to track file loads and other kinds of
2493  * location changes.
2495  * @constructor
2496  * @param object aWindow
2497  *        The window for which we need to track location changes.
2498  * @param object aOwner
2499  *        The listener owner which needs to implement two methods:
2500  *        - onFileActivity(aFileURI)
2501  *        - onLocationChange(aState, aTabURI, aPageTitle)
2502  */
2503 function ConsoleProgressListener(aWindow, aOwner)
2505   this.window = aWindow;
2506   this.owner = aOwner;
2508 exports.ConsoleProgressListener = ConsoleProgressListener;
2510 ConsoleProgressListener.prototype = {
2511   /**
2512    * Constant used for startMonitor()/stopMonitor() that tells you want to
2513    * monitor file loads.
2514    */
2515   MONITOR_FILE_ACTIVITY: 1,
2517   /**
2518    * Constant used for startMonitor()/stopMonitor() that tells you want to
2519    * monitor page location changes.
2520    */
2521   MONITOR_LOCATION_CHANGE: 2,
2523   /**
2524    * Tells if you want to monitor file activity.
2525    * @private
2526    * @type boolean
2527    */
2528   _fileActivity: false,
2530   /**
2531    * Tells if you want to monitor location changes.
2532    * @private
2533    * @type boolean
2534    */
2535   _locationChange: false,
2537   /**
2538    * Tells if the console progress listener is initialized or not.
2539    * @private
2540    * @type boolean
2541    */
2542   _initialized: false,
2544   _webProgress: null,
2546   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
2547                                          Ci.nsISupportsWeakReference]),
2549   /**
2550    * Initialize the ConsoleProgressListener.
2551    * @private
2552    */
2553   _init: function CPL__init()
2554   {
2555     if (this._initialized) {
2556       return;
2557     }
2559     this._webProgress = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
2560                         .getInterface(Ci.nsIWebNavigation)
2561                         .QueryInterface(Ci.nsIWebProgress);
2562     this._webProgress.addProgressListener(this,
2563                                           Ci.nsIWebProgress.NOTIFY_STATE_ALL);
2565     this._initialized = true;
2566   },
2568   /**
2569    * Start a monitor/tracker related to the current nsIWebProgressListener
2570    * instance.
2571    *
2572    * @param number aMonitor
2573    *        Tells what you want to track. Available constants:
2574    *        - this.MONITOR_FILE_ACTIVITY
2575    *          Track file loads.
2576    *        - this.MONITOR_LOCATION_CHANGE
2577    *          Track location changes for the top window.
2578    */
2579   startMonitor: function CPL_startMonitor(aMonitor)
2580   {
2581     switch (aMonitor) {
2582       case this.MONITOR_FILE_ACTIVITY:
2583         this._fileActivity = true;
2584         break;
2585       case this.MONITOR_LOCATION_CHANGE:
2586         this._locationChange = true;
2587         break;
2588       default:
2589         throw new Error("ConsoleProgressListener: unknown monitor type " +
2590                         aMonitor + "!");
2591     }
2592     this._init();
2593   },
2595   /**
2596    * Stop a monitor.
2597    *
2598    * @param number aMonitor
2599    *        Tells what you want to stop tracking. See this.startMonitor() for
2600    *        the list of constants.
2601    */
2602   stopMonitor: function CPL_stopMonitor(aMonitor)
2603   {
2604     switch (aMonitor) {
2605       case this.MONITOR_FILE_ACTIVITY:
2606         this._fileActivity = false;
2607         break;
2608       case this.MONITOR_LOCATION_CHANGE:
2609         this._locationChange = false;
2610         break;
2611       default:
2612         throw new Error("ConsoleProgressListener: unknown monitor type " +
2613                         aMonitor + "!");
2614     }
2616     if (!this._fileActivity && !this._locationChange) {
2617       this.destroy();
2618     }
2619   },
2621   onStateChange:
2622   function CPL_onStateChange(aProgress, aRequest, aState, aStatus)
2623   {
2624     if (!this.owner) {
2625       return;
2626     }
2628     if (this._fileActivity) {
2629       this._checkFileActivity(aProgress, aRequest, aState, aStatus);
2630     }
2632     if (this._locationChange) {
2633       this._checkLocationChange(aProgress, aRequest, aState, aStatus);
2634     }
2635   },
2637   /**
2638    * Check if there is any file load, given the arguments of
2639    * nsIWebProgressListener.onStateChange. If the state change tells that a file
2640    * URI has been loaded, then the remote Web Console instance is notified.
2641    * @private
2642    */
2643   _checkFileActivity:
2644   function CPL__checkFileActivity(aProgress, aRequest, aState, aStatus)
2645   {
2646     if (!(aState & Ci.nsIWebProgressListener.STATE_START)) {
2647       return;
2648     }
2650     let uri = null;
2651     if (aRequest instanceof Ci.imgIRequest) {
2652       let imgIRequest = aRequest.QueryInterface(Ci.imgIRequest);
2653       uri = imgIRequest.URI;
2654     }
2655     else if (aRequest instanceof Ci.nsIChannel) {
2656       let nsIChannel = aRequest.QueryInterface(Ci.nsIChannel);
2657       uri = nsIChannel.URI;
2658     }
2660     if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) {
2661       return;
2662     }
2664     this.owner.onFileActivity(uri.spec);
2665   },
2667   /**
2668    * Check if the current window.top location is changing, given the arguments
2669    * of nsIWebProgressListener.onStateChange. If that is the case, the remote
2670    * Web Console instance is notified.
2671    * @private
2672    */
2673   _checkLocationChange:
2674   function CPL__checkLocationChange(aProgress, aRequest, aState, aStatus)
2675   {
2676     let isStart = aState & Ci.nsIWebProgressListener.STATE_START;
2677     let isStop = aState & Ci.nsIWebProgressListener.STATE_STOP;
2678     let isNetwork = aState & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
2679     let isWindow = aState & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
2681     // Skip non-interesting states.
2682     if (!isNetwork || !isWindow || aProgress.DOMWindow != this.window) {
2683       return;
2684     }
2686     if (isStart && aRequest instanceof Ci.nsIChannel) {
2687       this.owner.onLocationChange("start", aRequest.URI.spec, "");
2688     }
2689     else if (isStop) {
2690       this.owner.onLocationChange("stop", this.window.location.href,
2691                                   this.window.document.title);
2692     }
2693   },
2695   onLocationChange: function() {},
2696   onStatusChange: function() {},
2697   onProgressChange: function() {},
2698   onSecurityChange: function() {},
2700   /**
2701    * Destroy the ConsoleProgressListener.
2702    */
2703   destroy: function CPL_destroy()
2704   {
2705     if (!this._initialized) {
2706       return;
2707     }
2709     this._initialized = false;
2710     this._fileActivity = false;
2711     this._locationChange = false;
2713     try {
2714       this._webProgress.removeProgressListener(this);
2715     }
2716     catch (ex) {
2717       // This can throw during browser shutdown.
2718     }
2720     this._webProgress = null;
2721     this.window = null;
2722     this.owner = null;
2723   },
2728  * A ReflowObserver that listens for reflow events from the page.
2729  * Implements nsIReflowObserver.
2731  * @constructor
2732  * @param object aWindow
2733  *        The window for which we need to track reflow.
2734  * @param object aOwner
2735  *        The listener owner which needs to implement:
2736  *        - onReflowActivity(aReflowInfo)
2737  */
2739 function ConsoleReflowListener(aWindow, aListener)
2741   this.docshell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
2742                          .getInterface(Ci.nsIWebNavigation)
2743                          .QueryInterface(Ci.nsIDocShell);
2744   this.listener = aListener;
2745   this.docshell.addWeakReflowObserver(this);
2748 exports.ConsoleReflowListener = ConsoleReflowListener;
2750 ConsoleReflowListener.prototype =
2752   QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
2753                                          Ci.nsISupportsWeakReference]),
2754   docshell: null,
2755   listener: null,
2757   /**
2758    * Forward reflow event to listener.
2759    *
2760    * @param DOMHighResTimeStamp aStart
2761    * @param DOMHighResTimeStamp aEnd
2762    * @param boolean aInterruptible
2763    */
2764   sendReflow: function CRL_sendReflow(aStart, aEnd, aInterruptible)
2765   {
2766     let frame = components.stack.caller.caller;
2768     let filename = frame.filename;
2770     if (filename) {
2771       // Because filename could be of the form "xxx.js -> xxx.js -> xxx.js",
2772       // we only take the last part.
2773       filename = filename.split(" ").pop();
2774     }
2776     this.listener.onReflowActivity({
2777       interruptible: aInterruptible,
2778       start: aStart,
2779       end: aEnd,
2780       sourceURL: filename,
2781       sourceLine: frame.lineNumber,
2782       functionName: frame.name
2783     });
2784   },
2786   /**
2787    * On uninterruptible reflow
2788    *
2789    * @param DOMHighResTimeStamp aStart
2790    * @param DOMHighResTimeStamp aEnd
2791    */
2792   reflow: function CRL_reflow(aStart, aEnd)
2793   {
2794     this.sendReflow(aStart, aEnd, false);
2795   },
2797   /**
2798    * On interruptible reflow
2799    *
2800    * @param DOMHighResTimeStamp aStart
2801    * @param DOMHighResTimeStamp aEnd
2802    */
2803   reflowInterruptible: function CRL_reflowInterruptible(aStart, aEnd)
2804   {
2805     this.sendReflow(aStart, aEnd, true);
2806   },
2808   /**
2809    * Unregister listener.
2810    */
2811   destroy: function CRL_destroy()
2812   {
2813     this.docshell.removeWeakReflowObserver(this);
2814     this.listener = this.docshell = null;
2815   },
2818 function gSequenceId()
2820   return gSequenceId.n++;
2822 gSequenceId.n = 0;