Upstream version 9.37.195.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / console / ConsoleViewMessage.js
1 /*
2  * Copyright (C) 2011 Google Inc.  All rights reserved.
3  * Copyright (C) 2007, 2008 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 Computer, 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 /**
32  * @constructor
33  * @implements {WebInspector.ViewportElement}
34  * @param {!WebInspector.ConsoleMessage} consoleMessage
35  * @param {?WebInspector.Linkifier} linkifier
36  * @param {number} nestingLevel
37  */
38 WebInspector.ConsoleViewMessage = function(consoleMessage, linkifier, nestingLevel)
39 {
40     this._message = consoleMessage;
41     this._linkifier = linkifier;
42     this._repeatCount = 1;
43     this._closeGroupDecorationCount = 0;
44     this._nestingLevel = nestingLevel;
45
46     /** @type {!Array.<!WebInspector.DataGrid>} */
47     this._dataGrids = [];
48     /** @type {!Map.<!WebInspector.DataGrid, ?Element>} */
49     this._dataGridParents = new Map();
50
51     /** @type {!Object.<string, function(!WebInspector.RemoteObject, !Element, boolean=)>} */
52     this._customFormatters = {
53         "object": this._formatParameterAsObject,
54         "array":  this._formatParameterAsArray,
55         "node":   this._formatParameterAsNode,
56         "string": this._formatParameterAsString
57     };
58 }
59
60 WebInspector.ConsoleViewMessage.prototype = {
61     /**
62      * @return {?WebInspector.Target}
63      */
64     _target: function()
65     {
66         return this.consoleMessage().target();
67     },
68
69     /**
70      * @return {!Element}
71      */
72     element: function()
73     {
74         return this.toMessageElement();
75     },
76
77     wasShown: function()
78     {
79         for (var i = 0; this._dataGrids && i < this._dataGrids.length; ++i) {
80             var dataGrid = this._dataGrids[i];
81             var parentElement = this._dataGridParents.get(dataGrid) || null;
82             dataGrid.show(parentElement);
83             dataGrid.updateWidths();
84         }
85     },
86
87     cacheFastHeight: function()
88     {
89         this._cachedHeight = this.contentElement().offsetHeight;
90     },
91
92     willHide: function()
93     {
94         for (var i = 0; this._dataGrids && i < this._dataGrids.length; ++i) {
95             var dataGrid = this._dataGrids[i];
96             this._dataGridParents.put(dataGrid, dataGrid.element.parentElement);
97             dataGrid.detach();
98         }
99     },
100
101     /**
102      * @return {number}
103      */
104     fastHeight: function()
105     {
106         if (this._cachedHeight)
107             return this._cachedHeight;
108         const defaultConsoleRowHeight = 16;
109         if (this._message.type === WebInspector.ConsoleMessage.MessageType.Table) {
110             var table = this._message.parameters[0];
111             if (table && table.preview)
112                 return defaultConsoleRowHeight * table.preview.properties.length;
113         }
114         return defaultConsoleRowHeight;
115     },
116
117     /**
118      * @return {!WebInspector.ConsoleMessage}
119      */
120     consoleMessage: function()
121     {
122         return this._message;
123     },
124
125     _formatMessage: function()
126     {
127         this._formattedMessage = document.createElement("span");
128         this._formattedMessage.className = "console-message-text source-code";
129
130         /**
131          * @param {string} title
132          * @return {!Element}
133          * @this {WebInspector.ConsoleMessage}
134          */
135         function linkifyRequest(title)
136         {
137             return WebInspector.Linkifier.linkifyUsingRevealer(/** @type {!WebInspector.NetworkRequest} */ (this.request), title, this.url);
138         }
139
140         var consoleMessage = this._message;
141         if (!this._messageElement) {
142             if (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.ConsoleAPI) {
143                 switch (consoleMessage.type) {
144                     case WebInspector.ConsoleMessage.MessageType.Trace:
145                         this._messageElement = this._format(consoleMessage.parameters || ["console.trace()"]);
146                         break;
147                     case WebInspector.ConsoleMessage.MessageType.Clear:
148                         this._messageElement = document.createTextNode(WebInspector.UIString("Console was cleared"));
149                         this._formattedMessage.classList.add("console-info");
150                         break;
151                     case WebInspector.ConsoleMessage.MessageType.Assert:
152                         var args = [WebInspector.UIString("Assertion failed:")];
153                         if (consoleMessage.parameters)
154                             args = args.concat(consoleMessage.parameters);
155                         this._messageElement = this._format(args);
156                         break;
157                     case WebInspector.ConsoleMessage.MessageType.Dir:
158                         var obj = consoleMessage.parameters ? consoleMessage.parameters[0] : undefined;
159                         var args = ["%O", obj];
160                         this._messageElement = this._format(args);
161                         break;
162                     case WebInspector.ConsoleMessage.MessageType.Profile:
163                     case WebInspector.ConsoleMessage.MessageType.ProfileEnd:
164                         this._messageElement = this._format([consoleMessage.messageText]);
165                         break;
166                     default:
167                         var args = consoleMessage.parameters || [consoleMessage.messageText];
168                         this._messageElement = this._format(args);
169                 }
170             } else if (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.Network) {
171                 if (consoleMessage.request) {
172                     this._messageElement = document.createElement("span");
173                     if (consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.Error) {
174                         this._messageElement.appendChild(document.createTextNode(consoleMessage.request.requestMethod + " "));
175                         this._messageElement.appendChild(WebInspector.Linkifier.linkifyUsingRevealer(consoleMessage.request, consoleMessage.request.url, consoleMessage.request.url));
176                         if (consoleMessage.request.failed)
177                             this._messageElement.appendChild(document.createTextNode(" " + consoleMessage.request.localizedFailDescription));
178                         else
179                             this._messageElement.appendChild(document.createTextNode(" " + consoleMessage.request.statusCode + " (" + consoleMessage.request.statusText + ")"));
180                     } else {
181                         var fragment = WebInspector.linkifyStringAsFragmentWithCustomLinkifier(consoleMessage.messageText, linkifyRequest.bind(consoleMessage));
182                         this._messageElement.appendChild(fragment);
183                     }
184                 } else {
185                     var url = consoleMessage.url;
186                     if (url) {
187                         var isExternal = !WebInspector.resourceForURL(url) && !WebInspector.workspace.uiSourceCodeForURL(url);
188                         this._anchorElement = WebInspector.linkifyURLAsNode(url, url, "console-message-url", isExternal);
189                     }
190                     this._messageElement = this._format([consoleMessage.messageText]);
191                 }
192             } else {
193                 var args = consoleMessage.parameters || [consoleMessage.messageText];
194                 this._messageElement = this._format(args);
195             }
196         }
197
198         if (consoleMessage.source !== WebInspector.ConsoleMessage.MessageSource.Network || consoleMessage.request) {
199             var callFrame = this._callFrameAnchorFromStackTrace(consoleMessage.stackTrace);
200             if (callFrame)
201                 this._anchorElement = this._linkifyCallFrame(callFrame);
202             else if (consoleMessage.url && consoleMessage.url !== "undefined")
203                 this._anchorElement = this._linkifyLocation(consoleMessage.url, consoleMessage.line, consoleMessage.column);
204         }
205
206         this._formattedMessage.appendChild(this._messageElement);
207         if (this._anchorElement) {
208             this._formattedMessage.appendChild(document.createTextNode(" "));
209             this._formattedMessage.appendChild(this._anchorElement);
210         }
211
212         var dumpStackTrace = !!consoleMessage.stackTrace && consoleMessage.stackTrace.length && (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.Network || consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.Error || consoleMessage.type === WebInspector.ConsoleMessage.MessageType.Trace);
213         if (dumpStackTrace) {
214             var ol = document.createElement("ol");
215             ol.className = "outline-disclosure";
216             var treeOutline = new TreeOutline(ol);
217
218             var content = this._formattedMessage;
219             var root = new TreeElement(content, null, true);
220             content.treeElementForTest = root;
221             treeOutline.appendChild(root);
222             if (consoleMessage.type === WebInspector.ConsoleMessage.MessageType.Trace)
223                 root.expand();
224
225             this._populateStackTraceTreeElement(root);
226             this._formattedMessage = ol;
227         }
228     },
229
230     _formattedMessageText: function()
231     {
232         this.formattedMessage();
233         return this._messageElement.textContent;
234     },
235
236     /**
237      * @return {!Element}
238      */
239     formattedMessage: function()
240     {
241         if (!this._formattedMessage)
242             this._formatMessage();
243         return this._formattedMessage;
244     },
245
246     /**
247      * @param {string} url
248      * @param {number} lineNumber
249      * @param {number} columnNumber
250      * @return {?Element}
251      */
252     _linkifyLocation: function(url, lineNumber, columnNumber)
253     {
254         console.assert(this._linkifier);
255         var target = this._target();
256         if (!this._linkifier || !target)
257             return null;
258         // FIXME(62725): stack trace line/column numbers are one-based.
259         lineNumber = lineNumber ? lineNumber - 1 : 0;
260         columnNumber = columnNumber ? columnNumber - 1 : 0;
261         if (this._message.source === WebInspector.ConsoleMessage.MessageSource.CSS) {
262             var headerIds = target.cssModel.styleSheetIdsForURL(url);
263             var cssLocation = new WebInspector.CSSLocation(target, url, lineNumber, columnNumber);
264             return this._linkifier.linkifyCSSLocation(headerIds[0] || null, cssLocation, "console-message-url");
265         }
266
267         return this._linkifier.linkifyLocation(target, url, lineNumber, columnNumber, "console-message-url");
268     },
269
270     /**
271      * @param {!ConsoleAgent.CallFrame} callFrame
272      * @return {?Element}
273      */
274     _linkifyCallFrame: function(callFrame)
275     {
276         console.assert(this._linkifier);
277         var target = this._target();
278         if (!this._linkifier || !target)
279             return null;
280         // FIXME(62725): stack trace line/column numbers are one-based.
281         var lineNumber = callFrame.lineNumber ? callFrame.lineNumber - 1 : 0;
282         var columnNumber = callFrame.columnNumber ? callFrame.columnNumber - 1 : 0;
283         var rawLocation = new WebInspector.DebuggerModel.Location(target, callFrame.scriptId, lineNumber, columnNumber);
284         return this._linkifier.linkifyRawLocation(rawLocation, "console-message-url");
285     },
286
287     /**
288      * @param {?Array.<!ConsoleAgent.CallFrame>} stackTrace
289      * @return {?ConsoleAgent.CallFrame}
290      */
291     _callFrameAnchorFromStackTrace: function(stackTrace)
292     {
293         if (!stackTrace || !stackTrace.length)
294             return null;
295         var callFrame = stackTrace[0].scriptId ? stackTrace[0] : null;
296         if (!WebInspector.experimentsSettings.frameworksDebuggingSupport.isEnabled())
297             return callFrame;
298         if (!WebInspector.settings.skipStackFramesSwitch.get())
299             return callFrame;
300         var regex = WebInspector.settings.skipStackFramesPattern.asRegExp();
301         if (!regex)
302             return callFrame;
303         for (var i = 0; i < stackTrace.length; ++i) {
304             var script = this._target().debuggerModel.scriptForId(stackTrace[i].scriptId);
305             if (!script || !regex.test(script.sourceURL))
306                 return stackTrace[i].scriptId ? stackTrace[i] : null;
307         }
308         return callFrame;
309     },
310
311     /**
312      * @return {boolean}
313      */
314     isErrorOrWarning: function()
315     {
316         return (this._message.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this._message.level === WebInspector.ConsoleMessage.MessageLevel.Error);
317     },
318
319     _format: function(parameters)
320     {
321         // This node is used like a Builder. Values are continually appended onto it.
322         var formattedResult = document.createElement("span");
323         if (!parameters.length)
324             return formattedResult;
325
326         var target = this._target();
327
328         // Formatting code below assumes that parameters are all wrappers whereas frontend console
329         // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here.
330         for (var i = 0; i < parameters.length; ++i) {
331             // FIXME: Only pass runtime wrappers here.
332             if (parameters[i] instanceof WebInspector.RemoteObject)
333                 continue;
334
335             if (!target) {
336                 parameters[i] = WebInspector.RemoteObject.fromLocalObject(parameters[i]);
337                 continue;
338             }
339
340             if (typeof parameters[i] === "object")
341                 parameters[i] = target.runtimeModel.createRemoteObject(parameters[i]);
342             else
343                 parameters[i] = target.runtimeModel.createRemoteObjectFromPrimitiveValue(parameters[i]);
344         }
345
346         // There can be string log and string eval result. We distinguish between them based on message type.
347         var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && this._message.type !== WebInspector.ConsoleMessage.MessageType.Result;
348
349         // Multiple parameters with the first being a format string. Save unused substitutions.
350         if (shouldFormatMessage) {
351             // Multiple parameters with the first being a format string. Save unused substitutions.
352             var result = this._formatWithSubstitutionString(parameters[0].description, parameters.slice(1), formattedResult);
353             parameters = result.unusedSubstitutions;
354             if (parameters.length)
355                 formattedResult.appendChild(document.createTextNode(" "));
356         }
357
358         if (this._message.type === WebInspector.ConsoleMessage.MessageType.Table) {
359             formattedResult.appendChild(this._formatParameterAsTable(parameters));
360             return formattedResult;
361         }
362
363         // Single parameter, or unused substitutions from above.
364         for (var i = 0; i < parameters.length; ++i) {
365             // Inline strings when formatting.
366             if (shouldFormatMessage && parameters[i].type === "string")
367                 formattedResult.appendChild(WebInspector.linkifyStringAsFragment(parameters[i].description));
368             else
369                 formattedResult.appendChild(this._formatParameter(parameters[i], false, true));
370             if (i < parameters.length - 1)
371                 formattedResult.appendChild(document.createTextNode(" "));
372         }
373         return formattedResult;
374     },
375
376     /**
377      * @param {!WebInspector.RemoteObject} output
378      * @param {boolean=} forceObjectFormat
379      * @param {boolean=} includePreview
380      * @return {!Element}
381      */
382     _formatParameter: function(output, forceObjectFormat, includePreview)
383     {
384         var type = forceObjectFormat ? "object" : (output.subtype || output.type);
385         var formatter = this._customFormatters[type] || this._formatParameterAsValue;
386         var span = document.createElement("span");
387         span.className = "console-formatted-" + type + " source-code";
388         formatter.call(this, output, span, includePreview);
389         return span;
390     },
391
392     /**
393      * @param {!WebInspector.RemoteObject} obj
394      * @param {!Element} elem
395      */
396     _formatParameterAsValue: function(obj, elem)
397     {
398         elem.appendChild(document.createTextNode(obj.description || ""));
399         if (obj.objectId)
400             elem.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, obj), false);
401     },
402
403     /**
404      * @param {!WebInspector.RemoteObject} obj
405      * @param {!Element} elem
406      * @param {boolean=} includePreview
407      */
408     _formatParameterAsObject: function(obj, elem, includePreview)
409     {
410         this._formatParameterAsArrayOrObject(obj, obj.description || "", elem, includePreview);
411     },
412
413     /**
414      * @param {!WebInspector.RemoteObject} obj
415      * @param {string} description
416      * @param {!Element} elem
417      * @param {boolean=} includePreview
418      */
419     _formatParameterAsArrayOrObject: function(obj, description, elem, includePreview)
420     {
421         var titleElement = document.createElement("span");
422         if (description)
423             titleElement.createTextChild(description);
424         if (includePreview && obj.preview) {
425             titleElement.classList.add("console-object-preview");
426             var lossless = this._appendObjectPreview(obj, description, titleElement);
427             if (lossless) {
428                 elem.appendChild(titleElement);
429                 titleElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, obj), false);
430                 return;
431             }
432         }
433         var section = new WebInspector.ObjectPropertiesSection(obj, titleElement);
434         section.enableContextMenu();
435         elem.appendChild(section.element);
436
437         var note = section.titleElement.createChild("span", "object-info-state-note");
438         note.title = WebInspector.UIString("Object state below is captured upon first expansion");
439     },
440
441     /**
442      * @param {!WebInspector.RemoteObject} obj
443      * @param {?Event} event
444      */
445     _contextMenuEventFired: function(obj, event)
446     {
447         var contextMenu = new WebInspector.ContextMenu(event);
448         contextMenu.appendApplicableItems(obj);
449         contextMenu.show();
450     },
451
452     /**
453      * @param {!WebInspector.RemoteObject} obj
454      * @param {string} description
455      * @param {!Element} titleElement
456      * @return {boolean} true iff preview captured all information.
457      */
458     _appendObjectPreview: function(obj, description, titleElement)
459     {
460         var preview = obj.preview;
461         var isArray = obj.subtype === "array";
462
463         if (description)
464             titleElement.createTextChild(" ");
465         titleElement.createTextChild(isArray ? "[" : "{");
466         for (var i = 0; i < preview.properties.length; ++i) {
467             if (i > 0)
468                 titleElement.createTextChild(", ");
469
470             var property = preview.properties[i];
471             var name = property.name;
472             if (!isArray || name != i) {
473                 if (/^\s|\s$|^$|\n/.test(name))
474                     name = "\"" + name.replace(/\n/g, "\u21B5") + "\"";
475                 titleElement.createChild("span", "name").textContent = name;
476                 titleElement.createTextChild(": ");
477             }
478
479             titleElement.appendChild(this._renderPropertyPreviewOrAccessor(obj, [property]));
480         }
481         if (preview.overflow)
482             titleElement.createChild("span").textContent = "\u2026";
483         titleElement.createTextChild(isArray ? "]" : "}");
484         return preview.lossless;
485     },
486
487     /**
488      * @param {!WebInspector.RemoteObject} object
489      * @param {!Array.<!RuntimeAgent.PropertyPreview>} propertyPath
490      * @return {!Element}
491      */
492     _renderPropertyPreviewOrAccessor: function(object, propertyPath)
493     {
494         var property = propertyPath.peekLast();
495         if (property.type === "accessor")
496             return this._formatAsAccessorProperty(object, propertyPath.select("name"), false);
497         return this._renderPropertyPreview(property.type, /** @type {string} */ (property.subtype), property.value);
498     },
499
500     /**
501      * @param {string} type
502      * @param {string=} subtype
503      * @param {string=} description
504      * @return {!Element}
505      */
506     _renderPropertyPreview: function(type, subtype, description)
507     {
508         var span = document.createElement("span");
509         span.className = "console-formatted-" + type;
510
511         if (type === "function") {
512             span.textContent = "function";
513             return span;
514         }
515
516         if (type === "object" && subtype === "regexp") {
517             span.classList.add("console-formatted-string");
518             span.textContent = description;
519             return span;
520         }
521
522         if (type === "object" && subtype === "node" && description) {
523             span.classList.add("console-formatted-preview-node");
524             WebInspector.DOMPresentationUtils.createSpansForNodeTitle(span, description);
525             return span;
526         }
527
528         if (type === "string") {
529             span.textContent = "\"" + description.replace(/\n/g, "\u21B5") + "\"";
530             return span;
531         }
532
533         span.textContent = description;
534         return span;
535     },
536
537     /**
538      * @param {!WebInspector.RemoteObject} object
539      * @param {!Element} elem
540      */
541     _formatParameterAsNode: function(object, elem)
542     {
543         /**
544          * @param {!WebInspector.DOMNode} node
545          * @this {WebInspector.ConsoleViewMessage}
546          */
547         function printNode(node)
548         {
549             if (!node) {
550                 // Sometimes DOM is loaded after the sync message is being formatted, so we get no
551                 // nodeId here. So we fall back to object formatting here.
552                 this._formatParameterAsObject(object, elem, false);
553                 return;
554             }
555             var renderer = WebInspector.moduleManager.instance(WebInspector.Renderer, node);
556             if (renderer)
557                 elem.appendChild(renderer.render(node));
558             else
559                 console.error("No renderer for node found");
560         }
561         object.pushNodeToFrontend(printNode.bind(this));
562     },
563
564     /**
565      * @param {!WebInspector.RemoteObject} array
566      * @return {boolean}
567      */
568     useArrayPreviewInFormatter: function(array)
569     {
570         return this._message.type !== WebInspector.ConsoleMessage.MessageType.DirXML && !!array.preview;
571     },
572
573     /**
574      * @param {!WebInspector.RemoteObject} array
575      * @param {!Element} elem
576      */
577     _formatParameterAsArray: function(array, elem)
578     {
579         if (this.useArrayPreviewInFormatter(array)) {
580             this._formatParameterAsArrayOrObject(array, "", elem, true);
581             return;
582         }
583
584         const maxFlatArrayLength = 100;
585         if (this._message.isOutdated || array.arrayLength() > maxFlatArrayLength)
586             this._formatParameterAsObject(array, elem, false);
587         else
588             array.getOwnProperties(this._printArray.bind(this, array, elem));
589     },
590
591     /**
592      * @param {!Array.<!WebInspector.RemoteObject>} parameters
593      * @return {!Element}
594      */
595     _formatParameterAsTable: function(parameters)
596     {
597         var element = document.createElement("span");
598         var table = parameters[0];
599         if (!table || !table.preview)
600             return element;
601
602         var columnNames = [];
603         var preview = table.preview;
604         var rows = [];
605         for (var i = 0; i < preview.properties.length; ++i) {
606             var rowProperty = preview.properties[i];
607             var rowPreview = rowProperty.valuePreview;
608             if (!rowPreview)
609                 continue;
610
611             var rowValue = {};
612             const maxColumnsToRender = 20;
613             for (var j = 0; j < rowPreview.properties.length; ++j) {
614                 var cellProperty = rowPreview.properties[j];
615                 var columnRendered = columnNames.indexOf(cellProperty.name) != -1;
616                 if (!columnRendered) {
617                     if (columnNames.length === maxColumnsToRender)
618                         continue;
619                     columnRendered = true;
620                     columnNames.push(cellProperty.name);
621                 }
622
623                 if (columnRendered) {
624                     var cellElement = this._renderPropertyPreviewOrAccessor(table, [rowProperty, cellProperty]);
625                     cellElement.classList.add("nowrap-below");
626                     rowValue[cellProperty.name] = cellElement;
627                 }
628             }
629             rows.push([rowProperty.name, rowValue]);
630         }
631
632         var flatValues = [];
633         for (var i = 0; i < rows.length; ++i) {
634             var rowName = rows[i][0];
635             var rowValue = rows[i][1];
636             flatValues.push(rowName);
637             for (var j = 0; j < columnNames.length; ++j)
638                 flatValues.push(rowValue[columnNames[j]]);
639         }
640
641         var dataGridContainer = element.createChild("span");
642         if (!preview.lossless || !flatValues.length) {
643             element.appendChild(this._formatParameter(table, true, false));
644             if (!flatValues.length)
645                 return element;
646         }
647
648         columnNames.unshift(WebInspector.UIString("(index)"));
649         var dataGrid = WebInspector.DataGrid.createSortableDataGrid(columnNames, flatValues);
650         dataGrid.renderInline();
651         this._dataGrids.push(dataGrid);
652         this._dataGridParents.put(dataGrid, dataGridContainer);
653         return element;
654     },
655
656     /**
657      * @param {!WebInspector.RemoteObject} output
658      * @param {!Element} elem
659      */
660     _formatParameterAsString: function(output, elem)
661     {
662         var span = document.createElement("span");
663         span.className = "console-formatted-string source-code";
664         span.appendChild(WebInspector.linkifyStringAsFragment(output.description || ""));
665
666         // Make black quotes.
667         elem.classList.remove("console-formatted-string");
668         elem.appendChild(document.createTextNode("\""));
669         elem.appendChild(span);
670         elem.appendChild(document.createTextNode("\""));
671     },
672
673     /**
674      * @param {!WebInspector.RemoteObject} array
675      * @param {!Element} elem
676      * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
677      */
678     _printArray: function(array, elem, properties)
679     {
680         if (!properties)
681             return;
682
683         var elements = [];
684         for (var i = 0; i < properties.length; ++i) {
685             var property = properties[i];
686             var name = property.name;
687             if (isNaN(name))
688                 continue;
689             if (property.getter)
690                 elements[name] = this._formatAsAccessorProperty(array, [name], true);
691             else if (property.value)
692                 elements[name] = this._formatAsArrayEntry(property.value);
693         }
694
695         elem.appendChild(document.createTextNode("["));
696         var lastNonEmptyIndex = -1;
697
698         function appendUndefined(elem, index)
699         {
700             if (index - lastNonEmptyIndex <= 1)
701                 return;
702             var span = elem.createChild("span", "console-formatted-undefined");
703             span.textContent = WebInspector.UIString("undefined Ã— %d", index - lastNonEmptyIndex - 1);
704         }
705
706         var length = array.arrayLength();
707         for (var i = 0; i < length; ++i) {
708             var element = elements[i];
709             if (!element)
710                 continue;
711
712             if (i - lastNonEmptyIndex > 1) {
713                 appendUndefined(elem, i);
714                 elem.appendChild(document.createTextNode(", "));
715             }
716
717             elem.appendChild(element);
718             lastNonEmptyIndex = i;
719             if (i < length - 1)
720                 elem.appendChild(document.createTextNode(", "));
721         }
722         appendUndefined(elem, length);
723
724         elem.appendChild(document.createTextNode("]"));
725         elem.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, array), false);
726     },
727
728     /**
729      * @param {!WebInspector.RemoteObject} output
730      * @return {!Element}
731      */
732     _formatAsArrayEntry: function(output)
733     {
734         // Prevent infinite expansion of cross-referencing arrays.
735         return this._formatParameter(output, output.subtype === "array", false);
736     },
737
738     /**
739      * @param {!WebInspector.RemoteObject} object
740      * @param {!Array.<string>} propertyPath
741      * @param {boolean} isArrayEntry
742      * @return {!Element}
743      */
744     _formatAsAccessorProperty: function(object, propertyPath, isArrayEntry)
745     {
746         var rootElement = WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan(object, propertyPath, onInvokeGetterClick.bind(this));
747
748         /**
749          * @param {?WebInspector.RemoteObject} result
750          * @param {boolean=} wasThrown
751          * @this {WebInspector.ConsoleViewMessage}
752          */
753         function onInvokeGetterClick(result, wasThrown)
754         {
755             if (!result)
756                 return;
757             rootElement.removeChildren();
758             if (wasThrown) {
759                 var element = rootElement.createChild("span", "error-message");
760                 element.textContent = WebInspector.UIString("<exception>");
761                 element.title = /** @type {string} */ (result.description);
762             } else if (isArrayEntry) {
763                 rootElement.appendChild(this._formatAsArrayEntry(result));
764             } else {
765                 // Make a PropertyPreview from the RemoteObject similar to the backend logic.
766                 const maxLength = 100;
767                 var type = result.type;
768                 var subtype = result.subtype;
769                 var description = "";
770                 if (type !== "function" && result.description) {
771                     if (type === "string" || subtype === "regexp")
772                         description = result.description.trimMiddle(maxLength);
773                     else
774                         description = result.description.trimEnd(maxLength);
775                 }
776                 rootElement.appendChild(this._renderPropertyPreview(type, subtype, description));
777             }
778         }
779
780         return rootElement;
781     },
782
783     /**
784      * @param {string} format
785      * @param {!Array.<string>} parameters
786      * @param {!Element} formattedResult
787      */
788     _formatWithSubstitutionString: function(format, parameters, formattedResult)
789     {
790         var formatters = {};
791
792         /**
793          * @param {boolean} force
794          * @param {!WebInspector.RemoteObject} obj
795          * @return {!Element}
796          * @this {WebInspector.ConsoleViewMessage}
797          */
798         function parameterFormatter(force, obj)
799         {
800             return this._formatParameter(obj, force, false);
801         }
802
803         function stringFormatter(obj)
804         {
805             return obj.description;
806         }
807
808         function floatFormatter(obj)
809         {
810             if (typeof obj.value !== "number")
811                 return "NaN";
812             return obj.value;
813         }
814
815         function integerFormatter(obj)
816         {
817             if (typeof obj.value !== "number")
818                 return "NaN";
819             return Math.floor(obj.value);
820         }
821
822         function bypassFormatter(obj)
823         {
824             return (obj instanceof Node) ? obj : "";
825         }
826
827         var currentStyle = null;
828         function styleFormatter(obj)
829         {
830             currentStyle = {};
831             var buffer = document.createElement("span");
832             buffer.setAttribute("style", obj.description);
833             for (var i = 0; i < buffer.style.length; i++) {
834                 var property = buffer.style[i];
835                 if (isWhitelistedProperty(property))
836                     currentStyle[property] = buffer.style[property];
837             }
838         }
839
840         function isWhitelistedProperty(property)
841         {
842             var prefixes = ["background", "border", "color", "font", "line", "margin", "padding", "text", "-webkit-background", "-webkit-border", "-webkit-font", "-webkit-margin", "-webkit-padding", "-webkit-text"];
843             for (var i = 0; i < prefixes.length; i++) {
844                 if (property.startsWith(prefixes[i]))
845                     return true;
846             }
847             return false;
848         }
849
850         // Firebug uses %o for formatting objects.
851         formatters.o = parameterFormatter.bind(this, false);
852         formatters.s = stringFormatter;
853         formatters.f = floatFormatter;
854         // Firebug allows both %i and %d for formatting integers.
855         formatters.i = integerFormatter;
856         formatters.d = integerFormatter;
857
858         // Firebug uses %c for styling the message.
859         formatters.c = styleFormatter;
860
861         // Support %O to force object formatting, instead of the type-based %o formatting.
862         formatters.O = parameterFormatter.bind(this, true);
863
864         formatters._ = bypassFormatter;
865
866         function append(a, b)
867         {
868             if (b instanceof Node)
869                 a.appendChild(b);
870             else if (typeof b !== "undefined") {
871                 var toAppend = WebInspector.linkifyStringAsFragment(String(b));
872                 if (currentStyle) {
873                     var wrapper = document.createElement('span');
874                     for (var key in currentStyle)
875                         wrapper.style[key] = currentStyle[key];
876                     wrapper.appendChild(toAppend);
877                     toAppend = wrapper;
878                 }
879                 a.appendChild(toAppend);
880             }
881             return a;
882         }
883
884         // String.format does treat formattedResult like a Builder, result is an object.
885         return String.format(format, parameters, formatters, formattedResult, append);
886     },
887
888     clearHighlight: function()
889     {
890         if (!this._formattedMessage)
891             return;
892
893         var highlightedMessage = this._formattedMessage;
894         delete this._formattedMessage;
895         delete this._anchorElement;
896         delete this._messageElement;
897         this._formatMessage();
898         this._element.replaceChild(this._formattedMessage, highlightedMessage);
899     },
900
901     highlightSearchResults: function(regexObject)
902     {
903         if (!this._formattedMessage)
904             return;
905
906         this._highlightSearchResultsInElement(regexObject, this._messageElement);
907         if (this._anchorElement)
908             this._highlightSearchResultsInElement(regexObject, this._anchorElement);
909     },
910
911     _highlightSearchResultsInElement: function(regexObject, element)
912     {
913         regexObject.lastIndex = 0;
914         var text = element.textContent;
915         var match = regexObject.exec(text);
916         var matchRanges = [];
917         while (match) {
918             matchRanges.push(new WebInspector.SourceRange(match.index, match[0].length));
919             match = regexObject.exec(text);
920         }
921         WebInspector.highlightSearchResults(element, matchRanges);
922     },
923
924     /**
925      * @return {boolean}
926      */
927     matchesRegex: function(regexObject)
928     {
929         regexObject.lastIndex = 0;
930         return regexObject.test(this._formattedMessageText()) || (!!this._anchorElement && regexObject.test(this._anchorElement.textContent));
931     },
932
933     /**
934      * @param {boolean} show
935      */
936     updateTimestamp: function(show)
937     {
938         if (!this._element)
939             return;
940
941         if (show && !this.timestampElement) {
942             this.timestampElement = this._element.createChild("span", "console-timestamp");
943             this.timestampElement.textContent = (new Date(this._message.timestamp)).toConsoleTime();
944             var afterRepeatCountChild = this._repeatCountElement && this._repeatCountElement.nextSibling;
945             this._element.insertBefore(this.timestampElement, afterRepeatCountChild || this._element.firstChild);
946             return;
947         }
948
949         if (!show && this.timestampElement) {
950             this.timestampElement.remove();
951             delete this.timestampElement;
952         }
953     },
954
955     /**
956      * @return {number}
957      */
958     nestingLevel: function()
959     {
960         return this._nestingLevel;
961     },
962
963     resetCloseGroupDecorationCount: function()
964     {
965         this._closeGroupDecorationCount = 0;
966         this._updateCloseGroupDecorations();
967     },
968
969     incrementCloseGroupDecorationCount: function()
970     {
971         ++this._closeGroupDecorationCount;
972         this._updateCloseGroupDecorations();
973     },
974
975     _updateCloseGroupDecorations: function()
976     {
977         if (!this._nestingLevelMarkers)
978             return;
979         for (var i = 0, n = this._nestingLevelMarkers.length; i < n; ++i) {
980             var marker = this._nestingLevelMarkers[i];
981             marker.classList.toggle("group-closed", n - i <= this._closeGroupDecorationCount);
982         }
983     },
984
985     /**
986      * @return {!Element}
987      */
988     contentElement: function()
989     {
990         if (this._element)
991             return this._element;
992
993         var element = document.createElementWithClass("div", "console-message");
994         this._element = element;
995
996         switch (this._message.level) {
997         case WebInspector.ConsoleMessage.MessageLevel.Log:
998             element.classList.add("console-log-level");
999             break;
1000         case WebInspector.ConsoleMessage.MessageLevel.Debug:
1001             element.classList.add("console-debug-level");
1002             break;
1003         case WebInspector.ConsoleMessage.MessageLevel.Warning:
1004             element.classList.add("console-warning-level");
1005             break;
1006         case WebInspector.ConsoleMessage.MessageLevel.Error:
1007             element.classList.add("console-error-level");
1008             break;
1009         case WebInspector.ConsoleMessage.MessageLevel.Info:
1010             element.classList.add("console-info-level");
1011             break;
1012         }
1013
1014         if (this._message.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this._message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
1015             element.classList.add("console-group-title");
1016
1017         element.appendChild(this.formattedMessage());
1018
1019         if (this._repeatCount > 1)
1020             this._showRepeatCountElement();
1021
1022         this.updateTimestamp(WebInspector.settings.consoleTimestampsEnabled.get());
1023
1024         return this._element;
1025     },
1026
1027     /**
1028      * @return {!Element}
1029      */
1030     toMessageElement: function()
1031     {
1032         if (this._wrapperElement)
1033             return this._wrapperElement;
1034
1035         this._wrapperElement = document.createElementWithClass("div", "console-message-wrapper");
1036         this._nestingLevelMarkers = [];
1037         for (var i = 0; i < this._nestingLevel; ++i)
1038             this._nestingLevelMarkers.push(this._wrapperElement.createChild("div", "nesting-level-marker"));
1039         this._updateCloseGroupDecorations();
1040         this._wrapperElement.message = this;
1041
1042         this._wrapperElement.appendChild(this.contentElement());
1043         return this._wrapperElement;
1044     },
1045
1046     _populateStackTraceTreeElement: function(parentTreeElement)
1047     {
1048         for (var i = 0; i < this._message.stackTrace.length; i++) {
1049             var frame = this._message.stackTrace[i];
1050
1051             var content = document.createElementWithClass("div", "stacktrace-entry");
1052             var messageTextElement = document.createElement("span");
1053             messageTextElement.className = "console-message-text source-code";
1054             var functionName = frame.functionName || WebInspector.UIString("(anonymous function)");
1055             messageTextElement.appendChild(document.createTextNode(functionName));
1056             content.appendChild(messageTextElement);
1057
1058             if (frame.scriptId) {
1059                 content.appendChild(document.createTextNode(" "));
1060                 var urlElement = this._linkifyCallFrame(frame);
1061                 if (!urlElement)
1062                     continue;
1063                 content.appendChild(urlElement);
1064             }
1065
1066             var treeElement = new TreeElement(content);
1067             parentTreeElement.appendChild(treeElement);
1068         }
1069     },
1070
1071     resetIncrementRepeatCount: function()
1072     {
1073         this._repeatCount = 1;
1074         if (!this._repeatCountElement)
1075             return;
1076
1077         this._repeatCountElement.remove();
1078         delete this._repeatCountElement;
1079     },
1080
1081     incrementRepeatCount: function()
1082     {
1083         this._repeatCount++;
1084         this._showRepeatCountElement();
1085     },
1086
1087     _showRepeatCountElement: function()
1088     {
1089         if (!this._element)
1090             return;
1091
1092         if (!this._repeatCountElement) {
1093             this._repeatCountElement = document.createElement("span");
1094             this._repeatCountElement.className = "bubble";
1095
1096             this._element.insertBefore(this._repeatCountElement, this._element.firstChild);
1097             this._element.classList.add("repeated-message");
1098         }
1099         this._repeatCountElement.textContent = this._repeatCount;
1100     },
1101
1102     /**
1103      * @return {string}
1104      */
1105     toString: function()
1106     {
1107         var sourceString;
1108         switch (this._message.source) {
1109             case WebInspector.ConsoleMessage.MessageSource.XML:
1110                 sourceString = "XML";
1111                 break;
1112             case WebInspector.ConsoleMessage.MessageSource.JS:
1113                 sourceString = "JavaScript";
1114                 break;
1115             case WebInspector.ConsoleMessage.MessageSource.Network:
1116                 sourceString = "Network";
1117                 break;
1118             case WebInspector.ConsoleMessage.MessageSource.ConsoleAPI:
1119                 sourceString = "ConsoleAPI";
1120                 break;
1121             case WebInspector.ConsoleMessage.MessageSource.Storage:
1122                 sourceString = "Storage";
1123                 break;
1124             case WebInspector.ConsoleMessage.MessageSource.AppCache:
1125                 sourceString = "AppCache";
1126                 break;
1127             case WebInspector.ConsoleMessage.MessageSource.Rendering:
1128                 sourceString = "Rendering";
1129                 break;
1130             case WebInspector.ConsoleMessage.MessageSource.CSS:
1131                 sourceString = "CSS";
1132                 break;
1133             case WebInspector.ConsoleMessage.MessageSource.Security:
1134                 sourceString = "Security";
1135                 break;
1136             case WebInspector.ConsoleMessage.MessageSource.Other:
1137                 sourceString = "Other";
1138                 break;
1139         }
1140
1141         var typeString;
1142         switch (this._message.type) {
1143             case WebInspector.ConsoleMessage.MessageType.Log:
1144                 typeString = "Log";
1145                 break;
1146             case WebInspector.ConsoleMessage.MessageType.Dir:
1147                 typeString = "Dir";
1148                 break;
1149             case WebInspector.ConsoleMessage.MessageType.DirXML:
1150                 typeString = "Dir XML";
1151                 break;
1152             case WebInspector.ConsoleMessage.MessageType.Trace:
1153                 typeString = "Trace";
1154                 break;
1155             case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
1156             case WebInspector.ConsoleMessage.MessageType.StartGroup:
1157                 typeString = "Start Group";
1158                 break;
1159             case WebInspector.ConsoleMessage.MessageType.EndGroup:
1160                 typeString = "End Group";
1161                 break;
1162             case WebInspector.ConsoleMessage.MessageType.Assert:
1163                 typeString = "Assert";
1164                 break;
1165             case WebInspector.ConsoleMessage.MessageType.Result:
1166                 typeString = "Result";
1167                 break;
1168             case WebInspector.ConsoleMessage.MessageType.Profile:
1169             case WebInspector.ConsoleMessage.MessageType.ProfileEnd:
1170                 typeString = "Profiling";
1171                 break;
1172         }
1173
1174         var levelString;
1175         switch (this._message.level) {
1176             case WebInspector.ConsoleMessage.MessageLevel.Log:
1177                 levelString = "Log";
1178                 break;
1179             case WebInspector.ConsoleMessage.MessageLevel.Warning:
1180                 levelString = "Warning";
1181                 break;
1182             case WebInspector.ConsoleMessage.MessageLevel.Debug:
1183                 levelString = "Debug";
1184                 break;
1185             case WebInspector.ConsoleMessage.MessageLevel.Error:
1186                 levelString = "Error";
1187                 break;
1188             case WebInspector.ConsoleMessage.MessageLevel.Info:
1189                 levelString = "Info";
1190                 break;
1191         }
1192
1193         return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage().textContent + "\n" + this._message.url + " line " + this._message.line;
1194     },
1195
1196     get text()
1197     {
1198         return this._message.messageText;
1199     },
1200 }
1201
1202 /**
1203  * @constructor
1204  * @extends {WebInspector.ConsoleViewMessage}
1205  * @param {!WebInspector.ConsoleMessage} consoleMessage
1206  * @param {?WebInspector.Linkifier} linkifier
1207  * @param {number} nestingLevel
1208  */
1209 WebInspector.ConsoleGroupViewMessage = function(consoleMessage, linkifier, nestingLevel)
1210 {
1211     console.assert(consoleMessage.isGroupStartMessage());
1212     WebInspector.ConsoleViewMessage.call(this, consoleMessage, linkifier, nestingLevel);
1213     this.setCollapsed(consoleMessage.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed);
1214 }
1215
1216 WebInspector.ConsoleGroupViewMessage.prototype = {
1217     /**
1218      * @param {boolean} collapsed
1219      */
1220     setCollapsed: function(collapsed)
1221     {
1222         this._collapsed = collapsed;
1223         if (this._wrapperElement)
1224             this._wrapperElement.classList.toggle("collapsed", this._collapsed);
1225     },
1226
1227     /**
1228      * @return {boolean}
1229      */
1230     collapsed: function()
1231     {
1232        return this._collapsed;
1233     },
1234
1235     /**
1236      * @return {!Element}
1237      */
1238     toMessageElement: function()
1239     {
1240         if (!this._wrapperElement) {
1241             WebInspector.ConsoleViewMessage.prototype.toMessageElement.call(this);
1242             this._wrapperElement.classList.toggle("collapsed", this._collapsed);
1243         }
1244         return this._wrapperElement;
1245     },
1246
1247     __proto__: WebInspector.ConsoleViewMessage.prototype
1248 }