2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2009 Joseph Pecoraro
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
30 const ExpressionStopCharacters = " =:[({;,!+-*/&|^<>";
32 WebInspector.ConsoleView = function(drawer)
34 WebInspector.View.call(this, document.getElementById("console-view"));
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);
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);
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;
53 this.topGroup = new WebInspector.ConsoleGroup(null);
54 this.messagesElement.insertBefore(this.topGroup.element, this.promptElement);
55 this.currentGroup = this.topGroup;
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);
61 // Will hold the list of filter elements
62 this.filterBarElement = document.getElementById("console-filter");
64 function createDividerElement() {
65 var dividerElement = document.createElement("div");
66 dividerElement.addStyleClass("scope-bar-divider");
67 this.filterBarElement.appendChild(dividerElement);
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;
78 this.filterBarElement.appendChild(categoryElement);
80 return categoryElement;
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"));
89 this.filter(this.allElement, false);
90 this._registerShortcuts();
92 this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
94 this._customFormatters = {
95 "object": this._formatobject,
96 "array": this._formatarray,
97 "node": this._formatnode,
98 "string": this._formatstring
101 this._registerConsoleDomainDispatcher();
104 WebInspector.ConsoleView.prototype = {
105 _registerConsoleDomainDispatcher: function() {
108 addConsoleMessage: function(payload)
110 var consoleMessage = new WebInspector.ConsoleMessage(
121 console.addMessage(consoleMessage);
124 updateConsoleMessageExpiredCount: function(count)
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));
130 updateConsoleMessageRepeatCount: function(count)
132 var msg = console.previousMessage;
133 var prevRepeatCount = msg.totalRepeatCount;
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);
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);
149 consoleMessagesCleared: function()
151 console.clearMessages();
154 InspectorBackend.registerDomainDispatcher("Console", dispatcher);
157 _updateFilter: function(e)
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;
166 this.filter(e.target, selectMultiple);
169 filter: function(target, selectMultiple)
171 function unselectAll()
173 this.allElement.removeStyleClass("selected");
174 this.errorElement.removeStyleClass("selected");
175 this.warningElement.removeStyleClass("selected");
176 this.logElement.removeStyleClass("selected");
178 this.messagesElement.removeStyleClass("filter-all");
179 this.messagesElement.removeStyleClass("filter-errors");
180 this.messagesElement.removeStyleClass("filter-warnings");
181 this.messagesElement.removeStyleClass("filter-logs");
184 var targetFilterClass = "filter-" + target.category;
186 if (target.category === "all") {
187 if (target.hasStyleClass("selected")) {
188 // We can't unselect all, so we break early here
192 unselectAll.call(this);
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");
201 if (!selectMultiple) {
202 // If multiple selection is off, we want to unselect everything else
203 // and just select ourselves.
204 unselectAll.call(this);
206 target.addStyleClass("selected");
207 this.messagesElement.addStyleClass(targetFilterClass);
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);
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);
225 _toggleConsoleButtonClicked: function()
227 this.drawer.visibleView = this;
230 attach: function(mainElement, statusBarElement)
232 mainElement.appendChild(this.element);
233 statusBarElement.appendChild(this.clearButton);
234 statusBarElement.appendChild(this.filterBarElement);
239 this.toggleConsoleButton.addStyleClass("toggled-on");
240 this.toggleConsoleButton.title = WebInspector.UIString("Hide console.");
241 if (!this.prompt.isCaretInsidePrompt())
242 this.prompt.moveCaretToEndOfPrompt();
245 afterShow: function()
247 WebInspector.currentFocusElement = this.promptElement;
252 this.toggleConsoleButton.removeStyleClass("toggled-on");
253 this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
256 _scheduleScrollIntoView: function()
258 if (this._scrollIntoViewTimer)
261 function scrollIntoView()
263 this.promptElement.scrollIntoView(true);
264 delete this._scrollIntoViewTimer;
266 this._scrollIntoViewTimer = setTimeout(scrollIntoView.bind(this), 20);
269 addMessage: function(msg)
271 var shouldScrollToLastMessage = this.messagesElement.isScrolledToBottom();
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;
285 this.messages.push(msg);
287 if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
288 var parentGroup = this.currentGroup.parentGroup
290 this.currentGroup = parentGroup;
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;
298 this.currentGroup.addMessage(msg);
301 if (shouldScrollToLastMessage)
302 this._scheduleScrollIntoView();
305 _incrementErrorWarningCount: function(msg)
308 case WebInspector.ConsoleMessage.MessageLevel.Warning:
309 WebInspector.warnings += msg.repeatDelta;
311 case WebInspector.ConsoleMessage.MessageLevel.Error:
312 WebInspector.errors += msg.repeatDelta;
317 requestClearMessages: function()
319 InspectorBackend.clearConsoleMessages();
322 clearMessages: function()
324 WebInspector.resourceTreeModel.clearConsoleMessages();
325 WebInspector.panels.scripts.clearConsoleMessages();
329 this.currentGroup = this.topGroup;
330 this.topGroup.messagesElement.removeChildren();
332 WebInspector.errors = 0;
333 WebInspector.warnings = 0;
335 delete this.commandSincePreviousMessage;
336 delete this.previousMessage;
339 completions: function(wordRange, bestMatchOnly, completionsReadyCallback)
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;
346 var dotNotation = (expressionString[lastIndex] === ".");
347 var bracketNotation = (expressionString[lastIndex] === "[");
349 if (dotNotation || bracketNotation)
350 expressionString = expressionString.substr(0, lastIndex);
352 var prefix = wordRange.toString();
353 if (!expressionString && !prefix)
356 var reportCompletions = this._reportCompletions.bind(this, bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix);
357 // Collect comma separated object properties for the completion.
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);
364 InspectorBackend.getCompletions(expressionString, includeCommandLineAPI, reportCompletions);
367 _reportCompletions: function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) {
371 if (bracketNotation) {
372 if (prefix.length && prefix[0] === "'")
375 var quoteUsed = "\"";
379 var properties = Object.keys(result).sort();
381 for (var i = 0; i < properties.length; ++i) {
382 var property = properties[i];
384 if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
387 if (bracketNotation) {
388 if (!/^[0-9]+$/.test(property))
389 property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
393 if (property.length < prefix.length)
395 if (property.indexOf(prefix) !== 0)
398 results.push(property);
402 completionsReadyCallback(results);
405 _clearButtonClicked: function()
407 this.requestClearMessages();
410 _handleContextMenuEvent: function(event)
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.
418 var itemAction = function () {
419 WebInspector.settings.monitoringXHREnabled = !WebInspector.settings.monitoringXHREnabled;
420 InspectorBackend.setMonitoringXHREnabled(WebInspector.settings.monitoringXHREnabled);
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);
428 _messagesSelectStart: function(event)
430 if (this._selectionTimeout)
431 clearTimeout(this._selectionTimeout);
433 this.prompt.clearAutoComplete();
435 function moveBackIfOutside()
437 delete this._selectionTimeout;
438 if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
439 this.prompt.moveCaretToEndOfPrompt();
440 this.prompt.autoCompleteSoon();
443 this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
446 _messagesClicked: function(event)
448 var link = event.target.enclosingNodeOrSelfWithNodeName("a");
449 if (!link || !link.representedNode)
452 WebInspector.updateFocusedNode(link.representedNode.id);
453 event.stopPropagation();
454 event.preventDefault();
457 _registerShortcuts: function()
459 this._shortcuts = {};
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;
467 var clearConsoleHandler = this.requestClearMessages.bind(this);
468 var shortcutL = shortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl);
469 this._shortcuts[shortcutL.key] = clearConsoleHandler;
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"));
476 shortcut.shortcutToString(shortcut.Keys.Tab),
477 shortcut.shortcutToString(shortcut.Keys.Tab, shortcut.Modifiers.Shift)
479 section.addRelatedKeys(keys, WebInspector.UIString("Next/previous suggestion"));
480 section.addKey(shortcut.shortcutToString(shortcut.Keys.Right), WebInspector.UIString("Accept suggestion"));
482 shortcut.shortcutToString(shortcut.Keys.Down),
483 shortcut.shortcutToString(shortcut.Keys.Up)
485 section.addRelatedKeys(keys, WebInspector.UIString("Next/previous line"));
487 shortcut.shortcutToString("N", shortcut.Modifiers.Alt),
488 shortcut.shortcutToString("P", shortcut.Modifiers.Alt)
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"));
495 _promptKeyDown: function(event)
497 if (isEnterKey(event)) {
498 this._enterKeyPressed(event);
502 var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
503 var handler = this._shortcuts[shortcut];
505 if (!this._shortcuts[shortcut].isMacOnly || WebInspector.isMac()) {
507 event.preventDefault();
513 evalInInspectedWindow: function(expression, objectGroup, includeCommandLineAPI, callback)
515 if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
516 WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, objectGroup, includeCommandLineAPI, callback);
521 // There is no expression, so the completion should happen against global properties.
525 function evalCallback(result)
527 callback(WebInspector.RemoteObject.fromPayload(result));
529 InspectorBackend.evaluate(expression, objectGroup, includeCommandLineAPI, evalCallback);
532 _enterKeyPressed: function(event)
534 if (event.altKey || event.ctrlKey || event.shiftKey)
537 event.preventDefault();
538 event.stopPropagation();
540 this.prompt.clearAutoComplete(true);
542 var str = this.prompt.text;
546 var commandMessage = new WebInspector.ConsoleCommand(str);
547 this.addMessage(commandMessage);
550 function printResult(result)
552 self.prompt.history.push(str);
553 self.prompt.historyOffset = 0;
554 self.prompt.text = "";
556 WebInspector.settings.consoleHistory = self.prompt.history.slice(-30);
558 self.addMessage(new WebInspector.ConsoleCommandResult(result, commandMessage));
560 this.evalInInspectedWindow(str, "console", true, printResult);
563 _format: function(output, forceObjectFormat)
565 var isProxy = (output != null && typeof output === "object");
566 var type = (forceObjectFormat ? "object" : WebInspector.RemoteObject.type(output));
568 var formatter = this._customFormatters[type];
569 if (!formatter || !isProxy) {
570 formatter = this._formatvalue;
571 output = output.description;
574 var span = document.createElement("span");
575 span.className = "console-formatted-" + type + " source-code";
576 formatter.call(this, output, span);
580 _formatvalue: function(val, elem)
582 elem.appendChild(document.createTextNode(val));
585 _formatobject: function(obj, elem)
587 elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element);
590 _formatnode: function(object, elem)
592 function printNode(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);
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);
608 object.pushNodeToFrontend(printNode.bind(this));
611 _formatarray: function(arr, elem)
613 arr.getOwnProperties(false, this._printArray.bind(this, elem));
616 _formatstring: function(output, elem)
618 var span = document.createElement("span");
619 span.className = "console-formatted-string source-code";
620 span.appendChild(WebInspector.linkifyStringAsFragment(output.description));
622 // Make black quotes.
623 elem.removeStyleClass("console-formatted-string");
624 elem.appendChild(document.createTextNode("\""));
625 elem.appendChild(span);
626 elem.appendChild(document.createTextNode("\""));
629 _printArray: function(elem, properties)
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);
641 elem.appendChild(document.createTextNode("["));
642 for (var i = 0; i < elements.length; ++i) {
643 var element = elements[i];
645 elem.appendChild(element);
647 elem.appendChild(document.createTextNode("undefined"))
648 if (i < elements.length - 1)
649 elem.appendChild(document.createTextNode(", "));
651 elem.appendChild(document.createTextNode("]"));
654 _formatAsArrayEntry: function(output)
656 // Prevent infinite expansion of cross-referencing arrays.
657 return this._format(output, WebInspector.RemoteObject.type(output) === "array");
661 WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype;
663 WebInspector.ConsoleMessage = function(source, type, level, line, url, repeatCount, message, parameters, stackTrace, requestId)
665 this.source = source;
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;
678 if (stackTrace && stackTrace.length) {
679 var topCallFrame = stackTrace[0];
681 this.url = topCallFrame.scriptName;
683 this.line = topCallFrame.lineNumber;
686 this._formatMessage();
689 WebInspector.ConsoleMessage.createTextMessage = function(text, level)
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);
695 WebInspector.ConsoleMessage.prototype = {
696 _formatMessage: function()
698 var stackTrace = this._stackTrace;
701 case WebInspector.ConsoleMessage.MessageType.Trace:
702 messageText = document.createTextNode("console.trace()");
704 case WebInspector.ConsoleMessage.MessageType.UncaughtException:
705 messageText = document.createTextNode(this._messageText);
707 case WebInspector.ConsoleMessage.MessageType.NetworkError:
708 var resource = this._requestId && WebInspector.networkResourceById(this._requestId);
710 stackTrace = resource.stackTrace;
712 messageText = document.createElement("span");
713 messageText.appendChild(document.createTextNode(resource.requestMethod + " "));
714 messageText.appendChild(WebInspector.linkifyURLAsNode(resource.url));
716 messageText.appendChild(document.createTextNode(" " + resource.localizedFailDescription));
718 messageText.appendChild(document.createTextNode(" " + resource.statusCode + " (" + resource.statusText + ")"));
720 messageText = this._format([this._messageText]);
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);
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);
734 var args = this._parameters || [this._messageText];
735 messageText = this._format(args);
739 this._formattedMessage = document.createElement("span");
740 this._formattedMessage.className = "console-message-text source-code";
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);
747 this._formattedMessage.appendChild(messageText);
749 if (this._stackTrace) {
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);
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)
766 this._populateStackTraceTreeElement(root);
767 this._formattedMessage = ol;
772 // This is used for inline message bubbles in SourceFrames, or other plain-text representations.
773 this.message = this._formattedMessage.textContent;
776 isErrorOrWarning: function()
778 return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error);
781 _format: function(parameters)
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;
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]);
794 parameters[i] = WebInspector.RemoteObject.fromPrimitiveValue(parameters[i]);
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;
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(" "));
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));
815 formattedResult.appendChild(WebInspector.console._format(parameters[i]));
816 if (i < parameters.length - 1)
817 formattedResult.appendChild(document.createTextNode(" "));
819 return formattedResult;
822 _formatWithSubstitutionString: function(parameters, formattedResult)
825 for (var i in String.standardFormatters)
826 formatters[i] = String.standardFormatters[i];
828 function consoleFormatWrapper(force)
830 return function(obj) {
831 return WebInspector.console._format(obj, force);
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);
842 function append(a, b)
844 if (!(b instanceof Node))
845 a.appendChild(WebInspector.linkifyStringAsFragment(b.toString()));
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);
855 toMessageElement: function()
858 return this._element;
860 var element = document.createElement("div");
861 element.message = this;
862 element.className = "console-message";
864 this._element = element;
866 switch (this.level) {
867 case WebInspector.ConsoleMessage.MessageLevel.Tip:
868 element.addStyleClass("console-tip-level");
870 case WebInspector.ConsoleMessage.MessageLevel.Log:
871 element.addStyleClass("console-log-level");
873 case WebInspector.ConsoleMessage.MessageLevel.Debug:
874 element.addStyleClass("console-debug-level");
876 case WebInspector.ConsoleMessage.MessageLevel.Warning:
877 element.addStyleClass("console-warning-level");
879 case WebInspector.ConsoleMessage.MessageLevel.Error:
880 element.addStyleClass("console-error-level");
884 if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
885 element.addStyleClass("console-group-title");
887 if (this.elementsTreeOutline) {
888 element.addStyleClass("outline-disclosure");
889 element.appendChild(this.elementsTreeOutline.element);
893 element.appendChild(this._formattedMessage);
895 if (this.repeatCount > 1)
896 this._updateRepeatCount();
901 _populateStackTraceTreeElement: function(parentTreeElement)
903 for (var i = 0; i < this._stackTrace.length; i++) {
904 var frame = this._stackTrace[i];
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);
913 var urlElement = WebInspector.linkifyResourceAsNode(frame.scriptName, "scripts", frame.lineNumber, "console-message-url");
914 content.appendChild(urlElement);
916 var treeElement = new TreeElement(content);
917 parentTreeElement.appendChild(treeElement);
921 _updateRepeatCount: function() {
922 if (!this.repeatCountElement) {
923 this.repeatCountElement = document.createElement("span");
924 this.repeatCountElement.className = "bubble";
926 this._element.insertBefore(this.repeatCountElement, this._element.firstChild);
927 this._element.addStyleClass("repeated-message");
929 this.repeatCountElement.textContent = this.repeatCount;
935 switch (this.source) {
936 case WebInspector.ConsoleMessage.MessageSource.HTML:
937 sourceString = "HTML";
939 case WebInspector.ConsoleMessage.MessageSource.WML:
940 sourceString = "WML";
942 case WebInspector.ConsoleMessage.MessageSource.XML:
943 sourceString = "XML";
945 case WebInspector.ConsoleMessage.MessageSource.JS:
948 case WebInspector.ConsoleMessage.MessageSource.CSS:
949 sourceString = "CSS";
951 case WebInspector.ConsoleMessage.MessageSource.Other:
952 sourceString = "Other";
958 case WebInspector.ConsoleMessage.MessageType.Log:
959 case WebInspector.ConsoleMessage.MessageType.UncaughtException:
960 case WebInspector.ConsoleMessage.MessageType.NetworkError:
963 case WebInspector.ConsoleMessage.MessageType.Object:
964 typeString = "Object";
966 case WebInspector.ConsoleMessage.MessageType.Trace:
967 typeString = "Trace";
969 case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
970 case WebInspector.ConsoleMessage.MessageType.StartGroup:
971 typeString = "Start Group";
973 case WebInspector.ConsoleMessage.MessageType.EndGroup:
974 typeString = "End Group";
976 case WebInspector.ConsoleMessage.MessageType.Assert:
977 typeString = "Assert";
979 case WebInspector.ConsoleMessage.MessageType.Result:
980 typeString = "Result";
985 switch (this.level) {
986 case WebInspector.ConsoleMessage.MessageLevel.Tip:
989 case WebInspector.ConsoleMessage.MessageLevel.Log:
992 case WebInspector.ConsoleMessage.MessageLevel.Warning:
993 levelString = "Warning";
995 case WebInspector.ConsoleMessage.MessageLevel.Debug:
996 levelString = "Debug";
998 case WebInspector.ConsoleMessage.MessageLevel.Error:
999 levelString = "Error";
1003 return sourceString + " " + typeString + " " + levelString + ": " + this._formattedMessage.textContent + "\n" + this.url + " line " + this.line;
1006 isEqual: function(msg)
1011 if (this._stackTrace) {
1012 if (!msg._stackTrace)
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)
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);
1035 // Note: Keep these constants in sync with the ones in Console.h
1036 WebInspector.ConsoleMessage.MessageSource = {
1045 WebInspector.ConsoleMessage.MessageType = {
1050 StartGroupCollapsed: 4,
1053 UncaughtException: 7,
1058 WebInspector.ConsoleMessage.MessageLevel = {
1066 WebInspector.ConsoleCommand = function(command)
1068 this.command = command;
1071 WebInspector.ConsoleCommand.prototype = {
1072 toMessageElement: function()
1074 var element = document.createElement("div");
1075 element.command = this;
1076 element.className = "console-user-command";
1078 var commandTextElement = document.createElement("span");
1079 commandTextElement.className = "console-message-text source-code";
1080 commandTextElement.textContent = this.command;
1081 element.appendChild(commandTextElement);
1087 WebInspector.ConsoleCommandResult = function(result, originatingCommand)
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]);
1094 WebInspector.ConsoleCommandResult.prototype = {
1095 toMessageElement: function()
1097 var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this);
1098 element.addStyleClass("console-user-command-result");
1103 WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;
1105 WebInspector.ConsoleGroup = function(parentGroup)
1107 this.parentGroup = parentGroup;
1109 var element = document.createElement("div");
1110 element.className = "console-group";
1111 element.group = this;
1112 this.element = element;
1114 var messagesElement = document.createElement("div");
1115 messagesElement.className = "console-group-messages";
1116 element.appendChild(messagesElement);
1117 this.messagesElement = messagesElement;
1120 WebInspector.ConsoleGroup.prototype = {
1121 addMessage: function(msg)
1123 var element = msg.toMessageElement();
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");
1132 this.messagesElement.appendChild(element);
1134 if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand)
1135 element.previousSibling.addStyleClass("console-adjacent-user-command-result");
1138 _titleClicked: function(event)
1140 var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title");
1141 if (groupTitleElement) {
1142 var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group");
1144 if (groupElement.hasStyleClass("collapsed"))
1145 groupElement.removeStyleClass("collapsed");
1147 groupElement.addStyleClass("collapsed");
1148 groupTitleElement.scrollIntoViewIfNeeded(true);
1151 event.stopPropagation();
1152 event.preventDefault();