OSDN Git Service

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