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