2008-03-26 Timothy Hatcher <timothy@apple.com>
[WebKit-https.git] / WebCore / page / inspector / Console.js
1 /*
2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 WebInspector.ConsolePanel = function()
30 {
31     WebInspector.Panel.call(this);
32
33     this.messages = [];
34
35     this.commandHistory = [];
36     this.commandOffset = 0;
37
38     this.messagesElement = document.createElement("div");
39     this.messagesElement.id = "console-messages";
40     this.messagesElement.addEventListener("selectstart", this.messagesSelectStart.bind(this), true);
41     this.messagesElement.addEventListener("click", this.messagesClicked.bind(this), true);
42     this.element.appendChild(this.messagesElement);
43
44     this.promptElement = document.createElement("div");
45     this.promptElement.id = "console-prompt";
46     this.promptElement.addEventListener("keydown", this.promptKeyDown.bind(this), false);
47     this.promptElement.appendChild(document.createElement("br"));
48     this.messagesElement.appendChild(this.promptElement);
49
50     this.clearButton = document.createElement("button");
51     this.clearButton.title = WebInspector.UIString("Clear");
52     this.clearButton.textContent = WebInspector.UIString("Clear");
53     this.clearButton.addEventListener("click", this.clearButtonClicked.bind(this), false);
54 }
55
56 WebInspector.ConsolePanel.prototype = {
57     get promptText()
58     {
59         return this.promptElement.textContent;
60     },
61
62     set promptText(x)
63     {
64         if (!x) {
65             // Append a break element instead of setting textContent to make sure the selection is inside the prompt.
66             this.promptElement.removeChildren();
67             this.promptElement.appendChild(document.createElement("br"));
68         } else
69             this.promptElement.textContent = x;
70
71         this._moveCaretToEndOfPrompt();
72     },
73
74     show: function()
75     {
76         WebInspector.Panel.prototype.show.call(this);
77         WebInspector.consoleListItem.select();
78
79         this.clearButton.removeStyleClass("hidden");
80         if (!this.clearButton.parentNode)
81             document.getElementById("toolbarButtons").appendChild(this.clearButton);
82
83         WebInspector.currentFocusElement = document.getElementById("main");
84
85         function focusPrompt()
86         {
87             if (!this._caretInsidePrompt())
88                 this._moveCaretToEndOfPrompt();
89         }
90
91         setTimeout(focusPrompt.bind(this), 0);
92     },
93
94     hide: function()
95     {
96         WebInspector.Panel.prototype.hide.call(this);
97         WebInspector.consoleListItem.deselect();
98         this.clearButton.addStyleClass("hidden");
99     },
100
101     addMessage: function(msg)
102     {
103         if (msg.url in WebInspector.resourceURLMap) {
104             msg.resource = WebInspector.resourceURLMap[msg.url];
105             switch (msg.level) {
106                 case WebInspector.ConsoleMessage.MessageLevel.Warning:
107                     ++msg.resource.warnings;
108                     msg.resource.panel.addMessageToSource(msg);
109                     break;
110                 case WebInspector.ConsoleMessage.MessageLevel.Error:
111                     ++msg.resource.errors;
112                     msg.resource.panel.addMessageToSource(msg);
113                     break;
114             }
115         }
116
117         this.messages.push(msg);
118
119         var element = msg.toMessageElement();
120         this.messagesElement.insertBefore(element, this.promptElement);
121         this.promptElement.scrollIntoView(false);
122     },
123
124     clearMessages: function()
125     {
126         for (var i = 0; i < this.messages.length; ++i) {
127             var resource = this.messages[i].resource;
128             if (!resource)
129                 continue;
130             resource.errors = 0;
131             resource.warnings = 0;
132         }
133
134         this.messages = [];
135
136         while (this.messagesElement.firstChild != this.promptElement)
137             this.messagesElement.removeChild(this.messagesElement.firstChild);
138     },
139
140     acceptAutoComplete: function()
141     {
142         if (!this.autoCompleteElement || !this.autoCompleteElement.parentNode)
143             return false;
144
145         var text = this.autoCompleteElement.textContent;
146         var textNode = document.createTextNode(text);
147         this.autoCompleteElement.parentNode.replaceChild(textNode, this.autoCompleteElement);
148         delete this.autoCompleteElement;
149
150         var finalSelectionRange = document.createRange();
151         finalSelectionRange.setStart(textNode, text.length);
152         finalSelectionRange.setEnd(textNode, text.length);
153
154         var selection = window.getSelection();
155         selection.removeAllRanges();
156         selection.addRange(finalSelectionRange);
157
158         return true;
159     },
160
161     clearAutoComplete: function(includeTimeout)
162     {
163         if (includeTimeout && "completeTimeout" in this) {
164             clearTimeout(this.completeTimeout);
165             delete this.completeTimeout;
166         }
167
168         if (!this.autoCompleteElement)
169             return;
170
171         if (this.autoCompleteElement.parentNode)
172             this.autoCompleteElement.parentNode.removeChild(this.autoCompleteElement);
173         delete this.autoCompleteElement;
174     },
175
176     autoCompleteSoon: function()
177     {
178         if (!("completeTimeout" in this))
179             this.completeTimeout = setTimeout(this.complete.bind(this, true), 250);
180     },
181
182     complete: function(auto)
183     {
184         this.clearAutoComplete(true);
185
186         var selection = window.getSelection();
187         if (!selection.rangeCount)
188             return;
189
190         var selectionRange = selection.getRangeAt(0);
191         if (!selectionRange.commonAncestorContainer.isDescendant(this.promptElement))
192             return;
193         if (auto && !this._caretAtEndOfPrompt())
194             return;
195
196         // Pass more characters to _backwardsRange so the range will be as short as possible.
197         var wordPrefixRange = this._backwardsRange(" .=:[({;", selectionRange.startContainer, selectionRange.startOffset, this.promptElement);
198
199         var completions = this.completions(wordPrefixRange, auto);
200
201         if (!completions || !completions.length)
202             return;
203
204         var fullWordRange = document.createRange();
205         fullWordRange.setStart(wordPrefixRange.startContainer, wordPrefixRange.startOffset);
206         fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffset);
207
208         if (completions.length === 1 || selection.isCollapsed || auto) {
209             var completionText = completions[0];
210         } else {
211             var currentText = fullWordRange.toString().trimTrailingWhitespace();
212
213             var foundIndex = null;
214             for (var i = 0; i < completions.length; ++i) {
215                 if (completions[i] === currentText)
216                     foundIndex = i;
217             }
218
219             if (foundIndex === null || (foundIndex + 1) >= completions.length)
220                 var completionText = completions[0];
221             else
222                 var completionText = completions[foundIndex + 1];
223         }
224
225         var wordPrefixLength = wordPrefixRange.toString().length;
226
227         fullWordRange.deleteContents();
228
229         var finalSelectionRange = document.createRange();
230
231         if (auto) {
232             var prefixText = completionText.substring(0, wordPrefixLength);
233             var suffixText = completionText.substring(wordPrefixLength);
234
235             var prefixTextNode = document.createTextNode(prefixText);
236             fullWordRange.insertNode(prefixTextNode);           
237
238             this.autoCompleteElement = document.createElement("span");
239             this.autoCompleteElement.className = "auto-complete-text";
240             this.autoCompleteElement.textContent = suffixText;
241
242             prefixTextNode.parentNode.insertBefore(this.autoCompleteElement, prefixTextNode.nextSibling);
243
244             finalSelectionRange.setStart(prefixTextNode, wordPrefixLength);
245             finalSelectionRange.setEnd(prefixTextNode, wordPrefixLength);
246         } else {
247             var completionTextNode = document.createTextNode(completionText);
248             fullWordRange.insertNode(completionTextNode);           
249
250             if (completions.length > 1)
251                 finalSelectionRange.setStart(completionTextNode, wordPrefixLength);
252             else
253                 finalSelectionRange.setStart(completionTextNode, completionText.length);
254
255             finalSelectionRange.setEnd(completionTextNode, completionText.length);
256         }
257
258         selection.removeAllRanges();
259         selection.addRange(finalSelectionRange);
260     },
261
262     completions: function(wordRange, bestMatchOnly)
263     {
264         // Pass less characters to _backwardsRange so the range will be a more complete expression.
265         var expression = this._backwardsRange(" =:{;", wordRange.startContainer, wordRange.startOffset, this.promptElement);
266         var expressionString = expression.toString();
267         var lastIndex = expressionString.length - 1;
268
269         var dotNotation = (expressionString[lastIndex] === ".");
270         var bracketNotation = (expressionString[lastIndex] === "[");
271
272         if (dotNotation || bracketNotation)
273             expressionString = expressionString.substr(0, lastIndex);
274
275         var prefix = wordRange.toString();
276         if (!expressionString && !prefix)
277             return;
278
279         var result = InspectorController.inspectedWindow();
280         if (expressionString) {
281             try {
282                 result = this._evalInInspectedWindow(expressionString);
283             } catch(e) {
284                 // Do nothing, the prefix will be considered a window property.
285             }
286         }
287
288         if (bracketNotation) {
289             if (prefix.length && prefix[0] === "'")
290                 var quoteUsed = "'";
291             else
292                 var quoteUsed = "\"";
293         }
294
295         var results = [];
296         var properties = Object.sortedProperties(result);
297         for (var i = 0; i < properties.length; ++i) {
298             var property = properties[i];
299             if (bracketNotation)
300                 property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed + "]";
301             if (property.length < prefix.length)
302                 continue;
303             if (property.indexOf(prefix) !== 0)
304                 continue;
305             results.push(property);
306             if (bestMatchOnly)
307                 break;
308         }
309
310         return results;
311     },
312
313     clearButtonClicked: function()
314     {
315         this.clearMessages();
316     },
317
318     messagesSelectStart: function(event)
319     {
320         if (this._selectionTimeout)
321             clearTimeout(this._selectionTimeout);
322
323         this.clearAutoComplete();
324
325         function moveBackIfOutside()
326         {
327             delete this._selectionTimeout;
328             if (!this._caretInsidePrompt() && window.getSelection().isCollapsed)
329                 this._moveCaretToEndOfPrompt();
330             this.autoCompleteSoon();
331         }
332
333         this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
334     },
335
336     messagesClicked: function(event)
337     {
338         var link = event.target.enclosingNodeOrSelfWithNodeName("a");
339         if (link && link.representedNode) {
340             WebInspector.updateFocusedNode(link.representedNode);
341             return;
342         }
343
344         var messageElement = event.target.enclosingNodeOrSelfWithClass("console-message");
345         if (!messageElement)
346             return;
347
348         if (!messageElement.message)
349             return;
350
351         var resource = messageElement.message.resource;
352         if (!resource)
353             return;
354
355         if (link && link.hasStyleClass("console-message-url")) {
356             WebInspector.navigateToResource(resource);
357             resource.panel.showSourceLine(item.message.line);
358         }
359
360         event.stopPropagation();
361         event.preventDefault();
362     },
363
364     promptKeyDown: function(event)
365     {
366         switch (event.keyIdentifier) {
367             case "Enter":
368                 this._onEnterPressed(event);
369                 break;
370             case "Up":
371                 this._onUpPressed(event);
372                 break;
373             case "Down":
374                 this._onDownPressed(event);
375                 break;
376             case "U+0009": // Tab
377                 this._onTabPressed(event);
378                 break;
379             case "Right":
380                 if (!this.acceptAutoComplete())
381                     this.autoCompleteSoon();
382                 break;
383             default:
384                 this.clearAutoComplete();
385                 this.autoCompleteSoon();
386                 break;
387         }
388     },
389
390     _backwardsRange: function(stopCharacters, endNode, endOffset, stayWithinElement)
391     {
392         var startNode;
393         var startOffset = 0;
394         var node = endNode;
395
396         while (node) {
397             if (node === stayWithinElement) {
398                 if (!startNode)
399                     startNode = stayWithinElement;
400                 break;
401             }
402
403             if (node.nodeType === Node.TEXT_NODE) {
404                 var start = (node === endNode ? endOffset : node.nodeValue.length);
405                 for (var i = (start - 1); i >= 0; --i) {
406                     var character = node.nodeValue[i];
407                     if (stopCharacters.indexOf(character) !== -1) {
408                         startNode = node;
409                         startOffset = i + 1;
410                         break;
411                     }
412                 }
413             }
414
415             if (startNode)
416                 break;
417
418             node = node.traversePreviousNode();
419         }
420
421         var result = document.createRange();
422         result.setStart(startNode, startOffset);
423         result.setEnd(endNode, endOffset);
424
425         return result;
426     },
427
428     _evalInInspectedWindow: function(expression)
429     {
430         // This with block is needed to work around http://bugs.webkit.org/show_bug.cgi?id=11399
431         with (InspectorController.inspectedWindow()) {
432             return eval(expression);
433         }
434     },
435
436     _caretInsidePrompt: function()
437     {
438         var selection = window.getSelection();
439         if (!selection.rangeCount || !selection.isCollapsed)
440             return false;
441         var selectionRange = selection.getRangeAt(0);
442         return selectionRange.startContainer === this.promptElement || selectionRange.startContainer.isDescendant(this.promptElement);
443     },
444
445     _caretAtEndOfPrompt: function()
446     {
447         var selection = window.getSelection();
448         if (!selection.rangeCount || !selection.isCollapsed)
449             return false;
450
451         var selectionRange = selection.getRangeAt(0);
452         var node = selectionRange.startContainer;
453         if (node !== this.promptElement && !node.isDescendant(this.promptElement))
454             return false;
455
456         if (node.nodeType === Node.TEXT_NODE && selectionRange.startOffset < node.nodeValue.length)
457             return false;
458
459         var foundNextText = false;
460         while (node) {
461             if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) {
462                 if (foundNextText)
463                     return false;
464                 foundNextText = true;
465             }
466
467             node = node.traverseNextNode(false, this.promptElement);
468         }
469
470         return true;
471     },
472
473     _moveCaretToEndOfPrompt: function()
474     {
475         var selection = window.getSelection();
476         var selectionRange = document.createRange();
477
478         var offset = this.promptElement.childNodes.length;
479         selectionRange.setStart(this.promptElement, offset);
480         selectionRange.setEnd(this.promptElement, offset);
481
482         selection.removeAllRanges();
483         selection.addRange(selectionRange);
484     },
485
486     _onTabPressed: function(event)
487     {
488         event.preventDefault();
489         event.stopPropagation();
490         this.complete();
491     },
492
493     _onEnterPressed: function(event)
494     {
495         event.preventDefault();
496         event.stopPropagation();
497
498         this.clearAutoComplete(true);
499
500         var str = this.promptText;
501         if (!str.length)
502             return;
503
504         this.commandHistory.push(str);
505         this.commandOffset = 0;
506
507         this.promptText = "";
508
509         var result;
510         var exception = false;
511         try {
512             result = this._evalInInspectedWindow(str);
513         } catch(e) {
514             result = e;
515             exception = true;
516         }
517
518         var level = exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log;
519         this.addMessage(new WebInspector.ConsoleCommand(str, result, this._format(result), level));
520     },
521
522     _onUpPressed: function(event)
523     {
524         event.preventDefault();
525         event.stopPropagation();
526
527         if (this.commandOffset == this.commandHistory.length)
528             return;
529
530         if (this.commandOffset == 0)
531             this.tempSavedCommand = this.promptText;
532
533         ++this.commandOffset;
534         this.promptText = this.commandHistory[this.commandHistory.length - this.commandOffset];
535     },
536
537     _onDownPressed: function(event)
538     {
539         event.preventDefault();
540         event.stopPropagation();
541
542         if (this.commandOffset == 0)
543             return;
544
545         --this.commandOffset;
546
547         if (this.commandOffset == 0) {
548             this.promptText = this.tempSavedCommand;
549             delete this.tempSavedCommand;
550             return;
551         }
552
553         this.promptText = this.commandHistory[this.commandHistory.length - this.commandOffset];
554     },
555
556     _format: function(output)
557     {
558         var type = Object.type(output);
559         if (type === "object") {
560             if (output instanceof Node)
561                 type = "node";
562         }
563
564         // We don't perform any special formatting on these types, so we just
565         // pass them through the simple _formatvalue function.
566         var undecoratedTypes = {
567             "undefined": 1,
568             "null": 1,
569             "boolean": 1,
570             "number": 1,
571             "date": 1,
572             "function": 1,
573         };
574
575         var formatter;
576         if (type in undecoratedTypes)
577             formatter = "_formatvalue";
578         else {
579             formatter = "_format" + type;
580             if (!(formatter in this)) {
581                 formatter = "_formatobject";
582                 type = "object";
583             }
584         }
585
586         var span = document.createElement("span");
587         span.addStyleClass("console-formatted-" + type);
588         this[formatter](output, span);
589         return span;
590     },
591
592     _formatvalue: function(val, elem)
593     {
594         elem.appendChild(document.createTextNode(val));
595     },
596
597     _formatstring: function(str, elem)
598     {
599         elem.appendChild(document.createTextNode("\"" + str + "\""));
600     },
601
602     _formatregexp: function(re, elem)
603     {
604         var formatted = String(re).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1);
605         elem.appendChild(document.createTextNode(formatted));
606     },
607
608     _formatarray: function(arr, elem)
609     {
610         elem.appendChild(document.createTextNode("["));
611         for (var i = 0; i < arr.length; ++i) {
612             elem.appendChild(this._format(arr[i]));
613             if (i < arr.length - 1)
614                 elem.appendChild(document.createTextNode(", "));
615         }
616         elem.appendChild(document.createTextNode("]"));
617     },
618
619     _formatnode: function(node, elem)
620     {
621         var anchor = document.createElement("a");
622         anchor.innerHTML = node.titleInfo().title;
623         anchor.representedNode = node;
624         anchor.addEventListener("mouseover", function() { InspectorController.highlightDOMNode(node) }, false);
625         anchor.addEventListener("mouseout", function() { InspectorController.hideDOMNodeHighlight() }, false);
626         elem.appendChild(anchor);
627     },
628
629     _formatobject: function(obj, elem)
630     {
631         elem.appendChild(document.createTextNode(Object.describe(obj)));
632     },
633 }
634
635 WebInspector.ConsolePanel.prototype.__proto__ = WebInspector.Panel.prototype;
636
637 WebInspector.ConsoleMessage = function(source, level, message, line, url)
638 {
639     this.source = source;
640     this.level = level;
641     this.message = message;
642     this.line = line;
643     this.url = url;
644 }
645
646 WebInspector.ConsoleMessage.prototype = {
647     get shortURL()
648     {
649         if (this.resource)
650             return this.resource.displayName;
651         return this.url;
652     },
653
654     toMessageElement: function()
655     {
656         var element = document.createElement("div");
657         element.message = this;
658         element.className = "console-message";
659
660         switch (this.source) {
661             case WebInspector.ConsoleMessage.MessageSource.HTML:
662                 element.addStyleClass("console-html-source");
663                 break;
664             case WebInspector.ConsoleMessage.MessageSource.XML:
665                 element.addStyleClass("console-xml-source");
666                 break;
667             case WebInspector.ConsoleMessage.MessageSource.JS:
668                 element.addStyleClass("console-js-source");
669                 break;
670             case WebInspector.ConsoleMessage.MessageSource.CSS:
671                 element.addStyleClass("console-css-source");
672                 break;
673             case WebInspector.ConsoleMessage.MessageSource.Other:
674                 element.addStyleClass("console-other-source");
675                 break;
676         }
677
678         switch (this.level) {
679             case WebInspector.ConsoleMessage.MessageLevel.Tip:
680                 element.addStyleClass("console-tip-level");
681                 break;
682             case WebInspector.ConsoleMessage.MessageLevel.Log:
683                 element.addStyleClass("console-log-level");
684                 break;
685             case WebInspector.ConsoleMessage.MessageLevel.Warning:
686                 element.addStyleClass("console-warning-level");
687                 break;
688             case WebInspector.ConsoleMessage.MessageLevel.Error:
689                 element.addStyleClass("console-error-level");
690         }
691
692         var messageTextElement = document.createElement("span");
693         messageTextElement.className = "console-message-text";
694         messageTextElement.textContent = this.message;
695         element.appendChild(messageTextElement);
696
697         element.appendChild(document.createTextNode(" "));
698
699         if (this.url && this.url !== "undefined") {
700             var urlElement = document.createElement("a");
701             urlElement.className = "console-message-url";
702
703             if (this.line > 0)
704                 urlElement.textContent = WebInspector.UIString("%s (line %d)", this.url, this.line);
705             else
706                 urlElement.textContent = this.url;
707
708             element.appendChild(urlElement);
709         }
710
711         return element;
712     },
713
714     toString: function()
715     {
716         var sourceString;
717         switch (this.source) {
718             case WebInspector.ConsoleMessage.MessageSource.HTML:
719                 sourceString = "HTML";
720                 break;
721             case WebInspector.ConsoleMessage.MessageSource.XML:
722                 sourceString = "XML";
723                 break;
724             case WebInspector.ConsoleMessage.MessageSource.JS:
725                 sourceString = "JS";
726                 break;
727             case WebInspector.ConsoleMessage.MessageSource.CSS:
728                 sourceString = "CSS";
729                 break;
730             case WebInspector.ConsoleMessage.MessageSource.Other:
731                 sourceString = "Other";
732                 break;
733         }
734
735         var levelString;
736         switch (this.level) {
737             case WebInspector.ConsoleMessage.MessageLevel.Tip:
738                 levelString = "Tip";
739                 break;
740             case WebInspector.ConsoleMessage.MessageLevel.Log:
741                 levelString = "Log";
742                 break;
743             case WebInspector.ConsoleMessage.MessageLevel.Warning:
744                 levelString = "Warning";
745                 break;
746             case WebInspector.ConsoleMessage.MessageLevel.Error:
747                 levelString = "Error";
748                 break;
749         }
750
751         return sourceString + " " + levelString + ": " + this.message + "\n" + this.url + " line " + this.line;
752     }
753 }
754
755 // Note: Keep these constants in sync with the ones in Chrome.h
756 WebInspector.ConsoleMessage.MessageSource = {
757     HTML: 0,
758     XML: 1,
759     JS: 2,
760     CSS: 3,
761     Other: 4,
762 }
763
764 WebInspector.ConsoleMessage.MessageLevel = {
765     Tip: 0,
766     Log: 1,
767     Warning: 2,
768     Error: 3
769 }
770
771 WebInspector.ConsoleCommand = function(command, result, formattedResultElement, level)
772 {
773     this.command = command;
774     this.formattedResultElement = formattedResultElement;
775     this.level = level;
776 }
777
778 WebInspector.ConsoleCommand.prototype = {
779     toMessageElement: function()
780     {
781         var element = document.createElement("div");
782         element.command = this;
783         element.className = "console-user-command";
784
785         var commandTextElement = document.createElement("span");
786         commandTextElement.className = "console-message-text";
787         commandTextElement.textContent = this.command;
788         element.appendChild(commandTextElement);
789
790         var resultElement = document.createElement("div");
791         resultElement.className = "console-message";
792         element.appendChild(resultElement);
793
794         switch (this.level) {
795             case WebInspector.ConsoleMessage.MessageLevel.Log:
796                 resultElement.addStyleClass("console-log-level");
797                 break;
798             case WebInspector.ConsoleMessage.MessageLevel.Warning:
799                 resultElement.addStyleClass("console-warning-level");
800                 break;
801             case WebInspector.ConsoleMessage.MessageLevel.Error:
802                 resultElement.addStyleClass("console-error-level");
803         }
804
805         var resultTextElement = document.createElement("span");
806         resultTextElement.className = "console-message-text";
807         resultTextElement.appendChild(this.formattedResultElement);
808         resultElement.appendChild(resultTextElement);
809
810         return element;
811     }
812 }