Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / console / ConsoleViewMessage.js
blob70269fee33d2d3fc87a45beeba343bb9fa66823b
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  */
31 /**
32  * @constructor
33  * @implements {WebInspector.ViewportElement}
34  * @param {!WebInspector.ConsoleMessage} consoleMessage
35  * @param {!WebInspector.Linkifier} linkifier
36  * @param {number} nestingLevel
37  */
38 WebInspector.ConsoleViewMessage = function(consoleMessage, linkifier, nestingLevel)
40     this._message = consoleMessage;
41     this._linkifier = linkifier;
42     this._repeatCount = 1;
43     this._closeGroupDecorationCount = 0;
44     this._nestingLevel = nestingLevel;
46     /** @type {!Array.<!WebInspector.DataGrid>} */
47     this._dataGrids = [];
48     /** @type {!Map.<!WebInspector.DataGrid, ?Element>} */
49     this._dataGridParents = new Map();
51     /** @type {!Object.<string, function(!WebInspector.RemoteObject, !Element, boolean=)>} */
52     this._customFormatters = {
53         "array": this._formatParameterAsArray,
54         "error": this._formatParameterAsError,
55         "function": this._formatParameterAsFunction,
56         "generator": this._formatParameterAsObject,
57         "iterator": this._formatParameterAsObject,
58         "map": this._formatParameterAsObject,
59         "node": this._formatParameterAsNode,
60         "object": this._formatParameterAsObject,
61         "set": this._formatParameterAsObject,
62         "string": this._formatParameterAsString
63     };
64     this._previewFormatter = new WebInspector.RemoteObjectPreviewFormatter();
65     this._searchRegex = null;
68 WebInspector.ConsoleViewMessage.prototype = {
69     /**
70      * @return {?WebInspector.Target}
71      */
72     _target: function()
73     {
74         return this.consoleMessage().target();
75     },
77     /**
78      * @override
79      * @return {!Element}
80      */
81     element: function()
82     {
83         return this.toMessageElement();
84     },
86     /**
87      * @override
88      */
89     wasShown: function()
90     {
91         for (var i = 0; this._dataGrids && i < this._dataGrids.length; ++i) {
92             var dataGrid = this._dataGrids[i];
93             var parentElement = this._dataGridParents.get(dataGrid) || null;
94             dataGrid.show(parentElement);
95             dataGrid.updateWidths();
96         }
97     },
99     /**
100      * @override
101      */
102     cacheFastHeight: function()
103     {
104         this._cachedHeight = this.contentElement().offsetHeight;
105     },
107     /**
108      * @override
109      */
110     willHide: function()
111     {
112         for (var i = 0; this._dataGrids && i < this._dataGrids.length; ++i) {
113             var dataGrid = this._dataGrids[i];
114             this._dataGridParents.set(dataGrid, dataGrid.element.parentElement);
115             dataGrid.detach();
116         }
117     },
119     /**
120      * @return {number}
121      */
122     fastHeight: function()
123     {
124         if (this._cachedHeight)
125             return this._cachedHeight;
126         const defaultConsoleRowHeight = 18;  // Sync with consoleView.css
127         if (this._message.type === WebInspector.ConsoleMessage.MessageType.Table) {
128             var table = this._message.parameters[0];
129             if (table && table.preview)
130                 return defaultConsoleRowHeight * table.preview.properties.length;
131         }
132         return defaultConsoleRowHeight;
133     },
135     /**
136      * @return {!WebInspector.ConsoleMessage}
137      */
138     consoleMessage: function()
139     {
140         return this._message;
141     },
143     _formatMessage: function()
144     {
145         this._formattedMessage = createElement("span");
146         this._formattedMessage.appendChild(WebInspector.Widget.createStyleElement("components/objectValue.css"));
147         this._formattedMessage.className = "console-message-text source-code";
149         /**
150          * @param {string} title
151          * @return {!Element}
152          * @this {WebInspector.ConsoleMessage}
153          */
154         function linkifyRequest(title)
155         {
156             return WebInspector.Linkifier.linkifyUsingRevealer(/** @type {!WebInspector.NetworkRequest} */ (this.request), title, this.request.url);
157         }
159         var consoleMessage = this._message;
160         if (!this._messageElement) {
161             if (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.ConsoleAPI) {
162                 switch (consoleMessage.type) {
163                     case WebInspector.ConsoleMessage.MessageType.Trace:
164                         this._messageElement = this._format(consoleMessage.parameters || ["console.trace()"]);
165                         break;
166                     case WebInspector.ConsoleMessage.MessageType.Clear:
167                         this._messageElement = createTextNode(WebInspector.UIString("Console was cleared"));
168                         this._formattedMessage.classList.add("console-info");
169                         break;
170                     case WebInspector.ConsoleMessage.MessageType.Assert:
171                         var args = [WebInspector.UIString("Assertion failed:")];
172                         if (consoleMessage.parameters)
173                             args = args.concat(consoleMessage.parameters);
174                         this._messageElement = this._format(args);
175                         break;
176                     case WebInspector.ConsoleMessage.MessageType.Dir:
177                         var obj = consoleMessage.parameters ? consoleMessage.parameters[0] : undefined;
178                         var args = ["%O", obj];
179                         this._messageElement = this._format(args);
180                         break;
181                     case WebInspector.ConsoleMessage.MessageType.Profile:
182                     case WebInspector.ConsoleMessage.MessageType.ProfileEnd:
183                         this._messageElement = this._format([consoleMessage.messageText]);
184                         break;
185                     default:
186                         if (consoleMessage.parameters && consoleMessage.parameters.length === 1 && consoleMessage.parameters[0].type === "string")
187                              this._messageElement = this._tryFormatAsError(/**@type {string} */(consoleMessage.parameters[0].value));
189                         var args = consoleMessage.parameters || [consoleMessage.messageText];
190                         this._messageElement = this._messageElement || this._format(args);
191                 }
192             } else if (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.Network) {
193                 if (consoleMessage.request) {
194                     this._messageElement = createElement("span");
195                     if (consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.Error || consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.RevokedError) {
196                         this._messageElement.createTextChildren(consoleMessage.request.requestMethod, " ");
197                         this._messageElement.appendChild(WebInspector.Linkifier.linkifyUsingRevealer(consoleMessage.request, consoleMessage.request.url, consoleMessage.request.url));
198                         if (consoleMessage.request.failed)
199                             this._messageElement.createTextChildren(" ", consoleMessage.request.localizedFailDescription);
200                         else
201                             this._messageElement.createTextChildren(" ", String(consoleMessage.request.statusCode), " (", consoleMessage.request.statusText, ")");
202                     } else {
203                         var fragment = WebInspector.linkifyStringAsFragmentWithCustomLinkifier(consoleMessage.messageText, linkifyRequest.bind(consoleMessage));
204                         this._messageElement.appendChild(fragment);
205                     }
206                 } else {
207                     var url = consoleMessage.url;
208                     if (url) {
209                         var isExternal = !WebInspector.resourceForURL(url) && !WebInspector.networkMapping.uiSourceCodeForURLForAnyTarget(url);
210                         this._anchorElement = WebInspector.linkifyURLAsNode(url, url, "console-message-url", isExternal);
211                     }
212                     this._messageElement = this._format([consoleMessage.messageText]);
213                 }
214             } else {
215                 var args = consoleMessage.parameters || [consoleMessage.messageText];
216                 this._messageElement = this._format(args);
217             }
218         }
220         if (consoleMessage.source !== WebInspector.ConsoleMessage.MessageSource.Network || consoleMessage.request) {
221             if (consoleMessage.scriptId) {
222                 this._anchorElement = this._linkifyScriptId(consoleMessage.scriptId, consoleMessage.url || "", consoleMessage.line, consoleMessage.column);
223             } else {
224                 var showBlackboxed = (consoleMessage.source !== WebInspector.ConsoleMessage.MessageSource.ConsoleAPI);
225                 var debuggerModel = WebInspector.DebuggerModel.fromTarget(this._target());
226                 var callFrame = WebInspector.DebuggerPresentationUtils.callFrameAnchorFromStackTrace(debuggerModel, consoleMessage.stackTrace, consoleMessage.asyncStackTrace, showBlackboxed);
227                 if (callFrame && callFrame.scriptId)
228                     this._anchorElement = this._linkifyCallFrame(callFrame);
229                 else if (consoleMessage.url && consoleMessage.url !== "undefined")
230                     this._anchorElement = this._linkifyLocation(consoleMessage.url, consoleMessage.line, consoleMessage.column);
231             }
232         }
234         this._formattedMessage.appendChild(this._messageElement);
235         if (this._anchorElement) {
236             // Append a space to prevent the anchor text from being glued to the console message when the user selects and copies the console messages.
237             this._anchorElement.appendChild(createTextNode(" "));
238             this._formattedMessage.insertBefore(this._anchorElement, this._formattedMessage.firstChild);
239         }
241         var dumpStackTrace = (!!consoleMessage.stackTrace || !!consoleMessage.asyncStackTrace) && (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.Network || consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.Error || consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.RevokedError || consoleMessage.type === WebInspector.ConsoleMessage.MessageType.Trace);
242         if (dumpStackTrace) {
243             var treeOutline = new TreeOutline();
244             treeOutline.element.classList.add("outline-disclosure", "outline-disclosure-no-padding");
245             var content = this._formattedMessage;
246             var root = new TreeElement(content);
247             root.toggleOnClick = true;
248             root.selectable = false;
249             content.treeElementForTest = root;
250             treeOutline.appendChild(root);
251             if (consoleMessage.type === WebInspector.ConsoleMessage.MessageType.Trace)
252                 root.expand();
254             this._populateStackTraceTreeElement(root);
255             this._formattedMessage = treeOutline.element;
256         }
257     },
259     /**
260      * @return {!Element}
261      */
262     formattedMessage: function()
263     {
264         if (!this._formattedMessage)
265             this._formatMessage();
266         return this._formattedMessage;
267     },
269     /**
270      * @param {string} url
271      * @param {number} lineNumber
272      * @param {number} columnNumber
273      * @return {?Element}
274      */
275     _linkifyLocation: function(url, lineNumber, columnNumber)
276     {
277         var target = this._target();
278         if (!target)
279             return null;
280         // FIXME(62725): stack trace line/column numbers are one-based.
281         lineNumber = lineNumber ? lineNumber - 1 : 0;
282         columnNumber = columnNumber ? columnNumber - 1 : 0;
283         return this._linkifier.linkifyScriptLocation(target, null, url, lineNumber, columnNumber, "console-message-url");
284     },
286     /**
287      * @param {!ConsoleAgent.CallFrame} callFrame
288      * @return {?Element}
289      */
290     _linkifyCallFrame: function(callFrame)
291     {
292         var target = this._target();
293         return this._linkifier.linkifyConsoleCallFrame(target, callFrame, "console-message-url");
294     },
296     /**
297      * @param {string} scriptId
298      * @param {string} url
299      * @param {number} lineNumber
300      * @param {number} columnNumber
301      * @return {?Element}
302      */
303     _linkifyScriptId: function(scriptId, url, lineNumber, columnNumber)
304     {
305         var target = this._target();
306         if (!target)
307             return null;
308         // FIXME(62725): stack trace line/column numbers are one-based.
309         lineNumber = lineNumber ? lineNumber - 1 : 0;
310         columnNumber = columnNumber ? columnNumber - 1 : 0;
311         return this._linkifier.linkifyScriptLocation(target, scriptId, url, lineNumber, columnNumber, "console-message-url");
312     },
314     _format: function(parameters)
315     {
316         // This node is used like a Builder. Values are continually appended onto it.
317         var formattedResult = createElement("span");
318         if (!parameters.length)
319             return formattedResult;
321         var target = this._target();
323         // Formatting code below assumes that parameters are all wrappers whereas frontend console
324         // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here.
325         for (var i = 0; i < parameters.length; ++i) {
326             // FIXME: Only pass runtime wrappers here.
327             if (parameters[i] instanceof WebInspector.RemoteObject)
328                 continue;
330             if (!target) {
331                 parameters[i] = WebInspector.RemoteObject.fromLocalObject(parameters[i]);
332                 continue;
333             }
335             if (typeof parameters[i] === "object")
336                 parameters[i] = target.runtimeModel.createRemoteObject(parameters[i]);
337             else
338                 parameters[i] = target.runtimeModel.createRemoteObjectFromPrimitiveValue(parameters[i]);
339         }
341         // There can be string log and string eval result. We distinguish between them based on message type.
342         var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && (this._message.type !== WebInspector.ConsoleMessage.MessageType.Result || this._message.level === WebInspector.ConsoleMessage.MessageLevel.Error || this._message.level === WebInspector.ConsoleMessage.MessageLevel.RevokedError);
344         // Multiple parameters with the first being a format string. Save unused substitutions.
345         if (shouldFormatMessage) {
346             // Multiple parameters with the first being a format string. Save unused substitutions.
347             var result = this._formatWithSubstitutionString(parameters[0].description, parameters.slice(1), formattedResult);
348             parameters = result.unusedSubstitutions;
349             if (parameters.length)
350                 formattedResult.createTextChild(" ");
351         }
353         if (this._message.type === WebInspector.ConsoleMessage.MessageType.Table) {
354             formattedResult.appendChild(this._formatParameterAsTable(parameters));
355             return formattedResult;
356         }
358         // Single parameter, or unused substitutions from above.
359         for (var i = 0; i < parameters.length; ++i) {
360             // Inline strings when formatting.
361             if (shouldFormatMessage && parameters[i].type === "string")
362                 formattedResult.appendChild(WebInspector.linkifyStringAsFragment(parameters[i].description));
363             else
364                 formattedResult.appendChild(this._formatParameter(parameters[i], false, true));
365             if (i < parameters.length - 1)
366                 formattedResult.createTextChild(" ");
367         }
368         return formattedResult;
369     },
371     /**
372      * @param {!WebInspector.RemoteObject} output
373      * @param {boolean=} forceObjectFormat
374      * @param {boolean=} includePreview
375      * @return {!Element}
376      */
377     _formatParameter: function(output, forceObjectFormat, includePreview)
378     {
379         if (output.customPreview()) {
380             return (new WebInspector.CustomPreviewComponent(output)).element;
381         }
383         var type = forceObjectFormat ? "object" : (output.subtype || output.type);
384         var formatter = this._customFormatters[type] || this._formatParameterAsValue;
385         var span = createElement("span");
386         span.className = "object-value-" + type + " source-code";
387         formatter.call(this, output, span, includePreview);
388         return span;
389     },
391     /**
392      * @param {!WebInspector.RemoteObject} obj
393      * @param {!Element} elem
394      */
395     _formatParameterAsValue: function(obj, elem)
396     {
397         elem.createTextChild(obj.description || "");
398         if (obj.objectId)
399             elem.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, obj), false);
400     },
402     /**
403      * @param {!WebInspector.RemoteObject} obj
404      * @param {!Element} elem
405      * @param {boolean=} includePreview
406      */
407     _formatParameterAsObject: function(obj, elem, includePreview)
408     {
409         this._formatParameterAsArrayOrObject(obj, elem, includePreview);
410     },
412     /**
413      * @param {!WebInspector.RemoteObject} obj
414      * @param {!Element} elem
415      * @param {boolean=} includePreview
416      */
417     _formatParameterAsArrayOrObject: function(obj, elem, includePreview)
418     {
419         var titleElement = createElement("span");
420         if (includePreview && obj.preview) {
421             titleElement.classList.add("console-object-preview");
422             var lossless = this._previewFormatter.appendObjectPreview(titleElement, obj.preview);
423             if (lossless) {
424                 elem.appendChild(titleElement);
425                 titleElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, obj), false);
426                 return;
427             }
428         } else {
429             if (obj.type === "function") {
430                 WebInspector.ObjectPropertiesSection.formatObjectAsFunction(obj, titleElement, false);
431                 titleElement.classList.add("object-value-function");
432             } else {
433                 titleElement.createTextChild(obj.description || "");
434             }
435         }
436         var note = titleElement.createChild("span", "object-info-state-note");
437         note.title = WebInspector.UIString("Object value at left was snapshotted when logged, value below was evaluated just now.");
438         var section = new WebInspector.ObjectPropertiesSection(obj, titleElement);
439         section.enableContextMenu();
440         elem.appendChild(section.element);
441         section.element.classList.add("console-view-object-properties-section");
442     },
444     /**
445      * @param {!WebInspector.RemoteObject} func
446      * @param {!Element} element
447      * @param {boolean=} includePreview
448      */
449     _formatParameterAsFunction: function(func, element, includePreview)
450     {
451         WebInspector.ObjectPropertiesSection.formatObjectAsFunction(func, element, true, includePreview);
452         element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, func), false);
453     },
455     /**
456      * @param {!WebInspector.RemoteObject} obj
457      * @param {!Event} event
458      */
459     _contextMenuEventFired: function(obj, event)
460     {
461         var contextMenu = new WebInspector.ContextMenu(event);
462         contextMenu.appendApplicableItems(obj);
463         contextMenu.show();
464     },
466     /**
467      * @param {?WebInspector.RemoteObject} object
468      * @param {!Array.<!RuntimeAgent.PropertyPreview>} propertyPath
469      * @return {!Element}
470      */
471     _renderPropertyPreviewOrAccessor: function(object, propertyPath)
472     {
473         var property = propertyPath.peekLast();
474         if (property.type === "accessor")
475             return this._formatAsAccessorProperty(object, propertyPath.select("name"), false);
476         return this._previewFormatter.renderPropertyPreview(property.type, /** @type {string} */ (property.subtype), property.value);
477     },
479     /**
480      * @param {!WebInspector.RemoteObject} object
481      * @param {!Element} elem
482      */
483     _formatParameterAsNode: function(object, elem)
484     {
485         WebInspector.Renderer.renderPromise(object).then(appendRenderer, failedToRender.bind(this));
486         /**
487          * @param {!Element} rendererElement
488          */
489         function appendRenderer(rendererElement)
490         {
491             elem.appendChild(rendererElement);
492         }
494         /**
495          * @this {WebInspector.ConsoleViewMessage}
496          */
497         function failedToRender()
498         {
499             this._formatParameterAsObject(object, elem, false);
500         }
501     },
503     /**
504      * @param {!WebInspector.RemoteObject} array
505      * @return {boolean}
506      */
507     useArrayPreviewInFormatter: function(array)
508     {
509         return this._message.type !== WebInspector.ConsoleMessage.MessageType.DirXML;
510     },
512     /**
513      * @param {!WebInspector.RemoteObject} array
514      * @param {!Element} elem
515      */
516     _formatParameterAsArray: function(array, elem)
517     {
518         var maxFlatArrayLength = 100;
519         if (this.useArrayPreviewInFormatter(array) || array.arrayLength() > maxFlatArrayLength)
520             this._formatParameterAsArrayOrObject(array, elem, this.useArrayPreviewInFormatter(array) || array.arrayLength() <= maxFlatArrayLength);
521         else
522             array.getAllProperties(false, this._printArray.bind(this, array, elem));
523     },
525     /**
526      * @param {!Array.<!WebInspector.RemoteObject>} parameters
527      * @return {!Element}
528      */
529     _formatParameterAsTable: function(parameters)
530     {
531         var element = createElementWithClass("div", "console-message-formatted-table");
532         var table = parameters[0];
533         if (!table || !table.preview)
534             return element;
536         var columnNames = [];
537         var preview = table.preview;
538         var rows = [];
539         for (var i = 0; i < preview.properties.length; ++i) {
540             var rowProperty = preview.properties[i];
541             var rowPreview = rowProperty.valuePreview;
542             if (!rowPreview)
543                 continue;
545             var rowValue = {};
546             const maxColumnsToRender = 20;
547             for (var j = 0; j < rowPreview.properties.length; ++j) {
548                 var cellProperty = rowPreview.properties[j];
549                 var columnRendered = columnNames.indexOf(cellProperty.name) != -1;
550                 if (!columnRendered) {
551                     if (columnNames.length === maxColumnsToRender)
552                         continue;
553                     columnRendered = true;
554                     columnNames.push(cellProperty.name);
555                 }
557                 if (columnRendered) {
558                     var cellElement = this._renderPropertyPreviewOrAccessor(table, [rowProperty, cellProperty]);
559                     cellElement.classList.add("console-message-nowrap-below");
560                     rowValue[cellProperty.name] = cellElement;
561                 }
562             }
563             rows.push([rowProperty.name, rowValue]);
564         }
566         var flatValues = [];
567         for (var i = 0; i < rows.length; ++i) {
568             var rowName = rows[i][0];
569             var rowValue = rows[i][1];
570             flatValues.push(rowName);
571             for (var j = 0; j < columnNames.length; ++j)
572                 flatValues.push(rowValue[columnNames[j]]);
573         }
575         var dataGridContainer = element.createChild("span");
576         if (!preview.lossless || !flatValues.length) {
577             element.appendChild(this._formatParameter(table, true, false));
578             if (!flatValues.length)
579                 return element;
580         }
582         columnNames.unshift(WebInspector.UIString("(index)"));
583         var dataGrid = WebInspector.SortableDataGrid.create(columnNames, flatValues);
584         dataGrid.renderInline();
585         this._dataGrids.push(dataGrid);
586         this._dataGridParents.set(dataGrid, dataGridContainer);
587         return element;
588     },
590     /**
591      * @param {!WebInspector.RemoteObject} output
592      * @param {!Element} elem
593      */
594     _formatParameterAsString: function(output, elem)
595     {
596         var span = createElement("span");
597         span.className = "object-value-string source-code";
598         span.appendChild(WebInspector.linkifyStringAsFragment(output.description || ""));
600         // Make black quotes.
601         elem.classList.remove("object-value-string");
602         elem.createTextChild("\"");
603         elem.appendChild(span);
604         elem.createTextChild("\"");
605     },
607     /**
608      * @param {!WebInspector.RemoteObject} output
609      * @param {!Element} elem
610      */
611     _formatParameterAsError: function(output, elem)
612     {
613         var span = elem.createChild("span", "object-value-error source-code");
614         var text = output.description || "";
615         var lines = text.split("\n", 2);
616         span.appendChild(WebInspector.linkifyStringAsFragment(lines[0]));
617         if (lines.length > 1) {
618             var detailedLink = elem.createChild("a");
619             detailedLink.textContent = "(\u2026)";
620             function showDetailed(event)
621             {
622                 span.removeChildren();
623                 detailedLink.remove();
624                 span.appendChild(WebInspector.linkifyStringAsFragment(text));
625                 event.consume(true);
626             }
627             detailedLink._showDetailedForTest = showDetailed.bind(null, new MouseEvent('click'));
628             detailedLink.addEventListener("click", showDetailed, false);
629         }
630     },
632     /**
633      * @param {!WebInspector.RemoteObject} array
634      * @param {!Element} elem
635      * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
636      */
637     _printArray: function(array, elem, properties)
638     {
639         if (!properties) {
640             this._formatParameterAsObject(array, elem, false);
641             return;
642         }
644         var elements = [];
645         for (var i = 0; i < properties.length; ++i) {
646             var property = properties[i];
647             var name = property.name;
648             if (isNaN(name))
649                 continue;
650             if (property.getter)
651                 elements[name] = this._formatAsAccessorProperty(array, [name], true);
652             else if (property.value)
653                 elements[name] = this._formatAsArrayEntry(property.value);
654         }
656         elem.createTextChild("[");
657         var lastNonEmptyIndex = -1;
659         function appendUndefined(elem, index)
660         {
661             if (index - lastNonEmptyIndex <= 1)
662                 return;
663             var span = elem.createChild("span", "object-value-undefined");
664             span.textContent = WebInspector.UIString("undefined Ã— %d", index - lastNonEmptyIndex - 1);
665         }
667         var length = array.arrayLength();
668         for (var i = 0; i < length; ++i) {
669             var element = elements[i];
670             if (!element)
671                 continue;
673             if (i - lastNonEmptyIndex > 1) {
674                 appendUndefined(elem, i);
675                 elem.createTextChild(", ");
676             }
678             elem.appendChild(element);
679             lastNonEmptyIndex = i;
680             if (i < length - 1)
681                 elem.createTextChild(", ");
682         }
683         appendUndefined(elem, length);
685         elem.createTextChild("]");
686         elem.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, array), false);
687     },
689     /**
690      * @param {!WebInspector.RemoteObject} output
691      * @return {!Element}
692      */
693     _formatAsArrayEntry: function(output)
694     {
695         // Prevent infinite expansion of cross-referencing arrays.
696         return this._formatParameter(output, output.subtype === "array", false);
697     },
699     /**
700      * @param {?WebInspector.RemoteObject} object
701      * @param {!Array.<string>} propertyPath
702      * @param {boolean} isArrayEntry
703      * @return {!Element}
704      */
705     _formatAsAccessorProperty: function(object, propertyPath, isArrayEntry)
706     {
707         var rootElement = WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan(object, propertyPath, onInvokeGetterClick.bind(this));
709         /**
710          * @param {?WebInspector.RemoteObject} result
711          * @param {boolean=} wasThrown
712          * @this {WebInspector.ConsoleViewMessage}
713          */
714         function onInvokeGetterClick(result, wasThrown)
715         {
716             if (!result)
717                 return;
718             rootElement.removeChildren();
719             if (wasThrown) {
720                 var element = rootElement.createChild("span", "error-message");
721                 element.textContent = WebInspector.UIString("<exception>");
722                 element.title = /** @type {string} */ (result.description);
723             } else if (isArrayEntry) {
724                 rootElement.appendChild(this._formatAsArrayEntry(result));
725             } else {
726                 // Make a PropertyPreview from the RemoteObject similar to the backend logic.
727                 const maxLength = 100;
728                 var type = result.type;
729                 var subtype = result.subtype;
730                 var description = "";
731                 if (type !== "function" && result.description) {
732                     if (type === "string" || subtype === "regexp")
733                         description = result.description.trimMiddle(maxLength);
734                     else
735                         description = result.description.trimEnd(maxLength);
736                 }
737                 rootElement.appendChild(this._previewFormatter.renderPropertyPreview(type, subtype, description));
738             }
739         }
741         return rootElement;
742     },
744     /**
745      * @param {string} format
746      * @param {!Array.<string>} parameters
747      * @param {!Element} formattedResult
748      */
749     _formatWithSubstitutionString: function(format, parameters, formattedResult)
750     {
751         var formatters = {};
753         /**
754          * @param {boolean} force
755          * @param {!WebInspector.RemoteObject} obj
756          * @return {!Element}
757          * @this {WebInspector.ConsoleViewMessage}
758          */
759         function parameterFormatter(force, obj)
760         {
761             return this._formatParameter(obj, force, false);
762         }
764         function stringFormatter(obj)
765         {
766             return obj.description;
767         }
769         function floatFormatter(obj)
770         {
771             if (typeof obj.value !== "number")
772                 return "NaN";
773             return obj.value;
774         }
776         function integerFormatter(obj)
777         {
778             if (typeof obj.value !== "number")
779                 return "NaN";
780             return Math.floor(obj.value);
781         }
783         function bypassFormatter(obj)
784         {
785             return (obj instanceof Node) ? obj : "";
786         }
788         var currentStyle = null;
789         function styleFormatter(obj)
790         {
791             currentStyle = {};
792             var buffer = createElement("span");
793             buffer.setAttribute("style", obj.description);
794             for (var i = 0; i < buffer.style.length; i++) {
795                 var property = buffer.style[i];
796                 var value = buffer.style.getPropertyValue(property);
797                 if (!value.startsWith("url(") && isWhitelistedProperty(property))
798                     currentStyle[property] = buffer.style[property];
799             }
800         }
802         function isWhitelistedProperty(property)
803         {
804             var prefixes = ["background", "border", "color", "font", "line", "margin", "padding", "text", "-webkit-background", "-webkit-border", "-webkit-font", "-webkit-margin", "-webkit-padding", "-webkit-text"];
805             for (var i = 0; i < prefixes.length; i++) {
806                 if (property.startsWith(prefixes[i]))
807                     return true;
808             }
809             return false;
810         }
812         // Firebug uses %o for formatting objects.
813         formatters.o = parameterFormatter.bind(this, false);
814         formatters.s = stringFormatter;
815         formatters.f = floatFormatter;
816         // Firebug allows both %i and %d for formatting integers.
817         formatters.i = integerFormatter;
818         formatters.d = integerFormatter;
820         // Firebug uses %c for styling the message.
821         formatters.c = styleFormatter;
823         // Support %O to force object formatting, instead of the type-based %o formatting.
824         formatters.O = parameterFormatter.bind(this, true);
826         formatters._ = bypassFormatter;
828         function append(a, b)
829         {
830             if (b instanceof Node)
831                 a.appendChild(b);
832             else if (typeof b !== "undefined") {
833                 var toAppend = WebInspector.linkifyStringAsFragment(String(b));
834                 if (currentStyle) {
835                     var wrapper = createElement('span');
836                     wrapper.appendChild(toAppend);
837                     applyCurrentStyle(wrapper);
838                     for (var i = 0; i < wrapper.children.length; ++i)
839                         applyCurrentStyle(wrapper.children[i]);
840                     toAppend = wrapper;
841                 }
842                 a.appendChild(toAppend);
843             }
844             return a;
845         }
847         /**
848          * @param {!Element} element
849          */
850         function applyCurrentStyle(element)
851         {
852             for (var key in currentStyle)
853                 element.style[key] = currentStyle[key];
854         }
856         // String.format does treat formattedResult like a Builder, result is an object.
857         return String.format(format, parameters, formatters, formattedResult, append);
858     },
860     /**
861      * @return {boolean}
862      */
863     matchesFilterRegex: function(regexObject)
864     {
865         regexObject.lastIndex = 0;
866         var text = this.searchableElement().deepTextContent();
867         if (this._anchorElement)
868             text += " " + this._anchorElement.textContent;
869         return regexObject.test(text);
870     },
872     /**
873      * @param {boolean} show
874      */
875     updateTimestamp: function(show)
876     {
877         if (!this._formattedMessage)
878             return;
880         if (show && !this.timestampElement) {
881             this.timestampElement = createElementWithClass("span", "console-timestamp");
882             this.timestampElement.textContent = (new Date(this._message.timestamp)).toConsoleTime() + " ";
883             var afterRepeatCountChild = this._repeatCountElement && this._repeatCountElement.nextSibling;
884             this._formattedMessage.insertBefore(this.timestampElement, this._formattedMessage.firstChild);
885             return;
886         }
888         if (!show && this.timestampElement) {
889             this.timestampElement.remove();
890             delete this.timestampElement;
891         }
892     },
894     /**
895      * @return {number}
896      */
897     nestingLevel: function()
898     {
899         return this._nestingLevel;
900     },
902     resetCloseGroupDecorationCount: function()
903     {
904         if (!this._closeGroupDecorationCount)
905             return;
906         this._closeGroupDecorationCount = 0;
907         this._updateCloseGroupDecorations();
908     },
910     incrementCloseGroupDecorationCount: function()
911     {
912         ++this._closeGroupDecorationCount;
913         this._updateCloseGroupDecorations();
914     },
916     _updateCloseGroupDecorations: function()
917     {
918         if (!this._nestingLevelMarkers)
919             return;
920         for (var i = 0, n = this._nestingLevelMarkers.length; i < n; ++i) {
921             var marker = this._nestingLevelMarkers[i];
922             marker.classList.toggle("group-closed", n - i <= this._closeGroupDecorationCount);
923         }
924     },
926     /**
927      * @return {!Element}
928      */
929     contentElement: function()
930     {
931         if (this._element)
932             return this._element;
934         var element = createElementWithClass("div", "console-message");
935         this._element = element;
937         if (this._message.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this._message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
938             element.classList.add("console-group-title");
940         element.appendChild(this.formattedMessage());
942         if (this._repeatCount > 1)
943             this._showRepeatCountElement();
945         this.updateTimestamp(WebInspector.moduleSetting("consoleTimestampsEnabled").get());
947         return this._element;
948     },
950     /**
951      * @return {!Element}
952      */
953     toMessageElement: function()
954     {
955         if (this._wrapperElement)
956             return this._wrapperElement;
958         this._wrapperElement = createElement("div");
959         this.updateMessageElement();
960         return this._wrapperElement;
961     },
963     updateMessageElement: function()
964     {
965         if (!this._wrapperElement)
966             return;
968         this._wrapperElement.className = "console-message-wrapper";
969         this._wrapperElement.removeChildren();
971         this._nestingLevelMarkers = [];
972         for (var i = 0; i < this._nestingLevel; ++i)
973             this._nestingLevelMarkers.push(this._wrapperElement.createChild("div", "nesting-level-marker"));
974         this._updateCloseGroupDecorations();
975         this._wrapperElement.message = this;
977         switch (this._message.level) {
978         case WebInspector.ConsoleMessage.MessageLevel.Log:
979             this._wrapperElement.classList.add("console-log-level");
980             break;
981         case WebInspector.ConsoleMessage.MessageLevel.Debug:
982             this._wrapperElement.classList.add("console-debug-level");
983             break;
984         case WebInspector.ConsoleMessage.MessageLevel.Warning:
985             this._wrapperElement.classList.add("console-warning-level");
986             break;
987         case WebInspector.ConsoleMessage.MessageLevel.Error:
988             this._wrapperElement.classList.add("console-error-level");
989             break;
990         case WebInspector.ConsoleMessage.MessageLevel.RevokedError:
991             this._wrapperElement.classList.add("console-revokedError-level");
992             break;
993         case WebInspector.ConsoleMessage.MessageLevel.Info:
994             this._wrapperElement.classList.add("console-info-level");
995             break;
996         }
998         this._wrapperElement.appendChild(this.contentElement());
999     },
1001     /**
1002      * @param {!TreeElement} parentTreeElement
1003      */
1004     _populateStackTraceTreeElement: function(parentTreeElement)
1005     {
1006         var target = this._target();
1007         if (!target)
1008             return;
1009         var content = WebInspector.DOMPresentationUtils.buildStackTracePreviewContents(target,
1010             this._linkifier, this._message.stackTrace, this._message.asyncStackTrace);
1011         var treeElement = new TreeElement(content);
1012         treeElement.selectable = false;
1013         parentTreeElement.appendChild(treeElement);
1014     },
1016     resetIncrementRepeatCount: function()
1017     {
1018         this._repeatCount = 1;
1019         if (!this._repeatCountElement)
1020             return;
1022         this._repeatCountElement.remove();
1023         delete this._repeatCountElement;
1024     },
1026     incrementRepeatCount: function()
1027     {
1028         this._repeatCount++;
1029         this._showRepeatCountElement();
1030     },
1032     _showRepeatCountElement: function()
1033     {
1034         if (!this._element)
1035             return;
1037         if (!this._repeatCountElement) {
1038             this._repeatCountElement = createElement("span");
1039             this._repeatCountElement.className = "bubble-repeat-count";
1041             this._element.insertBefore(this._repeatCountElement, this._element.firstChild);
1042             this._element.classList.add("repeated-message");
1043         }
1044         this._repeatCountElement.textContent = this._repeatCount;
1045     },
1047     /**
1048      * @override
1049      * @return {string}
1050      */
1051     toString: function()
1052     {
1053         var sourceString;
1054         switch (this._message.source) {
1055             case WebInspector.ConsoleMessage.MessageSource.XML:
1056                 sourceString = "XML";
1057                 break;
1058             case WebInspector.ConsoleMessage.MessageSource.JS:
1059                 sourceString = "JavaScript";
1060                 break;
1061             case WebInspector.ConsoleMessage.MessageSource.Network:
1062                 sourceString = "Network";
1063                 break;
1064             case WebInspector.ConsoleMessage.MessageSource.ConsoleAPI:
1065                 sourceString = "ConsoleAPI";
1066                 break;
1067             case WebInspector.ConsoleMessage.MessageSource.Storage:
1068                 sourceString = "Storage";
1069                 break;
1070             case WebInspector.ConsoleMessage.MessageSource.AppCache:
1071                 sourceString = "AppCache";
1072                 break;
1073             case WebInspector.ConsoleMessage.MessageSource.Rendering:
1074                 sourceString = "Rendering";
1075                 break;
1076             case WebInspector.ConsoleMessage.MessageSource.CSS:
1077                 sourceString = "CSS";
1078                 break;
1079             case WebInspector.ConsoleMessage.MessageSource.Security:
1080                 sourceString = "Security";
1081                 break;
1082             case WebInspector.ConsoleMessage.MessageSource.Other:
1083                 sourceString = "Other";
1084                 break;
1085         }
1087         var typeString;
1088         switch (this._message.type) {
1089             case WebInspector.ConsoleMessage.MessageType.Log:
1090                 typeString = "Log";
1091                 break;
1092             case WebInspector.ConsoleMessage.MessageType.Dir:
1093                 typeString = "Dir";
1094                 break;
1095             case WebInspector.ConsoleMessage.MessageType.DirXML:
1096                 typeString = "Dir XML";
1097                 break;
1098             case WebInspector.ConsoleMessage.MessageType.Trace:
1099                 typeString = "Trace";
1100                 break;
1101             case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
1102             case WebInspector.ConsoleMessage.MessageType.StartGroup:
1103                 typeString = "Start Group";
1104                 break;
1105             case WebInspector.ConsoleMessage.MessageType.EndGroup:
1106                 typeString = "End Group";
1107                 break;
1108             case WebInspector.ConsoleMessage.MessageType.Assert:
1109                 typeString = "Assert";
1110                 break;
1111             case WebInspector.ConsoleMessage.MessageType.Result:
1112                 typeString = "Result";
1113                 break;
1114             case WebInspector.ConsoleMessage.MessageType.Profile:
1115             case WebInspector.ConsoleMessage.MessageType.ProfileEnd:
1116                 typeString = "Profiling";
1117                 break;
1118         }
1120         var levelString;
1121         switch (this._message.level) {
1122             case WebInspector.ConsoleMessage.MessageLevel.Log:
1123                 levelString = "Log";
1124                 break;
1125             case WebInspector.ConsoleMessage.MessageLevel.Warning:
1126                 levelString = "Warning";
1127                 break;
1128             case WebInspector.ConsoleMessage.MessageLevel.Debug:
1129                 levelString = "Debug";
1130                 break;
1131             case WebInspector.ConsoleMessage.MessageLevel.Error:
1132                 levelString = "Error";
1133                 break;
1134             case WebInspector.ConsoleMessage.MessageLevel.RevokedError:
1135                 levelString = "RevokedError";
1136                 break;
1137             case WebInspector.ConsoleMessage.MessageLevel.Info:
1138                 levelString = "Info";
1139                 break;
1140         }
1142         return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage().textContent + "\n" + this._message.url + " line " + this._message.line;
1143     },
1145     get text()
1146     {
1147         return this._message.messageText;
1148     },
1150     /**
1151      * @param {?RegExp} regex
1152      */
1153     setSearchRegex: function(regex)
1154     {
1155         if (this._searchHiglightNodeChanges && this._searchHiglightNodeChanges.length)
1156             WebInspector.revertDomChanges(this._searchHiglightNodeChanges);
1157         this._searchRegex = regex;
1158         this._searchHighlightNodes = [];
1159         this._searchHiglightNodeChanges = [];
1160         if (!this._searchRegex)
1161             return;
1163         var text = this.searchableElement().deepTextContent();
1164         var match;
1165         this._searchRegex.lastIndex = 0;
1166         var sourceRanges = [];
1167         while ((match = this._searchRegex.exec(text)) && match[0])
1168             sourceRanges.push(new WebInspector.SourceRange(match.index, match[0].length));
1170         if (sourceRanges.length && this.searchableElement())
1171             this._searchHighlightNodes = WebInspector.highlightSearchResults(this.searchableElement(), sourceRanges, this._searchHiglightNodeChanges);
1172     },
1174     /**
1175      * @return {?RegExp}
1176      */
1177     searchRegex: function()
1178     {
1179         return this._searchRegex;
1180     },
1182     /**
1183      * @return {number}
1184      */
1185     searchCount: function()
1186     {
1187         return this._searchHighlightNodes.length;
1188     },
1190     /**
1191      * @return {!Element}
1192      */
1193     searchHighlightNode: function(index)
1194     {
1195         return this._searchHighlightNodes[index];
1196     },
1198     /**
1199      * @return {!Element}
1200      */
1201     searchableElement: function()
1202     {
1203         this.formattedMessage();
1204         return this._messageElement;
1205     },
1207     /**
1208      * @param {string} string
1209      * @return {?Element}
1210      */
1211     _tryFormatAsError: function(string)
1212     {
1213         /**
1214          * @param {string} prefix
1215          */
1216         function startsWith(prefix)
1217         {
1218             return string.startsWith(prefix);
1219         }
1221         var errorPrefixes = ["EvalError", "ReferenceError", "SyntaxError", "TypeError", "RangeError", "Error", "URIError"];
1222         var target = this._target();
1223         if (!target || !errorPrefixes.some(startsWith))
1224             return null;
1225         var debuggerModel = WebInspector.DebuggerModel.fromTarget(target);
1226         if (!debuggerModel)
1227             return null;
1229         var lines = string.split("\n");
1230         var links = [];
1231         var position = 0;
1232         for (var i = 0; i < lines.length; ++i) {
1233             position += i > 0 ? lines[i - 1].length + 1 : 0;
1234             var isCallFrameLine = /^\s*at\s/.test(lines[i]);
1235             if (!isCallFrameLine && links.length)
1236                 return null;
1238             if (!isCallFrameLine)
1239                 continue;
1241             var openBracketIndex = lines[i].indexOf("(");
1242             var closeBracketIndex = lines[i].indexOf(")");
1243             var hasOpenBracket = openBracketIndex !== -1;
1244             var hasCloseBracket = closeBracketIndex !== -1;
1246             if ((openBracketIndex > closeBracketIndex) ||  (hasOpenBracket ^ hasCloseBracket))
1247                 return null;
1249             var left = hasOpenBracket ? openBracketIndex + 1 : lines[i].indexOf("at") + 3;
1250             var right = hasOpenBracket ? closeBracketIndex : lines[i].length;
1251             var linkCandidate = lines[i].substring(left, right);
1252             var splitResult = WebInspector.ParsedURL.splitLineAndColumn(linkCandidate);
1253             if (!splitResult)
1254                 return null;
1256             var parsed = splitResult.url.asParsedURL();
1257             var url;
1258             if (parsed)
1259                 url = parsed.url;
1260             else if (debuggerModel.scriptsForSourceURL(splitResult.url).length)
1261                 url = splitResult.url;
1262             else if (splitResult.url === "<anonymous>")
1263                 continue;
1264             else
1265                 return null;
1267             links.push({url: url, positionLeft: position + left, positionRight: position + right, lineNumber: splitResult.lineNumber, columnNumber: splitResult.columnNumber});
1268         }
1270         if (!links.length)
1271             return null;
1273         var formattedResult = createElement("span");
1274         var start = 0;
1275         for (var i = 0; i < links.length; ++i) {
1276             formattedResult.appendChild(WebInspector.linkifyStringAsFragment(string.substring(start, links[i].positionLeft)));
1277             formattedResult.appendChild(this._linkifier.linkifyScriptLocation(target, null, links[i].url, links[i].lineNumber, links[i].columnNumber));
1278             start = links[i].positionRight;
1279         }
1281         if (start != string.length)
1282             formattedResult.appendChild(WebInspector.linkifyStringAsFragment(string.substring(start)));
1284         return formattedResult;
1285     }
1289  * @constructor
1290  * @extends {WebInspector.ConsoleViewMessage}
1291  * @param {!WebInspector.ConsoleMessage} consoleMessage
1292  * @param {!WebInspector.Linkifier} linkifier
1293  * @param {number} nestingLevel
1294  */
1295 WebInspector.ConsoleGroupViewMessage = function(consoleMessage, linkifier, nestingLevel)
1297     console.assert(consoleMessage.isGroupStartMessage());
1298     WebInspector.ConsoleViewMessage.call(this, consoleMessage, linkifier, nestingLevel);
1299     this.setCollapsed(consoleMessage.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed);
1302 WebInspector.ConsoleGroupViewMessage.prototype = {
1303     /**
1304      * @param {boolean} collapsed
1305      */
1306     setCollapsed: function(collapsed)
1307     {
1308         this._collapsed = collapsed;
1309         if (this._wrapperElement)
1310             this._wrapperElement.classList.toggle("collapsed", this._collapsed);
1311     },
1313     /**
1314      * @return {boolean}
1315      */
1316     collapsed: function()
1317     {
1318        return this._collapsed;
1319     },
1321     /**
1322      * @override
1323      * @return {!Element}
1324      */
1325     toMessageElement: function()
1326     {
1327         if (!this._wrapperElement) {
1328             WebInspector.ConsoleViewMessage.prototype.toMessageElement.call(this);
1329             this._wrapperElement.classList.toggle("collapsed", this._collapsed);
1330         }
1331         return this._wrapperElement;
1332     },
1334     __proto__: WebInspector.ConsoleViewMessage.prototype