toolkit/devtools/Console.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/devtools/Console.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,655 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +/**
    1.11 + * Define a 'console' API to roughly match the implementation provided by
    1.12 + * Firebug.
    1.13 + * This module helps cases where code is shared between the web and Firefox.
    1.14 + * See also Browser.jsm for an implementation of other web constants to help
    1.15 + * sharing code between the web and firefox;
    1.16 + *
    1.17 + * The API is only be a rough approximation for 3 reasons:
    1.18 + * - The Firebug console API is implemented in many places with differences in
    1.19 + *   the implementations, so there isn't a single reference to adhere to
    1.20 + * - The Firebug console is a rich display compared with dump(), so there will
    1.21 + *   be many things that we can't replicate
    1.22 + * - The primary use of this API is debugging and error logging so the perfect
    1.23 + *   implementation isn't always required (or even well defined)
    1.24 + */
    1.25 +
    1.26 +this.EXPORTED_SYMBOLS = [ "console", "ConsoleAPI" ];
    1.27 +
    1.28 +const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
    1.29 +
    1.30 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.31 +
    1.32 +XPCOMUtils.defineLazyModuleGetter(this, "Services",
    1.33 +                                  "resource://gre/modules/Services.jsm");
    1.34 +
    1.35 +let gTimerRegistry = new Map();
    1.36 +
    1.37 +/**
    1.38 + * String utility to ensure that strings are a specified length. Strings
    1.39 + * that are too long are truncated to the max length and the last char is
    1.40 + * set to "_". Strings that are too short are padded with spaces.
    1.41 + *
    1.42 + * @param {string} aStr
    1.43 + *        The string to format to the correct length
    1.44 + * @param {number} aMaxLen
    1.45 + *        The maximum allowed length of the returned string
    1.46 + * @param {number} aMinLen (optional)
    1.47 + *        The minimum allowed length of the returned string. If undefined,
    1.48 + *        then aMaxLen will be used
    1.49 + * @param {object} aOptions (optional)
    1.50 + *        An object allowing format customization. Allowed customizations:
    1.51 + *          'truncate' - can take the value "start" to truncate strings from
    1.52 + *             the start as opposed to the end or "center" to truncate
    1.53 + *             strings in the center.
    1.54 + *          'align' - takes an alignment when padding is needed for MinLen,
    1.55 + *             either "start" or "end".  Defaults to "start".
    1.56 + * @return {string}
    1.57 + *        The original string formatted to fit the specified lengths
    1.58 + */
    1.59 +function fmt(aStr, aMaxLen, aMinLen, aOptions) {
    1.60 +  if (aMinLen == null) {
    1.61 +    aMinLen = aMaxLen;
    1.62 +  }
    1.63 +  if (aStr == null) {
    1.64 +    aStr = "";
    1.65 +  }
    1.66 +  if (aStr.length > aMaxLen) {
    1.67 +    if (aOptions && aOptions.truncate == "start") {
    1.68 +      return "_" + aStr.substring(aStr.length - aMaxLen + 1);
    1.69 +    }
    1.70 +    else if (aOptions && aOptions.truncate == "center") {
    1.71 +      let start = aStr.substring(0, (aMaxLen / 2));
    1.72 +
    1.73 +      let end = aStr.substring((aStr.length - (aMaxLen / 2)) + 1);
    1.74 +      return start + "_" + end;
    1.75 +    }
    1.76 +    else {
    1.77 +      return aStr.substring(0, aMaxLen - 1) + "_";
    1.78 +    }
    1.79 +  }
    1.80 +  if (aStr.length < aMinLen) {
    1.81 +    let padding = Array(aMinLen - aStr.length + 1).join(" ");
    1.82 +    aStr = (aOptions.align === "end") ? padding + aStr : aStr + padding;
    1.83 +  }
    1.84 +  return aStr;
    1.85 +}
    1.86 +
    1.87 +/**
    1.88 + * Utility to extract the constructor name of an object.
    1.89 + * Object.toString gives: "[object ?????]"; we want the "?????".
    1.90 + *
    1.91 + * @param {object} aObj
    1.92 + *        The object from which to extract the constructor name
    1.93 + * @return {string}
    1.94 + *        The constructor name
    1.95 + */
    1.96 +function getCtorName(aObj) {
    1.97 +  if (aObj === null) {
    1.98 +    return "null";
    1.99 +  }
   1.100 +  if (aObj === undefined) {
   1.101 +    return "undefined";
   1.102 +  }
   1.103 +  if (aObj.constructor && aObj.constructor.name) {
   1.104 +    return aObj.constructor.name;
   1.105 +  }
   1.106 +  // If that fails, use Objects toString which sometimes gives something
   1.107 +  // better than 'Object', and at least defaults to Object if nothing better
   1.108 +  return Object.prototype.toString.call(aObj).slice(8, -1);
   1.109 +}
   1.110 +
   1.111 +/**
   1.112 + * A single line stringification of an object designed for use by humans
   1.113 + *
   1.114 + * @param {any} aThing
   1.115 + *        The object to be stringified
   1.116 + * @param {boolean} aAllowNewLines
   1.117 + * @return {string}
   1.118 + *        A single line representation of aThing, which will generally be at
   1.119 + *        most 80 chars long
   1.120 + */
   1.121 +function stringify(aThing, aAllowNewLines) {
   1.122 +  if (aThing === undefined) {
   1.123 +    return "undefined";
   1.124 +  }
   1.125 +
   1.126 +  if (aThing === null) {
   1.127 +    return "null";
   1.128 +  }
   1.129 +
   1.130 +  if (typeof aThing == "object") {
   1.131 +    let type = getCtorName(aThing);
   1.132 +    if (aThing instanceof Components.interfaces.nsIDOMNode && aThing.tagName) {
   1.133 +      return debugElement(aThing);
   1.134 +    }
   1.135 +    type = (type == "Object" ? "" : type + " ");
   1.136 +    let json;
   1.137 +    try {
   1.138 +      json = JSON.stringify(aThing);
   1.139 +    }
   1.140 +    catch (ex) {
   1.141 +      // Can't use a real ellipsis here, because cmd.exe isn't unicode-enabled
   1.142 +      json = "{" + Object.keys(aThing).join(":..,") + ":.., " + "}";
   1.143 +    }
   1.144 +    return type + json;
   1.145 +  }
   1.146 +
   1.147 +  if (typeof aThing == "function") {
   1.148 +    return aThing.toString().replace(/\s+/g, " ");
   1.149 +  }
   1.150 +
   1.151 +  let str = aThing.toString();
   1.152 +  if (!aAllowNewLines) {
   1.153 +    str = str.replace(/\n/g, "|");
   1.154 +  }
   1.155 +  return str;
   1.156 +}
   1.157 +
   1.158 +/**
   1.159 + * Create a simple debug representation of a given element.
   1.160 + *
   1.161 + * @param {nsIDOMElement} aElement
   1.162 + *        The element to debug
   1.163 + * @return {string}
   1.164 + *        A simple single line representation of aElement
   1.165 + */
   1.166 +function debugElement(aElement) {
   1.167 +  return "<" + aElement.tagName +
   1.168 +      (aElement.id ? "#" + aElement.id : "") +
   1.169 +      (aElement.className ?
   1.170 +          "." + aElement.className.split(" ").join(" .") :
   1.171 +          "") +
   1.172 +      ">";
   1.173 +}
   1.174 +
   1.175 +/**
   1.176 + * A multi line stringification of an object, designed for use by humans
   1.177 + *
   1.178 + * @param {any} aThing
   1.179 + *        The object to be stringified
   1.180 + * @return {string}
   1.181 + *        A multi line representation of aThing
   1.182 + */
   1.183 +function log(aThing) {
   1.184 +  if (aThing === null) {
   1.185 +    return "null\n";
   1.186 +  }
   1.187 +
   1.188 +  if (aThing === undefined) {
   1.189 +    return "undefined\n";
   1.190 +  }
   1.191 +
   1.192 +  if (typeof aThing == "object") {
   1.193 +    let reply = "";
   1.194 +    let type = getCtorName(aThing);
   1.195 +    if (type == "Map") {
   1.196 +      reply += "Map\n";
   1.197 +      for (let [key, value] of aThing) {
   1.198 +        reply += logProperty(key, value);
   1.199 +      }
   1.200 +    }
   1.201 +    else if (type == "Set") {
   1.202 +      let i = 0;
   1.203 +      reply += "Set\n";
   1.204 +      for (let value of aThing) {
   1.205 +        reply += logProperty('' + i, value);
   1.206 +        i++;
   1.207 +      }
   1.208 +    }
   1.209 +    else if (type.match("Error$") ||
   1.210 +             (typeof aThing.name == "string" &&
   1.211 +              aThing.name.match("NS_ERROR_"))) {
   1.212 +      reply += "  Message: " + aThing + "\n";
   1.213 +      if (aThing.stack) {
   1.214 +        reply += "  Stack:\n";
   1.215 +        var frame = aThing.stack;
   1.216 +        while (frame) {
   1.217 +          reply += "    " + frame + "\n";
   1.218 +          frame = frame.caller;
   1.219 +        }
   1.220 +      }
   1.221 +    }
   1.222 +    else if (aThing instanceof Components.interfaces.nsIDOMNode && aThing.tagName) {
   1.223 +      reply += "  " + debugElement(aThing) + "\n";
   1.224 +    }
   1.225 +    else {
   1.226 +      let keys = Object.getOwnPropertyNames(aThing);
   1.227 +      if (keys.length > 0) {
   1.228 +        reply += type + "\n";
   1.229 +        keys.forEach(function(aProp) {
   1.230 +          reply += logProperty(aProp, aThing[aProp]);
   1.231 +        });
   1.232 +      }
   1.233 +      else {
   1.234 +        reply += type + "\n";
   1.235 +        let root = aThing;
   1.236 +        let logged = [];
   1.237 +        while (root != null) {
   1.238 +          let properties = Object.keys(root);
   1.239 +          properties.sort();
   1.240 +          properties.forEach(function(property) {
   1.241 +            if (!(property in logged)) {
   1.242 +              logged[property] = property;
   1.243 +              reply += logProperty(property, aThing[property]);
   1.244 +            }
   1.245 +          });
   1.246 +
   1.247 +          root = Object.getPrototypeOf(root);
   1.248 +          if (root != null) {
   1.249 +            reply += '  - prototype ' + getCtorName(root) + '\n';
   1.250 +          }
   1.251 +        }
   1.252 +      }
   1.253 +    }
   1.254 +
   1.255 +    return reply;
   1.256 +  }
   1.257 +
   1.258 +  return "  " + aThing.toString() + "\n";
   1.259 +}
   1.260 +
   1.261 +/**
   1.262 + * Helper for log() which converts a property/value pair into an output
   1.263 + * string
   1.264 + *
   1.265 + * @param {string} aProp
   1.266 + *        The name of the property to include in the output string
   1.267 + * @param {object} aValue
   1.268 + *        Value assigned to aProp to be converted to a single line string
   1.269 + * @return {string}
   1.270 + *        Multi line output string describing the property/value pair
   1.271 + */
   1.272 +function logProperty(aProp, aValue) {
   1.273 +  let reply = "";
   1.274 +  if (aProp == "stack" && typeof value == "string") {
   1.275 +    let trace = parseStack(aValue);
   1.276 +    reply += formatTrace(trace);
   1.277 +  }
   1.278 +  else {
   1.279 +    reply += "    - " + aProp + " = " + stringify(aValue) + "\n";
   1.280 +  }
   1.281 +  return reply;
   1.282 +}
   1.283 +
   1.284 +const LOG_LEVELS = {
   1.285 +  "all": Number.MIN_VALUE,
   1.286 +  "debug": 2,
   1.287 +  "log": 3,
   1.288 +  "info": 3,
   1.289 +  "trace": 3,
   1.290 +  "timeEnd": 3,
   1.291 +  "time": 3,
   1.292 +  "group": 3,
   1.293 +  "groupEnd": 3,
   1.294 +  "dir": 3,
   1.295 +  "dirxml": 3,
   1.296 +  "warn": 4,
   1.297 +  "error": 5,
   1.298 +  "off": Number.MAX_VALUE,
   1.299 +};
   1.300 +
   1.301 +/**
   1.302 + * Helper to tell if a console message of `aLevel` type
   1.303 + * should be logged in stdout and sent to consoles given
   1.304 + * the current maximum log level being defined in `console.maxLogLevel`
   1.305 + *
   1.306 + * @param {string} aLevel
   1.307 + *        Console message log level
   1.308 + * @param {string} aMaxLevel {string}
   1.309 + *        String identifier (See LOG_LEVELS for possible
   1.310 + *        values) that allows to filter which messages
   1.311 + *        are logged based on their log level
   1.312 + * @return {boolean}
   1.313 + *        Should this message be logged or not?
   1.314 + */
   1.315 +function shouldLog(aLevel, aMaxLevel) {
   1.316 +  return LOG_LEVELS[aMaxLevel] <= LOG_LEVELS[aLevel];
   1.317 +}
   1.318 +
   1.319 +/**
   1.320 + * Parse a stack trace, returning an array of stack frame objects, where
   1.321 + * each has filename/lineNumber/functionName members
   1.322 + *
   1.323 + * @param {string} aStack
   1.324 + *        The serialized stack trace
   1.325 + * @return {object[]}
   1.326 + *        Array of { file: "...", line: NNN, call: "..." } objects
   1.327 + */
   1.328 +function parseStack(aStack) {
   1.329 +  let trace = [];
   1.330 +  aStack.split("\n").forEach(function(line) {
   1.331 +    if (!line) {
   1.332 +      return;
   1.333 +    }
   1.334 +    let at = line.lastIndexOf("@");
   1.335 +    let posn = line.substring(at + 1);
   1.336 +    trace.push({
   1.337 +      filename: posn.split(":")[0],
   1.338 +      lineNumber: posn.split(":")[1],
   1.339 +      functionName: line.substring(0, at)
   1.340 +    });
   1.341 +  });
   1.342 +  return trace;
   1.343 +}
   1.344 +
   1.345 +/**
   1.346 + * Format a frame coming from Components.stack such that it can be used by the
   1.347 + * Browser Console, via console-api-log-event notifications.
   1.348 + *
   1.349 + * @param {object} aFrame
   1.350 + *        The stack frame from which to begin the walk.
   1.351 + * @param {number=0} aMaxDepth
   1.352 + *        Maximum stack trace depth. Default is 0 - no depth limit.
   1.353 + * @return {object[]}
   1.354 + *         An array of {filename, lineNumber, functionName, language} objects.
   1.355 + *         These objects follow the same format as other console-api-log-event
   1.356 + *         messages.
   1.357 + */
   1.358 +function getStack(aFrame, aMaxDepth = 0) {
   1.359 +  if (!aFrame) {
   1.360 +    aFrame = Components.stack.caller;
   1.361 +  }
   1.362 +  let trace = [];
   1.363 +  while (aFrame) {
   1.364 +    trace.push({
   1.365 +      filename: aFrame.filename,
   1.366 +      lineNumber: aFrame.lineNumber,
   1.367 +      functionName: aFrame.name,
   1.368 +      language: aFrame.language,
   1.369 +    });
   1.370 +    if (aMaxDepth == trace.length) {
   1.371 +      break;
   1.372 +    }
   1.373 +    aFrame = aFrame.caller;
   1.374 +  }
   1.375 +  return trace;
   1.376 +}
   1.377 +
   1.378 +/**
   1.379 + * Take the output from parseStack() and convert it to nice readable
   1.380 + * output
   1.381 + *
   1.382 + * @param {object[]} aTrace
   1.383 + *        Array of trace objects as created by parseStack()
   1.384 + * @return {string} Multi line report of the stack trace
   1.385 + */
   1.386 +function formatTrace(aTrace) {
   1.387 +  let reply = "";
   1.388 +  aTrace.forEach(function(frame) {
   1.389 +    reply += fmt(frame.filename, 20, 20, { truncate: "start" }) + " " +
   1.390 +             fmt(frame.lineNumber, 5, 5) + " " +
   1.391 +             fmt(frame.functionName, 75, 0, { truncate: "center" }) + "\n";
   1.392 +  });
   1.393 +  return reply;
   1.394 +}
   1.395 +
   1.396 +/**
   1.397 + * Create a new timer by recording the current time under the specified name.
   1.398 + *
   1.399 + * @param {string} aName
   1.400 + *        The name of the timer.
   1.401 + * @param {number} [aTimestamp=Date.now()]
   1.402 + *        Optional timestamp that tells when the timer was originally started.
   1.403 + * @return {object}
   1.404 + *         The name property holds the timer name and the started property
   1.405 + *         holds the time the timer was started. In case of error, it returns
   1.406 + *         an object with the single property "error" that contains the key
   1.407 + *         for retrieving the localized error message.
   1.408 + */
   1.409 +function startTimer(aName, aTimestamp) {
   1.410 +  let key = aName.toString();
   1.411 +  if (!gTimerRegistry.has(key)) {
   1.412 +    gTimerRegistry.set(key, aTimestamp || Date.now());
   1.413 +  }
   1.414 +  return { name: aName, started: gTimerRegistry.get(key) };
   1.415 +}
   1.416 +
   1.417 +/**
   1.418 + * Stop the timer with the specified name and retrieve the elapsed time.
   1.419 + *
   1.420 + * @param {string} aName
   1.421 + *        The name of the timer.
   1.422 + * @param {number} [aTimestamp=Date.now()]
   1.423 + *        Optional timestamp that tells when the timer was originally stopped.
   1.424 + * @return {object}
   1.425 + *         The name property holds the timer name and the duration property
   1.426 + *         holds the number of milliseconds since the timer was started.
   1.427 + */
   1.428 +function stopTimer(aName, aTimestamp) {
   1.429 +  let key = aName.toString();
   1.430 +  let duration = (aTimestamp || Date.now()) - gTimerRegistry.get(key);
   1.431 +  gTimerRegistry.delete(key);
   1.432 +  return { name: aName, duration: duration };
   1.433 +}
   1.434 +
   1.435 +/**
   1.436 + * Dump a new message header to stdout by taking care of adding an eventual
   1.437 + * prefix
   1.438 + *
   1.439 + * @param {object} aConsole
   1.440 + *        ConsoleAPI instance
   1.441 + * @param {string} aLevel
   1.442 + *        The string identifier for the message log level
   1.443 + * @param {string} aMessage
   1.444 + *        The string message to print to stdout
   1.445 + */
   1.446 +function dumpMessage(aConsole, aLevel, aMessage) {
   1.447 +  aConsole.dump(
   1.448 +    "console." + aLevel + ": " +
   1.449 +    aConsole.prefix +
   1.450 +    aMessage + "\n"
   1.451 +  );
   1.452 +}
   1.453 +
   1.454 +/**
   1.455 + * Create a function which will output a concise level of output when used
   1.456 + * as a logging function
   1.457 + *
   1.458 + * @param {string} aLevel
   1.459 + *        A prefix to all output generated from this function detailing the
   1.460 + *        level at which output occurred
   1.461 + * @return {function}
   1.462 + *        A logging function
   1.463 + * @see createMultiLineDumper()
   1.464 + */
   1.465 +function createDumper(aLevel) {
   1.466 +  return function() {
   1.467 +    if (!shouldLog(aLevel, this.maxLogLevel)) {
   1.468 +      return;
   1.469 +    }
   1.470 +    let args = Array.prototype.slice.call(arguments, 0);
   1.471 +    let frame = getStack(Components.stack.caller, 1)[0];
   1.472 +    sendConsoleAPIMessage(this, aLevel, frame, args);
   1.473 +    let data = args.map(function(arg) {
   1.474 +      return stringify(arg, true);
   1.475 +    });
   1.476 +    dumpMessage(this, aLevel, data.join(" "));
   1.477 +  };
   1.478 +}
   1.479 +
   1.480 +/**
   1.481 + * Create a function which will output more detailed level of output when
   1.482 + * used as a logging function
   1.483 + *
   1.484 + * @param {string} aLevel
   1.485 + *        A prefix to all output generated from this function detailing the
   1.486 + *        level at which output occurred
   1.487 + * @return {function}
   1.488 + *        A logging function
   1.489 + * @see createDumper()
   1.490 + */
   1.491 +function createMultiLineDumper(aLevel) {
   1.492 +  return function() {
   1.493 +    if (!shouldLog(aLevel, this.maxLogLevel)) {
   1.494 +      return;
   1.495 +    }
   1.496 +    dumpMessage(this, aLevel, "");
   1.497 +    let args = Array.prototype.slice.call(arguments, 0);
   1.498 +    let frame = getStack(Components.stack.caller, 1)[0];
   1.499 +    sendConsoleAPIMessage(this, aLevel, frame, args);
   1.500 +    args.forEach(function(arg) {
   1.501 +      this.dump(log(arg));
   1.502 +    }, this);
   1.503 +  };
   1.504 +}
   1.505 +
   1.506 +/**
   1.507 + * Send a Console API message. This function will send a console-api-log-event
   1.508 + * notification through the nsIObserverService.
   1.509 + *
   1.510 + * @param {object} aConsole
   1.511 + *        The instance of ConsoleAPI performing the logging.
   1.512 + * @param {string} aLevel
   1.513 + *        Message severity level. This is usually the name of the console method
   1.514 + *        that was called.
   1.515 + * @param {object} aFrame
   1.516 + *        The youngest stack frame coming from Components.stack, as formatted by
   1.517 + *        getStack().
   1.518 + * @param {array} aArgs
   1.519 + *        The arguments given to the console method.
   1.520 + * @param {object} aOptions
   1.521 + *        Object properties depend on the console method that was invoked:
   1.522 + *        - timer: for time() and timeEnd(). Holds the timer information.
   1.523 + *        - groupName: for group(), groupCollapsed() and groupEnd().
   1.524 + *        - stacktrace: for trace(). Holds the array of stack frames as given by
   1.525 + *        getStack().
   1.526 + */
   1.527 +function sendConsoleAPIMessage(aConsole, aLevel, aFrame, aArgs, aOptions = {})
   1.528 +{
   1.529 +  let consoleEvent = {
   1.530 +    ID: "jsm",
   1.531 +    innerID: aConsole.innerID || aFrame.filename,
   1.532 +    level: aLevel,
   1.533 +    filename: aFrame.filename,
   1.534 +    lineNumber: aFrame.lineNumber,
   1.535 +    functionName: aFrame.functionName,
   1.536 +    timeStamp: Date.now(),
   1.537 +    arguments: aArgs,
   1.538 +  };
   1.539 +
   1.540 +  consoleEvent.wrappedJSObject = consoleEvent;
   1.541 +
   1.542 +  switch (aLevel) {
   1.543 +    case "trace":
   1.544 +      consoleEvent.stacktrace = aOptions.stacktrace;
   1.545 +      break;
   1.546 +    case "time":
   1.547 +    case "timeEnd":
   1.548 +      consoleEvent.timer = aOptions.timer;
   1.549 +      break;
   1.550 +    case "group":
   1.551 +    case "groupCollapsed":
   1.552 +    case "groupEnd":
   1.553 +      try {
   1.554 +        consoleEvent.groupName = Array.prototype.join.call(aArgs, " ");
   1.555 +      }
   1.556 +      catch (ex) {
   1.557 +        Cu.reportError(ex);
   1.558 +        Cu.reportError(ex.stack);
   1.559 +        return;
   1.560 +      }
   1.561 +      break;
   1.562 +  }
   1.563 +
   1.564 +  Services.obs.notifyObservers(consoleEvent, "console-api-log-event", null);
   1.565 +  let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
   1.566 +                            .getService(Ci.nsIConsoleAPIStorage);
   1.567 +  ConsoleAPIStorage.recordEvent("jsm", consoleEvent);
   1.568 +}
   1.569 +
   1.570 +/**
   1.571 + * This creates a console object that somewhat replicates Firebug's console
   1.572 + * object
   1.573 + *
   1.574 + * @param {object} aConsoleOptions
   1.575 + *        Optional dictionary with a set of runtime console options:
   1.576 + *        - prefix {string} : An optional prefix string to be printed before
   1.577 + *                            the actual logged message
   1.578 + *        - maxLogLevel {string} : String identifier (See LOG_LEVELS for
   1.579 + *                            possible values) that allows to filter which
   1.580 + *                            messages are logged based on their log level.
   1.581 + *                            If falsy value, all messages will be logged.
   1.582 + *                            If wrong value that doesn't match any key of
   1.583 + *                            LOG_LEVELS, no message will be logged
   1.584 + *        - dump {function} : An optional function to intercept all strings
   1.585 + *                            written to stdout
   1.586 + *        - innerID {string}: An ID representing the source of the message.
   1.587 + *                            Normally the inner ID of a DOM window.
   1.588 + * @return {object}
   1.589 + *        A console API instance object
   1.590 + */
   1.591 +function ConsoleAPI(aConsoleOptions = {}) {
   1.592 +  // Normalize console options to set default values
   1.593 +  // in order to avoid runtime checks on each console method call.
   1.594 +  this.dump = aConsoleOptions.dump || dump;
   1.595 +  this.prefix = aConsoleOptions.prefix || "";
   1.596 +  this.maxLogLevel = aConsoleOptions.maxLogLevel || "all";
   1.597 +  this.innerID = aConsoleOptions.innerID || null;
   1.598 +
   1.599 +  // Bind all the functions to this object.
   1.600 +  for (let prop in this) {
   1.601 +    if (typeof(this[prop]) === "function") {
   1.602 +      this[prop] = this[prop].bind(this);
   1.603 +    }
   1.604 +  }
   1.605 +}
   1.606 +
   1.607 +ConsoleAPI.prototype = {
   1.608 +  debug: createMultiLineDumper("debug"),
   1.609 +  log: createDumper("log"),
   1.610 +  info: createDumper("info"),
   1.611 +  warn: createDumper("warn"),
   1.612 +  error: createMultiLineDumper("error"),
   1.613 +  exception: createMultiLineDumper("error"),
   1.614 +
   1.615 +  trace: function Console_trace() {
   1.616 +    if (!shouldLog("trace", this.maxLogLevel)) {
   1.617 +      return;
   1.618 +    }
   1.619 +    let args = Array.prototype.slice.call(arguments, 0);
   1.620 +    let trace = getStack(Components.stack.caller);
   1.621 +    sendConsoleAPIMessage(this, "trace", trace[0], args,
   1.622 +                          { stacktrace: trace });
   1.623 +    dumpMessage(this, "trace", "\n" + formatTrace(trace));
   1.624 +  },
   1.625 +  clear: function Console_clear() {},
   1.626 +
   1.627 +  dir: createMultiLineDumper("dir"),
   1.628 +  dirxml: createMultiLineDumper("dirxml"),
   1.629 +  group: createDumper("group"),
   1.630 +  groupEnd: createDumper("groupEnd"),
   1.631 +
   1.632 +  time: function Console_time() {
   1.633 +    if (!shouldLog("time", this.maxLogLevel)) {
   1.634 +      return;
   1.635 +    }
   1.636 +    let args = Array.prototype.slice.call(arguments, 0);
   1.637 +    let frame = getStack(Components.stack.caller, 1)[0];
   1.638 +    let timer = startTimer(args[0]);
   1.639 +    sendConsoleAPIMessage(this, "time", frame, args, { timer: timer });
   1.640 +    dumpMessage(this, "time",
   1.641 +                "'" + timer.name + "' @ " + (new Date()));
   1.642 +  },
   1.643 +
   1.644 +  timeEnd: function Console_timeEnd() {
   1.645 +    if (!shouldLog("timeEnd", this.maxLogLevel)) {
   1.646 +      return;
   1.647 +    }
   1.648 +    let args = Array.prototype.slice.call(arguments, 0);
   1.649 +    let frame = getStack(Components.stack.caller, 1)[0];
   1.650 +    let timer = stopTimer(args[0]);
   1.651 +    sendConsoleAPIMessage(this, "timeEnd", frame, args, { timer: timer });
   1.652 +    dumpMessage(this, "timeEnd",
   1.653 +                "'" + timer.name + "' " + timer.duration + "ms");
   1.654 +  },
   1.655 +};
   1.656 +
   1.657 +this.console = new ConsoleAPI();
   1.658 +this.ConsoleAPI = ConsoleAPI;

mercurial