| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * Copyright (C) 2007, 2008, 2013-2015 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 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. |
| */ |
| |
| WebInspector.ConsoleMessageView = class ConsoleMessageView extends WebInspector.Object |
| { |
| constructor(message) |
| { |
| super(); |
| |
| console.assert(message instanceof WebInspector.ConsoleMessage); |
| |
| this._message = message; |
| this._expandable = false; |
| this._repeatCount = message._repeatCount || 0; |
| |
| // These are the parameters unused by the messages's optional format string. |
| // Any extra parameters will be displayed as children of this message. |
| this._extraParameters = message.parameters; |
| } |
| |
| // Public |
| |
| render() |
| { |
| this._element = document.createElement("div"); |
| this._element.classList.add("console-message"); |
| |
| // FIXME: <https://webkit.org/b/143545> Web Inspector: LogContentView should use higher level objects |
| this._element.__message = this._message; |
| this._element.__messageView = this; |
| |
| if (this._message.type === WebInspector.ConsoleMessage.MessageType.Result) { |
| this._element.classList.add("console-user-command-result"); |
| this._element.setAttribute("data-labelprefix", WebInspector.UIString("Output: ")); |
| } else if (this._message.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this._message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) |
| this._element.classList.add("console-group-title"); |
| |
| switch (this._message.level) { |
| case WebInspector.ConsoleMessage.MessageLevel.Log: |
| this._element.classList.add("console-log-level"); |
| this._element.setAttribute("data-labelprefix", WebInspector.UIString("Log: ")); |
| break; |
| case WebInspector.ConsoleMessage.MessageLevel.Info: |
| this._element.classList.add("console-info-level"); |
| this._element.setAttribute("data-labelprefix", WebInspector.UIString("Info: ")); |
| break; |
| case WebInspector.ConsoleMessage.MessageLevel.Debug: |
| this._element.classList.add("console-debug-level"); |
| this._element.setAttribute("data-labelprefix", WebInspector.UIString("Debug: ")); |
| break; |
| case WebInspector.ConsoleMessage.MessageLevel.Warning: |
| this._element.classList.add("console-warning-level"); |
| this._element.setAttribute("data-labelprefix", WebInspector.UIString("Warning: ")); |
| break; |
| case WebInspector.ConsoleMessage.MessageLevel.Error: |
| this._element.classList.add("console-error-level"); |
| this._element.setAttribute("data-labelprefix", WebInspector.UIString("Error: ")); |
| break; |
| } |
| |
| // FIXME: The location link should include stack trace information. |
| this._appendLocationLink(); |
| |
| this._messageTextElement = this._element.appendChild(document.createElement("span")); |
| this._messageTextElement.classList.add("console-top-level-message"); |
| this._messageTextElement.classList.add("console-message-text"); |
| this._appendMessageTextAndArguments(this._messageTextElement); |
| this._appendSavedResultIndex(); |
| |
| this._appendExtraParameters(); |
| this._appendStackTrace(); |
| |
| this._renderRepeatCount(); |
| } |
| |
| get element() |
| { |
| return this._element; |
| } |
| |
| get message() |
| { |
| return this._message; |
| } |
| |
| get repeatCount() |
| { |
| return this._repeatCount; |
| } |
| |
| set repeatCount(count) |
| { |
| console.assert(typeof count === "number"); |
| |
| if (this._repeatCount === count) |
| return; |
| |
| this._repeatCount = count; |
| |
| if (this._element) |
| this._renderRepeatCount(); |
| } |
| |
| _renderRepeatCount() |
| { |
| let count = this._repeatCount; |
| |
| if (count <= 1) { |
| if (this._repeatCountElement) { |
| this._repeatCountElement.remove(); |
| this._repeatCountElement = null; |
| } |
| return; |
| } |
| |
| if (!this._repeatCountElement) { |
| this._repeatCountElement = document.createElement("span"); |
| this._repeatCountElement.classList.add("repeat-count"); |
| this._element.insertBefore(this._repeatCountElement, this._element.firstChild); |
| } |
| |
| this._repeatCountElement.textContent = count; |
| } |
| |
| get expandable() |
| { |
| // There are extra arguments or a call stack that can be shown. |
| if (this._expandable) |
| return true; |
| |
| // There is an object tree that could be expanded. |
| if (this._objectTree) |
| return true; |
| |
| return false; |
| } |
| |
| expand() |
| { |
| if (this._expandable) |
| this._element.classList.add("expanded"); |
| |
| // Auto-expand an inner object tree if there is a single object. |
| // For Trace messages we are auto-expanding for the call stack, don't also auto-expand an object as well. |
| if (this._objectTree && this._message.type !== WebInspector.ConsoleMessage.MessageType.Trace) { |
| if (!this._extraParameters || this._extraParameters.length <= 1) |
| this._objectTree.expand(); |
| } |
| } |
| |
| collapse() |
| { |
| if (this._expandable) |
| this._element.classList.remove("expanded"); |
| |
| // Collapse the object tree just in cases where it was autoexpanded. |
| if (this._objectTree) { |
| if (!this._extraParameters || this._extraParameters.length <= 1) |
| this._objectTree.collapse(); |
| } |
| } |
| |
| toggle() |
| { |
| if (this._element.classList.contains("expanded")) |
| this.collapse(); |
| else |
| this.expand(); |
| } |
| |
| toClipboardString(isPrefixOptional) |
| { |
| let clipboardString = this._messageTextElement.innerText.removeWordBreakCharacters(); |
| if (this._message.savedResultIndex) |
| clipboardString = clipboardString.replace(/\s*=\s*(\$\d+)$/, ""); |
| |
| let hasStackTrace = this._shouldShowStackTrace(); |
| if (!hasStackTrace) { |
| let repeatString = this.repeatCount > 1 ? "x" + this.repeatCount : ""; |
| let urlLine = ""; |
| if (this._message.url) { |
| let components = [WebInspector.displayNameForURL(this._message.url), "line " + this._message.line]; |
| if (repeatString) |
| components.push(repeatString); |
| urlLine = " (" + components.join(", ") + ")"; |
| } else if (repeatString) |
| urlLine = " (" + repeatString + ")"; |
| |
| if (urlLine) { |
| let lines = clipboardString.split("\n"); |
| lines[0] += urlLine; |
| clipboardString = lines.join("\n"); |
| } |
| } |
| |
| if (this._extraElementsList) |
| clipboardString += "\n" + this._extraElementsList.innerText.removeWordBreakCharacters().trim(); |
| |
| if (hasStackTrace) { |
| this._message.stackTrace.callFrames.forEach(function(frame) { |
| clipboardString += "\n\t" + (frame.functionName || WebInspector.UIString("(anonymous function)")); |
| if (frame.sourceCodeLocation) |
| clipboardString += " (" + frame.sourceCodeLocation.originalLocationString() + ")"; |
| }); |
| } |
| |
| if (!isPrefixOptional || this._enforcesClipboardPrefixString()) |
| return this._clipboardPrefixString() + clipboardString; |
| return clipboardString; |
| } |
| |
| // Private |
| |
| _appendMessageTextAndArguments(element) |
| { |
| if (this._message.source === WebInspector.ConsoleMessage.MessageSource.ConsoleAPI) { |
| switch (this._message.type) { |
| case WebInspector.ConsoleMessage.MessageType.Trace: |
| var args = [WebInspector.UIString("Trace")]; |
| if (this._message.parameters) { |
| if (this._message.parameters[0].type === "string") { |
| var prefixedFormatString = WebInspector.UIString("Trace: %s").format(this._message.parameters[0].description); |
| args = [prefixedFormatString].concat(this._message.parameters.slice(1)); |
| } else |
| args = args.concat(this._message.parameters); |
| } |
| this._appendFormattedArguments(element, args); |
| break; |
| |
| case WebInspector.ConsoleMessage.MessageType.Assert: |
| var args = [WebInspector.UIString("Assertion Failed")]; |
| if (this._message.parameters) { |
| if (this._message.parameters[0].type === "string") { |
| var prefixedFormatString = WebInspector.UIString("Assertion Failed: %s").format(this._message.parameters[0].description); |
| args = [prefixedFormatString].concat(this._message.parameters.slice(1)); |
| } else |
| args = args.concat(this._message.parameters); |
| } |
| this._appendFormattedArguments(element, args); |
| break; |
| |
| case WebInspector.ConsoleMessage.MessageType.Dir: |
| var obj = this._message.parameters ? this._message.parameters[0] : undefined; |
| this._appendFormattedArguments(element, ["%O", obj]); |
| break; |
| |
| case WebInspector.ConsoleMessage.MessageType.Table: |
| var args = this._message.parameters; |
| element.appendChild(this._formatParameterAsTable(args)); |
| this._extraParameters = null; |
| break; |
| |
| case WebInspector.ConsoleMessage.MessageType.StartGroup: |
| case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed: |
| var args = this._message.parameters || [this._message.messageText || WebInspector.UIString("Group")]; |
| this._formatWithSubstitutionString(args, element); |
| this._extraParameters = null; |
| break; |
| |
| default: |
| var args = this._message.parameters || [this._message.messageText]; |
| this._appendFormattedArguments(element, args); |
| break; |
| } |
| return; |
| } |
| |
| // FIXME: Better handle WebInspector.ConsoleMessage.MessageSource.Network once it has request info. |
| |
| var args = this._message.parameters || [this._message.messageText]; |
| this._appendFormattedArguments(element, args); |
| } |
| |
| _appendSavedResultIndex(element) |
| { |
| if (!this._message.savedResultIndex) |
| return; |
| |
| console.assert(this._message instanceof WebInspector.ConsoleCommandResultMessage); |
| console.assert(this._message.type === WebInspector.ConsoleMessage.MessageType.Result); |
| |
| var savedVariableElement = document.createElement("span"); |
| savedVariableElement.classList.add("console-saved-variable"); |
| savedVariableElement.textContent = " = $" + this._message.savedResultIndex; |
| |
| if (this._objectTree) |
| this._objectTree.appendTitleSuffix(savedVariableElement); |
| else |
| this._messageTextElement.appendChild(savedVariableElement); |
| } |
| |
| _appendLocationLink() |
| { |
| if (this._message.source === WebInspector.ConsoleMessage.MessageSource.Network) { |
| if (this._message.url) { |
| var anchor = WebInspector.linkifyURLAsNode(this._message.url, this._message.url, "console-message-url"); |
| anchor.classList.add("console-message-location"); |
| this._element.appendChild(anchor); |
| } |
| return; |
| } |
| |
| var firstNonNativeNonAnonymousCallFrame = this._message.stackTrace.firstNonNativeNonAnonymousCallFrame; |
| |
| var callFrame; |
| if (firstNonNativeNonAnonymousCallFrame) { |
| // JavaScript errors and console.* methods. |
| callFrame = firstNonNativeNonAnonymousCallFrame; |
| } else if (this._message.url && !this._shouldHideURL(this._message.url)) { |
| // CSS warnings have no stack traces. |
| callFrame = WebInspector.CallFrame.fromPayload({ |
| functionName: "", |
| url: this._message.url, |
| lineNumber: this._message.line, |
| columnNumber: this._message.column |
| }); |
| } |
| |
| if (callFrame) { |
| const showFunctionName = !!callFrame.functionName; |
| var locationElement = new WebInspector.CallFrameView(callFrame, showFunctionName); |
| locationElement.classList.add("console-message-location"); |
| this._element.appendChild(locationElement); |
| return; |
| } |
| |
| if (this._message.parameters && this._message.parameters.length === 1) { |
| var parameter = this._createRemoteObjectIfNeeded(this._message.parameters[0]); |
| |
| parameter.findFunctionSourceCodeLocation().then(function(result) { |
| if (result === WebInspector.RemoteObject.SourceCodeLocationPromise.NoSourceFound || result === WebInspector.RemoteObject.SourceCodeLocationPromise.MissingObjectId) |
| return; |
| |
| var link = this._linkifyLocation(result.sourceCode.url, result.lineNumber, result.columnNumber); |
| link.classList.add("console-message-location"); |
| |
| if (this._element.hasChildNodes()) |
| this._element.insertBefore(link, this._element.firstChild); |
| else |
| this._element.appendChild(link); |
| }.bind(this)); |
| } |
| } |
| |
| _appendExtraParameters() |
| { |
| if (!this._extraParameters || !this._extraParameters.length) |
| return; |
| |
| this._makeExpandable(); |
| |
| // Auto-expand if there are multiple objects. |
| if (this._extraParameters.length > 1) |
| this.expand(); |
| |
| this._extraElementsList = this._element.appendChild(document.createElement("ol")); |
| this._extraElementsList.classList.add("console-message-extra-parameters-container"); |
| |
| for (var parameter of this._extraParameters) { |
| var listItemElement = this._extraElementsList.appendChild(document.createElement("li")); |
| const forceObjectFormat = parameter.type === "object" && (parameter.subtype !== "null" && parameter.subtype !== "regexp" && parameter.subtype !== "node" && parameter.subtype !== "error"); |
| listItemElement.classList.add("console-message-extra-parameter"); |
| listItemElement.appendChild(this._formatParameter(parameter, forceObjectFormat)); |
| } |
| } |
| |
| _appendStackTrace() |
| { |
| if (!this._shouldShowStackTrace()) |
| return; |
| |
| this._makeExpandable(); |
| |
| // Auto-expand for console.trace. |
| if (this._message.type === WebInspector.ConsoleMessage.MessageType.Trace) |
| this.expand(); |
| |
| this._stackTraceElement = this._element.appendChild(document.createElement("div")); |
| this._stackTraceElement.classList.add("console-message-text", "console-message-stack-trace-container"); |
| |
| var callFramesElement = new WebInspector.StackTraceView(this._message.stackTrace).element; |
| this._stackTraceElement.appendChild(callFramesElement); |
| } |
| |
| _createRemoteObjectIfNeeded(parameter) |
| { |
| // FIXME: Only pass RemoteObjects here so we can avoid this work. |
| if (parameter instanceof WebInspector.RemoteObject) |
| return parameter; |
| |
| if (typeof parameter === "object") |
| return WebInspector.RemoteObject.fromPayload(parameter, this._message.target); |
| |
| return WebInspector.RemoteObject.fromPrimitiveValue(parameter); |
| } |
| |
| _appendFormattedArguments(element, parameters) |
| { |
| if (!parameters.length) |
| return; |
| |
| for (var i = 0; i < parameters.length; ++i) |
| parameters[i] = this._createRemoteObjectIfNeeded(parameters[i]); |
| |
| var builderElement = element.appendChild(document.createElement("span")); |
| var shouldFormatWithStringSubstitution = WebInspector.RemoteObject.type(parameters[0]) === "string" && this._message.type !== WebInspector.ConsoleMessage.MessageType.Result; |
| |
| // Single object (e.g. console result or logging a non-string object). |
| if (parameters.length === 1 && !shouldFormatWithStringSubstitution) { |
| this._extraParameters = null; |
| builderElement.appendChild(this._formatParameter(parameters[0], false)); |
| return; |
| } |
| |
| console.assert(this._message.type !== WebInspector.ConsoleMessage.MessageType.Result); |
| |
| if (shouldFormatWithStringSubstitution && this._isStackTrace(parameters[0])) |
| shouldFormatWithStringSubstitution = false; |
| |
| // Format string / message / default message. |
| if (shouldFormatWithStringSubstitution) { |
| var result = this._formatWithSubstitutionString(parameters, builderElement); |
| parameters = result.unusedSubstitutions; |
| this._extraParameters = parameters; |
| } else { |
| var defaultMessage = WebInspector.UIString("No message"); |
| builderElement.append(defaultMessage); |
| } |
| |
| // Trailing parameters. |
| if (parameters.length) { |
| let enclosedElement = document.createElement("span"); |
| |
| if (parameters.length === 1 && !this._isStackTrace(parameters[0])) { |
| let parameter = parameters[0]; |
| |
| // Single object. Show a preview. |
| builderElement.append(enclosedElement); |
| enclosedElement.classList.add("console-message-preview-divider"); |
| enclosedElement.textContent = " \u2013 "; |
| |
| var previewContainer = builderElement.appendChild(document.createElement("span")); |
| previewContainer.classList.add("console-message-preview"); |
| |
| var preview = WebInspector.FormattedValue.createObjectPreviewOrFormattedValueForRemoteObject(parameter, WebInspector.ObjectPreviewView.Mode.Brief); |
| var isPreviewView = preview instanceof WebInspector.ObjectPreviewView; |
| |
| if (isPreviewView) |
| preview.setOriginatingObjectInfo(parameter, null); |
| |
| var previewElement = isPreviewView ? preview.element : preview; |
| previewContainer.appendChild(previewElement); |
| |
| // If this preview is effectively lossless, we can avoid making this console message expandable. |
| if ((isPreviewView && preview.lossless) || (!isPreviewView && this._shouldConsiderObjectLossless(parameter))) { |
| this._extraParameters = null; |
| enclosedElement.classList.add("inline-lossless"); |
| previewContainer.classList.add("inline-lossless"); |
| } |
| } else { |
| // Multiple objects. Show an indicator. |
| builderElement.append(" ", enclosedElement); |
| enclosedElement.classList.add("console-message-enclosed"); |
| enclosedElement.textContent = "(" + parameters.length + ")"; |
| } |
| } |
| } |
| |
| _isStackTrace(parameter) |
| { |
| console.assert(parameter instanceof WebInspector.RemoteObject); |
| |
| if (WebInspector.RemoteObject.type(parameter) !== "string") |
| return false; |
| |
| return WebInspector.StackTrace.isLikelyStackTrace(parameter.description); |
| } |
| |
| _shouldConsiderObjectLossless(object) |
| { |
| if (object.type === "string") { |
| const description = object.description; |
| const maxLength = WebInspector.FormattedValue.MAX_PREVIEW_STRING_LENGTH; |
| const longOrMultiLineString = description.length > maxLength || description.slice(0, maxLength).includes("\n"); |
| return !longOrMultiLineString; |
| } |
| |
| return object.type !== "object" || object.subtype === "null" || object.subtype === "regexp"; |
| } |
| |
| _formatParameter(parameter, forceObjectFormat) |
| { |
| var type; |
| if (forceObjectFormat) |
| type = "object"; |
| else if (parameter instanceof WebInspector.RemoteObject) |
| type = parameter.subtype || parameter.type; |
| else { |
| console.assert(false, "no longer reachable"); |
| type = typeof parameter; |
| } |
| |
| var formatters = { |
| "object": this._formatParameterAsObject, |
| "error": this._formatParameterAsError, |
| "map": this._formatParameterAsObject, |
| "set": this._formatParameterAsObject, |
| "weakmap": this._formatParameterAsObject, |
| "weakset": this._formatParameterAsObject, |
| "iterator": this._formatParameterAsObject, |
| "class": this._formatParameterAsObject, |
| "proxy": this._formatParameterAsObject, |
| "array": this._formatParameterAsArray, |
| "node": this._formatParameterAsNode, |
| "string": this._formatParameterAsString, |
| }; |
| |
| var formatter = formatters[type] || this._formatParameterAsValue; |
| |
| const fragment = document.createDocumentFragment(); |
| formatter.call(this, parameter, fragment, forceObjectFormat); |
| return fragment; |
| } |
| |
| _formatParameterAsValue(value, fragment) |
| { |
| fragment.appendChild(WebInspector.FormattedValue.createElementForRemoteObject(value)); |
| } |
| |
| _formatParameterAsString(object, fragment) |
| { |
| if (this._isStackTrace(object)) { |
| let stackTrace = WebInspector.StackTrace.fromString(object.description); |
| if (stackTrace.callFrames.length) { |
| let stackView = new WebInspector.StackTraceView(stackTrace); |
| fragment.appendChild(stackView.element); |
| return; |
| } |
| } |
| |
| fragment.appendChild(WebInspector.FormattedValue.createLinkifiedElementString(object.description)); |
| } |
| |
| _formatParameterAsNode(object, fragment) |
| { |
| fragment.appendChild(WebInspector.FormattedValue.createElementForNode(object)); |
| } |
| |
| _formatParameterAsObject(object, fragment, forceExpansion) |
| { |
| // FIXME: Should have a better ObjectTreeView mode for classes (static methods and methods). |
| this._objectTree = new WebInspector.ObjectTreeView(object, null, this._rootPropertyPathForObject(object), forceExpansion); |
| fragment.appendChild(this._objectTree.element); |
| } |
| |
| _formatParameterAsError(object, fragment) |
| { |
| this._objectTree = new WebInspector.ErrorObjectView(object); |
| fragment.appendChild(this._objectTree.element); |
| } |
| |
| _formatParameterAsArray(array, fragment) |
| { |
| this._objectTree = new WebInspector.ObjectTreeView(array, WebInspector.ObjectTreeView.Mode.Properties, this._rootPropertyPathForObject(array)); |
| fragment.appendChild(this._objectTree.element); |
| } |
| |
| _rootPropertyPathForObject(object) |
| { |
| if (!this._message.savedResultIndex) |
| return null; |
| |
| return new WebInspector.PropertyPath(object, "$" + this._message.savedResultIndex); |
| } |
| |
| _formatWithSubstitutionString(parameters, formattedResult) |
| { |
| function parameterFormatter(force, obj) |
| { |
| return this._formatParameter(obj, force); |
| } |
| |
| function stringFormatter(obj) |
| { |
| return obj.description; |
| } |
| |
| function floatFormatter(obj, token) |
| { |
| let value = typeof obj.value === "number" ? obj.value : obj.description; |
| return String.standardFormatters.f(value, token); |
| } |
| |
| function integerFormatter(obj) |
| { |
| let value = typeof obj.value === "number" ? obj.value : obj.description; |
| return String.standardFormatters.d(value); |
| } |
| |
| var currentStyle = null; |
| function styleFormatter(obj) |
| { |
| currentStyle = {}; |
| var buffer = document.createElement("span"); |
| buffer.setAttribute("style", obj.description); |
| for (var i = 0; i < buffer.style.length; i++) { |
| var property = buffer.style[i]; |
| if (isWhitelistedProperty(property)) |
| currentStyle[property] = buffer.style[property]; |
| } |
| } |
| |
| function isWhitelistedProperty(property) |
| { |
| for (var prefix of ["background", "border", "color", "font", "line", "margin", "padding", "text"]) { |
| if (property.startsWith(prefix) || property.startsWith("-webkit-" + prefix)) |
| return true; |
| } |
| return false; |
| } |
| |
| // Firebug uses %o for formatting objects. |
| var formatters = {}; |
| formatters.o = parameterFormatter.bind(this, false); |
| formatters.s = stringFormatter; |
| formatters.f = floatFormatter; |
| |
| // Firebug allows both %i and %d for formatting integers. |
| formatters.i = integerFormatter; |
| formatters.d = integerFormatter; |
| |
| // Firebug uses %c for styling the message. |
| formatters.c = styleFormatter; |
| |
| // Support %O to force object formatting, instead of the type-based %o formatting. |
| formatters.O = parameterFormatter.bind(this, true); |
| |
| function append(a, b) |
| { |
| if (b instanceof Node) |
| a.appendChild(b); |
| else if (b !== undefined) { |
| var toAppend = WebInspector.linkifyStringAsFragment(b.toString()); |
| if (currentStyle) { |
| var wrapper = document.createElement("span"); |
| for (var key in currentStyle) |
| wrapper.style[key] = currentStyle[key]; |
| wrapper.appendChild(toAppend); |
| toAppend = wrapper; |
| } |
| |
| a.appendChild(toAppend); |
| } |
| 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); |
| } |
| |
| _shouldShowStackTrace() |
| { |
| if (!this._message.stackTrace.callFrames.length) |
| return false; |
| |
| return this._message.source === WebInspector.ConsoleMessage.MessageSource.Network |
| || this._message.level === WebInspector.ConsoleMessage.MessageLevel.Error |
| || this._message.type === WebInspector.ConsoleMessage.MessageType.Trace; |
| } |
| |
| _shouldHideURL(url) |
| { |
| return url === "undefined" || url === "[native code]"; |
| } |
| |
| _linkifyLocation(url, lineNumber, columnNumber) |
| { |
| return WebInspector.linkifyLocation(url, lineNumber, columnNumber, "console-message-url"); |
| } |
| |
| _userProvidedColumnNames(columnNamesArgument) |
| { |
| if (!columnNamesArgument) |
| return null; |
| |
| console.assert(columnNamesArgument instanceof WebInspector.RemoteObject); |
| |
| // Single primitive argument. |
| if (columnNamesArgument.type === "string" || columnNamesArgument.type === "number") |
| return [String(columnNamesArgument.value)]; |
| |
| // Ignore everything that is not an array with property previews. |
| if (columnNamesArgument.type !== "object" || columnNamesArgument.subtype !== "array" || !columnNamesArgument.preview || !columnNamesArgument.preview.propertyPreviews) |
| return null; |
| |
| // Array. Look into the preview and get string values. |
| var extractedColumnNames = []; |
| for (var propertyPreview of columnNamesArgument.preview.propertyPreviews) { |
| if (propertyPreview.type === "string" || propertyPreview.type === "number") |
| extractedColumnNames.push(String(propertyPreview.value)); |
| } |
| |
| return extractedColumnNames.length ? extractedColumnNames : null; |
| } |
| |
| _formatParameterAsTable(parameters) |
| { |
| var element = document.createElement("span"); |
| var table = parameters[0]; |
| if (!table || !table.preview) |
| return element; |
| |
| var rows = []; |
| var columnNames = []; |
| var flatValues = []; |
| var preview = table.preview; |
| var userProvidedColumnNames = false; |
| |
| // User provided columnNames. |
| var extractedColumnNames = this._userProvidedColumnNames(parameters[1]); |
| if (extractedColumnNames) { |
| userProvidedColumnNames = true; |
| columnNames = extractedColumnNames; |
| } |
| |
| // Check first for valuePreviews in the properties meaning this was an array of objects. |
| if (preview.propertyPreviews) { |
| for (var i = 0; i < preview.propertyPreviews.length; ++i) { |
| var rowProperty = preview.propertyPreviews[i]; |
| var rowPreview = rowProperty.valuePreview; |
| if (!rowPreview || !rowPreview.propertyPreviews) |
| continue; |
| |
| var rowValue = {}; |
| var maxColumnsToRender = 10; |
| for (var j = 0; j < rowPreview.propertyPreviews.length; ++j) { |
| var cellProperty = rowPreview.propertyPreviews[j]; |
| var columnRendered = columnNames.includes(cellProperty.name); |
| if (!columnRendered) { |
| if (userProvidedColumnNames || columnNames.length === maxColumnsToRender) |
| continue; |
| columnRendered = true; |
| columnNames.push(cellProperty.name); |
| } |
| |
| rowValue[cellProperty.name] = WebInspector.FormattedValue.createElementForPropertyPreview(cellProperty); |
| } |
| rows.push([rowProperty.name, rowValue]); |
| } |
| } |
| |
| // If there were valuePreviews, convert to a flat list. |
| if (rows.length) { |
| columnNames.unshift(WebInspector.UIString("(Index)")); |
| for (var i = 0; i < rows.length; ++i) { |
| var rowName = rows[i][0]; |
| var rowValue = rows[i][1]; |
| flatValues.push(rowName); |
| for (var j = 1; j < columnNames.length; ++j) { |
| var columnName = columnNames[j]; |
| if (!(columnName in rowValue)) |
| flatValues.push(emDash); |
| else |
| flatValues.push(rowValue[columnName]); |
| } |
| } |
| } |
| |
| // If there were no value Previews, then check for an array of values. |
| if (!flatValues.length && preview.propertyPreviews) { |
| for (var i = 0; i < preview.propertyPreviews.length; ++i) { |
| var rowProperty = preview.propertyPreviews[i]; |
| if (!("value" in rowProperty)) |
| continue; |
| |
| if (!columnNames.length) { |
| columnNames.push(WebInspector.UIString("Index")); |
| columnNames.push(WebInspector.UIString("Value")); |
| } |
| |
| flatValues.push(rowProperty.name); |
| flatValues.push(WebInspector.FormattedValue.createElementForPropertyPreview(rowProperty)); |
| } |
| } |
| |
| // If no table data show nothing. |
| if (!flatValues.length) |
| return element; |
| |
| // FIXME: Should we output something extra if the preview is lossless? |
| |
| var dataGrid = WebInspector.DataGrid.createSortableDataGrid(columnNames, flatValues); |
| dataGrid.inline = true; |
| dataGrid.variableHeightRows = true; |
| |
| element.appendChild(dataGrid.element); |
| |
| dataGrid.updateLayoutIfNeeded(); |
| |
| return element; |
| } |
| |
| _levelString() |
| { |
| switch (this._message.level) { |
| case WebInspector.ConsoleMessage.MessageLevel.Log: |
| return "Log"; |
| case WebInspector.ConsoleMessage.MessageLevel.Info: |
| return "Info"; |
| case WebInspector.ConsoleMessage.MessageLevel.Warning: |
| return "Warning"; |
| case WebInspector.ConsoleMessage.MessageLevel.Debug: |
| return "Debug"; |
| case WebInspector.ConsoleMessage.MessageLevel.Error: |
| return "Error"; |
| } |
| } |
| |
| _enforcesClipboardPrefixString() |
| { |
| return this._message.type !== WebInspector.ConsoleMessage.MessageType.Result; |
| } |
| |
| _clipboardPrefixString() |
| { |
| if (this._message.type === WebInspector.ConsoleMessage.MessageType.Result) |
| return "< "; |
| |
| return "[" + this._levelString() + "] "; |
| } |
| |
| _makeExpandable() |
| { |
| if (this._expandable) |
| return; |
| |
| this._expandable = true; |
| |
| this._element.classList.add("expandable"); |
| |
| this._boundClickHandler = this.toggle.bind(this); |
| this._messageTextElement.addEventListener("click", this._boundClickHandler); |
| } |
| }; |