bb4fe494fedf13ccdc01575502ee1f8755d88dd6
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / ConsoleMessageView.js
1 /*
2  * Copyright (C) 2011 Google Inc.  All rights reserved.
3  * Copyright (C) 2007, 2008, 2013-2015 Apple Inc.  All rights reserved.
4  * Copyright (C) 2009 Joseph Pecoraro
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 WebInspector.ConsoleMessageView = class ConsoleMessageView extends WebInspector.Object
32 {
33     constructor(message)
34     {
35         super();
36
37         console.assert(message instanceof WebInspector.ConsoleMessage);
38
39         this._message = message;
40
41         this._element = document.createElement("div");
42         this._element.classList.add("console-message");
43
44         // FIXME: <https://webkit.org/b/143545> Web Inspector: LogContentView should use higher level objects
45         this._element.__message = this._message;
46         this._element.__messageView = this;
47
48         switch (this._message.level) {
49         case WebInspector.ConsoleMessage.MessageLevel.Log:
50             this._element.classList.add("console-log-level");
51             this._element.setAttribute("data-labelprefix", WebInspector.UIString("Log: "));
52             break;
53         case WebInspector.ConsoleMessage.MessageLevel.Debug:
54             this._element.classList.add("console-debug-level");
55             this._element.setAttribute("data-labelprefix", WebInspector.UIString("Debug: "));
56             break;
57         case WebInspector.ConsoleMessage.MessageLevel.Warning:
58             this._element.classList.add("console-warning-level");
59             this._element.setAttribute("data-labelprefix", WebInspector.UIString("Warning: "));
60             break;
61         case WebInspector.ConsoleMessage.MessageLevel.Error:
62             this._element.classList.add("console-error-level");
63             this._element.setAttribute("data-labelprefix", WebInspector.UIString("Error: "));
64             break;
65         }
66
67         if (this._message.type === WebInspector.ConsoleMessage.MessageType.Result) {
68             this._element.classList.add("console-user-command-result");
69             if (!this._element.getAttribute("data-labelprefix"))
70                 this._element.setAttribute("data-labelprefix", WebInspector.UIString("Output: "));
71         }
72
73         if (this._message.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this._message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
74             this._element.classList.add("console-group-title");
75
76         // These are the parameters unused by the messages's optional format string.
77         // Any extra parameters will be displayed as children of this message.
78         this._extraParameters = this._message.parameters;
79
80         // FIXME: The location link should include stack trace information.
81         this._appendLocationLink();
82
83         this._messageTextElement = this._element.appendChild(document.createElement("span"));
84         this._messageTextElement.classList.add("console-top-level-message");
85         this._messageTextElement.classList.add("console-message-text");
86         this._appendMessageTextAndArguments(this._messageTextElement);
87         this._appendSavedResultIndex();
88
89         this._appendExtraParameters();
90         this._appendStackTrace();
91
92         this.repeatCount = this._message.repeatCount;
93     }
94
95     // Public
96
97     get element()
98     {
99         return this._element;
100     }
101
102     get message()
103     {
104         return this._message;
105     }
106
107     get repeatCount()
108     {
109         return this._repeatCount;
110     }
111
112     set repeatCount(count)
113     {
114         console.assert(typeof count === "number");
115
116         if (this._repeatCount === count)
117             return;
118
119         this._repeatCount = count;
120
121         if (count <= 1) {
122             if (this._repeatCountElement) {
123                 this._repeatCountElement.remove();
124                 this._repeatCountElement = null;
125             }
126             return;
127         }
128
129         if (!this._repeatCountElement) {
130             this._repeatCountElement = document.createElement("span");
131             this._repeatCountElement.classList.add("repeat-count");
132             this._element.insertBefore(this._repeatCountElement, this._element.firstChild);
133         }
134
135         this._repeatCountElement.textContent = count;
136     }
137
138     get expandable()
139     {
140         return this._element.classList.contains("expandable");
141     }
142
143     set expandable(x)
144     {
145         if (x === this.expandable)
146             return;
147
148         if (!this._boundClickHandler)
149             this._boundClickHandler = this.toggle.bind(this);
150
151         var becameExpandable = this._element.classList.toggle("expandable", x);
152
153         if (becameExpandable)
154             this._messageTextElement.addEventListener("click", this._boundClickHandler);
155         else
156             this._messageTextElement.removeEventListener("click", this._boundClickHandler);
157     }
158
159     expand()
160     {
161         this._element.classList.add("expanded");
162
163         // Auto-expand an inner object tree if there is a single object.
164         if (this._objectTree && this._extraParameters.length === 1)
165             this._objectTree.expand();
166     }
167
168     collapse()
169     {
170         this._element.classList.remove("expanded");
171     }
172
173     toggle()
174     {
175         if (this._element.classList.contains("expanded"))
176             this.collapse();
177         else
178             this.expand();
179     }
180
181     toClipboardString(isPrefixOptional)
182     {
183         var clipboardString = this._messageTextElement.innerText;
184
185         if (this._message.type === WebInspector.ConsoleMessage.MessageType.Trace)
186             clipboardString = "console.trace()";
187
188         var hasStackTrace = this._shouldShowStackTrace();
189         if (hasStackTrace) {
190             this._message.stackTrace.forEach(function(frame) {
191                 clipboardString += "\n\t" + (frame.functionName || WebInspector.UIString("(anonymous function)"));
192                 if (frame.url)
193                     clipboardString += " (" + WebInspector.displayNameForURL(frame.url) + ", line " + frame.lineNumber + ")";
194             });
195         } else {
196             var repeatString = this.repeatCount > 1 ? "x" + this.repeatCount : "";
197             var urlLine = "";
198             if (this._message.url) {
199                 var components = [WebInspector.displayNameForURL(this._message.url), "line " + this._message.line];
200                 if (repeatString)
201                     components.push(repeatString);
202                 urlLine = " (" + components.join(", ") + ")";
203             } else if (repeatString)
204                 urlLine = " (" + repeatString + ")";
205
206             if (urlLine) {
207                 var lines = clipboardString.split("\n");
208                 lines[0] += urlLine;
209                 clipboardString = lines.join("\n");
210             }
211         }
212
213         if (!isPrefixOptional || this._enforcesClipboardPrefixString())
214             return this._clipboardPrefixString() + clipboardString;
215         return clipboardString;
216     }
217
218     // Private
219
220     _appendMessageTextAndArguments(element)
221     {
222         if (this._message.source === WebInspector.ConsoleMessage.MessageSource.ConsoleAPI) {
223             switch (this._message.type) {
224             case WebInspector.ConsoleMessage.MessageType.Trace:
225                 // FIXME: We should use a better string then console.trace.
226                 element.appendChild(document.createTextNode("console.trace()"));
227                 break;
228
229             case WebInspector.ConsoleMessage.MessageType.Assert:
230                 var args = [WebInspector.UIString("Assertion failed:")];
231                 if (this._message.parameters)
232                     args.concat(this._message.parameters);
233                 this._appendFormattedArguments(element, args);
234                 break;
235
236             case WebInspector.ConsoleMessage.MessageType.Dir:
237                 var obj = this._message.parameters ? this._message.parameters[0] : undefined;
238                 this._appendFormattedArguments(element, ["%O", obj]);
239                 break;
240
241             case WebInspector.ConsoleMessage.MessageType.Table:
242                 // FIXME: Remove messageText?
243                 var args = this._message.parameters || [this._message.messageText];
244                 element.appendChild(this._formatParameterAsTable(args));
245                 break;
246
247             default:
248                 var args = this._message.parameters || [this._message.messageText];
249                 this._appendFormattedArguments(element, args);
250                 break;
251             }
252             return;
253         }
254
255         // FIXME: Better handle WebInspector.ConsoleMessage.MessageSource.Network.
256
257         var args = this._message.parameters || [this._message.messageText];
258         this._appendFormattedArguments(element, args);
259     }
260
261     _appendSavedResultIndex(element)
262     {
263         if (!this._message.savedResultIndex)
264             return;
265
266         console.assert(this._message instanceof WebInspector.ConsoleCommandResultMessage);
267         console.assert(this._message.type === WebInspector.ConsoleMessage.MessageType.Result);
268
269         var savedVariableElement = document.createElement("span");
270         savedVariableElement.classList.add("console-saved-variable");
271         savedVariableElement.textContent = " = $" + this._message.savedResultIndex;
272
273         if (this._objectTree)
274             this._objectTree.appendTitleSuffix(savedVariableElement);
275         else
276             this._messageTextElement.appendChild(savedVariableElement);
277     }
278
279     _appendLocationLink()
280     {
281         // FIXME: Better handle WebInspector.ConsoleMessage.MessageSource.Network.
282         if (this._message.source === WebInspector.ConsoleMessage.MessageSource.Network || this._message.request)
283             return;
284
285         var firstNonNativeCallFrame = this._firstNonNativeCallFrame();
286         if (firstNonNativeCallFrame) {
287             var urlElement = this._linkifyCallFrame(firstNonNativeCallFrame);
288             this._element.appendChild(urlElement);
289         } else if (this._message.url && !this._shouldHideURL(this._message.url)) {
290             var urlElement = this._linkifyLocation(this._message.url, this._message.line, this._message.column);
291             this._element.appendChild(urlElement);
292         }
293     }
294
295     _appendExtraParameters()
296     {
297         if (!this._extraParameters || !this._extraParameters.length)
298             return;
299
300         this.expandable = true;
301
302         // Auto-expand if there are multiple objects.
303         if (this._extraParameters.length > 1)
304             this.expand();
305
306         this._extraElementsList = this._element.appendChild(document.createElement("ol"));
307         this._extraElementsList.classList.add("console-message-extra-parameters-container");
308
309         for (var parameter of this._extraParameters) {
310             var listItemElement = this._extraElementsList.appendChild(document.createElement("li"));
311             var forceObjectExpansion = parameter.type === "object" && (parameter.subtype !== "null" && parameter.subtype !== "regexp" && parameter.subtype !== "node");
312             listItemElement.classList.add("console-message-extra-parameter");
313             listItemElement.appendChild(this._formatParameter(parameter, forceObjectExpansion));
314         }
315     }
316
317     _appendStackTrace()
318     {
319         if (!this._shouldShowStackTrace())
320             return;
321
322         this.expandable = true;
323
324         // Auto-expand for console.trace.
325         if (this._message.type === WebInspector.ConsoleMessage.MessageType.Trace)
326             this.expand();
327
328         this._stackTraceElement = this._element.appendChild(document.createElement("ul"));
329         this._stackTraceElement.classList.add("console-message-stack-trace-container");
330         this._stackTraceElement.classList.add("console-message-text");
331
332         for (var callFrame of this._message.stackTrace) {
333             var callFrameElement = this._stackTraceElement.appendChild(document.createElement("li"));
334             callFrameElement.classList.add("console-message-stack-trace-call-frame");
335             callFrameElement.textContent = callFrame.functionName || WebInspector.UIString("(anonymous function)");
336             if (callFrame.url && !this._shouldHideURL(callFrame.url))
337                 callFrameElement.appendChild(this._linkifyCallFrame(callFrame));
338         }
339     }
340
341     _appendFormattedArguments(element, parameters)
342     {
343         if (!parameters.length)
344             return;
345
346         // FIXME: Only pass RemoteObjects here so we can avoid this work.
347         for (var i = 0; i < parameters.length; ++i) {
348             if (parameters[i] instanceof WebInspector.RemoteObject)
349                 continue;
350
351             if (typeof parameters[i] === "object")
352                 parameters[i] = WebInspector.RemoteObject.fromPayload(parameters[i]);
353             else
354                 parameters[i] = WebInspector.RemoteObject.fromPrimitiveValue(parameters[i]);
355         }
356
357         var builderElement = element.appendChild(document.createElement("span"));
358         var shouldFormatWithStringSubstitution = WebInspector.RemoteObject.type(parameters[0]) === "string" && this._message.type !== WebInspector.ConsoleMessage.MessageType.Result;
359
360         // Single object (e.g. console result or logging a non-string object).
361         if (parameters.length === 1 && !shouldFormatWithStringSubstitution) {
362             this._extraParameters = null;
363             builderElement.appendChild(this._formatParameter(parameters[0], false));
364             return;
365         }
366
367         console.assert(this._message.type !== WebInspector.ConsoleMessage.MessageType.Result);
368
369         // Format string / message / default message.
370         if (shouldFormatWithStringSubstitution) {
371             var result = this._formatWithSubstitutionString(parameters, builderElement);
372             parameters = result.unusedSubstitutions;
373             this._extraParameters = parameters;
374         } else {
375             var defaultMessage = WebInspector.UIString("No message");
376             builderElement.appendChild(document.createTextNode(defaultMessage));
377         }
378
379         // Trailing parameters.
380         if (parameters.length) {
381             if (parameters.length === 1) {
382                 // Single object. Show a preview.
383                 var enclosedElement = builderElement.appendChild(document.createElement("span"));
384                 enclosedElement.classList.add("console-message-preview-divider");
385                 enclosedElement.textContent = " \u2013 ";
386
387                 var previewContainer = builderElement.appendChild(document.createElement("span"));
388                 previewContainer.classList.add("console-message-preview");
389
390                 var parameter = parameters[0];
391                 var preview = WebInspector.FormattedValue.createObjectPreviewOrFormattedValueForRemoteObject(parameter, WebInspector.ObjectPreviewView.Mode.Brief);
392                 var isPreviewView = preview instanceof WebInspector.ObjectPreviewView;
393
394                 if (isPreviewView)
395                     preview.setOriginatingObjectInfo(parameter, null);
396
397                 var previewElement = isPreviewView ? preview.element : preview;
398                 previewContainer.appendChild(previewElement);
399
400                 // If this preview is effectively lossless, we can avoid making this console message expandable.
401                 if ((isPreviewView && preview.lossless) || (!isPreviewView && this._shouldConsiderObjectLossless(parameter)))
402                     this._extraParameters = null;
403             } else {
404                 // Multiple objects. Show an indicator.
405                 builderElement.appendChild(document.createTextNode(" "));
406                 var enclosedElement = builderElement.appendChild(document.createElement("span"));
407                 enclosedElement.classList.add("console-message-enclosed");
408                 enclosedElement.textContent = "(" + parameters.length + ")";
409             }
410         }
411     }
412
413     _shouldConsiderObjectLossless(object)
414     {
415         return object.type !== "object" || object.subtype === "null" || object.subtype === "regexp";
416     }
417
418     _formatParameter(parameter, forceObjectFormat)
419     {
420         var type;
421         if (forceObjectFormat)
422             type = "object";
423         else if (parameter instanceof WebInspector.RemoteObject)
424             type = parameter.subtype || parameter.type;
425         else {
426             console.assert(false, "no longer reachable");
427             type = typeof parameter;
428         }
429
430         var formatters = {
431             "object": this._formatParameterAsObject,
432             "error": this._formatParameterAsObject,
433             "map": this._formatParameterAsObject,
434             "set": this._formatParameterAsObject,
435             "weakmap": this._formatParameterAsObject,
436             "weakset": this._formatParameterAsObject,
437             "iterator": this._formatParameterAsObject,
438             "class": this._formatParameterAsObject,
439             "array": this._formatParameterAsArray,
440             "node": this._formatParameterAsNode,
441             "string": this._formatParameterAsString,
442         };
443
444         var formatter = formatters[type] || this._formatParameterAsValue;
445
446         var span = document.createElement("span");
447         formatter.call(this, parameter, span, forceObjectFormat);
448         return span;
449     }
450
451     _formatParameterAsValue(value, element)
452     {
453         element.appendChild(WebInspector.FormattedValue.createElementForRemoteObject(value));
454     }
455
456     _formatParameterAsString(object, element)
457     {
458         element.appendChild(WebInspector.FormattedValue.createLinkifiedElementString(object.description));
459     }
460
461     _formatParameterAsNode(object, element)
462     {
463         element.appendChild(WebInspector.FormattedValue.createElementForNode(object));
464     }
465
466     _formatParameterAsObject(object, element, forceExpansion)
467     {
468         // FIXME: Should have a better ObjectTreeView mode for classes (static methods and methods).
469         // FIXME: Only need to assign to objectTree if this is a ConsoleMessageResult. We should assert that.
470         this._objectTree = new WebInspector.ObjectTreeView(object, null, this._rootPropertyPathForObject(object), forceExpansion);
471         element.appendChild(this._objectTree.element);
472     }
473
474     _formatParameterAsArray(array, element)
475     {
476         this._objectTree = new WebInspector.ObjectTreeView(array, WebInspector.ObjectTreeView.Mode.Properties, this._rootPropertyPathForObject(array));
477         element.appendChild(this._objectTree.element);
478     }
479
480     _rootPropertyPathForObject(object)
481     {
482         if (!this._message.savedResultIndex)
483             return null;
484
485         return new WebInspector.PropertyPath(object, "$" + this._message.savedResultIndex);
486     }
487
488     _formatWithSubstitutionString(parameters, formattedResult)
489     {
490         function parameterFormatter(force, obj)
491         {
492             return this._formatParameter(obj, force);
493         }
494
495         function stringFormatter(obj)
496         {
497             return obj.description;
498         }
499
500         function floatFormatter(obj)
501         {
502             if (typeof obj.value !== "number")
503                 return parseFloat(obj.description);
504             return obj.value;
505         }
506
507         function integerFormatter(obj)
508         {
509             if (typeof obj.value !== "number")
510                 return parseInt(obj.description);
511             return Math.floor(obj.value);
512         }
513
514         var currentStyle = null;
515         function styleFormatter(obj)
516         {
517             currentStyle = {};
518             var buffer = document.createElement("span");
519             buffer.setAttribute("style", obj.description);
520             for (var i = 0; i < buffer.style.length; i++) {
521                 var property = buffer.style[i];
522                 if (isWhitelistedProperty(property))
523                     currentStyle[property] = buffer.style[property];
524             }
525         }
526
527         function isWhitelistedProperty(property)
528         {
529             for (var prefix of ["background", "border", "color", "font", "line", "margin", "padding", "text"]) {
530                 if (property.startsWith(prefix) || property.startsWith("-webkit-" + prefix))
531                     return true;
532             }
533             return false;
534         }
535
536         // Firebug uses %o for formatting objects.
537         var formatters = {};
538         formatters.o = parameterFormatter.bind(this, false);
539         formatters.s = stringFormatter;
540         formatters.f = floatFormatter;
541
542         // Firebug allows both %i and %d for formatting integers.
543         formatters.i = integerFormatter;
544         formatters.d = integerFormatter;
545
546         // Firebug uses %c for styling the message.
547         formatters.c = styleFormatter;
548
549         // Support %O to force object formatting, instead of the type-based %o formatting.
550         formatters.O = parameterFormatter.bind(this, true);
551
552         function append(a, b)
553         {
554             if (b instanceof Node)
555                 a.appendChild(b);
556             else if (b) {
557                 var toAppend = WebInspector.linkifyStringAsFragment(b.toString());
558                 if (currentStyle) {
559                     var wrapper = document.createElement("span");
560                     for (var key in currentStyle)
561                         wrapper.style[key] = currentStyle[key];
562                     wrapper.appendChild(toAppend);
563                     toAppend = wrapper;
564                 }
565
566                 var span = document.createElement("span");
567                 span.className = "type-string";
568                 span.appendChild(toAppend);
569                 a.appendChild(span);
570             }
571             return a;
572         }
573
574         // String.format does treat formattedResult like a Builder, result is an object.
575         return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append);
576     }
577
578     _shouldShowStackTrace()
579     {
580         if (!this._message.stackTrace || !this._message.stackTrace.length)
581             return false;
582
583         return this._message.source === WebInspector.ConsoleMessage.MessageSource.Network
584             || this._message.level === WebInspector.ConsoleMessage.MessageLevel.Error
585             || this._message.type === WebInspector.ConsoleMessage.MessageType.Trace;
586     }
587
588     _shouldHideURL(url)
589     {
590         return url === "undefined" || url === "[native code]";
591     }
592
593     _firstNonNativeCallFrame()
594     {
595         if (!this._message.stackTrace)
596             return null;
597
598         for (var i = 0; i < this._message.stackTrace.length; i++) {
599             var frame = this._message.stackTrace[i];
600             if (!frame.url || frame.url === "[native code]")
601                 continue;
602             return frame;
603         }
604
605         return null;
606     }
607
608     _linkifyLocation(url, lineNumber, columnNumber)
609     {
610         // ConsoleMessage stack trace line numbers are one-based.
611         lineNumber = lineNumber ? lineNumber - 1 : 0;
612         columnNumber = columnNumber ? columnNumber - 1 : 0;
613         return WebInspector.linkifyLocation(url, lineNumber, columnNumber, "console-message-url");
614     }
615
616     _linkifyCallFrame(callFrame)
617     {
618         return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
619     }
620
621     _userProvidedColumnNames(columnNamesArgument)
622     {
623         if (!columnNamesArgument)
624             return null;
625
626         console.assert(columnNamesArgument instanceof WebInspector.RemoteObject);
627
628         // Single primitive argument.
629         if (columnNamesArgument.type === "string" || columnNamesArgument.type === "number")
630             return [String(columnNamesArgument.value)];
631
632         // Ignore everything that is not an array with property previews.
633         if (columnNamesArgument.type !== "object" || columnNamesArgument.subtype !== "array" || !columnNamesArgument.preview || !columnNamesArgument.preview.propertyPreviews)
634             return null;
635
636         // Array. Look into the preview and get string values.
637         var extractedColumnNames = [];
638         for (var propertyPreview of columnNamesArgument.preview.propertyPreviews) {
639             if (propertyPreview.type === "string" || propertyPreview.type === "number")
640                 extractedColumnNames.push(String(propertyPreview.value));
641         }
642
643         return extractedColumnNames.length ? extractedColumnNames : null;
644     }
645
646     _formatParameterAsTable(parameters)
647     {
648         var element = document.createElement("span");
649         var table = parameters[0];
650         if (!table || !table.preview)
651             return element;
652
653         var rows = [];
654         var columnNames = [];
655         var flatValues = [];
656         var preview = table.preview;
657         var userProvidedColumnNames = false;
658
659         // User provided columnNames.
660         var extractedColumnNames = this._userProvidedColumnNames(parameters[1]);
661         if (extractedColumnNames) {
662             userProvidedColumnNames = true;
663             columnNames = extractedColumnNames;
664         }
665
666         // Check first for valuePreviews in the properties meaning this was an array of objects.
667         if (preview.propertyPreviews) {
668             for (var i = 0; i < preview.propertyPreviews.length; ++i) {
669                 var rowProperty = preview.propertyPreviews[i];
670                 var rowPreview = rowProperty.valuePreview;
671                 if (!rowPreview)
672                     continue;
673
674                 var rowValue = {};
675                 var maxColumnsToRender = 10;
676                 for (var j = 0; j < rowPreview.propertyPreviews.length; ++j) {
677                     var cellProperty = rowPreview.propertyPreviews[j];
678                     var columnRendered = columnNames.includes(cellProperty.name);
679                     if (!columnRendered) {
680                         if (userProvidedColumnNames || columnNames.length === maxColumnsToRender)
681                             continue;
682                         columnRendered = true;
683                         columnNames.push(cellProperty.name);
684                     }
685
686                     rowValue[cellProperty.name] = WebInspector.FormattedValue.createElementForPropertyPreview(cellProperty);
687                 }
688                 rows.push([rowProperty.name, rowValue]);
689             }
690         }
691
692         // If there were valuePreviews, convert to a flat list.
693         if (rows.length) {
694             var emDash = "\u2014";
695             columnNames.unshift(WebInspector.UIString("(Index)"));
696             for (var i = 0; i < rows.length; ++i) {
697                 var rowName = rows[i][0];
698                 var rowValue = rows[i][1];
699                 flatValues.push(rowName);
700                 for (var j = 1; j < columnNames.length; ++j) {
701                     var columnName = columnNames[j];
702                     if (!(columnName in rowValue))
703                         flatValues.push(emDash);
704                     else
705                         flatValues.push(rowValue[columnName]);
706                 }
707             }
708         }
709
710         // If there were no value Previews, then check for an array of values.
711         if (!flatValues.length && preview.propertyPreviews) {
712             for (var i = 0; i < preview.propertyPreviews.length; ++i) {
713                 var rowProperty = preview.propertyPreviews[i];
714                 if (!("value" in rowProperty))
715                     continue;
716
717                 if (!columnNames.length) {
718                     columnNames.push(WebInspector.UIString("Index"));
719                     columnNames.push(WebInspector.UIString("Value"));
720                 }
721
722                 flatValues.push(rowProperty.name);
723                 flatValues.push(WebInspector.FormattedValue.createElementForPropertyPreview(rowProperty));
724             }
725         }
726
727         // If lossless or not table data, output the object so full data can be gotten.
728         if (!preview.lossless || !flatValues.length) {
729             element.appendChild(this._formatParameter(table));
730             if (!flatValues.length)
731                 return element;
732         }
733
734         var dataGrid = WebInspector.DataGrid.createSortableDataGrid(columnNames, flatValues);
735         dataGrid.element.classList.add("inline");
736         element.appendChild(dataGrid.element);
737
738         return element;
739     }
740
741     _levelString()
742     {
743         switch (this._message.level) {
744         case WebInspector.ConsoleMessage.MessageLevel.Log:
745             return "Log";
746         case WebInspector.ConsoleMessage.MessageLevel.Warning:
747             return "Warning";
748         case WebInspector.ConsoleMessage.MessageLevel.Debug:
749             return "Debug";
750         case WebInspector.ConsoleMessage.MessageLevel.Error:
751             return "Error";
752         }
753     }
754
755     _enforcesClipboardPrefixString()
756     {
757         return this._message.type !== WebInspector.ConsoleMessage.MessageType.Result;
758     }
759
760     _clipboardPrefixString()
761     {
762         if (this._message.type === WebInspector.ConsoleMessage.MessageType.Result)
763             return "< ";
764
765         return "[" + this._levelString() + "] ";
766     }
767 };