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