summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Views/ConsoleMessageView.js
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/ConsoleMessageView.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Views/ConsoleMessageView.js871
1 files changed, 871 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/ConsoleMessageView.js b/Source/WebInspectorUI/UserInterface/Views/ConsoleMessageView.js
new file mode 100644
index 000000000..0fa66f610
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Views/ConsoleMessageView.js
@@ -0,0 +1,871 @@
+/*
+ * 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(this._message.target, {
+ 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(this._message.target, 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 = 15;
+ 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);
+ }
+};