1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/chrome/reps",
  5     "firebug/lib/locale",
  6     "firebug/lib/wrapper",
  7     "firebug/lib/url",
  8     "firebug/lib/string",
  9     "firebug/js/stackFrame",
 10     "firebug/console/errors",
 11     "firebug/trace/debug",
 12     "firebug/console/console",
 13     "firebug/lib/options",
 14 ],
 15 function(FirebugReps, Locale, Wrapper, Url, Str, StackFrame, Errors, Debug, Console, Options) {
 16 
 17 // ********************************************************************************************* //
 18 
 19 /**
 20  * Returns a console object (bundled with passed window through closure). The object
 21  * provides all necessary APIs as described here: http://getfirebug.com/wiki/index.php/Console_API
 22  *
 23  * @param {Object} context
 24  * @param {Object} win
 25  */
 26 function createFirebugConsole(context, win)
 27 {
 28     // Defined as a chrome object, but exposed into the web content scope.
 29     var console = {
 30         __exposedProps__: {}
 31     };
 32 
 33     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 34     // Exposed Properties
 35 
 36     console.log = function log()
 37     {
 38         return logFormatted(arguments, "log", true);
 39     };
 40 
 41     console.debug = function debug()
 42     {
 43         return logFormatted(arguments, "debug", true);
 44     };
 45 
 46     console.info = function info()
 47     {
 48         return logFormatted(arguments, "info", true);
 49     };
 50 
 51     console.warn = function warn()
 52     {
 53         return logFormatted(arguments, "warn", true);
 54     };
 55 
 56     console.exception = function exception()
 57     {
 58         return logAssert("error", arguments);
 59     };
 60 
 61     console.assert = function assert(x)
 62     {
 63         if (!x)
 64         {
 65             var rest = [];
 66             for (var i = 1; i < arguments.length; i++)
 67                 rest.push(arguments[i]);
 68             return logAssert("assert", rest);
 69         }
 70 
 71         return Console.getDefaultReturnValue(win);
 72     };
 73 
 74     console.dir = function dir(o)
 75     {
 76         Firebug.Console.log(o, context, "dir", Firebug.DOMPanel.DirTable);
 77         return Console.getDefaultReturnValue(win);
 78     };
 79 
 80     console.dirxml = function dirxml(o)
 81     {
 82         if (o instanceof Wrapper.getContentView(win).Window)
 83             o = o.document.documentElement;
 84         else if (o instanceof Wrapper.getContentView(win).Document)
 85             o = o.documentElement;
 86 
 87         Firebug.Console.log(o, context, "dirxml", Firebug.HTMLPanel.SoloElement);
 88         return Console.getDefaultReturnValue(win);
 89     };
 90 
 91     console.trace = function firebugDebuggerTracer()
 92     {
 93         var unwrapped = Wrapper.unwrapObject(win);
 94         unwrapped.top._firebugStackTrace = "console-tracer";
 95         debugger;
 96         delete unwrapped.top._firebugStackTrace;
 97 
 98         return Console.getDefaultReturnValue(win);
 99     };
100 
101     console.group = function group()
102     {
103         var sourceLink = getStackLink();
104         Firebug.Console.openGroup(arguments, null, "group", null, false, sourceLink);
105         return Console.getDefaultReturnValue(win);
106     };
107 
108     console.groupEnd = function()
109     {
110         Firebug.Console.closeGroup(context);
111         return Console.getDefaultReturnValue(win);
112     };
113 
114     console.groupCollapsed = function()
115     {
116         var sourceLink = getStackLink();
117 
118         // noThrottle true can't be used here (in order to get the result row now)
119         // because there can be some logs delayed in the queue and they would end up
120         // in a different grup.
121         // Use rather a different method that causes auto collapsing of the group
122         // when it's created.
123         Firebug.Console.openCollapsedGroup(arguments, null, "group", null, false, sourceLink);
124         return Console.getDefaultReturnValue(win);
125     };
126 
127     console.profile = function(title)
128     {
129         Firebug.Profiler.startProfiling(context, title);
130         return Console.getDefaultReturnValue(win);
131     };
132 
133     console.profileEnd = function()
134     {
135         Firebug.Profiler.stopProfiling(context);
136         return Console.getDefaultReturnValue(win);
137     };
138 
139     console.count = function(key)
140     {
141         var frameId = getStackFrameId();
142         if (frameId)
143         {
144             if (!context.frameCounters)
145                 context.frameCounters = {};
146 
147             if (key != undefined)
148                 frameId += key;
149 
150             var frameCounter = context.frameCounters[frameId];
151             if (!frameCounter)
152             {
153                 var logRow = logFormatted(["0"], null, true, true);
154 
155                 frameCounter = {logRow: logRow, count: 1};
156                 context.frameCounters[frameId] = frameCounter;
157             }
158             else
159                 ++frameCounter.count;
160 
161             var label = key == undefined
162                 ? frameCounter.count
163                 : key + " " + frameCounter.count;
164 
165             frameCounter.logRow.firstChild.firstChild.nodeValue = label;
166         }
167         return Console.getDefaultReturnValue(win);
168     };
169 
170     console.clear = function()
171     {
172         Firebug.Console.clear(context);
173         return Console.getDefaultReturnValue(win);
174     };
175 
176     console.time = function(name, reset)
177     {
178         if (!name)
179             return Console.getDefaultReturnValue(win);
180 
181         var time = new Date().getTime();
182 
183         if (!this.timeCounters)
184             this.timeCounters = {};
185 
186         var key = "KEY"+name.toString();
187 
188         if (!reset && this.timeCounters[key])
189             return Console.getDefaultReturnValue(win);
190 
191         this.timeCounters[key] = time;
192         return Console.getDefaultReturnValue(win);
193     };
194 
195     console.timeEnd = function(name)
196     {
197         var time = new Date().getTime();
198 
199         if (!this.timeCounters)
200             return Console.getDefaultReturnValue(win);
201 
202         var key = "KEY"+name.toString();
203 
204         var timeCounter = this.timeCounters[key];
205         if (timeCounter)
206         {
207             var diff = time - timeCounter;
208             var label = name + ": " + diff + "ms";
209 
210             this.info(label);
211 
212             delete this.timeCounters[key];
213         }
214         return diff;
215     };
216 
217     console.timeStamp = function(label)
218     {
219         label = label || "";
220 
221         if (FBTrace.DBG_CONSOLE)
222             FBTrace.sysout("consoleExposed.timeStamp; " + label);
223 
224         var now = new Date();
225         Firebug.NetMonitor.addTimeStamp(context, now.getTime(), label);
226 
227         var formattedTime = now.getHours() + ":" + now.getMinutes() + ":" +
228             now.getSeconds() + "." + now.getMilliseconds();
229         return logFormatted([formattedTime, label], "timeStamp");
230     };
231 
232     console.table = function(data, columns)
233     {
234         FirebugReps.Table.log(data, columns, context);
235         return Console.getDefaultReturnValue(win);
236     };
237 
238     console.error = function error()
239     {
240         // TODO stack trace
241         if (arguments.length == 1)
242         {
243             return logAssert("error", arguments);  // add more info based on stack trace
244         }
245         else
246         {
247             Errors.increaseCount(context);
248             return logFormatted(arguments, "error", true);  // user already added info
249         }
250     };
251 
252     // xxxHonza: removed from 1.10 (issue 5599)
253     /*console.memoryProfile = function(title)
254     {
255         Firebug.MemoryProfiler.start(context, title);
256         return Console.getDefaultReturnValue(win);
257     };
258 
259     console.memoryProfileEnd = function()
260     {
261         Firebug.MemoryProfiler.stop(context);
262         return Console.getDefaultReturnValue(win);
263     };*/
264 
265     // Expose only these properties to the content scope (read only).
266     console.__exposedProps__.log = "r";
267     console.__exposedProps__.debug = "r";
268     console.__exposedProps__.info = "r";
269     console.__exposedProps__.warn = "r";
270     console.__exposedProps__.exception = "r";
271     console.__exposedProps__.assert = "r";
272     console.__exposedProps__.dir = "r";
273     console.__exposedProps__.dirxml = "r";
274     console.__exposedProps__.trace = "r";
275     console.__exposedProps__.group = "r";
276     console.__exposedProps__.groupEnd = "r";
277     console.__exposedProps__.groupCollapsed = "r";
278     console.__exposedProps__.time = "r";
279     console.__exposedProps__.timeEnd = "r";
280     console.__exposedProps__.timeStamp = "r";
281     console.__exposedProps__.profile = "r";
282     console.__exposedProps__.profileEnd = "r";
283     console.__exposedProps__.count = "r";
284     console.__exposedProps__.clear = "r";
285     console.__exposedProps__.table = "r";
286     console.__exposedProps__.error = "r";
287     //console.__exposedProps__.memoryProfile = "r";
288     //console.__exposedProps__.memoryProfileEnd = "r";
289 
290     // DBG console.uid = Math.random();
291 
292     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
293     // Helpers (not accessible from web content)
294 
295     function logFormatted(args, className, linkToSource, noThrottle)
296     {
297         var sourceLink;
298 
299         // Using JSD to get user stack is time consuming.
300         if (Options.get("preferJSDSourceLinks"))
301         {
302             var stack = getJSDUserStack();
303             if (stack && stack.toSourceLink)
304                 sourceLink = stack.toSourceLink();
305         }
306 
307         if (!sourceLink)
308             sourceLink = linkToSource ? getStackLink() : null;
309 
310         var ignoreReturnValue = Firebug.Console.getDefaultReturnValue(win);
311         var rc = Firebug.Console.logFormatted(args, context, className, noThrottle, sourceLink);
312         return rc ? rc : ignoreReturnValue;
313     };
314 
315     function logAssert(category, args)
316     {
317         Errors.increaseCount(context);
318 
319         if (!args || !args.length || args.length == 0)
320             var msg = [Locale.$STR("Assertion")];
321         else
322             var msg = args[0];
323 
324         // If there's no error message, there's also no stack trace. See Issue 4700.
325         if (!msg)
326         {
327             var trace = null;
328         }
329         else if (msg.stack)
330         {
331             var trace = StackFrame.parseToStackTrace(msg.stack, context);
332             if (FBTrace.DBG_CONSOLE)
333                 FBTrace.sysout("logAssert trace from msg.stack", trace);
334         }
335         else if (context.stackTrace)
336         {
337             var trace = context.stackTrace;
338             if (FBTrace.DBG_CONSOLE)
339                 FBTrace.sysout("logAssert trace from context.window.stackTrace", trace);
340         }
341         else
342         {
343             var trace = getJSDUserStack();
344             if (FBTrace.DBG_CONSOLE)
345                 FBTrace.sysout("logAssert trace from getJSDUserStack", trace);
346         }
347 
348         trace = StackFrame.cleanStackTraceOfFirebug(trace);
349 
350         var url = msg && msg.fileName ? msg.fileName : win.location.href;
351 
352         // we may have only the line popped above
353         var lineNo = (trace && msg && msg.lineNumber) ? msg.lineNumber : 0;
354         var errorObject = new FirebugReps.ErrorMessageObj(msg, url, lineNo, "",
355             category, context, trace);
356 
357         if (trace && trace.frames && trace.frames[0])
358             errorObject.correctWithStackTrace(trace);
359 
360         errorObject.resetSource();
361 
362         if (args.length > 1)
363         {
364             errorObject.objects = []
365             for (var i = 1; i < args.length; i++)
366                 errorObject.objects.push(args[i]);
367         }
368 
369         var row = Firebug.Console.log(errorObject, context, "errorMessage");
370         if (row)
371             row.scrollIntoView();
372 
373         return Console.getDefaultReturnValue(win);
374     };
375 
376     function getComponentsStackDump()
377     {
378         // Starting with our stack, walk back to the user-level code
379         var frame = Components.stack;
380         var userURL = win.location.href.toString();
381 
382         if (FBTrace.DBG_CONSOLE)
383             FBTrace.sysout("consoleInjector.getComponentsStackDump initial stack for userURL " +
384                 userURL, frame);
385 
386         // Drop frames until we get into user code.
387         while (frame && Url.isSystemURL(frame.filename) )
388             frame = frame.caller;
389 
390         // Drop two more frames, the injected console function and firebugAppendConsole()
391         //if (frame)
392         //    frame = frame.caller;
393         //if (frame)
394         //    frame = frame.caller;
395 
396         if (FBTrace.DBG_CONSOLE)
397             FBTrace.sysout("consoleInjector.getComponentsStackDump final stack for userURL " +
398                 userURL, frame);
399 
400         return frame;
401     };
402 
403     function getStackLink()
404     {
405         return StackFrame.getFrameSourceLink(getComponentsStackDump());
406     };
407 
408     function getJSDUserStack()
409     {
410         var trace = Firebug.Debugger.getCurrentStackTrace(context);
411 
412         var frames = trace ? trace.frames : null;
413         if (frames && (frames.length > 0) )
414         {
415             var filteredFrames = [];
416 
417             for (var i = 0; i < frames.length; i++)
418             {
419                 if (Str.hasPrefix(frames[i].href, "chrome:"))
420                     continue;
421 
422                 if (Str.hasPrefix(frames[i].href, "resource:"))
423                     continue;
424 
425                 // firebug-service scope reached, in some cases the url starts with file://
426                 if (frames[i].href.indexOf("modules/firebug-service.js") != -1)
427                     continue;
428 
429                 // command line
430                 var fn = frames[i].getFunctionName() + "";
431                 if (fn && (fn.indexOf("_firebugEvalEvent") != -1))
432                     continue;
433 
434                 filteredFrames.push(frames[i]);
435             }
436 
437             // take the oldest frames, leave 2 behind they are injection code
438             trace.frames = filteredFrames; //trace.frames.slice(2 - i);
439 
440             return trace;
441         }
442         else
443         {
444             return "Firebug failed to get stack trace with any frames";
445         }
446     };
447 
448     function getStackFrameId(inputFrame)
449     {
450         for (var frame = Components.stack; frame; frame = frame.caller)
451         {
452             if (frame.languageName == "JavaScript"
453                 && !(frame.filename && frame.filename.indexOf("://firebug/") > 0))
454             {
455                 return frame.filename + "/" + frame.lineNumber;
456             }
457         }
458         return null;
459     };
460 
461     return console;
462 }
463 
464 // ********************************************************************************************* //
465 // Registration
466 
467 Firebug.ConsoleExposed =
468 {
469     createFirebugConsole: createFirebugConsole
470 };
471 
472 return Firebug.ConsoleExposed;
473 
474 // ********************************************************************************************* //
475 });
476