diff options
Diffstat (limited to 'Source/WebCore/inspector/front-end/ConsoleView.js')
-rw-r--r-- | Source/WebCore/inspector/front-end/ConsoleView.js | 1127 |
1 files changed, 1127 insertions, 0 deletions
diff --git a/Source/WebCore/inspector/front-end/ConsoleView.js b/Source/WebCore/inspector/front-end/ConsoleView.js new file mode 100644 index 0000000..c98552d --- /dev/null +++ b/Source/WebCore/inspector/front-end/ConsoleView.js @@ -0,0 +1,1127 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +const ExpressionStopCharacters = " =:[({;,!+-*/&|^<>"; + +WebInspector.ConsoleView = function(drawer) +{ + WebInspector.View.call(this, document.getElementById("console-view")); + + this.messages = []; + this.drawer = drawer; + + this.clearButton = document.getElementById("clear-console-status-bar-item"); + this.clearButton.title = WebInspector.UIString("Clear console log."); + this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false); + + this.messagesElement = document.getElementById("console-messages"); + this.messagesElement.addEventListener("selectstart", this._messagesSelectStart.bind(this), false); + this.messagesElement.addEventListener("click", this._messagesClicked.bind(this), true); + + this.promptElement = document.getElementById("console-prompt"); + this.promptElement.className = "source-code"; + this.promptElement.addEventListener("keydown", this._promptKeyDown.bind(this), true); + this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), ExpressionStopCharacters + "."); + this.prompt.history = WebInspector.settings.consoleHistory; + + this.topGroup = new WebInspector.ConsoleGroup(null); + this.messagesElement.insertBefore(this.topGroup.element, this.promptElement); + this.currentGroup = this.topGroup; + + this.toggleConsoleButton = document.getElementById("console-status-bar-item"); + this.toggleConsoleButton.title = WebInspector.UIString("Show console."); + this.toggleConsoleButton.addEventListener("click", this._toggleConsoleButtonClicked.bind(this), false); + + // Will hold the list of filter elements + this.filterBarElement = document.getElementById("console-filter"); + + function createDividerElement() { + var dividerElement = document.createElement("div"); + dividerElement.addStyleClass("scope-bar-divider"); + this.filterBarElement.appendChild(dividerElement); + } + + var updateFilterHandler = this._updateFilter.bind(this); + function createFilterElement(category, label) { + var categoryElement = document.createElement("li"); + categoryElement.category = category; + categoryElement.className = category; + categoryElement.addEventListener("click", updateFilterHandler, false); + categoryElement.textContent = label; + + this.filterBarElement.appendChild(categoryElement); + + return categoryElement; + } + + this.allElement = createFilterElement.call(this, "all", WebInspector.UIString("All")); + createDividerElement.call(this); + this.errorElement = createFilterElement.call(this, "errors", WebInspector.UIString("Errors")); + this.warningElement = createFilterElement.call(this, "warnings", WebInspector.UIString("Warnings")); + this.logElement = createFilterElement.call(this, "logs", WebInspector.UIString("Logs")); + + this.filter(this.allElement, false); + this._registerShortcuts(); + + this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); + + this._customFormatters = { + "object": this._formatobject, + "array": this._formatarray, + "node": this._formatnode, + "string": this._formatstring + }; +} + +WebInspector.ConsoleView.prototype = { + _updateFilter: function(e) + { + var isMac = WebInspector.isMac(); + var selectMultiple = false; + if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) + selectMultiple = true; + if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) + selectMultiple = true; + + this.filter(e.target, selectMultiple); + }, + + filter: function(target, selectMultiple) + { + function unselectAll() + { + this.allElement.removeStyleClass("selected"); + this.errorElement.removeStyleClass("selected"); + this.warningElement.removeStyleClass("selected"); + this.logElement.removeStyleClass("selected"); + + this.messagesElement.removeStyleClass("filter-all"); + this.messagesElement.removeStyleClass("filter-errors"); + this.messagesElement.removeStyleClass("filter-warnings"); + this.messagesElement.removeStyleClass("filter-logs"); + } + + var targetFilterClass = "filter-" + target.category; + + if (target.category === "all") { + if (target.hasStyleClass("selected")) { + // We can't unselect all, so we break early here + return; + } + + unselectAll.call(this); + } else { + // Something other than all is being selected, so we want to unselect all + if (this.allElement.hasStyleClass("selected")) { + this.allElement.removeStyleClass("selected"); + this.messagesElement.removeStyleClass("filter-all"); + } + } + + if (!selectMultiple) { + // If multiple selection is off, we want to unselect everything else + // and just select ourselves. + unselectAll.call(this); + + target.addStyleClass("selected"); + this.messagesElement.addStyleClass(targetFilterClass); + + return; + } + + if (target.hasStyleClass("selected")) { + // If selectMultiple is turned on, and we were selected, we just + // want to unselect ourselves. + target.removeStyleClass("selected"); + this.messagesElement.removeStyleClass(targetFilterClass); + } else { + // If selectMultiple is turned on, and we weren't selected, we just + // want to select ourselves. + target.addStyleClass("selected"); + this.messagesElement.addStyleClass(targetFilterClass); + } + }, + + _toggleConsoleButtonClicked: function() + { + this.drawer.visibleView = this; + }, + + attach: function(mainElement, statusBarElement) + { + mainElement.appendChild(this.element); + statusBarElement.appendChild(this.clearButton); + statusBarElement.appendChild(this.filterBarElement); + }, + + show: function() + { + this.toggleConsoleButton.addStyleClass("toggled-on"); + this.toggleConsoleButton.title = WebInspector.UIString("Hide console."); + if (!this.prompt.isCaretInsidePrompt()) + this.prompt.moveCaretToEndOfPrompt(); + }, + + afterShow: function() + { + WebInspector.currentFocusElement = this.promptElement; + }, + + hide: function() + { + this.toggleConsoleButton.removeStyleClass("toggled-on"); + this.toggleConsoleButton.title = WebInspector.UIString("Show console."); + }, + + _scheduleScrollIntoView: function() + { + if (this._scrollIntoViewTimer) + return; + + function scrollIntoView() + { + this.promptElement.scrollIntoView(true); + delete this._scrollIntoViewTimer; + } + this._scrollIntoViewTimer = setTimeout(scrollIntoView.bind(this), 20); + }, + + addMessage: function(msg) + { + var shouldScrollToLastMessage = this.messagesElement.isScrolledToBottom(); + + if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) { + this._incrementErrorWarningCount(msg); + WebInspector.resourceTreeModel.addConsoleMessage(msg); + this.commandSincePreviousMessage = false; + this.previousMessage = msg; + } else if (msg instanceof WebInspector.ConsoleCommand) { + if (this.previousMessage) { + this.commandSincePreviousMessage = true; + } + } + + this.messages.push(msg); + + if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) { + var parentGroup = this.currentGroup.parentGroup + if (parentGroup) + this.currentGroup = parentGroup; + } else { + if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) { + var group = new WebInspector.ConsoleGroup(this.currentGroup); + this.currentGroup.messagesElement.appendChild(group.element); + this.currentGroup = group; + } + + this.currentGroup.addMessage(msg); + } + + if (shouldScrollToLastMessage) + this._scheduleScrollIntoView(); + }, + + updateMessageRepeatCount: function(count) + { + var msg = this.previousMessage; + var prevRepeatCount = msg.totalRepeatCount; + + if (!this.commandSincePreviousMessage) { + msg.repeatDelta = count - prevRepeatCount; + msg.repeatCount = msg.repeatCount + msg.repeatDelta; + msg.totalRepeatCount = count; + msg._updateRepeatCount(); + this._incrementErrorWarningCount(msg); + } else { + 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); + msgCopy.totalRepeatCount = count; + msgCopy._formatMessage(); + this.addMessage(msgCopy); + } + }, + + _incrementErrorWarningCount: function(msg) + { + switch (msg.level) { + case WebInspector.ConsoleMessage.MessageLevel.Warning: + WebInspector.warnings += msg.repeatDelta; + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + WebInspector.errors += msg.repeatDelta; + break; + } + }, + + requestClearMessages: function() + { + InspectorBackend.clearConsoleMessages(); + }, + + clearMessages: function() + { + WebInspector.resourceTreeModel.clearConsoleMessages(); + + this.messages = []; + + this.currentGroup = this.topGroup; + this.topGroup.messagesElement.removeChildren(); + + WebInspector.errors = 0; + WebInspector.warnings = 0; + + delete this.commandSincePreviousMessage; + delete this.previousMessage; + }, + + completions: function(wordRange, bestMatchOnly, completionsReadyCallback) + { + // Pass less stop characters to rangeOfWord so the range will be a more complete expression. + var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, ExpressionStopCharacters, this.promptElement, "backward"); + var expressionString = expressionRange.toString(); + var lastIndex = expressionString.length - 1; + + var dotNotation = (expressionString[lastIndex] === "."); + var bracketNotation = (expressionString[lastIndex] === "["); + + if (dotNotation || bracketNotation) + expressionString = expressionString.substr(0, lastIndex); + + var prefix = wordRange.toString(); + if (!expressionString && !prefix) + return; + + var reportCompletions = this._reportCompletions.bind(this, bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix); + // Collect comma separated object properties for the completion. + + var includeInspectorCommandLineAPI = (!dotNotation && !bracketNotation); + var callFrameId = WebInspector.panels.scripts.selectedCallFrameId(); + var injectedScriptAccess; + if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) { + var selectedCallFrame = WebInspector.panels.scripts.sidebarPanes.callstack.selectedCallFrame; + injectedScriptAccess = InjectedScriptAccess.get(selectedCallFrame.worldId); + } else + injectedScriptAccess = InjectedScriptAccess.getDefault(); + injectedScriptAccess.getCompletions(expressionString, includeInspectorCommandLineAPI, callFrameId, reportCompletions); + }, + + _reportCompletions: function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) { + if (isException) + return; + + if (bracketNotation) { + if (prefix.length && prefix[0] === "'") + var quoteUsed = "'"; + else + var quoteUsed = "\""; + } + + var results = []; + var properties = Object.keys(result).sort(); + + for (var i = 0; i < properties.length; ++i) { + var property = properties[i]; + + if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property)) + continue; + + if (bracketNotation) { + if (!/^[0-9]+$/.test(property)) + property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed; + property += "]"; + } + + if (property.length < prefix.length) + continue; + if (property.indexOf(prefix) !== 0) + continue; + + results.push(property); + if (bestMatchOnly) + break; + } + completionsReadyCallback(results); + }, + + _clearButtonClicked: function() + { + this.requestClearMessages(); + }, + + _handleContextMenuEvent: function(event) + { + if (!window.getSelection().isCollapsed) { + // If there is a selection, we want to show our normal context menu + // (with Copy, etc.), and not Clear Console. + return; + } + + var contextMenu = new WebInspector.ContextMenu(); + + function monitoringXHRWasChanged(newState) + { + WebInspector.monitoringXHREnabled = newState; + } + var itemAction = InspectorBackend.setMonitoringXHREnabled.bind(InspectorBackend, !WebInspector.monitoringXHREnabled, monitoringXHRWasChanged); + contextMenu.appendCheckboxItem(WebInspector.UIString("XMLHttpRequest logging"), itemAction, WebInspector.monitoringXHREnabled); + contextMenu.appendItem(WebInspector.UIString("Clear Console"), this.requestClearMessages.bind(this)); + contextMenu.show(event); + }, + + _messagesSelectStart: function(event) + { + if (this._selectionTimeout) + clearTimeout(this._selectionTimeout); + + this.prompt.clearAutoComplete(); + + function moveBackIfOutside() + { + delete this._selectionTimeout; + if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed) + this.prompt.moveCaretToEndOfPrompt(); + this.prompt.autoCompleteSoon(); + } + + this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100); + }, + + _messagesClicked: function(event) + { + var link = event.target.enclosingNodeOrSelfWithNodeName("a"); + if (!link || !link.representedNode) + return; + + WebInspector.updateFocusedNode(link.representedNode.id); + event.stopPropagation(); + event.preventDefault(); + }, + + _registerShortcuts: function() + { + this._shortcuts = {}; + + var shortcut = WebInspector.KeyboardShortcut; + var shortcutK = shortcut.makeDescriptor("k", WebInspector.KeyboardShortcut.Modifiers.Meta); + // This case requires a separate bound function as its isMacOnly property should not be shared among different shortcut handlers. + this._shortcuts[shortcutK.key] = this.requestClearMessages.bind(this); + this._shortcuts[shortcutK.key].isMacOnly = true; + + var clearConsoleHandler = this.requestClearMessages.bind(this); + var shortcutL = shortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl); + this._shortcuts[shortcutL.key] = clearConsoleHandler; + + var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Console")); + var keys = WebInspector.isMac() ? [ shortcutK.name, shortcutL.name ] : [ shortcutL.name ]; + section.addAlternateKeys(keys, WebInspector.UIString("Clear Console")); + + keys = [ + shortcut.shortcutToString(shortcut.Keys.Tab), + shortcut.shortcutToString(shortcut.Keys.Tab, shortcut.Modifiers.Shift) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Next/previous suggestion")); + section.addKey(shortcut.shortcutToString(shortcut.Keys.Right), WebInspector.UIString("Accept suggestion")); + keys = [ + shortcut.shortcutToString(shortcut.Keys.Down), + shortcut.shortcutToString(shortcut.Keys.Up) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Next/previous line")); + keys = [ + shortcut.shortcutToString("N", shortcut.Modifiers.Alt), + shortcut.shortcutToString("P", shortcut.Modifiers.Alt) + ]; + if (WebInspector.isMac()) + section.addRelatedKeys(keys, WebInspector.UIString("Next/previous command")); + section.addKey(shortcut.shortcutToString(shortcut.Keys.Enter), WebInspector.UIString("Execute command")); + }, + + _promptKeyDown: function(event) + { + if (isEnterKey(event)) { + this._enterKeyPressed(event); + return; + } + + var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event); + var handler = this._shortcuts[shortcut]; + if (handler) { + if (!this._shortcuts[shortcut].isMacOnly || WebInspector.isMac()) { + handler(); + event.preventDefault(); + return; + } + } + }, + + evalInInspectedWindow: function(expression, objectGroup, callback) + { + if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) { + WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, objectGroup, callback); + return; + } + this.doEvalInWindow(expression, objectGroup, callback); + }, + + doEvalInWindow: function(expression, objectGroup, callback) + { + if (!expression) { + // There is no expression, so the completion should happen against global properties. + expression = "this"; + } + + function evalCallback(result) + { + callback(WebInspector.RemoteObject.fromPayload(result)); + }; + InjectedScriptAccess.getDefault().evaluate(expression, objectGroup, evalCallback); + }, + + _enterKeyPressed: function(event) + { + if (event.altKey || event.ctrlKey || event.shiftKey) + return; + + event.preventDefault(); + event.stopPropagation(); + + this.prompt.clearAutoComplete(true); + + var str = this.prompt.text; + if (!str.length) + return; + + var commandMessage = new WebInspector.ConsoleCommand(str); + this.addMessage(commandMessage); + + var self = this; + function printResult(result) + { + self.prompt.history.push(str); + self.prompt.historyOffset = 0; + self.prompt.text = ""; + + WebInspector.settings.consoleHistory = self.prompt.history.slice(-30); + + self.addMessage(new WebInspector.ConsoleCommandResult(result, commandMessage)); + } + this.evalInInspectedWindow(str, "console", printResult); + }, + + _format: function(output, forceObjectFormat) + { + var isProxy = (output != null && typeof output === "object"); + var type = (forceObjectFormat ? "object" : WebInspector.RemoteObject.type(output)); + + var formatter = this._customFormatters[type]; + if (!formatter || !isProxy) { + formatter = this._formatvalue; + output = output.description; + } + + var span = document.createElement("span"); + span.className = "console-formatted-" + type + " source-code"; + formatter.call(this, output, span); + return span; + }, + + _formatvalue: function(val, elem) + { + elem.appendChild(document.createTextNode(val)); + }, + + _formatobject: function(obj, elem) + { + elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element); + }, + + _formatnode: function(object, elem) + { + function printNode(nodeId) + { + if (!nodeId) { + // Sometimes DOM is loaded after the sync message is being formatted, so we get no + // nodeId here. So we fall back to object formatting here. + this._formatobject(object, elem); + return; + } + var treeOutline = new WebInspector.ElementsTreeOutline(); + treeOutline.showInElementsPanelEnabled = true; + treeOutline.rootDOMNode = WebInspector.domAgent.nodeForId(nodeId); + treeOutline.element.addStyleClass("outline-disclosure"); + if (!treeOutline.children[0].hasChildren) + treeOutline.element.addStyleClass("single-node"); + elem.appendChild(treeOutline.element); + } + object.pushNodeToFrontend(printNode.bind(this)); + }, + + _formatarray: function(arr, elem) + { + arr.getOwnProperties(false, this._printArray.bind(this, elem)); + }, + + _formatstring: function(output, elem) + { + var span = document.createElement("span"); + span.className = "console-formatted-string source-code"; + span.appendChild(WebInspector.linkifyStringAsFragment(output.description)); + + // Make black quotes. + elem.removeStyleClass("console-formatted-string"); + elem.appendChild(document.createTextNode("\"")); + elem.appendChild(span); + elem.appendChild(document.createTextNode("\"")); + }, + + _printArray: function(elem, properties) + { + if (!properties) + return; + + var elements = []; + for (var i = 0; i < properties.length; ++i) { + var name = properties[i].name; + if (name == parseInt(name)) + elements[name] = this._formatAsArrayEntry(properties[i].value); + } + + elem.appendChild(document.createTextNode("[")); + for (var i = 0; i < elements.length; ++i) { + var element = elements[i]; + if (element) + elem.appendChild(element); + else + elem.appendChild(document.createTextNode("undefined")) + if (i < elements.length - 1) + elem.appendChild(document.createTextNode(", ")); + } + elem.appendChild(document.createTextNode("]")); + }, + + _formatAsArrayEntry: function(output) + { + // Prevent infinite expansion of cross-referencing arrays. + return this._format(output, WebInspector.RemoteObject.type(output) === "array"); + } +} + +WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype; + +WebInspector.ConsoleMessage = function(source, type, level, line, url, repeatCount, message, parameters, stackTrace, requestId) +{ + this.source = source; + this.type = type; + this.level = level; + this.line = line; + this.url = url; + this.repeatCount = repeatCount; + this.repeatDelta = repeatCount; + this.totalRepeatCount = repeatCount; + this._messageText = message; + this._parameters = parameters; + this._stackTrace = stackTrace; + this._requestId = requestId; + this._formatMessage(); +} + +WebInspector.ConsoleMessage.createTextMessage = function(text, level) +{ + level = level || WebInspector.ConsoleMessage.MessageLevel.Log; + return new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, 0, null, 1, null, [text], null); +} + +WebInspector.ConsoleMessage.prototype = { + _formatMessage: function() + { + var stackTrace = this._stackTrace; + var messageText; + switch (this.type) { + case WebInspector.ConsoleMessage.MessageType.Trace: + messageText = document.createTextNode("console.trace()"); + break; + case WebInspector.ConsoleMessage.MessageType.UncaughtException: + messageText = document.createTextNode(this._messageText); + break; + case WebInspector.ConsoleMessage.MessageType.NetworkError: + var resource = this._requestId && WebInspector.panels.network.resources[this._requestId]; + if (resource) { + stackTrace = resource.stackTrace; + + messageText = document.createElement("span"); + messageText.appendChild(document.createTextNode(resource.requestMethod + " ")); + messageText.appendChild(WebInspector.linkifyURLAsNode(resource.url)); + if (resource.failed) + messageText.appendChild(document.createTextNode(" " + resource.localizedFailDescription)); + else + messageText.appendChild(document.createTextNode(" " + resource.statusCode + " (" + resource.statusText + ")")); + } else + messageText = this._format([this._messageText]); + break; + case WebInspector.ConsoleMessage.MessageType.Assert: + var args = [WebInspector.UIString("Assertion failed:")]; + if (this._parameters) + args = args.concat(this._parameters); + messageText = this._format(args); + break; + case WebInspector.ConsoleMessage.MessageType.Object: + var obj = this._parameters ? this._parameters[0] : undefined; + var args = ["%O", obj]; + messageText = this._format(args); + break; + default: + var args = this._parameters || [this._messageText]; + messageText = this._format(args); + break; + } + + this._formattedMessage = document.createElement("span"); + this._formattedMessage.className = "console-message-text source-code"; + + if (stackTrace && stackTrace.length) { + var topCallFrame = stackTrace[0]; + var sourceName = topCallFrame.scriptName; + var sourceLine = topCallFrame.lineNumber; + } else { + var sourceName = this.url; + var sourceLine = this.line; + } + + if (sourceName && sourceName !== "undefined") { + var urlElement = WebInspector.linkifyResourceAsNode(sourceName, "scripts", sourceLine, "console-message-url"); + this._formattedMessage.appendChild(urlElement); + } + + this._formattedMessage.appendChild(messageText); + + if (this._stackTrace) { + switch (this.type) { + case WebInspector.ConsoleMessage.MessageType.Trace: + case WebInspector.ConsoleMessage.MessageType.UncaughtException: + case WebInspector.ConsoleMessage.MessageType.NetworkError: + case WebInspector.ConsoleMessage.MessageType.Assert: { + var ol = document.createElement("ol"); + ol.className = "outline-disclosure"; + var treeOutline = new TreeOutline(ol); + + var content = this._formattedMessage; + var root = new TreeElement(content, null, true); + content.treeElementForTest = root; + treeOutline.appendChild(root); + if (this.type === WebInspector.ConsoleMessage.MessageType.Trace) + root.expand(); + + this._populateStackTraceTreeElement(root); + this._formattedMessage = ol; + } + } + } + + // This is used for inline message bubbles in SourceFrames, or other plain-text representations. + this.message = this._formattedMessage.textContent; + }, + + isErrorOrWarning: function() + { + return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error); + }, + + _format: function(parameters) + { + // This node is used like a Builder. Values are continually appended onto it. + var formattedResult = document.createElement("span"); + if (!parameters.length) + return formattedResult; + + // Formatting code below assumes that parameters are all wrappers whereas frontend console + // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here. + for (var i = 0; i < parameters.length; ++i) { + if (typeof parameters[i] === "object") + parameters[i] = WebInspector.RemoteObject.fromPayload(parameters[i]); + else + parameters[i] = WebInspector.RemoteObject.fromPrimitiveValue(parameters[i]); + } + + // There can be string log and string eval result. We distinguish between them based on message type. + var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && this.type !== WebInspector.ConsoleMessage.MessageType.Result; + + // Multiple parameters with the first being a format string. Save unused substitutions. + if (shouldFormatMessage) { + // Multiple parameters with the first being a format string. Save unused substitutions. + var result = this._formatWithSubstitutionString(parameters, formattedResult); + parameters = result.unusedSubstitutions; + if (parameters.length) + formattedResult.appendChild(document.createTextNode(" ")); + } + + // Single parameter, or unused substitutions from above. + for (var i = 0; i < parameters.length; ++i) { + // Inline strings when formatting. + if (shouldFormatMessage && parameters[i].type === "string") + formattedResult.appendChild(document.createTextNode(parameters[i].description)); + else + formattedResult.appendChild(WebInspector.console._format(parameters[i])); + if (i < parameters.length - 1) + formattedResult.appendChild(document.createTextNode(" ")); + } + return formattedResult; + }, + + _formatWithSubstitutionString: function(parameters, formattedResult) + { + var formatters = {} + for (var i in String.standardFormatters) + formatters[i] = String.standardFormatters[i]; + + function consoleFormatWrapper(force) + { + return function(obj) { + return WebInspector.console._format(obj, force); + }; + } + + // Firebug uses %o for formatting objects. + formatters.o = consoleFormatWrapper(); + // Firebug allows both %i and %d for formatting integers. + formatters.i = formatters.d; + // Support %O to force object formatting, instead of the type-based %o formatting. + formatters.O = consoleFormatWrapper(true); + + function append(a, b) + { + if (!(b instanceof Node)) + a.appendChild(WebInspector.linkifyStringAsFragment(b.toString())); + else + a.appendChild(b); + return a; + } + + // String.format does treat formattedResult like a Builder, result is an object. + return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append); + }, + + toMessageElement: function() + { + if (this._element) + return this._element; + + var element = document.createElement("div"); + element.message = this; + element.className = "console-message"; + + this._element = element; + + switch (this.level) { + case WebInspector.ConsoleMessage.MessageLevel.Tip: + element.addStyleClass("console-tip-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Log: + element.addStyleClass("console-log-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Debug: + element.addStyleClass("console-debug-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + element.addStyleClass("console-warning-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + element.addStyleClass("console-error-level"); + break; + } + + if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) + element.addStyleClass("console-group-title"); + + if (this.elementsTreeOutline) { + element.addStyleClass("outline-disclosure"); + element.appendChild(this.elementsTreeOutline.element); + return element; + } + + element.appendChild(this._formattedMessage); + + if (this.repeatCount > 1) + this._updateRepeatCount(); + + return element; + }, + + _populateStackTraceTreeElement: function(parentTreeElement) + { + for (var i = 0; i < this._stackTrace.length; i++) { + var frame = this._stackTrace[i]; + + var content = document.createElement("div"); + var messageTextElement = document.createElement("span"); + messageTextElement.className = "console-message-text source-code"; + var functionName = frame.functionName || WebInspector.UIString("(anonymous function)"); + messageTextElement.appendChild(document.createTextNode(functionName)); + content.appendChild(messageTextElement); + + var urlElement = WebInspector.linkifyResourceAsNode(frame.scriptName, "scripts", frame.lineNumber, "console-message-url"); + content.appendChild(urlElement); + + var treeElement = new TreeElement(content); + parentTreeElement.appendChild(treeElement); + } + }, + + _updateRepeatCount: function() { + if (!this.repeatCountElement) { + this.repeatCountElement = document.createElement("span"); + this.repeatCountElement.className = "bubble"; + + this._element.insertBefore(this.repeatCountElement, this._element.firstChild); + this._element.addStyleClass("repeated-message"); + } + this.repeatCountElement.textContent = this.repeatCount; + }, + + toString: function() + { + var sourceString; + switch (this.source) { + case WebInspector.ConsoleMessage.MessageSource.HTML: + sourceString = "HTML"; + break; + case WebInspector.ConsoleMessage.MessageSource.WML: + sourceString = "WML"; + break; + case WebInspector.ConsoleMessage.MessageSource.XML: + sourceString = "XML"; + break; + case WebInspector.ConsoleMessage.MessageSource.JS: + sourceString = "JS"; + break; + case WebInspector.ConsoleMessage.MessageSource.CSS: + sourceString = "CSS"; + break; + case WebInspector.ConsoleMessage.MessageSource.Other: + sourceString = "Other"; + break; + } + + var typeString; + switch (this.type) { + case WebInspector.ConsoleMessage.MessageType.Log: + case WebInspector.ConsoleMessage.MessageType.UncaughtException: + case WebInspector.ConsoleMessage.MessageType.NetworkError: + typeString = "Log"; + break; + case WebInspector.ConsoleMessage.MessageType.Object: + typeString = "Object"; + break; + case WebInspector.ConsoleMessage.MessageType.Trace: + typeString = "Trace"; + break; + case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed: + case WebInspector.ConsoleMessage.MessageType.StartGroup: + typeString = "Start Group"; + break; + case WebInspector.ConsoleMessage.MessageType.EndGroup: + typeString = "End Group"; + break; + case WebInspector.ConsoleMessage.MessageType.Assert: + typeString = "Assert"; + break; + case WebInspector.ConsoleMessage.MessageType.Result: + typeString = "Result"; + break; + } + + var levelString; + switch (this.level) { + case WebInspector.ConsoleMessage.MessageLevel.Tip: + levelString = "Tip"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Log: + levelString = "Log"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + levelString = "Warning"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Debug: + levelString = "Debug"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + levelString = "Error"; + break; + } + + return sourceString + " " + typeString + " " + levelString + ": " + this._formattedMessage.textContent + "\n" + this.url + " line " + this.line; + }, + + isEqual: function(msg) + { + if (!msg) + return false; + + if (this._stackTrace) { + if (!msg._stackTrace) + return false; + var l = this._stackTrace; + var r = msg._stackTrace; + for (var i = 0; i < l.length; i++) { + if (l[i].scriptName !== r[i].scriptName || + l[i].functionName !== r[i].functionName || + l[i].lineNumber !== r[i].lineNumber || + l[i].column !== r[i].column) + return false; + } + } + + return (this.source === msg.source) + && (this.type === msg.type) + && (this.level === msg.level) + && (this.line === msg.line) + && (this.url === msg.url) + && (this.message === msg.message) + && (this._requestId === msg._requestId); + } +} + +// Note: Keep these constants in sync with the ones in Console.h +WebInspector.ConsoleMessage.MessageSource = { + HTML: 0, + WML: 1, + XML: 2, + JS: 3, + CSS: 4, + Other: 5 +} + +WebInspector.ConsoleMessage.MessageType = { + Log: 0, + Object: 1, + Trace: 2, + StartGroup: 3, + StartGroupCollapsed: 4, + EndGroup: 5, + Assert: 6, + UncaughtException: 7, + NetworkError:8, + Result: 9 +} + +WebInspector.ConsoleMessage.MessageLevel = { + Tip: 0, + Log: 1, + Warning: 2, + Error: 3, + Debug: 4 +} + +WebInspector.ConsoleCommand = function(command) +{ + this.command = command; +} + +WebInspector.ConsoleCommand.prototype = { + toMessageElement: function() + { + var element = document.createElement("div"); + element.command = this; + element.className = "console-user-command"; + + var commandTextElement = document.createElement("span"); + commandTextElement.className = "console-message-text source-code"; + commandTextElement.textContent = this.command; + element.appendChild(commandTextElement); + + return element; + } +} + +WebInspector.ConsoleCommandResult = function(result, originatingCommand) +{ + var level = (result.isError() ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log); + this.originatingCommand = originatingCommand; + WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Result, level, -1, null, 1, null, [result]); +} + +WebInspector.ConsoleCommandResult.prototype = { + toMessageElement: function() + { + var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this); + element.addStyleClass("console-user-command-result"); + return element; + } +} + +WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype; + +WebInspector.ConsoleGroup = function(parentGroup) +{ + this.parentGroup = parentGroup; + + var element = document.createElement("div"); + element.className = "console-group"; + element.group = this; + this.element = element; + + var messagesElement = document.createElement("div"); + messagesElement.className = "console-group-messages"; + element.appendChild(messagesElement); + this.messagesElement = messagesElement; +} + +WebInspector.ConsoleGroup.prototype = { + addMessage: function(msg) + { + var element = msg.toMessageElement(); + + if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) { + this.messagesElement.parentNode.insertBefore(element, this.messagesElement); + element.addEventListener("click", this._titleClicked.bind(this), false); + var groupElement = element.enclosingNodeOrSelfWithClass("console-group"); + if (groupElement && msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) + groupElement.addStyleClass("collapsed"); + } else + this.messagesElement.appendChild(element); + + if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand) + element.previousSibling.addStyleClass("console-adjacent-user-command-result"); + }, + + _titleClicked: function(event) + { + var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title"); + if (groupTitleElement) { + var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group"); + if (groupElement) + if (groupElement.hasStyleClass("collapsed")) + groupElement.removeStyleClass("collapsed"); + else + groupElement.addStyleClass("collapsed"); + groupTitleElement.scrollIntoViewIfNeeded(true); + } + + event.stopPropagation(); + event.preventDefault(); + } +} + |