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