2008-03-16 Timothy Hatcher <timothy@apple.com>
[WebKit-https.git] / WebCore / page / inspector / ConsolePanel.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
194         if (auto) {
195             if (!selection.isCollapsed)
196                 return;
197
198             var node = selectionRange.startContainer;
199             if (node.nodeType === Node.TEXT_NODE && selectionRange.startOffset < node.nodeValue.length)
200                 return;
201
202             var foundNextText = false;
203             while (node) {
204                 if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) {
205                     if (foundNextText)
206                         return;
207                     foundNextText = true;
208                 }
209
210                 node = node.traverseNextNode(false, this.promptElement);
211             }
212         }
213
214         // Pass more characters to _backwardsRange so the range will be as short as possible.
215         var wordPrefixRange = this._backwardsRange(" .=:[({;", selectionRange.startContainer, selectionRange.startOffset, this.promptElement);
216
217         var completions = this.completions(wordPrefixRange, auto);
218
219         if (!completions || !completions.length)
220             return;
221
222         var fullWordRange = document.createRange();
223         fullWordRange.setStart(wordPrefixRange.startContainer, wordPrefixRange.startOffset);
224         fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffset);
225
226         if (completions.length === 1 || selection.isCollapsed || auto) {
227             var completionText = completions[0];
228         } else {
229             var currentText = fullWordRange.toString().trimTrailingWhitespace();
230
231             var foundIndex = null;
232             for (var i = 0; i < completions.length; ++i) {
233                 if (completions[i] === currentText)
234                     foundIndex = i;
235             }
236
237             if (foundIndex === null || (foundIndex + 1) >= completions.length)
238                 var completionText = completions[0];
239             else
240                 var completionText = completions[foundIndex + 1];
241         }
242
243         var wordPrefixLength = wordPrefixRange.toString().length;
244
245         fullWordRange.deleteContents();
246
247         var finalSelectionRange = document.createRange();
248
249         if (auto) {
250             var prefixText = completionText.substring(0, wordPrefixLength);
251             var suffixText = completionText.substring(wordPrefixLength);
252
253             var prefixTextNode = document.createTextNode(prefixText);
254             fullWordRange.insertNode(prefixTextNode);           
255
256             this.autoCompleteElement = document.createElement("span");
257             this.autoCompleteElement.className = "auto-complete-text";
258             this.autoCompleteElement.textContent = suffixText;
259
260             prefixTextNode.parentNode.insertBefore(this.autoCompleteElement, prefixTextNode.nextSibling);
261
262             finalSelectionRange.setStart(prefixTextNode, wordPrefixLength);
263             finalSelectionRange.setEnd(prefixTextNode, wordPrefixLength);
264         } else {
265             var completionTextNode = document.createTextNode(completionText);
266             fullWordRange.insertNode(completionTextNode);           
267
268             if (completions.length > 1)
269                 finalSelectionRange.setStart(completionTextNode, wordPrefixLength);
270             else
271                 finalSelectionRange.setStart(completionTextNode, completionText.length);
272
273             finalSelectionRange.setEnd(completionTextNode, completionText.length);
274         }
275
276         selection.removeAllRanges();
277         selection.addRange(finalSelectionRange);
278     },
279
280     completions: function(wordRange, bestMatchOnly)
281     {
282         // Pass less characters to _backwardsRange so the range will be a more complete expression.
283         var expression = this._backwardsRange(" =:{;", wordRange.startContainer, wordRange.startOffset, this.promptElement);
284         var expressionString = expression.toString();
285         var lastIndex = expressionString.length - 1;
286
287         var dotNotation = (expressionString[lastIndex] === ".");
288         var bracketNotation = (expressionString[lastIndex] === "[");
289
290         if (dotNotation || bracketNotation)
291             expressionString = expressionString.substr(0, lastIndex);
292
293         var prefix = wordRange.toString();
294         if (!expressionString && !prefix)
295             return;
296
297         var result = InspectorController.inspectedWindow();
298         if (expressionString) {
299             try {
300                 result = this._evalInInspectedWindow(expressionString);
301             } catch(e) {
302                 // Do nothing, the prefix will be considered a window property.
303             }
304         }
305
306         if (bracketNotation) {
307             if (prefix.length && prefix[0] === "'")
308                 var quoteUsed = "'";
309             else
310                 var quoteUsed = "\"";
311         }
312
313         var results = [];
314         var properties = Object.sortedProperties(result);
315         for (var i = 0; i < properties.length; ++i) {
316             var property = properties[i];
317             if (bracketNotation)
318                 property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed + "]";
319             if (property.length < prefix.length)
320                 continue;
321             if (property.indexOf(prefix) !== 0)
322                 continue;
323             results.push(property);
324             if (bestMatchOnly)
325                 break;
326         }
327
328         return results;
329     },
330
331     clearButtonClicked: function()
332     {
333         this.clearMessages();
334     },
335
336     messagesSelectStart: function(event)
337     {
338         if (this._selectionTimeout)
339             clearTimeout(this._selectionTimeout);
340
341         function moveBackIfOutside()
342         {
343             delete this._selectionTimeout;
344             if (this._caretInsidePrompt() || !window.getSelection().isCollapsed)
345                 return;
346             this._moveCaretToEndOfPrompt();
347         }
348
349         this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
350     },
351
352     messagesClicked: function(event)
353     {
354         var link = event.target.firstParentOrSelfWithNodeName("a");
355         if (link && link.representedNode) {
356             WebInspector.updateFocusedNode(link.representedNode);
357             return;
358         }
359
360         var messageElement = event.target.firstParentOrSelfWithClass("console-message");
361         if (!messageElement)
362             return;
363
364         if (!messageElement.message)
365             return;
366
367         var resource = messageElement.message.resource;
368         if (!resource)
369             return;
370
371         if (link && link.hasStyleClass("console-message-url")) {
372             WebInspector.navigateToResource(resource);
373             resource.panel.showSourceLine(item.message.line);
374         }
375
376         event.stopPropagation();
377         event.preventDefault();
378     },
379
380     promptKeyDown: function(event)
381     {
382         switch (event.keyIdentifier) {
383             case "Enter":
384                 this._onEnterPressed(event);
385                 break;
386             case "Up":
387                 this._onUpPressed(event);
388                 break;
389             case "Down":
390                 this._onDownPressed(event);
391                 break;
392             case "U+0009": // Tab
393                 this._onTabPressed(event);
394                 break;
395             case "Right":
396                 if (!this.acceptAutoComplete())
397                     this.autoCompleteSoon();
398                 break;
399             default:
400                 this.clearAutoComplete();
401                 this.autoCompleteSoon();
402                 break;
403         }
404     },
405
406     _backwardsRange: function(stopCharacters, endNode, endOffset, stayWithinElement)
407     {
408         var startNode;
409         var startOffset = 0;
410         var node = endNode;
411
412         while (node) {
413             if (node === stayWithinElement) {
414                 if (!startNode)
415                     startNode = stayWithinElement;
416                 break;
417             }
418
419             if (node.nodeType === Node.TEXT_NODE) {
420                 var start = (node === endNode ? endOffset : node.nodeValue.length);
421                 for (var i = (start - 1); i >= 0; --i) {
422                     var character = node.nodeValue[i];
423                     if (stopCharacters.indexOf(character) !== -1) {
424                         startNode = node;
425                         startOffset = i + 1;
426                         break;
427                     }
428                 }
429             }
430
431             if (startNode)
432                 break;
433
434             node = node.traversePreviousNode();
435         }
436
437         var result = document.createRange();
438         result.setStart(startNode, startOffset);
439         result.setEnd(endNode, endOffset);
440
441         return result;
442     },
443
444     _evalInInspectedWindow: function(expression)
445     {
446         // This with block is needed to work around http://bugs.webkit.org/show_bug.cgi?id=11399
447         with (InspectorController.inspectedWindow()) {
448             return eval(expression);
449         }
450     },
451
452     _caretInsidePrompt: function()
453     {
454         var selection = window.getSelection();
455         if (!selection.rangeCount || !selection.isCollapsed)
456             return false;
457         var selectionRange = selection.getRangeAt(0);
458         return selectionRange.startContainer === this.promptElement && selectionRange.startContainer.isDescendant(this.promptElement);
459     },
460
461     _moveCaretToEndOfPrompt: function()
462     {
463         var selection = window.getSelection();
464         var selectionRange = document.createRange();
465
466         var offset = this.promptElement.firstChild ? 1 : 0;
467         selectionRange.setStart(this.promptElement, offset);
468         selectionRange.setEnd(this.promptElement, offset);
469
470         selection.removeAllRanges();
471         selection.addRange(selectionRange);
472     },
473
474     _onTabPressed: function(event)
475     {
476         event.preventDefault();
477         event.stopPropagation();
478         this.complete();
479     },
480
481     _onEnterPressed: function(event)
482     {
483         event.preventDefault();
484         event.stopPropagation();
485
486         this.clearAutoComplete(true);
487
488         var str = this.promptText;
489         if (!str.length)
490             return;
491
492         this.commandHistory.push(str);
493         this.commandOffset = 0;
494
495         this.promptText = "";
496
497         var result;
498         var exception = false;
499         try {
500             result = this._evalInInspectedWindow(str);
501         } catch(e) {
502             result = e;
503             exception = true;
504         }
505
506         var level = exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log;
507         this.addMessage(new WebInspector.ConsoleCommand(str, result, this._format(result), level));
508     },
509
510     _onUpPressed: function(event)
511     {
512         event.preventDefault();
513         event.stopPropagation();
514
515         if (this.commandOffset == this.commandHistory.length)
516             return;
517
518         if (this.commandOffset == 0)
519             this.tempSavedCommand = this.promptText;
520
521         ++this.commandOffset;
522         this.promptText = this.commandHistory[this.commandHistory.length - this.commandOffset];
523     },
524
525     _onDownPressed: function(event)
526     {
527         event.preventDefault();
528         event.stopPropagation();
529
530         if (this.commandOffset == 0)
531             return;
532
533         --this.commandOffset;
534
535         if (this.commandOffset == 0) {
536             this.promptText = this.tempSavedCommand;
537             delete this.tempSavedCommand;
538             return;
539         }
540
541         this.promptText = this.commandHistory[this.commandHistory.length - this.commandOffset];
542     },
543
544     _format: function(output)
545     {
546         var type = Object.type(output);
547         if (type === "object") {
548             if (output instanceof Node)
549                 type = "node";
550         }
551
552         // We don't perform any special formatting on these types, so we just
553         // pass them through the simple _formatvalue function.
554         var undecoratedTypes = {
555             "undefined": 1,
556             "null": 1,
557             "boolean": 1,
558             "number": 1,
559             "date": 1,
560             "function": 1,
561         };
562
563         var formatter;
564         if (type in undecoratedTypes)
565             formatter = "_formatvalue";
566         else {
567             formatter = "_format" + type;
568             if (!(formatter in this)) {
569                 formatter = "_formatobject";
570                 type = "object";
571             }
572         }
573
574         var span = document.createElement("span");
575         span.addStyleClass("console-formatted-" + type);
576         this[formatter](output, span);
577         return span;
578     },
579
580     _formatvalue: function(val, elem)
581     {
582         elem.appendChild(document.createTextNode(val));
583     },
584
585     _formatstring: function(str, elem)
586     {
587         elem.appendChild(document.createTextNode("\"" + str + "\""));
588     },
589
590     _formatregexp: function(re, elem)
591     {
592         var formatted = String(re).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1);
593         elem.appendChild(document.createTextNode(formatted));
594     },
595
596     _formatarray: function(arr, elem)
597     {
598         elem.appendChild(document.createTextNode("["));
599         for (var i = 0; i < arr.length; ++i) {
600             elem.appendChild(this._format(arr[i]));
601             if (i < arr.length - 1)
602                 elem.appendChild(document.createTextNode(", "));
603         }
604         elem.appendChild(document.createTextNode("]"));
605     },
606
607     _formatnode: function(node, elem)
608     {
609         var anchor = document.createElement("a");
610         anchor.innerHTML = node.titleInfo().title;
611         anchor.representedNode = node;
612         elem.appendChild(anchor);
613     },
614
615     _formatobject: function(obj, elem)
616     {
617         elem.appendChild(document.createTextNode(Object.describe(obj)));
618     },
619 }
620
621 WebInspector.ConsolePanel.prototype.__proto__ = WebInspector.Panel.prototype;
622
623 WebInspector.ConsoleMessage = function(source, level, message, line, url)
624 {
625     this.source = source;
626     this.level = level;
627     this.message = message;
628     this.line = line;
629     this.url = url;
630 }
631
632 WebInspector.ConsoleMessage.prototype = {
633     get shortURL()
634     {
635         if (this.resource)
636             return this.resource.displayName;
637         return this.url;
638     },
639
640     toMessageElement: function()
641     {
642         var element = document.createElement("div");
643         element.message = this;
644         element.className = "console-message";
645
646         switch (this.source) {
647             case WebInspector.ConsoleMessage.MessageSource.HTML:
648                 element.addStyleClass("console-html-source");
649                 break;
650             case WebInspector.ConsoleMessage.MessageSource.XML:
651                 element.addStyleClass("console-xml-source");
652                 break;
653             case WebInspector.ConsoleMessage.MessageSource.JS:
654                 element.addStyleClass("console-js-source");
655                 break;
656             case WebInspector.ConsoleMessage.MessageSource.CSS:
657                 element.addStyleClass("console-css-source");
658                 break;
659             case WebInspector.ConsoleMessage.MessageSource.Other:
660                 element.addStyleClass("console-other-source");
661                 break;
662         }
663
664         switch (this.level) {
665             case WebInspector.ConsoleMessage.MessageLevel.Tip:
666                 element.addStyleClass("console-tip-level");
667                 break;
668             case WebInspector.ConsoleMessage.MessageLevel.Log:
669                 element.addStyleClass("console-log-level");
670                 break;
671             case WebInspector.ConsoleMessage.MessageLevel.Warning:
672                 element.addStyleClass("console-warning-level");
673                 break;
674             case WebInspector.ConsoleMessage.MessageLevel.Error:
675                 element.addStyleClass("console-error-level");
676         }
677
678         var messageTextElement = document.createElement("span");
679         messageTextElement.className = "console-message-text";
680         messageTextElement.textContent = this.message;
681         element.appendChild(messageTextElement);
682
683         element.appendChild(document.createTextNode(" "));
684
685         if (this.url && this.url !== "undefined") {
686             var urlElement = document.createElement("a");
687             urlElement.className = "console-message-url";
688
689             if (this.line > 0)
690                 urlElement.textContent = WebInspector.UIString("%s (line %d)", this.url, this.line);
691             else
692                 urlElement.textContent = this.url;
693
694             element.appendChild(urlElement);
695         }
696
697         return element;
698     },
699
700     toString: function()
701     {
702         var sourceString;
703         switch (this.source) {
704             case WebInspector.ConsoleMessage.MessageSource.HTML:
705                 sourceString = "HTML";
706                 break;
707             case WebInspector.ConsoleMessage.MessageSource.XML:
708                 sourceString = "XML";
709                 break;
710             case WebInspector.ConsoleMessage.MessageSource.JS:
711                 sourceString = "JS";
712                 break;
713             case WebInspector.ConsoleMessage.MessageSource.CSS:
714                 sourceString = "CSS";
715                 break;
716             case WebInspector.ConsoleMessage.MessageSource.Other:
717                 sourceString = "Other";
718                 break;
719         }
720
721         var levelString;
722         switch (this.level) {
723             case WebInspector.ConsoleMessage.MessageLevel.Tip:
724                 levelString = "Tip";
725                 break;
726             case WebInspector.ConsoleMessage.MessageLevel.Log:
727                 levelString = "Log";
728                 break;
729             case WebInspector.ConsoleMessage.MessageLevel.Warning:
730                 levelString = "Warning";
731                 break;
732             case WebInspector.ConsoleMessage.MessageLevel.Error:
733                 levelString = "Error";
734                 break;
735         }
736
737         return sourceString + " " + levelString + ": " + this.message + "\n" + this.url + " line " + this.line;
738     }
739 }
740
741 // Note: Keep these constants in sync with the ones in Chrome.h
742 WebInspector.ConsoleMessage.MessageSource = {
743     HTML: 0,
744     XML: 1,
745     JS: 2,
746     CSS: 3,
747     Other: 4,
748 }
749
750 WebInspector.ConsoleMessage.MessageLevel = {
751     Tip: 0,
752     Log: 1,
753     Warning: 2,
754     Error: 3
755 }
756
757 WebInspector.ConsoleCommand = function(command, result, formattedResultElement, level)
758 {
759     this.command = command;
760     this.formattedResultElement = formattedResultElement;
761     this.level = level;
762 }
763
764 WebInspector.ConsoleCommand.prototype = {
765     toMessageElement: function()
766     {
767         var element = document.createElement("div");
768         element.command = this;
769         element.className = "console-user-command";
770
771         var commandTextElement = document.createElement("span");
772         commandTextElement.className = "console-message-text";
773         commandTextElement.textContent = this.command;
774         element.appendChild(commandTextElement);
775
776         var resultElement = document.createElement("div");
777         resultElement.className = "console-message";
778         element.appendChild(resultElement);
779
780         switch (this.level) {
781             case WebInspector.ConsoleMessage.MessageLevel.Log:
782                 resultElement.addStyleClass("console-log-level");
783                 break;
784             case WebInspector.ConsoleMessage.MessageLevel.Warning:
785                 resultElement.addStyleClass("console-warning-level");
786                 break;
787             case WebInspector.ConsoleMessage.MessageLevel.Error:
788                 resultElement.addStyleClass("console-error-level");
789         }
790
791         var resultTextElement = document.createElement("span");
792         resultTextElement.className = "console-message-text";
793         resultTextElement.appendChild(this.formattedResultElement);
794         resultElement.appendChild(resultTextElement);
795
796         return element;
797     }
798 }