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;