• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
30WebInspector.ConsoleView = function(drawer)
31{
32    WebInspector.View.call(this, document.getElementById("console-view"));
33
34    this.messages = [];
35    this.drawer = drawer;
36
37    this.clearButton = document.getElementById("clear-console-status-bar-item");
38    this.clearButton.title = WebInspector.UIString("Clear console log.");
39    this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false);
40
41    this.messagesElement = document.getElementById("console-messages");
42    this.messagesElement.addEventListener("selectstart", this._messagesSelectStart.bind(this), false);
43    this.messagesElement.addEventListener("click", this._messagesClicked.bind(this), true);
44
45    this.promptElement = document.getElementById("console-prompt");
46    this.promptElement.handleKeyEvent = this._promptKeyDown.bind(this);
47    this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), " .=:[({;");
48
49    this.topGroup = new WebInspector.ConsoleGroup(null, 0);
50    this.messagesElement.insertBefore(this.topGroup.element, this.promptElement);
51    this.groupLevel = 0;
52    this.currentGroup = this.topGroup;
53
54    this.toggleConsoleButton = document.getElementById("console-status-bar-item");
55    this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
56    this.toggleConsoleButton.addEventListener("click", this._toggleConsoleButtonClicked.bind(this), false);
57
58    var anchoredStatusBar = document.getElementById("anchored-status-bar-items");
59    anchoredStatusBar.appendChild(this.toggleConsoleButton);
60
61}
62
63WebInspector.ConsoleView.prototype = {
64    _toggleConsoleButtonClicked: function()
65    {
66        this.drawer.visibleView = this;
67    },
68
69    attach: function(mainElement, statusBarElement)
70    {
71        mainElement.appendChild(this.element);
72        statusBarElement.appendChild(this.clearButton);
73    },
74
75    show: function()
76    {
77        this.toggleConsoleButton.addStyleClass("toggled-on");
78        this.toggleConsoleButton.title = WebInspector.UIString("Hide console.");
79        if (!this.prompt.isCaretInsidePrompt())
80            this.prompt.moveCaretToEndOfPrompt();
81    },
82
83    afterShow: function()
84    {
85        WebInspector.currentFocusElement = this.promptElement;
86    },
87
88    hide: function()
89    {
90        this.toggleConsoleButton.removeStyleClass("toggled-on");
91        this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
92    },
93
94    addMessage: function(msg)
95    {
96        if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) {
97            msg.totalRepeatCount = msg.repeatCount;
98            msg.repeatDelta = msg.repeatCount;
99
100            var messageRepeated = false;
101
102            if (msg.isEqual && msg.isEqual(this.previousMessage)) {
103                // Because sometimes we get a large number of repeated messages and sometimes
104                // we get them one at a time, we need to know the difference between how many
105                // repeats we used to have and how many we have now.
106                msg.repeatDelta -= this.previousMessage.totalRepeatCount;
107
108                if (!isNaN(this.repeatCountBeforeCommand))
109                    msg.repeatCount -= this.repeatCountBeforeCommand;
110
111                if (!this.commandSincePreviousMessage) {
112                    // Recreate the previous message element to reset the repeat count.
113                    var messagesElement = this.currentGroup.messagesElement;
114                    messagesElement.removeChild(messagesElement.lastChild);
115                    messagesElement.appendChild(msg.toMessageElement());
116
117                    messageRepeated = true;
118                }
119            } else
120                delete this.repeatCountBeforeCommand;
121
122            // Increment the error or warning count
123            switch (msg.level) {
124            case WebInspector.ConsoleMessage.MessageLevel.Warning:
125                WebInspector.warnings += msg.repeatDelta;
126                break;
127            case WebInspector.ConsoleMessage.MessageLevel.Error:
128                WebInspector.errors += msg.repeatDelta;
129                break;
130            }
131
132            // Add message to the resource panel
133            if (msg.url in WebInspector.resourceURLMap) {
134                msg.resource = WebInspector.resourceURLMap[msg.url];
135                if (WebInspector.panels.resources)
136                    WebInspector.panels.resources.addMessageToResource(msg.resource, msg);
137            }
138
139            this.commandSincePreviousMessage = false;
140            this.previousMessage = msg;
141
142            if (messageRepeated)
143                return;
144        } else if (msg instanceof WebInspector.ConsoleCommand) {
145            if (this.previousMessage) {
146                this.commandSincePreviousMessage = true;
147                this.repeatCountBeforeCommand = this.previousMessage.totalRepeatCount;
148            }
149        }
150
151        this.messages.push(msg);
152
153        if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
154            if (this.groupLevel < 1)
155                return;
156
157            this.groupLevel--;
158
159            this.currentGroup = this.currentGroup.parentGroup;
160        } else {
161            if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup) {
162                this.groupLevel++;
163
164                var group = new WebInspector.ConsoleGroup(this.currentGroup, this.groupLevel);
165                this.currentGroup.messagesElement.appendChild(group.element);
166                this.currentGroup = group;
167            }
168
169            this.currentGroup.addMessage(msg);
170        }
171
172        this.promptElement.scrollIntoView(false);
173    },
174
175    clearMessages: function(clearInspectorController)
176    {
177        if (clearInspectorController)
178            InspectorController.clearMessages();
179        if (WebInspector.panels.resources)
180            WebInspector.panels.resources.clearMessages();
181
182        this.messages = [];
183
184        this.groupLevel = 0;
185        this.currentGroup = this.topGroup;
186        this.topGroup.messagesElement.removeChildren();
187
188        WebInspector.errors = 0;
189        WebInspector.warnings = 0;
190
191        delete this.commandSincePreviousMessage;
192        delete this.repeatCountBeforeCommand;
193        delete this.previousMessage;
194    },
195
196    completions: function(wordRange, bestMatchOnly, completionsReadyCallback)
197    {
198        // Pass less stop characters to rangeOfWord so the range will be a more complete expression.
199        const expressionStopCharacters = " =:{;";
200        var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, expressionStopCharacters, this.promptElement, "backward");
201        var expressionString = expressionRange.toString();
202        var lastIndex = expressionString.length - 1;
203
204        var dotNotation = (expressionString[lastIndex] === ".");
205        var bracketNotation = (expressionString[lastIndex] === "[");
206
207        if (dotNotation || bracketNotation)
208            expressionString = expressionString.substr(0, lastIndex);
209
210        var prefix = wordRange.toString();
211        if (!expressionString && !prefix)
212            return;
213
214        var reportCompletions = this._reportCompletions.bind(this, bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix);
215        this._evalInInspectedWindow(expressionString, reportCompletions);
216    },
217
218    _reportCompletions: function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result) {
219        if (bracketNotation) {
220            if (prefix.length && prefix[0] === "'")
221                var quoteUsed = "'";
222            else
223                var quoteUsed = "\"";
224        }
225
226        var results = [];
227        var properties = Object.properties(result);
228        if (!dotNotation && !bracketNotation && result._inspectorCommandLineAPI) {
229            var commandLineAPI = Object.properties(result._inspectorCommandLineAPI);
230            for (var i = 0; i < commandLineAPI.length; ++i) {
231                var property = commandLineAPI[i];
232                if (property.charAt(0) !== "_")
233                    properties.push(property);
234            }
235        }
236        properties.sort();
237
238        for (var i = 0; i < properties.length; ++i) {
239            var property = properties[i];
240
241            if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
242                continue;
243
244            if (bracketNotation) {
245                if (!/^[0-9]+$/.test(property))
246                    property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
247                property += "]";
248            }
249
250            if (property.length < prefix.length)
251                continue;
252            if (property.indexOf(prefix) !== 0)
253                continue;
254
255            results.push(property);
256            if (bestMatchOnly)
257                break;
258        }
259        setTimeout(completionsReadyCallback, 0, results);
260    },
261
262    _clearButtonClicked: function()
263    {
264        this.clearMessages(true);
265    },
266
267    _messagesSelectStart: function(event)
268    {
269        if (this._selectionTimeout)
270            clearTimeout(this._selectionTimeout);
271
272        this.prompt.clearAutoComplete();
273
274        function moveBackIfOutside()
275        {
276            delete this._selectionTimeout;
277            if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
278                this.prompt.moveCaretToEndOfPrompt();
279            this.prompt.autoCompleteSoon();
280        }
281
282        this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
283    },
284
285    _messagesClicked: function(event)
286    {
287        var link = event.target.enclosingNodeOrSelfWithNodeName("a");
288        if (!link || !link.representedNode)
289            return;
290
291        WebInspector.updateFocusedNode(link.representedNode);
292        event.stopPropagation();
293        event.preventDefault();
294    },
295
296    _promptKeyDown: function(event)
297    {
298        switch (event.keyIdentifier) {
299            case "Enter":
300                this._enterKeyPressed(event);
301                return;
302        }
303
304        this.prompt.handleKeyEvent(event);
305    },
306
307    _evalInInspectedWindow: function(expression, callback)
308    {
309        if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
310            WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, callback);
311            return;
312        }
313        this.doEvalInWindow(expression, callback);
314    },
315
316    _ensureCommandLineAPIInstalled: function(inspectedWindow)
317    {
318        if (!inspectedWindow._inspectorCommandLineAPI) {
319            inspectedWindow.eval("window._inspectorCommandLineAPI = { \
320                $: function() { return document.getElementById.apply(document, arguments) }, \
321                $$: function() { return document.querySelectorAll.apply(document, arguments) }, \
322                $x: function(xpath, context) { \
323                    var nodes = []; \
324                    try { \
325                        var doc = context || document; \
326                        var results = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); \
327                        var node; \
328                        while (node = results.iterateNext()) nodes.push(node); \
329                    } catch (e) {} \
330                    return nodes; \
331                }, \
332                dir: function() { return console.dir.apply(console, arguments) }, \
333                dirxml: function() { return console.dirxml.apply(console, arguments) }, \
334                keys: function(o) { var a = []; for (var k in o) a.push(k); return a; }, \
335                values: function(o) { var a = []; for (var k in o) a.push(o[k]); return a; }, \
336                profile: function() { return console.profile.apply(console, arguments) }, \
337                profileEnd: function() { return console.profileEnd.apply(console, arguments) }, \
338                _inspectedNodes: [], \
339                get $0() { return _inspectorCommandLineAPI._inspectedNodes[0] }, \
340                get $1() { return _inspectorCommandLineAPI._inspectedNodes[1] }, \
341                get $2() { return _inspectorCommandLineAPI._inspectedNodes[2] }, \
342                get $3() { return _inspectorCommandLineAPI._inspectedNodes[3] }, \
343                get $4() { return _inspectorCommandLineAPI._inspectedNodes[4] } \
344            };");
345
346            inspectedWindow._inspectorCommandLineAPI.clear = InspectorController.wrapCallback(this.clearMessages.bind(this));
347            inspectedWindow._inspectorCommandLineAPI.inspect = InspectorController.wrapCallback(inspectObject.bind(this));
348
349            function inspectObject(o)
350            {
351                if (arguments.length === 0)
352                    return;
353
354                InspectorController.inspectedWindow().console.log(o);
355                if (Object.type(o, InspectorController.inspectedWindow()) === "node") {
356                    WebInspector.showElementsPanel();
357                    WebInspector.panels.elements.treeOutline.revealAndSelectNode(o);
358                } else {
359                    switch (Object.describe(o)) {
360                        case "Database":
361                            WebInspector.showDatabasesPanel();
362                            WebInspector.panels.databases.selectDatabase(o);
363                            break;
364                        case "Storage":
365                            WebInspector.showDatabasesPanel();
366                            WebInspector.panels.databases.selectDOMStorage(o);
367                            break;
368                    }
369                }
370            }
371        }
372    },
373
374    addInspectedNode: function(node)
375    {
376        var inspectedWindow = InspectorController.inspectedWindow();
377        this._ensureCommandLineAPIInstalled(inspectedWindow);
378        var inspectedNodes = inspectedWindow._inspectorCommandLineAPI._inspectedNodes;
379        inspectedNodes.unshift(node);
380        if (inspectedNodes.length >= 5)
381            inspectedNodes.pop();
382    },
383
384    doEvalInWindow: function(expression, callback)
385    {
386        if (!expression) {
387            // There is no expression, so the completion should happen against global properties.
388            expression = "this";
389        }
390
391        // Surround the expression in with statements to inject our command line API so that
392        // the window object properties still take more precedent than our API functions.
393        expression = "with (window._inspectorCommandLineAPI) { with (window) { " + expression + " } }";
394
395        var self = this;
396        function delayedEvaluation()
397        {
398            var inspectedWindow = InspectorController.inspectedWindow();
399            self._ensureCommandLineAPIInstalled(inspectedWindow);
400            try {
401                callback(inspectedWindow.eval(expression));
402            } catch (e) {
403                callback(e, true);
404            }
405        }
406        setTimeout(delayedEvaluation, 0);
407    },
408
409    _enterKeyPressed: function(event)
410    {
411        if (event.altKey)
412            return;
413
414        event.preventDefault();
415        event.stopPropagation();
416
417        this.prompt.clearAutoComplete(true);
418
419        var str = this.prompt.text;
420        if (!str.length)
421            return;
422
423        var commandMessage = new WebInspector.ConsoleCommand(str);
424        this.addMessage(commandMessage);
425
426        var self = this;
427        function printResult(result, exception)
428        {
429            self.prompt.history.push(str);
430            self.prompt.historyOffset = 0;
431            self.prompt.text = "";
432            self.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage));
433        }
434        this._evalInInspectedWindow(str, printResult);
435    },
436
437    _format: function(output, forceObjectFormat)
438    {
439        var inspectedWindow = InspectorController.inspectedWindow();
440        if (forceObjectFormat)
441            var type = "object";
442        else if (output instanceof inspectedWindow.NodeList)
443            var type = "array";
444        else
445            var type = Object.type(output, inspectedWindow);
446
447        // We don't perform any special formatting on these types, so we just
448        // pass them through the simple _formatvalue function.
449        var undecoratedTypes = {
450            "undefined": 1,
451            "null": 1,
452            "boolean": 1,
453            "number": 1,
454            "date": 1,
455            "function": 1,
456        };
457
458        var formatter;
459        if (forceObjectFormat)
460            formatter = "_formatobject";
461        else if (type in undecoratedTypes)
462            formatter = "_formatvalue";
463        else {
464            formatter = "_format" + type;
465            if (!(formatter in this)) {
466                formatter = "_formatobject";
467                type = "object";
468            }
469        }
470
471        var span = document.createElement("span");
472        span.addStyleClass("console-formatted-" + type);
473        this[formatter](output, span);
474        return span;
475    },
476
477    _formatvalue: function(val, elem)
478    {
479        elem.appendChild(document.createTextNode(val));
480    },
481
482    _formatstring: function(str, elem)
483    {
484        elem.appendChild(document.createTextNode("\"" + str + "\""));
485    },
486
487    _formatregexp: function(re, elem)
488    {
489        var formatted = String(re).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1);
490        elem.appendChild(document.createTextNode(formatted));
491    },
492
493    _formatarray: function(arr, elem)
494    {
495        elem.appendChild(document.createTextNode("["));
496        for (var i = 0; i < arr.length; ++i) {
497            elem.appendChild(this._format(arr[i]));
498            if (i < arr.length - 1)
499                elem.appendChild(document.createTextNode(", "));
500        }
501        elem.appendChild(document.createTextNode("]"));
502    },
503
504    _formatnode: function(node, elem)
505    {
506        var treeOutline = new WebInspector.ElementsTreeOutline();
507        treeOutline.rootDOMNode = node;
508        treeOutline.element.addStyleClass("outline-disclosure");
509        if (!treeOutline.children[0].hasChildren)
510            treeOutline.element.addStyleClass("single-node");
511        elem.appendChild(treeOutline.element);
512    },
513
514    _formatobject: function(obj, elem)
515    {
516        elem.appendChild(new WebInspector.ObjectPropertiesSection(new WebInspector.ObjectProxy(obj), Object.describe(obj, true), null, null, true).element);
517    },
518
519    _formaterror: function(obj, elem)
520    {
521        var messageElement = document.createElement("span");
522        messageElement.className = "error-message";
523        messageElement.textContent = obj.name + ": " + obj.message;
524        elem.appendChild(messageElement);
525
526        if (obj.sourceURL) {
527            var urlElement = document.createElement("a");
528            urlElement.className = "webkit-html-resource-link";
529            urlElement.href = obj.sourceURL;
530            urlElement.lineNumber = obj.line;
531            urlElement.preferredPanel = "scripts";
532
533            if (obj.line > 0)
534                urlElement.textContent = WebInspector.displayNameForURL(obj.sourceURL) + ":" + obj.line;
535            else
536                urlElement.textContent = WebInspector.displayNameForURL(obj.sourceURL);
537
538            elem.appendChild(document.createTextNode(" ("));
539            elem.appendChild(urlElement);
540            elem.appendChild(document.createTextNode(")"));
541        }
542    }
543}
544
545WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype;
546
547WebInspector.ConsoleMessage = function(source, type, level, line, url, groupLevel, repeatCount)
548{
549    this.source = source;
550    this.type = type;
551    this.level = level;
552    this.line = line;
553    this.url = url;
554    this.groupLevel = groupLevel;
555    this.repeatCount = repeatCount;
556    if (arguments.length > 7)
557        this.setMessageBody(Array.prototype.slice.call(arguments, 7));
558}
559
560WebInspector.ConsoleMessage.prototype = {
561    setMessageBody: function(args)
562    {
563        switch (this.type) {
564            case WebInspector.ConsoleMessage.MessageType.Trace:
565                var span = document.createElement("span");
566                span.addStyleClass("console-formatted-trace");
567                var stack = Array.prototype.slice.call(args);
568                var funcNames = stack.map(function(f) {
569                    return f || WebInspector.UIString("(anonymous function)");
570                });
571                span.appendChild(document.createTextNode(funcNames.join("\n")));
572                this.formattedMessage = span;
573                break;
574            case WebInspector.ConsoleMessage.MessageType.Object:
575                this.formattedMessage = this._format(["%O", args[0]]);
576                break;
577            default:
578                this.formattedMessage = this._format(args);
579                break;
580        }
581
582        // This is used for inline message bubbles in SourceFrames, or other plain-text representations.
583        this.message = this.formattedMessage.textContent;
584    },
585
586    isErrorOrWarning: function()
587    {
588        return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error);
589    },
590
591    _format: function(parameters)
592    {
593        var formattedResult = document.createElement("span");
594
595        if (!parameters.length)
596            return formattedResult;
597
598        function formatForConsole(obj)
599        {
600            return WebInspector.console._format(obj);
601        }
602
603        function formatAsObjectForConsole(obj)
604        {
605            return WebInspector.console._format(obj, true);
606        }
607
608        if (Object.type(parameters[0], InspectorController.inspectedWindow()) === "string") {
609            var formatters = {}
610            for (var i in String.standardFormatters)
611                formatters[i] = String.standardFormatters[i];
612
613            // Firebug uses %o for formatting objects.
614            formatters.o = formatForConsole;
615            // Firebug allows both %i and %d for formatting integers.
616            formatters.i = formatters.d;
617            // Support %O to force object formating, instead of the type-based %o formatting.
618            formatters.O = formatAsObjectForConsole;
619
620            function append(a, b)
621            {
622                if (!(b instanceof Node))
623                    a.appendChild(WebInspector.linkifyStringAsFragment(b.toString()));
624                else
625                    a.appendChild(b);
626                return a;
627            }
628
629            var result = String.format(parameters[0], parameters.slice(1), formatters, formattedResult, append);
630            formattedResult = result.formattedResult;
631            parameters = result.unusedSubstitutions;
632            if (parameters.length)
633                formattedResult.appendChild(document.createTextNode(" "));
634        }
635
636        for (var i = 0; i < parameters.length; ++i) {
637            if (typeof parameters[i] === "string")
638                formattedResult.appendChild(WebInspector.linkifyStringAsFragment(parameters[i]));
639            else
640                formattedResult.appendChild(formatForConsole(parameters[i]));
641
642            if (i < parameters.length - 1)
643                formattedResult.appendChild(document.createTextNode(" "));
644        }
645
646        return formattedResult;
647    },
648
649    toMessageElement: function()
650    {
651        if (this.propertiesSection)
652            return this.propertiesSection.element;
653
654        var element = document.createElement("div");
655        element.message = this;
656        element.className = "console-message";
657
658        switch (this.source) {
659            case WebInspector.ConsoleMessage.MessageSource.HTML:
660                element.addStyleClass("console-html-source");
661                break;
662            case WebInspector.ConsoleMessage.MessageSource.WML:
663                element.addStyleClass("console-wml-source");
664                break;
665            case WebInspector.ConsoleMessage.MessageSource.XML:
666                element.addStyleClass("console-xml-source");
667                break;
668            case WebInspector.ConsoleMessage.MessageSource.JS:
669                element.addStyleClass("console-js-source");
670                break;
671            case WebInspector.ConsoleMessage.MessageSource.CSS:
672                element.addStyleClass("console-css-source");
673                break;
674            case WebInspector.ConsoleMessage.MessageSource.Other:
675                element.addStyleClass("console-other-source");
676                break;
677        }
678
679        switch (this.level) {
680            case WebInspector.ConsoleMessage.MessageLevel.Tip:
681                element.addStyleClass("console-tip-level");
682                break;
683            case WebInspector.ConsoleMessage.MessageLevel.Log:
684                element.addStyleClass("console-log-level");
685                break;
686            case WebInspector.ConsoleMessage.MessageLevel.Warning:
687                element.addStyleClass("console-warning-level");
688                break;
689            case WebInspector.ConsoleMessage.MessageLevel.Error:
690                element.addStyleClass("console-error-level");
691                break;
692        }
693
694        if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup) {
695            element.addStyleClass("console-group-title");
696        }
697
698        if (this.elementsTreeOutline) {
699            element.addStyleClass("outline-disclosure");
700            element.appendChild(this.elementsTreeOutline.element);
701            return element;
702        }
703
704        if (this.repeatCount > 1) {
705            var messageRepeatCountElement = document.createElement("span");
706            messageRepeatCountElement.className = "bubble";
707            messageRepeatCountElement.textContent = this.repeatCount;
708
709            element.appendChild(messageRepeatCountElement);
710            element.addStyleClass("repeated-message");
711        }
712
713        if (this.url && this.url !== "undefined") {
714            var urlElement = document.createElement("a");
715            urlElement.className = "console-message-url webkit-html-resource-link";
716            urlElement.href = this.url;
717            urlElement.lineNumber = this.line;
718
719            if (this.source === WebInspector.ConsoleMessage.MessageSource.JS)
720                urlElement.preferredPanel = "scripts";
721
722            if (this.line > 0)
723                urlElement.textContent = WebInspector.displayNameForURL(this.url) + ":" + this.line;
724            else
725                urlElement.textContent = WebInspector.displayNameForURL(this.url);
726
727            element.appendChild(urlElement);
728        }
729
730        var messageTextElement = document.createElement("span");
731        messageTextElement.className = "console-message-text";
732        messageTextElement.appendChild(this.formattedMessage);
733        element.appendChild(messageTextElement);
734
735        return element;
736    },
737
738    toString: function()
739    {
740        var sourceString;
741        switch (this.source) {
742            case WebInspector.ConsoleMessage.MessageSource.HTML:
743                sourceString = "HTML";
744                break;
745            case WebInspector.ConsoleMessage.MessageSource.WML:
746                sourceString = "WML";
747                break;
748            case WebInspector.ConsoleMessage.MessageSource.XML:
749                sourceString = "XML";
750                break;
751            case WebInspector.ConsoleMessage.MessageSource.JS:
752                sourceString = "JS";
753                break;
754            case WebInspector.ConsoleMessage.MessageSource.CSS:
755                sourceString = "CSS";
756                break;
757            case WebInspector.ConsoleMessage.MessageSource.Other:
758                sourceString = "Other";
759                break;
760        }
761
762        var typeString;
763        switch (this.type) {
764            case WebInspector.ConsoleMessage.MessageType.Log:
765                typeString = "Log";
766                break;
767            case WebInspector.ConsoleMessage.MessageType.Object:
768                typeString = "Object";
769                break;
770            case WebInspector.ConsoleMessage.MessageType.Trace:
771                typeString = "Trace";
772                break;
773            case WebInspector.ConsoleMessage.MessageType.StartGroup:
774                typeString = "Start Group";
775                break;
776            case WebInspector.ConsoleMessage.MessageType.EndGroup:
777                typeString = "End Group";
778                break;
779        }
780
781        var levelString;
782        switch (this.level) {
783            case WebInspector.ConsoleMessage.MessageLevel.Tip:
784                levelString = "Tip";
785                break;
786            case WebInspector.ConsoleMessage.MessageLevel.Log:
787                levelString = "Log";
788                break;
789            case WebInspector.ConsoleMessage.MessageLevel.Warning:
790                levelString = "Warning";
791                break;
792            case WebInspector.ConsoleMessage.MessageLevel.Error:
793                levelString = "Error";
794                break;
795        }
796
797        return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line;
798    },
799
800    isEqual: function(msg, disreguardGroup)
801    {
802        if (!msg)
803            return false;
804
805        var ret = (this.source == msg.source)
806            && (this.type == msg.type)
807            && (this.level == msg.level)
808            && (this.line == msg.line)
809            && (this.url == msg.url)
810            && (this.message == msg.message);
811
812        return (disreguardGroup ? ret : (ret && (this.groupLevel == msg.groupLevel)));
813    }
814}
815
816// Note: Keep these constants in sync with the ones in Console.h
817WebInspector.ConsoleMessage.MessageSource = {
818    HTML: 0,
819    WML: 1,
820    XML: 2,
821    JS: 3,
822    CSS: 4,
823    Other: 5
824}
825
826WebInspector.ConsoleMessage.MessageType = {
827    Log: 0,
828    Object: 1,
829    Trace: 2,
830    StartGroup: 3,
831    EndGroup: 4
832}
833
834WebInspector.ConsoleMessage.MessageLevel = {
835    Tip: 0,
836    Log: 1,
837    Warning: 2,
838    Error: 3
839}
840
841WebInspector.ConsoleCommand = function(command)
842{
843    this.command = command;
844}
845
846WebInspector.ConsoleCommand.prototype = {
847    toMessageElement: function()
848    {
849        var element = document.createElement("div");
850        element.command = this;
851        element.className = "console-user-command";
852
853        var commandTextElement = document.createElement("span");
854        commandTextElement.className = "console-message-text";
855        commandTextElement.textContent = this.command;
856        element.appendChild(commandTextElement);
857
858        return element;
859    }
860}
861
862WebInspector.ConsoleCommandResult = function(result, exception, originatingCommand)
863{
864    var level = (exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
865    var message = (exception ? String(result) : result);
866    var line = (exception ? result.line : -1);
867    var url = (exception ? result.sourceURL : null);
868
869    WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, line, url, null, 1, message);
870
871    this.originatingCommand = originatingCommand;
872}
873
874WebInspector.ConsoleCommandResult.prototype = {
875    toMessageElement: function()
876    {
877        var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this);
878        element.addStyleClass("console-user-command-result");
879        return element;
880    }
881}
882
883WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;
884
885WebInspector.ConsoleGroup = function(parentGroup, level)
886{
887    this.parentGroup = parentGroup;
888    this.level = level;
889
890    var element = document.createElement("div");
891    element.className = "console-group";
892    element.group = this;
893    this.element = element;
894
895    var messagesElement = document.createElement("div");
896    messagesElement.className = "console-group-messages";
897    element.appendChild(messagesElement);
898    this.messagesElement = messagesElement;
899}
900
901WebInspector.ConsoleGroup.prototype = {
902    addMessage: function(msg)
903    {
904        var element = msg.toMessageElement();
905
906        if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup) {
907            this.messagesElement.parentNode.insertBefore(element, this.messagesElement);
908            element.addEventListener("click", this._titleClicked.bind(this), true);
909        } else
910            this.messagesElement.appendChild(element);
911
912        if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand)
913            element.previousSibling.addStyleClass("console-adjacent-user-command-result");
914    },
915
916    _titleClicked: function(event)
917    {
918        var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title");
919        if (groupTitleElement) {
920            var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group");
921            if (groupElement)
922                if (groupElement.hasStyleClass("collapsed"))
923                    groupElement.removeStyleClass("collapsed");
924                else
925                    groupElement.addStyleClass("collapsed");
926            groupTitleElement.scrollIntoViewIfNeeded(true);
927        }
928
929        event.stopPropagation();
930        event.preventDefault();
931    }
932}
933