e16c89e99449457e8f4262446dbd93b4c2863b6b
[WebKit-https.git] / WebCore / inspector / front-end / ConsoleView.js
1 /*
2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
3  * Copyright (C) 2009 Joseph Pecoraro
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer. 
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution. 
14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission. 
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 const ExpressionStopCharacters = " =:[({;,!+-*/&|^<>";
31
32 WebInspector.ConsoleView = function(drawer)
33 {
34     WebInspector.View.call(this, document.getElementById("console-view"));
35
36     this.messages = [];
37     this.drawer = drawer;
38
39     this.clearButton = document.getElementById("clear-console-status-bar-item");
40     this.clearButton.title = WebInspector.UIString("Clear console log.");
41     this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false);
42
43     this.messagesElement = document.getElementById("console-messages");
44     this.messagesElement.addEventListener("selectstart", this._messagesSelectStart.bind(this), false);
45     this.messagesElement.addEventListener("click", this._messagesClicked.bind(this), true);
46
47     this.promptElement = document.getElementById("console-prompt");
48     this.promptElement.className = "source-code";
49     this.promptElement.addEventListener("keydown", this._promptKeyDown.bind(this), true);
50     this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), ExpressionStopCharacters + ".");
51     WebInspector.applicationSettings.addEventListener("loaded", this._settingsLoaded, this);
52
53     this.topGroup = new WebInspector.ConsoleGroup(null, 0);
54     this.messagesElement.insertBefore(this.topGroup.element, this.promptElement);
55     this.groupLevel = 0;
56     this.currentGroup = this.topGroup;
57
58     this.toggleConsoleButton = document.getElementById("console-status-bar-item");
59     this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
60     this.toggleConsoleButton.addEventListener("click", this._toggleConsoleButtonClicked.bind(this), false);
61
62     // Will hold the list of filter elements
63     this.filterBarElement = document.getElementById("console-filter");
64
65     function createDividerElement() {
66         var dividerElement = document.createElement("div");
67         dividerElement.addStyleClass("divider");
68         this.filterBarElement.appendChild(dividerElement);
69     }
70
71     var updateFilterHandler = this._updateFilter.bind(this);
72     function createFilterElement(category) {
73         var categoryElement = document.createElement("li");
74         categoryElement.category = category;
75         categoryElement.addStyleClass(categoryElement.category);            
76         categoryElement.addEventListener("click", updateFilterHandler, false);
77
78         var label = category.toString();
79         categoryElement.appendChild(document.createTextNode(label));
80
81         this.filterBarElement.appendChild(categoryElement);
82         return categoryElement;
83     }
84     
85     this.allElement = createFilterElement.call(this, WebInspector.UIString("All"));
86     createDividerElement.call(this);
87     this.errorElement = createFilterElement.call(this, WebInspector.UIString("Errors"));
88     this.warningElement = createFilterElement.call(this, WebInspector.UIString("Warnings"));
89     this.logElement = createFilterElement.call(this, WebInspector.UIString("Logs"));
90
91     this.filter(this.allElement, false);
92     this._registerShortcuts();
93
94     this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
95     
96     this._customFormatters = {
97         "object": this._formatobject,
98         "array":  this._formatarray,
99         "node":   this._formatnode,
100         "string": this._formatstring
101     };
102 }
103
104 WebInspector.ConsoleView.prototype = {
105     _settingsLoaded: function()
106     {
107         this.prompt.history = WebInspector.applicationSettings.consoleHistory;
108     },
109     
110     _updateFilter: function(e)
111     {
112         var isMac = WebInspector.isMac();
113         var selectMultiple = false;
114         if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
115             selectMultiple = true;
116         if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
117             selectMultiple = true;
118
119         this.filter(e.target, selectMultiple);
120     },
121     
122     filter: function(target, selectMultiple)
123     {
124         function unselectAll()
125         {
126             this.allElement.removeStyleClass("selected");
127             this.errorElement.removeStyleClass("selected");
128             this.warningElement.removeStyleClass("selected");
129             this.logElement.removeStyleClass("selected");
130             
131             this.messagesElement.removeStyleClass("filter-all");
132             this.messagesElement.removeStyleClass("filter-errors");
133             this.messagesElement.removeStyleClass("filter-warnings");
134             this.messagesElement.removeStyleClass("filter-logs");
135         }
136         
137         var targetFilterClass = "filter-" + target.category.toLowerCase();
138
139         if (target.category == "All") {
140             if (target.hasStyleClass("selected")) {
141                 // We can't unselect all, so we break early here
142                 return;
143             }
144
145             unselectAll.call(this);
146         } else {
147             // Something other than all is being selected, so we want to unselect all
148             if (this.allElement.hasStyleClass("selected")) {
149                 this.allElement.removeStyleClass("selected");
150                 this.messagesElement.removeStyleClass("filter-all");
151             }
152         }
153         
154         if (!selectMultiple) {
155             // If multiple selection is off, we want to unselect everything else
156             // and just select ourselves.
157             unselectAll.call(this);
158             
159             target.addStyleClass("selected");
160             this.messagesElement.addStyleClass(targetFilterClass);
161             
162             return;
163         }
164         
165         if (target.hasStyleClass("selected")) {
166             // If selectMultiple is turned on, and we were selected, we just
167             // want to unselect ourselves.
168             target.removeStyleClass("selected");
169             this.messagesElement.removeStyleClass(targetFilterClass);
170         } else {
171             // If selectMultiple is turned on, and we weren't selected, we just
172             // want to select ourselves.
173             target.addStyleClass("selected");
174             this.messagesElement.addStyleClass(targetFilterClass);
175         }
176     },
177     
178     _toggleConsoleButtonClicked: function()
179     {
180         this.drawer.visibleView = this;
181     },
182
183     attach: function(mainElement, statusBarElement)
184     {
185         mainElement.appendChild(this.element);
186         statusBarElement.appendChild(this.clearButton);
187         statusBarElement.appendChild(this.filterBarElement);
188     },
189
190     show: function()
191     {
192         this.toggleConsoleButton.addStyleClass("toggled-on");
193         this.toggleConsoleButton.title = WebInspector.UIString("Hide console.");
194         if (!this.prompt.isCaretInsidePrompt())
195             this.prompt.moveCaretToEndOfPrompt();
196     },
197
198     afterShow: function()
199     {
200         WebInspector.currentFocusElement = this.promptElement;  
201     },
202
203     hide: function()
204     {
205         this.toggleConsoleButton.removeStyleClass("toggled-on");
206         this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
207     },
208
209     _scheduleScrollIntoView: function()
210     {
211         if (this._scrollIntoViewTimer)
212             return;
213
214         function scrollIntoView()
215         {
216             this.promptElement.scrollIntoView(false);
217             delete this._scrollIntoViewTimer;
218         }
219         this._scrollIntoViewTimer = setTimeout(scrollIntoView.bind(this), 20);
220     },
221
222     addMessage: function(msg)
223     {
224         if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) {
225             this._incrementErrorWarningCount(msg);
226
227             // Add message to the resource panel
228             if (msg.url in WebInspector.resourceURLMap) {
229                 msg.resource = WebInspector.resourceURLMap[msg.url];
230                 if (WebInspector.panels.resources)
231                     WebInspector.panels.resources.addMessageToResource(msg.resource, msg);
232             }
233
234             this.commandSincePreviousMessage = false;
235             this.previousMessage = msg;
236         } else if (msg instanceof WebInspector.ConsoleCommand) {
237             if (this.previousMessage) {
238                 this.commandSincePreviousMessage = true;
239             }
240         }
241
242         this.messages.push(msg);
243
244         if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
245             if (this.groupLevel < 1)
246                 return;
247
248             this.groupLevel--;
249
250             this.currentGroup = this.currentGroup.parentGroup;
251         } else {
252             if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
253                 this.groupLevel++;
254
255                 var group = new WebInspector.ConsoleGroup(this.currentGroup, this.groupLevel);
256                 this.currentGroup.messagesElement.appendChild(group.element);
257                 this.currentGroup = group;
258             }
259
260             this.currentGroup.addMessage(msg);
261         }
262
263         this._scheduleScrollIntoView();
264     },
265
266     updateMessageRepeatCount: function(count)
267     {
268         var msg = this.previousMessage;
269         var prevRepeatCount = msg.totalRepeatCount;
270         
271         if (!this.commandSincePreviousMessage) {
272             msg.repeatDelta = count - prevRepeatCount;
273             msg.repeatCount = msg.repeatCount + msg.repeatDelta;
274             msg.totalRepeatCount = count;
275             msg._updateRepeatCount();
276             this._incrementErrorWarningCount(msg);
277         } else {
278             var msgCopy = new WebInspector.ConsoleMessage(msg.source, msg.type, msg.level, msg.line, msg.url, msg.groupLevel, count - prevRepeatCount, msg._messageText, msg._parameters, msg._stackTrace);
279             msgCopy.totalRepeatCount = count;
280             msgCopy._formatMessage();
281             this.addMessage(msgCopy);
282         }
283     },
284
285     _incrementErrorWarningCount: function(msg)
286     {
287         switch (msg.level) {
288             case WebInspector.ConsoleMessage.MessageLevel.Warning:
289                 WebInspector.warnings += msg.repeatDelta;
290                 break;
291             case WebInspector.ConsoleMessage.MessageLevel.Error:
292                 WebInspector.errors += msg.repeatDelta;
293                 break;
294         }
295     },
296
297     requestClearMessages: function()
298     {
299         InspectorBackend.clearConsoleMessages(WebInspector.Callback.wrap(this.clearMessages.bind(this)));
300     },
301
302     clearMessages: function()
303     {
304         if (WebInspector.panels.resources)
305             WebInspector.panels.resources.clearMessages();
306
307         this.messages = [];
308
309         this.groupLevel = 0;
310         this.currentGroup = this.topGroup;
311         this.topGroup.messagesElement.removeChildren();
312
313         WebInspector.errors = 0;
314         WebInspector.warnings = 0;
315
316         delete this.commandSincePreviousMessage;
317         delete this.previousMessage;
318     },
319
320     completions: function(wordRange, bestMatchOnly, completionsReadyCallback)
321     {
322         // Pass less stop characters to rangeOfWord so the range will be a more complete expression.
323         var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, ExpressionStopCharacters, this.promptElement, "backward");
324         var expressionString = expressionRange.toString();
325         var lastIndex = expressionString.length - 1;
326
327         var dotNotation = (expressionString[lastIndex] === ".");
328         var bracketNotation = (expressionString[lastIndex] === "[");
329
330         if (dotNotation || bracketNotation)
331             expressionString = expressionString.substr(0, lastIndex);
332
333         var prefix = wordRange.toString();
334         if (!expressionString && !prefix)
335             return;
336
337         var reportCompletions = this._reportCompletions.bind(this, bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix);
338         // Collect comma separated object properties for the completion.
339
340         var includeInspectorCommandLineAPI = (!dotNotation && !bracketNotation);
341         var callFrameId = WebInspector.panels.scripts.selectedCallFrameId();
342         var injectedScriptAccess;
343         if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
344             var selectedCallFrame = WebInspector.panels.scripts.sidebarPanes.callstack.selectedCallFrame;
345             injectedScriptAccess = InjectedScriptAccess.get(selectedCallFrame.injectedScriptId);
346         } else
347             injectedScriptAccess = InjectedScriptAccess.getDefault();
348         injectedScriptAccess.getCompletions(expressionString, includeInspectorCommandLineAPI, callFrameId, reportCompletions);
349     },
350
351     _reportCompletions: function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) {
352         if (isException)
353             return;
354
355         if (bracketNotation) {
356             if (prefix.length && prefix[0] === "'")
357                 var quoteUsed = "'";
358             else
359                 var quoteUsed = "\"";
360         }
361
362         var results = [];
363         var properties = Object.sortedProperties(result);
364
365         for (var i = 0; i < properties.length; ++i) {
366             var property = properties[i];
367
368             if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
369                 continue;
370
371             if (bracketNotation) {
372                 if (!/^[0-9]+$/.test(property))
373                     property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
374                 property += "]";
375             }
376
377             if (property.length < prefix.length)
378                 continue;
379             if (property.indexOf(prefix) !== 0)
380                 continue;
381
382             results.push(property);
383             if (bestMatchOnly)
384                 break;
385         }
386         completionsReadyCallback(results);
387     },
388
389     _clearButtonClicked: function()
390     {
391         this.requestClearMessages();
392     },
393
394     _handleContextMenuEvent: function(event)
395     {
396         if (!window.getSelection().isCollapsed) {
397             // If there is a selection, we want to show our normal context menu
398             // (with Copy, etc.), and not Clear Console.
399             return;
400         }
401
402         var contextMenu = new WebInspector.ContextMenu();
403         if (!WebInspector.monitoringXHREnabled)
404             contextMenu.appendItem(WebInspector.UIString("Enable XMLHttpRequest logging"), InspectorBackend.enableMonitoringXHR.bind(InspectorBackend));
405         else
406             contextMenu.appendItem(WebInspector.UIString("Disable XMLHttpRequest logging"), InspectorBackend.disableMonitoringXHR.bind(InspectorBackend));
407         contextMenu.appendItem(WebInspector.UIString("Clear Console"), this.requestClearMessages.bind(this));
408         contextMenu.show(event);
409     },
410
411     _messagesSelectStart: function(event)
412     {
413         if (this._selectionTimeout)
414             clearTimeout(this._selectionTimeout);
415
416         this.prompt.clearAutoComplete();
417
418         function moveBackIfOutside()
419         {
420             delete this._selectionTimeout;
421             if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
422                 this.prompt.moveCaretToEndOfPrompt();
423             this.prompt.autoCompleteSoon();
424         }
425
426         this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
427     },
428
429     _messagesClicked: function(event)
430     {
431         var link = event.target.enclosingNodeOrSelfWithNodeName("a");
432         if (!link || !link.representedNode)
433             return;
434
435         WebInspector.updateFocusedNode(link.representedNode.id);
436         event.stopPropagation();
437         event.preventDefault();
438     },
439
440     _registerShortcuts: function()
441     {
442         this._shortcuts = {};
443
444         var shortcut = WebInspector.KeyboardShortcut;
445         var shortcutK = shortcut.makeDescriptor("k", WebInspector.KeyboardShortcut.Modifiers.Meta);
446         // This case requires a separate bound function as its isMacOnly property should not be shared among different shortcut handlers.
447         this._shortcuts[shortcutK.key] = this.requestClearMessages.bind(this);
448         this._shortcuts[shortcutK.key].isMacOnly = true;
449
450         var clearConsoleHandler = this.requestClearMessages.bind(this);
451         var shortcutL = shortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl);
452         this._shortcuts[shortcutL.key] = clearConsoleHandler;
453
454         var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Console"));
455         var keys = WebInspector.isMac() ? [ shortcutK.name, shortcutL.name ] : [ shortcutL.name ];
456         section.addAlternateKeys(keys, WebInspector.UIString("Clear Console"));
457
458         keys = [
459             shortcut.shortcutToString(shortcut.Keys.Tab),
460             shortcut.shortcutToString(shortcut.Keys.Tab, shortcut.Modifiers.Shift)
461         ];
462         section.addRelatedKeys(keys, WebInspector.UIString("Next/previous suggestion"));
463         section.addKey(shortcut.shortcutToString(shortcut.Keys.Right), WebInspector.UIString("Accept suggestion"));
464         keys = [
465             shortcut.shortcutToString(shortcut.Keys.Down),
466             shortcut.shortcutToString(shortcut.Keys.Up)
467         ];
468         section.addRelatedKeys(keys, WebInspector.UIString("Next/previous line"));
469         keys = [
470             shortcut.shortcutToString("N", shortcut.Modifiers.Alt),
471             shortcut.shortcutToString("P", shortcut.Modifiers.Alt)
472         ];
473         if (WebInspector.isMac())
474             section.addRelatedKeys(keys, WebInspector.UIString("Next/previous command"));
475         section.addKey(shortcut.shortcutToString(shortcut.Keys.Enter), WebInspector.UIString("Execute command"));
476     },
477
478     _promptKeyDown: function(event)
479     {
480         if (isEnterKey(event)) {
481             this._enterKeyPressed(event);
482             return;
483         }
484
485         var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
486         var handler = this._shortcuts[shortcut];
487         if (handler) {
488             if (!this._shortcuts[shortcut].isMacOnly || WebInspector.isMac()) {
489                 handler();
490                 event.preventDefault();
491                 return;
492             }
493         }
494     },
495
496     evalInInspectedWindow: function(expression, objectGroup, callback)
497     {
498         if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
499             WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, objectGroup, callback);
500             return;
501         }
502         this.doEvalInWindow(expression, objectGroup, callback);
503     },
504
505     doEvalInWindow: function(expression, objectGroup, callback)
506     {
507         if (!expression) {
508             // There is no expression, so the completion should happen against global properties.
509             expression = "this";
510         }
511
512         function evalCallback(result)
513         {
514             callback(result.value, result.isException);
515         };
516         InjectedScriptAccess.getDefault().evaluate(expression, objectGroup, evalCallback);
517     },
518
519     _enterKeyPressed: function(event)
520     {
521         if (event.altKey)
522             return;
523
524         event.preventDefault();
525         event.stopPropagation();
526
527         this.prompt.clearAutoComplete(true);
528
529         var str = this.prompt.text;
530         if (!str.length)
531             return;
532
533         var commandMessage = new WebInspector.ConsoleCommand(str);
534         this.addMessage(commandMessage);
535
536         var self = this;
537         function printResult(result, exception)
538         {
539             self.prompt.history.push(str);
540             self.prompt.historyOffset = 0;
541             self.prompt.text = "";
542
543             WebInspector.applicationSettings.consoleHistory = self.prompt.history.slice(-30);
544
545             self.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage));
546         }
547         this.evalInInspectedWindow(str, "console", printResult);
548     },
549
550     _format: function(output, forceObjectFormat)
551     {
552         var isProxy = (output != null && typeof output === "object");
553         var type = (forceObjectFormat ? "object" : Object.proxyType(output));
554
555         var formatter = this._customFormatters[type];
556         if (!formatter || !isProxy) {
557             formatter = this._formatvalue;
558             output = output.description;
559         }
560
561         var span = document.createElement("span");
562         span.className = "console-formatted-" + type + " source-code";
563         formatter.call(this, output, span);
564         return span;
565     },
566
567     _formatvalue: function(val, elem)
568     {
569         elem.appendChild(document.createTextNode(val));
570     },
571
572     _formatobject: function(obj, elem)
573     {
574         elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element);
575     },
576
577     _formatnode: function(object, elem)
578     {
579         function printNode(nodeId)
580         {
581             if (!nodeId)
582                 return;
583             var treeOutline = new WebInspector.ElementsTreeOutline();
584             treeOutline.showInElementsPanelEnabled = true;
585             treeOutline.rootDOMNode = WebInspector.domAgent.nodeForId(nodeId);
586             treeOutline.element.addStyleClass("outline-disclosure");
587             if (!treeOutline.children[0].hasChildren)
588                 treeOutline.element.addStyleClass("single-node");
589             elem.appendChild(treeOutline.element);
590         }
591
592         InjectedScriptAccess.get(object.injectedScriptId).pushNodeToFrontend(object, printNode);
593     },
594
595     _formatarray: function(arr, elem)
596     {
597         InjectedScriptAccess.get(arr.injectedScriptId).getProperties(arr, false, false, this._printArray.bind(this, elem));
598     },
599
600     _formatstring: function(output, elem)
601     {
602         var span = document.createElement("span");
603         span.className = "console-formatted-string source-code";
604         span.appendChild(WebInspector.linkifyStringAsFragment(output.description));
605
606         // Make black quotes.
607         elem.removeStyleClass("console-formatted-string");
608         elem.appendChild(document.createTextNode("\""));
609         elem.appendChild(span);
610         elem.appendChild(document.createTextNode("\""));
611     },
612
613     _printArray: function(elem, properties)
614     {
615         if (!properties)
616             return;
617
618         var elements = [];
619         for (var i = 0; i < properties.length; ++i) {
620             var name = properties[i].name;
621             if (name == parseInt(name))
622                 elements[name] = this._formatAsArrayEntry(properties[i].value);
623         }
624
625         elem.appendChild(document.createTextNode("["));
626         for (var i = 0; i < elements.length; ++i) {
627             var element = elements[i];
628             if (element)
629                 elem.appendChild(element);
630             else
631                 elem.appendChild(document.createTextNode("undefined"))
632             if (i < elements.length - 1)
633                 elem.appendChild(document.createTextNode(", "));
634         }
635         elem.appendChild(document.createTextNode("]"));
636     },
637
638     _formatAsArrayEntry: function(output)
639     {
640         var type = Object.proxyType(output);
641         // Prevent infinite expansion of cross-referencing arrays.
642         return this._format(output, type === "array");
643     }
644 }
645
646 WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype;
647
648 WebInspector.ConsoleMessage = function(source, type, level, line, url, groupLevel, repeatCount, message, parameters, stackTrace)
649 {
650     this.source = source;
651     this.type = type;
652     this.level = level;
653     this.line = line;
654     this.url = url;
655     this.groupLevel = groupLevel;
656     this.repeatCount = repeatCount;
657     this.repeatDelta = repeatCount;
658     this.totalRepeatCount = repeatCount;
659     this._messageText = message;
660     this._parameters = parameters;
661     this._stackTrace = stackTrace;
662     this._formatMessage();
663 }
664
665 WebInspector.ConsoleMessage.createTextMessage = function(text, level)
666 {
667     level = level || WebInspector.ConsoleMessage.MessageLevel.Log;
668     return new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, 0, null, null, 1, null, [text], null);
669 }
670
671 WebInspector.ConsoleMessage.prototype = {
672     _formatMessage: function()
673     {
674         switch (this.type) {
675             case WebInspector.ConsoleMessage.MessageType.Assert:
676             case WebInspector.ConsoleMessage.MessageType.Trace:
677             case WebInspector.ConsoleMessage.MessageType.UncaughtException:
678                 var ol = document.createElement("ol");
679                 ol.addStyleClass("stack-trace");
680                 if (this.type === WebInspector.ConsoleMessage.MessageType.Trace)
681                     ol.addStyleClass("trace-message");
682                 var treeOutline = new TreeOutline(ol);
683
684                 var root = treeOutline;
685                 if (this.type === WebInspector.ConsoleMessage.MessageType.UncaughtException ||
686                     this.type === WebInspector.ConsoleMessage.MessageType.Assert) {
687                     var messageText;
688                     if (this.type === WebInspector.ConsoleMessage.MessageType.Assert)
689                         messageText = this._format(this._parameters);
690                     else
691                         messageText = document.createTextNode(this._messageText);
692
693                     var content = document.createElement("div");
694                     this._addMessageHeader(content, messageText);
695                     root = new TreeElement(content, null, true);
696                     content.treeElementForTest = root;
697                     treeOutline.appendChild(root);
698                 }
699
700                 this._populateStackTraceTreeElement(root);
701                 this.formattedMessage = ol;
702                 break;
703             case WebInspector.ConsoleMessage.MessageType.Object:
704                 var obj = this._parameters ? this._parameters[0] : undefined;
705                 this.formattedMessage = this._format(["%O", obj]);
706                 break;
707             default:
708                 var args = this._parameters || [this._messageText];
709                 this.formattedMessage = this._format(args);
710                 break;
711         }
712
713         // This is used for inline message bubbles in SourceFrames, or other plain-text representations.
714         this.message = this.formattedMessage.textContent;
715     },
716
717     isErrorOrWarning: function()
718     {
719         return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error);
720     },
721
722     _format: function(parameters)
723     {
724         // This node is used like a Builder. Values are continually appended onto it.
725         var formattedResult = document.createElement("span");
726         if (!parameters.length)
727             return formattedResult;
728
729         // Formatting code below assumes that parameters are all wrappers whereas frontend console
730         // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here.
731         for (var i = 0; i < parameters.length; ++i)
732             if (typeof parameters[i] !== "object" && typeof parameters[i] !== "function")
733                 parameters[i] = WebInspector.ObjectProxy.wrapPrimitiveValue(parameters[i]);
734
735         // There can be string log and string eval result. We distinguish between them based on message type.
736         var shouldFormatMessage = Object.proxyType(parameters[0]) === "string" && this.type !== WebInspector.ConsoleMessage.MessageType.Result;
737
738         // Multiple parameters with the first being a format string. Save unused substitutions.
739         if (shouldFormatMessage) {
740             // Multiple parameters with the first being a format string. Save unused substitutions.
741             var result = this._formatWithSubstitutionString(parameters, formattedResult);
742             parameters = result.unusedSubstitutions;
743             if (parameters.length)
744                 formattedResult.appendChild(document.createTextNode(" "));
745         }
746
747         // Single parameter, or unused substitutions from above.
748         for (var i = 0; i < parameters.length; ++i) {
749             // Inline strings when formatting.
750             if (shouldFormatMessage && parameters[i].type === "string")
751                 formattedResult.appendChild(document.createTextNode(parameters[i].description));
752             else
753                 formattedResult.appendChild(WebInspector.console._format(parameters[i]));
754             if (i < parameters.length - 1)
755                 formattedResult.appendChild(document.createTextNode(" "));
756         }
757         return formattedResult;
758     },
759
760     _formatWithSubstitutionString: function(parameters, formattedResult)
761     {
762         var formatters = {}
763         for (var i in String.standardFormatters)
764             formatters[i] = String.standardFormatters[i];
765
766         function consoleFormatWrapper(force)
767         {
768             return function(obj) {
769                 return WebInspector.console._format(obj, force);
770             };
771         }
772
773         // Firebug uses %o for formatting objects.
774         formatters.o = consoleFormatWrapper();
775         // Firebug allows both %i and %d for formatting integers.
776         formatters.i = formatters.d;
777         // Support %O to force object formatting, instead of the type-based %o formatting.
778         formatters.O = consoleFormatWrapper(true);
779
780         function append(a, b)
781         {
782             if (!(b instanceof Node))
783                 a.appendChild(WebInspector.linkifyStringAsFragment(b.toString()));
784             else
785                 a.appendChild(b);
786             return a;
787         }
788
789         // String.format does treat formattedResult like a Builder, result is an object.
790         return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append);
791     },
792
793     toMessageElement: function()
794     {
795         if (this._element)
796             return this._element;
797
798         var element = document.createElement("div");
799         element.message = this;
800         element.className = "console-message";
801
802         this._element = element;
803
804         switch (this.source) {
805             case WebInspector.ConsoleMessage.MessageSource.HTML:
806                 element.addStyleClass("console-html-source");
807                 break;
808             case WebInspector.ConsoleMessage.MessageSource.WML:
809                 element.addStyleClass("console-wml-source");
810                 break;
811             case WebInspector.ConsoleMessage.MessageSource.XML:
812                 element.addStyleClass("console-xml-source");
813                 break;
814             case WebInspector.ConsoleMessage.MessageSource.JS:
815                 element.addStyleClass("console-js-source");
816                 break;
817             case WebInspector.ConsoleMessage.MessageSource.CSS:
818                 element.addStyleClass("console-css-source");
819                 break;
820             case WebInspector.ConsoleMessage.MessageSource.Other:
821                 element.addStyleClass("console-other-source");
822                 break;
823         }
824
825         switch (this.level) {
826             case WebInspector.ConsoleMessage.MessageLevel.Tip:
827                 element.addStyleClass("console-tip-level");
828                 break;
829             case WebInspector.ConsoleMessage.MessageLevel.Log:
830                 element.addStyleClass("console-log-level");
831                 break;
832             case WebInspector.ConsoleMessage.MessageLevel.Debug:
833                 element.addStyleClass("console-debug-level");
834                 break;
835             case WebInspector.ConsoleMessage.MessageLevel.Warning:
836                 element.addStyleClass("console-warning-level");
837                 break;
838             case WebInspector.ConsoleMessage.MessageLevel.Error:
839                 element.addStyleClass("console-error-level");
840                 break;
841         }
842
843         if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
844             element.addStyleClass("console-group-title");
845
846         if (this.elementsTreeOutline) {
847             element.addStyleClass("outline-disclosure");
848             element.appendChild(this.elementsTreeOutline.element);
849             return element;
850         }
851
852         if (this.type === WebInspector.ConsoleMessage.MessageType.Trace ||
853             this.type === WebInspector.ConsoleMessage.MessageType.Assert ||
854             this.type === WebInspector.ConsoleMessage.MessageType.UncaughtException)
855             element.appendChild(this.formattedMessage);
856         else
857             this._addMessageHeader(element, this.formattedMessage);
858
859         if (this.repeatCount > 1)
860             this._updateRepeatCount();
861
862         return element;
863     },
864
865     _populateStackTraceTreeElement: function(parentTreeElement)
866     {
867         for (var i = 0; i < this._stackTrace.length; i++) {
868             var frame = this._stackTrace[i];
869
870             var content = document.createElement("div");
871             var messageTextElement = document.createElement("span");
872             messageTextElement.className = "console-message-text source-code";
873             var functionName = frame.functionName || WebInspector.UIString("(anonymous function)");
874             messageTextElement.appendChild(document.createTextNode(functionName));
875             content.appendChild(messageTextElement);
876
877             var urlElement = WebInspector.linkifyResourceAsNode(frame.sourceURL, "scripts", frame.lineNumber, "console-message-url"); 
878             content.appendChild(urlElement);
879
880             var treeElement = new TreeElement(content);
881             parentTreeElement.appendChild(treeElement);
882         }
883     },
884
885     _addMessageHeader: function(parentElement, formattedMessage)
886     {
887         if (this.url && this.url !== "undefined") {
888             var urlElement = WebInspector.linkifyResourceAsNode(this.url, "scripts", this.line, "console-message-url"); 
889             parentElement.appendChild(urlElement);
890         }
891
892         var messageTextElement = document.createElement("span");
893         messageTextElement.className = "console-message-text source-code";
894         if (this.type === WebInspector.ConsoleMessage.MessageType.Assert)
895             messageTextElement.appendChild(document.createTextNode(WebInspector.UIString("Assertion failed: ")));
896         messageTextElement.appendChild(formattedMessage);
897         parentElement.appendChild(messageTextElement);
898     },
899
900     _updateRepeatCount: function() {
901         if (!this.repeatCountElement) {
902             this.repeatCountElement = document.createElement("span");
903             this.repeatCountElement.className = "bubble";
904     
905             this._element.insertBefore(this.repeatCountElement, this._element.firstChild);
906             this._element.addStyleClass("repeated-message");
907         }
908         this.repeatCountElement.textContent = this.repeatCount;
909     },
910
911     toString: function()
912     {
913         var sourceString;
914         switch (this.source) {
915             case WebInspector.ConsoleMessage.MessageSource.HTML:
916                 sourceString = "HTML";
917                 break;
918             case WebInspector.ConsoleMessage.MessageSource.WML:
919                 sourceString = "WML";
920                 break;
921             case WebInspector.ConsoleMessage.MessageSource.XML:
922                 sourceString = "XML";
923                 break;
924             case WebInspector.ConsoleMessage.MessageSource.JS:
925                 sourceString = "JS";
926                 break;
927             case WebInspector.ConsoleMessage.MessageSource.CSS:
928                 sourceString = "CSS";
929                 break;
930             case WebInspector.ConsoleMessage.MessageSource.Other:
931                 sourceString = "Other";
932                 break;
933         }
934
935         var typeString;
936         switch (this.type) {
937             case WebInspector.ConsoleMessage.MessageType.Log:
938             case WebInspector.ConsoleMessage.MessageType.UncaughtException:
939                 typeString = "Log";
940                 break;
941             case WebInspector.ConsoleMessage.MessageType.Object:
942                 typeString = "Object";
943                 break;
944             case WebInspector.ConsoleMessage.MessageType.Trace:
945                 typeString = "Trace";
946                 break;
947             case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
948             case WebInspector.ConsoleMessage.MessageType.StartGroup:
949                 typeString = "Start Group";
950                 break;
951             case WebInspector.ConsoleMessage.MessageType.EndGroup:
952                 typeString = "End Group";
953                 break;
954             case WebInspector.ConsoleMessage.MessageType.Assert:
955                 typeString = "Assert";
956                 break;
957             case WebInspector.ConsoleMessage.MessageType.Result:
958                 typeString = "Result";
959                 break;
960         }
961         
962         var levelString;
963         switch (this.level) {
964             case WebInspector.ConsoleMessage.MessageLevel.Tip:
965                 levelString = "Tip";
966                 break;
967             case WebInspector.ConsoleMessage.MessageLevel.Log:
968                 levelString = "Log";
969                 break;
970             case WebInspector.ConsoleMessage.MessageLevel.Warning:
971                 levelString = "Warning";
972                 break;
973             case WebInspector.ConsoleMessage.MessageLevel.Debug:
974                 levelString = "Debug";
975                 break;
976             case WebInspector.ConsoleMessage.MessageLevel.Error:
977                 levelString = "Error";
978                 break;
979         }
980
981         return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line;
982     },
983
984     isEqual: function(msg, disreguardGroup)
985     {
986         if (!msg)
987             return false;
988
989         var ret = (this.source == msg.source)
990             && (this.type == msg.type)
991             && (this.level == msg.level)
992             && (this.line == msg.line)
993             && (this.url == msg.url)
994             && (this.message == msg.message);
995
996         return (disreguardGroup ? ret : (ret && (this.groupLevel == msg.groupLevel)));
997     }
998 }
999
1000 // Note: Keep these constants in sync with the ones in Console.h
1001 WebInspector.ConsoleMessage.MessageSource = {
1002     HTML: 0,
1003     WML: 1,
1004     XML: 2,
1005     JS: 3,
1006     CSS: 4,
1007     Other: 5
1008 }
1009
1010 WebInspector.ConsoleMessage.MessageType = {
1011     Log: 0,
1012     Object: 1,
1013     Trace: 2,
1014     StartGroup: 3,
1015     StartGroupCollapsed: 4,
1016     EndGroup: 5,
1017     Assert: 6,
1018     UncaughtException: 7,
1019     Result: 8
1020 }
1021
1022 WebInspector.ConsoleMessage.MessageLevel = {
1023     Tip: 0,
1024     Log: 1,
1025     Warning: 2,
1026     Error: 3,
1027     Debug: 4
1028 }
1029
1030 WebInspector.ConsoleCommand = function(command)
1031 {
1032     this.command = command;
1033 }
1034
1035 WebInspector.ConsoleCommand.prototype = {
1036     toMessageElement: function()
1037     {
1038         var element = document.createElement("div");
1039         element.command = this;
1040         element.className = "console-user-command";
1041
1042         var commandTextElement = document.createElement("span");
1043         commandTextElement.className = "console-message-text source-code";
1044         commandTextElement.textContent = this.command;
1045         element.appendChild(commandTextElement);
1046
1047         return element;
1048     }
1049 }
1050
1051 WebInspector.ConsoleCommandResult = function(result, exception, originatingCommand)
1052 {
1053     var level = (exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
1054     var message = result;
1055     if (exception) {
1056         // Distinguish between strings and errors (no need to quote latter).
1057         message = WebInspector.ObjectProxy.wrapPrimitiveValue(result);
1058         message.type = "error";
1059     }
1060     var line = (exception ? result.line : -1);
1061     var url = (exception ? result.sourceURL : null);
1062
1063     this.originatingCommand = originatingCommand;
1064
1065     WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Result, level, line, url, null, 1, null, [message]);
1066 }
1067
1068 WebInspector.ConsoleCommandResult.prototype = {
1069     toMessageElement: function()
1070     {
1071         var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this);
1072         element.addStyleClass("console-user-command-result");
1073         return element;
1074     }
1075 }
1076
1077 WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;
1078
1079 WebInspector.ConsoleGroup = function(parentGroup, level)
1080 {
1081     this.parentGroup = parentGroup;
1082     this.level = level;
1083
1084     var element = document.createElement("div");
1085     element.className = "console-group";
1086     element.group = this;
1087     this.element = element;
1088
1089     var messagesElement = document.createElement("div");
1090     messagesElement.className = "console-group-messages";
1091     element.appendChild(messagesElement);
1092     this.messagesElement = messagesElement;
1093 }
1094
1095 WebInspector.ConsoleGroup.prototype = {
1096     addMessage: function(msg)
1097     {
1098         var element = msg.toMessageElement();
1099
1100         if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
1101             this.messagesElement.parentNode.insertBefore(element, this.messagesElement);
1102             element.addEventListener("click", this._titleClicked.bind(this), true);
1103             var groupElement = element.enclosingNodeOrSelfWithClass("console-group");
1104             if (groupElement && msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
1105                 groupElement.addStyleClass("collapsed");
1106         } else
1107             this.messagesElement.appendChild(element);
1108
1109         if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand)
1110             element.previousSibling.addStyleClass("console-adjacent-user-command-result");
1111     },
1112
1113     _titleClicked: function(event)
1114     {
1115         var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title");
1116         if (groupTitleElement) {
1117             var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group");
1118             if (groupElement)
1119                 if (groupElement.hasStyleClass("collapsed"))
1120                     groupElement.removeStyleClass("collapsed");
1121                 else
1122                     groupElement.addStyleClass("collapsed");
1123             groupTitleElement.scrollIntoViewIfNeeded(true);
1124         }
1125
1126         event.stopPropagation();
1127         event.preventDefault();
1128     }
1129 }
1130
1131 WebInspector.didClearConsoleMessages = WebInspector.Callback.processCallback;