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