toolkit/devtools/webconsole/utils.js

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

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

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

     1 /* -*- 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.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
    14 loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm");
    16 // TODO: Bug 842672 - toolkit/ imports modules from browser/.
    17 // Note that these are only used in JSTermHelpers, see $0 and pprint().
    18 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
    19 loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
    20 loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
    21 loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm");
    23 // Match the function name from the result of toString() or toSource().
    24 //
    25 // Examples:
    26 // (function foobar(a, b) { ...
    27 // function foobar2(a) { ...
    28 // function() { ...
    29 const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
    31 // Match the function arguments from the result of toString() or toSource().
    32 const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/;
    34 let WebConsoleUtils = {
    35   /**
    36    * Convenience function to unwrap a wrapped object.
    37    *
    38    * @param aObject the object to unwrap.
    39    * @return aObject unwrapped.
    40    */
    41   unwrap: function WCU_unwrap(aObject)
    42   {
    43     try {
    44       return XPCNativeWrapper.unwrap(aObject);
    45     }
    46     catch (ex) {
    47       return aObject;
    48     }
    49   },
    51   /**
    52    * Wrap a string in an nsISupportsString object.
    53    *
    54    * @param string aString
    55    * @return nsISupportsString
    56    */
    57   supportsString: function WCU_supportsString(aString)
    58   {
    59     let str = Cc["@mozilla.org/supports-string;1"].
    60               createInstance(Ci.nsISupportsString);
    61     str.data = aString;
    62     return str;
    63   },
    65   /**
    66    * Clone an object.
    67    *
    68    * @param object aObject
    69    *        The object you want cloned.
    70    * @param boolean aRecursive
    71    *        Tells if you want to dig deeper into the object, to clone
    72    *        recursively.
    73    * @param function [aFilter]
    74    *        Optional, filter function, called for every property. Three
    75    *        arguments are passed: key, value and object. Return true if the
    76    *        property should be added to the cloned object. Return false to skip
    77    *        the property.
    78    * @return object
    79    *         The cloned object.
    80    */
    81   cloneObject: function WCU_cloneObject(aObject, aRecursive, aFilter)
    82   {
    83     if (typeof aObject != "object") {
    84       return aObject;
    85     }
    87     let temp;
    89     if (Array.isArray(aObject)) {
    90       temp = [];
    91       Array.forEach(aObject, function(aValue, aIndex) {
    92         if (!aFilter || aFilter(aIndex, aValue, aObject)) {
    93           temp.push(aRecursive ? WCU_cloneObject(aValue) : aValue);
    94         }
    95       });
    96     }
    97     else {
    98       temp = {};
    99       for (let key in aObject) {
   100         let value = aObject[key];
   101         if (aObject.hasOwnProperty(key) &&
   102             (!aFilter || aFilter(key, value, aObject))) {
   103           temp[key] = aRecursive ? WCU_cloneObject(value) : value;
   104         }
   105       }
   106     }
   108     return temp;
   109   },
   111   /**
   112    * Copies certain style attributes from one element to another.
   113    *
   114    * @param nsIDOMNode aFrom
   115    *        The target node.
   116    * @param nsIDOMNode aTo
   117    *        The destination node.
   118    */
   119   copyTextStyles: function WCU_copyTextStyles(aFrom, aTo)
   120   {
   121     let win = aFrom.ownerDocument.defaultView;
   122     let style = win.getComputedStyle(aFrom);
   123     aTo.style.fontFamily = style.getPropertyCSSValue("font-family").cssText;
   124     aTo.style.fontSize = style.getPropertyCSSValue("font-size").cssText;
   125     aTo.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText;
   126     aTo.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
   127   },
   129   /**
   130    * Gets the ID of the inner window of this DOM window.
   131    *
   132    * @param nsIDOMWindow aWindow
   133    * @return integer
   134    *         Inner ID for the given aWindow.
   135    */
   136   getInnerWindowId: function WCU_getInnerWindowId(aWindow)
   137   {
   138     return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
   139              getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
   140   },
   142   /**
   143    * Recursively gather a list of inner window ids given a
   144    * top level window.
   145    *
   146    * @param nsIDOMWindow aWindow
   147    * @return Array
   148    *         list of inner window ids.
   149    */
   150   getInnerWindowIDsForFrames: function WCU_getInnerWindowIDsForFrames(aWindow)
   151   {
   152     let innerWindowID = this.getInnerWindowId(aWindow);
   153     let ids = [innerWindowID];
   155     if (aWindow.frames) {
   156       for (let i = 0; i < aWindow.frames.length; i++) {
   157         let frame = aWindow.frames[i];
   158         ids = ids.concat(this.getInnerWindowIDsForFrames(frame));
   159       }
   160     }
   162     return ids;
   163   },
   166   /**
   167    * Gets the ID of the outer window of this DOM window.
   168    *
   169    * @param nsIDOMWindow aWindow
   170    * @return integer
   171    *         Outer ID for the given aWindow.
   172    */
   173   getOuterWindowId: function WCU_getOuterWindowId(aWindow)
   174   {
   175     return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
   176            getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
   177   },
   179   /**
   180    * Abbreviates the given source URL so that it can be displayed flush-right
   181    * without being too distracting.
   182    *
   183    * @param string aSourceURL
   184    *        The source URL to shorten.
   185    * @param object [aOptions]
   186    *        Options:
   187    *        - onlyCropQuery: boolean that tells if the URL abbreviation function
   188    *        should only remove the query parameters and the hash fragment from
   189    *        the given URL.
   190    * @return string
   191    *         The abbreviated form of the source URL.
   192    */
   193   abbreviateSourceURL:
   194   function WCU_abbreviateSourceURL(aSourceURL, aOptions = {})
   195   {
   196     if (!aOptions.onlyCropQuery && 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.replace(/\/+$/, "");
   218     }
   220     // Remove all but the last path component.
   221     if (!aOptions.onlyCropQuery) {
   222       let slashIndex = aSourceURL.lastIndexOf("/");
   223       if (slashIndex > -1) {
   224         aSourceURL = aSourceURL.substring(slashIndex + 1);
   225       }
   226     }
   228     return aSourceURL;
   229   },
   231   /**
   232    * Tells if the given function is native or not.
   233    *
   234    * @param function aFunction
   235    *        The function you want to check if it is native or not.
   236    * @return boolean
   237    *         True if the given function is native, false otherwise.
   238    */
   239   isNativeFunction: function WCU_isNativeFunction(aFunction)
   240   {
   241     return typeof aFunction == "function" && !("prototype" in aFunction);
   242   },
   244   /**
   245    * Tells if the given property of the provided object is a non-native getter or
   246    * not.
   247    *
   248    * @param object aObject
   249    *        The object that contains the property.
   250    * @param string aProp
   251    *        The property you want to check if it is a getter or not.
   252    * @return boolean
   253    *         True if the given property is a getter, false otherwise.
   254    */
   255   isNonNativeGetter: function WCU_isNonNativeGetter(aObject, aProp)
   256   {
   257     if (typeof aObject != "object") {
   258       return false;
   259     }
   260     let desc = this.getPropertyDescriptor(aObject, aProp);
   261     return desc && desc.get && !this.isNativeFunction(desc.get);
   262   },
   264   /**
   265    * Get the property descriptor for the given object.
   266    *
   267    * @param object aObject
   268    *        The object that contains the property.
   269    * @param string aProp
   270    *        The property you want to get the descriptor for.
   271    * @return object
   272    *         Property descriptor.
   273    */
   274   getPropertyDescriptor: function WCU_getPropertyDescriptor(aObject, aProp)
   275   {
   276     let desc = null;
   277     while (aObject) {
   278       try {
   279         if ((desc = Object.getOwnPropertyDescriptor(aObject, aProp))) {
   280           break;
   281         }
   282       }
   283       catch (ex if (ex.name == "NS_ERROR_XPC_BAD_CONVERT_JS" ||
   284                     ex.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO" ||
   285                     ex.name == "TypeError")) {
   286         // Native getters throw here. See bug 520882.
   287         // null throws TypeError.
   288       }
   289       try {
   290         aObject = Object.getPrototypeOf(aObject);
   291       }
   292       catch (ex if (ex.name == "TypeError")) {
   293         return desc;
   294       }
   295     }
   296     return desc;
   297   },
   299   /**
   300    * Sort function for object properties.
   301    *
   302    * @param object a
   303    *        Property descriptor.
   304    * @param object b
   305    *        Property descriptor.
   306    * @return integer
   307    *         -1 if a.name < b.name,
   308    *         1 if a.name > b.name,
   309    *         0 otherwise.
   310    */
   311   propertiesSort: function WCU_propertiesSort(a, b)
   312   {
   313     // Convert the pair.name to a number for later sorting.
   314     let aNumber = parseFloat(a.name);
   315     let bNumber = parseFloat(b.name);
   317     // Sort numbers.
   318     if (!isNaN(aNumber) && isNaN(bNumber)) {
   319       return -1;
   320     }
   321     else if (isNaN(aNumber) && !isNaN(bNumber)) {
   322       return 1;
   323     }
   324     else if (!isNaN(aNumber) && !isNaN(bNumber)) {
   325       return aNumber - bNumber;
   326     }
   327     // Sort string.
   328     else if (a.name < b.name) {
   329       return -1;
   330     }
   331     else if (a.name > b.name) {
   332       return 1;
   333     }
   334     else {
   335       return 0;
   336     }
   337   },
   339   /**
   340    * Create a grip for the given value. If the value is an object,
   341    * an object wrapper will be created.
   342    *
   343    * @param mixed aValue
   344    *        The value you want to create a grip for, before sending it to the
   345    *        client.
   346    * @param function aObjectWrapper
   347    *        If the value is an object then the aObjectWrapper function is
   348    *        invoked to give us an object grip. See this.getObjectGrip().
   349    * @return mixed
   350    *         The value grip.
   351    */
   352   createValueGrip: function WCU_createValueGrip(aValue, aObjectWrapper)
   353   {
   354     switch (typeof aValue) {
   355       case "boolean":
   356         return aValue;
   357       case "string":
   358         return aObjectWrapper(aValue);
   359       case "number":
   360         if (aValue === Infinity) {
   361           return { type: "Infinity" };
   362         }
   363         else if (aValue === -Infinity) {
   364           return { type: "-Infinity" };
   365         }
   366         else if (Number.isNaN(aValue)) {
   367           return { type: "NaN" };
   368         }
   369         else if (!aValue && 1 / aValue === -Infinity) {
   370           return { type: "-0" };
   371         }
   372         return aValue;
   373       case "undefined":
   374         return { type: "undefined" };
   375       case "object":
   376         if (aValue === null) {
   377           return { type: "null" };
   378         }
   379       case "function":
   380         return aObjectWrapper(aValue);
   381       default:
   382         Cu.reportError("Failed to provide a grip for value of " + typeof aValue
   383                        + ": " + aValue);
   384         return null;
   385     }
   386   },
   388   /**
   389    * Check if the given object is an iterator or a generator.
   390    *
   391    * @param object aObject
   392    *        The object you want to check.
   393    * @return boolean
   394    *         True if the given object is an iterator or a generator, otherwise
   395    *         false is returned.
   396    */
   397   isIteratorOrGenerator: function WCU_isIteratorOrGenerator(aObject)
   398   {
   399     if (aObject === null) {
   400       return false;
   401     }
   403     if (typeof aObject == "object") {
   404       if (typeof aObject.__iterator__ == "function" ||
   405           aObject.constructor && aObject.constructor.name == "Iterator") {
   406         return true;
   407       }
   409       try {
   410         let str = aObject.toString();
   411         if (typeof aObject.next == "function" &&
   412             str.indexOf("[object Generator") == 0) {
   413           return true;
   414         }
   415       }
   416       catch (ex) {
   417         // window.history.next throws in the typeof check above.
   418         return false;
   419       }
   420     }
   422     return false;
   423   },
   425   /**
   426    * Determine if the given request mixes HTTP with HTTPS content.
   427    *
   428    * @param string aRequest
   429    *        Location of the requested content.
   430    * @param string aLocation
   431    *        Location of the current page.
   432    * @return boolean
   433    *         True if the content is mixed, false if not.
   434    */
   435   isMixedHTTPSRequest: function WCU_isMixedHTTPSRequest(aRequest, aLocation)
   436   {
   437     try {
   438       let requestURI = Services.io.newURI(aRequest, null, null);
   439       let contentURI = Services.io.newURI(aLocation, null, null);
   440       return (contentURI.scheme == "https" && requestURI.scheme != "https");
   441     }
   442     catch (ex) {
   443       return false;
   444     }
   445   },
   447   /**
   448    * Helper function to deduce the name of the provided function.
   449    *
   450    * @param funtion aFunction
   451    *        The function whose name will be returned.
   452    * @return string
   453    *         Function name.
   454    */
   455   getFunctionName: function WCF_getFunctionName(aFunction)
   456   {
   457     let name = null;
   458     if (aFunction.name) {
   459       name = aFunction.name;
   460     }
   461     else {
   462       let desc;
   463       try {
   464         desc = aFunction.getOwnPropertyDescriptor("displayName");
   465       }
   466       catch (ex) { }
   467       if (desc && typeof desc.value == "string") {
   468         name = desc.value;
   469       }
   470     }
   471     if (!name) {
   472       try {
   473         let str = (aFunction.toString() || aFunction.toSource()) + "";
   474         name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
   475       }
   476       catch (ex) { }
   477     }
   478     return name;
   479   },
   481   /**
   482    * Get the object class name. For example, the |window| object has the Window
   483    * class name (based on [object Window]).
   484    *
   485    * @param object aObject
   486    *        The object you want to get the class name for.
   487    * @return string
   488    *         The object class name.
   489    */
   490   getObjectClassName: function WCU_getObjectClassName(aObject)
   491   {
   492     if (aObject === null) {
   493       return "null";
   494     }
   495     if (aObject === undefined) {
   496       return "undefined";
   497     }
   499     let type = typeof aObject;
   500     if (type != "object") {
   501       // Grip class names should start with an uppercase letter.
   502       return type.charAt(0).toUpperCase() + type.substr(1);
   503     }
   505     let className;
   507     try {
   508       className = ((aObject + "").match(/^\[object (\S+)\]$/) || [])[1];
   509       if (!className) {
   510         className = ((aObject.constructor + "").match(/^\[object (\S+)\]$/) || [])[1];
   511       }
   512       if (!className && typeof aObject.constructor == "function") {
   513         className = this.getFunctionName(aObject.constructor);
   514       }
   515     }
   516     catch (ex) { }
   518     return className;
   519   },
   521   /**
   522    * Check if the given value is a grip with an actor.
   523    *
   524    * @param mixed aGrip
   525    *        Value you want to check if it is a grip with an actor.
   526    * @return boolean
   527    *         True if the given value is a grip with an actor.
   528    */
   529   isActorGrip: function WCU_isActorGrip(aGrip)
   530   {
   531     return aGrip && typeof(aGrip) == "object" && aGrip.actor;
   532   },
   533 };
   534 exports.Utils = WebConsoleUtils;
   536 //////////////////////////////////////////////////////////////////////////
   537 // Localization
   538 //////////////////////////////////////////////////////////////////////////
   540 WebConsoleUtils.l10n = function WCU_l10n(aBundleURI)
   541 {
   542   this._bundleUri = aBundleURI;
   543 };
   545 WebConsoleUtils.l10n.prototype = {
   546   _stringBundle: null,
   548   get stringBundle()
   549   {
   550     if (!this._stringBundle) {
   551       this._stringBundle = Services.strings.createBundle(this._bundleUri);
   552     }
   553     return this._stringBundle;
   554   },
   556   /**
   557    * Generates a formatted timestamp string for displaying in console messages.
   558    *
   559    * @param integer [aMilliseconds]
   560    *        Optional, allows you to specify the timestamp in milliseconds since
   561    *        the UNIX epoch.
   562    * @return string
   563    *         The timestamp formatted for display.
   564    */
   565   timestampString: function WCU_l10n_timestampString(aMilliseconds)
   566   {
   567     let d = new Date(aMilliseconds ? aMilliseconds : null);
   568     let hours = d.getHours(), minutes = d.getMinutes();
   569     let seconds = d.getSeconds(), milliseconds = d.getMilliseconds();
   570     let parameters = [hours, minutes, seconds, milliseconds];
   571     return this.getFormatStr("timestampFormat", parameters);
   572   },
   574   /**
   575    * Retrieve a localized string.
   576    *
   577    * @param string aName
   578    *        The string name you want from the Web Console string bundle.
   579    * @return string
   580    *         The localized string.
   581    */
   582   getStr: function WCU_l10n_getStr(aName)
   583   {
   584     let result;
   585     try {
   586       result = this.stringBundle.GetStringFromName(aName);
   587     }
   588     catch (ex) {
   589       Cu.reportError("Failed to get string: " + aName);
   590       throw ex;
   591     }
   592     return result;
   593   },
   595   /**
   596    * Retrieve a localized string formatted with values coming from the given
   597    * array.
   598    *
   599    * @param string aName
   600    *        The string name you want from the Web Console string bundle.
   601    * @param array aArray
   602    *        The array of values you want in the formatted string.
   603    * @return string
   604    *         The formatted local string.
   605    */
   606   getFormatStr: function WCU_l10n_getFormatStr(aName, aArray)
   607   {
   608     let result;
   609     try {
   610       result = this.stringBundle.formatStringFromName(aName, aArray, aArray.length);
   611     }
   612     catch (ex) {
   613       Cu.reportError("Failed to format string: " + aName);
   614       throw ex;
   615     }
   616     return result;
   617   },
   618 };
   621 //////////////////////////////////////////////////////////////////////////
   622 // JS Completer
   623 //////////////////////////////////////////////////////////////////////////
   625 (function _JSPP(WCU) {
   626 const STATE_NORMAL = 0;
   627 const STATE_QUOTE = 2;
   628 const STATE_DQUOTE = 3;
   630 const OPEN_BODY = "{[(".split("");
   631 const CLOSE_BODY = "}])".split("");
   632 const OPEN_CLOSE_BODY = {
   633   "{": "}",
   634   "[": "]",
   635   "(": ")",
   636 };
   638 const MAX_COMPLETIONS = 1500;
   640 /**
   641  * Analyses a given string to find the last statement that is interesting for
   642  * later completion.
   643  *
   644  * @param   string aStr
   645  *          A string to analyse.
   646  *
   647  * @returns object
   648  *          If there was an error in the string detected, then a object like
   649  *
   650  *            { err: "ErrorMesssage" }
   651  *
   652  *          is returned, otherwise a object like
   653  *
   654  *            {
   655  *              state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
   656  *              startPos: index of where the last statement begins
   657  *            }
   658  */
   659 function findCompletionBeginning(aStr)
   660 {
   661   let bodyStack = [];
   663   let state = STATE_NORMAL;
   664   let start = 0;
   665   let c;
   666   for (let i = 0; i < aStr.length; i++) {
   667     c = aStr[i];
   669     switch (state) {
   670       // Normal JS state.
   671       case STATE_NORMAL:
   672         if (c == '"') {
   673           state = STATE_DQUOTE;
   674         }
   675         else if (c == "'") {
   676           state = STATE_QUOTE;
   677         }
   678         else if (c == ";") {
   679           start = i + 1;
   680         }
   681         else if (c == " ") {
   682           start = i + 1;
   683         }
   684         else if (OPEN_BODY.indexOf(c) != -1) {
   685           bodyStack.push({
   686             token: c,
   687             start: start
   688           });
   689           start = i + 1;
   690         }
   691         else if (CLOSE_BODY.indexOf(c) != -1) {
   692           var last = bodyStack.pop();
   693           if (!last || OPEN_CLOSE_BODY[last.token] != c) {
   694             return {
   695               err: "syntax error"
   696             };
   697           }
   698           if (c == "}") {
   699             start = i + 1;
   700           }
   701           else {
   702             start = last.start;
   703           }
   704         }
   705         break;
   707       // Double quote state > " <
   708       case STATE_DQUOTE:
   709         if (c == "\\") {
   710           i++;
   711         }
   712         else if (c == "\n") {
   713           return {
   714             err: "unterminated string literal"
   715           };
   716         }
   717         else if (c == '"') {
   718           state = STATE_NORMAL;
   719         }
   720         break;
   722       // Single quote state > ' <
   723       case STATE_QUOTE:
   724         if (c == "\\") {
   725           i++;
   726         }
   727         else if (c == "\n") {
   728           return {
   729             err: "unterminated string literal"
   730           };
   731         }
   732         else if (c == "'") {
   733           state = STATE_NORMAL;
   734         }
   735         break;
   736     }
   737   }
   739   return {
   740     state: state,
   741     startPos: start
   742   };
   743 }
   745 /**
   746  * Provides a list of properties, that are possible matches based on the passed
   747  * Debugger.Environment/Debugger.Object and inputValue.
   748  *
   749  * @param object aDbgObject
   750  *        When the debugger is not paused this Debugger.Object wraps the scope for autocompletion.
   751  *        It is null if the debugger is paused.
   752  * @param object anEnvironment
   753  *        When the debugger is paused this Debugger.Environment is the scope for autocompletion.
   754  *        It is null if the debugger is not paused.
   755  * @param string aInputValue
   756  *        Value that should be completed.
   757  * @param number [aCursor=aInputValue.length]
   758  *        Optional offset in the input where the cursor is located. If this is
   759  *        omitted then the cursor is assumed to be at the end of the input
   760  *        value.
   761  * @returns null or object
   762  *          If no completion valued could be computed, null is returned,
   763  *          otherwise a object with the following form is returned:
   764  *            {
   765  *              matches: [ string, string, string ],
   766  *              matchProp: Last part of the inputValue that was used to find
   767  *                         the matches-strings.
   768  *            }
   769  */
   770 function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor)
   771 {
   772   if (aCursor === undefined) {
   773     aCursor = aInputValue.length;
   774   }
   776   let inputValue = aInputValue.substring(0, aCursor);
   778   // Analyse the inputValue and find the beginning of the last part that
   779   // should be completed.
   780   let beginning = findCompletionBeginning(inputValue);
   782   // There was an error analysing the string.
   783   if (beginning.err) {
   784     return null;
   785   }
   787   // If the current state is not STATE_NORMAL, then we are inside of an string
   788   // which means that no completion is possible.
   789   if (beginning.state != STATE_NORMAL) {
   790     return null;
   791   }
   793   let completionPart = inputValue.substring(beginning.startPos);
   795   // Don't complete on just an empty string.
   796   if (completionPart.trim() == "") {
   797     return null;
   798   }
   800   let lastDot = completionPart.lastIndexOf(".");
   801   if (lastDot > 0 &&
   802       (completionPart[0] == "'" || completionPart[0] == '"') &&
   803       completionPart[lastDot - 1] == completionPart[0]) {
   804     // We are completing a string literal.
   805     let matchProp = completionPart.slice(lastDot + 1);
   806     return getMatchedProps(String.prototype, matchProp);
   807   }
   809   // We are completing a variable / a property lookup.
   810   let properties = completionPart.split(".");
   811   let matchProp = properties.pop().trimLeft();
   812   let obj = aDbgObject;
   814   // The first property must be found in the environment if the debugger is
   815   // paused.
   816   if (anEnvironment) {
   817     if (properties.length == 0) {
   818       return getMatchedPropsInEnvironment(anEnvironment, matchProp);
   819     }
   820     obj = getVariableInEnvironment(anEnvironment, properties.shift());
   821   }
   823   if (!isObjectUsable(obj)) {
   824     return null;
   825   }
   827   // We get the rest of the properties recursively starting from the Debugger.Object
   828   // that wraps the first property
   829   for (let prop of properties) {
   830     prop = prop.trim();
   831     if (!prop) {
   832       return null;
   833     }
   835     if (/\[\d+\]$/.test(prop)) {
   836       // The property to autocomplete is a member of array. For example
   837       // list[i][j]..[n]. Traverse the array to get the actual element.
   838       obj = getArrayMemberProperty(obj, prop);
   839     }
   840     else {
   841       obj = DevToolsUtils.getProperty(obj, prop);
   842     }
   844     if (!isObjectUsable(obj)) {
   845       return null;
   846     }
   847   }
   849   // If the final property is a primitive
   850   if (typeof obj != "object") {
   851     return getMatchedProps(obj, matchProp);
   852   }
   854   return getMatchedPropsInDbgObject(obj, matchProp);
   855 }
   857 /**
   858  * Get the array member of aObj for the given aProp. For example, given
   859  * aProp='list[0][1]' the element at [0][1] of aObj.list is returned.
   860  *
   861  * @param object aObj
   862  *        The object to operate on.
   863  * @param string aProp
   864  *        The property to return.
   865  * @return null or Object
   866  *         Returns null if the property couldn't be located. Otherwise the array
   867  *         member identified by aProp.
   868  */
   869 function getArrayMemberProperty(aObj, aProp)
   870 {
   871   // First get the array.
   872   let obj = aObj;
   873   let propWithoutIndices = aProp.substr(0, aProp.indexOf("["));
   874   obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
   875   if (!isObjectUsable(obj)) {
   876     return null;
   877   }
   879   // Then traverse the list of indices to get the actual element.
   880   let result;
   881   let arrayIndicesRegex = /\[[^\]]*\]/g;
   882   while ((result = arrayIndicesRegex.exec(aProp)) !== null) {
   883     let indexWithBrackets = result[0];
   884     let indexAsText = indexWithBrackets.substr(1, indexWithBrackets.length - 2);
   885     let index = parseInt(indexAsText);
   887     if (isNaN(index)) {
   888       return null;
   889     }
   891     obj = DevToolsUtils.getProperty(obj, index);
   893     if (!isObjectUsable(obj)) {
   894       return null;
   895     }
   896   }
   898   return obj;
   899 }
   901 /**
   902  * Check if the given Debugger.Object can be used for autocomplete.
   903  *
   904  * @param Debugger.Object aObject
   905  *        The Debugger.Object to check.
   906  * @return boolean
   907  *         True if further inspection into the object is possible, or false
   908  *         otherwise.
   909  */
   910 function isObjectUsable(aObject)
   911 {
   912   if (aObject == null) {
   913     return false;
   914   }
   916   if (typeof aObject == "object" && aObject.class == "DeadObject") {
   917     return false;
   918   }
   920   return true;
   921 }
   923 /**
   924  * @see getExactMatch_impl()
   925  */
   926 function getVariableInEnvironment(anEnvironment, aName)
   927 {
   928   return getExactMatch_impl(anEnvironment, aName, DebuggerEnvironmentSupport);
   929 }
   931 /**
   932  * @see getMatchedProps_impl()
   933  */
   934 function getMatchedPropsInEnvironment(anEnvironment, aMatch)
   935 {
   936   return getMatchedProps_impl(anEnvironment, aMatch, DebuggerEnvironmentSupport);
   937 }
   939 /**
   940  * @see getMatchedProps_impl()
   941  */
   942 function getMatchedPropsInDbgObject(aDbgObject, aMatch)
   943 {
   944   return getMatchedProps_impl(aDbgObject, aMatch, DebuggerObjectSupport);
   945 }
   947 /**
   948  * @see getMatchedProps_impl()
   949  */
   950 function getMatchedProps(aObj, aMatch)
   951 {
   952   if (typeof aObj != "object") {
   953     aObj = aObj.constructor.prototype;
   954   }
   955   return getMatchedProps_impl(aObj, aMatch, JSObjectSupport);
   956 }
   958 /**
   959  * Get all properties in the given object (and its parent prototype chain) that
   960  * match a given prefix.
   961  *
   962  * @param mixed aObj
   963  *        Object whose properties we want to filter.
   964  * @param string aMatch
   965  *        Filter for properties that match this string.
   966  * @return object
   967  *         Object that contains the matchProp and the list of names.
   968  */
   969 function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties})
   970 {
   971   let matches = new Set();
   973   // We need to go up the prototype chain.
   974   let iter = chainIterator(aObj);
   975   for (let obj of iter) {
   976     let props = getProperties(obj);
   977     for (let prop of props) {
   978       if (prop.indexOf(aMatch) != 0) {
   979         continue;
   980       }
   982       // If it is an array index, we can't take it.
   983       // This uses a trick: converting a string to a number yields NaN if
   984       // the operation failed, and NaN is not equal to itself.
   985       if (+prop != +prop) {
   986         matches.add(prop);
   987       }
   989       if (matches.size > MAX_COMPLETIONS) {
   990         break;
   991       }
   992     }
   994     if (matches.size > MAX_COMPLETIONS) {
   995       break;
   996     }
   997   }
   999   return {
  1000     matchProp: aMatch,
  1001     matches: [...matches],
  1002   };
  1005 /**
  1006  * Returns a property value based on its name from the given object, by
  1007  * recursively checking the object's prototype.
  1009  * @param object aObj
  1010  *        An object to look the property into.
  1011  * @param string aName
  1012  *        The property that is looked up.
  1013  * @returns object|undefined
  1014  *        A Debugger.Object if the property exists in the object's prototype
  1015  *        chain, undefined otherwise.
  1016  */
  1017 function getExactMatch_impl(aObj, aName, {chainIterator, getProperty})
  1019   // We need to go up the prototype chain.
  1020   let iter = chainIterator(aObj);
  1021   for (let obj of iter) {
  1022     let prop = getProperty(obj, aName, aObj);
  1023     if (prop) {
  1024       return prop.value;
  1027   return undefined;
  1031 let JSObjectSupport = {
  1032   chainIterator: function(aObj)
  1034     while (aObj) {
  1035       yield aObj;
  1036       aObj = Object.getPrototypeOf(aObj);
  1038   },
  1040   getProperties: function(aObj)
  1042     return Object.getOwnPropertyNames(aObj);
  1043   },
  1045   getProperty: function()
  1047     // getProperty is unsafe with raw JS objects.
  1048     throw "Unimplemented!";
  1049   },
  1050 };
  1052 let DebuggerObjectSupport = {
  1053   chainIterator: function(aObj)
  1055     while (aObj) {
  1056       yield aObj;
  1057       aObj = aObj.proto;
  1059   },
  1061   getProperties: function(aObj)
  1063     return aObj.getOwnPropertyNames();
  1064   },
  1066   getProperty: function(aObj, aName, aRootObj)
  1068     // This is left unimplemented in favor to DevToolsUtils.getProperty().
  1069     throw "Unimplemented!";
  1070   },
  1071 };
  1073 let DebuggerEnvironmentSupport = {
  1074   chainIterator: function(aObj)
  1076     while (aObj) {
  1077       yield aObj;
  1078       aObj = aObj.parent;
  1080   },
  1082   getProperties: function(aObj)
  1084     return aObj.names();
  1085   },
  1087   getProperty: function(aObj, aName)
  1089     // TODO: we should use getVariableDescriptor() here - bug 725815.
  1090     let result = aObj.getVariable(aName);
  1091     // FIXME: Need actual UI, bug 941287.
  1092     if (result.optimizedOut || result.missingArguments) {
  1093       return null;
  1095     return result === undefined ? null : { value: result };
  1096   },
  1097 };
  1100 exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider);
  1101 })(WebConsoleUtils);
  1103 ///////////////////////////////////////////////////////////////////////////////
  1104 // The page errors listener
  1105 ///////////////////////////////////////////////////////////////////////////////
  1107 /**
  1108  * The nsIConsoleService listener. This is used to send all of the console
  1109  * messages (JavaScript, CSS and more) to the remote Web Console instance.
  1111  * @constructor
  1112  * @param nsIDOMWindow [aWindow]
  1113  *        Optional - the window object for which we are created. This is used
  1114  *        for filtering out messages that belong to other windows.
  1115  * @param object aListener
  1116  *        The listener object must have one method:
  1117  *        - onConsoleServiceMessage(). This method is invoked with one argument,
  1118  *        the nsIConsoleMessage, whenever a relevant message is received.
  1119  */
  1120 function ConsoleServiceListener(aWindow, aListener)
  1122   this.window = aWindow;
  1123   this.listener = aListener;
  1124   if (this.window) {
  1125     this.layoutHelpers = new LayoutHelpers(this.window);
  1128 exports.ConsoleServiceListener = ConsoleServiceListener;
  1130 ConsoleServiceListener.prototype =
  1132   QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
  1134   /**
  1135    * The content window for which we listen to page errors.
  1136    * @type nsIDOMWindow
  1137    */
  1138   window: null,
  1140   /**
  1141    * The listener object which is notified of messages from the console service.
  1142    * @type object
  1143    */
  1144   listener: null,
  1146   /**
  1147    * Initialize the nsIConsoleService listener.
  1148    */
  1149   init: function CSL_init()
  1151     Services.console.registerListener(this);
  1152   },
  1154   /**
  1155    * The nsIConsoleService observer. This method takes all the script error
  1156    * messages belonging to the current window and sends them to the remote Web
  1157    * Console instance.
  1159    * @param nsIConsoleMessage aMessage
  1160    *        The message object coming from the nsIConsoleService.
  1161    */
  1162   observe: function CSL_observe(aMessage)
  1164     if (!this.listener) {
  1165       return;
  1168     if (this.window) {
  1169       if (!(aMessage instanceof Ci.nsIScriptError) ||
  1170           !aMessage.outerWindowID ||
  1171           !this.isCategoryAllowed(aMessage.category)) {
  1172         return;
  1175       let errorWindow = Services.wm.getOuterWindowWithId(aMessage.outerWindowID);
  1176       if (!errorWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(errorWindow)) {
  1177         return;
  1181     this.listener.onConsoleServiceMessage(aMessage);
  1182   },
  1184   /**
  1185    * Check if the given message category is allowed to be tracked or not.
  1186    * We ignore chrome-originating errors as we only care about content.
  1188    * @param string aCategory
  1189    *        The message category you want to check.
  1190    * @return boolean
  1191    *         True if the category is allowed to be logged, false otherwise.
  1192    */
  1193   isCategoryAllowed: function CSL_isCategoryAllowed(aCategory)
  1195     if (!aCategory) {
  1196       return false;
  1199     switch (aCategory) {
  1200       case "XPConnect JavaScript":
  1201       case "component javascript":
  1202       case "chrome javascript":
  1203       case "chrome registration":
  1204       case "XBL":
  1205       case "XBL Prototype Handler":
  1206       case "XBL Content Sink":
  1207       case "xbl javascript":
  1208         return false;
  1211     return true;
  1212   },
  1214   /**
  1215    * Get the cached page errors for the current inner window and its (i)frames.
  1217    * @param boolean [aIncludePrivate=false]
  1218    *        Tells if you want to also retrieve messages coming from private
  1219    *        windows. Defaults to false.
  1220    * @return array
  1221    *         The array of cached messages. Each element is an nsIScriptError or
  1222    *         an nsIConsoleMessage
  1223    */
  1224   getCachedMessages: function CSL_getCachedMessages(aIncludePrivate = false)
  1226     let errors = Services.console.getMessageArray() || [];
  1228     // if !this.window, we're in a browser console. Still need to filter
  1229     // private messages.
  1230     if (!this.window) {
  1231       return errors.filter((aError) => {
  1232         if (aError instanceof Ci.nsIScriptError) {
  1233           if (!aIncludePrivate && aError.isFromPrivateWindow) {
  1234             return false;
  1238         return true;
  1239       });
  1242     let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
  1244     return errors.filter((aError) => {
  1245       if (aError instanceof Ci.nsIScriptError) {
  1246         if (!aIncludePrivate && aError.isFromPrivateWindow) {
  1247           return false;
  1249         if (ids &&
  1250             (ids.indexOf(aError.innerWindowID) == -1 ||
  1251              !this.isCategoryAllowed(aError.category))) {
  1252           return false;
  1255       else if (ids && ids[0]) {
  1256         // If this is not an nsIScriptError and we need to do window-based
  1257         // filtering we skip this message.
  1258         return false;
  1261       return true;
  1262     });
  1263   },
  1265   /**
  1266    * Remove the nsIConsoleService listener.
  1267    */
  1268   destroy: function CSL_destroy()
  1270     Services.console.unregisterListener(this);
  1271     this.listener = this.window = null;
  1272   },
  1273 };
  1276 ///////////////////////////////////////////////////////////////////////////////
  1277 // The window.console API observer
  1278 ///////////////////////////////////////////////////////////////////////////////
  1280 /**
  1281  * The window.console API observer. This allows the window.console API messages
  1282  * to be sent to the remote Web Console instance.
  1284  * @constructor
  1285  * @param nsIDOMWindow aWindow
  1286  *        Optional - the window object for which we are created. This is used
  1287  *        for filtering out messages that belong to other windows.
  1288  * @param object aOwner
  1289  *        The owner object must have the following methods:
  1290  *        - onConsoleAPICall(). This method is invoked with one argument, the
  1291  *        Console API message that comes from the observer service, whenever
  1292  *        a relevant console API call is received.
  1293  */
  1294 function ConsoleAPIListener(aWindow, aOwner)
  1296   this.window = aWindow;
  1297   this.owner = aOwner;
  1298   if (this.window) {
  1299     this.layoutHelpers = new LayoutHelpers(this.window);
  1302 exports.ConsoleAPIListener = ConsoleAPIListener;
  1304 ConsoleAPIListener.prototype =
  1306   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
  1308   /**
  1309    * The content window for which we listen to window.console API calls.
  1310    * @type nsIDOMWindow
  1311    */
  1312   window: null,
  1314   /**
  1315    * The owner object which is notified of window.console API calls. It must
  1316    * have a onConsoleAPICall method which is invoked with one argument: the
  1317    * console API call object that comes from the observer service.
  1319    * @type object
  1320    * @see WebConsoleActor
  1321    */
  1322   owner: null,
  1324   /**
  1325    * Initialize the window.console API observer.
  1326    */
  1327   init: function CAL_init()
  1329     // Note that the observer is process-wide. We will filter the messages as
  1330     // needed, see CAL_observe().
  1331     Services.obs.addObserver(this, "console-api-log-event", false);
  1332   },
  1334   /**
  1335    * The console API message observer. When messages are received from the
  1336    * observer service we forward them to the remote Web Console instance.
  1338    * @param object aMessage
  1339    *        The message object receives from the observer service.
  1340    * @param string aTopic
  1341    *        The message topic received from the observer service.
  1342    */
  1343   observe: function CAL_observe(aMessage, aTopic)
  1345     if (!this.owner) {
  1346       return;
  1349     let apiMessage = aMessage.wrappedJSObject;
  1350     if (this.window) {
  1351       let msgWindow = Services.wm.getCurrentInnerWindowWithId(apiMessage.innerID);
  1352       if (!msgWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(msgWindow)) {
  1353         // Not the same window!
  1354         return;
  1358     this.owner.onConsoleAPICall(apiMessage);
  1359   },
  1361   /**
  1362    * Get the cached messages for the current inner window and its (i)frames.
  1364    * @param boolean [aIncludePrivate=false]
  1365    *        Tells if you want to also retrieve messages coming from private
  1366    *        windows. Defaults to false.
  1367    * @return array
  1368    *         The array of cached messages.
  1369    */
  1370   getCachedMessages: function CAL_getCachedMessages(aIncludePrivate = false)
  1372     let messages = [];
  1373     let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
  1374                               .getService(Ci.nsIConsoleAPIStorage);
  1376     // if !this.window, we're in a browser console. Retrieve all events
  1377     // for filtering based on privacy.
  1378     if (!this.window) {
  1379       messages = ConsoleAPIStorage.getEvents();
  1380     } else {
  1381       let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
  1382       ids.forEach((id) => {
  1383         messages = messages.concat(ConsoleAPIStorage.getEvents(id));
  1384       });
  1387     if (aIncludePrivate) {
  1388       return messages;
  1391     return messages.filter((m) => !m.private);
  1392   },
  1394   /**
  1395    * Destroy the console API listener.
  1396    */
  1397   destroy: function CAL_destroy()
  1399     Services.obs.removeObserver(this, "console-api-log-event");
  1400     this.window = this.owner = null;
  1401   },
  1402 };
  1406 /**
  1407  * JSTerm helper functions.
  1409  * Defines a set of functions ("helper functions") that are available from the
  1410  * Web Console but not from the web page.
  1412  * A list of helper functions used by Firebug can be found here:
  1413  *   http://getfirebug.com/wiki/index.php/Command_Line_API
  1415  * @param object aOwner
  1416  *        The owning object.
  1417  */
  1418 function JSTermHelpers(aOwner)
  1420   /**
  1421    * Find a node by ID.
  1423    * @param string aId
  1424    *        The ID of the element you want.
  1425    * @return nsIDOMNode or null
  1426    *         The result of calling document.querySelector(aSelector).
  1427    */
  1428   aOwner.sandbox.$ = function JSTH_$(aSelector)
  1430     return aOwner.window.document.querySelector(aSelector);
  1431   };
  1433   /**
  1434    * Find the nodes matching a CSS selector.
  1436    * @param string aSelector
  1437    *        A string that is passed to window.document.querySelectorAll.
  1438    * @return nsIDOMNodeList
  1439    *         Returns the result of document.querySelectorAll(aSelector).
  1440    */
  1441   aOwner.sandbox.$$ = function JSTH_$$(aSelector)
  1443     return aOwner.window.document.querySelectorAll(aSelector);
  1444   };
  1446   /**
  1447    * Runs an xPath query and returns all matched nodes.
  1449    * @param string aXPath
  1450    *        xPath search query to execute.
  1451    * @param [optional] nsIDOMNode aContext
  1452    *        Context to run the xPath query on. Uses window.document if not set.
  1453    * @return array of nsIDOMNode
  1454    */
  1455   aOwner.sandbox.$x = function JSTH_$x(aXPath, aContext)
  1457     let nodes = new aOwner.window.wrappedJSObject.Array();
  1458     let doc = aOwner.window.document;
  1459     aContext = aContext || doc;
  1461     let results = doc.evaluate(aXPath, aContext, null,
  1462                                Ci.nsIDOMXPathResult.ANY_TYPE, null);
  1463     let node;
  1464     while ((node = results.iterateNext())) {
  1465       nodes.push(node);
  1468     return nodes;
  1469   };
  1471   /**
  1472    * Returns the currently selected object in the highlighter.
  1474    * TODO: this implementation crosses the client/server boundaries! This is not
  1475    * usable within a remote browser. To implement this feature correctly we need
  1476    * support for remote inspection capabilities within the Inspector as well.
  1477    * See bug 787975.
  1479    * @return nsIDOMElement|null
  1480    *         The DOM element currently selected in the highlighter.
  1481    */
  1482    Object.defineProperty(aOwner.sandbox, "$0", {
  1483     get: function() {
  1484       let window = aOwner.chromeWindow();
  1485       if (!window) {
  1486         return null;
  1489       let target = null;
  1490       try {
  1491         target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab);
  1493       catch (ex) {
  1494         // If we report this exception the user will get it in the Browser
  1495         // Console every time when she evaluates any string.
  1498       if (!target) {
  1499         return null;
  1502       let toolbox = gDevTools.getToolbox(target);
  1503       let node = toolbox && toolbox.selection ? toolbox.selection.node : null;
  1505       return node ? aOwner.makeDebuggeeValue(node) : null;
  1506     },
  1507     enumerable: true,
  1508     configurable: false
  1509   });
  1511   /**
  1512    * Clears the output of the JSTerm.
  1513    */
  1514   aOwner.sandbox.clear = function JSTH_clear()
  1516     aOwner.helperResult = {
  1517       type: "clearOutput",
  1518     };
  1519   };
  1521   /**
  1522    * Returns the result of Object.keys(aObject).
  1524    * @param object aObject
  1525    *        Object to return the property names from.
  1526    * @return array of strings
  1527    */
  1528   aOwner.sandbox.keys = function JSTH_keys(aObject)
  1530     return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject));
  1531   };
  1533   /**
  1534    * Returns the values of all properties on aObject.
  1536    * @param object aObject
  1537    *        Object to display the values from.
  1538    * @return array of string
  1539    */
  1540   aOwner.sandbox.values = function JSTH_values(aObject)
  1542     let arrValues = new aOwner.window.wrappedJSObject.Array();
  1543     let obj = WebConsoleUtils.unwrap(aObject);
  1545     for (let prop in obj) {
  1546       arrValues.push(obj[prop]);
  1549     return arrValues;
  1550   };
  1552   /**
  1553    * Opens a help window in MDN.
  1554    */
  1555   aOwner.sandbox.help = function JSTH_help()
  1557     aOwner.helperResult = { type: "help" };
  1558   };
  1560   /**
  1561    * Change the JS evaluation scope.
  1563    * @param DOMElement|string|window aWindow
  1564    *        The window object to use for eval scope. This can be a string that
  1565    *        is used to perform document.querySelector(), to find the iframe that
  1566    *        you want to cd() to. A DOMElement can be given as well, the
  1567    *        .contentWindow property is used. Lastly, you can directly pass
  1568    *        a window object. If you call cd() with no arguments, the current
  1569    *        eval scope is cleared back to its default (the top window).
  1570    */
  1571   aOwner.sandbox.cd = function JSTH_cd(aWindow)
  1573     if (!aWindow) {
  1574       aOwner.consoleActor.evalWindow = null;
  1575       aOwner.helperResult = { type: "cd" };
  1576       return;
  1579     if (typeof aWindow == "string") {
  1580       aWindow = aOwner.window.document.querySelector(aWindow);
  1582     if (aWindow instanceof Ci.nsIDOMElement && aWindow.contentWindow) {
  1583       aWindow = aWindow.contentWindow;
  1585     if (!(aWindow instanceof Ci.nsIDOMWindow)) {
  1586       aOwner.helperResult = { type: "error", message: "cdFunctionInvalidArgument" };
  1587       return;
  1590     aOwner.consoleActor.evalWindow = aWindow;
  1591     aOwner.helperResult = { type: "cd" };
  1592   };
  1594   /**
  1595    * Inspects the passed aObject. This is done by opening the PropertyPanel.
  1597    * @param object aObject
  1598    *        Object to inspect.
  1599    */
  1600   aOwner.sandbox.inspect = function JSTH_inspect(aObject)
  1602     let dbgObj = aOwner.makeDebuggeeValue(aObject);
  1603     let grip = aOwner.createValueGrip(dbgObj);
  1604     aOwner.helperResult = {
  1605       type: "inspectObject",
  1606       input: aOwner.evalInput,
  1607       object: grip,
  1608     };
  1609   };
  1611   /**
  1612    * Prints aObject to the output.
  1614    * @param object aObject
  1615    *        Object to print to the output.
  1616    * @return string
  1617    */
  1618   aOwner.sandbox.pprint = function JSTH_pprint(aObject)
  1620     if (aObject === null || aObject === undefined || aObject === true ||
  1621         aObject === false) {
  1622       aOwner.helperResult = {
  1623         type: "error",
  1624         message: "helperFuncUnsupportedTypeError",
  1625       };
  1626       return null;
  1629     aOwner.helperResult = { rawOutput: true };
  1631     if (typeof aObject == "function") {
  1632       return aObject + "\n";
  1635     let output = [];
  1637     let obj = WebConsoleUtils.unwrap(aObject);
  1638     for (let name in obj) {
  1639       let desc = WebConsoleUtils.getPropertyDescriptor(obj, name) || {};
  1640       if (desc.get || desc.set) {
  1641         // TODO: Bug 842672 - toolkit/ imports modules from browser/.
  1642         let getGrip = VariablesView.getGrip(desc.get);
  1643         let setGrip = VariablesView.getGrip(desc.set);
  1644         let getString = VariablesView.getString(getGrip);
  1645         let setString = VariablesView.getString(setGrip);
  1646         output.push(name + ":", "  get: " + getString, "  set: " + setString);
  1648       else {
  1649         let valueGrip = VariablesView.getGrip(obj[name]);
  1650         let valueString = VariablesView.getString(valueGrip);
  1651         output.push(name + ": " + valueString);
  1655     return "  " + output.join("\n  ");
  1656   };
  1658   /**
  1659    * Print a string to the output, as-is.
  1661    * @param string aString
  1662    *        A string you want to output.
  1663    * @return void
  1664    */
  1665   aOwner.sandbox.print = function JSTH_print(aString)
  1667     aOwner.helperResult = { rawOutput: true };
  1668     return String(aString);
  1669   };
  1671 exports.JSTermHelpers = JSTermHelpers;
  1674 /**
  1675  * A ReflowObserver that listens for reflow events from the page.
  1676  * Implements nsIReflowObserver.
  1678  * @constructor
  1679  * @param object aWindow
  1680  *        The window for which we need to track reflow.
  1681  * @param object aOwner
  1682  *        The listener owner which needs to implement:
  1683  *        - onReflowActivity(aReflowInfo)
  1684  */
  1686 function ConsoleReflowListener(aWindow, aListener)
  1688   this.docshell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
  1689                          .getInterface(Ci.nsIWebNavigation)
  1690                          .QueryInterface(Ci.nsIDocShell);
  1691   this.listener = aListener;
  1692   this.docshell.addWeakReflowObserver(this);
  1695 exports.ConsoleReflowListener = ConsoleReflowListener;
  1697 ConsoleReflowListener.prototype =
  1699   QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
  1700                                          Ci.nsISupportsWeakReference]),
  1701   docshell: null,
  1702   listener: null,
  1704   /**
  1705    * Forward reflow event to listener.
  1707    * @param DOMHighResTimeStamp aStart
  1708    * @param DOMHighResTimeStamp aEnd
  1709    * @param boolean aInterruptible
  1710    */
  1711   sendReflow: function CRL_sendReflow(aStart, aEnd, aInterruptible)
  1713     let frame = components.stack.caller.caller;
  1715     let filename = frame.filename;
  1717     if (filename) {
  1718       // Because filename could be of the form "xxx.js -> xxx.js -> xxx.js",
  1719       // we only take the last part.
  1720       filename = filename.split(" ").pop();
  1723     this.listener.onReflowActivity({
  1724       interruptible: aInterruptible,
  1725       start: aStart,
  1726       end: aEnd,
  1727       sourceURL: filename,
  1728       sourceLine: frame.lineNumber,
  1729       functionName: frame.name
  1730     });
  1731   },
  1733   /**
  1734    * On uninterruptible reflow
  1736    * @param DOMHighResTimeStamp aStart
  1737    * @param DOMHighResTimeStamp aEnd
  1738    */
  1739   reflow: function CRL_reflow(aStart, aEnd)
  1741     this.sendReflow(aStart, aEnd, false);
  1742   },
  1744   /**
  1745    * On interruptible reflow
  1747    * @param DOMHighResTimeStamp aStart
  1748    * @param DOMHighResTimeStamp aEnd
  1749    */
  1750   reflowInterruptible: function CRL_reflowInterruptible(aStart, aEnd)
  1752     this.sendReflow(aStart, aEnd, true);
  1753   },
  1755   /**
  1756    * Unregister listener.
  1757    */
  1758   destroy: function CRL_destroy()
  1760     this.docshell.removeWeakReflowObserver(this);
  1761     this.listener = this.docshell = null;
  1762   },
  1763 };
  1765 function gSequenceId()
  1767   return gSequenceId.n++;
  1769 gSequenceId.n = 0;

mercurial