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