Home | History | Annotate | Download | only in console
      1 /*
      2  * Copyright (C) 2011 Google Inc.  All rights reserved.
      3  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
      4  * Copyright (C) 2009 Joseph Pecoraro
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  *
     10  * 1.  Redistributions of source code must retain the above copyright
     11  *     notice, this list of conditions and the following disclaimer.
     12  * 2.  Redistributions in binary form must reproduce the above copyright
     13  *     notice, this list of conditions and the following disclaimer in the
     14  *     documentation and/or other materials provided with the distribution.
     15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     16  *     its contributors may be used to endorse or promote products derived
     17  *     from this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /**
     32  * @constructor
     33  * @implements {WebInspector.ViewportElement}
     34  * @param {!WebInspector.ConsoleMessage} consoleMessage
     35  * @param {?WebInspector.Linkifier} linkifier
     36  * @param {number} nestingLevel
     37  */
     38 WebInspector.ConsoleViewMessage = function(consoleMessage, linkifier, nestingLevel)
     39 {
     40     this._message = consoleMessage;
     41     this._linkifier = linkifier;
     42     this._repeatCount = 1;
     43     this._closeGroupDecorationCount = 0;
     44     this._nestingLevel = nestingLevel;
     45 
     46     /** @type {!Array.<!WebInspector.DataGrid>} */
     47     this._dataGrids = [];
     48     /** @type {!Map.<!WebInspector.DataGrid, ?Element>} */
     49     this._dataGridParents = new Map();
     50 
     51     /** @type {!Object.<string, function(!WebInspector.RemoteObject, !Element, boolean=)>} */
     52     this._customFormatters = {
     53         "object": this._formatParameterAsObject,
     54         "array": this._formatParameterAsArray,
     55         "node": this._formatParameterAsNode,
     56         "map": this._formatParameterAsObject,
     57         "set": this._formatParameterAsObject,
     58         "string": this._formatParameterAsString
     59     };
     60 }
     61 
     62 WebInspector.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  */
   1290 WebInspector.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 
   1297 WebInspector.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