toolkit/devtools/Console.jsm

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 /**
michael@0 8 * Define a 'console' API to roughly match the implementation provided by
michael@0 9 * Firebug.
michael@0 10 * This module helps cases where code is shared between the web and Firefox.
michael@0 11 * See also Browser.jsm for an implementation of other web constants to help
michael@0 12 * sharing code between the web and firefox;
michael@0 13 *
michael@0 14 * The API is only be a rough approximation for 3 reasons:
michael@0 15 * - The Firebug console API is implemented in many places with differences in
michael@0 16 * the implementations, so there isn't a single reference to adhere to
michael@0 17 * - The Firebug console is a rich display compared with dump(), so there will
michael@0 18 * be many things that we can't replicate
michael@0 19 * - The primary use of this API is debugging and error logging so the perfect
michael@0 20 * implementation isn't always required (or even well defined)
michael@0 21 */
michael@0 22
michael@0 23 this.EXPORTED_SYMBOLS = [ "console", "ConsoleAPI" ];
michael@0 24
michael@0 25 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
michael@0 26
michael@0 27 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 28
michael@0 29 XPCOMUtils.defineLazyModuleGetter(this, "Services",
michael@0 30 "resource://gre/modules/Services.jsm");
michael@0 31
michael@0 32 let gTimerRegistry = new Map();
michael@0 33
michael@0 34 /**
michael@0 35 * String utility to ensure that strings are a specified length. Strings
michael@0 36 * that are too long are truncated to the max length and the last char is
michael@0 37 * set to "_". Strings that are too short are padded with spaces.
michael@0 38 *
michael@0 39 * @param {string} aStr
michael@0 40 * The string to format to the correct length
michael@0 41 * @param {number} aMaxLen
michael@0 42 * The maximum allowed length of the returned string
michael@0 43 * @param {number} aMinLen (optional)
michael@0 44 * The minimum allowed length of the returned string. If undefined,
michael@0 45 * then aMaxLen will be used
michael@0 46 * @param {object} aOptions (optional)
michael@0 47 * An object allowing format customization. Allowed customizations:
michael@0 48 * 'truncate' - can take the value "start" to truncate strings from
michael@0 49 * the start as opposed to the end or "center" to truncate
michael@0 50 * strings in the center.
michael@0 51 * 'align' - takes an alignment when padding is needed for MinLen,
michael@0 52 * either "start" or "end". Defaults to "start".
michael@0 53 * @return {string}
michael@0 54 * The original string formatted to fit the specified lengths
michael@0 55 */
michael@0 56 function fmt(aStr, aMaxLen, aMinLen, aOptions) {
michael@0 57 if (aMinLen == null) {
michael@0 58 aMinLen = aMaxLen;
michael@0 59 }
michael@0 60 if (aStr == null) {
michael@0 61 aStr = "";
michael@0 62 }
michael@0 63 if (aStr.length > aMaxLen) {
michael@0 64 if (aOptions && aOptions.truncate == "start") {
michael@0 65 return "_" + aStr.substring(aStr.length - aMaxLen + 1);
michael@0 66 }
michael@0 67 else if (aOptions && aOptions.truncate == "center") {
michael@0 68 let start = aStr.substring(0, (aMaxLen / 2));
michael@0 69
michael@0 70 let end = aStr.substring((aStr.length - (aMaxLen / 2)) + 1);
michael@0 71 return start + "_" + end;
michael@0 72 }
michael@0 73 else {
michael@0 74 return aStr.substring(0, aMaxLen - 1) + "_";
michael@0 75 }
michael@0 76 }
michael@0 77 if (aStr.length < aMinLen) {
michael@0 78 let padding = Array(aMinLen - aStr.length + 1).join(" ");
michael@0 79 aStr = (aOptions.align === "end") ? padding + aStr : aStr + padding;
michael@0 80 }
michael@0 81 return aStr;
michael@0 82 }
michael@0 83
michael@0 84 /**
michael@0 85 * Utility to extract the constructor name of an object.
michael@0 86 * Object.toString gives: "[object ?????]"; we want the "?????".
michael@0 87 *
michael@0 88 * @param {object} aObj
michael@0 89 * The object from which to extract the constructor name
michael@0 90 * @return {string}
michael@0 91 * The constructor name
michael@0 92 */
michael@0 93 function getCtorName(aObj) {
michael@0 94 if (aObj === null) {
michael@0 95 return "null";
michael@0 96 }
michael@0 97 if (aObj === undefined) {
michael@0 98 return "undefined";
michael@0 99 }
michael@0 100 if (aObj.constructor && aObj.constructor.name) {
michael@0 101 return aObj.constructor.name;
michael@0 102 }
michael@0 103 // If that fails, use Objects toString which sometimes gives something
michael@0 104 // better than 'Object', and at least defaults to Object if nothing better
michael@0 105 return Object.prototype.toString.call(aObj).slice(8, -1);
michael@0 106 }
michael@0 107
michael@0 108 /**
michael@0 109 * A single line stringification of an object designed for use by humans
michael@0 110 *
michael@0 111 * @param {any} aThing
michael@0 112 * The object to be stringified
michael@0 113 * @param {boolean} aAllowNewLines
michael@0 114 * @return {string}
michael@0 115 * A single line representation of aThing, which will generally be at
michael@0 116 * most 80 chars long
michael@0 117 */
michael@0 118 function stringify(aThing, aAllowNewLines) {
michael@0 119 if (aThing === undefined) {
michael@0 120 return "undefined";
michael@0 121 }
michael@0 122
michael@0 123 if (aThing === null) {
michael@0 124 return "null";
michael@0 125 }
michael@0 126
michael@0 127 if (typeof aThing == "object") {
michael@0 128 let type = getCtorName(aThing);
michael@0 129 if (aThing instanceof Components.interfaces.nsIDOMNode && aThing.tagName) {
michael@0 130 return debugElement(aThing);
michael@0 131 }
michael@0 132 type = (type == "Object" ? "" : type + " ");
michael@0 133 let json;
michael@0 134 try {
michael@0 135 json = JSON.stringify(aThing);
michael@0 136 }
michael@0 137 catch (ex) {
michael@0 138 // Can't use a real ellipsis here, because cmd.exe isn't unicode-enabled
michael@0 139 json = "{" + Object.keys(aThing).join(":..,") + ":.., " + "}";
michael@0 140 }
michael@0 141 return type + json;
michael@0 142 }
michael@0 143
michael@0 144 if (typeof aThing == "function") {
michael@0 145 return aThing.toString().replace(/\s+/g, " ");
michael@0 146 }
michael@0 147
michael@0 148 let str = aThing.toString();
michael@0 149 if (!aAllowNewLines) {
michael@0 150 str = str.replace(/\n/g, "|");
michael@0 151 }
michael@0 152 return str;
michael@0 153 }
michael@0 154
michael@0 155 /**
michael@0 156 * Create a simple debug representation of a given element.
michael@0 157 *
michael@0 158 * @param {nsIDOMElement} aElement
michael@0 159 * The element to debug
michael@0 160 * @return {string}
michael@0 161 * A simple single line representation of aElement
michael@0 162 */
michael@0 163 function debugElement(aElement) {
michael@0 164 return "<" + aElement.tagName +
michael@0 165 (aElement.id ? "#" + aElement.id : "") +
michael@0 166 (aElement.className ?
michael@0 167 "." + aElement.className.split(" ").join(" .") :
michael@0 168 "") +
michael@0 169 ">";
michael@0 170 }
michael@0 171
michael@0 172 /**
michael@0 173 * A multi line stringification of an object, designed for use by humans
michael@0 174 *
michael@0 175 * @param {any} aThing
michael@0 176 * The object to be stringified
michael@0 177 * @return {string}
michael@0 178 * A multi line representation of aThing
michael@0 179 */
michael@0 180 function log(aThing) {
michael@0 181 if (aThing === null) {
michael@0 182 return "null\n";
michael@0 183 }
michael@0 184
michael@0 185 if (aThing === undefined) {
michael@0 186 return "undefined\n";
michael@0 187 }
michael@0 188
michael@0 189 if (typeof aThing == "object") {
michael@0 190 let reply = "";
michael@0 191 let type = getCtorName(aThing);
michael@0 192 if (type == "Map") {
michael@0 193 reply += "Map\n";
michael@0 194 for (let [key, value] of aThing) {
michael@0 195 reply += logProperty(key, value);
michael@0 196 }
michael@0 197 }
michael@0 198 else if (type == "Set") {
michael@0 199 let i = 0;
michael@0 200 reply += "Set\n";
michael@0 201 for (let value of aThing) {
michael@0 202 reply += logProperty('' + i, value);
michael@0 203 i++;
michael@0 204 }
michael@0 205 }
michael@0 206 else if (type.match("Error$") ||
michael@0 207 (typeof aThing.name == "string" &&
michael@0 208 aThing.name.match("NS_ERROR_"))) {
michael@0 209 reply += " Message: " + aThing + "\n";
michael@0 210 if (aThing.stack) {
michael@0 211 reply += " Stack:\n";
michael@0 212 var frame = aThing.stack;
michael@0 213 while (frame) {
michael@0 214 reply += " " + frame + "\n";
michael@0 215 frame = frame.caller;
michael@0 216 }
michael@0 217 }
michael@0 218 }
michael@0 219 else if (aThing instanceof Components.interfaces.nsIDOMNode && aThing.tagName) {
michael@0 220 reply += " " + debugElement(aThing) + "\n";
michael@0 221 }
michael@0 222 else {
michael@0 223 let keys = Object.getOwnPropertyNames(aThing);
michael@0 224 if (keys.length > 0) {
michael@0 225 reply += type + "\n";
michael@0 226 keys.forEach(function(aProp) {
michael@0 227 reply += logProperty(aProp, aThing[aProp]);
michael@0 228 });
michael@0 229 }
michael@0 230 else {
michael@0 231 reply += type + "\n";
michael@0 232 let root = aThing;
michael@0 233 let logged = [];
michael@0 234 while (root != null) {
michael@0 235 let properties = Object.keys(root);
michael@0 236 properties.sort();
michael@0 237 properties.forEach(function(property) {
michael@0 238 if (!(property in logged)) {
michael@0 239 logged[property] = property;
michael@0 240 reply += logProperty(property, aThing[property]);
michael@0 241 }
michael@0 242 });
michael@0 243
michael@0 244 root = Object.getPrototypeOf(root);
michael@0 245 if (root != null) {
michael@0 246 reply += ' - prototype ' + getCtorName(root) + '\n';
michael@0 247 }
michael@0 248 }
michael@0 249 }
michael@0 250 }
michael@0 251
michael@0 252 return reply;
michael@0 253 }
michael@0 254
michael@0 255 return " " + aThing.toString() + "\n";
michael@0 256 }
michael@0 257
michael@0 258 /**
michael@0 259 * Helper for log() which converts a property/value pair into an output
michael@0 260 * string
michael@0 261 *
michael@0 262 * @param {string} aProp
michael@0 263 * The name of the property to include in the output string
michael@0 264 * @param {object} aValue
michael@0 265 * Value assigned to aProp to be converted to a single line string
michael@0 266 * @return {string}
michael@0 267 * Multi line output string describing the property/value pair
michael@0 268 */
michael@0 269 function logProperty(aProp, aValue) {
michael@0 270 let reply = "";
michael@0 271 if (aProp == "stack" && typeof value == "string") {
michael@0 272 let trace = parseStack(aValue);
michael@0 273 reply += formatTrace(trace);
michael@0 274 }
michael@0 275 else {
michael@0 276 reply += " - " + aProp + " = " + stringify(aValue) + "\n";
michael@0 277 }
michael@0 278 return reply;
michael@0 279 }
michael@0 280
michael@0 281 const LOG_LEVELS = {
michael@0 282 "all": Number.MIN_VALUE,
michael@0 283 "debug": 2,
michael@0 284 "log": 3,
michael@0 285 "info": 3,
michael@0 286 "trace": 3,
michael@0 287 "timeEnd": 3,
michael@0 288 "time": 3,
michael@0 289 "group": 3,
michael@0 290 "groupEnd": 3,
michael@0 291 "dir": 3,
michael@0 292 "dirxml": 3,
michael@0 293 "warn": 4,
michael@0 294 "error": 5,
michael@0 295 "off": Number.MAX_VALUE,
michael@0 296 };
michael@0 297
michael@0 298 /**
michael@0 299 * Helper to tell if a console message of `aLevel` type
michael@0 300 * should be logged in stdout and sent to consoles given
michael@0 301 * the current maximum log level being defined in `console.maxLogLevel`
michael@0 302 *
michael@0 303 * @param {string} aLevel
michael@0 304 * Console message log level
michael@0 305 * @param {string} aMaxLevel {string}
michael@0 306 * String identifier (See LOG_LEVELS for possible
michael@0 307 * values) that allows to filter which messages
michael@0 308 * are logged based on their log level
michael@0 309 * @return {boolean}
michael@0 310 * Should this message be logged or not?
michael@0 311 */
michael@0 312 function shouldLog(aLevel, aMaxLevel) {
michael@0 313 return LOG_LEVELS[aMaxLevel] <= LOG_LEVELS[aLevel];
michael@0 314 }
michael@0 315
michael@0 316 /**
michael@0 317 * Parse a stack trace, returning an array of stack frame objects, where
michael@0 318 * each has filename/lineNumber/functionName members
michael@0 319 *
michael@0 320 * @param {string} aStack
michael@0 321 * The serialized stack trace
michael@0 322 * @return {object[]}
michael@0 323 * Array of { file: "...", line: NNN, call: "..." } objects
michael@0 324 */
michael@0 325 function parseStack(aStack) {
michael@0 326 let trace = [];
michael@0 327 aStack.split("\n").forEach(function(line) {
michael@0 328 if (!line) {
michael@0 329 return;
michael@0 330 }
michael@0 331 let at = line.lastIndexOf("@");
michael@0 332 let posn = line.substring(at + 1);
michael@0 333 trace.push({
michael@0 334 filename: posn.split(":")[0],
michael@0 335 lineNumber: posn.split(":")[1],
michael@0 336 functionName: line.substring(0, at)
michael@0 337 });
michael@0 338 });
michael@0 339 return trace;
michael@0 340 }
michael@0 341
michael@0 342 /**
michael@0 343 * Format a frame coming from Components.stack such that it can be used by the
michael@0 344 * Browser Console, via console-api-log-event notifications.
michael@0 345 *
michael@0 346 * @param {object} aFrame
michael@0 347 * The stack frame from which to begin the walk.
michael@0 348 * @param {number=0} aMaxDepth
michael@0 349 * Maximum stack trace depth. Default is 0 - no depth limit.
michael@0 350 * @return {object[]}
michael@0 351 * An array of {filename, lineNumber, functionName, language} objects.
michael@0 352 * These objects follow the same format as other console-api-log-event
michael@0 353 * messages.
michael@0 354 */
michael@0 355 function getStack(aFrame, aMaxDepth = 0) {
michael@0 356 if (!aFrame) {
michael@0 357 aFrame = Components.stack.caller;
michael@0 358 }
michael@0 359 let trace = [];
michael@0 360 while (aFrame) {
michael@0 361 trace.push({
michael@0 362 filename: aFrame.filename,
michael@0 363 lineNumber: aFrame.lineNumber,
michael@0 364 functionName: aFrame.name,
michael@0 365 language: aFrame.language,
michael@0 366 });
michael@0 367 if (aMaxDepth == trace.length) {
michael@0 368 break;
michael@0 369 }
michael@0 370 aFrame = aFrame.caller;
michael@0 371 }
michael@0 372 return trace;
michael@0 373 }
michael@0 374
michael@0 375 /**
michael@0 376 * Take the output from parseStack() and convert it to nice readable
michael@0 377 * output
michael@0 378 *
michael@0 379 * @param {object[]} aTrace
michael@0 380 * Array of trace objects as created by parseStack()
michael@0 381 * @return {string} Multi line report of the stack trace
michael@0 382 */
michael@0 383 function formatTrace(aTrace) {
michael@0 384 let reply = "";
michael@0 385 aTrace.forEach(function(frame) {
michael@0 386 reply += fmt(frame.filename, 20, 20, { truncate: "start" }) + " " +
michael@0 387 fmt(frame.lineNumber, 5, 5) + " " +
michael@0 388 fmt(frame.functionName, 75, 0, { truncate: "center" }) + "\n";
michael@0 389 });
michael@0 390 return reply;
michael@0 391 }
michael@0 392
michael@0 393 /**
michael@0 394 * Create a new timer by recording the current time under the specified name.
michael@0 395 *
michael@0 396 * @param {string} aName
michael@0 397 * The name of the timer.
michael@0 398 * @param {number} [aTimestamp=Date.now()]
michael@0 399 * Optional timestamp that tells when the timer was originally started.
michael@0 400 * @return {object}
michael@0 401 * The name property holds the timer name and the started property
michael@0 402 * holds the time the timer was started. In case of error, it returns
michael@0 403 * an object with the single property "error" that contains the key
michael@0 404 * for retrieving the localized error message.
michael@0 405 */
michael@0 406 function startTimer(aName, aTimestamp) {
michael@0 407 let key = aName.toString();
michael@0 408 if (!gTimerRegistry.has(key)) {
michael@0 409 gTimerRegistry.set(key, aTimestamp || Date.now());
michael@0 410 }
michael@0 411 return { name: aName, started: gTimerRegistry.get(key) };
michael@0 412 }
michael@0 413
michael@0 414 /**
michael@0 415 * Stop the timer with the specified name and retrieve the elapsed time.
michael@0 416 *
michael@0 417 * @param {string} aName
michael@0 418 * The name of the timer.
michael@0 419 * @param {number} [aTimestamp=Date.now()]
michael@0 420 * Optional timestamp that tells when the timer was originally stopped.
michael@0 421 * @return {object}
michael@0 422 * The name property holds the timer name and the duration property
michael@0 423 * holds the number of milliseconds since the timer was started.
michael@0 424 */
michael@0 425 function stopTimer(aName, aTimestamp) {
michael@0 426 let key = aName.toString();
michael@0 427 let duration = (aTimestamp || Date.now()) - gTimerRegistry.get(key);
michael@0 428 gTimerRegistry.delete(key);
michael@0 429 return { name: aName, duration: duration };
michael@0 430 }
michael@0 431
michael@0 432 /**
michael@0 433 * Dump a new message header to stdout by taking care of adding an eventual
michael@0 434 * prefix
michael@0 435 *
michael@0 436 * @param {object} aConsole
michael@0 437 * ConsoleAPI instance
michael@0 438 * @param {string} aLevel
michael@0 439 * The string identifier for the message log level
michael@0 440 * @param {string} aMessage
michael@0 441 * The string message to print to stdout
michael@0 442 */
michael@0 443 function dumpMessage(aConsole, aLevel, aMessage) {
michael@0 444 aConsole.dump(
michael@0 445 "console." + aLevel + ": " +
michael@0 446 aConsole.prefix +
michael@0 447 aMessage + "\n"
michael@0 448 );
michael@0 449 }
michael@0 450
michael@0 451 /**
michael@0 452 * Create a function which will output a concise level of output when used
michael@0 453 * as a logging function
michael@0 454 *
michael@0 455 * @param {string} aLevel
michael@0 456 * A prefix to all output generated from this function detailing the
michael@0 457 * level at which output occurred
michael@0 458 * @return {function}
michael@0 459 * A logging function
michael@0 460 * @see createMultiLineDumper()
michael@0 461 */
michael@0 462 function createDumper(aLevel) {
michael@0 463 return function() {
michael@0 464 if (!shouldLog(aLevel, this.maxLogLevel)) {
michael@0 465 return;
michael@0 466 }
michael@0 467 let args = Array.prototype.slice.call(arguments, 0);
michael@0 468 let frame = getStack(Components.stack.caller, 1)[0];
michael@0 469 sendConsoleAPIMessage(this, aLevel, frame, args);
michael@0 470 let data = args.map(function(arg) {
michael@0 471 return stringify(arg, true);
michael@0 472 });
michael@0 473 dumpMessage(this, aLevel, data.join(" "));
michael@0 474 };
michael@0 475 }
michael@0 476
michael@0 477 /**
michael@0 478 * Create a function which will output more detailed level of output when
michael@0 479 * used as a logging function
michael@0 480 *
michael@0 481 * @param {string} aLevel
michael@0 482 * A prefix to all output generated from this function detailing the
michael@0 483 * level at which output occurred
michael@0 484 * @return {function}
michael@0 485 * A logging function
michael@0 486 * @see createDumper()
michael@0 487 */
michael@0 488 function createMultiLineDumper(aLevel) {
michael@0 489 return function() {
michael@0 490 if (!shouldLog(aLevel, this.maxLogLevel)) {
michael@0 491 return;
michael@0 492 }
michael@0 493 dumpMessage(this, aLevel, "");
michael@0 494 let args = Array.prototype.slice.call(arguments, 0);
michael@0 495 let frame = getStack(Components.stack.caller, 1)[0];
michael@0 496 sendConsoleAPIMessage(this, aLevel, frame, args);
michael@0 497 args.forEach(function(arg) {
michael@0 498 this.dump(log(arg));
michael@0 499 }, this);
michael@0 500 };
michael@0 501 }
michael@0 502
michael@0 503 /**
michael@0 504 * Send a Console API message. This function will send a console-api-log-event
michael@0 505 * notification through the nsIObserverService.
michael@0 506 *
michael@0 507 * @param {object} aConsole
michael@0 508 * The instance of ConsoleAPI performing the logging.
michael@0 509 * @param {string} aLevel
michael@0 510 * Message severity level. This is usually the name of the console method
michael@0 511 * that was called.
michael@0 512 * @param {object} aFrame
michael@0 513 * The youngest stack frame coming from Components.stack, as formatted by
michael@0 514 * getStack().
michael@0 515 * @param {array} aArgs
michael@0 516 * The arguments given to the console method.
michael@0 517 * @param {object} aOptions
michael@0 518 * Object properties depend on the console method that was invoked:
michael@0 519 * - timer: for time() and timeEnd(). Holds the timer information.
michael@0 520 * - groupName: for group(), groupCollapsed() and groupEnd().
michael@0 521 * - stacktrace: for trace(). Holds the array of stack frames as given by
michael@0 522 * getStack().
michael@0 523 */
michael@0 524 function sendConsoleAPIMessage(aConsole, aLevel, aFrame, aArgs, aOptions = {})
michael@0 525 {
michael@0 526 let consoleEvent = {
michael@0 527 ID: "jsm",
michael@0 528 innerID: aConsole.innerID || aFrame.filename,
michael@0 529 level: aLevel,
michael@0 530 filename: aFrame.filename,
michael@0 531 lineNumber: aFrame.lineNumber,
michael@0 532 functionName: aFrame.functionName,
michael@0 533 timeStamp: Date.now(),
michael@0 534 arguments: aArgs,
michael@0 535 };
michael@0 536
michael@0 537 consoleEvent.wrappedJSObject = consoleEvent;
michael@0 538
michael@0 539 switch (aLevel) {
michael@0 540 case "trace":
michael@0 541 consoleEvent.stacktrace = aOptions.stacktrace;
michael@0 542 break;
michael@0 543 case "time":
michael@0 544 case "timeEnd":
michael@0 545 consoleEvent.timer = aOptions.timer;
michael@0 546 break;
michael@0 547 case "group":
michael@0 548 case "groupCollapsed":
michael@0 549 case "groupEnd":
michael@0 550 try {
michael@0 551 consoleEvent.groupName = Array.prototype.join.call(aArgs, " ");
michael@0 552 }
michael@0 553 catch (ex) {
michael@0 554 Cu.reportError(ex);
michael@0 555 Cu.reportError(ex.stack);
michael@0 556 return;
michael@0 557 }
michael@0 558 break;
michael@0 559 }
michael@0 560
michael@0 561 Services.obs.notifyObservers(consoleEvent, "console-api-log-event", null);
michael@0 562 let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
michael@0 563 .getService(Ci.nsIConsoleAPIStorage);
michael@0 564 ConsoleAPIStorage.recordEvent("jsm", consoleEvent);
michael@0 565 }
michael@0 566
michael@0 567 /**
michael@0 568 * This creates a console object that somewhat replicates Firebug's console
michael@0 569 * object
michael@0 570 *
michael@0 571 * @param {object} aConsoleOptions
michael@0 572 * Optional dictionary with a set of runtime console options:
michael@0 573 * - prefix {string} : An optional prefix string to be printed before
michael@0 574 * the actual logged message
michael@0 575 * - maxLogLevel {string} : String identifier (See LOG_LEVELS for
michael@0 576 * possible values) that allows to filter which
michael@0 577 * messages are logged based on their log level.
michael@0 578 * If falsy value, all messages will be logged.
michael@0 579 * If wrong value that doesn't match any key of
michael@0 580 * LOG_LEVELS, no message will be logged
michael@0 581 * - dump {function} : An optional function to intercept all strings
michael@0 582 * written to stdout
michael@0 583 * - innerID {string}: An ID representing the source of the message.
michael@0 584 * Normally the inner ID of a DOM window.
michael@0 585 * @return {object}
michael@0 586 * A console API instance object
michael@0 587 */
michael@0 588 function ConsoleAPI(aConsoleOptions = {}) {
michael@0 589 // Normalize console options to set default values
michael@0 590 // in order to avoid runtime checks on each console method call.
michael@0 591 this.dump = aConsoleOptions.dump || dump;
michael@0 592 this.prefix = aConsoleOptions.prefix || "";
michael@0 593 this.maxLogLevel = aConsoleOptions.maxLogLevel || "all";
michael@0 594 this.innerID = aConsoleOptions.innerID || null;
michael@0 595
michael@0 596 // Bind all the functions to this object.
michael@0 597 for (let prop in this) {
michael@0 598 if (typeof(this[prop]) === "function") {
michael@0 599 this[prop] = this[prop].bind(this);
michael@0 600 }
michael@0 601 }
michael@0 602 }
michael@0 603
michael@0 604 ConsoleAPI.prototype = {
michael@0 605 debug: createMultiLineDumper("debug"),
michael@0 606 log: createDumper("log"),
michael@0 607 info: createDumper("info"),
michael@0 608 warn: createDumper("warn"),
michael@0 609 error: createMultiLineDumper("error"),
michael@0 610 exception: createMultiLineDumper("error"),
michael@0 611
michael@0 612 trace: function Console_trace() {
michael@0 613 if (!shouldLog("trace", this.maxLogLevel)) {
michael@0 614 return;
michael@0 615 }
michael@0 616 let args = Array.prototype.slice.call(arguments, 0);
michael@0 617 let trace = getStack(Components.stack.caller);
michael@0 618 sendConsoleAPIMessage(this, "trace", trace[0], args,
michael@0 619 { stacktrace: trace });
michael@0 620 dumpMessage(this, "trace", "\n" + formatTrace(trace));
michael@0 621 },
michael@0 622 clear: function Console_clear() {},
michael@0 623
michael@0 624 dir: createMultiLineDumper("dir"),
michael@0 625 dirxml: createMultiLineDumper("dirxml"),
michael@0 626 group: createDumper("group"),
michael@0 627 groupEnd: createDumper("groupEnd"),
michael@0 628
michael@0 629 time: function Console_time() {
michael@0 630 if (!shouldLog("time", this.maxLogLevel)) {
michael@0 631 return;
michael@0 632 }
michael@0 633 let args = Array.prototype.slice.call(arguments, 0);
michael@0 634 let frame = getStack(Components.stack.caller, 1)[0];
michael@0 635 let timer = startTimer(args[0]);
michael@0 636 sendConsoleAPIMessage(this, "time", frame, args, { timer: timer });
michael@0 637 dumpMessage(this, "time",
michael@0 638 "'" + timer.name + "' @ " + (new Date()));
michael@0 639 },
michael@0 640
michael@0 641 timeEnd: function Console_timeEnd() {
michael@0 642 if (!shouldLog("timeEnd", this.maxLogLevel)) {
michael@0 643 return;
michael@0 644 }
michael@0 645 let args = Array.prototype.slice.call(arguments, 0);
michael@0 646 let frame = getStack(Components.stack.caller, 1)[0];
michael@0 647 let timer = stopTimer(args[0]);
michael@0 648 sendConsoleAPIMessage(this, "timeEnd", frame, args, { timer: timer });
michael@0 649 dumpMessage(this, "timeEnd",
michael@0 650 "'" + timer.name + "' " + timer.duration + "ms");
michael@0 651 },
michael@0 652 };
michael@0 653
michael@0 654 this.console = new ConsoleAPI();
michael@0 655 this.ConsoleAPI = ConsoleAPI;

mercurial