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