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/. */
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().
32 // (function foobar(a, b) { ...
33 // function foobar2(a) { ...
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 = {
42 * Convenience function to unwrap a wrapped object.
44 * @param aObject the object to unwrap.
45 * @return aObject unwrapped.
47 unwrap: function WCU_unwrap(aObject)
50 return XPCNativeWrapper.unwrap(aObject);
58 * Wrap a string in an nsISupportsString object.
60 * @param string aString
61 * @return nsISupportsString
63 supportsString: function WCU_supportsString(aString)
65 let str = Cc["@mozilla.org/supports-string;1"].
66 createInstance(Ci.nsISupportsString);
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
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
87 cloneObject: function WCU_cloneObject(aObject, aRecursive, aFilter)
89 if (typeof aObject != "object") {
95 if (Array.isArray(aObject)) {
97 Array.forEach(aObject, function(aValue, aIndex) {
98 if (!aFilter || aFilter(aIndex, aValue, aObject)) {
99 temp.push(aRecursive ? WCU_cloneObject(aValue) : aValue);
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;
118 * Copies certain style attributes from one element to another.
120 * @param nsIDOMNode aFrom
122 * @param nsIDOMNode aTo
123 * The destination node.
125 copyTextStyles: function WCU_copyTextStyles(aFrom, aTo)
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;
136 * Gets the ID of the inner window of this DOM window.
138 * @param nsIDOMWindow aWindow
140 * Inner ID for the given aWindow.
142 getInnerWindowId: function WCU_getInnerWindowId(aWindow)
144 return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
145 getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
149 * Recursively gather a list of inner window ids given a
152 * @param nsIDOMWindow aWindow
154 * list of inner window ids.
156 getInnerWindowIDsForFrames: function WCU_getInnerWindowIDsForFrames(aWindow)
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));
173 * Gets the ID of the outer window of this DOM window.
175 * @param nsIDOMWindow aWindow
177 * Outer ID for the given aWindow.
179 getOuterWindowId: function WCU_getOuterWindowId(aWindow)
181 return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
182 getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
186 * Abbreviates the given source URL so that it can be displayed flush-right
187 * without being too distracting.
189 * @param string aSourceURL
190 * The source URL to shorten.
192 * The abbreviated form of the source URL.
194 abbreviateSourceURL: function WCU_abbreviateSourceURL(aSourceURL)
196 if (aSourceURL.substr(0, 5) == "data:") {
197 let commaIndex = aSourceURL.indexOf(",");
198 if (commaIndex > -1) {
199 aSourceURL = "data:" + aSourceURL.substring(commaIndex + 1);
203 // Remove any query parameters.
204 let hookIndex = aSourceURL.indexOf("?");
205 if (hookIndex > -1) {
206 aSourceURL = aSourceURL.substring(0, hookIndex);
209 // Remove any hash fragments.
210 let hashIndex = aSourceURL.indexOf("#");
211 if (hashIndex > -1) {
212 aSourceURL = aSourceURL.substring(0, hashIndex);
215 // Remove a trailing "/".
216 if (aSourceURL[aSourceURL.length - 1] == "/") {
217 aSourceURL = aSourceURL.substring(0, aSourceURL.length - 1);
220 // Remove all but the last path component.
221 let slashIndex = aSourceURL.lastIndexOf("/");
222 if (slashIndex > -1) {
223 aSourceURL = aSourceURL.substring(slashIndex + 1);
230 * Tells if the given function is native or not.
232 * @param function aFunction
233 * The function you want to check if it is native or not.
235 * True if the given function is native, false otherwise.
237 isNativeFunction: function WCU_isNativeFunction(aFunction)
239 return typeof aFunction == "function" && !("prototype" in aFunction);
243 * Tells if the given property of the provided object is a non-native getter or
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.
251 * True if the given property is a getter, false otherwise.
253 isNonNativeGetter: function WCU_isNonNativeGetter(aObject, aProp)
255 if (typeof aObject != "object") {
258 let desc = this.getPropertyDescriptor(aObject, aProp);
259 return desc && desc.get && !this.isNativeFunction(desc.get);
263 * Get the property descriptor for the given object.
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.
270 * Property descriptor.
272 getPropertyDescriptor: function WCU_getPropertyDescriptor(aObject, aProp)
277 if ((desc = Object.getOwnPropertyDescriptor(aObject, aProp))) {
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.
288 aObject = Object.getPrototypeOf(aObject);
290 catch (ex if (ex.name == "TypeError")) {
298 * Sort function for object properties.
301 * Property descriptor.
303 * Property descriptor.
305 * -1 if a.name < b.name,
306 * 1 if a.name > b.name,
309 propertiesSort: function WCU_propertiesSort(a, b)
311 // Convert the pair.name to a number for later sorting.
312 let aNumber = parseFloat(a.name);
313 let bNumber = parseFloat(b.name);
316 if (!isNaN(aNumber) && isNaN(bNumber)) {
319 else if (isNaN(aNumber) && !isNaN(bNumber)) {
322 else if (!isNaN(aNumber) && !isNaN(bNumber)) {
323 return aNumber - bNumber;
326 else if (a.name < b.name) {
329 else if (a.name > b.name) {
338 * Create a grip for the given value. If the value is an object,
339 * an object wrapper will be created.
341 * @param mixed aValue
342 * The value you want to create a grip for, before sending it to the
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().
350 createValueGrip: function WCU_createValueGrip(aValue, aObjectWrapper)
352 switch (typeof aValue) {
356 return aObjectWrapper(aValue);
358 if (aValue === Infinity) {
359 return { type: "Infinity" };
361 else if (aValue === -Infinity) {
362 return { type: "-Infinity" };
364 else if (Number.isNaN(aValue)) {
365 return { type: "NaN" };
367 else if (!aValue && 1 / aValue === -Infinity) {
368 return { type: "-0" };
372 return { type: "undefined" };
374 if (aValue === null) {
375 return { type: "null" };
378 return aObjectWrapper(aValue);
380 Cu.reportError("Failed to provide a grip for value of " + typeof aValue
387 * Check if the given object is an iterator or a generator.
389 * @param object aObject
390 * The object you want to check.
392 * True if the given object is an iterator or a generator, otherwise
395 isIteratorOrGenerator: function WCU_isIteratorOrGenerator(aObject)
397 if (aObject === null) {
401 if (typeof aObject == "object") {
402 if (typeof aObject.__iterator__ == "function" ||
403 aObject.constructor && aObject.constructor.name == "Iterator") {
408 let str = aObject.toString();
409 if (typeof aObject.next == "function" &&
410 str.indexOf("[object Generator") == 0) {
415 // window.history.next throws in the typeof check above.
424 * Determine if the given request mixes HTTP with HTTPS content.
426 * @param string aRequest
427 * Location of the requested content.
428 * @param string aLocation
429 * Location of the current page.
431 * True if the content is mixed, false if not.
433 isMixedHTTPSRequest: function WCU_isMixedHTTPSRequest(aRequest, aLocation)
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");
446 * Helper function to deduce the name of the provided function.
448 * @param funtion aFunction
449 * The function whose name will be returned.
453 getFunctionName: function WCF_getFunctionName(aFunction)
456 if (aFunction.name) {
457 name = aFunction.name;
462 desc = aFunction.getOwnPropertyDescriptor("displayName");
465 if (desc && typeof desc.value == "string") {
471 let str = (aFunction.toString() || aFunction.toSource()) + "";
472 name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
480 * Get the object class name. For example, the |window| object has the Window
481 * class name (based on [object Window]).
483 * @param object aObject
484 * The object you want to get the class name for.
486 * The object class name.
488 getObjectClassName: function WCU_getObjectClassName(aObject)
490 if (aObject === null) {
493 if (aObject === undefined) {
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);
506 className = ((aObject + "").match(/^\[object (\S+)\]$/) || [])[1];
508 className = ((aObject.constructor + "").match(/^\[object (\S+)\]$/) || [])[1];
510 if (!className && typeof aObject.constructor == "function") {
511 className = this.getFunctionName(aObject.constructor);
520 * Check if the given value is a grip with an actor.
523 * Value you want to check if it is a grip with an actor.
525 * True if the given value is a grip with an actor.
527 isActorGrip: function WCU_isActorGrip(aGrip)
529 return aGrip && typeof(aGrip) == "object" && aGrip.actor;
532 exports.Utils = WebConsoleUtils;
534 //////////////////////////////////////////////////////////////////////////
536 //////////////////////////////////////////////////////////////////////////
538 WebConsoleUtils.l10n = function WCU_l10n(aBundleURI)
540 this._bundleUri = aBundleURI;
543 WebConsoleUtils.l10n.prototype = {
548 if (!this._stringBundle) {
549 this._stringBundle = Services.strings.createBundle(this._bundleUri);
551 return this._stringBundle;
555 * Generates a formatted timestamp string for displaying in console messages.
557 * @param integer [aMilliseconds]
558 * Optional, allows you to specify the timestamp in milliseconds since
561 * The timestamp formatted for display.
563 timestampString: function WCU_l10n_timestampString(aMilliseconds)
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);
573 * Retrieve a localized string.
575 * @param string aName
576 * The string name you want from the Web Console string bundle.
578 * The localized string.
580 getStr: function WCU_l10n_getStr(aName)
584 result = this.stringBundle.GetStringFromName(aName);
587 Cu.reportError("Failed to get string: " + aName);
594 * Retrieve a localized string formatted with values coming from the given
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.
602 * The formatted local string.
604 getFormatStr: function WCU_l10n_getFormatStr(aName, aArray)
608 result = this.stringBundle.formatStringFromName(aName, aArray, aArray.length);
611 Cu.reportError("Failed to format string: " + aName);
619 //////////////////////////////////////////////////////////////////////////
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 = {
636 const MAX_COMPLETIONS = 1500;
639 * Analyses a given string to find the last statement that is interesting for
643 * A string to analyse.
646 * If there was an error in the string detected, then a object like
648 * { err: "ErrorMesssage" }
650 * is returned, otherwise a object like
653 * state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
654 * startPos: index of where the last statement begins
657 function findCompletionBeginning(aStr)
661 let state = STATE_NORMAL;
664 for (let i = 0; i < aStr.length; i++) {
671 state = STATE_DQUOTE;
682 else if (OPEN_BODY.indexOf(c) != -1) {
689 else if (CLOSE_BODY.indexOf(c) != -1) {
690 var last = bodyStack.pop();
691 if (!last || OPEN_CLOSE_BODY[last.token] != c) {
705 // Double quote state > " <
710 else if (c == "\n") {
712 err: "unterminated string literal"
716 state = STATE_NORMAL;
720 // Single quote state > ' <
725 else if (c == "\n") {
727 err: "unterminated string literal"
731 state = STATE_NORMAL;
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
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:
763 * matches: [ string, string, string ],
764 * matchProp: Last part of the inputValue that was used to find
765 * the matches-strings.
768 function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor)
770 if (aCursor === undefined) {
771 aCursor = aInputValue.length;
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.
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) {
791 let completionPart = inputValue.substring(beginning.startPos);
793 // Don't complete on just an empty string.
794 if (completionPart.trim() == "") {
798 let lastDot = completionPart.lastIndexOf(".");
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);
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
815 if (properties.length == 0) {
816 return getMatchedPropsInEnvironment(anEnvironment, matchProp);
818 obj = getVariableInEnvironment(anEnvironment, properties.shift());
821 if (!isObjectUsable(obj)) {
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) {
833 obj = DevToolsUtils.getProperty(obj, prop);
835 if (!isObjectUsable(obj)) {
840 // If the final property is a primitive
841 if (typeof obj != "object") {
842 return getMatchedProps(obj, matchProp);
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.
854 * True if further inspection into the object is possible, or false
857 function isObjectUsable(aObject)
859 if (aObject == null) {
863 if (typeof aObject == "object" && aObject.class == "DeadObject") {
871 * @see getExactMatch_impl()
873 function getVariableInEnvironment(anEnvironment, aName)
875 return getExactMatch_impl(anEnvironment, aName, DebuggerEnvironmentSupport);
879 * @see getMatchedProps_impl()
881 function getMatchedPropsInEnvironment(anEnvironment, aMatch)
883 return getMatchedProps_impl(anEnvironment, aMatch, DebuggerEnvironmentSupport);
887 * @see getMatchedProps_impl()
889 function getMatchedPropsInDbgObject(aDbgObject, aMatch)
891 return getMatchedProps_impl(aDbgObject, aMatch, DebuggerObjectSupport);
895 * @see getMatchedProps_impl()
897 function getMatchedProps(aObj, aMatch)
899 if (typeof aObj != "object") {
900 aObj = aObj.constructor.prototype;
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.
910 * Object whose properties we want to filter.
911 * @param string aMatch
912 * Filter for properties that match this string.
914 * Object that contains the matchProp and the list of names.
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) {
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) {
936 if (matches.size > MAX_COMPLETIONS) {
941 if (matches.size > MAX_COMPLETIONS) {
948 matches: [...matches],
953 * Returns a property value based on its name from the given object, by
954 * recursively checking the object's prototype.
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.
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);
978 let JSObjectSupport = {
979 chainIterator: function(aObj)
983 aObj = Object.getPrototypeOf(aObj);
987 getProperties: function(aObj)
989 return Object.getOwnPropertyNames(aObj);
992 getProperty: function()
994 // getProperty is unsafe with raw JS objects.
995 throw "Unimplemented!";
999 let DebuggerObjectSupport = {
1000 chainIterator: function(aObj)
1008 getProperties: function(aObj)
1010 return aObj.getOwnPropertyNames();
1013 getProperty: function(aObj, aName, aRootObj)
1015 // This is left unimplemented in favor to DevToolsUtils.getProperty().
1016 throw "Unimplemented!";
1020 let DebuggerEnvironmentSupport = {
1021 chainIterator: function(aObj)
1029 getProperties: function(aObj)
1031 return aObj.names();
1034 getProperty: function(aObj, aName)
1036 // TODO: we should use getVariableDescriptor() here - bug 725815.
1037 let result = aObj.getVariable(aName);
1038 return result === undefined ? null : { value: result };
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.
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.
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]),
1075 * The content window for which we listen to page errors.
1076 * @type nsIDOMWindow
1081 * The listener object which is notified of messages from the console service.
1087 * Initialize the nsIConsoleService listener.
1089 init: function CSL_init()
1091 Services.console.registerListener(this);
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
1099 * @param nsIConsoleMessage aMessage
1100 * The message object coming from the nsIConsoleService.
1102 observe: function CSL_observe(aMessage)
1104 if (!this.listener) {
1109 if (!(aMessage instanceof Ci.nsIScriptError) ||
1110 !aMessage.outerWindowID ||
1111 !this.isCategoryAllowed(aMessage.category)) {
1115 let errorWindow = Services.wm.getOuterWindowWithId(aMessage.outerWindowID);
1116 if (!errorWindow || errorWindow.top != this.window) {
1121 this.listener.onConsoleServiceMessage(aMessage);
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.
1128 * @param string aCategory
1129 * The message category you want to check.
1131 * True if the category is allowed to be logged, false otherwise.
1133 isCategoryAllowed: function CSL_isCategoryAllowed(aCategory)
1139 switch (aCategory) {
1140 case "XPConnect JavaScript":
1141 case "component javascript":
1142 case "chrome javascript":
1143 case "chrome registration":
1145 case "XBL Prototype Handler":
1146 case "XBL Content Sink":
1147 case "xbl javascript":
1155 * Get the cached page errors for the current inner window and its (i)frames.
1157 * @param boolean [aIncludePrivate=false]
1158 * Tells if you want to also retrieve messages coming from private
1159 * windows. Defaults to false.
1161 * The array of cached messages. Each element is an nsIScriptError or
1162 * an nsIConsoleMessage
1164 getCachedMessages: function CSL_getCachedMessages(aIncludePrivate = false)
1166 let errors = Services.console.getMessageArray() || [];
1168 // if !this.window, we're in a browser console. Still need to filter
1169 // private messages.
1171 return errors.filter((aError) => {
1172 if (aError instanceof Ci.nsIScriptError) {
1173 if (!aIncludePrivate && aError.isFromPrivateWindow) {
1182 let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
1184 return errors.filter((aError) => {
1185 if (aError instanceof Ci.nsIScriptError) {
1186 if (!aIncludePrivate && aError.isFromPrivateWindow) {
1190 (ids.indexOf(aError.innerWindowID) == -1 ||
1191 !this.isCategoryAllowed(aError.category))) {
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.
1206 * Remove the nsIConsoleService listener.
1208 destroy: function CSL_destroy()
1210 Services.console.unregisterListener(this);
1211 this.listener = this.window = null;
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.
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.
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]),
1246 * The content window for which we listen to window.console API calls.
1247 * @type nsIDOMWindow
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.
1257 * @see WebConsoleActor
1262 * Initialize the window.console API observer.
1264 init: function CAL_init()
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);
1272 * The console API message observer. When messages are received from the
1273 * observer service we forward them to the remote Web Console instance.
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.
1280 observe: function CAL_observe(aMessage, aTopic)
1286 let apiMessage = aMessage.wrappedJSObject;
1288 let msgWindow = Services.wm.getOuterWindowWithId(apiMessage.ID);
1289 if (!msgWindow || msgWindow.top != this.window) {
1290 // Not the same window!
1295 this.owner.onConsoleAPICall(apiMessage);
1299 * Get the cached messages for the current inner window and its (i)frames.
1301 * @param boolean [aIncludePrivate=false]
1302 * Tells if you want to also retrieve messages coming from private
1303 * windows. Defaults to false.
1305 * The array of cached messages.
1307 getCachedMessages: function CAL_getCachedMessages(aIncludePrivate = false)
1311 // if !this.window, we're in a browser console. Retrieve all events
1312 // for filtering based on privacy.
1314 messages = ConsoleAPIStorage.getEvents();
1316 let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
1317 ids.forEach((id) => {
1318 messages = messages.concat(ConsoleAPIStorage.getEvents(id));
1322 if (aIncludePrivate) {
1326 return messages.filter((m) => !m.private);
1330 * Destroy the console API listener.
1332 destroy: function CAL_destroy()
1334 Services.obs.removeObserver(this, "console-api-log-event");
1335 this.window = this.owner = null;
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.
1353 function JSTermHelpers(aOwner)
1356 * Find a node by ID.
1359 * The ID of the element you want.
1360 * @return nsIDOMNode or null
1361 * The result of calling document.querySelector(aSelector).
1363 aOwner.sandbox.$ = function JSTH_$(aSelector)
1365 return aOwner.window.document.querySelector(aSelector);
1369 * Find the nodes matching a CSS selector.
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).
1376 aOwner.sandbox.$$ = function JSTH_$$(aSelector)
1378 return aOwner.window.document.querySelectorAll(aSelector);
1382 * Runs an xPath query and returns all matched nodes.
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
1390 aOwner.sandbox.$x = function JSTH_$x(aXPath, aContext)
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);
1399 while ((node = results.iterateNext())) {
1407 * Returns the currently selected object in the highlighter.
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.
1414 * @return nsIDOMElement|null
1415 * The DOM element currently selected in the highlighter.
1417 Object.defineProperty(aOwner.sandbox, "$0", {
1419 let window = aOwner.chromeWindow();
1426 target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab);
1429 // If we report this exception the user will get it in the Browser
1430 // Console every time when she evaluates any string.
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;
1448 * Clears the output of the JSTerm.
1450 aOwner.sandbox.clear = function JSTH_clear()
1452 aOwner.helperResult = {
1453 type: "clearOutput",
1458 * Returns the result of Object.keys(aObject).
1460 * @param object aObject
1461 * Object to return the property names from.
1462 * @return array of strings
1464 aOwner.sandbox.keys = function JSTH_keys(aObject)
1466 return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject));
1470 * Returns the values of all properties on aObject.
1472 * @param object aObject
1473 * Object to display the values from.
1474 * @return array of string
1476 aOwner.sandbox.values = function JSTH_values(aObject)
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]);
1489 * Opens a help window in MDN.
1491 aOwner.sandbox.help = function JSTH_help()
1493 aOwner.helperResult = { type: "help" };
1497 * Inspects the passed aObject. This is done by opening the PropertyPanel.
1499 * @param object aObject
1500 * Object to inspect.
1502 aOwner.sandbox.inspect = function JSTH_inspect(aObject)
1504 let dbgObj = aOwner.makeDebuggeeValue(aObject);
1505 let grip = aOwner.createValueGrip(dbgObj);
1506 aOwner.helperResult = {
1507 type: "inspectObject",
1508 input: aOwner.evalInput,
1514 * Prints aObject to the output.
1516 * @param object aObject
1517 * Object to print to the output.
1520 aOwner.sandbox.pprint = function JSTH_pprint(aObject)
1522 if (aObject === null || aObject === undefined || aObject === true ||
1523 aObject === false) {
1524 aOwner.helperResult = {
1526 message: "helperFuncUnsupportedTypeError",
1531 aOwner.helperResult = { rawOutput: true };
1533 if (typeof aObject == "function") {
1534 return aObject + "\n";
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);
1551 let valueGrip = VariablesView.getGrip(obj[name]);
1552 let valueString = VariablesView.getString(valueGrip);
1553 output.push(name + ": " + valueString);
1557 return " " + output.join("\n ");
1561 * Print a string to the output, as-is.
1563 * @param string aString
1564 * A string you want to output.
1567 aOwner.sandbox.print = function JSTH_print(aString)
1569 aOwner.helperResult = { rawOutput: true };
1570 return String(aString);
1573 exports.JSTermHelpers = JSTermHelpers;
1576 ///////////////////////////////////////////////////////////////////////////////
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/
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.
1610 function NetworkResponseListener(aOwner, aHttpActivity)
1612 this.owner = aOwner;
1613 this.receivedData = "";
1614 this.httpActivity = aHttpActivity;
1618 NetworkResponseListener.prototype = {
1620 XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback,
1621 Ci.nsIRequestObserver, Ci.nsISupports]),
1624 * This NetworkResponseListener tracks the NetworkMonitor.openResponses object
1625 * to find the associated uncached headers.
1628 _foundOpenResponse: false,
1631 * The response listener owner.
1636 * The response will be written into the outputStream of this nsIPipe.
1637 * Both ends of the pipe must be blocking.
1642 * The HttpActivity object associated with this response.
1647 * Stores the received data as a string.
1652 * The network response body size.
1657 * The nsIRequest we are started for.
1662 * Set the async listener for the given nsIAsyncInputStream. This allows us to
1663 * wait asynchronously for any data coming from the stream.
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.
1673 setAsyncListener: function NRL_setAsyncListener(aStream, aListener)
1675 // Asynchronously wait for the stream to be readable or closed.
1676 aStream.asyncWait(aListener, 0, 0, Services.tm.mainThread);
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.
1684 * Learn more about nsIStreamListener at:
1685 * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIStreamListener
1687 * @param nsIRequest aRequest
1688 * @param nsISupports aContext
1689 * @param nsIInputStream aInputStream
1690 * @param unsigned long aOffset
1691 * @param unsigned long aCount
1694 function NRL_onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount)
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);
1709 * See documentation at
1710 * https://developer.mozilla.org/En/NsIRequestObserver
1712 * @param nsIRequest aRequest
1713 * @param nsISupports aContext
1715 onStartRequest: function NRL_onStartRequest(aRequest)
1717 this.request = aRequest;
1718 this._findOpenResponse();
1719 // Asynchronously wait for the data coming from the request.
1720 this.setAsyncListener(this.sink.inputStream, this);
1724 * Handle the onStopRequest by closing the sink output stream.
1726 * For more documentation about nsIRequestObserver go to:
1727 * https://developer.mozilla.org/En/NsIRequestObserver
1729 onStopRequest: function NRL_onStopRequest()
1731 this._findOpenResponse();
1732 this.sink.outputStream.close();
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.
1744 _findOpenResponse: function NRL__findOpenResponse()
1746 if (!this.owner || this._foundOpenResponse) {
1750 let openResponse = null;
1752 for each (let item in this.owner.openResponses) {
1753 if (item.channel === this.httpActivity.channel) {
1754 openResponse = item;
1759 if (!openResponse) {
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);
1771 * Clean up the response listener once the response input stream is closed.
1772 * This is called from onStopRequest() or from onInputStreamReady() when the
1776 onStreamClose: function NRL_onStreamClose()
1778 if (!this.httpActivity) {
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);
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));
1802 * Handler for when the response completes. This function cleans up the
1803 * response listener.
1805 * @param string [aData]
1806 * Optional, the received data coming from the response listener or
1809 _onComplete: function NRL__onComplete(aData)
1816 response.size = response.text.length;
1819 response.mimeType = this.request.contentType;
1823 if (!response.mimeType || !NetworkHelper.isTextMimeType(response.mimeType)) {
1824 response.encoding = "base64";
1825 response.text = btoa(response.text);
1828 if (response.mimeType && this.request.contentCharset) {
1829 response.mimeType += "; charset=" + this.request.contentCharset;
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;
1841 this.inputStream = null;
1842 this.request = null;
1847 * The nsIInputStreamCallback for when the request input stream is ready -
1848 * either it has more data or it is closed.
1850 * @param nsIAsyncInputStream aStream
1851 * The sink input stream from which data is coming.
1854 onInputStreamReady: function NRL_onInputStreamReady(aStream)
1856 if (!(aStream instanceof Ci.nsIAsyncInputStream) || !this.httpActivity) {
1862 // This may throw if the stream is closed normally or due to an error.
1863 available = aStream.available();
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
1872 this.onDataAvailable(this.request, null, aStream, 0, available);
1874 this.setAsyncListener(aStream, this);
1877 this.onStreamClose();
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.
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.
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"
1929 // Network response bodies are piped through a buffer of the given size (in
1931 responsePipeSegmentSize: null,
1936 * Whether to save the bodies of network requests and responses. Disabled by
1937 * default to save memory.
1939 get saveRequestAndResponseBodies()
1940 this.owner && this.owner.saveRequestAndResponseBodies,
1943 * Object that holds the HTTP activity objects for ongoing requests.
1948 * Object that holds response headers coming from this._httpResponseExaminer.
1950 openResponses: null,
1953 * The network monitor initializer.
1955 init: function NM_init()
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);
1969 * Observe notifications for the http-on-examine-response topic, coming from
1970 * the nsIObserverService.
1973 * @param nsIHttpChannel aSubject
1974 * @param string aTopic
1977 _httpResponseExaminer: function NM__httpResponseExaminer(aSubject, aTopic)
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)) {
1989 let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
1992 // Try to get the source window of the request.
1993 let win = NetworkHelper.getWindowForRequest(channel);
1994 if (!win || win.top !== this.window) {
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;
2014 response.headers.push({ name: aName, value: aValue });
2018 if (!response.headers.length) {
2019 return; // No need to continue.
2022 if (setCookieHeader) {
2023 response.cookies = NetworkHelper.parseSetCookieHeader(setCookieHeader);
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;
2042 * Begin observing HTTP traffic that originates inside the current tab.
2044 * @see https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIHttpActivityObserver
2046 * @param nsIHttpChannel aChannel
2047 * @param number aActivityType
2048 * @param number aActivitySubtype
2049 * @param number aTimestamp
2050 * @param number aExtraSizeData
2051 * @param string aExtraStringData
2054 function NM_observeActivity(aChannel, aActivityType, aActivitySubtype,
2055 aTimestamp, aExtraSizeData, aExtraStringData)
2058 aActivityType != gActivityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION &&
2059 aActivityType != gActivityDistributor.ACTIVITY_TYPE_SOCKET_TRANSPORT) {
2063 if (!(aChannel instanceof Ci.nsIHttpChannel)) {
2067 aChannel = aChannel.QueryInterface(Ci.nsIHttpChannel);
2069 if (aActivitySubtype ==
2070 gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER) {
2071 this._onRequestHeader(aChannel, aTimestamp, aExtraStringData);
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;
2085 if (!httpActivity) {
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;
2098 httpActivity.timings[stage] = {
2105 switch (aActivitySubtype) {
2106 case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
2107 this._onRequestBodySent(httpActivity);
2109 case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
2110 this._onResponseHeader(httpActivity, aExtraStringData);
2112 case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
2113 this._onTransactionClose(httpActivity);
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.
2127 * @param nsIHttpChannel aChannel
2128 * @param number aTimestamp
2129 * @param string aExtraStringData
2133 function NM__onRequestHeader(aChannel, aTimestamp, aExtraStringData)
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)) {
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 = {
2153 let httpVersionMaj = {};
2154 let httpVersionMin = {};
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.
2164 let callbacks = aChannel.notificationCallbacks;
2165 let xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null;
2166 httpActivity.isXHR = event.isXHR = !!xhrRequest;
2168 httpActivity.isXHR = event.isXHR = false;
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;
2183 let cookieHeader = null;
2185 // Copy the request header data.
2186 aChannel.visitRequestHeaders({
2187 visitHeader: function NM__visitHeader(aName, aValue)
2189 if (aName == "Cookie") {
2190 cookieHeader = aValue;
2192 headers.push({ name: aName, value: aValue });
2197 cookies = NetworkHelper.parseCookieHeader(cookieHeader);
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);
2211 * Create the empty HTTP activity object. This object is used for storing all
2212 * the request and response information.
2214 * This is a HAR-like object. Conformance to the spec is not guaranteed at
2217 * TODO: Bug 708717 - Add support for network log export to HAR
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.
2223 * The new HTTP activity object.
2225 createActivityObject: function NM_createActivityObject(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
2241 * Setup the network response listener for the given HTTP activity. The
2242 * NetworkResponseListener is responsible for storing the response body.
2245 * @param object aHttpActivity
2246 * The HTTP activity object we are tracking.
2248 _setupResponseListener: function NM__setupResponseListener(aHttpActivity)
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
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
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);
2279 * Handler for ACTIVITY_SUBTYPE_REQUEST_BODY_SENT. The request body is logged
2283 * @param object aHttpActivity
2284 * The HTTP activity object we are working with.
2286 _onRequestBodySent: function NM__onRequestBodySent(aHttpActivity)
2288 if (aHttpActivity.discardRequestBody) {
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);
2312 aHttpActivity.owner.addRequestPostData({ text: sentBody });
2317 * Handler for ACTIVITY_SUBTYPE_RESPONSE_HEADER. This method stores
2318 * information about the response headers.
2321 * @param object aHttpActivity
2322 * The HTTP activity object we are working with.
2323 * @param string aExtraStringData
2324 * The uncached response headers.
2327 function NM__onResponseHeader(aHttpActivity, aExtraStringData)
2329 // aExtraStringData contains the uncached response headers. The first line
2330 // contains the response status (e.g. HTTP/1.1 200 OK).
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.
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(" ");
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:
2356 case HTTP_SEE_OTHER:
2357 case HTTP_TEMPORARY_REDIRECT:
2358 aHttpActivity.discardResponseBody = true;
2362 response.discardResponseBody = aHttpActivity.discardResponseBody;
2364 aHttpActivity.owner.addResponseStart(response);
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.
2373 * @param object aHttpActivity
2374 * The HTTP activity object we work with.
2376 _onTransactionClose: function NM__onTransactionClose(aHttpActivity)
2378 let result = this._setupHarTimings(aHttpActivity);
2379 aHttpActivity.owner.addEventTimings(result.total, result.timings);
2380 delete this.openRequests[aHttpActivity.id];
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.
2389 * @param object aHttpActivity
2390 * The HTTP activity object we are working with.
2392 * This object holds two properties:
2393 * - total - the total time for all of the request and response.
2394 * - timings - the HAR timings object.
2396 _setupHarTimings: function NM__setupHarTimings(aHttpActivity)
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
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;
2414 else if (timings.STATUS_SENDING_TO) {
2415 harTimings.connect = timings.STATUS_SENDING_TO.first -
2416 timings.REQUEST_HEADER.first;
2419 harTimings.connect = -1;
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;
2430 harTimings.send = -1;
2433 if (timings.RESPONSE_START) {
2434 harTimings.wait = timings.RESPONSE_START.first -
2435 (timings.REQUEST_BODY_SENT ||
2436 timings.STATUS_SENDING_TO).last;
2439 harTimings.wait = -1;
2442 if (timings.RESPONSE_START && timings.RESPONSE_COMPLETE) {
2443 harTimings.receive = timings.RESPONSE_COMPLETE.last -
2444 timings.RESPONSE_START.first;
2447 harTimings.receive = -1;
2451 for (let timing in harTimings) {
2452 let time = Math.max(Math.round(harTimings[timing] / 1000), -1);
2453 harTimings[timing] = time;
2461 timings: harTimings,
2466 * Suspend Web Console activity. This is called when all Web Consoles are
2469 destroy: function NM_destroy()
2471 if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
2472 Services.obs.removeObserver(this._httpResponseExaminer,
2473 "http-on-examine-response");
2476 gActivityDistributor.removeObserver(this);
2478 this.openRequests = {};
2479 this.openResponses = {};
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
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)
2503 function ConsoleProgressListener(aWindow, aOwner)
2505 this.window = aWindow;
2506 this.owner = aOwner;
2508 exports.ConsoleProgressListener = ConsoleProgressListener;
2510 ConsoleProgressListener.prototype = {
2512 * Constant used for startMonitor()/stopMonitor() that tells you want to
2513 * monitor file loads.
2515 MONITOR_FILE_ACTIVITY: 1,
2518 * Constant used for startMonitor()/stopMonitor() that tells you want to
2519 * monitor page location changes.
2521 MONITOR_LOCATION_CHANGE: 2,
2524 * Tells if you want to monitor file activity.
2528 _fileActivity: false,
2531 * Tells if you want to monitor location changes.
2535 _locationChange: false,
2538 * Tells if the console progress listener is initialized or not.
2542 _initialized: false,
2546 QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
2547 Ci.nsISupportsWeakReference]),
2550 * Initialize the ConsoleProgressListener.
2553 _init: function CPL__init()
2555 if (this._initialized) {
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;
2569 * Start a monitor/tracker related to the current nsIWebProgressListener
2572 * @param number aMonitor
2573 * Tells what you want to track. Available constants:
2574 * - this.MONITOR_FILE_ACTIVITY
2576 * - this.MONITOR_LOCATION_CHANGE
2577 * Track location changes for the top window.
2579 startMonitor: function CPL_startMonitor(aMonitor)
2582 case this.MONITOR_FILE_ACTIVITY:
2583 this._fileActivity = true;
2585 case this.MONITOR_LOCATION_CHANGE:
2586 this._locationChange = true;
2589 throw new Error("ConsoleProgressListener: unknown monitor type " +
2598 * @param number aMonitor
2599 * Tells what you want to stop tracking. See this.startMonitor() for
2600 * the list of constants.
2602 stopMonitor: function CPL_stopMonitor(aMonitor)
2605 case this.MONITOR_FILE_ACTIVITY:
2606 this._fileActivity = false;
2608 case this.MONITOR_LOCATION_CHANGE:
2609 this._locationChange = false;
2612 throw new Error("ConsoleProgressListener: unknown monitor type " +
2616 if (!this._fileActivity && !this._locationChange) {
2622 function CPL_onStateChange(aProgress, aRequest, aState, aStatus)
2628 if (this._fileActivity) {
2629 this._checkFileActivity(aProgress, aRequest, aState, aStatus);
2632 if (this._locationChange) {
2633 this._checkLocationChange(aProgress, aRequest, aState, aStatus);
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.
2644 function CPL__checkFileActivity(aProgress, aRequest, aState, aStatus)
2646 if (!(aState & Ci.nsIWebProgressListener.STATE_START)) {
2651 if (aRequest instanceof Ci.imgIRequest) {
2652 let imgIRequest = aRequest.QueryInterface(Ci.imgIRequest);
2653 uri = imgIRequest.URI;
2655 else if (aRequest instanceof Ci.nsIChannel) {
2656 let nsIChannel = aRequest.QueryInterface(Ci.nsIChannel);
2657 uri = nsIChannel.URI;
2660 if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) {
2664 this.owner.onFileActivity(uri.spec);
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.
2673 _checkLocationChange:
2674 function CPL__checkLocationChange(aProgress, aRequest, aState, aStatus)
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) {
2686 if (isStart && aRequest instanceof Ci.nsIChannel) {
2687 this.owner.onLocationChange("start", aRequest.URI.spec, "");
2690 this.owner.onLocationChange("stop", this.window.location.href,
2691 this.window.document.title);
2695 onLocationChange: function() {},
2696 onStatusChange: function() {},
2697 onProgressChange: function() {},
2698 onSecurityChange: function() {},
2701 * Destroy the ConsoleProgressListener.
2703 destroy: function CPL_destroy()
2705 if (!this._initialized) {
2709 this._initialized = false;
2710 this._fileActivity = false;
2711 this._locationChange = false;
2714 this._webProgress.removeProgressListener(this);
2717 // This can throw during browser shutdown.
2720 this._webProgress = null;
2728 * A ReflowObserver that listens for reflow events from the page.
2729 * Implements nsIReflowObserver.
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)
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]),
2758 * Forward reflow event to listener.
2760 * @param DOMHighResTimeStamp aStart
2761 * @param DOMHighResTimeStamp aEnd
2762 * @param boolean aInterruptible
2764 sendReflow: function CRL_sendReflow(aStart, aEnd, aInterruptible)
2766 let frame = components.stack.caller.caller;
2768 let filename = frame.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();
2776 this.listener.onReflowActivity({
2777 interruptible: aInterruptible,
2780 sourceURL: filename,
2781 sourceLine: frame.lineNumber,
2782 functionName: frame.name
2787 * On uninterruptible reflow
2789 * @param DOMHighResTimeStamp aStart
2790 * @param DOMHighResTimeStamp aEnd
2792 reflow: function CRL_reflow(aStart, aEnd)
2794 this.sendReflow(aStart, aEnd, false);
2798 * On interruptible reflow
2800 * @param DOMHighResTimeStamp aStart
2801 * @param DOMHighResTimeStamp aEnd
2803 reflowInterruptible: function CRL_reflowInterruptible(aStart, aEnd)
2805 this.sendReflow(aStart, aEnd, true);
2809 * Unregister listener.
2811 destroy: function CRL_destroy()
2813 this.docshell.removeWeakReflowObserver(this);
2814 this.listener = this.docshell = null;
2818 function gSequenceId()
2820 return gSequenceId.n++;