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